refactor(client): use webpack for mermaid

This commit is contained in:
Elian Doran 2025-03-21 23:25:04 +02:00
parent f4d5b9fc29
commit 28c51cb38a
No known key found for this signature in database
11 changed files with 23 additions and 67 deletions

View File

@ -76,7 +76,6 @@ try {
"node_modules/dayjs/", "node_modules/dayjs/",
"node_modules/boxicons/css/", "node_modules/boxicons/css/",
"node_modules/boxicons/fonts/", "node_modules/boxicons/fonts/",
"node_modules/mermaid/dist/",
"node_modules/jquery/dist/", "node_modules/jquery/dist/",
"node_modules/jquery-hotkeys/", "node_modules/jquery-hotkeys/",
"node_modules/split.js/dist/", "node_modules/split.js/dist/",

View File

@ -16,7 +16,6 @@ import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js"; import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js"; import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
import MermaidWidget from "../widgets/mermaid.js";
import NoteListWidget from "../widgets/note_list.js"; import NoteListWidget from "../widgets/note_list.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js"; import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js"; import LauncherContainer from "../widgets/containers/launcher_container.js";
@ -165,7 +164,6 @@ export default class MobileLayout {
.child(new BacklinksWidget()) .child(new BacklinksWidget())
.child(new HideFloatingButtonsButton()) .child(new HideFloatingButtonsButton())
) )
.child(new MermaidWidget())
.child(new PromotedAttributesWidget()) .child(new PromotedAttributesWidget())
.child( .child(
new ScrollingContainer() new ScrollingContainer()

View File

@ -15,6 +15,7 @@ import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
import { normalizeMimeTypeForCKEditor } from "./mime_type_definitions.js"; import { normalizeMimeTypeForCKEditor } from "./mime_type_definitions.js";
import renderDoc from "./doc_renderer.js"; import renderDoc from "./doc_renderer.js";
import { t } from "i18next"; import { t } from "i18next";
import type { Mermaid } from "mermaid";
let idCounter = 1; let idCounter = 1;
@ -226,7 +227,7 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
} }
async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) { async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
await libraryLoader.requireLibrary(libraryLoader.MERMAID); const mermaid = (await import("mermaid")).default;
const blob = await note.getBlob(); const blob = await note.getBlob();
const content = blob?.content || ""; const content = blob?.content || "";
@ -236,10 +237,10 @@ async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery
const documentStyle = window.getComputedStyle(document.documentElement); const documentStyle = window.getComputedStyle(document.documentElement);
const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme"); const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme");
mermaid.mermaidAPI.initialize({ startOnLoad: false, theme: mermaidTheme.trim(), securityLevel: "antiscript" }); mermaid.mermaidAPI.initialize({ startOnLoad: false, theme: mermaidTheme.trim() as "default", securityLevel: "antiscript" });
try { try {
await loadElkIfNeeded(content); await loadElkIfNeeded(mermaid, content);
const { svg } = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content); const { svg } = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content);
$renderedContent.append($(postprocessMermaidSvg(svg))); $renderedContent.append($(postprocessMermaidSvg(svg)));

View File

