katex-extension.ts 3.57 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
import katex from 'katex';

Timothy J. Baek's avatar
Timothy J. Baek committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const DELIMITER_LIST = [
    { left: '$$', right: '$$', display: false },
    { left: '$', right: '$', display: false },
    { left: '\\pu{', right: '}', display: false },
    { left: '\\ce{', right: '}', display: false },
    { left: '\\(', right: '\\)', display: false },
    { left: '( ', right: ' )', display: false },
    { left: '\\[', right: '\\]', display: true },
    { left: '[', right: ']', display: true }
]

// const DELIMITER_LIST = [
//     { left: '$$', right: '$$', display: false },
//     { left: '$', right: '$', display: false },
// ];

// const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
// const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;

let inlinePatterns = [];
let blockPatterns = [];

function escapeRegex(string) {
    return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

function generateRegexRules(delimiters) {
    delimiters.forEach(delimiter => {
        const { left, right } = delimiter;
        // Ensure regex-safe delimiters
        const escapedLeft = escapeRegex(left);
        const escapedRight = escapeRegex(right);

        // Inline pattern - Capture group $1, token content, followed by end delimiter and normal punctuation marks.
        // Example: $text$
        inlinePatterns.push(`${escapedLeft}((?:\\\\.|[^\\\\\\n])*?(?:\\\\.|[^\\\\\\n${escapedRight}]))${escapedRight}`);

        // Block pattern - Starts and ends with the delimiter on new lines. Example:
        // $$\ncontent here\n$$
        blockPatterns.push(`${escapedLeft}\n((?:\\\\[^]|[^\\\\])+?)\n${escapedRight}`);
    });

    const inlineRule = new RegExp(`^(${inlinePatterns.join('|')})(?=[\\s?!.,:?!。,:]|$)`, 'u');
    const blockRule = new RegExp(`^(${blockPatterns.join('|')})(?:\n|$)`, 'u');

    return { inlineRule, blockRule };
}

const { inlineRule, blockRule } = generateRegexRules(DELIMITER_LIST);



export default function(options = {}) {
  return {
    extensions: [
      inlineKatex(options, createRenderer(options, false)),
      blockKatex(options, createRenderer(options, true)),
    ],
  };
Timothy J. Baek's avatar
Timothy J. Baek committed
62
63
64
}

function createRenderer(options, newlineAfter) {
Timothy J. Baek's avatar
Timothy J. Baek committed
65
  return (token) => katex.renderToString(token.text, { ...options, displayMode: token.displayMode }) + (newlineAfter ? '\n' : '');
Timothy J. Baek's avatar
Timothy J. Baek committed
66
67
68
}

function inlineKatex(options, renderer) {
Timothy J. Baek's avatar
Timothy J. Baek committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
  const ruleReg =  inlineRule;
  return {
    name: 'inlineKatex',
    level: 'inline',
    start(src) {
      let index;
      let indexSrc = src;

      while (indexSrc) {
        index = indexSrc.indexOf('$');
        if (index === -1) {
          return;
        }
        const f = index === 0 || indexSrc.charAt(index - 1) === ' ';
        if (f) {
          const possibleKatex = indexSrc.substring(index);

          if (possibleKatex.match(ruleReg)) {
            return index;
          }
        }

        indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, '');
      }
    },
    tokenizer(src, tokens) {
      const match = src.match(ruleReg);

      if (match) {
        console.log(match)
        const text = match.slice(2).filter((item) => item).find((item) => item.trim());

        return {
          type: 'inlineKatex',
          raw: match[0],
          text: text,
        };
      }
    },
    renderer,
  };
Timothy J. Baek's avatar
Timothy J. Baek committed
110
111
112
}

function blockKatex(options, renderer) {
Timothy J. Baek's avatar
Timothy J. Baek committed
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  return {
    name: 'blockKatex',
    level: 'block',
    tokenizer(src, tokens) {
      const match = src.match(blockRule);
      if (match) {
        return {
          type: 'blockKatex',
          raw: match[0],
          text: match[0],
        };
      }
    },
    renderer,
  };
Timothy J. Baek's avatar
Timothy J. Baek committed
128
}
Timothy J. Baek's avatar
Timothy J. Baek committed
129