replace.js 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. // Emojies & shortcuts replacement logic.
  2. //
  3. // Note: In theory, it could be faster to parse :smile: in inline chain and
  4. // leave only shortcuts here. But, who care...
  5. //
  6. 'use strict';
  7. module.exports = function create_rule(md, emojies, shortcuts, scanRE, replaceRE) {
  8. var arrayReplaceAt = md.utils.arrayReplaceAt,
  9. ucm = md.utils.lib.ucmicro,
  10. ZPCc = new RegExp([ ucm.Z.source, ucm.P.source, ucm.Cc.source ].join('|'));
  11. function splitTextToken(text, level, Token) {
  12. var token, last_pos = 0, nodes = [];
  13. text.replace(replaceRE, function (match, offset, src) {
  14. var emoji_name;
  15. // Validate emoji name
  16. if (shortcuts.hasOwnProperty(match)) {
  17. // replace shortcut with full name
  18. emoji_name = shortcuts[match];
  19. // Don't allow letters before any shortcut (as in no ":/" in http://)
  20. if (offset > 0 && !ZPCc.test(src[offset - 1])) {
  21. return;
  22. }
  23. // Don't allow letters after any shortcut
  24. if (offset + match.length < src.length && !ZPCc.test(src[offset + match.length])) {
  25. return;
  26. }
  27. } else {
  28. emoji_name = match.slice(1, -1);
  29. }
  30. // Add new tokens to pending list
  31. if (offset > last_pos) {
  32. token = new Token('text', '', 0);
  33. token.content = text.slice(last_pos, offset);
  34. nodes.push(token);
  35. }
  36. token = new Token('emoji', '', 0);
  37. token.markup = emoji_name;
  38. token.content = emojies[emoji_name];
  39. nodes.push(token);
  40. last_pos = offset + match.length;
  41. });
  42. if (last_pos < text.length) {
  43. token = new Token('text', '', 0);
  44. token.content = text.slice(last_pos);
  45. nodes.push(token);
  46. }
  47. return nodes;
  48. }
  49. return function emoji_replace(state) {
  50. var i, j, l, tokens, token,
  51. blockTokens = state.tokens,
  52. autolinkLevel = 0;
  53. for (j = 0, l = blockTokens.length; j < l; j++) {
  54. if (blockTokens[j].type !== 'inline') { continue; }
  55. tokens = blockTokens[j].children;
  56. // We scan from the end, to keep position when new tags added.
  57. // Use reversed logic in links start/end match
  58. for (i = tokens.length - 1; i >= 0; i--) {
  59. token = tokens[i];
  60. if (token.type === 'link_open' || token.type === 'link_close') {
  61. if (token.info === 'auto') { autolinkLevel -= token.nesting; }
  62. }
  63. if (token.type === 'text' && autolinkLevel === 0 && scanRE.test(token.content)) {
  64. // replace current node
  65. blockTokens[j].children = tokens = arrayReplaceAt(
  66. tokens, i, splitTextToken(token.content, token.level, state.Token)
  67. );
  68. }
  69. }
  70. }
  71. };
  72. };