"use strict"; /** * replace all :emoji: codes with corresponding image * @param {string} inputString * @return {string} **/ function formatEmojis(inputString) { return inputString.replace(/:([^ \t:]+):/g, function(returnFailed, emoji) { var emojiDom = makeEmojiDom(emoji); if (emojiDom) { var domParent = document.createElement("span"); domParent.className = returnFailed === inputString ? R.klass.emoji.medium : R.klass.emoji.small; domParent.appendChild(emojiDom); return domParent.outerHTML; } return returnFailed; }); } /** @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); } /** * @constructor * @param {MsgBranch!} _parent **/ function MsgTextLeaf(_parent) { /** @type {string} */ this.text = ""; /** @const @type {MsgBranch} */ this._parent = _parent; } /** * @constructor * @param {MsgBranch|MsgTree} _parent * @param {number} triggerIndex * @param {string=} trigger */ function MsgBranch(_parent, triggerIndex, trigger) { /** @const @type {number} */ this.triggerIndex = triggerIndex; /** @type {MsgBranch|MsgTextLeaf} */ this.lastNode = new MsgTextLeaf(this); /** @type {Array} */ this.subNodes = [ this.lastNode ]; /** @const @type {string} */ this.trigger = trigger || ''; /** @type {boolean} */ this.isLink = this.trigger === '<'; /** @type {boolean} */ this.isBold = this.trigger === '*'; /** @type {boolean} */ this.isItalic = this.trigger === '_'; /** @type {boolean} */ this.isStrike = this.trigger === '~' || this.trigger === '-'; /** @type {boolean} */ this.isQuote = this.trigger === '>'; /** @type {boolean} */ this.isEmoji = this.trigger === ':'; /** @type {boolean} */ this.isCode = this.trigger === '`'; /** @type {boolean} */ this.isCodeBlock = this.trigger === '```'; /** @type {boolean} */ this.isEol = this.trigger === '\n'; /** @const @type {MsgBranch|MsgTree} */ this._parent = _parent; /** @type {boolean} */ this.terminated = false; } /** @return {boolean} */ MsgBranch.prototype.checkIsBold = function() { return (this.isBold && this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsBold()); }; /** @return {boolean} */ MsgBranch.prototype.checkIsItalic = function() { return (this.isItalic && this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsItalic()); }; /** @return {boolean} */ MsgBranch.prototype.checkIsStrike = function() { return (this.isStrike && this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsStrike()); }; /** @return {boolean} */ MsgBranch.prototype.checkIsQuote = function() { return (this.isQuote && this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsQuote()); }; /** @return {boolean} */ MsgBranch.prototype.checkIsEmoji = function() { return (this.isEmoji && this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsEmoji()); }; /** @return {boolean} */ MsgBranch.prototype.checkIsCode = function() { return (this.isCode && this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsCode()); }; /** @return {boolean} */ MsgBranch.prototype.checkIsCodeBlock = function() { return (this.isCodeBlock && this.terminated) || (this._parent instanceof MsgBranch && this._parent.checkIsCodeBlock()); }; /** * Check if this token closes the branch * FIXME it is possible that an inner branch still lives * In this case, finishWith should return the new leave * Exemple: `_text *bold_ bold continue*` should be * + root * | italic * | "text" * | bold * | "bold" * | bold * | "bold continue" * and this function will return a "bold" branch * * @param {string} str * @param {number} i * @return {MsgTextLeaf|MsgBranch|null} **/ MsgBranch.prototype.finishWith = function(str, i) { if (this.trigger === '<' && str[i] === '>') { return new MsgTextLeaf(/** @type {MsgBranch!} */ (this._parent)); } if (str.substr(i, this.trigger.length) === this.trigger) { return new MsgTextLeaf(/** @type {MsgBranch!} */ (this._parent)); } // FIXME // check if branch terminates here // If yes return a branche containing all unfinished children // If yes but no children, return a new MsgTextLeaf // If not return null return null; }; /** * 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')) return false; return true; }; /** * 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) { if (this.lastNode instanceof MsgTextLeaf) { if (str.substr(i, 3) === '```') return '```'; if ('`' === str[i]) return str[i]; if (['*', '~', '-', '_' ].indexOf(str[i]) !== -1 && (isAlphadec(str[i +1]) || ['*', '`', '~', '-', '_', ':', '<'].indexOf(str[i+1]) !== -1)) return str[i]; if ([':', '<'].indexOf(str[i]) !== -1 && isAlphadec(str[i +1])) return str[i]; } return null; }; /** @return {number} */ MsgTextLeaf.prototype.addChar = function(str, i) { this.text += str[i]; return 1; }; /** * Parse next char * @param {string} str * @param {number} i * @return {number} **/ MsgBranch.prototype.addChar = function(str, i) { var isFinished = this.lastNode.finishWith ? this.lastNode.finishWith(str, i) : null; if (isFinished) { // terminated token, move pointer to a new leaf this.lastNode.terminated = true; this.lastNode = isFinished; this.subNodes.push(isFinished); return 1; } else { if (this.lastNode instanceof MsgTextLeaf || this.lastNode.isAcceptable(str, i)) { var isNewToken = this.isNewToken(str, i); if (isNewToken) { this.lastNode = new MsgBranch(this, i, isNewToken); this.subNodes.push(this.lastNode); return this.lastNode.trigger.length; } else { return this.lastNode.addChar(str, i); } } else { // last branch child is not compatible with this token. // So, lastBranch is not a branch // Add a new "escaped" node to replace trigger var textNode = new MsgTextLeaf(this); textNode.addChar(str, this.lastNode.trigger); this.subNodes.pop(); this.subNodes.push(textNode); // new root var newBranch = new MsgBranch(this, this.lastNode.triggerIndex +1); for (var charIndex = this.lastNode.triggerIndex +1; charIndex <= i;) charIndex += newBranch.addChar(str, charIndex); this.lastNode = this.subNodes[this.subNodes.length -1]; return 1; } } }; /** * @return {boolean} true if still contains stuff **/ MsgBranch.prototype.prune = function() { var branches = []; this.subNodes.forEach(function(i) { if (i instanceof MsgTextLeaf) { if (i.text !== '') branches.push(i); } else if (i.prune()) { branches.push(i); } }); this.subNodes = branches; this.lastNode = branches[branches.length -1]; return !!this.subNodes.length; }; /** * @return {string} **/ MsgTextLeaf.prototype.innerHTML = function() { return this.text; }; /** * @return {string} **/ MsgTextLeaf.prototype.outerHTML = function() { var tagName = 'span' ,classList = []; if (this._parent.checkIsCodeBlock()) { classList.push('codeblock'); } else if (this._parent.checkIsCode()) { classList.push('code'); } else { if (this.isLink) tagName = 'a'; if (this._parent.checkIsBold()) classList.push('bold'); if (this._parent.checkIsItalic()) classList.push('italic'); if (this._parent.checkIsStrike()) classList.push('strike'); if (this._parent.checkIsQuote()) // FIXME nope. classList.push('quote'); if (this._parent.checkIsEmoji()) classList.push('emoji'); } return '<' +tagName +(classList.length ? ' class="' +classList.join(' ') +'"' : '') +'>' +this.innerHTML() +''; }; MsgBranch.prototype.outerHTML = function() { var html = ""; this.subNodes.forEach(function(node) { html += node.outerHTML(); }); return html; }; MsgBranch.prototype.eof = function() { // FIXME check for any unterminated branches to reinterpret them }; /** * @constructor * @param {string} text */ function MsgTree(text) { /** @const @type {string} */ this.text = text; /** @type {MsgBranch|null} */ this.root = null; } MsgTree.prototype.parse = function() { this.root = new MsgBranch(this, 0); for (var i =0, textLen = this.text.length; i < textLen;) i += this.root.addChar(this.text, i); this.root.eof(); if (!this.root.prune()) { this.root = null; } }; /** * @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()); }; })();