72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
import hljs from 'highlight.js/lib/common';
|
|
import type { Node as ProseMirrorNode } from '@tiptap/pm/model';
|
|
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
import { Decoration, DecorationSet } from '@tiptap/pm/view';
|
|
import { registerAardioLanguage } from './hljsAardio';
|
|
|
|
registerAardioLanguage(hljs);
|
|
|
|
const key = new PluginKey('articleCodeBlockHighlight');
|
|
|
|
/** 将 hljs HTML 映射为 ProseMirror inline Decoration */
|
|
function decorationsForCodeBlock(node: ProseMirrorNode, pos: number): Decoration[] {
|
|
const language = ((node.attrs.language as string) || '').trim();
|
|
const text = node.textContent;
|
|
if (!text || !language || !hljs.getLanguage(language)) return [];
|
|
|
|
let html: string;
|
|
try {
|
|
html = hljs.highlight(text, { language, ignoreIllegals: true }).value;
|
|
} catch {
|
|
return [];
|
|
}
|
|
|
|
const root = document.createElement('div');
|
|
root.innerHTML = html;
|
|
const out: Decoration[] = [];
|
|
let offset = 0;
|
|
|
|
const walk = (dom: Node, className: string | null) => {
|
|
if (dom.nodeType === Node.TEXT_NODE) {
|
|
const len = dom.textContent?.length ?? 0;
|
|
if (len > 0 && className) {
|
|
const from = pos + 1 + offset;
|
|
out.push(Decoration.inline(from, from + len, { class: className }));
|
|
}
|
|
offset += len;
|
|
return;
|
|
}
|
|
if (dom.nodeType !== Node.ELEMENT_NODE) return;
|
|
const el = dom as Element;
|
|
const nextClass = el.className || className;
|
|
el.childNodes.forEach(child => walk(child, nextClass));
|
|
};
|
|
|
|
root.childNodes.forEach(child => walk(child, null));
|
|
return out;
|
|
}
|
|
|
|
function buildDecorations(doc: ProseMirrorNode): DecorationSet {
|
|
const decos: Decoration[] = [];
|
|
doc.descendants((node, pos) => {
|
|
if (node.type.name !== 'codeBlock') return true;
|
|
decos.push(...decorationsForCodeBlock(node, pos));
|
|
return false;
|
|
});
|
|
return DecorationSet.create(doc, decos);
|
|
}
|
|
|
|
/** 富文本代码块:用 Decoration 叠 hljs 着色,保持可编辑纯文本 */
|
|
export function createCodeBlockHighlightPlugin(): Plugin {
|
|
return new Plugin({
|
|
key,
|
|
state: {
|
|
init: (_, state) => buildDecorations(state.doc),
|
|
apply: (tr, old) => (tr.docChanged ? buildDecorations(tr.doc) : old),
|
|
},
|
|
props: {
|
|
decorations: state => key.getState(state),
|
|
},
|
|
});
|
|
}
|