mirror of
https://github.com/blossom-editor/blossom
synced 2024-11-17 22:48:03 +08:00
支持 katex
This commit is contained in:
parent
df22c0847a
commit
a13a210e93
39
blossom-editor/package-lock.json
generated
39
blossom-editor/package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 `<div class='bl-preview-analysis-fail-block'>
|
||||
Katex 语法解析失败!<br/>
|
||||
${error}<br/><br/>
|
||||
你可以尝试前往 Katex 官网来校验你的公式, 或者查看相关文档: <a href='https://katex.org/#demo' target='_blank'>https://katex.org/#demo</a>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
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" ></iframe>`
|
||||
}
|
||||
|
||||
return `<pre><code class="hljs language-${language}">${code}</code></pre>`
|
||||
}
|
||||
|
||||
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 `<div class='bl-preview-analysis-fail-inline'>
|
||||
Katex 语法解析失败! 你可以尝试前往<a href='https://katex.org/#demo' target='_blank'> Katex 官网</a> 来校验你的公式。
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
return `<code>${src}</code>`
|
||||
}
|
||||
|
||||
/**
|
||||
* 拓展图片设置
|
||||
* ![照片A$$shadow$$w100]()
|
||||
@ -158,4 +213,6 @@ export const renderImage = (href: string | null, _title: string | null, text: st
|
||||
</p>`
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export default marked
|
Loading…
Reference in New Issue
Block a user