@xmzang/wang_editor_formula_custom
Version:
wangEditor formula 公式自定义插件
249 lines (209 loc) • 7.8 kB
text/typescript
/**
* @description insert formula menu
* @author wangfupeng
*/
import { IModalMenu } from '@wangeditor/editor'
import './custom.css'
import {
DomEditor,
IDomEditor,
SlateNode,
SlateRange,
t,
genModalTextareaElems,
genModalButtonElems,
} from '@wangeditor/editor'
import { SIGMA_SVG } from '../../constants/icon-svg'
import $, { Dom7Array, DOMElement } from '../../utils/dom'
import { genRandomStr } from '../../utils/util'
import { FormulaElement } from '../custom-types'
import { customSymbols, customFormula } from '../../utils/actionBut'
import katex from 'katex'
/**
* 生成唯一的 DOM ID
*/
function genDomID(): string {
return genRandomStr('w-e-insert-formula')
}
class InsertFormulaMenu implements IModalMenu {
readonly title = t('formula.insert')
readonly iconSvg = SIGMA_SVG
readonly tag = 'button'
readonly showModal = true // 点击 button 时显示 modal
readonly modalWidth = 300
private $content: Dom7Array | null = null
private readonly textareaId = genDomID()
private readonly buttonId = genDomID()
// 插入默认格式公式class组名
private readonly formatClass = genDomID()
// 预览区域id
private readonly previewId = genDomID()
getValue(editor: IDomEditor): string | boolean {
// 插入菜单,不需要 value
return ''
}
isActive(editor: IDomEditor): boolean {
// 任何时候,都不用激活 menu
return false
}
exec(editor: IDomEditor, value: string | boolean) {
// 点击菜单时,弹出 modal 之前,不需要执行其他代码
// 此处空着即可
}
isDisabled(editor: IDomEditor): boolean {
const { selection } = editor
if (selection == null) return true
if (SlateRange.isExpanded(selection)) return true // 选区非折叠,禁用
const selectedElems = DomEditor.getSelectedElems(editor)
const hasVoidElem = selectedElems.some(elem => editor.isVoid(elem))
if (hasVoidElem) return true // 选中了 void 元素,禁用
const hasPreElem = selectedElems.some(elem => DomEditor.getNodeType(elem) === 'pre')
if (hasPreElem) return true // 选中了 pre 原则,禁用
return false
}
getModalPositionNode(editor: IDomEditor): SlateNode | null {
return null // modal 依据选区定位
}
getModalContentElem(editor: IDomEditor): DOMElement {
const { textareaId, buttonId, previewId, formatClass } = this
// eslint-disable-next-line no-debugger
const [textareaContainerElem, textareaElem] = genModalTextareaElems(
t('formula.formula'),
textareaId,
t('formula.placeholder')
)
const $textarea = $(textareaElem)
const [buttonContainerElem] = genModalButtonElems(buttonId, t('formula.ok'))
if (this.$content == null) {
// 第一次渲染
const $content = $('<div></div>')
// 绑定事件(第一次渲染时绑定,不要重复绑定)
$content.on('click', `#${buttonId}`, e => {
e.preventDefault()
const value = $content.find(`#${textareaId}`).val().trim()
this.insertFormula(editor, value, $content)
editor.hidePanelOrModal() // 隐藏 modal
})
// 绑定插入事件
$content.on('click', `.${buttonId}`, e => {
e.preventDefault()
const value = $content.find(`#${textareaId}`).val().trim()
this.insertFormula(editor, value, $content)
})
// 预设字符输入点击
$content.on('click', '.formula_text', e => {
e.preventDefault()
console.log(document.activeElement)
const target = e.target as EventTarget | null
if (target) {
// 获取textarea内容
const textStr = $content.find(`#${textareaId}`).val().trim()
// 获取当前textarea光标位置
const textarea = $content.find(`#${textareaId}`)[0] as HTMLTextAreaElement
const activeIndex = textarea.selectionStart
// 获取当前点击的字符内容
const value = (target as HTMLElement).dataset.formulaValue
// 拼接字符串
const resultTest = textStr.slice(0, activeIndex) + value + textStr.slice(activeIndex)
$content.find(`#${textareaId}`).val(resultTest)
// 展示区域
const displayAreaElem = $content.find(`#${previewId}`)[0] as HTMLTextAreaElement
this.displayAreainsertFormula(displayAreaElem, resultTest, $content)
}
})
// 监听输入框内容变动
$content.on('input', `#${textareaId}`, e => {
const textStr = $content.find(`#${textareaId}`).val().trim()
// 展示区域
const displayAreaElem = $content.find(`#${previewId}`)[0] as HTMLTextAreaElement
this.displayAreainsertFormula(displayAreaElem, textStr, $content)
})
// 记录属性,重要
this.$content = $content
}
const $content = this.$content
$content.html('') // 先清空内容
// 常用公式
const commonFormulasElem = this.additionalFeatures()
// 展示区域
const displayArea = this.contentDisplayArea()
// 常用符号
const commonSymbolsElem = this.additionalTopFeatures()
// 展示区域
// append textarea and button
const $editAreaWrap = $('<div class="edit_area_wrap"></div>')
const $editArea = $('<div id="' + formatClass + '"></div>')
// 插入位置
$content.append(commonSymbolsElem)
// 输入
$editArea.append(textareaContainerElem)
// 展示
$editArea.append(displayArea)
$editAreaWrap.append($editArea)
// 常用公式
$editAreaWrap.append(commonFormulasElem)
$content.append($editAreaWrap)
// 确认按钮
$content.append(buttonContainerElem)
// 设置 input val
$textarea.val('')
// focus 一个 input(异步,此时 DOM 尚未渲染)
setTimeout(() => {
$textarea.focus()
})
return $content[0]
}
// 自定义内容展示区
private contentDisplayArea() {
const { previewId } = this
const $addition = $('<div class="content_display_area" id="' + previewId + '"></div>')
return $addition
}
// 自定义加入插入/编辑弹窗内容(常用符号)
private additionalTopFeatures() {
let htmlStr = '<div class="formula_text_top_wrap">'
customSymbols.forEach(item => {
htmlStr += `<div class="formula_text_top_wrap"><div data-formula-value="${item.value}" class="formula_text">${item.name}</div></div>`
})
htmlStr += '</div>'
const $addition = $(htmlStr)
return $addition
}
// 自定义加入插入/编辑弹窗内容(常用公式)
private additionalFeatures() {
const $addition = $(
'<div class="formula_text_wrap"><div data-formula-value="\\dfrac{a}{b}\\pm \\dfrac{c}{d}= \\dfrac{ad \\pm bc}{bd}" class="formula_text">2222</div></div>'
)
return $addition
}
// 展示区显示内容
private displayAreainsertFormula(show: Element, value: string, content: any) {
const { previewId } = this
if (!value) return
show.innerHTML = ''
const span = document.createElement('span')
span.style.display = 'inline-block'
content.find(`#${previewId}`)[0].append(span)
katex.render(value, span, {
throwOnError: false,
})
}
private insertFormula(editor: IDomEditor, value: string, content: any) {
if (!value) return
// 还原选区
editor.restoreSelection()
if (this.isDisabled(editor)) return
const formulaElem: FormulaElement = {
type: 'formula',
value,
children: [{ text: '' }], // void node 需要有一个空 text
}
const span = document.createElement('span')
span.style.display = 'inline-block'
katex.render(value, span, {
throwOnError: false,
})
editor.insertNode(formulaElem)
}
}
export default InsertFormulaMenu