UNPKG

@rtdui/editor

Version:

React rich text editor based on tiptap

116 lines (113 loc) 4.44 kB
const require_shiki_highlighter = require('./shiki-highlighter.cjs'); let _tiptap_core = require("@tiptap/core"); let _tiptap_pm_state = require("@tiptap/pm/state"); let _tiptap_pm_view = require("@tiptap/pm/view"); //#region packages/editor/src/RichTextEditor/tiptap_extensions/extension-code-block-shiki/prosemirror-shiki-plugin.ts /** shiki的单主题token样式映射 */ function tokenStyleToCssStyle(token) { let style = ""; if (token.color) style += `color: ${token.color};`; if (token.fontStyle) { if (token.fontStyle & 1) style += "font-style: italic;"; if (token.fontStyle & 2) style += "font-weight: bold;"; if (token.fontStyle & 4) style += "text-underline: underline;"; if (token.fontStyle & 8) style += "text-underline: line-through;"; } return style; } /** Create code decorations for the current document */ function getDecorations({ doc, name, defaultTheme, defaultLanguage }) { const decorations = []; (0, _tiptap_core.findChildren)(doc, (node) => node.type.name === name).forEach((block) => { let from = block.pos + 1; let language = block.node.attrs.language || defaultLanguage; const theme = block.node.attrs.theme || defaultTheme; const highlighter = require_shiki_highlighter.getShiki(); if (!highlighter) return; if (!highlighter.getLoadedLanguages().includes(language)) language = "plaintext"; const themeToApply = highlighter.getLoadedThemes().includes(theme) ? theme : highlighter.getLoadedThemes()[0]; const tokens = highlighter.codeToTokensBase(block.node.textContent, { lang: language, theme: themeToApply }); for (const line of tokens) { for (const token of line) { const to = from + token.content.length; const decoration = _tiptap_pm_view.Decoration.inline(from, to, { style: tokenStyleToCssStyle(token) }); decorations.push(decoration); from = to; } from += 1; } }); return _tiptap_pm_view.DecorationSet.create(doc, decorations); } function ProseMirrorShikiPlugin({ name, defaultLanguage, defaultTheme }) { const shikiPlugin = new _tiptap_pm_state.Plugin({ key: new _tiptap_pm_state.PluginKey("shiki"), view(view) { class ShikiPluginView { constructor() { this.initDecorations(); } update() { this.checkUndecoratedBlocks(); } destroy() {} async initDecorations() { const doc = view.state.doc; await require_shiki_highlighter.initHighlighter({ doc, name, defaultLanguage, defaultTheme }); const tr = view.state.tr.setMeta("shikiPluginForceDecoration", true); view.dispatch(tr); } async checkUndecoratedBlocks() { const codeBlocks = (0, _tiptap_core.findChildren)(view.state.doc, (node) => node.type.name === name); if ((await Promise.all(codeBlocks.flatMap((block) => [require_shiki_highlighter.loadTheme(block.node.attrs.theme), require_shiki_highlighter.loadLanguage(block.node.attrs.language)]))).includes(true)) { const tr = view.state.tr.setMeta("shikiPluginForceDecoration", true); view.dispatch(tr); } } } return new ShikiPluginView(); }, state: { init: (_, { doc }) => { return getDecorations({ doc, name, defaultLanguage, defaultTheme }); }, apply: (transaction, decorationSet, oldState, newState) => { const oldNodeName = oldState.selection.$head.parent.type.name; const newNodeName = newState.selection.$head.parent.type.name; const oldNodes = (0, _tiptap_core.findChildren)(oldState.doc, (node) => node.type.name === name); const newNodes = (0, _tiptap_core.findChildren)(newState.doc, (node) => node.type.name === name); const didChangeSomeCodeBlock = transaction.docChanged && ([oldNodeName, newNodeName].includes(name) || newNodes.length !== oldNodes.length || transaction.steps.some((step) => { return step.from !== void 0 && step.to !== void 0 && oldNodes.some((node) => { return node.pos >= step.from && node.pos + node.node.nodeSize <= step.to; }); })); if (transaction.getMeta("shikiPluginForceDecoration") || didChangeSomeCodeBlock) return getDecorations({ doc: transaction.doc, name, defaultLanguage, defaultTheme }); return decorationSet.map(transaction.mapping, transaction.doc); } }, props: { decorations(state) { return shikiPlugin.getState(state); } } }); return shikiPlugin; } //#endregion exports.ProseMirrorShikiPlugin = ProseMirrorShikiPlugin;