diff --git a/examples/formula.html b/examples/formula.html new file mode 100644 index 00000000..f1031505 --- /dev/null +++ b/examples/formula.html @@ -0,0 +1,53 @@ + + + + + Cherry Editor - Markdown Editor + + + + + + + + + + +
+
+ + + + + + + diff --git a/examples/images/frac.svg b/examples/images/frac.svg new file mode 100644 index 00000000..7f3b9b00 --- /dev/null +++ b/examples/images/frac.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/images/minus.svg b/examples/images/minus.svg new file mode 100644 index 00000000..3f9bd7ee --- /dev/null +++ b/examples/images/minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/images/plus.svg b/examples/images/plus.svg new file mode 100644 index 00000000..e203fcb2 --- /dev/null +++ b/examples/images/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/markdown/formula.md b/examples/markdown/formula.md new file mode 100644 index 00000000..e8350d6d --- /dev/null +++ b/examples/markdown/formula.md @@ -0,0 +1,27 @@ +# Formula + +## 数学公式 + +### symbol + +$+$ +$-$ + +### fraction + +$\frac{a}{b}$ + +## 物理公式 + +### symbol + +$\vec{a}$ + +$\vec{v}$ + +### mechanics + +$\vec{F}=m\vec{a}$ + +$W=\int_{C} \vec{F} \cdot \mathrm{d} \vec{s}$ + diff --git a/examples/scripts/formula.js b/examples/scripts/formula.js new file mode 100644 index 00000000..84edbb40 --- /dev/null +++ b/examples/scripts/formula.js @@ -0,0 +1,314 @@ +/** + * 自定义一个语法 + */ +var CustomHookA = Cherry.createSyntaxHook('codeBlock', Cherry.constants.HOOKS_TYPE_LIST.PAR, { + makeHtml(str) { + console.warn('custom hook', 'hello'); + return str; + }, + rule(str) { + const regex = { + begin: '', + content: '', + end: '', + }; + regex.reg = new RegExp(regex.begin + regex.content + regex.end, 'g'); + return regex; + }, +}); +/** + * 自定义一个自定义菜单 + * 点第一次时,把选中的文字变成同时加粗和斜体 + * 保持光标选区不变,点第二次时,把加粗斜体的文字变成普通文本 + */ +var customMenuA = Cherry.createMenuHook('加粗斜体', { + iconName: 'font', + onClick: function (selection) { + // 获取用户选中的文字,调用getSelection方法后,如果用户没有选中任何文字,会尝试获取光标所在位置的单词或句子 + let $selection = this.getSelection(selection) || '同时加粗斜体'; + // 如果是单选,并且选中内容的开始结束内没有加粗语法,则扩大选中范围 + if (!this.isSelections && !/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) { + this.getMoreSelection('***', '***', () => { + const newSelection = this.editor.editor.getSelection(); + const isBoldItalic = /^\s*(\*\*\*)[\s\S]+(\1)/.test(newSelection); + if (isBoldItalic) { + $selection = newSelection; + } + return isBoldItalic; + }); + } + // 如果选中的文本中已经有加粗语法了,则去掉加粗语法 + if (/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) { + return $selection.replace(/(^)(\s*)(\*\*\*)([^\n]+)(\3)(\s*)($)/gm, '$1$4$7'); + } + /** + * 注册缩小选区的规则 + * 注册后,插入“***TEXT***”,选中状态会变成“***【TEXT】***” + * 如果不注册,插入后效果为:“【***TEXT***】” + */ + this.registerAfterClickCb(() => { + this.setLessSelection('***', '***'); + }); + return $selection.replace(/(^)([^\n]+)($)/gm, '$1***$2***$3'); + }, +}); +/** + * 定义一个空壳,用于自行规划cherry已有工具栏的层级结构 + */ +var customMenuB = Cherry.createMenuHook('实验室', { + iconName: '', +}); +/** + * 定义一个自带二级菜单的工具栏 + */ +var customMenuC = Cherry.createMenuHook('帮助中心', { + iconName: 'question', + onClick: (selection, type) => { + switch (type) { + case 'shortKey': + return `${selection}快捷键看这里:https://codemirror.net/5/demo/sublime.html`; + case 'github': + return `${selection}我们在这里:https://github.com/Tencent/cherry-markdown`; + case 'release': + return `${selection}我们在这里:https://github.com/Tencent/cherry-markdown/releases`; + default: + return selection; + } + }, + subMenuConfig: [ + { + noIcon: true, + name: '快捷键', + onclick: (event) => { + cherry.toolbar.menus.hooks.customMenuCName.fire(null, 'shortKey'); + }, + }, + { + noIcon: true, + name: '联系我们', + onclick: (event) => { + cherry.toolbar.menus.hooks.customMenuCName.fire(null, 'github'); + }, + }, + { + noIcon: true, + name: '更新日志', + onclick: (event) => { + cherry.toolbar.menus.hooks.customMenuCName.fire(null, 'release'); + }, + }, + ], +}); + +var basicConfig = { + id: 'markdown', + externals: { + echarts: window.echarts, + katex: window.katex, + MathJax: window.MathJax, + }, + isPreviewOnly: false, + engine: { + global: { + urlProcessor(url, srcType) { + console.log(`url-processor`, url, srcType); + return url; + }, + }, + syntax: { + codeBlock: { + theme: 'twilight', + }, + table: { + enableChart: false, + // chartEngine: Engine Class + }, + fontEmphasis: { + allowWhitespace: false, // 是否允许首尾空格 + }, + strikethrough: { + needWhitespace: false, // 是否必须有前后空格 + }, + mathBlock: { + engine: 'MathJax', // katex或MathJax + src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js', // 如果使用MathJax plugins,则需要使用该url通过script标签引入 + }, + inlineMath: { + engine: 'MathJax', // katex或MathJax + }, + emoji: { + useUnicode: false, + customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8', + upperCase: true, + }, + formula: { + config: { + math: { + title: '数学公式', + subCategory: { + symbol: { + title: '符号', + formulas: [ + { + name: '加号', + latex: '+', + img: 'images/plus.svg', + imgStyle: 'width: 30px; height: 30px;', + imgClass: 'custom-img-class', + formulaStyle: 'border: 1px solid red;', + formulaClass: 'custom-formula-class', + }, + { + name: '减号', + latex: '-', + img: 'images/minus.svg', + }, + ], + }, + fraction: { + title: '分式', + formulas: [ + { + name: '分式', + latex: '\\frac{a}{b}', + img: 'images/frac.svg', + }, + ], + }, + }, + }, + physics: { + title: '物理公式', + subCategory: { + symbol: { + title: '符号', + formulas: [ + { + name: '加速度', + latex: '\\vec{a}', + }, + { + name: '速度', + latex: '\\vec{v}', + }, + ], + }, + // 力学公式 + mechanics: { + title: '力学', + formulas: [ + { + name: '牛顿第二定律', + latex: '\\vec{F}=m\\vec{a}', + }, + { + name: '功', + latex: 'W=\\int_{C} \\vec{F} \\cdot \\mathrm{d} \\vec{s}', + }, + ], + }, + }, + }, + }, + }, + // toc: { + // tocStyle: 'nested' + // } + // 'header': { + // strict: false + // } + }, + customSyntax: { + // SyntaxHookClass + CustomHook: { + syntaxClass: CustomHookA, + force: false, + after: 'br', + }, + }, + }, + toolbars: { + toolbar: [ + 'bold', + 'italic', + { + strikethrough: ['strikethrough', 'underline', 'sub', 'sup', 'ruby', 'customMenuAName'], + }, + 'size', + '|', + 'color', + 'header', + '|', + 'drawIo', + '|', + 'ol', + 'ul', + 'checklist', + 'panel', + 'justify', + 'detail', + '|', + 'formula', + { + insert: [ + 'image', + 'audio', + 'video', + 'link', + 'hr', + 'br', + 'code', + 'formula', + 'toc', + 'table', + 'pdf', + 'word', + 'ruby', + ], + }, + 'graph', + 'togglePreview', + 'settings', + 'codeTheme', + 'export', + { + customMenuBName: ['ruby', 'audio', 'video', 'customMenuAName'], + }, + 'customMenuCName', + 'theme', + ], + toolbarRight: ['fullScreen', '|'], + bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', 'ruby', '|', 'size', 'color'], // array or false + sidebar: ['mobilePreview', 'copy', 'theme'], + customMenu: { + customMenuAName: customMenuA, + customMenuBName: customMenuB, + customMenuCName: customMenuC, + }, + }, + drawioIframeUrl: './drawio_demo.html', + editor: { + defaultModel: 'edit&preview', + }, + previewer: { + // 自定义markdown预览区域class + // className: 'markdown' + }, + keydown: [], + //extensions: [], + callback: { + changeString2Pinyin: pinyin, + }, + editor: { + id: 'cherry-text', + name: 'cherry-text', + autoSave2Textarea: true, + }, +}; + +fetch('./markdown/formula.md') + .then((response) => response.text()) + .then((value) => { + var config = Object.assign({}, basicConfig, { value: value }); + window.cherry = new Cherry(config); + }); diff --git a/src/sass/bubble_formula.scss b/src/sass/bubble_formula.scss index 8190e4b2..c24d8499 100644 --- a/src/sass/bubble_formula.scss +++ b/src/sass/bubble_formula.scss @@ -1,10 +1,9 @@ .cherry-insert-formula-wrappler { - width: 500px; - height: 300px; + width: 500px !important; // 覆盖默认.cherry-dropdown样式 + height: 300px !important; padding: 15px; display: flex; - background-color: #fff; - position: fixed; + position: fixed !important; z-index: 9999999; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); box-sizing: border-box; @@ -32,7 +31,9 @@ text-decoration: none; user-select: none; user-select: none; - color: black; + } + &:not(:first-child) { + margin-top: 10px; } } @@ -106,7 +107,6 @@ cursor: pointer; display: inline-block; font-weight: 400; - color: #212529; text-align: center; vertical-align: middle; user-select: none; diff --git a/src/toolbars/BubbleFormula.js b/src/toolbars/BubbleFormula.js index 40e0506e..d04e348c 100644 --- a/src/toolbars/BubbleFormula.js +++ b/src/toolbars/BubbleFormula.js @@ -51,12 +51,17 @@ export default class BubbleFormula { */ afterClick(latex) {} - constructor() { + /** + * @param {Object.} formulaConfig + */ + constructor(formulaConfig = {}) { + this.formulaConfig = formulaConfig; this.init(); this.initEventListeners(); } generateBubbleFormulaHtmlStr() { + console.log('this.formulaConfig: ', this.formulaConfig); const entries = Object.entries(this.formulaConfig || {}); const liStr = entries .map( @@ -90,7 +95,7 @@ export default class BubbleFormula { }) .join(''); const formulaCategaryFuncStr = `
${formulaCategaryFuncInnerStr}
`; - const formulaCategaryBtnStr = ``; + const formulaCategaryBtnStr = ``; return `
${formulaCategaryBtnStr}${formulaCategaryFuncStr}
`; }) .join(''); @@ -103,11 +108,13 @@ export default class BubbleFormula { } init() { - this.dom = document.createElement('div'); - this.dom.className = ['cherry-insert-formula', 'cherry-insert-formula-wrappler'].join(' '); - this.dom.innerHTML = this.generateBubbleFormulaHtmlStr(); - // 实例化后,将容器插入到富文本编辑器中,默认隐藏 - this.dom.style.display = 'none'; + if (Object.keys(this.formulaConfig).length) { + this.dom = document.createElement('div'); + this.dom.className = ['cherry-dropdown', 'cherry-insert-formula', 'cherry-insert-formula-wrappler'].join(' '); + this.dom.innerHTML = this.generateBubbleFormulaHtmlStr(); + // 实例化后,将容器插入到富文本编辑器中,默认隐藏 + this.dom.style.display = 'none'; + } } /** diff --git a/src/toolbars/Toolbar.js b/src/toolbars/Toolbar.js index 286ba92c..50452289 100644 --- a/src/toolbars/Toolbar.js +++ b/src/toolbars/Toolbar.js @@ -168,6 +168,7 @@ export default class Toolbar { * 展开/收起二级菜单 */ toggleSubMenu(name) { + console.log('name: ', name); if (!this.subMenus[name]) { // 如果没有二级菜单,则先画出来,然后再显示 this.hideAllSubMenu(); diff --git a/src/toolbars/hooks/Formula.js b/src/toolbars/hooks/Formula.js index b2089e1a..d9d66c49 100644 --- a/src/toolbars/hooks/Formula.js +++ b/src/toolbars/hooks/Formula.js @@ -22,7 +22,7 @@ export default class Formula extends MenuBase { constructor($cherry) { super($cherry); this.setName('formula', 'insertFormula'); - this.subBubbleFormulaMenu = new BubbleFormula(); + this.subBubbleFormulaMenu = new BubbleFormula($cherry?.options?.engine?.syntax?.formula?.config || {}); $cherry.editor.options.wrapperDom.appendChild(this.subBubbleFormulaMenu.dom); this.catchOnce = ''; } diff --git a/types/syntax.d.ts b/types/syntax.d.ts index f3566350..6cf40f5f 100644 --- a/types/syntax.d.ts +++ b/types/syntax.d.ts @@ -44,9 +44,4 @@ export interface BasicHookRegexpRule { } // TODO: -export type HookRegexpRule = { - begin: string; - end: string; - content: string; - reg?: RegExp; -}; +export type HookRegexpRule = {};