Spaces:
Paused
Paused
| import katex from 'katex'; | |
| 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)) | |
| ] | |
| }; | |
| } | |
| function createRenderer(options, newlineAfter) { | |
| return (token) => | |
| katex.renderToString(token.text, { ...options, displayMode: token.displayMode }) + | |
| (newlineAfter ? '\n' : ''); | |
| } | |
| function inlineKatex(options, renderer) { | |
| 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) { | |
| const text = match | |
| .slice(2) | |
| .filter((item) => item) | |
| .find((item) => item.trim()); | |
| return { | |
| type: 'inlineKatex', | |
| raw: match[0], | |
| text: text | |
| }; | |
| } | |
| }, | |
| renderer | |
| }; | |
| } | |
| function blockKatex(options, renderer) { | |
| return { | |
| name: 'blockKatex', | |
| level: 'block', | |
| tokenizer(src, tokens) { | |
| const match = src.match(blockRule); | |
| if (match) { | |
| const text = match | |
| .slice(2) | |
| .filter((item) => item) | |
| .find((item) => item.trim()); | |
| return { | |
| type: 'blockKatex', | |
| raw: match[0], | |
| text: text | |
| }; | |
| } | |
| }, | |
| renderer | |
| }; | |
| } | |