katex-extension.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import katex from 'katex';
  2. const DELIMITER_LIST = [
  3. { left: '$$', right: '$$', display: false },
  4. { left: '$', right: '$', display: false },
  5. { left: '\\pu{', right: '}', display: false },
  6. { left: '\\ce{', right: '}', display: false },
  7. { left: '\\(', right: '\\)', display: false },
  8. { left: '( ', right: ' )', display: false },
  9. { left: '\\[', right: '\\]', display: true },
  10. { left: '[', right: ']', display: true }
  11. ]
  12. // const DELIMITER_LIST = [
  13. // { left: '$$', right: '$$', display: false },
  14. // { left: '$', right: '$', display: false },
  15. // ];
  16. // const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
  17. // const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;
  18. let inlinePatterns = [];
  19. let blockPatterns = [];
  20. function escapeRegex(string) {
  21. return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  22. }
  23. function generateRegexRules(delimiters) {
  24. delimiters.forEach(delimiter => {
  25. const { left, right } = delimiter;
  26. // Ensure regex-safe delimiters
  27. const escapedLeft = escapeRegex(left);
  28. const escapedRight = escapeRegex(right);
  29. // Inline pattern - Capture group $1, token content, followed by end delimiter and normal punctuation marks.
  30. // Example: $text$
  31. inlinePatterns.push(`${escapedLeft}((?:\\\\.|[^\\\\\\n])*?(?:\\\\.|[^\\\\\\n${escapedRight}]))${escapedRight}`);
  32. // Block pattern - Starts and ends with the delimiter on new lines. Example:
  33. // $$\ncontent here\n$$
  34. blockPatterns.push(`${escapedLeft}\n((?:\\\\[^]|[^\\\\])+?)\n${escapedRight}`);
  35. });
  36. const inlineRule = new RegExp(`^(${inlinePatterns.join('|')})(?=[\\s?!.,:?!。,:]|$)`, 'u');
  37. const blockRule = new RegExp(`^(${blockPatterns.join('|')})(?:\n|$)`, 'u');
  38. return { inlineRule, blockRule };
  39. }
  40. const { inlineRule, blockRule } = generateRegexRules(DELIMITER_LIST);
  41. export default function(options = {}) {
  42. return {
  43. extensions: [
  44. inlineKatex(options, createRenderer(options, false)),
  45. blockKatex(options, createRenderer(options, true)),
  46. ],
  47. };
  48. }
  49. function createRenderer(options, newlineAfter) {
  50. return (token) => katex.renderToString(token.text, { ...options, displayMode: token.displayMode }) + (newlineAfter ? '\n' : '');
  51. }
  52. function inlineKatex(options, renderer) {
  53. const ruleReg = inlineRule;
  54. return {
  55. name: 'inlineKatex',
  56. level: 'inline',
  57. start(src) {
  58. let index;
  59. let indexSrc = src;
  60. while (indexSrc) {
  61. index = indexSrc.indexOf('$');
  62. if (index === -1) {
  63. return;
  64. }
  65. const f = index === 0 || indexSrc.charAt(index - 1) === ' ';
  66. if (f) {
  67. const possibleKatex = indexSrc.substring(index);
  68. if (possibleKatex.match(ruleReg)) {
  69. return index;
  70. }
  71. }
  72. indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, '');
  73. }
  74. },
  75. tokenizer(src, tokens) {
  76. const match = src.match(ruleReg);
  77. if (match) {
  78. const text = match.slice(2).filter((item) => item).find((item) => item.trim());
  79. return {
  80. type: 'inlineKatex',
  81. raw: match[0],
  82. text: text,
  83. };
  84. }
  85. },
  86. renderer,
  87. };
  88. }
  89. function blockKatex(options, renderer) {
  90. return {
  91. name: 'blockKatex',
  92. level: 'block',
  93. tokenizer(src, tokens) {
  94. const match = src.match(blockRule);
  95. if (match) {
  96. const text = match.slice(2).filter((item) => item).find((item) => item.trim());
  97. return {
  98. type: 'blockKatex',
  99. raw: match[0],
  100. text: text,
  101. };
  102. }
  103. },
  104. renderer,
  105. };
  106. }