summaryrefslogtreecommitdiffstats
path: root/web/react/utils
diff options
context:
space:
mode:
authorReed Garmsen <rgarmsen2295@gmail.com>2015-07-08 11:34:34 -0700
committerReed Garmsen <rgarmsen2295@gmail.com>2015-08-12 11:19:42 -0700
commitd293bc0b799a679cd27ed4ef6e818b0ca96998d9 (patch)
tree6b12bb2d447cbe9cf24f0ea66ec096e6233c87d7 /web/react/utils
parent95e6626f9f59b876479d8267b6c95105345e661d (diff)
downloadchat-d293bc0b799a679cd27ed4ef6e818b0ca96998d9.tar.gz
chat-d293bc0b799a679cd27ed4ef6e818b0ca96998d9.tar.bz2
chat-d293bc0b799a679cd27ed4ef6e818b0ca96998d9.zip
Implemented basic text formatting package using a modfied version of the marked js library. Supports *bold*, _italics_, and `code`
Diffstat (limited to 'web/react/utils')
-rw-r--r--web/react/utils/utils.jsx153
1 files changed, 126 insertions, 27 deletions
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 2214b6239..d4654d7d5 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -9,6 +9,7 @@ var ActionTypes = Constants.ActionTypes;
var AsyncClient = require('./async_client.jsx');
var client = require('./client.jsx');
var Autolinker = require('autolinker');
+var formatText = require('../../static/js/marked/lib/marked.js');
module.exports.isEmail = function(email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
@@ -95,9 +96,8 @@ module.exports.getCookie = function(name) {
if (parts.length == 2) return parts.pop().split(";").shift();
}
-
module.exports.notifyMe = function(title, body, channel) {
- if ("Notification" in window && Notification.permission !== 'denied') {
+ if ("Notification" in window && Notification.permission !== 'denied') {
Notification.requestPermission(function (permission) {
if (Notification.permission !== permission) {
Notification.permission = permission;
@@ -385,11 +385,53 @@ module.exports.searchForTerm = function(term) {
});
}
+/* Options:
+ - disable: Parses out *'s and other format specifiers in the text, but doesn't convert to html
+*/
+module.exports.customMarkedRenderer = function(options) {
+ var customTextRenderer = new formatText.Renderer();
+ if (options && options.disable) {
+ customTextRenderer.paragraph = function(text) {
+ return text + ' ';
+ };
+ customTextRenderer.strong = function(text) {
+ return text;
+ };
+ customTextRenderer.em = function(text) {
+ return text;
+ };
+ customTextRenderer.codespan = function(code) {
+ return code;
+ };
+ customTextRenderer.link = function(href, title, text) {
+ return href;
+ };
+ customTextRenderer.image = function(href, title, text) {
+ return href;
+ };
+ } else {
+ customTextRenderer.link = function(href, title, text) {
+ return href;
+ };
+ customTextRenderer.image = function(href, title, text) {
+ return href;
+ };
+ }
+ return customTextRenderer;
+};
+
var oldExplicitMentionRegex = /(?:<mention>)([\s\S]*?)(?:<\/mention>)/g;
-var puncStartRegex = /^((?![@#])\W)+/g;
-var puncEndRegex = /(\W)+$/g;
+var puncStartRegex = /^((?![@#])[^A-Za-z0-9_<>])+/g;
+var puncEndRegex = /([^A-Za-z0-9_<>])+$/g;
+var startTagRegex = /(<\s*\w.*?>)+/g;
+var endTagRegex = /(<\s*\/\s*\w\s*.*?>|<\s*br\s*>)+/g;
module.exports.textToJsx = function(text, options) {
+ var useTextFormatting = config.AllowTextFormatting && (!options || !options.noTextFormatting);
+
+ if (useTextFormatting) {
+ text = formatText(text, {sanitize: true, mangle: false, gfm: true, breaks: true, tables: false, smartypants: true, renderer: module.exports.customMarkedRenderer()});
+ }
if (options && options['singleline']) {
var repRegex = new RegExp("\n", "g");
@@ -398,7 +440,7 @@ module.exports.textToJsx = function(text, options) {
var searchTerm = ""
if (options && options['searchTerm']) {
- searchTerm = options['searchTerm'].toLowerCase()
+ searchTerm = options['searchTerm'].toLowerCase();
}
var mentionClass = "mention-highlight";
@@ -407,6 +449,8 @@ module.exports.textToJsx = function(text, options) {
}
var inner = [];
+ var codeFlag = false;
+ var codeString = '';
// Function specific regex
var hashRegex = /^href="#[^"]+"|(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
@@ -420,16 +464,45 @@ module.exports.textToJsx = function(text, options) {
var highlightSearchClass = "";
for (var z = 0; z < words.length; z++) {
var word = words[z];
- var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
+ var trimWord;
+ if (useTextFormatting) {
+ trimWord = word.replace(endTagRegex, '').replace(startTagRegex, '').replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
+ } else {
+ trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
+ }
var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here
var explicitMention = mentionRegex.exec(trimWord);
- if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != "") {
+ var prefix;
+ var suffix;
+ var prefixSpan;
+ var suffixSpan;
+ if (useTextFormatting) {
+ prefix = (word.match(startTagRegex) ? word.match(startTagRegex) : "") + (word.replace(startTagRegex, '').match(puncStartRegex) ? word.replace(startTagRegex, '').match(puncStartRegex) : "");
+ suffix = (word.match(endTagRegex) ? word.match(endTagRegex) : "") + (word.replace(endTagRegex, '').match(puncEndRegex) ? word.replace(endTagRegex, '').match(puncEndRegex) : "");
+ prefixSpan = prefix ? (<span key={word+i+z+"pre_span"}><span dangerouslySetInnerHTML={{__html: prefix}} /></span>) : null;
+ suffixSpan = suffix ? (<span key={word+i+z+"suf_span"}><span dangerouslySetInnerHTML={{__html: suffix}} /> </span>) : (<span key={word+i+z+"suf_span"}> </span>);
+ } else {
+ prefix = word.match(puncStartRegex);
+ suffix = word.match(puncEndRegex);
+ prefixSpan = prefix ? (<span key={word+i+z+"pre_span"}>{prefix}</span>) : null;
+ suffixSpan = suffix ? (<span key={word+i+z+"suf_span"}>{suffix} </span>) : (<span key={word+i+z+"suf_span"}> </span>);
+ }
+ if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != "") {
highlightSearchClass = " search-highlight";
}
- if (explicitMention &&
+ if (useTextFormatting && (codeFlag || word.indexOf('<code>') !== -1)) {
+ codeString += word + ' ';
+ codeFlag = true;
+
+ if (word.indexOf('</code>') !== -1) {
+ inner.push(<span key={word+i+z+"_span"} className={highlightSearchClass} dangerouslySetInnerHTML={{__html: codeString}} />);
+ codeString = '';
+ codeFlag = false;
+ }
+ } else if (explicitMention &&
(UserStore.getProfileByUsername(explicitMention[1]) ||
Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
{
@@ -437,60 +510,86 @@ module.exports.textToJsx = function(text, options) {
// do both a non-case sensitive and case senstive check
var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : "";
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
-
if (searchTerm === name) {
highlightSearchClass = " search-highlight";
}
- inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
+ if (useTextFormatting) {
+ prefixSpan ? inner.push(prefixSpan) : null;
+ inner.push(<span key={word+i+z+"word_span"}><a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>{"@" + name}</a></span>);
+ suffixSpan ? inner.push(suffixSpan) : null;
+ }
+ else
+ inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
} else if (testUrlMatch(word).length) {
var match = testUrlMatch(word)[0];
var link = match.link;
- var prefix = word.substring(0,word.indexOf(match.text));
- var suffix = word.substring(word.indexOf(match.text)+match.text.length);
-
- inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix} </span>);
+ prefix = word.substring(0,word.indexOf(match.text));
+ suffix = word.substring(word.indexOf(match.text)+match.text.length);
+ prefixSpan = prefix ? (<span key={word+i+z+"pre_span"}><span dangerouslySetInnerHTML={{__html: prefix}} /></span>) : null;
+ suffixSpan = suffix ? (<span key={word+i+z+"suf_span"}><span dangerouslySetInnerHTML={{__html: suffix}} /> </span>) : <span key={word+i+z+"suf_span"}> </span>;
+ if (useTextFormatting) {
+ prefixSpan ? inner.push(prefixSpan) : null;
+ inner.push(<span key={word+i+z+"_span"}><a key={name+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a></span>);
+ suffixSpan ? inner.push(suffixSpan) : null;
+ }
+ else
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix} </span>);
} else if (trimWord.match(hashRegex)) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : "";
if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
highlightSearchClass = " search-highlight";
}
- inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
+ if (useTextFormatting) {
+ prefixSpan ? inner.push(prefixSpan) : null;
+ inner.push(<span key={word+i+z+"_span"}><a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a></span>);
+ suffixSpan ? inner.push(suffixSpan) : null;
+ }
+ else
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
} else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
-
if (trimWord.charAt(0) === '@') {
if (searchTerm === trimWord.substring(1).toLowerCase()) {
highlightSearchClass = " search-highlight";
}
- inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a>{suffix} </span>);
+ if (useTextFormatting) {
+ prefixSpan ? inner.push(prefixSpan) : null;
+ inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}><a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a></span>);
+ suffixSpan ? inner.push(suffixSpan) : null;
+ }
+ else
+ inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a>{suffix} </span>);
} else {
- inner.push(<span key={word+i+z+"_span"}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
+ if (useTextFormatting) {
+ prefixSpan ? inner.push(prefixSpan) : null;
+ inner.push(<span key={word+i+z+"_span"}><span className={mentionClass + highlightSearchClass}>{trimWord}</span></span>);
+ suffixSpan ? inner.push(suffixSpan) : null;
+ }
+ else
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
}
} else if (word === "") {
// if word is empty dont include a span
} else {
- inner.push(<span key={word+i+z+"_span"}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
+ if (useTextFormatting)
+ inner.push(<span key={word+i+z+"_span"} className={highlightSearchClass} dangerouslySetInnerHTML={{__html: word + ' '}} />);
+ else
+ inner.push(<span key={word+i+z+"_span"}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
}
highlightSearchClass = "";
}
- if (i != lines.length-1)
+ if (!useTextFormatting && i != lines.length-1)
inner.push(<br key={"br_"+i+z}/>);
}
return inner;
-}
+};
module.exports.getFileType = function(ext) {
ext = ext.toLowerCase();