From e9d09da3a4095bdfbd2a6bb97ffcb7111626decc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Wielu=C5=84ski?= Date: Sat, 1 Mar 2025 13:09:16 +0100 Subject: [PATCH] open diagram in new tab --- d2/css/mkdocs_d2_plugin.css | 17 +++++++++++++++ d2/fence.py | 41 +++++++++++++++++++++++++++++-------- d2/img.py | 13 ++++++++++++ d2/js/__init__.py | 0 d2/js/mkdocs_d2_plugin.js | 23 +++++++++++++++++++++ d2/plugin.py | 29 +++++++++++++++++++++----- setup.py | 5 ++++- 7 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 d2/js/__init__.py create mode 100644 d2/js/mkdocs_d2_plugin.js diff --git a/d2/css/mkdocs_d2_plugin.css b/d2/css/mkdocs_d2_plugin.css index 31d0e40..236477d 100644 --- a/d2/css/mkdocs_d2_plugin.css +++ b/d2/css/mkdocs_d2_plugin.css @@ -1,7 +1,24 @@ +.d2, +.d2-light, +.d2-dark { + display: flex; + flex-direction: column; +} + .d2 a:hover { text-decoration: underline } +.d2-button { + margin: 10px 0; + padding: 5px 10px; + cursor: pointer; +} + +.d2-button:hover { + text-decoration: underline; +} + [data-md-color-scheme="default"] div.d2-dark { display: none; } diff --git a/d2/fence.py b/d2/fence.py index f207a8a..3cd138a 100644 --- a/d2/fence.py +++ b/d2/fence.py @@ -1,3 +1,4 @@ +import xml.etree.ElementTree as etree from typing import Any, Dict from markdown import Markdown @@ -56,17 +57,29 @@ def formatter( source, language, class_name, options, md, **kwargs ) - result, _, ok = self.renderer(source.encode(), options["opts"], options["alt"]) + result, svg, ok = self.renderer( + source.encode(), options["opts"], options["alt"] + ) if not ok: error(result) return fence_code_format( source, language, class_name, options, md, **kwargs ) + elem = etree.Element("div") + elem.set("class", "d2") + + new_tab_button = etree.Element("button") + new_tab_button.set("class", "d2-button") + new_tab_button.set("onclick", f'd2OpenInNewTab("{result}")') + new_tab_button.text = "Open diagram in new tab" + if "opts_dark" not in options: - return f'
{result}
' + elem.append(svg.root) + elem.append(new_tab_button) + return etree.tostring(elem, encoding="utf-8", method="html").decode() - dark_result, _, ok = self.renderer( + dark_result, dark_svg, ok = self.renderer( source.encode(), options["opts_dark"], options["alt"] ) if not ok: @@ -75,12 +88,22 @@ def formatter( source, language, class_name, options, md, **kwargs ) - return ( - '
' - f'
{result}
' - f'
{dark_result}
' - "
" - ) + light = etree.Element("div", {"class": "d2-light"}) + light.append(svg.root) + light.append(new_tab_button) + elem.append(light) + + dark_new_tab_button = etree.Element("button") + dark_new_tab_button.set("class", "d2-button") + dark_new_tab_button.set("onclick", f'd2OpenInNewTab("{dark_result}")') + dark_new_tab_button.text = "Open diagram in new tab" + + dark = etree.Element("div", {"class": "d2-dark"}) + dark.append(dark_svg.root) + dark.append(dark_new_tab_button) + elem.append(dark) + + return etree.tostring(elem, encoding="utf-8", method="html").decode() def falsy(value: str) -> bool: diff --git a/d2/img.py b/d2/img.py index c0f07bc..083546a 100644 --- a/d2/img.py +++ b/d2/img.py @@ -52,8 +52,14 @@ def run(self, root: etree.Element) -> Optional[etree.Element]: elem.clear() elem.set("class", "d2") + new_tab_button = etree.Element("button") + new_tab_button.set("class", "d2-button") + new_tab_button.set("onclick", f'd2OpenInNewTab("{result}")') + new_tab_button.text = "Open diagram in new tab" + if not cfg.has_dark_theme(): elem.append(svg.root) + elem.append(new_tab_button) continue dark_result, dark_svg, ok = self.renderer( @@ -65,10 +71,17 @@ def run(self, root: etree.Element) -> Optional[etree.Element]: light = etree.Element("div", {"class": "d2-light"}) light.append(svg.root) + light.append(new_tab_button) elem.append(light) + dark_new_tab_button = etree.Element("button") + dark_new_tab_button.set("class", "d2-button") + dark_new_tab_button.set("onclick", f'd2OpenInNewTab("{dark_result}")') + dark_new_tab_button.text = "Open diagram in new tab" + dark = etree.Element("div", {"class": "d2-dark"}) dark.append(dark_svg.root) + dark.append(dark_new_tab_button) elem.append(dark) diff --git a/d2/js/__init__.py b/d2/js/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/d2/js/mkdocs_d2_plugin.js b/d2/js/mkdocs_d2_plugin.js new file mode 100644 index 0000000..2d02161 --- /dev/null +++ b/d2/js/mkdocs_d2_plugin.js @@ -0,0 +1,23 @@ +function d2OpenInNewTab(svgID) { + const container = document.getElementById(svgID); + const svg = container ? container.querySelector('svg') : null; + if (!svg) return; + + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = svg.outerHTML; + + const links = tempDiv.querySelectorAll('a'); + links.forEach(link => { + link.setAttribute('onmouseover', 'this.style.textDecoration="underline"'); + link.setAttribute('onmouseout', 'this.style.textDecoration="none"'); + link.setAttribute('target', '_blank'); + }); + + const blob = new Blob([tempDiv.innerHTML], { type: "text/html" }); + const url = URL.createObjectURL(blob); + const win = window.open(url); + + if (win) { + win.onload = () => URL.revokeObjectURL(url); + } +} diff --git a/d2/plugin.py b/d2/plugin.py index e5f829b..d67e3d3 100644 --- a/d2/plugin.py +++ b/d2/plugin.py @@ -84,6 +84,7 @@ def on_config(self, config: MkDocsConfig) -> Optional[MkDocsConfig]: } config["extra_css"].append("assets/stylesheets/mkdocs_d2_plugin.css") + config["extra_javascript"].append("assets/javascript/mkdocs_d2_plugin.js") return config @@ -92,16 +93,31 @@ def on_post_build(self, config: MkDocsConfig) -> None: self.cache.close() def on_files(self, files: Files, config): - content = importlib_files("d2.css").joinpath("mkdocs_d2_plugin.css").read_text() - file = File( + # CSS + css_content = ( + importlib_files("d2.css").joinpath("mkdocs_d2_plugin.css").read_text() + ) + css_file = File( "assets/stylesheets/mkdocs_d2_plugin.css", None, config["site_dir"], config["use_directory_urls"], ) - file.content_string = content + css_file.content_string = css_content + files.append(css_file) - files.append(file) + # JS + js_content = ( + importlib_files("d2.js").joinpath("mkdocs_d2_plugin.js").read_text() + ) + js_file = File( + "assets/javascript/mkdocs_d2_plugin.js", + None, + config["site_dir"], + config["use_directory_urls"], + ) + js_file.content_string = js_content + files.append(js_file) def render( @@ -166,4 +182,7 @@ def render( svg.root.set("role", "img") svg.root.set("aria-label", alt) - return etree.tostring(svg.root, encoding="unicode"), svg, True + svg_id = uuid4().hex + svg.root.set("id", svg_id) + + return svg_id, svg, True diff --git a/setup.py b/setup.py index 5cfade0..2d2e9b6 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,10 @@ "Programming Language :: Python :: 3.12", ], packages=find_packages(), - package_data={"d2.css": ["mkdocs_d2_plugin.css"]}, + package_data={ + "d2.css": ["mkdocs_d2_plugin.css"], + "d2.js": ["mkdocs_d2_plugin.js"], + }, include_package_data=True, entry_points={ "mkdocs.plugins": ["d2 = d2.plugin:Plugin"],