File: /var/www/intranet.kauko.lt/wp-content/plugins/wise-chat/assets/js/src/utils/html-renderer.jsx
import React from "react";
import ImageViewer from "utils/image-viewer";
import { Base64 } from 'js-base64';
/**
* Renders plain text with shortcodes to HTML.
*/
export default class HtmlRenderer {
get PARSERS() {
const emoticonsConfig = this.configuration.interface.input.emoticons;
const soundsConfig = this.configuration.interface.input.sounds;
return [{
regExp: /\[link src="(.+?)"(\s+name="(.+?)")?]/g,
callback: (match, i, parserIndex) => {
let url = match[1];
if (this.configuration.interface.message.links) {
let finalUrl = (!url.match(/^https|http|ftp|mailto:/) ? "http://" : '') + url;
if (match[2]) {
url = match[3];
}
return <a key={ this.currentKey++ } href={ finalUrl } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>{ url }</a>;
} else {
return url;
}
}
}, {
regExp: /\[sound id="(.+?)" src="(.+?)" name-org="(.+?)"]/g,
callback: (match, i, parserIndex) => {
let attachmentSrc = match[2];
if (soundsConfig.enabled) {
return <audio key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
<source src={ attachmentSrc } type="audio/mpeg" />
Your browser does not support the audio element.
</audio>;
} else {
return '';
}
}
}, {
regExp: /\[attachment id="(.+?)" src="(.+?)" name-org="(.+?)"]/g,
callback: (match, i, parserIndex) => {
let attachmentSrc = match[2];
let linkBody = match[3];
const videoPlayer = this.configuration.interface.message.attachmentsVideoPlayer;
const soundPlayer = this.configuration.interface.message.attachmentsSoundPlayer;
if (this.configuration.interface.message.attachments) {
if (videoPlayer && this.isFile(attachmentSrc, 'mp4')) {
return <video key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
<source src={attachmentSrc} type="video/mp4"/>
Your browser does not support the video tag.
</video>
} else if (videoPlayer && this.isFile(attachmentSrc, 'webm')) {
return <video key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
<source src={ attachmentSrc } type="video/webm" />
Your browser does not support the video tag.
</video>
} else if (soundPlayer && this.isFile(attachmentSrc, 'mp3')) {
return <audio key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
<source src={ attachmentSrc } type="audio/mpeg" />
Your browser does not support the audio element.
</audio>;
} else if (soundPlayer && this.isFile(attachmentSrc, 'wav')) {
return <audio key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
<source src={ attachmentSrc } type="audio/wav" />
Your browser does not support the audio element.
</audio>;
} else {
return <a key={this.currentKey++} href={attachmentSrc} target="_blank"
rel="noopener noreferrer nofollow"
data-org={Base64.encode(match[0])}>{linkBody}</a>;
}
} else if (this.configuration.interface.message.links) {
let finalUrl = (!attachmentSrc.match(/^https|http|ftp:/) ? "http://" : '') + attachmentSrc;
return <a key={ this.currentKey++ } href={ finalUrl } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>{ linkBody }</a>;
} else {
return linkBody;
}
}
}, {
regExp: /\[img id="(\d+)" src="(.+?)" src-th="(.+?)" src-org="(.+?)"]/g,
callback: (match, i, parserIndex) => {
let imageSrc = match[2];
let imageThumbnailSrc = match[3];
let imageOrgSrc = match[4];
if (this.configuration.interface.message.images) {
const viewer = this.configuration.interface.message.imagesViewer;
return <a
key={ this.currentKey++ }
href={ imageSrc }
target={ viewer === 'browser_window' ? "_blank" : undefined }
data-lightbox={ viewer === 'lightbox' ? "wise_chat" : undefined }
className="wcFunctional"
rel={ viewer === 'lightbox' ? "lightbox[wise_chat]" : undefined }
data-org={ Base64.encode(match[0]) }
onClick={ e => viewer === 'internal' ? this.handleImagePreview(e, imageSrc) : undefined }
>
<img src={ imageThumbnailSrc } className="wcImage wcFunctional" alt="Chat image"/>
</a>;
} else if (this.configuration.interface.message.links) {
if (imageOrgSrc === '_') {
imageOrgSrc = imageSrc;
}
let finalUrl = (!imageOrgSrc.match(/^https|http|ftp:/) ? "http://" : '') + imageOrgSrc;
return <a key={this.currentKey++} href={finalUrl} target="_blank"
rel="noopener noreferrer nofollow" data-org={Base64.encode(match[0])}>{imageOrgSrc}</a>;
} else {
return imageOrgSrc !== '_' ? imageOrgSrc : imageSrc;
}
}
}, {
regExp: /\[img src="(.+?)"(\s+className="(.+?)")?]/g,
callback: (match, i, parserIndex) => {
let imageSrc = match[1];
let className = match[2] ? match[3] : '';
if (this.configuration.interface.message.images) {
return <img key={ this.currentKey++ } src={ imageSrc } className={ `wcImage wcFunctional ${className}` } alt="Chat image" data-org={ Base64.encode(match[0]) } />;
} else {
return imageSrc;
}
}
}, {
regExp: /\[span className="(.+?)" content="(.*?)"]/g,
callback: (match, i, parserIndex) => {
let className = match[1];
let content = match[2];
return <span key={ this.currentKey++ } className={ className } data-org={ Base64.encode(match[0]) }>{ content }</span>;
}
}, {
regExp: /\[youtube movie-id="(.+?)" src-org="(.+?)"]/g,
callback: (match, i, parserIndex) => {
let movieId = match[1];
let srcOrg = match[2];
if (this.configuration.interface.message.yt && movieId.length > 0) {
return <iframe
key={ this.currentKey++ }
width={ this.configuration.interface.message.ytWidth }
height={ this.configuration.interface.message.ytHeight }
className="wcVideoPlayer"
src={ "https://www.youtube.com/embed/" + movieId }
frameBorder="0" allowFullScreen
data-org={ Base64.encode(match[0]) }
/>;
} else if (this.configuration.interface.message.links && srcOrg.length > 0) {
let finalUrl = (!srcOrg.match(/^https|http|ftp:/) ? "http://" : '') + srcOrg;
return <a key={ this.currentKey++ } href={ finalUrl } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>{ srcOrg }</a>;
} else if (srcOrg.length > 0) {
return srcOrg;
}
}
}, {
regExp: /#([^\s#\[\]\\<&;]+)/g,
callback: (match, i, parserIndex) => {
let tag = match[1];
if (this.configuration.interface.message.tt) {
return <React.Fragment key={ this.currentKey++ }><a href={ 'https://twitter.com/hashtag/' + tag + '?src=hash' } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>#{ tag }</a></React.Fragment>;
} else {
return match[0];
}
}
}, {
regExp: /\[emoticon set="(.+?)" index="(.+?)" size="(\d+)"]/g,
callback: (match, i, parserIndex) => {
let setId = match[1];
let index = match[2];
let size = match[3];
if (emoticonsConfig.set) {
return <img key={ this.currentKey++ }
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt={'Emoticon set #' + setId + ' ' + size + 'px #' + index }
className={'wcFunctional wcEmoticon bg-emot_' + setId + '_' + size + '_' + index } data-org={ Base64.encode(match[0]) } />;
} else {
return <span key={ this.currentKey++ } />;
}
}
}, {
regExp: /\[emoticon custom="(\d+)"]/g,
callback: (match, i, parserIndex) => {
let emoticonId = parseInt(match[1]);
if (emoticonsConfig.custom) {
const customEmoticon = emoticonsConfig.custom.find( emoticon => emoticon.id === emoticonId );
if (customEmoticon) {
return <img key={ this.currentKey++ }
src={ customEmoticon.url }
alt={'Emoticon ' + emoticonId }
className={'wcFunctional wcEmoticon' } data-org={ Base64.encode(match[0]) } />;
} else {
return <span key={ this.currentKey++ } />;
}
} else {
return <span key={ this.currentKey++ } />;
}
}
}, {
regExp: /\[video-call channelId="([^"]+)"]/g,
callback: (match, i, parserIndex) => {
let channelId = match[1];
if (this.rendererConfiguration.onShortcodeRender) {
return this.rendererConfiguration.onShortcodeRender('video-call', { channelId: channelId }, this.currentKey++);
} else {
return <span key={ this.currentKey++ } />;
}
}
}, {
regExp: /\n/g,
callback: (match, i, parserIndex) => {
return <br key={ this.currentKey++ } />;
}
}
];
}
/**
* @param {Object} configuration
* @param {Object} rendererConfiguration
*/
constructor(configuration, rendererConfiguration = {}) {
this.configuration = configuration;
this.rendererConfiguration = rendererConfiguration;
this.currentKey = 0;
this.imageViewer = new ImageViewer();
this.parse = this.parse.bind(this);
this.handleImagePreview = this.handleImagePreview.bind(this);
}
toHtml(mixed) {
return this.parse(mixed, 0);
}
escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
/**
*
* @param {String} src Path or URL to file
* @param {String} type File's extension
* @returns {Boolean}
*/
isFile(src, type) {
return src.toLowerCase().match(new RegExp('\.' + type + '$')) !== null;
}
handleImagePreview(e, data) {
e.preventDefault();
this.imageViewer.show(data);
}
/**
* @param {String} string
* @param {Number} parserIndex
* @returns Array
*/
parseString(string, parserIndex) {
if (this.PARSERS.length <= parserIndex) {
return [string];
}
if (string === '') {
return [string];
}
const matches = [...string.matchAll(this.PARSERS[parserIndex].regExp)];
if (matches.length === 0) {
return this.parseString(string, parserIndex + 1);
}
const elements = [];
let lastIndex = 0;
matches.forEach((match, i) => {
// parse preceding text if there is any:
if (match.index > lastIndex) {
elements.push(...this.parseString(string.substring(lastIndex, match.index), parserIndex + 1));
}
const callBackResult = this.PARSERS[parserIndex].callback(match, i, parserIndex);
elements.push(callBackResult);
lastIndex = match.index + match[0].length;
});
// parse the remaining text if there is any:
if (string.length > lastIndex) {
elements.push(...this.parseString(string.substring(lastIndex), parserIndex + 1));
}
return elements;
}
parse(children, key) {
if (typeof children === 'string') {
return this.parseString(children, 0);
} else if (React.isValidElement(children) && (children.type !== 'a') && (children.type !== 'button')) {
return React.cloneElement(children, {key: key}, this.parse(children.props.children));
} else if (Array.isArray(children)) {
return children.map((child, i) => this.parse(child, i));
}
return children;
}
}