mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-21 18:37:43 +08:00
201 lines
5.7 KiB
JavaScript
201 lines
5.7 KiB
JavaScript
import katex from 'katex'
|
||
import Quill from 'quill'
|
||
import { getChromeVersion, htmlEscape } from '../utils/index'
|
||
import { getBaseStyleText, getFontStyleText } from './FormulaStyle'
|
||
|
||
let extended = false
|
||
const QuillFormula = Quill.import('formats/formula')
|
||
|
||
// 数学公式支持插件
|
||
// 该插件在富文本模式下可用
|
||
class Formula {
|
||
// 构造函数
|
||
constructor(opt) {
|
||
this.opt = opt
|
||
this.mindMap = opt.mindMap
|
||
window.katex = katex
|
||
this.init()
|
||
this.config = this.getKatexConfig()
|
||
this.cssEl = null
|
||
this.addStyle()
|
||
this.extendQuill()
|
||
this.onDestroy = this.onDestroy.bind(this)
|
||
this.mindMap.on('beforeDestroy', this.onDestroy)
|
||
}
|
||
|
||
onDestroy() {
|
||
const instanceCount = Object.getPrototypeOf(this.mindMap).constructor
|
||
.instanceCount
|
||
// 如果思维导图实例数量变成0了,那么就恢复成默认的
|
||
if (instanceCount <= 1) {
|
||
extended = false
|
||
Quill.register('formats/formula', QuillFormula, true)
|
||
}
|
||
}
|
||
|
||
init() {
|
||
if (this.mindMap.opt.enableEditFormulaInRichTextEdit) {
|
||
this.mindMap.opt.transformRichTextOnEnterEdit =
|
||
this.latexRichToText.bind(this)
|
||
this.mindMap.opt.beforeHideRichTextEdit = this.formatLatex.bind(this)
|
||
}
|
||
}
|
||
|
||
// 获取katex配置
|
||
getKatexConfig() {
|
||
const config = {
|
||
throwOnError: false,
|
||
errorColor: '#f00',
|
||
output: 'mathml' // 默认只输出公式
|
||
}
|
||
let { getKatexOutputType } = this.mindMap.opt
|
||
getKatexOutputType =
|
||
getKatexOutputType ||
|
||
function () {
|
||
// Chrome内核100以下,mathml配置公式无法正确渲染
|
||
const chromeVersion = getChromeVersion()
|
||
if (chromeVersion && chromeVersion <= 100) {
|
||
return 'html'
|
||
}
|
||
}
|
||
const output = getKatexOutputType() || 'mathml'
|
||
config.output = ['mathml', 'html'].includes(output) ? output : 'mathml'
|
||
return config
|
||
}
|
||
|
||
// 修改formula格式工具
|
||
extendQuill() {
|
||
if (extended) return
|
||
extended = true
|
||
|
||
const self = this
|
||
|
||
class CustomFormulaBlot extends QuillFormula {
|
||
static create(value) {
|
||
let node = super.create(value)
|
||
if (typeof value === 'string') {
|
||
katex.render(value, node, self.config)
|
||
node.setAttribute('data-value', htmlEscape(value))
|
||
}
|
||
return node
|
||
}
|
||
}
|
||
|
||
Quill.register('formats/formula', CustomFormulaBlot, true)
|
||
}
|
||
|
||
getStyleText() {
|
||
const { katexFontPath } = this.mindMap.opt
|
||
let text = ''
|
||
if (this.config.output === 'html') {
|
||
text = getFontStyleText(katexFontPath)
|
||
}
|
||
text += getBaseStyleText()
|
||
return text
|
||
}
|
||
|
||
addStyle() {
|
||
this.cssEl = document.createElement('style')
|
||
this.cssEl.type = 'text/css'
|
||
this.cssEl.innerHTML = this.getStyleText()
|
||
document.head.appendChild(this.cssEl)
|
||
}
|
||
|
||
removeStyle() {
|
||
document.head.removeChild(this.cssEl)
|
||
}
|
||
|
||
// 给指定的节点插入指定公式
|
||
insertFormulaToNode(node, formula) {
|
||
let richTextPlugin = this.mindMap.richText
|
||
richTextPlugin.showEditText({ node })
|
||
richTextPlugin.quill.insertEmbed(
|
||
richTextPlugin.quill.getLength() - 1,
|
||
'formula',
|
||
formula
|
||
)
|
||
richTextPlugin.setTextStyleIfNotRichText(richTextPlugin.node)
|
||
richTextPlugin.hideEditText([node])
|
||
}
|
||
|
||
// 将公式富文本转换为公式源码
|
||
latexRichToText(nodeText) {
|
||
if (nodeText.indexOf('class="ql-formula"') !== -1) {
|
||
const parser = new DOMParser()
|
||
const doc = parser.parseFromString(nodeText, 'text/html')
|
||
const els = doc.getElementsByClassName('ql-formula')
|
||
for (const el of els)
|
||
nodeText = nodeText.replace(
|
||
el.outerHTML,
|
||
`\$${el.getAttribute('data-value')}\$`
|
||
)
|
||
}
|
||
return nodeText
|
||
}
|
||
|
||
// 使用格式化的 latex 字符串内容更新 quill 内容:输入 $*****$
|
||
formatLatex(richText) {
|
||
const contents = richText.quill.getContents()
|
||
const ops = contents.ops
|
||
let mod = false
|
||
for (let i = ops.length - 1; i >= 0; i--) {
|
||
const op = ops[i]
|
||
const insert = op.insert
|
||
if (insert && typeof insert !== 'object' && insert !== '\n') {
|
||
if (/\$.+?\$/g.test(insert)) {
|
||
const m = [...insert.matchAll(/\$.+?\$/g)]
|
||
const arr = insert.split(/\$.+?\$/g)
|
||
for (let j = m.length - 1; j >= 0; j--) {
|
||
const exp = m[j] && m[j][0] ? m[j][0].slice(1, -1) || null : null // $...$ 之间的表达式
|
||
if (exp !== null && exp.trim().length > 0) {
|
||
const isLegal = this.checkFormulaIsLegal(exp)
|
||
if (isLegal) {
|
||
arr.splice(j + 1, 0, { insert: { formula: exp } }) // 添加到对应位置之后
|
||
mod = true
|
||
} else {
|
||
arr.splice(j + 1, 0, '')
|
||
}
|
||
} else arr.splice(j + 1, 0, '') // 表达式为空时,占位
|
||
}
|
||
while (arr.length > 0) {
|
||
let v = arr.pop()
|
||
if (typeof v === 'string') {
|
||
if (v.length < 1) continue
|
||
v = { insert: v }
|
||
}
|
||
v['attributes'] = ops[i]['attributes']
|
||
ops.splice(i + 1, 0, v)
|
||
}
|
||
ops.splice(i, 1) // 删除原来的字符串
|
||
}
|
||
}
|
||
}
|
||
if (mod) richText.quill.setContents(contents)
|
||
}
|
||
|
||
checkFormulaIsLegal(str) {
|
||
try {
|
||
katex.renderToString(str)
|
||
return true
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 插件被移除前做的事情
|
||
beforePluginRemove() {
|
||
this.removeStyle()
|
||
this.mindMap.off('beforeDestroy', this.onDestroy)
|
||
}
|
||
|
||
// 插件被卸载前做的事情
|
||
beforePluginDestroy() {
|
||
this.removeStyle()
|
||
this.mindMap.off('beforeDestroy', this.onDestroy)
|
||
}
|
||
}
|
||
|
||
Formula.instanceName = 'formula'
|
||
|
||
export default Formula
|