mkdocs-terok 0.5.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ Copyright © 2026 by Jiří Vyskočil <jiri@vyskocil.com>
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,5 @@
1
+ Copyright © 2026 by Jiří Vyskočil <jiri@vyskocil.com>
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: mkdocs-terok
3
+ Version: 0.5.6
4
+ Summary: Shared ProperDocs documentation generators for terok projects
5
+ License: 0BSD
6
+ License-File: LICENSE
7
+ License-File: LICENSES/0BSD.txt
8
+ Keywords: properdocs,mkdocs,documentation,quality-report,ci-map
9
+ Author: Jiri Vyskocil
10
+ Author-email: jiri@vyskocil.com
11
+ Maintainer: Jiri Vyskocil
12
+ Maintainer-email: jiri@vyskocil.com
13
+ Requires-Python: >=3.12,<4.0
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Framework :: MkDocs
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: License :: OSI Approved
19
+ Classifier: License :: OSI Approved :: BSD License
20
+ Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Programming Language :: Python
22
+ Classifier: Programming Language :: Python :: 3
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Topic :: Documentation
27
+ Classifier: Typing :: Typed
28
+ Requires-Dist: PyYAML (>=6.0)
29
+ Requires-Dist: properdocs (>=1.6.7)
30
+ Project-URL: Changelog, https://github.com/terok-ai/mkdocs-terok/releases
31
+ Project-URL: Documentation, https://terok-ai.github.io/mkdocs-terok/
32
+ Project-URL: Homepage, https://terok.ai/
33
+ Project-URL: Issues, https://github.com/terok-ai/mkdocs-terok/issues
34
+ Project-URL: Repository, https://github.com/terok-ai/mkdocs-terok
35
+ Project-URL: Security, https://github.com/terok-ai/mkdocs-terok/security/policy
36
+ Description-Content-Type: text/markdown
37
+
38
+ # mkdocs-terok
39
+
40
+ [![License: 0BSD](https://img.shields.io/badge/License-0BSD-green.svg)](https://opensource.org/license/0bsd)
41
+ [![REUSE](https://api.reuse.software/badge/github.com/terok-ai/mkdocs-terok)](https://api.reuse.software/info/github.com/terok-ai/mkdocs-terok)
42
+ [![codecov](https://codecov.io/gh/terok-ai/mkdocs-terok/graph/badge.svg)](https://codecov.io/gh/terok-ai/mkdocs-terok)
43
+
44
+ Shared [ProperDocs](https://properdocs.org/) documentation generators for terok projects.
45
+
46
+ Provides reusable modules for generating CI workflow maps, integration test maps,
47
+ code quality reports, API reference pages, and config reference documentation from
48
+ Pydantic models. A built-in `terok` ProperDocs plugin drives all generators
49
+ automatically; the generator modules themselves never import the doc engine and can
50
+ also be used standalone via `mkdocs-gen-files` shims.
51
+
52
+ The quality report module can optionally parse output from
53
+ [scc](https://github.com/boyter/scc),
54
+ [complexipy](https://github.com/rohaquinern/complexipy),
55
+ [tach](https://github.com/gauge-sh/tach),
56
+ [vulture](https://github.com/jendrikseipp/vulture), and
57
+ [docstr-coverage](https://github.com/HunterMcGushion/docstr_coverage).
58
+ When any of these tools is absent, the corresponding report section degrades
59
+ gracefully to a warning admonition.
60
+
61
+ ## Installation
62
+
63
+ Add to your project's `pyproject.toml` as a docs-build dependency:
64
+
65
+ ```toml
66
+ [tool.poetry.group.docs.dependencies]
67
+ mkdocs-terok = "^0.5"
68
+ ```
69
+
70
+ ## License
71
+
72
+ [0BSD](https://opensource.org/license/0bsd) — use freely, no strings attached.
73
+
@@ -0,0 +1,35 @@
1
+ # mkdocs-terok
2
+
3
+ [![License: 0BSD](https://img.shields.io/badge/License-0BSD-green.svg)](https://opensource.org/license/0bsd)
4
+ [![REUSE](https://api.reuse.software/badge/github.com/terok-ai/mkdocs-terok)](https://api.reuse.software/info/github.com/terok-ai/mkdocs-terok)
5
+ [![codecov](https://codecov.io/gh/terok-ai/mkdocs-terok/graph/badge.svg)](https://codecov.io/gh/terok-ai/mkdocs-terok)
6
+
7
+ Shared [ProperDocs](https://properdocs.org/) documentation generators for terok projects.
8
+
9
+ Provides reusable modules for generating CI workflow maps, integration test maps,
10
+ code quality reports, API reference pages, and config reference documentation from
11
+ Pydantic models. A built-in `terok` ProperDocs plugin drives all generators
12
+ automatically; the generator modules themselves never import the doc engine and can
13
+ also be used standalone via `mkdocs-gen-files` shims.
14
+
15
+ The quality report module can optionally parse output from
16
+ [scc](https://github.com/boyter/scc),
17
+ [complexipy](https://github.com/rohaquinern/complexipy),
18
+ [tach](https://github.com/gauge-sh/tach),
19
+ [vulture](https://github.com/jendrikseipp/vulture), and
20
+ [docstr-coverage](https://github.com/HunterMcGushion/docstr_coverage).
21
+ When any of these tools is absent, the corresponding report section degrades
22
+ gracefully to a warning admonition.
23
+
24
+ ## Installation
25
+
26
+ Add to your project's `pyproject.toml` as a docs-build dependency:
27
+
28
+ ```toml
29
+ [tool.poetry.group.docs.dependencies]
30
+ mkdocs-terok = "^0.5"
31
+ ```
32
+
33
+ ## License
34
+
35
+ [0BSD](https://opensource.org/license/0bsd) — use freely, no strings attached.
@@ -0,0 +1,134 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=1.9.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
3
+ build-backend = "poetry_dynamic_versioning.backend"
4
+
5
+ [tool.poetry]
6
+ name = "mkdocs-terok"
7
+ version = "0.5.6"
8
+ description = "Shared ProperDocs documentation generators for terok projects"
9
+ readme = "README.md"
10
+ license = "0BSD"
11
+ authors = ["Jiri Vyskocil <jiri@vyskocil.com>"]
12
+ keywords = ["properdocs", "mkdocs", "documentation", "quality-report", "ci-map"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Framework :: MkDocs",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: BSD License",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python",
21
+ "Topic :: Documentation",
22
+ "Typing :: Typed",
23
+ ]
24
+
25
+ packages = [
26
+ { include = "mkdocs_terok", from = "src" },
27
+ ]
28
+
29
+ include = [
30
+ { path = "src/mkdocs_terok/_assets/*.css" },
31
+ { path = "src/mkdocs_terok/_assets/*.js" },
32
+ ]
33
+ maintainers = ["Jiri Vyskocil <jiri@vyskocil.com>"]
34
+ homepage = "https://terok.ai/"
35
+ repository = "https://github.com/terok-ai/mkdocs-terok"
36
+ documentation = "https://terok-ai.github.io/mkdocs-terok/"
37
+
38
+ [tool.poetry.dependencies]
39
+ python = ">=3.12,<4.0"
40
+ PyYAML = ">=6.0"
41
+ properdocs = ">=1.6.7"
42
+
43
+ [tool.poetry.plugins."mkdocs.plugins"]
44
+ terok = "mkdocs_terok.plugin:TerokPlugin"
45
+
46
+ [tool.poetry.group.dev.dependencies]
47
+ ruff = ">=0.8"
48
+ docstr-coverage = "^2.3.2"
49
+ vulture = ">=2.12"
50
+ reuse = "^6.2.0"
51
+
52
+ [tool.poetry.group.test.dependencies]
53
+ pytest = ">=7.0"
54
+ pytest-cov = ">=4.0"
55
+ pydantic = ">=2.6"
56
+
57
+
58
+ [tool.poetry.urls]
59
+ Issues = "https://github.com/terok-ai/mkdocs-terok/issues"
60
+ Changelog = "https://github.com/terok-ai/mkdocs-terok/releases"
61
+ Security = "https://github.com/terok-ai/mkdocs-terok/security/policy"
62
+
63
+ [tool.pytest.ini_options]
64
+ testpaths = ["tests"]
65
+ python_files = ["test_*.py"]
66
+ python_classes = ["Test*", "*Tests"]
67
+ python_functions = ["test_*"]
68
+ addopts = "-v --tb=short"
69
+
70
+ [tool.poetry.group.docs]
71
+ optional = true
72
+
73
+ [tool.poetry.group.docs.dependencies]
74
+ mkdocs-material = ">=9.7.6"
75
+ mkdocs-gen-files = ">=0.5"
76
+ mkdocs-literate-nav = ">=0.6"
77
+ mkdocstrings = {version = ">=0.26", extras = ["python"]}
78
+ pydantic = ">=2.6"
79
+ complexipy = ">=5.0"
80
+ tach = ">=0.34"
81
+
82
+ [tool.ruff]
83
+ target-version = "py312"
84
+ line-length = 100
85
+ src = ["src", "tests", "docs"]
86
+
87
+ [tool.ruff.lint]
88
+ select = [
89
+ "E", # pycodestyle errors
90
+ "W", # pycodestyle warnings
91
+ "F", # pyflakes
92
+ "I", # isort
93
+ "B", # flake8-bugbear
94
+ "C4", # flake8-comprehensions
95
+ "UP", # pyupgrade
96
+ "SIM", # flake8-simplify
97
+ ]
98
+ ignore = [
99
+ "E501", # line too long (handled by formatter)
100
+ "B904", # raise from in except
101
+ "SIM102", # nested if statements
102
+ "SIM105", # contextlib.suppress vs try/except/pass
103
+ "SIM108", # use ternary operator
104
+ "SIM117", # nested with statements
105
+ ]
106
+
107
+ [tool.ruff.lint.per-file-ignores]
108
+ "tests/**" = ["E402"]
109
+ "docs/**" = ["E402"]
110
+
111
+ [tool.ruff.lint.isort]
112
+ known-first-party = ["mkdocs_terok"]
113
+ combine-as-imports = true
114
+
115
+ [tool.ruff.format]
116
+ quote-style = "double"
117
+ indent-style = "space"
118
+ docstring-code-format = true
119
+
120
+ [tool.coverage.run]
121
+ source = ["mkdocs_terok"]
122
+ relative_files = true
123
+
124
+ [tool.coverage.xml]
125
+ output = "reports/coverage.xml"
126
+
127
+ [tool.poetry-dynamic-versioning]
128
+ enable = false
129
+ vcs = "git"
130
+ pattern = "^v(?P<base>\\d+\\.\\d+\\.\\d+)$"
131
+ style = "pep440"
132
+
133
+ [tool.poetry-dynamic-versioning.substitution]
134
+ folders = [{path = "src"}]
@@ -0,0 +1,26 @@
1
+ # SPDX-FileCopyrightText: 2026 Jiri Vyskocil
2
+ # SPDX-License-Identifier: 0BSD
3
+
4
+ """Shared ProperDocs documentation generators for terok projects.
5
+
6
+ Provides reusable modules for CI maps, test maps, quality reports,
7
+ API reference pages, and Pydantic config reference rendering.
8
+ The ``terok`` ProperDocs plugin wraps all generators; individual modules
9
+ remain usable standalone (they never import properdocs themselves).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+
16
+ __version__ = "0.5.6" # managed by poetry-dynamic-versioning
17
+
18
+
19
+ def brand_css_path() -> Path:
20
+ """Return the filesystem path to the shared brand CSS file."""
21
+ return Path(__file__).parent / "_assets" / "extra.css"
22
+
23
+
24
+ def mermaid_zoom_js_path() -> Path:
25
+ """Return the filesystem path to the Mermaid diagram zoom script."""
26
+ return Path(__file__).parent / "_assets" / "mermaid_zoom.js"
@@ -0,0 +1,211 @@
1
+ /* Black/Red/Gold palette with subdued neutrals. */
2
+ :root {
3
+ --terok-black: #0b0b0b;
4
+ --terok-red: #a31219;
5
+ --terok-red-dark: #7f0f14;
6
+ --terok-red-pure: #c00000;
7
+ --terok-red-bright: #e04b3f;
8
+ --terok-gold: #c79a22;
9
+ --terok-gold-bright: #d8b454;
10
+ --terok-gold-link: #7a6200;
11
+ --terok-red-hover: #8b0000;
12
+
13
+ --md-text-font: "Ubuntu", sans-serif;
14
+ --md-code-font: "Ubuntu Mono", monospace;
15
+
16
+ --md-primary-fg-color: var(--terok-red-dark);
17
+ --md-primary-fg-color--light: #8f1117;
18
+ --md-primary-fg-color--dark: #650b10;
19
+
20
+ --md-primary-bg-color: #f7f2e8;
21
+ --md-primary-bg-color--light: #f7f2e8;
22
+ --md-primary-bg-color--dark: #e7decc;
23
+
24
+ --md-accent-fg-color: var(--terok-gold);
25
+ --md-accent-fg-color--transparent: rgba(199, 154, 34, 0.12);
26
+
27
+ --md-typeset-a-color: var(--terok-gold-link);
28
+ --md-typeset-mark-color: var(--terok-gold-bright);
29
+ }
30
+
31
+ body,
32
+ .md-typeset,
33
+ .md-header,
34
+ .md-tabs,
35
+ .md-nav,
36
+ .md-search__input {
37
+ font-family: var(--md-text-font);
38
+ }
39
+
40
+ pre,
41
+ code,
42
+ kbd,
43
+ samp {
44
+ font-family: var(--md-code-font);
45
+ }
46
+
47
+ .md-header,
48
+ .md-tabs,
49
+ .md-header__inner,
50
+ .md-tabs__inner {
51
+ background-color: var(--terok-red-dark) !important;
52
+ }
53
+
54
+ .md-typeset a,
55
+ .md-typeset a:visited {
56
+ color: var(--terok-gold-link);
57
+ }
58
+
59
+ .md-typeset a:hover,
60
+ .md-nav__link:hover,
61
+ .md-tabs__link:hover,
62
+ .md-header__button:hover {
63
+ color: var(--terok-red-hover) !important;
64
+ }
65
+
66
+ .md-nav__item--active > .md-nav__link,
67
+ .md-nav__link--active,
68
+ .md-nav--secondary .md-nav__link--active {
69
+ color: var(--terok-gold-bright) !important;
70
+ }
71
+
72
+ .md-nav--secondary .md-nav__link--active::before,
73
+ .md-nav--secondary .md-nav__link--active::after {
74
+ background-color: var(--terok-gold-bright);
75
+ }
76
+
77
+ .md-nav--secondary .md-nav__link--active {
78
+ border-left-color: var(--terok-gold-bright);
79
+ }
80
+
81
+ .md-tabs__link--active {
82
+ color: inherit !important;
83
+ font-weight: 700;
84
+ }
85
+
86
+ #codecov-treemap-img {
87
+ display: block;
88
+ min-width: 300px;
89
+ width: 60%;
90
+ margin: 0 auto;
91
+ }
92
+
93
+ [data-md-color-scheme="default"] {
94
+ --md-primary-fg-color: var(--terok-red-dark);
95
+ --md-primary-fg-color--light: #8f1117;
96
+ --md-primary-fg-color--dark: #650b10;
97
+ --md-default-bg-color: #f7f2e8;
98
+ --md-default-fg-color: #111111;
99
+ --md-default-fg-color--light: #2b2b2b;
100
+ --md-default-fg-color--lighter: #4a4a4a;
101
+ --md-default-fg-color--lightest: #6a6a6a;
102
+ --md-code-bg-color: #efe6d6;
103
+ --md-code-fg-color: #111111;
104
+ --md-footer-bg-color: #0b0b0b;
105
+ --md-footer-fg-color: #f7f2e8;
106
+ --md-footer-fg-color--light: #d9cfbd;
107
+ --md-footer-fg-color--lighter: #bdb39f;
108
+ --md-footer-fg-color--lightest: #a79c87;
109
+ --md-accent-fg-color: var(--terok-gold);
110
+ --md-accent-fg-color--transparent: rgba(199, 154, 34, 0.12);
111
+ --md-typeset-a-color: var(--terok-gold-link);
112
+ }
113
+
114
+ [data-md-color-scheme="slate"] {
115
+ --md-primary-fg-color: var(--terok-red-dark);
116
+ --md-primary-fg-color--light: #8f1117;
117
+ --md-primary-fg-color--dark: #650b10;
118
+ --md-default-bg-color: #0b0b0b;
119
+ --md-default-fg-color: #f2ead9;
120
+ --md-default-fg-color--light: #d8cfbf;
121
+ --md-default-fg-color--lighter: #bcb4a5;
122
+ --md-default-fg-color--lightest: #a19a8d;
123
+ --md-code-bg-color: #151515;
124
+ --md-code-fg-color: #f2ead9;
125
+ --md-footer-bg-color: #000000;
126
+ --md-footer-fg-color: #f2ead9;
127
+ --md-footer-fg-color--light: #d8cfbf;
128
+ --md-footer-fg-color--lighter: #bcb4a5;
129
+ --md-footer-fg-color--lightest: #a19a8d;
130
+
131
+ --md-primary-bg-color: #f2ead9;
132
+ --md-primary-bg-color--light: #f2ead9;
133
+ --md-primary-bg-color--dark: #d8cfbf;
134
+
135
+ --md-accent-fg-color: var(--terok-gold-bright);
136
+ --md-accent-fg-color--transparent: rgba(216, 180, 84, 0.14);
137
+ --md-typeset-a-color: var(--terok-gold-bright);
138
+ }
139
+
140
+ /* ── Mermaid diagram zoom overlay ───────────────────────────── */
141
+ .mermaid-zoom-wrapper {
142
+ position: relative;
143
+ }
144
+
145
+ .mermaid-zoom-btn {
146
+ position: absolute;
147
+ top: 4px;
148
+ right: 4px;
149
+ z-index: 1;
150
+ padding: 2px 8px;
151
+ border: 1px solid var(--md-default-fg-color--lightest);
152
+ border-radius: 4px;
153
+ background: var(--md-default-bg-color);
154
+ color: var(--md-default-fg-color--light);
155
+ font: 0.7rem var(--md-text-font);
156
+ cursor: pointer;
157
+ }
158
+
159
+ .mermaid-zoom-overlay {
160
+ display: none;
161
+ position: fixed;
162
+ inset: 0;
163
+ z-index: 9999;
164
+ background: rgba(0, 0, 0, 0.75);
165
+ backdrop-filter: blur(4px);
166
+ }
167
+
168
+ .mermaid-zoom-overlay.mermaid-zoom-active {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ }
173
+
174
+ .mermaid-zoom-viewport {
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ width: calc(100vw - 4rem);
179
+ height: calc(100vh - 4rem);
180
+ padding: 1rem;
181
+ border-radius: 8px;
182
+ background: var(--md-default-bg-color);
183
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
184
+ overflow: auto;
185
+ }
186
+
187
+ .mermaid-zoom-viewport svg {
188
+ max-width: 100%;
189
+ max-height: 100%;
190
+ }
191
+
192
+ .mermaid-zoom-close {
193
+ position: absolute;
194
+ top: 0.75rem;
195
+ right: 0.75rem;
196
+ z-index: 10000;
197
+ width: 2rem;
198
+ height: 2rem;
199
+ border: none;
200
+ border-radius: 50%;
201
+ background: var(--terok-red-dark);
202
+ color: #fff;
203
+ font-size: 1.1rem;
204
+ line-height: 1;
205
+ cursor: pointer;
206
+ transition: background 0.15s;
207
+ }
208
+
209
+ .mermaid-zoom-close:hover {
210
+ background: var(--terok-red);
211
+ }
@@ -0,0 +1,128 @@
1
+ // SPDX-FileCopyrightText: 2026 Jiri Vyskocil
2
+ // SPDX-License-Identifier: 0BSD
3
+
4
+ /**
5
+ * Adds an "Enlarge" button to each rendered Mermaid diagram.
6
+ * Clicking it opens the diagram in a fullscreen overlay (lightbox-style).
7
+ * Close via backdrop click, the X button, or Escape.
8
+ */
9
+ ;(() => {
10
+ const BUTTON_LABEL = "\u2922 Enlarge"
11
+
12
+ // ── Main action ────────────────────────────────────────
13
+
14
+ /** Scan the DOM for rendered mermaid SVGs and attach buttons. */
15
+ function scan() {
16
+ document.querySelectorAll("pre.mermaid, .mermaid").forEach((el) => {
17
+ if (el.querySelector("svg") || el.tagName === "SVG") attachButton(el)
18
+ })
19
+ }
20
+
21
+ /** Wrap a rendered mermaid container and inject the enlarge button. */
22
+ function attachButton(container) {
23
+ if (container.dataset.zoomAttached) return
24
+ container.dataset.zoomAttached = "1"
25
+
26
+ const wrapper = document.createElement("div")
27
+ wrapper.className = "mermaid-zoom-wrapper"
28
+ container.parentNode.insertBefore(wrapper, container)
29
+ wrapper.appendChild(container)
30
+
31
+ const btn = document.createElement("button")
32
+ btn.className = "mermaid-zoom-btn"
33
+ btn.textContent = BUTTON_LABEL
34
+ btn.addEventListener("click", () => {
35
+ openOverlay(container.querySelector("svg") ?? container)
36
+ })
37
+ container.before(btn)
38
+ }
39
+
40
+ // ── Overlay mechanics ──────────────────────────────────
41
+
42
+ function openOverlay(svgSource) {
43
+ const overlay =
44
+ document.querySelector(".mermaid-zoom-overlay") || createOverlay()
45
+ const viewport = overlay.querySelector(".mermaid-zoom-viewport")
46
+ viewport.innerHTML = ""
47
+ const clone = svgSource.cloneNode(true)
48
+ clone.removeAttribute("height")
49
+ clone.removeAttribute("width")
50
+ clone.style.maxWidth = "100%"
51
+ clone.style.maxHeight = "100%"
52
+ clone.style.width = "auto"
53
+ clone.style.height = "auto"
54
+ viewport.appendChild(clone)
55
+ overlay.classList.add("mermaid-zoom-active")
56
+ document.addEventListener("keydown", onEscape)
57
+ }
58
+
59
+ function closeOverlay(overlay) {
60
+ overlay.classList.remove("mermaid-zoom-active")
61
+ document.removeEventListener("keydown", onEscape)
62
+ }
63
+
64
+ function onEscape(e) {
65
+ if (e.key === "Escape") {
66
+ const overlay = document.querySelector(".mermaid-zoom-overlay")
67
+ if (overlay) closeOverlay(overlay)
68
+ }
69
+ }
70
+
71
+ /** Build the overlay element (singleton, appended on first use). */
72
+ function createOverlay() {
73
+ const overlay = document.createElement("div")
74
+ overlay.className = "mermaid-zoom-overlay"
75
+ overlay.addEventListener("click", (e) => {
76
+ if (e.target === overlay) closeOverlay(overlay)
77
+ })
78
+
79
+ const close = document.createElement("button")
80
+ close.className = "mermaid-zoom-close"
81
+ close.textContent = "\u2715"
82
+ close.title = "Close"
83
+ close.addEventListener("click", () => closeOverlay(overlay))
84
+
85
+ const viewport = document.createElement("div")
86
+ viewport.className = "mermaid-zoom-viewport"
87
+
88
+ overlay.append(close, viewport)
89
+ document.body.appendChild(overlay)
90
+ return overlay
91
+ }
92
+
93
+ // ── Initialization ─────────────────────────────────────
94
+
95
+ // Initial scan once DOM + mermaid rendering settle.
96
+ if (document.readyState === "loading") {
97
+ document.addEventListener("DOMContentLoaded", () => setTimeout(scan, 2000))
98
+ } else {
99
+ setTimeout(scan, 2000)
100
+ }
101
+
102
+ // Catch diagrams rendered after initial load (e.g. instant navigation).
103
+ const observer = new MutationObserver((mutations) => {
104
+ for (const m of mutations) {
105
+ for (const node of m.addedNodes) {
106
+ if (node.nodeType !== 1) continue
107
+ if (
108
+ (node.matches?.(".mermaid, pre.mermaid") && node.querySelector("svg")) ||
109
+ node.querySelector?.(".mermaid svg, pre.mermaid svg") ||
110
+ (node.tagName === "svg" && node.parentElement?.matches?.(".mermaid, pre.mermaid"))
111
+ ) {
112
+ setTimeout(scan, 200)
113
+ return
114
+ }
115
+ }
116
+ }
117
+ })
118
+
119
+ function startObserver() {
120
+ observer.observe(document.body, { childList: true, subtree: true })
121
+ }
122
+
123
+ if (document.body) {
124
+ startObserver()
125
+ } else {
126
+ document.addEventListener("DOMContentLoaded", startObserver, { once: true })
127
+ }
128
+ })()