@ -55,10 +55,6 @@ const WHEEL_ZOOM: Library = {
js: ["node_modules/vanilla-js-wheel-zoom/dist/wheel-zoom.min.js"] js: ["node_modules/vanilla-js-wheel-zoom/dist/wheel-zoom.min.js"]
}; };
const MERMAID: Library = {
js: ["node_modules/mermaid/dist/mermaid.min.js"]
};
const MARKJS: Library = { const MARKJS: Library = {
js: ["node_modules/mark.js/dist/jquery.mark.es6.min.js"] js: ["node_modules/mark.js/dist/jquery.mark.es6.min.js"]
}; };
@ -181,7 +177,6 @@ export default {
CALENDAR_WIDGET, CALENDAR_WIDGET,
KATEX, KATEX,
WHEEL_ZOOM, WHEEL_ZOOM,
MERMAID,
MARKJS, MARKJS,
HIGHLIGHT_JS, HIGHLIGHT_JS,
LEAFLET LEAFLET

View File

@ -1,3 +1,5 @@
import type { Mermaid } from "mermaid";
let elkLoaded = false; let elkLoaded = false;
/** /**
@ -9,7 +11,7 @@ let elkLoaded = false;
* *
* @param mermaidContent the plain text of the mermaid diagram, potentially including a frontmatter. * @param mermaidContent the plain text of the mermaid diagram, potentially including a frontmatter.
*/ */
export async function loadElkIfNeeded(mermaidContent: string) { export async function loadElkIfNeeded(mermaid: Mermaid, mermaidContent: string) {
if (elkLoaded) { if (elkLoaded) {
// Exit immediately since the ELK library is already loaded. // Exit immediately since the ELK library is already loaded.
return; return;
@ -18,7 +20,7 @@ export async function loadElkIfNeeded(mermaidContent: string) {
const parsedContent = await mermaid.parse(mermaidContent, { const parsedContent = await mermaid.parse(mermaidContent, {
suppressErrors: true suppressErrors: true
}); });
if (parsedContent?.config?.layout === "elk") { if (parsedContent && parsedContent.config?.layout === "elk") {
elkLoaded = true; elkLoaded = true;
mermaid.registerLayoutLoaders((await import("@mermaid-js/layout-elk")).default); mermaid.registerLayoutLoaders((await import("@mermaid-js/layout-elk")).default);
} }

View File

@ -8,6 +8,7 @@ import library_loader, { Library } from "./services/library_loader.ts";
import type { init } from "i18next"; import type { init } from "i18next";
import type { lint } from "./services/eslint.ts"; import type { lint } from "./services/eslint.ts";
import type { RelationType } from "./widgets/type_widgets/relation_map.ts"; import type { RelationType } from "./widgets/type_widgets/relation_map.ts";
import type { Mermaid } from "mermaid";
interface ElectronProcess { interface ElectronProcess {
type: string; type: string;
@ -138,45 +139,6 @@ declare global {
zoomOnClick: boolean zoomOnClick: boolean
}) })
}; };
interface MermaidApi {
initialize(opts: {
startOnLoad: boolean,
theme: string,
securityLevel: "antiscript"
}): void;
render(selector: string, data: string);
}
interface MermaidLoader {
}
interface MermaidChartConfig {
useMaxWidth: boolean;
}
interface MermaidConfig {
theme: string;
securityLevel: "antiscript",
flow: MermaidChartConfig;
sequence: MermaidChartConfig;
gantt: MermaidChartConfig;
class: MermaidChartConfig;
state: MermaidChartConfig;
pie: MermaidChartConfig;
journey: MermaidChartConfig;
git: MermaidChartConfig;
}
var mermaid: {
mermaidAPI: MermaidApi;
registerLayoutLoaders(loader: MermaidLoader);
init(config: MermaidConfig, el: HTMLElement | JQuery<HTMLElement>);
parse(content: string, opts: {
suppressErrors: true
}): Promise<{
config: {
layout: string;
}
}>
};
interface CKCodeBlockLanguage { interface CKCodeBlockLanguage {
language: string; language: string;
label: string; label: string;
@ -207,7 +169,7 @@ declare global {
enablePreview: boolean enablePreview: boolean
}, },
mermaid: { mermaid: {
lazyLoad: () => Promise<void>, lazyLoad: () => Promise<Mermaid>,
config: MermaidConfig config: MermaidConfig
} }
}); });

View File

@ -7,6 +7,7 @@ import AbstractSplitTypeWidget from "./abstract_split_type_widget.js";
* This adds the following functionality: * This adds the following functionality:
* *
* - Automatic handling of the preview when content or the note changes. * - Automatic handling of the preview when content or the note changes.
* - Built-in pan and zoom functionality with automatic re-centering.
* *
*/ */
export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTypeWidget { export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTypeWidget {

View File

@ -13,10 +13,10 @@ import dialogService from "../../services/dialog.js";
import { initSyntaxHighlighting } from "./ckeditor/syntax_highlight.js"; import { initSyntaxHighlighting } from "./ckeditor/syntax_highlight.js";
import options from "../../services/options.js"; import options from "../../services/options.js";
import toast from "../../services/toast.js"; import toast from "../../services/toast.js";
import { getMermaidConfig } from "../mermaid.js";
import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js"; import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js";
import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js"; import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import { getMermaidConfig } from "./mermaid.js";
const ENABLE_INSPECTOR = false; const ENABLE_INSPECTOR = false;
@ -279,7 +279,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
enablePreview: true // Enable preview view enablePreview: true // Enable preview view
}, },
mermaid: { mermaid: {
lazyLoad: async () => await libraryLoader.requireLibrary(libraryLoader.MERMAID), lazyLoad: async () => (await import("mermaid")).default, // FIXME
config: getMermaidConfig() config: getMermaidConfig()
} }
}); });

View File

@ -1,3 +1,4 @@
import type { MermaidConfig } from "mermaid";
import library_loader from "../../services/library_loader.js"; import library_loader from "../../services/library_loader.js";
import { loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js"; import { loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js";
import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js"; import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js";
@ -11,8 +12,8 @@ export class MermaidTypeWidget extends AbstractSvgSplitTypeWidget {
} }
async renderSvg(content: string) { async renderSvg(content: string) {
await library_loader.requireLibrary(library_loader.MERMAID); const mermaid = (await import("mermaid")).default;
await loadElkIfNeeded(content); await loadElkIfNeeded(mermaid, content);
mermaid.mermaidAPI.initialize({ mermaid.mermaidAPI.initialize({
startOnLoad: false, startOnLoad: false,
@ -28,19 +29,18 @@ export class MermaidTypeWidget extends AbstractSvgSplitTypeWidget {
export function getMermaidConfig(): MermaidConfig { export function getMermaidConfig(): MermaidConfig {
const documentStyle = window.getComputedStyle(document.documentElement); const documentStyle = window.getComputedStyle(document.documentElement);
const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme"); const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme") as "default";
return { return {
theme: mermaidTheme.trim(), theme: mermaidTheme.trim() as "default",
securityLevel: "antiscript", securityLevel: "antiscript",
// TODO: Are all these options correct? flowchart: { useMaxWidth: false },
flow: { useMaxWidth: false },
sequence: { useMaxWidth: false }, sequence: { useMaxWidth: false },
gantt: { useMaxWidth: false }, gantt: { useMaxWidth: false },
class: { useMaxWidth: false }, class: { useMaxWidth: false },
state: { useMaxWidth: false }, state: { useMaxWidth: false },
pie: { useMaxWidth: true }, pie: { useMaxWidth: true },
journey: { useMaxWidth: false }, journey: { useMaxWidth: false },
git: { useMaxWidth: false } gitGraph: { useMaxWidth: false }
}; };
} }

View File

@ -1,10 +1,10 @@
import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
import libraryLoader from "../../services/library_loader.js"; import libraryLoader from "../../services/library_loader.js";
import { applySyntaxHighlight } from "../../services/syntax_highlight.js"; import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
import { getMermaidConfig } from "../mermaid.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import type { EventData } from "../../components/app_context.js"; import type { EventData } from "../../components/app_context.js";
import { getLocaleById } from "../../services/i18n.js"; import { getLocaleById } from "../../services/i18n.js";
import { getMermaidConfig } from "./mermaid.js";
const TPL = ` const TPL = `
<div class="note-detail-readonly-text note-detail-printable"> <div class="note-detail-readonly-text note-detail-printable">
@ -141,8 +141,8 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
}); });
// Initialize mermaid // Initialize mermaid
await libraryLoader.requireLibrary(libraryLoader.MERMAID); const mermaid = (await import("mermaid")).default;
mermaid.init(getMermaidConfig(), this.$content.find(".mermaid-diagram")); mermaid.init(getMermaidConfig(), this.$content.find(".mermaid-diagram")[0]);
} }
async refreshIncludedNoteEvent({ noteId }: EventData<"refreshIncludedNote">) { async refreshIncludedNoteEvent({ noteId }: EventData<"refreshIncludedNote">) {

View File

@ -64,8 +64,6 @@ async function register(app: express.Application) {
app.use(`/${assetPath}/node_modules/boxicons/css/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/boxicons/css/"))); app.use(`/${assetPath}/node_modules/boxicons/css/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/boxicons/css/")));
app.use(`/${assetPath}/node_modules/boxicons/fonts/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/boxicons/fonts/"))); app.use(`/${assetPath}/node_modules/boxicons/fonts/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/boxicons/fonts/")));
app.use(`/${assetPath}/node_modules/mermaid/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/mermaid/dist/")));
app.use(`/${assetPath}/node_modules/jquery/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/jquery/dist/"))); app.use(`/${assetPath}/node_modules/jquery/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/jquery/dist/")));
app.use(`/${assetPath}/node_modules/jquery-hotkeys/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/jquery-hotkeys/"))); app.use(`/${assetPath}/node_modules/jquery-hotkeys/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/jquery-hotkeys/")));