Files
WRNexusJS/packages/block-editor/src/source-editor.ts
T

91 lines
3.1 KiB
TypeScript

/** Read-only derived output. Loading this entry does not load CodeMirror until opened. */
export async function mountBuilderSourceEditor(
parent: HTMLElement,
language: "text" | "xml",
content: string,
options: { label?: string; theme?: "tokens" } = {},
) {
const [
{ EditorView, basicSetup },
{ EditorState, Compartment },
{ xml },
{ HighlightStyle, syntaxHighlighting },
{ tags },
] = await Promise.all([
import("codemirror"),
import("@codemirror/state"),
import("@codemirror/lang-xml"),
import("@codemirror/language"),
import("@lezer/highlight"),
]);
if (!parent.isConnected) return null;
const stopShortcut = (event: KeyboardEvent) => event.stopPropagation();
parent.addEventListener("keydown", stopShortcut);
const wrapping = new Compartment();
const color = (token: string, legacy: string) =>
options.theme === "tokens" ? `var(--wrn-color-${token})` : legacy;
const view = new EditorView({
parent,
doc: content,
extensions: [
basicSetup,
EditorState.readOnly.of(true),
EditorView.editable.of(false),
EditorView.contentAttributes.of({
tabindex: "0",
"aria-label": options.label ?? "Generated email source",
}),
language === "xml" ? xml() : [],
wrapping.of([]),
syntaxHighlighting(
HighlightStyle.define([
{ tag: tags.tagName, color: color("primary", "#7dd3fc") },
{ tag: tags.attributeName, color: color("text", "#c4b5fd") },
{ tag: tags.attributeValue, color: color("primary", "#a7f3d0") },
{ tag: tags.angleBracket, color: color("muted", "#94a3b8") },
{ tag: tags.comment, color: color("muted", "#94a3b8") },
]),
),
EditorView.theme({
"&": {
height: "100%",
fontSize: "12px",
backgroundColor: color("surface", "#111827"),
color: color("text", "#e5e7eb"),
},
".cm-scroller": { overflow: "auto", fontFamily: "ui-monospace, Consolas, monospace" },
".cm-gutters": {
backgroundColor: color("surface", "#111827"),
color: color("muted", "#94a3b8"),
borderColor: color("border", "#334155"),
},
".cm-activeLine, .cm-activeLineGutter": { backgroundColor: "transparent" },
"&.cm-focused": { outline: "2px solid var(--wrn-color-primary)", outlineOffset: "-2px" },
}),
],
});
let destroyed = false;
const observer = new MutationObserver(() => {
if (!parent.isConnected) destroy();
});
function destroy() {
if (destroyed) return;
destroyed = true;
observer.disconnect();
parent.removeEventListener("keydown", stopShortcut);
view.destroy();
}
observer.observe(parent.ownerDocument.documentElement, { childList: true, subtree: true });
return {
update(value: string) {
if (!destroyed && view.state.doc.toString() !== value)
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: value } });
},
wrap(enabled: boolean) {
if (!destroyed)
view.dispatch({ effects: wrapping.reconfigure(enabled ? EditorView.lineWrapping : []) });
},
destroy,
};
}