From 71a380379a4db691086f9c2c3daf9e736a4f1382 Mon Sep 17 00:00:00 2001 From: Kirill Makhonin Date: Wed, 22 Jan 2025 16:41:06 -0500 Subject: [PATCH 1/2] feat: add ability to control mermaid icon packs --- docs/reference/diagrams.md | 31 +++++++++++--- docs/schema/theme.json | 29 +++++++++++++ material/overrides/main.html | 2 +- material/templates/base.html | 5 ++- src/templates/assets/javascripts/_/index.ts | 9 ++++ .../javascripts/components/content/_/index.ts | 4 +- .../components/content/mermaid/index.ts | 42 +++++++++++++------ src/templates/base.html | 5 +++ 8 files changed, 106 insertions(+), 21 deletions(-) diff --git a/docs/reference/diagrams.md b/docs/reference/diagrams.md index 9125af3f7fb..1c34f272fd5 100644 --- a/docs/reference/diagrams.md +++ b/docs/reference/diagrams.md @@ -16,7 +16,7 @@ popular and flexible solution for drawing diagrams. This configuration enables native support for [Mermaid.js] diagrams. Material -for MkDocs will automatically initialize the JavaScript runtime when a page +for MkDocs will automatically initialize the JavaScript runtime when a page includes a `mermaid` code block: ``` yaml @@ -38,7 +38,7 @@ No further configuration is necessary. Advantages over a custom integration: [^1]: While all [Mermaid.js] features should work out-of-the-box, Material for MkDocs will currently only adjust the fonts and colors for flowcharts, - sequence diagrams, class diagrams, state diagrams and entity relationship + sequence diagrams, class diagrams, state diagrams and entity relationship diagrams. See the section on [other diagrams] for more information why this is currently not implemented for all diagrams. @@ -46,6 +46,27 @@ No further configuration is necessary. Advantages over a custom integration: [additional style sheets]: ../customization.md#additional-css [other diagrams]: #other-diagram-types +### Loading additional icon packs + +You can define additional [icon packs](https://mermaid.js.org/config/icons.html) you want be loaded by specifying list in `theme.mermaid.iconPacks` + +``` yaml +theme: + mermaid: + iconPacks: + - logos@1 +``` + +### Overriding Mermaid JS version + +Mermaid JS version can be overrided by providing `theme.mermaid.version` value. Use this carefully. + +``` yaml +theme: + mermaid: + version: "11" +``` + ## Usage ### Using flowcharts @@ -82,7 +103,7 @@ graph LR ### Using sequence diagrams -[Sequence diagrams] describe a specific scenario as sequential interactions +[Sequence diagrams] describe a specific scenario as sequential interactions between multiple objects or actors, including the messages that are exchanged between those actors: @@ -194,7 +215,7 @@ classDiagram +int postalCode +String country -validate() - +outputAsLabel() + +outputAsLabel() } ``` ```` @@ -226,7 +247,7 @@ classDiagram +int postalCode +String country -validate() - +outputAsLabel() + +outputAsLabel() } ``` diff --git a/docs/schema/theme.json b/docs/schema/theme.json index 15fca4bc82a..26bbff19ae5 100644 --- a/docs/schema/theme.json +++ b/docs/schema/theme.json @@ -800,6 +800,35 @@ } } ] + }, + "mermaid": { + "title": "Mermaid JS configuration", + "type": "object", + "properties": { + "version": { + "title": "Mermaid JS version", + "description": "ID of version to be substituted in https://unpkg.com/mermaid@(VERSION)/dist/mermaid.min.js", + "examples": [ + "11" + ] + }, + "iconPacks": { + "title": "Icon packs", + "type": "array", + "items": { + "title": "Name of icon pack", + "description": "Name of icon pack to be substituted in https://unpkg.com/@iconify-json/(name)/icons.json - should include version", + "type": "string", + "examples": [ + "logos@1" + ] + }, + "examples": [ + ["logos@1"] + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false, diff --git a/material/overrides/main.html b/material/overrides/main.html index 57fc7f985ac..64ba728be6b 100644 --- a/material/overrides/main.html +++ b/material/overrides/main.html @@ -23,5 +23,5 @@ {% endblock %} {% block scripts %} {{ super() }} - + {% endblock %} diff --git a/material/templates/base.html b/material/templates/base.html index 9c008d9f0cc..e221fe10ea9 100644 --- a/material/templates/base.html +++ b/material/templates/base.html @@ -244,12 +244,15 @@ ] -%} {%- set _ = translations.update({ key: lang.t(key) }) -%} {%- endfor -%} + {%- if config.theme.mermaid -%} + {%- set _ = app.update({ "mermaid": config.theme.mermaid }) -%} + {%- endif -%} {% endblock %} {% block scripts %} - + {% for script in config.extra_javascript %} {{ script | script_tag }} {% endfor %} diff --git a/src/templates/assets/javascripts/_/index.ts b/src/templates/assets/javascripts/_/index.ts index e90f8d5398d..7796d31815f 100644 --- a/src/templates/assets/javascripts/_/index.ts +++ b/src/templates/assets/javascripts/_/index.ts @@ -86,6 +86,14 @@ export interface Versioning { alias?: boolean /* Show alias */ } +/** + * Mermaid JS Configuration + */ +export interface MermaidConfig { + version?: string + iconPacks?: string[] +} + /** * Configuration */ @@ -96,6 +104,7 @@ export interface Config { search: string /* Search worker URL */ tags?: Record /* Tags mapping */ version?: Versioning /* Versioning */ + mermaid?: MermaidConfig /* Mermaid JS configuration */ } /* ---------------------------------------------------------------------------- diff --git a/src/templates/assets/javascripts/components/content/_/index.ts b/src/templates/assets/javascripts/components/content/_/index.ts index 998fe77c5a0..024e4b298b9 100644 --- a/src/templates/assets/javascripts/components/content/_/index.ts +++ b/src/templates/assets/javascripts/components/content/_/index.ts @@ -22,7 +22,7 @@ import { Observable, merge } from "rxjs" -import { feature } from "~/_" +import { configuration, feature } from "~/_" import { Viewport, getElements } from "~/browser" import { Component } from "../../_" @@ -114,7 +114,7 @@ export function mountContent( /* Mermaid diagrams */ ...getElements("pre.mermaid", el) - .map(child => mountMermaid(child)), + .map(child => mountMermaid(child, configuration().mermaid)), /* Data tables */ ...getElements("table:not([class])", el) diff --git a/src/templates/assets/javascripts/components/content/mermaid/index.ts b/src/templates/assets/javascripts/components/content/mermaid/index.ts index 44e52e7e9a9..6e384739e65 100644 --- a/src/templates/assets/javascripts/components/content/mermaid/index.ts +++ b/src/templates/assets/javascripts/components/content/mermaid/index.ts @@ -30,6 +30,7 @@ import { import { watchScript } from "~/browser" import { h } from "~/utilities" +import { MermaidConfig } from "~/_" import { Component } from "../../_" @@ -67,9 +68,14 @@ let sequence = 0 * * @returns Mermaid scripts observable */ -function fetchScripts(): Observable { +function fetchScripts(config?: MermaidConfig): Observable { + let version = "11"; + if (config && config.version) { + version = config.version; + } + return typeof mermaid === "undefined" || mermaid instanceof Element - ? watchScript("https://unpkg.com/mermaid@11/dist/mermaid.min.js") + ? watchScript(`https://unpkg.com/mermaid@${version}/dist/mermaid.min.js`) : of(undefined) } @@ -85,20 +91,32 @@ function fetchScripts(): Observable { * @returns Mermaid diagram component observable */ export function mountMermaid( - el: HTMLElement + el: HTMLElement, config?: MermaidConfig ): Observable> { el.classList.remove("mermaid") // Hack: mitigate https://bit.ly/3CiN6Du - mermaid$ ||= fetchScripts() + mermaid$ ||= fetchScripts(config) .pipe( - tap(() => mermaid.initialize({ - startOnLoad: false, - themeCSS, - sequence: { - actorFontSize: "16px", // Hack: mitigate https://bit.ly/3y0NEi3 - messageFontSize: "16px", - noteFontSize: "16px" + tap(() => { + mermaid.initialize({ + startOnLoad: false, + themeCSS, + sequence: { + actorFontSize: "16px", // Hack: mitigate https://bit.ly/3y0NEi3 + messageFontSize: "16px", + noteFontSize: "16px" + } + }); + + /* Load icon packs */ + if (config && config.iconPacks) { + mermaid.registerIconPacks( + config.iconPacks.map(name => ({ + name: name.split('@')[0], + loader: () => fetch(`https://unpkg.com/@iconify-json/${name}/icons.json`).then((res) => res.json()), + })) + ); } - })), + }), map(() => undefined), shareReplay(1) ) diff --git a/src/templates/base.html b/src/templates/base.html index 0e67342eb39..4463ad3d22c 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -424,6 +424,11 @@ {%- set _ = translations.update({ key: lang.t(key) }) -%} {%- endfor -%} + + {%- if config.theme.mermaid -%} + {%- set _ = app.update({ "mermaid": config.theme.mermaid }) -%} + {%- endif -%} + {% endblock %} {% block scripts %} - + {% for script in config.extra_javascript %} {{ script | script_tag }} {% endfor %} diff --git a/src/templates/assets/javascripts/components/content/mermaid/index.ts b/src/templates/assets/javascripts/components/content/mermaid/index.ts index 6e384739e65..e0995709ade 100644 --- a/src/templates/assets/javascripts/components/content/mermaid/index.ts +++ b/src/templates/assets/javascripts/components/content/mermaid/index.ts @@ -83,6 +83,55 @@ function fetchScripts(config?: MermaidConfig): Observable { * Functions * ------------------------------------------------------------------------- */ + +/** + * Icon descriptor for Mermaid JS icon Packs + */ +interface IconPackDescriptor { + name: string, + loader: () => Promise +} + +/** + * Prepare details about icon packs for Mermaid JS + * + * @param iconName - Icon name. url:https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v19.0/dist/aws-icons-mermaid.json or iconify:logos@1 + * @returns icon pack descriptor for Mermaid JS + */ +export function prepareIconDescriptor(iconName: string): IconPackDescriptor | undefined { + // First - we need to strip type from full icon name + const parts = iconName.split(':'); + if (parts.length < 2) { + return undefined; + } + + switch (parts[0]){ + case 'url': + if (parts.length < 3) { + return undefined; + } + + const url = parts.slice(2).join(':'); + return { + name: parts[1], + loader: () => fetch(url).then((res) => res.json()), + } + case 'iconify': + const iconifyParts = parts[1].split('@', 2); + if (iconifyParts.length !== 2) { + return undefined; + } + + return { + name: iconifyParts[0], + loader: () => fetch(`https://unpkg.com/@iconify-json/${parts[1]}/icons.json`).then((res) => res.json()), + } + + default: + return undefined; + } +} + /** * Mount Mermaid diagram * @@ -110,10 +159,7 @@ export function mountMermaid( /* Load icon packs */ if (config && config.iconPacks) { mermaid.registerIconPacks( - config.iconPacks.map(name => ({ - name: name.split('@')[0], - loader: () => fetch(`https://unpkg.com/@iconify-json/${name}/icons.json`).then((res) => res.json()), - })) + config.iconPacks.map(name => prepareIconDescriptor(name)).filter(descriptor => descriptor !== undefined) ); } }),