diff --git a/v8/shortcode_mermaid/README.md b/v8/shortcode_mermaid/README.md new file mode 100644 index 00000000..0959f24b --- /dev/null +++ b/v8/shortcode_mermaid/README.md @@ -0,0 +1,89 @@ +# mermaid (Nikola plugin) + +Render Mermaid diagrams from Nikola shortcode blocks. + +This plugin provides a `mermaid` shortcode that wraps diagram source code in a +`
` block and, by default, injects a guarded Mermaid.js +loader next to the diagram. It works without editing theme templates. + +![shortcode_mermaid](shortcode_mermaid.png) + +## Install + +Copy the plugin folder into your Nikola site: + +```text +your_site/ + plugins/ + mermaid/ + mermaid.py + mermaid.plugin + README.md + conf.py.sample + requirements-nonpy.txt +``` + +## Configuration + +No Nikola configuration is required for the default behavior. + +Optional configuration: + +```python +MERMAID_CONFIG = { + "auto_load": True, + "cdn_url": "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js", + "initialize": { + "startOnLoad": False, + }, +} +``` + +Set `auto_load` to `False` if your theme already loads and initializes +Mermaid.js. When `auto_load` is enabled, the loader is protected by global +browser flags so multiple diagrams do not inject multiple Mermaid.js scripts. + +## Usage + +Use the shortcode in a post or page: + +```markdown +{{% mermaid %}} +mindmap + root((Data Scientist - ML)) + Analyse de donnees + MLOps + NLP et RAG +{{% /mermaid %}} +``` + +You can pass a theme name through the shortcode: + +```markdown +{{% mermaid theme="dark" %}} +graph TD + A[Data] --> B[Model] + B --> C[API] +{{% /mermaid %}} +``` + +The selected theme is exposed as a `data-theme` attribute: + +```html +
+... +
+``` + +## Online example + +- The following website use the Mermaid shortcode plugin : [stephmnt/datascience_portfolio](stephmnt.github.io/datascience_portfolio) + +## Notes + +- This plugin does not bundle Mermaid.js; it loads Mermaid.js from the configured + CDN URL when `auto_load` is enabled. +- `requirements-nonpy.txt` declares Mermaid.js as a non-Python runtime dependency + for the Nikola plugin index. +- If diagrams do not render, check the browser console, CDN access, and that the + shortcode is closed with `{{% /mermaid %}}`. diff --git a/v8/shortcode_mermaid/conf.py.sample b/v8/shortcode_mermaid/conf.py.sample new file mode 100644 index 00000000..6f1d8675 --- /dev/null +++ b/v8/shortcode_mermaid/conf.py.sample @@ -0,0 +1,16 @@ +# mermaid plugin sample configuration + +MERMAID_CONFIG = { + # Load Mermaid.js automatically from the shortcode output. + # Set to False if your theme already loads and initializes Mermaid. + "auto_load": True, + + # Mermaid.js URL used when auto_load is True. + "cdn_url": "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js", + + # Passed to mermaid.initialize(). + # The plugin renders diagrams explicitly, so startOnLoad should usually stay False. + "initialize": { + "startOnLoad": False, + }, +} diff --git a/v8/shortcode_mermaid/mermaid.plugin b/v8/shortcode_mermaid/mermaid.plugin new file mode 100644 index 00000000..f144d8a5 --- /dev/null +++ b/v8/shortcode_mermaid/mermaid.plugin @@ -0,0 +1,13 @@ +[Core] +Name = mermaid +Module = mermaid + +[Documentation] +Author = Stéphane Manet +Version = 0.1.0 +Website = https://plugins.getnikola.com/#mermaid +Description = Render Mermaid diagrams from Nikola shortcode blocks with an automatic Mermaid.js loader. + +[Nikola] +PluginCategory = ShortcodePlugin +MinVersion = 8.0.0 diff --git a/v8/shortcode_mermaid/mermaid.py b/v8/shortcode_mermaid/mermaid.py new file mode 100644 index 00000000..e584647d --- /dev/null +++ b/v8/shortcode_mermaid/mermaid.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# MIT License +# +# Copyright (c) 2026 Stephane Manet +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import html +import json + +from nikola.plugin_categories import ShortcodePlugin # type: ignore + + +DEFAULT_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js" +DEFAULT_INITIALIZE = {"startOnLoad": False} + + +class MermaidShortcode(ShortcodePlugin): + name = "mermaid" + + def handler(self, site=None, data=None, lang=None, **kwargs): + theme = html.escape(str(kwargs.get("theme", "default")), quote=True) + content = (data or "").strip() + settings = _settings(site) + + diagram = ( + f'
\n' + f'{content}\n' + '
' + ) + + if not settings["auto_load"]: + return diagram + + return diagram + "\n" + _loader_script(settings) + + +def _settings(site): + config = {} + if site is not None: + config = site.config.get("MERMAID_CONFIG") or {} + + initialize = config.get("initialize", DEFAULT_INITIALIZE) + if not isinstance(initialize, dict): + initialize = DEFAULT_INITIALIZE + + initialize = dict(initialize) + initialize.setdefault("startOnLoad", False) + + return { + "auto_load": bool(config.get("auto_load", True)), + "cdn_url": str(config.get("cdn_url", DEFAULT_CDN_URL)), + "initialize": initialize, + } + + +def _loader_script(settings): + cdn_url = _to_script_json(settings["cdn_url"]) + initialize = _to_script_json(settings["initialize"], sort_keys=True) + + return ( + '' + ) + + +def _to_script_json(value, **kwargs): + return json.dumps(value, **kwargs).replace("