From 4967ef7e022ff1faef8bd6816ff6339a0f6b429e Mon Sep 17 00:00:00 2001 From: purocean Date: Fri, 25 Jun 2021 15:54:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=87=E7=BA=A7=20Katex=20=E5=85=AC?= =?UTF-8?q?=E7=A4=BA=E6=94=AF=E6=8C=81=E4=B8=AD=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/renderer/components/Preview.vue | 2 - src/renderer/plugins/markdown-it-katex.ts | 204 ++++++++++++++++++++++ src/renderer/shims-vue.d.ts | 1 + src/renderer/useful/plugin/build-in.ts | 2 + yarn.lock | 27 ++- 6 files changed, 218 insertions(+), 20 deletions(-) create mode 100644 src/renderer/plugins/markdown-it-katex.ts diff --git a/package.json b/package.json index 2ddcd963..a91cc809 100644 --- a/package.json +++ b/package.json @@ -75,10 +75,10 @@ "eslint-plugin-vue": "^7.11.1", "github-markdown-css": "^4.0.0", "highlight.js": "^11.0.1", + "katex": "^0.13.11", "lodash-es": "^4.17.21", "markdown-it": "^12.0.6", "markdown-it-attrs": "^4.0.0", - "markdown-it-katex": "^2.0.3", "markdown-it-multimd-table": "^4.1.0", "mermaid": "^8.10.2", "monaco-editor": "^0.25.2", diff --git a/src/renderer/components/Preview.vue b/src/renderer/components/Preview.vue index f868ca1a..19ed3194 100644 --- a/src/renderer/components/Preview.vue +++ b/src/renderer/components/Preview.vue @@ -44,7 +44,6 @@ import { debounce } from 'lodash-es' import { useStore } from 'vuex' import { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue' import Markdown from 'markdown-it' -import katex from 'markdown-it-katex' import MarkdownItAttrs from 'markdown-it-attrs' import MultimdTable from 'markdown-it-multimd-table' @@ -59,7 +58,6 @@ import 'highlight.js/styles/atom-one-dark.css' import 'katex/dist/katex.min.css' const markdown = Markdown({ linkify: true, breaks: true, html: true, }) - .use(katex) .use(MarkdownItAttrs) .use(MultimdTable, { multiline: true }) diff --git a/src/renderer/plugins/markdown-it-katex.ts b/src/renderer/plugins/markdown-it-katex.ts new file mode 100644 index 00000000..cf82ce32 --- /dev/null +++ b/src/renderer/plugins/markdown-it-katex.ts @@ -0,0 +1,204 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable prefer-const */ +// https://raw.githubusercontent.com/waylonflinn/markdown-it-katex/master/index.js + +/* eslint-disable camelcase */ +/* Process inline math */ +/* +Like markdown-it-simplemath, this is a stripped down, simplified version of: +https://github.com/runarberg/markdown-it-math + +It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX +for rendering output. +*/ + +import katex from 'katex' +import { Plugin } from '@fe/useful/plugin' + +// Test if potential opening or closing delimieter +// Assumes that there is a "$" at state.src[pos] +function isValidDelim (state: any, pos: number) { + let prevChar; let nextChar + const max = state.posMax + let can_open = true + let can_close = true + + prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1 + nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1 + + // Check non-whitespace conditions for opening and closing, and + // check that closing delimeter isn't followed by a number + if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ || + (nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) { + can_close = false + } + if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) { + can_open = false + } + + return { + can_open: can_open, + can_close: can_close + } +} + +function math_inline (state: any, silent: boolean) { + let start, match, token, res, pos, esc_count + + if (state.src[state.pos] !== '$') { return false } + + res = isValidDelim(state, state.pos) + if (!res.can_open) { + if (!silent) { state.pending += '$' } + state.pos += 1 + return true + } + + // First check for and bypass all properly escaped delimieters + // This loop will assume that the first leading backtick can not + // be the first character in state.src, which is known since + // we have found an opening delimieter already. + start = state.pos + 1 + match = start + while ((match = state.src.indexOf('$', match)) !== -1) { + // Found potential $, look for escapes, pos will point to + // first non escape when complete + pos = match - 1 + while (state.src[pos] === '\\') { pos -= 1 } + + // Even number of escapes, potential closing delimiter found + if (((match - pos) % 2) === 1) { break } + match += 1 + } + + // No closing delimter found. Consume $ and continue. + if (match === -1) { + if (!silent) { state.pending += '$' } + state.pos = start + return true + } + + // Check if we have empty content, ie: $$. Do not parse. + if (match - start === 0) { + if (!silent) { state.pending += '$$' } + state.pos = start + 1 + return true + } + + // Check for valid closing delimiter + res = isValidDelim(state, match) + if (!res.can_close) { + if (!silent) { state.pending += '$' } + state.pos = start + return true + } + + if (!silent) { + token = state.push('math_inline', 'math', 0) + token.markup = '$' + token.content = state.src.slice(start, match) + } + + state.pos = match + 1 + return true +} + +function math_block (state: any, start: number, end: number, silent: boolean) { + let firstLine; let lastLine; let next; let lastPos; let found = false; let token + let pos = state.bMarks[start] + state.tShift[start] + let max = state.eMarks[start] + + if (pos + 2 > max) { return false } + if (state.src.slice(pos, pos + 2) !== '$$') { return false } + + pos += 2 + firstLine = state.src.slice(pos, max) + + if (silent) { return true } + if (firstLine.trim().slice(-2) === '$$') { + // Single line expression + firstLine = firstLine.trim().slice(0, -2) + found = true + } + + for (next = start; !found;) { + next++ + + if (next >= end) { break } + + pos = state.bMarks[next] + state.tShift[next] + max = state.eMarks[next] + + if (pos < max && state.tShift[next] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + break + } + + if (state.src.slice(pos, max).trim().slice(-2) === '$$') { + lastPos = state.src.slice(0, max).lastIndexOf('$$') + lastLine = state.src.slice(pos, lastPos) + found = true + } + } + + state.line = next + 1 + + token = state.push('math_block', 'math', 0) + token.block = true + token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') + + state.getLines(start + 1, next, state.tShift[start], true) + + (lastLine && lastLine.trim() ? lastLine : '') + token.map = [start, state.line] + token.markup = '$$' + return true +} + +function math_plugin (md: any, options: any) { + // Default options + + options = options || {} + + // set KaTeX as the renderer for markdown-it-simplemath + const katexInline = function (latex: any) { + options.displayMode = false + try { + return katex.renderToString(latex, options) + } catch (error) { + if (options.throwOnError) { console.log(error) } + return latex + } + } + + const inlineRenderer = function (tokens: any, idx: any) { + return katexInline(tokens[idx].content) + } + + const katexBlock = function (latex: any) { + options.displayMode = true + try { + return '

' + katex.renderToString(latex, options) + '

' + } catch (error) { + console.log(error) + if (options.throwOnError) { console.log(error) } + return latex + } + } + + const blockRenderer = function (tokens: any, idx: any) { + return katexBlock(tokens[idx].content) + '\n' + } + + md.inline.ruler.after('escape', 'math_inline', math_inline) + md.block.ruler.after('blockquote', 'math_block', math_block, { + alt: ['paragraph', 'reference', 'blockquote', 'list'] + }) + md.renderer.rules.math_inline = inlineRenderer + md.renderer.rules.math_block = blockRenderer +} + +export default { + name: 'markdown-toc', + register: ctx => { + ctx.markdown.registerPlugin(math_plugin) + } +} as Plugin diff --git a/src/renderer/shims-vue.d.ts b/src/renderer/shims-vue.d.ts index a64791ec..236df356 100644 --- a/src/renderer/shims-vue.d.ts +++ b/src/renderer/shims-vue.d.ts @@ -10,3 +10,4 @@ declare module 'markdown-it-multimd-table' declare module 'markdown-it-katex' declare module '@json-editor/json-editor' declare module 'mermaid/dist/mermaid.js' +declare module 'katex' diff --git a/src/renderer/useful/plugin/build-in.ts b/src/renderer/useful/plugin/build-in.ts index cf85399f..e19428e1 100644 --- a/src/renderer/useful/plugin/build-in.ts +++ b/src/renderer/useful/plugin/build-in.ts @@ -13,6 +13,7 @@ import markdownLink from '@fe/plugins/markdown-link' import markdownTaskList from '@fe/plugins/markdown-task-list' import markdownFootnote from '@/renderer/plugins/markdown-footnote' import transformImgOutLink from '@fe/plugins/transform-img-out-link' +import markdownItKatex from '@/renderer/plugins/markdown-it-katex' import copyText from '@fe/plugins/copy-text' import tableCellEdit from '@fe/plugins/table-cell-edit' import switchTodo from '@fe/plugins/switch-todo' @@ -39,6 +40,7 @@ export default [ markdownLink, markdownTaskList, markdownFootnote, + markdownItKatex, transformImgOutLink, copyText, tableCellEdit, diff --git a/yarn.lock b/yarn.lock index b26479fc..e02a89cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1456,6 +1456,11 @@ commander@^5.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +commander@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + component-bind@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" @@ -3731,12 +3736,12 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -katex@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.6.0.tgz#12418e09121c05c92041b6b3b9fb6bab213cb6f3" - integrity sha1-EkGOCRIcBckgQbazuftrqyE8tvM= +katex@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.13.11.tgz#66138ebf173f25ef130cd3a3ea3ea1d12a3f1362" + integrity sha512-yJBHVIgwlAaapzlbvTpVF/ZOs8UkTj/sd46Fl8+qAf2/UiituPYVeapVD8ADZtqyRg/qNWUKt7gJoyYVWLrcXw== dependencies: - match-at "^0.1.0" + commander "^6.0.0" keygrip@~1.1.0: version "1.1.0" @@ -3967,13 +3972,6 @@ markdown-it-attrs@^4.0.0: resolved "https://registry.yarnpkg.com/markdown-it-attrs/-/markdown-it-attrs-4.0.0.tgz#eadae3ea0c7d17c5e83b98a4ec95a47f4e4960f5" integrity sha512-uLjtdCmhhmL3BuZsReYkFxk74qKjj5ahe34teBpOCJ4hYZZl7/ftLyXWLowngC2moRkbLEvKwN/7TMwbhbHE/A== -markdown-it-katex@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz#d7b86a1aea0b9d6496fab4e7919a18fdef589c39" - integrity sha1-17hqGuoLnWSW+rTnkZoY/e9YnDk= - dependencies: - katex "^0.6.0" - markdown-it-multimd-table@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/markdown-it-multimd-table/-/markdown-it-multimd-table-4.1.0.tgz#5c23eba9ceda580dc5c7301927a01610e0ee37b9" @@ -4003,11 +4001,6 @@ markdown-it@^12.0.6: mdurl "^1.0.1" uc.micro "^1.0.5" -match-at@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540" - integrity sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q== - matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"