diff --git a/src/public/app/services/content_renderer.ts b/src/public/app/services/content_renderer.ts index ec81ef3bc..3a7c3b2e5 100644 --- a/src/public/app/services/content_renderer.ts +++ b/src/public/app/services/content_renderer.ts @@ -11,7 +11,7 @@ import FNote from "../entities/fnote.js"; import FAttachment from "../entities/fattachment.js"; import imageContextMenuService from "../menus/image_context_menu.js"; import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js"; -import { loadElkIfNeeded } from "./mermaid.js"; +import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js"; import { normalizeMimeTypeForCKEditor } from "./mime_type_definitions.js"; let idCounter = 1; @@ -226,7 +226,7 @@ async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery await loadElkIfNeeded(content); const { svg } = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content); - $renderedContent.append($(svg)); + $renderedContent.append($(postprocessMermaidSvg(svg))); } catch (e) { const $error = $("

The diagram could not displayed.

"); diff --git a/src/public/app/services/mermaid.spec.ts b/src/public/app/services/mermaid.spec.ts new file mode 100644 index 000000000..3da396eec --- /dev/null +++ b/src/public/app/services/mermaid.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; +import { postprocessMermaidSvg } from "./mermaid.js"; +import { trimIndentation } from "../../../../spec/support/utils.js"; + +describe("Mermaid", () => { + it("converts
properly", () => { + const before = trimIndentation`\ + + + +
+ +

Verify Output
Against
Criteria

+
+
+
+
+ `; + const after = trimIndentation`\ + + + +
+ +

Verify Output
Against
Criteria

+
+
+
+
+ `; + expect(postprocessMermaidSvg(before)).toBe(after); + }); +}); diff --git a/src/public/app/services/mermaid.ts b/src/public/app/services/mermaid.ts index 3e0acca86..1100081c9 100644 --- a/src/public/app/services/mermaid.ts +++ b/src/public/app/services/mermaid.ts @@ -23,3 +23,16 @@ export async function loadElkIfNeeded(mermaidContent: string) { mermaid.registerLayoutLoaders((await import("@mermaid-js/layout-elk")).default); } } + +/** + * Processes the output of a Mermaid SVG render before it should be delivered to the user. + * + *

+ * Currently this fixes
to
which would otherwise cause an invalid XML. + * + * @param svg the Mermaid SVG to process. + * @returns the processed SVG. + */ +export function postprocessMermaidSvg(svg: string) { + return svg.replaceAll(//ig, "
"); +} diff --git a/src/public/app/widgets/mermaid.ts b/src/public/app/widgets/mermaid.ts index 8fcd3efdf..54f44825b 100644 --- a/src/public/app/widgets/mermaid.ts +++ b/src/public/app/widgets/mermaid.ts @@ -3,7 +3,7 @@ import libraryLoader from "../services/library_loader.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; import server from "../services/server.js"; import utils from "../services/utils.js"; -import { loadElkIfNeeded } from "../services/mermaid.js"; +import { loadElkIfNeeded, postprocessMermaidSvg } from "../services/mermaid.js"; import type FNote from "../entities/fnote.js"; import type { EventData } from "../components/app_context.js"; @@ -122,7 +122,7 @@ export default class MermaidWidget extends NoteContextAwareWidget { await loadElkIfNeeded(content); const { svg } = await mermaid.mermaidAPI.render(`mermaid-graph-${idCounter}`, content); - return svg; + return postprocessMermaidSvg(svg); } async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {