|
|
@@ -1,17 +1,21 @@
|
|
|
-// FIXME eol and quote ( > ) with quote level
|
|
|
|
|
|
-/** @type {function(string):string} */
|
|
|
var formatText = (function() {
|
|
|
- /**
|
|
|
- * @param {string} c
|
|
|
- * @return {boolean}
|
|
|
- **/
|
|
|
- function isAlphadec(c) {
|
|
|
- return ((c >= 'A' && c <= 'Z') ||
|
|
|
- (c >= 'a' && c <= 'z') ||
|
|
|
- (c >= '0' && c <= '9') ||
|
|
|
- "àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇߨøÅ寿œ".indexOf(c) !== -1);
|
|
|
- }
|
|
|
+ /** @type {string} */
|
|
|
+ var text;
|
|
|
+
|
|
|
+ /** @type {MsgBranch} */
|
|
|
+ var root;
|
|
|
+
|
|
|
+ var opts = {
|
|
|
+ /** @type {Array<string>} */
|
|
|
+ highlights: [],
|
|
|
+
|
|
|
+ /** @type {function(string):string} */
|
|
|
+ emojiFormatFunction: identity,
|
|
|
+
|
|
|
+ /** @type {function(string):string} */
|
|
|
+ textFilter: identity
|
|
|
+ };
|
|
|
|
|
|
/**
|
|
|
* @constructor
|
|
|
@@ -27,7 +31,7 @@ var formatText = (function() {
|
|
|
|
|
|
/**
|
|
|
* @constructor
|
|
|
- * @param {MsgBranch|MsgTree} _parent
|
|
|
+ * @param {MsgBranch} _parent
|
|
|
* @param {number} triggerIndex
|
|
|
* @param {string=} trigger
|
|
|
*/
|
|
|
@@ -71,14 +75,23 @@ var formatText = (function() {
|
|
|
/** @type {boolean} */
|
|
|
this.isEol = this.trigger === '\n';
|
|
|
|
|
|
- /** @const @type {MsgBranch|MsgTree} */
|
|
|
+ /** @type {boolean} */
|
|
|
+ this.isHighlight = trigger !== undefined && opts.highlights.indexOf(trigger) !== -1;
|
|
|
+
|
|
|
+ /** @const @type {MsgBranch} */
|
|
|
this._parent = _parent;
|
|
|
|
|
|
/** @type {MsgBranch|null} */
|
|
|
this.prevTwin = null;
|
|
|
|
|
|
/** @type {boolean|number} */
|
|
|
- this.terminated = this.isEol ? triggerIndex : false;
|
|
|
+ this.terminated = this.isEol || this.isHighlight ? (triggerIndex +trigger.length -1) : false;
|
|
|
+
|
|
|
+ if (this.isHighlight) {
|
|
|
+ this.lastNode = new MsgTextLeaf(this);
|
|
|
+ this.subNodes.push(this.lastNode);
|
|
|
+ this.lastNode.text = /** @type {string} */ (trigger);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/** @return {boolean} */
|
|
|
@@ -106,6 +119,11 @@ var formatText = (function() {
|
|
|
return (this.isEmoji && !!this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsEmoji());
|
|
|
};
|
|
|
|
|
|
+ /** @return {boolean} */
|
|
|
+ MsgBranch.prototype.checkIsHighlight = function() {
|
|
|
+ return (this.isHighlight && !!this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsHighlight());
|
|
|
+ };
|
|
|
+
|
|
|
/** @return {boolean} */
|
|
|
MsgBranch.prototype.checkIsCode = function() {
|
|
|
return (this.isCode && !!this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsCode());
|
|
|
@@ -138,22 +156,26 @@ var formatText = (function() {
|
|
|
* | "bold continue"
|
|
|
* and this function will return a "bold" branch
|
|
|
*
|
|
|
- * @param {string} str
|
|
|
* @param {number} i
|
|
|
* @return {boolean|MsgBranch}
|
|
|
**/
|
|
|
- MsgBranch.prototype.finishWith = function(str, i) {
|
|
|
- if (this.trigger === '<' && str[i] === '>')
|
|
|
+ MsgBranch.prototype.finishWith = function(i) {
|
|
|
+ var prevIsAlphadec = isAlphadec(text[i -1]);
|
|
|
+ if (this.trigger === '<' && text[i] === '>' && prevIsAlphadec)
|
|
|
return true;
|
|
|
|
|
|
- if (str.substr(i, this.trigger.length) === this.trigger) {
|
|
|
+ if (!this.isQuote && text.substr(i, this.trigger.length) === this.trigger) {
|
|
|
+ if (!prevIsAlphadec && (this.isBold || this.isItalic || this.isStrike)) {
|
|
|
+ // previous char is not compatible with finishing now
|
|
|
+ return false;
|
|
|
+ }
|
|
|
if (this.lastNode && this.containsUnterminatedBranch()) // sub nodes contains at least one branch wich should be replicated
|
|
|
return this.lastNode.makeNewBranchFromThis();
|
|
|
else if (this.hasContent()) // self or any prevTwin has Content
|
|
|
return true;
|
|
|
// else no content, should not be finished now
|
|
|
}
|
|
|
- if (str[i] === '\n' && this.isEol)
|
|
|
+ if (text[i] === '\n' && this.isQuote)
|
|
|
return true;
|
|
|
return false;
|
|
|
};
|
|
|
@@ -188,15 +210,13 @@ var formatText = (function() {
|
|
|
|
|
|
/**
|
|
|
* Check if this token is compatible with this branch
|
|
|
- * @param {string} str
|
|
|
* @param {number} i
|
|
|
* @return {boolean}
|
|
|
**/
|
|
|
- MsgBranch.prototype.isAcceptable = function(str, i) {
|
|
|
- if (this.isEmoji && (str[i] === ' ' || str[i] === '\t'))
|
|
|
+ MsgBranch.prototype.isAcceptable = function(i) {
|
|
|
+ if (this.isEmoji && (text[i] === ' ' || text[i] === '\t'))
|
|
|
return false;
|
|
|
- if ((this.isEmoji || this.isLink || this.isBold || this.isItalic || this.isStrike || this.isCode) &&
|
|
|
- str[i] === '\n')
|
|
|
+ if ((this.isEmoji || this.isLink || this.isBold || this.isItalic || this.isStrike || this.isCode) && text[i] === '\n')
|
|
|
return false;
|
|
|
return true;
|
|
|
};
|
|
|
@@ -204,60 +224,102 @@ var formatText = (function() {
|
|
|
/**
|
|
|
* Check if str[i] is a trigger for a new node
|
|
|
* if true, return the trigger
|
|
|
- * @param {string} str
|
|
|
* @param {number} i
|
|
|
* @return {string|null}
|
|
|
**/
|
|
|
- MsgBranch.prototype.isNewToken = function(str, i) {
|
|
|
+ MsgBranch.prototype.isNewToken = function(i) {
|
|
|
if (this.isCode || this.isEmoji || this.isCodeBlock)
|
|
|
return null;
|
|
|
- if (!this.lastNode || this.lastNode.terminated || this.lastNode instanceof MsgTextLeaf) {
|
|
|
- var nextIsAlphadec = isAlphadec(str[i +1])
|
|
|
- ,prevIsAlphadec = isAlphadec(str[i -1]);
|
|
|
+ var nextIsAlphadec = isAlphadec(text[i +1]);
|
|
|
|
|
|
- if (str.substr(i, 3) === '```')
|
|
|
- return '```';
|
|
|
- if (str.substr(i, 4) === ">") // FIXME AND begining of line OR begining of QUOTED
|
|
|
+ if (text.substr(i, 3) === '```')
|
|
|
+ return '```';
|
|
|
+ if (isBeginingOfLine()) {
|
|
|
+ if (text.substr(i, 4) === ">")
|
|
|
return ">";
|
|
|
- if (str[i] === '>') // FIXME AND begining of line OR begining of QUOTED
|
|
|
- return str[i];
|
|
|
- if (['`', '\n'].indexOf(str[i]) !== -1)
|
|
|
- return str[i];
|
|
|
- if (['*', '~', '-', '_' ].indexOf(str[i]) !== -1 && (nextIsAlphadec || ['*', '~', '-', '_', '<', '&'].indexOf(str[i+1]) !== -1))
|
|
|
- return str[i];
|
|
|
- if ([':', '<'].indexOf(str[i]) !== -1 && nextIsAlphadec)
|
|
|
- return str[i];
|
|
|
+ if (text[i] === '>')
|
|
|
+ return text[i];
|
|
|
+ }
|
|
|
+ if (['`', '\n'].indexOf(text[i]) !== -1)
|
|
|
+ return text[i];
|
|
|
+ if (['*', '~', '-', '_' ].indexOf(text[i]) !== -1 && (nextIsAlphadec || ['*', '~', '-', '_', '<', '&'].indexOf(text[i+1]) !== -1))
|
|
|
+ return text[i];
|
|
|
+ if ([':', '<'].indexOf(text[i]) !== -1 && nextIsAlphadec)
|
|
|
+ return text[i];
|
|
|
+
|
|
|
+ for (var highlightIndex =0, nbHighlight =opts.highlights.length; highlightIndex < nbHighlight; highlightIndex++) {
|
|
|
+ var highlight = opts.highlights[highlightIndex];
|
|
|
+
|
|
|
+ // TODO compare with collators
|
|
|
+ if (text.substr(i, highlight.length) === highlight)
|
|
|
+ return highlight;
|
|
|
}
|
|
|
return null;
|
|
|
};
|
|
|
|
|
|
+ /** @return {boolean|undefined} */
|
|
|
+ MsgTextLeaf.prototype.isBeginingOfLine = function() {
|
|
|
+ if (this.text.trim() !== '')
|
|
|
+ return false;
|
|
|
+ return undefined;
|
|
|
+ };
|
|
|
+
|
|
|
+ /** @return {boolean|undefined} */
|
|
|
+ MsgBranch.prototype.isBeginingOfLine = function() {
|
|
|
+ for (var i = this.subNodes.length -1; i >= 0; i--) {
|
|
|
+ var isNewLine = this.subNodes[i].isBeginingOfLine();
|
|
|
+ if (isNewLine !== undefined)
|
|
|
+ return isNewLine;
|
|
|
+ }
|
|
|
+ if (this.isEol || this.isQuote)
|
|
|
+ return true;
|
|
|
+ return undefined;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param {string} c
|
|
|
+ * @return {boolean}
|
|
|
+ **/
|
|
|
+ function isAlphadec(c) {
|
|
|
+ return ((c >= 'A' && c <= 'Z') ||
|
|
|
+ (c >= 'a' && c <= 'z') ||
|
|
|
+ (c >= '0' && c <= '9') ||
|
|
|
+ "àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇߨøÅ寿œ".indexOf(c) !== -1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @return {boolean} */
|
|
|
+ function isBeginingOfLine() {
|
|
|
+ var isNewLine = root.isBeginingOfLine();
|
|
|
+ return isNewLine === undefined ? true : isNewLine;
|
|
|
+ };
|
|
|
+
|
|
|
/** @return {number} */
|
|
|
- MsgTextLeaf.prototype.addChar = function(str, i) {
|
|
|
- this.text += str[i];
|
|
|
+ MsgTextLeaf.prototype.addChar = function(i) {
|
|
|
+ this.text += text[i];
|
|
|
return 1;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Parse next char
|
|
|
- * @param {string} str
|
|
|
* @param {number} i
|
|
|
* @return {number}
|
|
|
**/
|
|
|
- MsgBranch.prototype.addChar = function(str, i) {
|
|
|
- var isFinished = (this.lastNode && this.lastNode.finishWith) ? this.lastNode.finishWith(str, i) : null;
|
|
|
+ MsgBranch.prototype.addChar = function(i) {
|
|
|
+ var isFinished = (this.lastNode && !this.lastNode.terminated && this.lastNode.finishWith) ? this.lastNode.finishWith(i) : null;
|
|
|
|
|
|
if (isFinished) {
|
|
|
var lastTriggerLen = this.lastNode.trigger.length;
|
|
|
- this.lastNode.terminate(this.lastNode.triggerIndex, i);
|
|
|
+ this.lastNode.terminate(i);
|
|
|
if (isFinished instanceof MsgBranch) {
|
|
|
this.lastNode = isFinished;
|
|
|
this.subNodes.push(isFinished);
|
|
|
}
|
|
|
return lastTriggerLen;
|
|
|
} else {
|
|
|
- if (!this.lastNode || this.lastNode.terminated || this.lastNode instanceof MsgTextLeaf || this.lastNode.isAcceptable(str, i)) {
|
|
|
- var isNewToken = this.isNewToken(str, i);
|
|
|
+ if (!this.lastNode || this.lastNode.terminated || this.lastNode instanceof MsgTextLeaf || this.lastNode.isAcceptable(i)) {
|
|
|
+ var isNewToken = this.isNewToken(i);
|
|
|
if (isNewToken) {
|
|
|
+ // FIXME if this is a quote and newToken is also a quote, trim lastnode (and remove it if necessary)
|
|
|
this.lastNode = new MsgBranch(this, i, isNewToken);
|
|
|
this.subNodes.push(this.lastNode);
|
|
|
return this.lastNode.trigger.length;
|
|
|
@@ -266,17 +328,17 @@ var formatText = (function() {
|
|
|
this.lastNode = new MsgTextLeaf(this);
|
|
|
this.subNodes.push(this.lastNode);
|
|
|
}
|
|
|
- return this.lastNode.addChar(str, i);
|
|
|
+ return this.lastNode.addChar(i);
|
|
|
}
|
|
|
} else {
|
|
|
// last branch child is not compatible with this token.
|
|
|
// So, lastBranch is not a branch
|
|
|
var revertTo = this.lastNode.triggerIndex +1;
|
|
|
|
|
|
- this.getRoot().cancelTerminate(this.lastNode.triggerIndex);
|
|
|
+ root.cancelTerminate(this.lastNode.triggerIndex);
|
|
|
// Add a new "escaped" node to replace trigger
|
|
|
this.lastNode = new MsgTextLeaf(this);
|
|
|
- this.lastNode.addChar(str, revertTo -1); // Add "escaped" trigger
|
|
|
+ this.lastNode.addChar(revertTo -1); // Add "escaped" trigger
|
|
|
this.subNodes.pop();
|
|
|
this.subNodes.push(this.lastNode);
|
|
|
|
|
|
@@ -286,7 +348,7 @@ var formatText = (function() {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- MsgBranch.prototype.terminate = function(triggerIndex, terminateAtIndex) {
|
|
|
+ MsgBranch.prototype.terminate = function(terminateAtIndex) {
|
|
|
var _this = this;
|
|
|
while (_this) {
|
|
|
_this.terminated = terminateAtIndex;
|
|
|
@@ -294,13 +356,6 @@ var formatText = (function() {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- MsgBranch.prototype.getRoot = function() {
|
|
|
- var branch = this;
|
|
|
- while (branch._parent && branch._parent instanceof MsgBranch)
|
|
|
- branch = branch._parent;
|
|
|
- return branch;
|
|
|
- };
|
|
|
-
|
|
|
/**
|
|
|
* @param {number} unTerminatedIndex
|
|
|
**/
|
|
|
@@ -318,7 +373,17 @@ var formatText = (function() {
|
|
|
* @return {string}
|
|
|
**/
|
|
|
MsgTextLeaf.prototype.innerHTML = function() {
|
|
|
- return this._parent.checkIsEmoji() ? (':' +this.text +':') : (this.text);
|
|
|
+ if (this._parent.checkIsEmoji()) {
|
|
|
+ var _parent = this._parent;
|
|
|
+ while (_parent && !_parent.isEmoji)
|
|
|
+ _parent = _parent._parent;
|
|
|
+ if (_parent)
|
|
|
+ return opts.emojiFormatFunction(_parent.trigger +this.text +_parent.trigger);
|
|
|
+ return opts.emojiFormatFunction(this.text);
|
|
|
+ }
|
|
|
+ if (this._parent.checkIsCodeBlock())
|
|
|
+ return this.text.replace(/\n/g, '<br/>'); // TODO syntax highlight
|
|
|
+ return opts.textFilter(this.text);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -329,8 +394,6 @@ var formatText = (function() {
|
|
|
,classList = [];
|
|
|
|
|
|
if (this._parent.checkIsCodeBlock()) {
|
|
|
- // TODO syntax highlight
|
|
|
- // TODO line breaks
|
|
|
classList.push('codeblock');
|
|
|
} else if (this._parent.checkIsCode()) {
|
|
|
classList.push('code');
|
|
|
@@ -344,7 +407,9 @@ var formatText = (function() {
|
|
|
if (this._parent.checkIsStrike())
|
|
|
classList.push('strike');
|
|
|
if (this._parent.checkIsEmoji())
|
|
|
- classList.push('emoji'); // FIXME emoji
|
|
|
+ classList.push('emoji');
|
|
|
+ if (this._parent.checkIsHighlight())
|
|
|
+ classList.push('highlight');
|
|
|
}
|
|
|
return '<' +tagName +(classList.length ? ' class="' +classList.join(' ') +'"' : '') +'>' +this.innerHTML() +'</' +tagName +'>';
|
|
|
};
|
|
|
@@ -366,39 +431,17 @@ var formatText = (function() {
|
|
|
return html;
|
|
|
};
|
|
|
|
|
|
- /**
|
|
|
- * @constructor
|
|
|
- * @param {string} text
|
|
|
- */
|
|
|
- function MsgTree(text) {
|
|
|
- /** @const @type {string} */
|
|
|
- this.text = text;
|
|
|
-
|
|
|
- /** @type {MsgBranch|null} */
|
|
|
- this.root = null;
|
|
|
- }
|
|
|
-
|
|
|
- MsgTree.prototype.parseFrom = function(i) {
|
|
|
- for (var textLen = this.text.length; i < textLen;)
|
|
|
- i += this.root.addChar(this.text, i);
|
|
|
- this.eof();
|
|
|
- };
|
|
|
-
|
|
|
- MsgTree.prototype.parse = function() {
|
|
|
- this.root = new MsgBranch(this, 0);
|
|
|
- this.parseFrom(0);
|
|
|
- };
|
|
|
-
|
|
|
- /** @param {MsgBranch} root */
|
|
|
- MsgTree.prototype.getFirstUnterminated = function(root) {
|
|
|
- for (var i =0, nbBranches = root.subNodes.length; i < nbBranches; i++) {
|
|
|
- var branch = root.subNodes[i];
|
|
|
+ /** @param {MsgBranch=} _root */
|
|
|
+ function getFirstUnterminated(_root) {
|
|
|
+ _root = _root || root;
|
|
|
+ for (var i =0, nbBranches = _root.subNodes.length; i < nbBranches; i++) {
|
|
|
+ var branch = _root.subNodes[i];
|
|
|
|
|
|
if (branch instanceof MsgBranch) {
|
|
|
if (!branch.terminated) {
|
|
|
return branch;
|
|
|
} else {
|
|
|
- var unTerminatedChild = this.getFirstUnterminated(branch);
|
|
|
+ var unTerminatedChild = getFirstUnterminated(branch);
|
|
|
if (unTerminatedChild)
|
|
|
return unTerminatedChild;
|
|
|
}
|
|
|
@@ -411,51 +454,91 @@ var formatText = (function() {
|
|
|
* @param {MsgBranch} branch
|
|
|
* @param {boolean} next kill this branch or all the next ones ?
|
|
|
**/
|
|
|
- MsgTree.prototype.revertTree = function(branch, next) {
|
|
|
+ function revertTree(branch, next) {
|
|
|
if (branch._parent instanceof MsgBranch) {
|
|
|
branch._parent.subNodes.splice(branch._parent.subNodes.indexOf(branch) +(next ? 1 : 0));
|
|
|
branch._parent.lastNode = branch._parent.subNodes[branch._parent.subNodes.length -1];
|
|
|
- this.revertTree(branch._parent, true);
|
|
|
+ revertTree(branch._parent, true);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ MsgBranch.prototype.implicitClose = function(i) {
|
|
|
+ if (this.isQuote && !this.terminated) {
|
|
|
+ this.terminate(i);
|
|
|
}
|
|
|
+ this.subNodes.forEach(function(node) {
|
|
|
+ if (node instanceof MsgBranch)
|
|
|
+ node.implicitClose(i);
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
- MsgTree.prototype.eof = function() {
|
|
|
- // FIXME merge of same block-branches (opti + quoted)
|
|
|
- var unterminated = this.getFirstUnterminated(this.root);
|
|
|
+ /**
|
|
|
+ * Try to close the tree.
|
|
|
+ * If a problem is found, return its index after reverting tree at this position
|
|
|
+ * @return {number|undefined}
|
|
|
+ **/
|
|
|
+ function eof() {
|
|
|
+ root.implicitClose(text.length);
|
|
|
+ var unterminated = getFirstUnterminated();
|
|
|
|
|
|
if (unterminated) {
|
|
|
// We have a first token, but never closed
|
|
|
// kill that branch
|
|
|
- this.revertTree(unterminated, false);
|
|
|
+ revertTree(unterminated, false);
|
|
|
|
|
|
// "unterminate" all branch that will be closed in the future
|
|
|
- this.root.cancelTerminate(unterminated.triggerIndex);
|
|
|
+ root.cancelTerminate(unterminated.triggerIndex);
|
|
|
|
|
|
// Add a new text leaf containing trigger
|
|
|
var textNode = new MsgTextLeaf(unterminated._parent);
|
|
|
- textNode.addChar(this.text, unterminated.triggerIndex);
|
|
|
+ textNode.addChar(unterminated.triggerIndex);
|
|
|
unterminated._parent.subNodes.push(textNode);
|
|
|
unterminated._parent.lastNode = textNode;
|
|
|
|
|
|
// Restart parsing
|
|
|
- this.parseFrom(unterminated.triggerIndex +1);
|
|
|
+ return unterminated.triggerIndex +1;
|
|
|
+ } else {
|
|
|
+ // FIXME merge of same block-branches (opti)
|
|
|
}
|
|
|
- // Else no problem
|
|
|
};
|
|
|
|
|
|
+ function identity(a) { return a; }
|
|
|
+
|
|
|
/**
|
|
|
+ * @param {string} _text
|
|
|
+ * @param {({
|
|
|
+ * highlights: (Array<string>|undefined),
|
|
|
+ * emojiFormatFunction: (function(string):string|undefined),
|
|
|
+ * textFilter: (function(string):string|undefined)
|
|
|
+ * })=} _opts
|
|
|
* @return {string}
|
|
|
**/
|
|
|
- MsgTree.prototype.toHTML = function() {
|
|
|
- return this.root ? this.root.outerHTML() : "";
|
|
|
- };
|
|
|
-
|
|
|
- return function(text) {
|
|
|
- var root = new MsgTree(text);
|
|
|
- root.parse();
|
|
|
- return (root.toHTML());
|
|
|
+ function _parse(_text, _opts) {
|
|
|
+ if (!_opts)
|
|
|
+ _opts = {};
|
|
|
+ opts.highlights = _opts.highlights || [];
|
|
|
+ opts.emojiFormatFunction = _opts.emojiFormatFunction || identity;
|
|
|
+ opts.textFilter = _opts.textFilter || identity;
|
|
|
+ text = _text;
|
|
|
+
|
|
|
+ root = new MsgBranch(this, 0);
|
|
|
+ var i =0,
|
|
|
+ textLen = text.length;
|
|
|
+ do {
|
|
|
+ while (i < textLen)
|
|
|
+ i += root.addChar(i);
|
|
|
+ i = eof();
|
|
|
+ } while (i !== undefined);
|
|
|
+
|
|
|
+ return root.outerHTML();
|
|
|
};
|
|
|
+ return _parse;
|
|
|
})();
|
|
|
|
|
|
+window['_formatText'] = function(str, opts) {
|
|
|
+ return formatText(str, {
|
|
|
+ highlights: opts ? opts["highlights"] : undefined
|
|
|
+ });
|
|
|
+}
|
|
|
if (typeof module !== "undefined") module.exports.formatText = formatText;
|
|
|
|