diff --git a/src/public/app/types.d.ts b/src/public/app/types.d.ts index 8a2ebe75b..9dd85192b 100644 --- a/src/public/app/types.d.ts +++ b/src/public/app/types.d.ts @@ -207,6 +207,8 @@ declare global { modeInfo: ModeInfo[]; findModeByMIME(mime: string): ModeInfo; autoLoadMode(instance: CodeMirrorInstance, mode: string) + registerHelper(type: string, filter: string | null, callback: (text: string, options: object) => unknown); + Pos(line: number, col: number); } interface ModeInfo { diff --git a/src/public/app/widgets/type_widgets/linters/mermaid.ts b/src/public/app/widgets/type_widgets/linters/mermaid.ts new file mode 100644 index 000000000..85b11a352 --- /dev/null +++ b/src/public/app/widgets/type_widgets/linters/mermaid.ts @@ -0,0 +1,43 @@ +import mermaid from "mermaid"; + +interface MermaidParseError extends Error { + hash: { + text: string; + token: string; + line: number; + loc: { + first_line: number; + first_column: number; + last_line: number; + last_column: number; + }; + expected: string[] + } +} + +export default function registerErrorReporter() { + CodeMirror.registerHelper("lint", null, (async (text, options) => { + if (!text.trim()) { + return []; + } + + try { + await mermaid.parse(text); + } catch (e: unknown) { + console.warn("Got validation error", JSON.stringify(e)); + + const mermaidError = (e as MermaidParseError); + const loc = mermaidError.hash.loc; + return [ + { + message: mermaidError.message, + severity: "error", + from: CodeMirror.Pos(loc.first_line - 1, loc.first_column - 1), + to: CodeMirror.Pos(loc.last_line - 1, loc.last_column - 1) + } + ]; + } + + return []; + })); +} diff --git a/src/public/app/widgets/type_widgets/mermaid.ts b/src/public/app/widgets/type_widgets/mermaid.ts index 2e0da337d..7d6f00f1b 100644 --- a/src/public/app/widgets/type_widgets/mermaid.ts +++ b/src/public/app/widgets/type_widgets/mermaid.ts @@ -1,9 +1,9 @@ import type { MermaidConfig } from "mermaid"; -import library_loader from "../../services/library_loader.js"; import { loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js"; import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js"; let idCounter = 1; +let registeredErrorReporter = false; export class MermaidTypeWidget extends AbstractSvgSplitTypeWidget { @@ -14,19 +14,29 @@ export class MermaidTypeWidget extends AbstractSvgSplitTypeWidget { async renderSvg(content: string) { const mermaid = (await import("mermaid")).default; await loadElkIfNeeded(mermaid, content); + if (!registeredErrorReporter) { + (await import("./linters/mermaid.js")).default(); + registeredErrorReporter = true; + } - mermaid.mermaidAPI.initialize({ + mermaid.initialize({ startOnLoad: false, - ...(getMermaidConfig() as any) + ...(getMermaidConfig() as any), }); idCounter++; - const { svg } = await mermaid.mermaidAPI.render(`mermaid-graph-${idCounter}`, content); - return postprocessMermaidSvg(svg); + try { + const { svg } = await mermaid.render(`mermaid-graph-${idCounter}`, content); + return postprocessMermaidSvg(svg); + } catch (e) { + console.warn(JSON.stringify(e)); + return ""; + } } } + export function getMermaidConfig(): MermaidConfig { const documentStyle = window.getComputedStyle(document.documentElement); const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme") as "default";