Skip to content

Commit e9d09da

Browse files
committed
open diagram in new tab
1 parent c30bf23 commit e9d09da

File tree

7 files changed

+113
-15
lines changed

7 files changed

+113
-15
lines changed

d2/css/mkdocs_d2_plugin.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
1+
.d2,
2+
.d2-light,
3+
.d2-dark {
4+
display: flex;
5+
flex-direction: column;
6+
}
7+
18
.d2 a:hover {
29
text-decoration: underline
310
}
411

12+
.d2-button {
13+
margin: 10px 0;
14+
padding: 5px 10px;
15+
cursor: pointer;
16+
}
17+
18+
.d2-button:hover {
19+
text-decoration: underline;
20+
}
21+
522
[data-md-color-scheme="default"] div.d2-dark {
623
display: none;
724
}

d2/fence.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import xml.etree.ElementTree as etree
12
from typing import Any, Dict
23

34
from markdown import Markdown
@@ -56,17 +57,29 @@ def formatter(
5657
source, language, class_name, options, md, **kwargs
5758
)
5859

59-
result, _, ok = self.renderer(source.encode(), options["opts"], options["alt"])
60+
result, svg, ok = self.renderer(
61+
source.encode(), options["opts"], options["alt"]
62+
)
6063
if not ok:
6164
error(result)
6265
return fence_code_format(
6366
source, language, class_name, options, md, **kwargs
6467
)
6568

69+
elem = etree.Element("div")
70+
elem.set("class", "d2")
71+
72+
new_tab_button = etree.Element("button")
73+
new_tab_button.set("class", "d2-button")
74+
new_tab_button.set("onclick", f'd2OpenInNewTab("{result}")')
75+
new_tab_button.text = "Open diagram in new tab"
76+
6677
if "opts_dark" not in options:
67-
return f'<div class="d2">{result}</div>'
78+
elem.append(svg.root)
79+
elem.append(new_tab_button)
80+
return etree.tostring(elem, encoding="utf-8", method="html").decode()
6881

69-
dark_result, _, ok = self.renderer(
82+
dark_result, dark_svg, ok = self.renderer(
7083
source.encode(), options["opts_dark"], options["alt"]
7184
)
7285
if not ok:
@@ -75,12 +88,22 @@ def formatter(
7588
source, language, class_name, options, md, **kwargs
7689
)
7790

78-
return (
79-
'<div class="d2">'
80-
f'<div class="d2-light">{result}</div>'
81-
f'<div class="d2-dark">{dark_result}</div>'
82-
"</div>"
83-
)
91+
light = etree.Element("div", {"class": "d2-light"})
92+
light.append(svg.root)
93+
light.append(new_tab_button)
94+
elem.append(light)
95+
96+
dark_new_tab_button = etree.Element("button")
97+
dark_new_tab_button.set("class", "d2-button")
98+
dark_new_tab_button.set("onclick", f'd2OpenInNewTab("{dark_result}")')
99+
dark_new_tab_button.text = "Open diagram in new tab"
100+
101+
dark = etree.Element("div", {"class": "d2-dark"})
102+
dark.append(dark_svg.root)
103+
dark.append(dark_new_tab_button)
104+
elem.append(dark)
105+
106+
return etree.tostring(elem, encoding="utf-8", method="html").decode()
84107

85108

86109
def falsy(value: str) -> bool:

d2/img.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,14 @@ def run(self, root: etree.Element) -> Optional[etree.Element]:
5252
elem.clear()
5353
elem.set("class", "d2")
5454

55+
new_tab_button = etree.Element("button")
56+
new_tab_button.set("class", "d2-button")
57+
new_tab_button.set("onclick", f'd2OpenInNewTab("{result}")')
58+
new_tab_button.text = "Open diagram in new tab"
59+
5560
if not cfg.has_dark_theme():
5661
elem.append(svg.root)
62+
elem.append(new_tab_button)
5763
continue
5864

5965
dark_result, dark_svg, ok = self.renderer(
@@ -65,10 +71,17 @@ def run(self, root: etree.Element) -> Optional[etree.Element]:
6571

6672
light = etree.Element("div", {"class": "d2-light"})
6773
light.append(svg.root)
74+
light.append(new_tab_button)
6875
elem.append(light)
6976

77+
dark_new_tab_button = etree.Element("button")
78+
dark_new_tab_button.set("class", "d2-button")
79+
dark_new_tab_button.set("onclick", f'd2OpenInNewTab("{dark_result}")')
80+
dark_new_tab_button.text = "Open diagram in new tab"
81+
7082
dark = etree.Element("div", {"class": "d2-dark"})
7183
dark.append(dark_svg.root)
84+
dark.append(dark_new_tab_button)
7285
elem.append(dark)
7386

7487

d2/js/__init__.py

Whitespace-only changes.

d2/js/mkdocs_d2_plugin.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function d2OpenInNewTab(svgID) {
2+
const container = document.getElementById(svgID);
3+
const svg = container ? container.querySelector('svg') : null;
4+
if (!svg) return;
5+
6+
const tempDiv = document.createElement('div');
7+
tempDiv.innerHTML = svg.outerHTML;
8+
9+
const links = tempDiv.querySelectorAll('a');
10+
links.forEach(link => {
11+
link.setAttribute('onmouseover', 'this.style.textDecoration="underline"');
12+
link.setAttribute('onmouseout', 'this.style.textDecoration="none"');
13+
link.setAttribute('target', '_blank');
14+
});
15+
16+
const blob = new Blob([tempDiv.innerHTML], { type: "text/html" });
17+
const url = URL.createObjectURL(blob);
18+
const win = window.open(url);
19+
20+
if (win) {
21+
win.onload = () => URL.revokeObjectURL(url);
22+
}
23+
}

d2/plugin.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def on_config(self, config: MkDocsConfig) -> Optional[MkDocsConfig]:
8484
}
8585

8686
config["extra_css"].append("assets/stylesheets/mkdocs_d2_plugin.css")
87+
config["extra_javascript"].append("assets/javascript/mkdocs_d2_plugin.js")
8788

8889
return config
8990

@@ -92,16 +93,31 @@ def on_post_build(self, config: MkDocsConfig) -> None:
9293
self.cache.close()
9394

9495
def on_files(self, files: Files, config):
95-
content = importlib_files("d2.css").joinpath("mkdocs_d2_plugin.css").read_text()
96-
file = File(
96+
# CSS
97+
css_content = (
98+
importlib_files("d2.css").joinpath("mkdocs_d2_plugin.css").read_text()
99+
)
100+
css_file = File(
97101
"assets/stylesheets/mkdocs_d2_plugin.css",
98102
None,
99103
config["site_dir"],
100104
config["use_directory_urls"],
101105
)
102-
file.content_string = content
106+
css_file.content_string = css_content
107+
files.append(css_file)
103108

104-
files.append(file)
109+
# JS
110+
js_content = (
111+
importlib_files("d2.js").joinpath("mkdocs_d2_plugin.js").read_text()
112+
)
113+
js_file = File(
114+
"assets/javascript/mkdocs_d2_plugin.js",
115+
None,
116+
config["site_dir"],
117+
config["use_directory_urls"],
118+
)
119+
js_file.content_string = js_content
120+
files.append(js_file)
105121

106122

107123
def render(
@@ -166,4 +182,7 @@ def render(
166182
svg.root.set("role", "img")
167183
svg.root.set("aria-label", alt)
168184

169-
return etree.tostring(svg.root, encoding="unicode"), svg, True
185+
svg_id = uuid4().hex
186+
svg.root.set("id", svg_id)
187+
188+
return svg_id, svg, True

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@
3838
"Programming Language :: Python :: 3.12",
3939
],
4040
packages=find_packages(),
41-
package_data={"d2.css": ["mkdocs_d2_plugin.css"]},
41+
package_data={
42+
"d2.css": ["mkdocs_d2_plugin.css"],
43+
"d2.js": ["mkdocs_d2_plugin.js"],
44+
},
4245
include_package_data=True,
4346
entry_points={
4447
"mkdocs.plugins": ["d2 = d2.plugin:Plugin"],

0 commit comments

Comments
 (0)