@rtdui/editor
Version:
React rich text editor based on tiptap
116 lines (113 loc) • 4.44 kB
JavaScript
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;