| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- "use strict";
- // FIXME Error with _*a_* and _*a*_
- /**
- * 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<MsgBranch|MsgTextLeaf>} */
- 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
- * 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) {
- if (this.lastNode instanceof MsgBranch)
- return this.lastNode.makeNewBranchFromThis();
- else if (this.lastNode.text !== '')
- return new MsgTextLeaf(/** @type {MsgBranch!} */ (this._parent));
- }
- return null;
- };
- /**
- * @return {MsgBranch}
- **/
- MsgBranch.prototype.makeNewBranchFromThis = function() {
- var other = new MsgBranch(this._parent, this.triggerIndex, this.trigger);
- if (this.lastNode instanceof MsgBranch) {
- other.lastNode = this.lastNode.makeNewBranchFromThis();
- other.subNodes = [ other.lastNode ];
- }
- return other;
- };
- /**
- * 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;
- if ((this.isEmoji || this.isLink || this.isBold || this.isItalic || this.isStrike || this.isCode) &&
- str[i] === '\n')
- 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.isCode || this.isCodeBlock || this.isEmoji)
- return null;
- if (this.lastNode instanceof MsgTextLeaf) {
- if (str.substr(i, 3) === '```')
- return '```';
- if (['`', '\n'].indexOf(str[i]) !== -1)
- 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) {
- this.lastNode.terminated = true;
- this.lastNode = isFinished;
- this.subNodes.push(isFinished);
- this.getRoot().terminate(this.triggerIndex);
- 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.triggerIndex);
- 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;
- }
- }
- };
- MsgBranch.prototype.terminate = function(triggerIndex) {
- if (this.triggerIndex === triggerIndex)
- this.terminated = true;
- this.subNodes.forEach(function(i) {
- if (i instanceof MsgBranch)
- i.terminate(triggerIndex);
- });
- };
- MsgBranch.prototype.getRoot = function() {
- var branch = this;
- while (branch._parent && branch._parent instanceof MsgBranch)
- branch = branch._parent;
- return branch;
- };
- /**
- * @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()) {
- // TODO syntax highlight
- 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.checkIsEmoji())
- classList.push('emoji'); // FIXME emoji
- }
- return '<' +tagName +(classList.length ? ' class="' +classList.join(' ') +'"' : '') +'>' +this.innerHTML() +'</' +tagName +'>';
- };
- MsgBranch.prototype.outerHTML = function() {
- var html = "";
- if (this.isQuote) {
- html += '<span class="quote">';
- }
- this.subNodes.forEach(function(node) {
- html += node.outerHTML();
- });
- if (this.isQuote) {
- html += '</span>';
- }
- 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);
- if (!this.root.prune()) {
- this.root = null;
- }
- };
- /** @param {MsgBranch} root */
- MsgTree.prototype.getFirstUnterminated = function(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);
- if (unTerminatedChild)
- return unTerminatedChild;
- }
- }
- }
- return null;
- };
- /**
- * @param {MsgBranch} branch
- * @param {boolean} next kill this branch or the next one ?
- **/
- MsgTree.prototype.revertTree = function(branch, next) {
- if (branch._parent instanceof MsgBranch) {
- branch._parent.subNodes.splice(branch._parent.subNodes.indexOf(branch));
- branch._parent.lastNode = branch._parent.subNodes[branch._parent.subNodes.length -1];
- this.revertTree(branch._parent, true);
- }
- };
- MsgTree.prototype.eof = function() {
- var unterminated = this.getFirstUnterminated(this.root);
- if (unterminated) {
- // We have a first token, but never closed
- // kill that branch
- this.revertTree(unterminated, false);
- // Add a new text leaf containing trigger
- var textNode = new MsgTextLeaf(unterminated._parent);
- textNode.addChar(this.text, unterminated.triggerIndex);
- unterminated._parent.subNodes.push(textNode);
- unterminated._parent.lastNode = textNode;
- // Restart parsing
- this.parseFrom(unterminated.triggerIndex +1);
- }
- // Else no problem
- };
- /**
- * @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());
- };
- })();
|