From 10a40d128eb8f02f9ffb6c3f0c2715fa720a1dbe Mon Sep 17 00:00:00 2001 From: cycleccc <2991205548@qq.com> Date: Sat, 9 Nov 2024 22:50:32 +0800 Subject: [PATCH] feat: add formula plugin --- .changeset/tame-mice-marry.md | 5 + packages/plugin-formula/CHANGELOG.md | 0 packages/plugin-formula/package.json | 56 ++++++ packages/plugin-formula/rollup.config.js | 37 ++++ .../plugin-formula/src/constants/icon-svg.ts | 16 ++ packages/plugin-formula/src/index.ts | 11 ++ .../plugin-formula/src/module/custom-types.ts | 14 ++ .../plugin-formula/src/module/elem-to-html.ts | 23 +++ packages/plugin-formula/src/module/index.ts | 24 +++ packages/plugin-formula/src/module/local.ts | 26 +++ .../src/module/menu/EditFormula.ts | 164 ++++++++++++++++++ .../src/module/menu/InsertFormula.ts | 149 ++++++++++++++++ .../plugin-formula/src/module/menu/index.ts | 21 +++ .../src/module/parse-elem-html.ts | 30 ++++ packages/plugin-formula/src/module/plugin.ts | 37 ++++ .../plugin-formula/src/module/render-elem.ts | 54 ++++++ .../src/register-custom-elem/README.md | 3 + .../src/register-custom-elem/index.ts | 67 +++++++ .../src/register-custom-elem/native-shim.ts | 51 ++++++ packages/plugin-formula/src/utils/dom.ts | 34 ++++ packages/plugin-formula/src/utils/util.ts | 19 ++ packages/plugin-formula/tsconfig.json | 8 + yarn.lock | 46 ++++- 23 files changed, 893 insertions(+), 2 deletions(-) create mode 100644 .changeset/tame-mice-marry.md create mode 100644 packages/plugin-formula/CHANGELOG.md create mode 100644 packages/plugin-formula/package.json create mode 100644 packages/plugin-formula/rollup.config.js create mode 100644 packages/plugin-formula/src/constants/icon-svg.ts create mode 100644 packages/plugin-formula/src/index.ts create mode 100644 packages/plugin-formula/src/module/custom-types.ts create mode 100644 packages/plugin-formula/src/module/elem-to-html.ts create mode 100644 packages/plugin-formula/src/module/index.ts create mode 100644 packages/plugin-formula/src/module/local.ts create mode 100644 packages/plugin-formula/src/module/menu/EditFormula.ts create mode 100644 packages/plugin-formula/src/module/menu/InsertFormula.ts create mode 100644 packages/plugin-formula/src/module/menu/index.ts create mode 100644 packages/plugin-formula/src/module/parse-elem-html.ts create mode 100644 packages/plugin-formula/src/module/plugin.ts create mode 100644 packages/plugin-formula/src/module/render-elem.ts create mode 100644 packages/plugin-formula/src/register-custom-elem/README.md create mode 100644 packages/plugin-formula/src/register-custom-elem/index.ts create mode 100644 packages/plugin-formula/src/register-custom-elem/native-shim.ts create mode 100644 packages/plugin-formula/src/utils/dom.ts create mode 100644 packages/plugin-formula/src/utils/util.ts create mode 100644 packages/plugin-formula/tsconfig.json diff --git a/.changeset/tame-mice-marry.md b/.changeset/tame-mice-marry.md new file mode 100644 index 000000000..9af71ce18 --- /dev/null +++ b/.changeset/tame-mice-marry.md @@ -0,0 +1,5 @@ +--- +'@wangeditor-next/plugin-formula': patch +--- + +feat: add formula plugin diff --git a/packages/plugin-formula/CHANGELOG.md b/packages/plugin-formula/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugin-formula/package.json b/packages/plugin-formula/package.json new file mode 100644 index 000000000..2ce873c95 --- /dev/null +++ b/packages/plugin-formula/package.json @@ -0,0 +1,56 @@ +{ + "name": "@wangeditor-next/plugin-formula", + "version": "0.0.0", + "description": "wangEditor next formula 公式", + "author": "cycleccc <2991205548@qq.com>", + "type": "module", + "homepage": "https://github.com/cycleccc/wangEditor-next#readme", + "license": "MIT", + "types": "dist/plugin-formula/src/index.d.ts", + "main": "dist/index.js", + "module": "dist/index.mjs", + "exports": { + ".": { + "types": "./dist/plugin-formula/src/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./dist/css/style.css": "./dist/css/style.css" + }, + "directories": { + "lib": "dist" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/cycleccc/wangEditor-next.git" + }, + "scripts": { + "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js", + "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", + "build": "cross-env NODE_ENV=production rollup -c rollup.config.js", + "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js", + "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js" + }, + "bugs": { + "url": "https://github.com/cycleccc/wangEditor/issues" + }, + "devDependencies": { + "katex": "^0.15.2", + "rollup-plugin-string": "^3.0.0" + }, + "peerDependencies": { + "@wangeditor-next/editor": "5.6.10", + "katex": "^0.15.2", + "snabbdom": "^3.1.0" + }, + "dependencies": { + "dom7": "^3.0.0", + "nanoid": "^3.2.0" + } +} diff --git a/packages/plugin-formula/rollup.config.js b/packages/plugin-formula/rollup.config.js new file mode 100644 index 000000000..55c7e55f7 --- /dev/null +++ b/packages/plugin-formula/rollup.config.js @@ -0,0 +1,37 @@ +import { createRollupConfig } from '@wangeditor-next-shared/rollup-config' +import { string } from 'rollup-plugin-string' + +import pkg from './package.json' assert { type: 'json' } + +const name = 'WangEditorFormulaPlugin' + +const configList = [] + +// esm +const esmConf = createRollupConfig({ + output: { + file: pkg.module, + format: 'esm', + name, + }, +}) + +configList.push(esmConf) + +// umd +const umdConf = createRollupConfig({ + output: { + file: pkg.main, + format: 'umd', + name, + }, + plugins: [ + string({ + include: '**/*.css', + }), + ], +}) + +configList.push(umdConf) + +export default configList diff --git a/packages/plugin-formula/src/constants/icon-svg.ts b/packages/plugin-formula/src/constants/icon-svg.ts new file mode 100644 index 000000000..45cbd1923 --- /dev/null +++ b/packages/plugin-formula/src/constants/icon-svg.ts @@ -0,0 +1,16 @@ +/** + * @description icon svg + * @author wangfupeng + */ + +/** + * 【注意】svg 字符串的长度 ,否则会导致代码体积过大 + * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293 + * 找不到再从 iconfont.com 搜索 + */ + +// 公式 +export const SIGMA_SVG = '' + +// 编辑 +export const PENCIL_SVG = '' diff --git a/packages/plugin-formula/src/index.ts b/packages/plugin-formula/src/index.ts new file mode 100644 index 000000000..bbb34233b --- /dev/null +++ b/packages/plugin-formula/src/index.ts @@ -0,0 +1,11 @@ +/** + * @description src entry + * @author wangfupeng + */ + +// 全局注册自定义组件,用于渲染公式 +import './register-custom-elem' + +import module from './module/index' + +export default module diff --git a/packages/plugin-formula/src/module/custom-types.ts b/packages/plugin-formula/src/module/custom-types.ts new file mode 100644 index 000000000..4986110d1 --- /dev/null +++ b/packages/plugin-formula/src/module/custom-types.ts @@ -0,0 +1,14 @@ +/** + * @description formula element + * @author wangfupeng + */ + +type EmptyText = { + text: '' +} + +export type FormulaElement = { + type: 'formula' + value: string + children: EmptyText[] +} diff --git a/packages/plugin-formula/src/module/elem-to-html.ts b/packages/plugin-formula/src/module/elem-to-html.ts new file mode 100644 index 000000000..62ca81388 --- /dev/null +++ b/packages/plugin-formula/src/module/elem-to-html.ts @@ -0,0 +1,23 @@ +/** + * @description elem to html + * @author wangfupeng + */ + +import { SlateElement } from '@wangeditor-next/editor' + +import { FormulaElement } from './custom-types' + +// 生成 html 的函数 +function formulaToHtml(elem: SlateElement, _childrenHtml: string): string { + const { value = '' } = elem as FormulaElement + + return `` +} + +// 配置 +const conf = { + type: 'formula', // 节点 type ,重要!!! + elemToHtml: formulaToHtml, +} + +export default conf diff --git a/packages/plugin-formula/src/module/index.ts b/packages/plugin-formula/src/module/index.ts new file mode 100644 index 000000000..14250a120 --- /dev/null +++ b/packages/plugin-formula/src/module/index.ts @@ -0,0 +1,24 @@ +/** + * @description formula module entry + * @author wangfupeng + */ + +import './local' // 多语言 + +import { IModuleConf } from '@wangeditor-next/editor' + +import elemToHtmlConf from './elem-to-html' +import { editFormulaMenuConf, insertFormulaMenuConf } from './menu/index' +import parseHtmlConf from './parse-elem-html' +import withFormula from './plugin' +import renderElemConf from './render-elem' + +const module: Partial = { + editorPlugin: withFormula, + renderElems: [renderElemConf], + elemsToHtml: [elemToHtmlConf], + parseElemsHtml: [parseHtmlConf], + menus: [insertFormulaMenuConf, editFormulaMenuConf], +} + +export default module diff --git a/packages/plugin-formula/src/module/local.ts b/packages/plugin-formula/src/module/local.ts new file mode 100644 index 000000000..4a64ea126 --- /dev/null +++ b/packages/plugin-formula/src/module/local.ts @@ -0,0 +1,26 @@ +/** + * @description 多语言 + * @author wangfupeng + */ + +import { i18nAddResources } from '@wangeditor-next/editor' + +i18nAddResources('en', { + formula: { + formula: 'Formula', + placeholder: 'Use LateX syntax', + insert: 'Insert formula', + edit: 'Edit formula', + ok: 'OK', + }, +}) + +i18nAddResources('zh-CN', { + formula: { + formula: '公式', + placeholder: '使用 LateX 语法', + insert: '插入公式', + edit: '编辑公式', + ok: '确定', + }, +}) diff --git a/packages/plugin-formula/src/module/menu/EditFormula.ts b/packages/plugin-formula/src/module/menu/EditFormula.ts new file mode 100644 index 000000000..153deb110 --- /dev/null +++ b/packages/plugin-formula/src/module/menu/EditFormula.ts @@ -0,0 +1,164 @@ +/** + * @description edit formula menu + * @author wangfupeng + */ + +import { + DomEditor, + genModalButtonElems, + genModalTextareaElems, + IDomEditor, + IModalMenu, + SlateNode, + SlateRange, + SlateTransforms, + t, +} from '@wangeditor-next/editor' + +import { PENCIL_SVG } from '../../constants/icon-svg' +import $, { Dom7Array, DOMElement } from '../../utils/dom' +import { genRandomStr } from '../../utils/util' +import { FormulaElement } from '../custom-types' + +/** + * 生成唯一的 DOM ID + */ +function genDomID(): string { + return genRandomStr('w-e-insert-formula') +} + +class EditFormulaMenu implements IModalMenu { + readonly title = t('formula.edit') + + readonly iconSvg = PENCIL_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() + + private getSelectedElem(editor: IDomEditor): FormulaElement | null { + const node = DomEditor.getSelectedNodeByType(editor, 'formula') + + if (node == null) { return null } + return node as FormulaElement + } + + /** + * 获取公式 value + * @param editor editor + */ + getValue(editor: IDomEditor): string | boolean { + const formulaElem = this.getSelectedElem(editor) + + if (formulaElem) { + return formulaElem.value || '' + } + return '' + } + + isActive(_editor: IDomEditor): boolean { + // 无需 active + 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 } // 选区非折叠,禁用 + + // 未匹配到 formula node 则禁用 + const formulaElem = this.getSelectedElem(editor) + + if (formulaElem == null) { return true } + + return false + } + + // modal 定位 + getModalPositionNode(editor: IDomEditor): SlateNode | null { + return this.getSelectedElem(editor) + } + + getModalContentElem(editor: IDomEditor): DOMElement { + const { textareaId, buttonId } = this + + 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 = $('
') + + // 绑定事件(第一次渲染时绑定,不要重复绑定) + $content.on('click', `#${buttonId}`, e => { + e.preventDefault() + const value = $content.find(`#${textareaId}`).val().trim() + + this.updateFormula(editor, value) + editor.hidePanelOrModal() // 隐藏 modal + }) + + // 记录属性,重要 + this.$content = $content + } + + const $content = this.$content + + $content.html('') // 先清空内容 + + // append textarea and button + $content.append(textareaContainerElem) + $content.append(buttonContainerElem) + + // 设置 input val + const value = this.getValue(editor) + + $textarea.val(value) + + // focus 一个 input(异步,此时 DOM 尚未渲染) + setTimeout(() => { + $textarea.focus() + }) + + return $content[0] + } + + private updateFormula(editor: IDomEditor, value: string) { + if (!value) { return } + + // 还原选区 + editor.restoreSelection() + + if (this.isDisabled(editor)) { return } + + const selectedElem = this.getSelectedElem(editor) + + if (selectedElem == null) { return } + + const path = DomEditor.findPath(editor, selectedElem) + const props: Partial = { value } + + SlateTransforms.setNodes(editor, props, { at: path }) + } +} + +export default EditFormulaMenu diff --git a/packages/plugin-formula/src/module/menu/InsertFormula.ts b/packages/plugin-formula/src/module/menu/InsertFormula.ts new file mode 100644 index 000000000..a1ca82278 --- /dev/null +++ b/packages/plugin-formula/src/module/menu/InsertFormula.ts @@ -0,0 +1,149 @@ +/** + * @description insert formula menu + * @author wangfupeng + */ + +import { + DomEditor, + genModalButtonElems, + genModalTextareaElems, + IDomEditor, + IModalMenu, + SlateNode, + SlateRange, + t, +} from '@wangeditor-next/editor' + +import { SIGMA_SVG } from '../../constants/icon-svg' +import $, { Dom7Array, DOMElement } from '../../utils/dom' +import { genRandomStr } from '../../utils/util' +import { FormulaElement } from '../custom-types' + +/** + * 生成唯一的 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() + + 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 } = this + + 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 = $('
') + + // 绑定事件(第一次渲染时绑定,不要重复绑定) + $content.on('click', `#${buttonId}`, e => { + e.preventDefault() + const value = $content.find(`#${textareaId}`).val().trim() + + this.insertFormula(editor, value) + editor.hidePanelOrModal() // 隐藏 modal + }) + + // 记录属性,重要 + this.$content = $content + } + + const $content = this.$content + + $content.html('') // 先清空内容 + + // append textarea and button + $content.append(textareaContainerElem) + $content.append(buttonContainerElem) + + // 设置 input val + $textarea.val('') + + // focus 一个 input(异步,此时 DOM 尚未渲染) + setTimeout(() => { + $textarea.focus() + }) + + return $content[0] + } + + private insertFormula(editor: IDomEditor, value: string) { + if (!value) { return } + + // 还原选区 + editor.restoreSelection() + + if (this.isDisabled(editor)) { return } + + const formulaElem: FormulaElement = { + type: 'formula', + value, + children: [{ text: '' }], // void node 需要有一个空 text + } + + editor.insertNode(formulaElem) + } +} + +export default InsertFormulaMenu diff --git a/packages/plugin-formula/src/module/menu/index.ts b/packages/plugin-formula/src/module/menu/index.ts new file mode 100644 index 000000000..1bb9594f6 --- /dev/null +++ b/packages/plugin-formula/src/module/menu/index.ts @@ -0,0 +1,21 @@ +/** + * @description formula menu entry + * @author wangfupeng + */ + +import EditFormulaMenu from './EditFormula' +import InsertFormulaMenu from './InsertFormula' + +export const insertFormulaMenuConf = { + key: 'insertFormula', // menu key ,唯一。注册之后,可配置到工具栏 + factory() { + return new InsertFormulaMenu() + }, +} + +export const editFormulaMenuConf = { + key: 'editFormula', // menu key ,唯一。注册之后,可配置到工具栏 + factory() { + return new EditFormulaMenu() + }, +} diff --git a/packages/plugin-formula/src/module/parse-elem-html.ts b/packages/plugin-formula/src/module/parse-elem-html.ts new file mode 100644 index 000000000..b428f7441 --- /dev/null +++ b/packages/plugin-formula/src/module/parse-elem-html.ts @@ -0,0 +1,30 @@ +/** + * @description parse elem html + * @author wangfupeng + */ + +import { IDomEditor, SlateDescendant, SlateElement } from '@wangeditor-next/editor' + +import { DOMElement } from '../utils/dom' +import { FormulaElement } from './custom-types' + +function parseHtml( + elem: DOMElement, + _children: SlateDescendant[], + _editor: IDomEditor, +): SlateElement { + const value = elem.getAttribute('data-value') || '' + + return { + type: 'formula', + value, + children: [{ text: '' }], // void node 必须有一个空白 text + } as FormulaElement +} + +const parseHtmlConf = { + selector: 'span[data-w-e-type="formula"]', + parseElemHtml: parseHtml, +} + +export default parseHtmlConf diff --git a/packages/plugin-formula/src/module/plugin.ts b/packages/plugin-formula/src/module/plugin.ts new file mode 100644 index 000000000..c1185d9ac --- /dev/null +++ b/packages/plugin-formula/src/module/plugin.ts @@ -0,0 +1,37 @@ +/** + * @description formula plugin + * @author wangfupeng + */ + +import { DomEditor, IDomEditor } from '@wangeditor-next/editor' + +function withFormula(editor: T) { + const { isInline, isVoid } = editor + const newEditor = editor + + // 重写 isInline + newEditor.isInline = elem => { + const type = DomEditor.getNodeType(elem) + + if (type === 'formula') { + return true + } + + return isInline(elem) + } + + // 重写 isVoid + newEditor.isVoid = elem => { + const type = DomEditor.getNodeType(elem) + + if (type === 'formula') { + return true + } + + return isVoid(elem) + } + + return newEditor +} + +export default withFormula diff --git a/packages/plugin-formula/src/module/render-elem.ts b/packages/plugin-formula/src/module/render-elem.ts new file mode 100644 index 000000000..5abebf1c3 --- /dev/null +++ b/packages/plugin-formula/src/module/render-elem.ts @@ -0,0 +1,54 @@ +/** + * @description render elem + * @author wangfupeng + */ + +import { DomEditor, IDomEditor, SlateElement } from '@wangeditor-next/editor' +import { h, VNode } from 'snabbdom' + +import { FormulaElement } from './custom-types' + +function renderFormula(elem: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode { + // 当前节点是否选中 + const selected = DomEditor.isNodeSelected(editor, elem) + + // 构建 formula vnode + const { value = '' } = elem as FormulaElement + const formulaVnode = h( + 'w-e-formula-card', + { + dataset: { value }, + }, + null, + ) + + // 构建容器 vnode + const containerVnode = h( + 'div', + { + props: { + contentEditable: false, // 不可编辑 + }, + style: { + display: 'inline-block', // inline + marginLeft: '3px', + marginRight: '3px', + border: selected // 选中/不选中,样式不一样 + ? '2px solid var(--w-e-textarea-selected-border-color)' // wangEditor 提供了 css var https://www.wangeditor.com/v5/theme.html + : '2px solid transparent', + borderRadius: '3px', + padding: '3px 3px', + }, + }, + [formulaVnode], + ) + + return containerVnode +} + +const conf = { + type: 'formula', // 节点 type ,重要!!! + renderElem: renderFormula, +} + +export default conf diff --git a/packages/plugin-formula/src/register-custom-elem/README.md b/packages/plugin-formula/src/register-custom-elem/README.md new file mode 100644 index 000000000..f09936e9a --- /dev/null +++ b/packages/plugin-formula/src/register-custom-elem/README.md @@ -0,0 +1,3 @@ +# register custom elem + +全局注册一个自定义元素 `` 用于渲染公式。 diff --git a/packages/plugin-formula/src/register-custom-elem/index.ts b/packages/plugin-formula/src/register-custom-elem/index.ts new file mode 100644 index 000000000..9b32edf50 --- /dev/null +++ b/packages/plugin-formula/src/register-custom-elem/index.ts @@ -0,0 +1,67 @@ +/** + * @description 注册自定义 elem + * @author wangfupeng + */ + +import './native-shim' + +import katex from 'katex' +// @ts-ignore +import katexStyleContent from 'katex/dist/katex.css' + +console.log(katexStyleContent) // CSS 内容作为字符串 + +class WangEditorFormulaCard extends HTMLElement { + private span: HTMLElement + + // 监听的 attr + static get observedAttributes() { + return ['data-value'] + } + + constructor() { + super() + const shadow = this.attachShadow({ mode: 'open' }) + const document = shadow.ownerDocument + + const style = document.createElement('style') + + style.innerHTML = katexStyleContent // 加载 css 文本 + shadow.appendChild(style) + + const span = document.createElement('span') + + span.style.display = 'inline-block' + shadow.appendChild(span) + this.span = span + } + + // connectedCallback() { + // // 当 custom element首次被插入文档DOM时,被调用 + // console.log('connected') + // } + // disconnectedCallback() { + // // 当 custom element从文档DOM中删除时,被调用 + // console.log('disconnected') + // } + // adoptedCallback() { + // // 当 custom element被移动到新的文档时,被调用 + // console.log('adopted') + // } + attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) { + if (name === 'data-value') { + if (oldValue === newValue) { return } + this.render(newValue || '') + } + } + + private render(value: string) { + katex.render(value, this.span, { + throwOnError: false, + }) + } +} + +if (!window.customElements.get('w-e-formula-card')) { + window.customElements.define('w-e-formula-card', WangEditorFormulaCard) +} diff --git a/packages/plugin-formula/src/register-custom-elem/native-shim.ts b/packages/plugin-formula/src/register-custom-elem/native-shim.ts new file mode 100644 index 000000000..2807b1b7e --- /dev/null +++ b/packages/plugin-formula/src/register-custom-elem/native-shim.ts @@ -0,0 +1,51 @@ +// @ts-nocheck + +// 参考 https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js + +/** + * @license + * Copyright (c) 2016 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ + +/** + * This shim allows elements written in, or compiled to, ES5 to work on native + * implementations of Custom Elements v1. It sets new.target to the value of + * this.constructor so that the native HTMLElement constructor can access the + * current under-construction element's definition. + */ +(function () { + if ( + // No Reflect, no classes, no need for shim because native custom elements + // require ES2015 classes or Reflect. + window.Reflect === undefined + || window.customElements === undefined + // The webcomponentsjs custom elements polyfill doesn't require + // ES2015-compatible construction (`super()` or `Reflect.construct`). + || window.customElements.polyfillWrapFlushCallback + ) { + return + } + const BuiltInHTMLElement = HTMLElement + /** + * With jscompiler's RECOMMENDED_FLAGS the function name will be optimized away. + * However, if we declare the function as a property on an object literal, and + * use quotes for the property name, then closure will leave that much intact, + * which is enough for the JS VM to correctly set Function.prototype.name. + */ + const wrapperForTheName = { + // eslint-disable-next-line func-names + HTMLElement: /** @this {!Object} */ function HTMLElement() { + return Reflect.construct(BuiltInHTMLElement, [], /** @type {!Function} */ this.constructor) + }, + } + + window.HTMLElement = wrapperForTheName.HTMLElement + HTMLElement.prototype = BuiltInHTMLElement.prototype + HTMLElement.prototype.constructor = HTMLElement + Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement) +}()) diff --git a/packages/plugin-formula/src/utils/dom.ts b/packages/plugin-formula/src/utils/dom.ts new file mode 100644 index 000000000..a5a0506ea --- /dev/null +++ b/packages/plugin-formula/src/utils/dom.ts @@ -0,0 +1,34 @@ +/** + * @description DOM 操作 + * @author wangfupeng + */ + +import $, { + append, find, focus, html, is, on, parents, val, +} from 'dom7' + +// COMPAT: This is required to prevent TypeScript aliases from doing some very +// weird things for Slate's types with the same name as globals. (2019/11/27) +// https://github.com/microsoft/TypeScript/issues/35002 +import DOMNode = globalThis.Node +import DOMComment = globalThis.Comment +import DOMElement = globalThis.Element +import DOMText = globalThis.Text +import DOMRange = globalThis.Range +import DOMSelection = globalThis.Selection +import DOMStaticRange = globalThis.StaticRange + +if (append) { $.fn.append = append } +if (html) { $.fn.html = html } +if (val) { $.fn.val = val } +if (on) { $.fn.on = on } +if (focus) { $.fn.focus = focus } +if (is) { $.fn.is = is } +if (parents) { $.fn.parents = parents } +if (find) { $.fn.find = find } + +export { Dom7Array } from 'dom7' +export default $ +export { + DOMComment, DOMElement, DOMNode, DOMRange, DOMSelection, DOMStaticRange, DOMText, +} diff --git a/packages/plugin-formula/src/utils/util.ts b/packages/plugin-formula/src/utils/util.ts new file mode 100644 index 000000000..7c34a2d86 --- /dev/null +++ b/packages/plugin-formula/src/utils/util.ts @@ -0,0 +1,19 @@ +/** + * @description 工具函数 + * @author wangfupeng + */ + +import { nanoid } from 'nanoid' + +/** + * 获取随机数字符串 + * @param prefix 前缀 + * @returns 随机数字符串 + */ +export function genRandomStr(prefix: string = 'r'): string { + return `${prefix}-${nanoid()}` +} + +// export function replaceSymbols(str: string) { +// return str.replace(//g, '>') +// } diff --git a/packages/plugin-formula/tsconfig.json b/packages/plugin-formula/tsconfig.json new file mode 100644 index 000000000..9bef938c9 --- /dev/null +++ b/packages/plugin-formula/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": {}, + "extends": "../../tsconfig.json", + "include": [ + "./src/**/*", + "../custom-types.d.ts" + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8400a3a82..e513558ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3526,6 +3526,21 @@ __metadata: languageName: unknown linkType: soft +"@wangeditor-next/plugin-formula@workspace:packages/plugin-formula": + version: 0.0.0-use.local + resolution: "@wangeditor-next/plugin-formula@workspace:packages/plugin-formula" + dependencies: + dom7: "npm:^3.0.0" + katex: "npm:^0.15.2" + nanoid: "npm:^3.2.0" + rollup-plugin-string: "npm:^3.0.0" + peerDependencies: + "@wangeditor-next/editor": 5.6.10 + katex: ^0.15.2 + snabbdom: ^3.1.0 + languageName: unknown + linkType: soft + "@wangeditor-next/plugin-markdown@workspace:packages/plugin-markdown": version: 0.0.0-use.local resolution: "@wangeditor-next/plugin-markdown@workspace:packages/plugin-markdown" @@ -4685,6 +4700,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^8.0.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10c0/8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 + languageName: node + linkType: hard + "commander@npm:~12.1.0": version: 12.1.0 resolution: "commander@npm:12.1.0" @@ -8432,6 +8454,17 @@ __metadata: languageName: node linkType: hard +"katex@npm:^0.15.2": + version: 0.15.6 + resolution: "katex@npm:0.15.6" + dependencies: + commander: "npm:^8.0.0" + bin: + katex: cli.js + checksum: 10c0/722db3b0442138f76d76017a384acd45ae0cbe67d730f20cbbfed0bf519c3640aa5352ba19e813743a63aead6065082bd225d21c04a16f58abb1d52684f95333 + languageName: node + linkType: hard + "klaw-sync@npm:^6.0.0": version: 6.0.0 resolution: "klaw-sync@npm:6.0.0" @@ -9247,7 +9280,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.7": +"nanoid@npm:^3.2.0, nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" bin: @@ -11253,6 +11286,15 @@ __metadata: languageName: node linkType: hard +"rollup-plugin-string@npm:^3.0.0": + version: 3.0.0 + resolution: "rollup-plugin-string@npm:3.0.0" + dependencies: + rollup-pluginutils: "npm:^2.4.1" + checksum: 10c0/83bbc2230d5271bc014739d52320b66cd4071fc4f7181369533809e4a3f0269b6cb4873caa96426396b36f694023a9f68f5de7a94254e1f2764ca6bacff61995 + languageName: node + linkType: hard + "rollup-plugin-typescript2@npm:^0.36.0": version: 0.36.0 resolution: "rollup-plugin-typescript2@npm:0.36.0" @@ -11288,7 +11330,7 @@ __metadata: languageName: node linkType: hard -"rollup-pluginutils@npm:^2.8.2": +"rollup-pluginutils@npm:^2.4.1, rollup-pluginutils@npm:^2.8.2": version: 2.8.2 resolution: "rollup-pluginutils@npm:2.8.2" dependencies: