支持 katex

This commit is contained in:
jasminexz 2023-08-13 14:13:44 +08:00
parent df22c0847a
commit a13a210e93
7 changed files with 152 additions and 12 deletions

View File

@ -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",

View File

@ -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"
}
}
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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 {

View File

@ -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

View File

@ -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