UNPKG

wangeditor-plugin-formula-plus

Version:
200 lines (174 loc) 6.74 kB
import { DomEditor, IDomEditor } from '@wangeditor/editor' import $, { DOMElement } from '../utils/dom' import { Editor } from 'slate' import autoComplete from './components/AutoComplete' import { formulaRender } from '../utils/util' import { KatexOptions } from 'katex' export const IS_MAC = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent) export interface IFormulaConfig { formulaConfig: { katexOptions?: KatexOptions formulaRender?: (value: string, el: HTMLElement) => void } } export function getFormulaConfig(editor: IDomEditor): IFormulaConfig['formulaConfig'] { const { EXTEND_CONF } = editor.getConfig() const { formulaConfig = {} } = (EXTEND_CONF || {}) as IFormulaConfig return formulaConfig } export function formulaRenderWithEditor(editor: IDomEditor, value: string, elem: HTMLElement) { const { katexOptions, formulaRender: katexRender } = getFormulaConfig(editor) formulaRender(value, elem, { katexOptions, katexRender }) } const prefixClassName = (className: string) => `w-e-formula-${className}` export function isMenuDisabled(editor: IDomEditor, mark?: string): boolean { if (editor.selection == null) return true const [match] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) if (type === 'pre') return true // 代码块 if (Editor.isVoid(editor, n)) return true // void node return false }, universal: true, }) // 命中,则禁用 if (match) return true return false } export const stringToHtml = (s: string): string => { return s .replace(/\n$/g, '_65a5ba9e52a3761bd68eb531e9794ae12c1d34c167f071a6b239c855b5cf57b2') //最后一个换行符 .replace(/\n/g, '_dafd41284316e72de3a0b07bd1262cd142151708353024244238eec3699e22ae') //普通换行符 .replace(/\s/g, '_e613de3e0d3b707ade3d6a289abbab35c67d4476dc12211865d70a493d7e144c') //空格 .replace( /(\+|-|\*|\/|=|>|<|!|\^|\(|\)|%)/g, '<span style="color: SeaGreen;" class="hl">$1</span>' ) .replace(/(\\{2})/g, '<span style="color: orange;" class="hl">$1</span>') .replace(/(\\[a-zA-Z]+)/g, '<span style="color: #008de9;" class="hl">$1</span>') .replace( /<span style="color: #008de9;" class="hl">(\\begin|\\end)<\/span>/g, '<span style="color:orange;" class="hl">$1</span>' ) .replace(/([{}])/g, '<span style="color: #608b4e;" class="hl">$1</span>') .replace(/_dafd41284316e72de3a0b07bd1262cd142151708353024244238eec3699e22ae/g, '<br>') .replace(/_e613de3e0d3b707ade3d6a289abbab35c67d4476dc12211865d70a493d7e144c/g, '&nbsp;') .replace( /_65a5ba9e52a3761bd68eb531e9794ae12c1d34c167f071a6b239c855b5cf57b2/g, "<br><span style='color:rgba(0,0,0,0);'>_</span>" ) } function insertText(textarea, text) { const currentPosition = textarea.selectionStart const startValue = textarea.value.substring(0, currentPosition) const startPos = Math.max(0, startValue.lastIndexOf('\\')) // 插入文本 textarea.value = textarea.value.substring(0, startPos) + text + textarea.value.substring(currentPosition) // 计算新的光标位置并设置 const index = text.lastIndexOf('{}') const newPos = index === -1 ? startPos + text.length : startPos + index + 1 textarea.selectionStart = textarea.selectionEnd = newPos } /** * 生成 modal textarea elems * @param labelText label text * @param textareaId input dom id * @param placeholder input placeholder * @returns [$container, $textarea, $textareaBox] */ export function genModalTextareaElems( editor: IDomEditor, labelText: string, textareaId: string, placeholder?: string ) { const $container = $('<div class="babel-container"></div>') $container.append(`<div class="${prefixClassName('modal-label')}">${labelText}</div>`) const $textareaBox = $(`<div class="${prefixClassName('modal-textarea-box')}"></div>`) const $textarea = $( `<textarea class="${prefixClassName( 'modal-textarea' )}" type="text" id="${textareaId}" placeholder="${placeholder || ''}"></textarea>` ) const $textareaContent = $( `<div class="${prefixClassName('modal-textarea')} ${prefixClassName( 'modal-textarea--content' )}"></div>` ) const $textareaCursor = $( `<div class="${prefixClassName('modal-textarea')} ${prefixClassName( 'modal-textarea--cursor' )}"></div>` ) const $render = $('<div class="w-e-formula-modal-latex"></div>') const renderLatex = (str: string) => { formulaRenderWithEditor(editor, str, $render[0] as any) } $textareaBox.append($textarea) $textareaBox.append($textareaContent) $textareaBox.append($textareaCursor) $container.append($textareaBox) let keyword = '' let isShowAutoComplete = false const cursorElem = document.createElement('span') cursorElem.textContent = '|' $textarea.on('input', (e: any) => { const cursorPosition = e.target.selectionStart const cursorBeforeText = ($textarea.val() || '').slice(0, cursorPosition) $textareaCursor[0].textContent = cursorBeforeText $textareaCursor.append(cursorElem) // 输入字符和删除单个字符 if (['insertText', 'deleteContentBackward'].includes(e.inputType)) { if (e.data === '\\' && !isShowAutoComplete) { // 输入 \ 触发 keyword = e.data isShowAutoComplete = true autoComplete .open({ input: $textarea[0], target: cursorElem, editor, }) .then(symbol => { insertText($textarea[0], symbol) setTextareaValue($textarea.val()) isShowAutoComplete = false }) .catch(() => { isShowAutoComplete = false }) } else { if (e.inputType === 'deleteContentBackward') { keyword = keyword.slice(0, -1) } else { if (keyword.startsWith('\\')) { keyword = `\\${cursorBeforeText.split('\\').pop()}` } } } } else { keyword = '' } const value = $textarea.val() const html = stringToHtml(value) $textareaContent[0].innerHTML = html renderLatex($textarea.val()) $textarea[0].setAttribute('data-cursor-value', keyword) }) $textarea.on('scroll', e => { $textareaContent[0].scrollLeft = (e.target as any).scrollLeft $textareaContent[0].scrollTop = (e.target as any).scrollTop }) const setTextareaValue = (value: string) => { $textarea.val(value) renderLatex(value) const html = stringToHtml(value) $textareaContent[0].innerHTML = html } setTimeout(() => { $textarea.focus() }) return { textareaContainerElem: $container[0], setTextareaValue, renderElem: $render[0] } }