msgFormatter.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /**
  2. * replace all :emoji: codes with corresponding image
  3. * @param {string} inputString
  4. * @return {string}
  5. **/
  6. function formatEmojis(inputString) {
  7. return inputString.replace(/:([^ \t:]+):/g, function(returnFailed, emoji) {
  8. var emojiDom = makeEmojiDom(emoji);
  9. if (emojiDom) {
  10. var domParent = document.createElement("span");
  11. domParent.className = returnFailed === inputString ? R.klass.emoji.medium : R.klass.emoji.small;
  12. domParent.appendChild(emojiDom);
  13. return domParent.outerHTML;
  14. }
  15. return returnFailed;
  16. });
  17. }
  18. /** @type {function(string):string} */
  19. var formatText = (function() {
  20. /**
  21. * @constructor
  22. * @param {string} fullText
  23. */
  24. function MessagePart(fullText) {
  25. /** @type {Array.<MessagePart>} */
  26. this.subParts = [];
  27. /** @type {string} */
  28. this.text = "";
  29. /** @type {string} */
  30. this.fullText = fullText;
  31. /** @type {boolean} */
  32. this.italic = false;
  33. /** @type {boolean} */
  34. this.bold = false;
  35. /** @type {boolean} */
  36. this.strike = false;
  37. /** @type {boolean} */
  38. this.code = false;
  39. /** @type {boolean} */
  40. this.longCode = false;
  41. /** @type {boolean} */
  42. this.quote = false;
  43. }
  44. var isAlphadec = function(c) {
  45. return ((c >= 'A' && c <= 'Z') ||
  46. (c >= 'a' && c <= 'z') ||
  47. (c >= '0' && c <= '9') ||
  48. "àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇߨøÅ寿œ".indexOf(c) !== -1);
  49. }
  50. ,checkEnd = function(str, pos, c) {
  51. while (str[pos]) {
  52. if (isAlphadec(str[pos]) && str[pos] != c && str[pos +1] == c) {
  53. return true;
  54. }
  55. pos++;
  56. }
  57. return false;
  58. }
  59. MessagePart.prototype.isFinished = function() {
  60. var result = false;
  61. if (this.longCode)
  62. return this.text.substr(this.text.length -3) === '```';
  63. if (this.code)
  64. return this.text[this.text.length -1] === '`' && this.text.length > 1;
  65. if (this.bold)
  66. result |= this.text[this.text.length -1] === '*' && this.text.length > 1;
  67. if (this.strike)
  68. result |= this.text[this.text.length -1] === '~' && this.text.length > 1;
  69. if (this.italic)
  70. result |= this.text[this.text.length -1] === '_' && this.text.length > 1;
  71. if (this.quote)
  72. result |= this.text[this.text.length -1] === '\n';
  73. return result || this.isOnlyText();
  74. };
  75. MessagePart.prototype.addChar = function(c) {
  76. this.text += c;
  77. return this;
  78. };
  79. MessagePart.prototype.isOnlyText = function() {
  80. return !this.italic &&
  81. !this.bold &&
  82. !this.strike &&
  83. !this.code &&
  84. !this.longCode &&
  85. !this.quote;
  86. };
  87. /**
  88. * @return {MessagePart}
  89. **/
  90. MessagePart.prototype.finalize = function() {
  91. if (this.longCode)
  92. this.text = this.text.substr(0, this.text.length -3);
  93. else if (this.code)
  94. this.text = this.text.substr(0, this.text.length -1);
  95. else if (this.bold)
  96. this.text = this.text.substr(0, this.text.length -1);
  97. else if (this.strike)
  98. this.text = this.text.substr(0, this.text.length -1);
  99. else if (this.italic)
  100. this.text = this.text.substr(0, this.text.length -1);
  101. else if (this.quote)
  102. this.text = this.text.substr(0, this.text.length -1);
  103. return this;
  104. };
  105. MessagePart.prototype.cloneType = function() {
  106. var clone = new MessagePart(this.fullText);
  107. clone.italic = this.italic;
  108. clone.bold = this.bold;
  109. clone.strike = this.strike;
  110. clone.code = this.code;
  111. clone.longCode = this.longCode;
  112. clone.quote = this.quote;
  113. return clone;
  114. };
  115. /**
  116. * @param {boolean=} skipFirst
  117. **/
  118. MessagePart.prototype.formatText = function(skipFirst) {
  119. var lastChild = new MessagePart(this.fullText);
  120. for (var i =skipFirst === true ? 1 : 0, textLength =this.fullText.length; i < textLength; i++) {
  121. //First, check if we have a ``` code block
  122. if (!lastChild.longCode && this.fullText.substr(i, 3) === '```') {
  123. this.subParts.push(lastChild.finalize());
  124. lastChild = new MessagePart(this.fullText.substr(i));
  125. lastChild.longCode = true;
  126. i += 2;
  127. } else if (!lastChild.longCode && !lastChild.code && this.fullText[i] === '`') {
  128. this.subParts.push(lastChild.finalize());
  129. lastChild = new MessagePart(this.fullText.substr(i));
  130. lastChild.code = true;
  131. } else if (!lastChild.longCode && !lastChild.italic && this.fullText[i] === '_') {
  132. this.subParts.push(lastChild.finalize());
  133. lastChild = new MessagePart(this.fullText.substr(i));
  134. lastChild.italic = true;
  135. } else if (!lastChild.longCode && !lastChild.bold && this.fullText[i] === '*') {
  136. this.subParts.push(lastChild.finalize());
  137. lastChild = new MessagePart(this.fullText.substr(i));
  138. lastChild.bold = true;
  139. } else if (!lastChild.longCode && !lastChild.strike && this.fullText[i] === '~') {
  140. this.subParts.push(lastChild.finalize());
  141. lastChild = new MessagePart(this.fullText.substr(i));
  142. lastChild.strike = true;
  143. } else {
  144. lastChild.addChar(this.fullText[i]);
  145. if (!lastChild.isOnlyText() && lastChild.isFinished()) {
  146. this.subParts.push(lastChild.finalize());
  147. lastChild = new MessagePart(this.fullText.substr(i +1));
  148. }
  149. }
  150. }
  151. if (lastChild.isFinished()) {
  152. this.subParts.push(lastChild.finalize());
  153. } else {
  154. // Unterminated sequence
  155. var textChild = new MessagePart(lastChild.fullText[0]);
  156. textChild.text = lastChild.fullText[0];
  157. this.subParts.push(textChild.finalize());
  158. lastChild.formatText(true);
  159. this.subParts.push(lastChild);
  160. }
  161. /*
  162. var _msgContent = ""
  163. ,currentMods = {}
  164. ,quote = false
  165. ,i =0
  166. var msgContent = this.text.replace(new RegExp('<([@#]?)([^>]*)>', 'g'),
  167. function(matched, type, entity) {
  168. var sub = entity.split('|');
  169. if (type === '@') {
  170. if (!sub[1]) {
  171. var user = SLACK.context.users[sub[0]];
  172. sub[1] = user ? ('@' +user.name) : locale.unknownMember;
  173. } else if ('@' !== sub[1][0]) {
  174. sub[1] = '@' +sub[1];
  175. }
  176. sub[0] = '#' +sub[0];
  177. sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkuser;
  178. } else if (type === '#') {
  179. if (!sub[1]) {
  180. var chan = SLACK.context.channels[sub[0]];
  181. sub[1] = chan ? ('#' +chan.name) : locale.unknownChannel;
  182. } else if ('#' !== sub[1][0]) {
  183. sub[1] = '#' +sub[1];
  184. }
  185. sub[0] = '#' +sub[0];
  186. sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkchan;
  187. } else if (sub[0].indexOf("://") !== -1) {
  188. if (!sub[1])
  189. sub[1] = sub[0];
  190. sub[2] = R.klass.msg.link;
  191. } else {
  192. return matched;
  193. }
  194. return '<a href="' +sub[0] +'" class="' +sub[2] +'"' +(!type ? ' target="_blank"' : '') +'>' +sub[1] +'</a>';
  195. });
  196. msgContent = formatEmojis(msgContent);
  197. var msgLength = msgContent.length,
  198. appendMod = function(mods) {
  199. if (!Object.keys(currentMods).length)
  200. return "";
  201. return '<span class="' +Object.keys(mods).join(' ') +'">';
  202. };
  203. // Skip trailing
  204. while (i < msgLength && (msgContent[i] === ' ' || msgContent[i] === '\t'))
  205. i++;
  206. if (msgContent.substr(i, 4) === '&gt;') {
  207. quote = true;
  208. i += 4;
  209. }
  210. for (; i < msgLength; i++) {
  211. var c = msgContent[i];
  212. if (c === '<') {
  213. do {
  214. _msgContent += msgContent[i++];
  215. } while (msgContent[i -1] !== '>' && msgContent[i]);
  216. i--;
  217. continue;
  218. }
  219. if (!(currentMods[R.klass.msg.style.bold]) && c === '*' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  220. if (Object.keys(currentMods).length)
  221. _msgContent += '</span>';
  222. currentMods[R.klass.msg.style.bold] = true;
  223. _msgContent += appendMod(currentMods);
  224. } else if (!(currentMods[R.klass.msg.style.strike]) && c === '~' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  225. if (Object.keys(currentMods).length)
  226. _msgContent += '</span>';
  227. currentMods[R.klass.msg.style.strike] = true;
  228. _msgContent += appendMod(currentMods);
  229. } else if (!(currentMods[R.klass.msg.style.code]) && c === '`' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  230. if (Object.keys(currentMods).length)
  231. _msgContent += '</span>';
  232. currentMods[R.klass.msg.style.code] = true;
  233. _msgContent += appendMod(currentMods);
  234. } else if (!(currentMods[R.klass.msg.style.italic]) && c === '_' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  235. if (Object.keys(currentMods).length)
  236. _msgContent += '</span>';
  237. currentMods[R.klass.msg.style.italic] = true;
  238. _msgContent += appendMod(currentMods);
  239. } else {
  240. var finalFound = false;
  241. _msgContent += c;
  242. do {
  243. if ((currentMods[R.klass.msg.style.bold]) && c !== '*' && msgContent[i +1] === '*') {
  244. delete currentMods[R.klass.msg.style.bold];
  245. finalFound = true;
  246. } else if ((currentMods[R.klass.msg.style.strike]) && c !== '~' && msgContent[i +1] === '~') {
  247. delete currentMods[R.klass.msg.style.strike];
  248. finalFound = true;
  249. } else if ((currentMods[R.klass.msg.style.code]) && c !== '`' && msgContent[i +1] === '`') {
  250. delete currentMods[R.klass.msg.style.code];
  251. finalFound = true;
  252. } else if ((currentMods[R.klass.msg.style.italic]) && c !== '_' && msgContent[i +1] === '_') {
  253. delete currentMods[R.klass.msg.style.italic];
  254. finalFound = true;
  255. } else {
  256. break;
  257. }
  258. c = msgContent[++i];
  259. } while (i < msgLength);
  260. if (finalFound)
  261. _msgContent += '</span>' +appendMod(currentMods);
  262. }
  263. }
  264. if (!isObjectEmpty(currentMods)) {
  265. // Should not append
  266. console.warn("formatter warning");
  267. _msgContent += '</span>';
  268. }
  269. /*
  270. if (quote)
  271. msgContents[msgContentIndex] = '<span class="' +R.klass.msg.style.quote +'">' +_msgContent +'</span>';
  272. else
  273. msgContents[msgContentIndex] = _msgContent;
  274. */
  275. };
  276. MessagePart.prototype.getOuterClass = function() {
  277. var classList = '';
  278. if (this.longCode) {
  279. classList += ' ' +R.klass.msg.style.longCode;
  280. }
  281. if (this.bold) {
  282. classList += ' ' +R.klass.msg.style.bold;
  283. }
  284. if (this.code) {
  285. classList += ' ' +R.klass.msg.style.code;
  286. }
  287. if (this.italic) {
  288. classList += ' ' +R.klass.msg.style.italic;
  289. }
  290. if (this.strike) {
  291. classList += ' ' +R.klass.msg.style.strike;
  292. }
  293. if (this.quote) {
  294. classList += ' ' +R.klass.msg.style.quote;
  295. }
  296. return classList;
  297. };
  298. /** @return {string} */
  299. MessagePart.prototype.getInnerHTML = function() {
  300. return this.text
  301. .replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '&nbsp;') // replace trailing spaces par non-secable spaces
  302. .replace(/\n/g, '<br/>');
  303. };
  304. /** @return {string} */
  305. MessagePart.prototype.toHTML = function() {
  306. if (this.subParts.length) {
  307. var result = "";
  308. this.subParts.forEach(function(part) {
  309. result += part.toHTML();
  310. });
  311. return result;
  312. } else {
  313. if (this.isOnlyText() && !this.text.length)
  314. return "";
  315. return '<span class="' +this.getOuterClass() +'">' +this.getInnerHTML() +'</span>';
  316. }
  317. };
  318. return function (fullText) {
  319. // trivial empty string
  320. var text = (fullText || "").trim();
  321. if (text == "")
  322. return "";
  323. var msgContent = new MessagePart(text);
  324. msgContent.formatText();
  325. console.log(msgContent);
  326. return msgContent.toHTML();
  327. }
  328. })();