UNPKG

@xmzang/wang_editor_formula_custom

Version:
249 lines (209 loc) 7.8 kB
/** * @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