diff --git a/blossom-editor/package-lock.json b/blossom-editor/package-lock.json index 6b9c304..2d22340 100644 --- a/blossom-editor/package-lock.json +++ b/blossom-editor/package-lock.json @@ -21,6 +21,7 @@ "electron-updater": "^5.3.0", "element-plus": "^2.3.9", "highlight.js": "^11.8.0", + "katex": "^0.16.8", "marked": "^5.1.2", "marked-highlight": "^2.0.1", "pinia": "^2.1.6", @@ -5122,6 +5123,29 @@ "setimmediate": "^1.0.5" } }, + "node_modules/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", @@ -11442,6 +11466,21 @@ "setimmediate": "^1.0.5" } }, + "katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "requires": { + "commander": "^8.3.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + } + } + }, "keyv": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", diff --git a/blossom-editor/package.json b/blossom-editor/package.json index ec116ce..aafe941 100644 --- a/blossom-editor/package.json +++ b/blossom-editor/package.json @@ -36,6 +36,7 @@ "electron-updater": "^5.3.0", "element-plus": "^2.3.9", "highlight.js": "^11.8.0", + "katex": "^0.16.8", "marked": "^5.1.2", "marked-highlight": "^2.0.1", "pinia": "^2.1.6", @@ -64,4 +65,4 @@ "vue": "^3.3.4", "vue-tsc": "^1.8.8" } -} \ No newline at end of file +} diff --git a/blossom-editor/src/renderer/src/assets/styles/bl-preview.scss b/blossom-editor/src/renderer/src/assets/styles/bl-preview.scss index c1934bf..5f7f9ae 100644 --- a/blossom-editor/src/renderer/src/assets/styles/bl-preview.scss +++ b/blossom-editor/src/renderer/src/assets/styles/bl-preview.scss @@ -356,4 +356,18 @@ font-weight: 700 } } +} + +.bl-preview-analysis-fail-block { + width: 100%; + padding: 30px; + background-color: #000000; + color: #ffffff; +} + +.bl-preview-analysis-fail-inline { + display: inline-block; + padding: 0 5px; + background-color: #000000; + color: #ffffff; } \ No newline at end of file diff --git a/blossom-editor/src/renderer/src/assets/utils/util.ts b/blossom-editor/src/renderer/src/assets/utils/util.ts index 45870f5..823e305 100644 --- a/blossom-editor/src/renderer/src/assets/utils/util.ts +++ b/blossom-editor/src/renderer/src/assets/utils/util.ts @@ -209,7 +209,20 @@ export const randomBoolean = (): boolean => { return Math.random() >= 0.5 } -randomBoolean() +/** + * html 反转义 + * @param str + * @returns + */ +export const escape2Html = (str) => { + //1.首先动态创建一个容器标签元素,如DIV + let temp = document.createElement("div"); + //2.然后将要转换的字符串设置为这个元素的innerHTML(ie,火狐,google都支持) + temp.innerHTML = str; + //3.最后返回这个元素的innerText或者textContent,即得到经过HTML解码的字符串了。 + let output = temp.innerText || temp.textContent; + return output; +} export const getSolar = (): string => { let today = new Date(); diff --git a/blossom-editor/src/renderer/src/views/article/ArticleIndex.scss b/blossom-editor/src/renderer/src/views/article/ArticleIndex.scss index 8fd9993..7e06e07 100644 --- a/blossom-editor/src/renderer/src/views/article/ArticleIndex.scss +++ b/blossom-editor/src/renderer/src/views/article/ArticleIndex.scss @@ -172,8 +172,12 @@ font-size: 14px; word-wrap: break-word; padding: 10px 20px 0 20px; - } + :deep(.katex) { + font-size: 1.1em; + font-family: 'KaTeX_Size1'; + } + } } .editor-status { diff --git a/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue b/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue index 8ca33ef..2094509 100644 --- a/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue +++ b/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue @@ -203,7 +203,8 @@ import Notify from '@renderer/components/Notify' // codemirror import { CmWrapper } from './codemirror' // marked -import marked, { renderBlockquote, renderCode, renderHeading, renderImage, renderTable } from './markedjs' +import marked, { renderBlockquote, renderCode, renderCodespan, renderHeading, renderImage, renderTable, tokenizerCodespan } from './markedjs' + // 快捷键注册 import type { shortcutFunc } from '@renderer/assets/utils/ShortcutRegister' import ShortcutRegistrant from '@renderer/assets/utils/ShortcutRegister' @@ -564,9 +565,13 @@ let parseTocAndReferences: boolean = true // 解析 markdown 时, 是否将图 let isDebounce: boolean = false // 是否在渲染时设置防抖, 切换文档时不用防抖渲染 /** - * 自定义解析 + * 自定义渲染 */ const renderer = { + table(header: string, body: string) { return renderTable(header, body) }, + blockquote(quote: string) { return renderBlockquote(quote) }, + code(code: string, language: string | undefined, _isEscaped: boolean) { return renderCode(code, language, _isEscaped) }, + codespan(src: string) { return renderCodespan(src) }, heading(text: any, level: number) { const realLevel = level if (parseTocAndReferences) { @@ -574,9 +579,6 @@ const renderer = { } return renderHeading(text, level) }, - table(header: string, body: string) { return renderTable(header, body) }, - blockquote(quote: string) { return renderBlockquote(quote) }, - code(code: string, language: string | undefined, _isEscaped: boolean) { return renderCode(code, language, _isEscaped) }, image(href: string | null, _title: string | null, text: string) { if (parseTocAndReferences) { articleImg.value.push({ targetId: 0, targetName: text, targetUrl: href as string, type: 10 }) @@ -625,7 +627,17 @@ const renderer = { } } -marked.use({ renderer }) +/** + * 自定义解析 + */ +const tokenizer = { + codespan(src: string): any { return tokenizerCodespan(src) } +} + +marked.use({ + tokenizer: tokenizer, + renderer: renderer +}) /** * 解析 markdown 为 html, 并将 html 赋值给 articleHtml diff --git a/blossom-editor/src/renderer/src/views/article/markedjs.ts b/blossom-editor/src/renderer/src/views/article/markedjs.ts index 8cb24bb..6452907 100644 --- a/blossom-editor/src/renderer/src/views/article/markedjs.ts +++ b/blossom-editor/src/renderer/src/views/article/markedjs.ts @@ -1,11 +1,13 @@ import { isBlank } from '@renderer/assets/utils/obj' +import { escape2Html } from '@renderer/assets/utils/util' import { marked } from 'marked' import { markedHighlight } from "marked-highlight" import hljs from 'highlight.js' +import katex from 'katex' +import 'katex/dist/katex.min.css' // import 'highlight.js/styles/atom-one-light.css'; // import 'highlight.js/styles/base16/darcula.css'; -//#region ----------------------------------------< marked >-------------------------------------- marked.use({ async: true, pedantic: false, @@ -23,6 +25,21 @@ marked.use(markedHighlight({ } })) +//#region ----------------------------------------< tokenizer >-------------------------------------- +export const tokenizerCodespan = (src: string): any => { + const match = src.match(/^\$+([^\$\n]+?)\$+/); + if (match) { + let result = { + type: 'codespan', + raw: match[0], + text: match[0] + } + return result + } + return false; +} + +//#region ----------------------------------------< renderer >-------------------------------------- /** * 标题解析为 TOC 集合, 增加锚点跳转 @@ -70,9 +87,10 @@ export const renderBlockquote = (quote: string) => { /** * 自定义代码块内容解析: * 1. bilibili - * 格式为: ```bilibili$$bvid$$w100$$h100 + * 格式为: ```bilibili$$bvid$$w100$$h100 + * 官方使用文档: https://player.bilibili.com/ * - * 官方使用文档: https://player.bilibili.com/ + * 2. katex * * @param code 解析后的 HTML 代码 * @param language 语言 @@ -83,6 +101,24 @@ export const renderCode = (code: string, language: string | undefined, _isEscape language = 'text' } + if (language === 'katex') { + let escape = escape2Html(code) + try { + return katex.renderToString(escape, { + throwOnError: true, + displayMode: true, + output: 'html' + }); + } catch (error) { + console.error(error); + return `
+ Katex 语法解析失败!
+ ${error}

+ 你可以尝试前往 Katex 官网来校验你的公式, 或者查看相关文档: https://katex.org/#demo +
` + } + } + if (language.startsWith('bilibili')) { let bvid = '' let width = '100%' @@ -119,9 +155,28 @@ export const renderCode = (code: string, language: string | undefined, _isEscape scrolling="no" border="0" frameborder="no" framespacing="0" src="https://player.bilibili.com/player.html?bvid=${bvid}&page=1&autoplay=0" >` } + return `
${code}
` } +export const renderCodespan = (src: string) => { + let arr = src.match(/^\$+([^\$\n]+?)\$+/); + if (arr != null && arr.length > 0) { + try { + return katex.renderToString(arr[1], { + throwOnError: true, + output: 'html' + }); + } catch (error) { + console.error(error); + return `
+ Katex 语法解析失败! 你可以尝试前往 Katex 官网 来校验你的公式。 +
` + } + } + return `${src}` +} + /** * 拓展图片设置 * ![照片A$$shadow$$w100]() @@ -158,4 +213,6 @@ export const renderImage = (href: string | null, _title: string | null, text: st

` } +//#endregion + export default marked \ No newline at end of file