@docmd/ui 0.4.10 → 0.5.0
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.
- package/assets/css/docmd-main.css +126 -6
- package/assets/js/docmd-main.js +89 -22
- package/package.json +1 -1
- package/templates/404.ejs +89 -0
- package/templates/layout.ejs +75 -31
- package/templates/no-style.ejs +1 -1
- package/templates/partials/footer.ejs +4 -0
- package/templates/partials/version-dropdown.ejs +50 -0
|
@@ -155,7 +155,8 @@ pre {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
a:any-link {
|
|
158
|
-
color: var(--link-color)
|
|
158
|
+
color: var(--link-color);
|
|
159
|
+
text-decoration: none;
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
.skip-link {
|
|
@@ -175,18 +176,19 @@ a:any-link {
|
|
|
175
176
|
.sidebar {
|
|
176
177
|
display: flex;
|
|
177
178
|
flex-direction: column;
|
|
178
|
-
width:
|
|
179
|
+
width: var(--sidebar-width);
|
|
179
180
|
background-color: var(--sidebar-bg);
|
|
180
181
|
color: var(--sidebar-text);
|
|
181
|
-
padding: .5em .25em .5em .5em;
|
|
182
182
|
border-right: 1px solid var(--border-color);
|
|
183
183
|
height: 100vh;
|
|
184
184
|
position: fixed;
|
|
185
|
+
padding: .5em .25em .5em .5em;
|
|
185
186
|
top: 0;
|
|
186
187
|
left: 0;
|
|
187
|
-
overflow
|
|
188
|
+
overflow: hidden;
|
|
188
189
|
box-sizing: border-box;
|
|
189
|
-
flex-shrink: 0
|
|
190
|
+
flex-shrink: 0;
|
|
191
|
+
z-index: 100;
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
.sidebar h1 {
|
|
@@ -526,7 +528,7 @@ body.sidebar-collapsed .main-content-wrapper {
|
|
|
526
528
|
}
|
|
527
529
|
|
|
528
530
|
.sidebar-options-wrapper {
|
|
529
|
-
padding: .
|
|
531
|
+
padding: .25rem 0;
|
|
530
532
|
}
|
|
531
533
|
|
|
532
534
|
.sidebar-options-wrapper.mt-auto {
|
|
@@ -535,6 +537,123 @@ body.sidebar-collapsed .main-content-wrapper {
|
|
|
535
537
|
border-top: 1px solid var(--border-color);
|
|
536
538
|
}
|
|
537
539
|
|
|
540
|
+
/* --- Version Dropdown (Flat UI) --- */
|
|
541
|
+
.sidebar-version-wrapper {
|
|
542
|
+
padding: 0.25rem .5em;
|
|
543
|
+
border-bottom: 1px solid var(--border-color);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.sidebar-bottom-group.mt-auto {
|
|
547
|
+
margin-top: auto;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.sidebar-bottom-group .sidebar-version-wrapper {
|
|
551
|
+
border-bottom: none;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.docmd-version-dropdown {
|
|
555
|
+
position: relative;
|
|
556
|
+
width: 100%;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.version-dropdown-toggle {
|
|
560
|
+
display: flex;
|
|
561
|
+
align-items: center;
|
|
562
|
+
justify-content: space-between;
|
|
563
|
+
width: 100%;
|
|
564
|
+
padding: 0.5rem 0.75rem;
|
|
565
|
+
background-color: var(--bg-color);
|
|
566
|
+
border: 1px solid var(--border-color);
|
|
567
|
+
border-radius: var(--ui-border-radius);
|
|
568
|
+
color: var(--text-heading);
|
|
569
|
+
font-size: 0.85rem;
|
|
570
|
+
font-weight: 500;
|
|
571
|
+
cursor: pointer;
|
|
572
|
+
transition: all 0.2s ease;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.version-dropdown-toggle:hover {
|
|
576
|
+
border-color: var(--text-muted);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.version-dropdown-menu {
|
|
580
|
+
position: absolute;
|
|
581
|
+
top: calc(100% + 4px);
|
|
582
|
+
left: 0;
|
|
583
|
+
right: 0;
|
|
584
|
+
margin: 0;
|
|
585
|
+
padding: 0.25rem;
|
|
586
|
+
background-color: var(--bg-color);
|
|
587
|
+
border: 1px solid var(--border-color);
|
|
588
|
+
border-radius: var(--ui-border-radius);
|
|
589
|
+
box-shadow: var(--shadow-md);
|
|
590
|
+
list-style: none;
|
|
591
|
+
z-index: 100;
|
|
592
|
+
opacity: 0;
|
|
593
|
+
visibility: hidden;
|
|
594
|
+
transform: translateY(-5px);
|
|
595
|
+
transition: all 0.2s ease;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.docmd-version-dropdown.open .version-dropdown-menu {
|
|
599
|
+
opacity: 1;
|
|
600
|
+
visibility: visible;
|
|
601
|
+
transform: translateY(0);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.version-dropdown-item {
|
|
605
|
+
display: flex;
|
|
606
|
+
align-items: center;
|
|
607
|
+
justify-content: space-between;
|
|
608
|
+
padding: 0.4rem 0.5rem;
|
|
609
|
+
color: var(--text-color);
|
|
610
|
+
font-size: 0.85rem;
|
|
611
|
+
text-decoration: none;
|
|
612
|
+
border-radius: 4px;
|
|
613
|
+
transition: background-color 0.2s;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.version-dropdown-item:hover {
|
|
617
|
+
background-color: var(--sidebar-link-active-bg);
|
|
618
|
+
color: var(--text-heading);
|
|
619
|
+
text-decoration: none;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.version-dropdown-item.active {
|
|
623
|
+
font-weight: 600;
|
|
624
|
+
color: var(--link-color);
|
|
625
|
+
background-color: var(--sidebar-link-active-bg);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.version-check {
|
|
629
|
+
width: 1rem;
|
|
630
|
+
height: 1rem;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.version-chevron {
|
|
634
|
+
width: 1rem;
|
|
635
|
+
height: 1rem;
|
|
636
|
+
transition: transform 0.2s ease;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.docmd-version-dropdown.open .version-chevron {
|
|
640
|
+
transform: rotate(180deg);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.sidebar-bottom-group .version-dropdown-menu {
|
|
644
|
+
top: auto;
|
|
645
|
+
bottom: calc(100% + 4px);
|
|
646
|
+
transform: translateY(5px);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.sidebar-bottom-group .docmd-version-dropdown.open .version-dropdown-menu {
|
|
650
|
+
transform: translateY(0);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.sidebar-bottom-group .version-dropdown-toggle .version-chevron {
|
|
654
|
+
transform: rotate(180deg);
|
|
655
|
+
}
|
|
656
|
+
|
|
538
657
|
.card .card-title {
|
|
539
658
|
border-bottom: 1px solid var(--border-color)
|
|
540
659
|
}
|
|
@@ -644,6 +763,7 @@ html[data-theme=dark] .theme-toggle-button .icon-sun {
|
|
|
644
763
|
.sidebar-nav {
|
|
645
764
|
flex-grow: 1;
|
|
646
765
|
overflow-y: auto;
|
|
766
|
+
overflow-x: hidden;
|
|
647
767
|
min-height: 0;
|
|
648
768
|
scrollbar-width: thin;
|
|
649
769
|
}
|
package/assets/js/docmd-main.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* [docmd-source] - Please do not remove this header.
|
|
12
12
|
* --------------------------------------------------------------------
|
|
13
13
|
*/
|
|
14
|
+
|
|
14
15
|
/**
|
|
15
16
|
* --------------------------------------------------------------------
|
|
16
17
|
* docmd : Client-Side Application Logic (SPA Router & UI)
|
|
@@ -18,9 +19,8 @@
|
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
(function() {
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
// 1. EVENT DELEGATION
|
|
23
|
-
// =========================================================================
|
|
24
24
|
document.addEventListener('click', (e) => {
|
|
25
25
|
// Collapsible Navigation
|
|
26
26
|
const navLabel = e.target.closest('.nav-label, .collapse-icon-wrapper');
|
|
@@ -61,6 +61,51 @@
|
|
|
61
61
|
tabItem.classList.add('active');
|
|
62
62
|
if (tabPanes[index]) tabPanes[index].classList.add('active');
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
// Version Dropdown Toggle
|
|
66
|
+
const versionToggle = e.target.closest('.version-dropdown-toggle');
|
|
67
|
+
if (versionToggle) {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
const dropdown = versionToggle.closest('.docmd-version-dropdown');
|
|
70
|
+
dropdown.classList.toggle('open');
|
|
71
|
+
versionToggle.setAttribute('aria-expanded', dropdown.classList.contains('open'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Sticky Version Switching (Path Preservation)
|
|
76
|
+
const versionLink = e.target.closest('.version-dropdown-item');
|
|
77
|
+
if (versionLink) {
|
|
78
|
+
const targetRoot = versionLink.dataset.versionRoot;
|
|
79
|
+
// Use global fallback if undefined (e.g. on 404 pages)
|
|
80
|
+
const currentRoot = window.DOCMD_VERSION_ROOT || '/';
|
|
81
|
+
|
|
82
|
+
if (targetRoot && window.location.pathname) {
|
|
83
|
+
try {
|
|
84
|
+
let currentPath = window.location.pathname;
|
|
85
|
+
const normCurrentRoot = currentRoot.endsWith('/') ? currentRoot : currentRoot + '/';
|
|
86
|
+
|
|
87
|
+
// Only try sticky if we are actually INSIDE the known version path
|
|
88
|
+
if (currentPath.startsWith(normCurrentRoot)) {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
const suffix = currentPath.substring(normCurrentRoot.length);
|
|
91
|
+
const normTargetRoot = targetRoot.endsWith('/') ? targetRoot : targetRoot + '/';
|
|
92
|
+
window.location.href = normTargetRoot + suffix + window.location.hash;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
} catch(e) {
|
|
96
|
+
// Ignore errors, let default click happen
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// If sticky logic skipped (e.g. on 404 page or outside root), default <a> click handles it
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Close Dropdown if clicked outside
|
|
103
|
+
if (!e.target.closest('.docmd-version-dropdown')) {
|
|
104
|
+
document.querySelectorAll('.docmd-version-dropdown.open').forEach(d => {
|
|
105
|
+
d.classList.remove('open');
|
|
106
|
+
d.querySelector('.version-dropdown-toggle').setAttribute('aria-expanded', 'false');
|
|
107
|
+
});
|
|
108
|
+
}
|
|
64
109
|
|
|
65
110
|
// Copy Code Button
|
|
66
111
|
const copyBtn = e.target.closest('.copy-code-button');
|
|
@@ -79,9 +124,7 @@
|
|
|
79
124
|
}
|
|
80
125
|
});
|
|
81
126
|
|
|
82
|
-
// =========================================================================
|
|
83
127
|
// 2. COMPONENT INITIALIZERS
|
|
84
|
-
// =========================================================================
|
|
85
128
|
function injectCopyButtons() {
|
|
86
129
|
if (document.body.dataset.copyCodeEnabled !== 'true') return;
|
|
87
130
|
const svg = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>`;
|
|
@@ -143,19 +186,42 @@
|
|
|
143
186
|
});
|
|
144
187
|
}
|
|
145
188
|
|
|
146
|
-
// =========================================================================
|
|
147
189
|
// 3. TARGETED SPA ROUTER
|
|
148
|
-
// =========================================================================
|
|
149
190
|
function initializeSPA() {
|
|
150
191
|
if (location.protocol === 'file:') return;
|
|
151
192
|
if (document.body.dataset.spaEnabled !== 'true') return;
|
|
152
193
|
|
|
153
194
|
let currentPath = window.location.pathname;
|
|
195
|
+
const pageCache = new Map();
|
|
196
|
+
let prefetchTimer = null;
|
|
197
|
+
|
|
198
|
+
// Intent-based Hover Prefetching
|
|
199
|
+
document.addEventListener('mouseover', (e) => {
|
|
200
|
+
const link = e.target.closest('.sidebar-nav a, .page-navigation a');
|
|
201
|
+
if (!link || link.target === '_blank' || link.hasAttribute('download')) return;
|
|
202
|
+
|
|
203
|
+
const url = new URL(link.href).href;
|
|
204
|
+
if (new URL(url).origin !== location.origin) return;
|
|
205
|
+
if (pageCache.has(url)) return;
|
|
206
|
+
|
|
207
|
+
// Wait 65ms to ensure the user actually intends to click
|
|
208
|
+
clearTimeout(prefetchTimer);
|
|
209
|
+
prefetchTimer = setTimeout(() => {
|
|
210
|
+
pageCache.set(url, fetch(url).then(res => {
|
|
211
|
+
if (!res.ok) throw new Error('Prefetch failed');
|
|
212
|
+
return { html: res.text(), finalUrl: res.url };
|
|
213
|
+
}).catch(() => pageCache.delete(url)));
|
|
214
|
+
}, 65);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Cancel prefetch if the mouse leaves before the 65ms "intent" delay
|
|
218
|
+
document.addEventListener('mouseout', () => clearTimeout(prefetchTimer));
|
|
154
219
|
|
|
155
220
|
document.addEventListener('click', async (e) => {
|
|
156
|
-
// Ignore clicks on expand/collapse arrows so they don't trigger navigation
|
|
157
221
|
if (e.target.closest('.collapse-icon-wrapper')) return;
|
|
158
222
|
|
|
223
|
+
if (e.target.closest('[data-spa-ignore]')) return;
|
|
224
|
+
|
|
159
225
|
const link = e.target.closest('.sidebar-nav a, .page-navigation a');
|
|
160
226
|
if (!link || link.target === '_blank' || link.hasAttribute('download')) return;
|
|
161
227
|
|
|
@@ -168,7 +234,7 @@
|
|
|
168
234
|
});
|
|
169
235
|
|
|
170
236
|
window.addEventListener('popstate', () => {
|
|
171
|
-
if (window.location.pathname === currentPath) return;
|
|
237
|
+
if (window.location.pathname === currentPath) return;
|
|
172
238
|
navigateTo(window.location.href, false);
|
|
173
239
|
});
|
|
174
240
|
|
|
@@ -176,13 +242,22 @@
|
|
|
176
242
|
const layout = document.querySelector('.content-layout');
|
|
177
243
|
|
|
178
244
|
try {
|
|
179
|
-
// Lock height to prevent scrollbar jitter/dragging during DOM swap
|
|
180
245
|
if (layout) layout.style.minHeight = layout.getBoundingClientRect().height + 'px';
|
|
181
246
|
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
247
|
+
let data;
|
|
248
|
+
if (pageCache.has(url)) {
|
|
249
|
+
data = await pageCache.get(url);
|
|
250
|
+
data.html = await data.html;
|
|
251
|
+
} else {
|
|
252
|
+
const res = await fetch(url);
|
|
253
|
+
if (!res.ok) throw new Error('Fetch failed');
|
|
254
|
+
data = { html: await res.text(), finalUrl: res.url };
|
|
255
|
+
pageCache.set(url, Promise.resolve(data));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const finalUrl = data.finalUrl;
|
|
259
|
+
const html = data.html;
|
|
260
|
+
|
|
186
261
|
const parser = new DOMParser();
|
|
187
262
|
const doc = parser.parseFromString(html, 'text/html');
|
|
188
263
|
|
|
@@ -205,24 +280,21 @@
|
|
|
205
280
|
}
|
|
206
281
|
});
|
|
207
282
|
|
|
208
|
-
//
|
|
283
|
+
// Sync Sidebar State
|
|
209
284
|
const oldLis = Array.from(document.querySelectorAll('.sidebar-nav li'));
|
|
210
285
|
const newLis = Array.from(doc.querySelectorAll('.sidebar-nav li'));
|
|
211
286
|
|
|
212
287
|
oldLis.forEach((oldLi, i) => {
|
|
213
288
|
const newLi = newLis[i];
|
|
214
289
|
if (newLi) {
|
|
215
|
-
// Sync active classes
|
|
216
290
|
oldLi.classList.toggle('active', newLi.classList.contains('active'));
|
|
217
291
|
oldLi.classList.toggle('active-parent', newLi.classList.contains('active-parent'));
|
|
218
292
|
|
|
219
|
-
// Add expanded class if the new page requires it, but NEVER remove it
|
|
220
293
|
if (newLi.classList.contains('expanded')) {
|
|
221
294
|
oldLi.classList.add('expanded');
|
|
222
295
|
oldLi.setAttribute('aria-expanded', 'true');
|
|
223
296
|
}
|
|
224
297
|
|
|
225
|
-
// Sync relative hrefs
|
|
226
298
|
const oldA = oldLi.querySelector('a');
|
|
227
299
|
const newA = newLi.querySelector('a');
|
|
228
300
|
if (oldA && newA) {
|
|
@@ -232,7 +304,6 @@
|
|
|
232
304
|
}
|
|
233
305
|
});
|
|
234
306
|
|
|
235
|
-
// 3. Swap Body Components (Removed .sidebar-nav from this list)
|
|
236
307
|
const selectorsToSwap =[
|
|
237
308
|
'.content-layout',
|
|
238
309
|
'.page-header .header-title',
|
|
@@ -247,7 +318,6 @@
|
|
|
247
318
|
if (oldEl && newEl) oldEl.innerHTML = newEl.innerHTML;
|
|
248
319
|
});
|
|
249
320
|
|
|
250
|
-
// Scroll & Init
|
|
251
321
|
const hash = new URL(finalUrl).hash;
|
|
252
322
|
if (hash) {
|
|
253
323
|
document.querySelector(hash)?.scrollIntoView();
|
|
@@ -262,7 +332,6 @@
|
|
|
262
332
|
|
|
263
333
|
document.dispatchEvent(new CustomEvent('docmd:page-mounted', { detail: { url: finalUrl } }));
|
|
264
334
|
|
|
265
|
-
// Unlock height smoothly
|
|
266
335
|
setTimeout(() => {
|
|
267
336
|
const newLayout = document.querySelector('.content-layout');
|
|
268
337
|
if (newLayout) newLayout.style.minHeight = '';
|
|
@@ -274,9 +343,7 @@
|
|
|
274
343
|
}
|
|
275
344
|
}
|
|
276
345
|
|
|
277
|
-
// =========================================================================
|
|
278
346
|
// 4. BOOTSTRAP
|
|
279
|
-
// =========================================================================
|
|
280
347
|
document.addEventListener('DOMContentLoaded', () => {
|
|
281
348
|
if (localStorage.getItem('docmd-sidebar-collapsed') === 'true') {
|
|
282
349
|
document.body.classList.add('sidebar-collapsed');
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
---------------------------------------------------------------
|
|
3
|
+
docmd : the minimalist, zero-config documentation generator.
|
|
4
|
+
@website https://docmd.io
|
|
5
|
+
[docmd-source] - Please do not remove this header.
|
|
6
|
+
---------------------------------------------------------------
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
+
<title><%= pageTitle %></title>
|
|
15
|
+
|
|
16
|
+
<!-- Force absolute paths for assets using relativePathToRoot (which we will set to '/') -->
|
|
17
|
+
<%- faviconLinkHtml || '' %>
|
|
18
|
+
|
|
19
|
+
<!-- 1. Core CSS -->
|
|
20
|
+
<link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css?v=<%= buildHash %>">
|
|
21
|
+
|
|
22
|
+
<!-- 2. Highlight.js (Theme aware) -->
|
|
23
|
+
<%
|
|
24
|
+
const isDarkDefault = defaultMode === 'dark';
|
|
25
|
+
%>
|
|
26
|
+
<link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-highlight-light.css?v=<%= buildHash %>" id="hljs-light" <%= isDarkDefault ? 'disabled' : '' %>>
|
|
27
|
+
<link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-highlight-dark.css?v=<%= buildHash %>" id="hljs-dark" <%= isDarkDefault ? '' : 'disabled' %>>
|
|
28
|
+
|
|
29
|
+
<!-- 3. Theme CSS -->
|
|
30
|
+
<% if (theme && theme.name !== 'default') { %>
|
|
31
|
+
<link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-theme-<%= theme.name %>.css?v=<%= buildHash %>">
|
|
32
|
+
<% } %>
|
|
33
|
+
|
|
34
|
+
<!-- 4. Custom CSS -->
|
|
35
|
+
<% (customCssFiles || []).forEach(cssFile => {
|
|
36
|
+
const cleanPath = cssFile.startsWith('/') ? cssFile.substring(1) : cssFile;
|
|
37
|
+
%>
|
|
38
|
+
<link rel="stylesheet" href="<%= relativePathToRoot %><%= cleanPath %>?v=<%= buildHash %>">
|
|
39
|
+
<% }); %>
|
|
40
|
+
|
|
41
|
+
<!-- 5. Theme Init (Dark Mode logic) -->
|
|
42
|
+
<%- themeInitScript %>
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
body {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
min-height: 100vh;
|
|
51
|
+
text-align: center;
|
|
52
|
+
padding: 20px;
|
|
53
|
+
background-color: var(--bg-color);
|
|
54
|
+
color: var(--text-color);
|
|
55
|
+
}
|
|
56
|
+
.error-logo { height: 60px; margin-bottom: 2rem; width: auto; display: block; }
|
|
57
|
+
.error-logo.light { display: var(--display-light, block); }
|
|
58
|
+
.error-logo.dark { display: var(--display-dark, none); }
|
|
59
|
+
|
|
60
|
+
/* Simple theme logic for logo if JS fails */
|
|
61
|
+
@media (prefers-color-scheme: dark) {
|
|
62
|
+
:root[data-theme="system"] .error-logo.light { display: none; }
|
|
63
|
+
:root[data-theme="system"] .error-logo.dark { display: block; }
|
|
64
|
+
}
|
|
65
|
+
:root[data-theme="dark"] .error-logo.light { display: none; }
|
|
66
|
+
:root[data-theme="dark"] .error-logo.dark { display: block; }
|
|
67
|
+
|
|
68
|
+
h1 { font-size: 5rem; margin: 0; line-height: 1; color: var(--link-color); font-weight: 700; }
|
|
69
|
+
h2 { font-size: 2rem; margin: 1rem 0; }
|
|
70
|
+
p { font-size: 1.2rem; color: var(--text-muted); margin-bottom: 2rem; max-width: 500px; }
|
|
71
|
+
.docmd-button { font-size: 1.1rem; padding: 0.8rem 1.5rem; }
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<% if (logo) { %>
|
|
76
|
+
<% if (logo.light) { %>
|
|
77
|
+
<img src="<%= relativePathToRoot %><%= logo.light.replace(/^\//, '') %>" class="error-logo light" alt="Logo">
|
|
78
|
+
<% } %>
|
|
79
|
+
<% if (logo.dark) { %>
|
|
80
|
+
<img src="<%= relativePathToRoot %><%= logo.dark.replace(/^\//, '') %>" class="error-logo dark" alt="Logo">
|
|
81
|
+
<% } %>
|
|
82
|
+
<% } %>
|
|
83
|
+
|
|
84
|
+
<h1>404</h1>
|
|
85
|
+
<h2>Page Not Found</h2>
|
|
86
|
+
<p><%= content %></p>
|
|
87
|
+
<a href="<%= relativePathToRoot %>" class="docmd-button">Return Home</a>
|
|
88
|
+
</body>
|
|
89
|
+
</html>
|
package/templates/layout.ejs
CHANGED
|
@@ -10,14 +10,40 @@
|
|
|
10
10
|
<html lang="en">
|
|
11
11
|
<head>
|
|
12
12
|
<meta charset="UTF-8">
|
|
13
|
-
<meta name="generator" content="docmd v0.
|
|
13
|
+
<meta name="generator" content="docmd v0.5.x">
|
|
14
14
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
15
|
+
<%
|
|
16
|
+
let versionRoot = '/';
|
|
17
|
+
let siteRoot = relativePathToRoot;
|
|
18
|
+
|
|
19
|
+
if (config.versions?.current && config._activeVersion?.id) {
|
|
20
|
+
const isSubVersion = config.versions.current !== config._activeVersion.id;
|
|
21
|
+
versionRoot = isSubVersion ? '/' + config._activeVersion.id + '/' : '/';
|
|
22
|
+
|
|
23
|
+
// If we are in a sub-version (v04/), the relativePathToRoot takes us to /v04/.
|
|
24
|
+
// We need to go up one more level to reach the true site root.
|
|
25
|
+
if (isSubVersion) {
|
|
26
|
+
siteRoot = (relativePathToRoot === './' ? '' : relativePathToRoot) + '../';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (locals.isOfflineMode) {
|
|
31
|
+
versionRoot = '';
|
|
32
|
+
}
|
|
33
|
+
%>
|
|
15
34
|
<script>
|
|
16
35
|
var root = "<%= relativePathToRoot %>";
|
|
17
36
|
if (root && !root.endsWith('/')) root += '/';
|
|
18
37
|
if (root === '') root = './';
|
|
19
|
-
window.DOCMD_ROOT = root;
|
|
38
|
+
window.DOCMD_ROOT = root; // Context Root (Assets)
|
|
39
|
+
|
|
40
|
+
var siteRoot = "<%= siteRoot %>";
|
|
41
|
+
if (siteRoot && !siteRoot.endsWith('/')) siteRoot += '/';
|
|
42
|
+
if (siteRoot === '') siteRoot = './';
|
|
43
|
+
window.DOCMD_SITE_ROOT = siteRoot; // True Site Root (Search Index)
|
|
44
|
+
|
|
20
45
|
window.DOCMD_DEFAULT_MODE = "<%= defaultMode %>";
|
|
46
|
+
window.DOCMD_VERSION_ROOT = "<%- versionRoot %>";
|
|
21
47
|
</script>
|
|
22
48
|
<title><%= pageTitle %></title>
|
|
23
49
|
<%- faviconLinkHtml || '' %>
|
|
@@ -41,42 +67,60 @@
|
|
|
41
67
|
<% }); %>
|
|
42
68
|
<%- themeInitScript %>
|
|
43
69
|
</head>
|
|
44
|
-
<body class="<%= sidebarConfig?.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
<body class="<%= sidebarConfig?.enabled === false ? 'no-sidebar' : (sidebarConfig?.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible') %>"
|
|
71
|
+
data-default-collapsed="<%= sidebarConfig?.defaultCollapsed %>"
|
|
72
|
+
data-copy-code-enabled="<%= config.copyCode === true %>"
|
|
73
|
+
data-spa-enabled="<%= config.layout?.spa !== false %>">
|
|
48
74
|
|
|
49
75
|
<a href="#main-content" class="skip-link">Skip to main content</a>
|
|
50
|
-
|
|
51
|
-
<aside class="sidebar">
|
|
52
|
-
<div class="sidebar-header">
|
|
53
|
-
<% if (logo && logo.light && logo.dark) { %>
|
|
54
|
-
<a href="<%= logo.href || relativePathToRoot %>" class="logo-link">
|
|
55
|
-
<img src="<%= relativePathToRoot %><%- logo.light.startsWith('/') ? logo.light.substring(1) : logo.light %>" alt="<%= logo.alt || siteTitle %>" class="logo-light" <% if (logo.height) { %>style="height: <%= logo.height %>;"<% } %>>
|
|
56
|
-
<img src="<%= relativePathToRoot %><%- logo.dark.startsWith('/') ? logo.dark.substring(1) : logo.dark %>" alt="<%= logo.alt || siteTitle %>" class="logo-dark" <% if (logo.height) { %>style="height: <%= logo.height %>;"<% } %>>
|
|
57
|
-
</a>
|
|
58
|
-
<% } else { %>
|
|
59
|
-
<h1><a href="<%= relativePathToRoot %>index.html"><%= siteTitle %></a></h1>
|
|
60
|
-
<% } %>
|
|
61
|
-
<span class="mobile-view sidebar-menu-button float-right">
|
|
62
|
-
<%- renderIcon("menu") %>
|
|
63
|
-
</span>
|
|
64
|
-
</div>
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
<% if (sidebarConfig?.enabled !== false) { %>
|
|
78
|
+
<aside class="sidebar">
|
|
79
|
+
<div class="sidebar-header">
|
|
80
|
+
<% if (logo && logo.light && logo.dark) { %>
|
|
81
|
+
<a href="<%= logo.href || relativePathToRoot %>" class="logo-link">
|
|
82
|
+
<img src="<%= relativePathToRoot %><%- logo.light.startsWith('/') ? logo.light.substring(1) : logo.light %>" alt="<%= logo.alt || siteTitle %>" class="logo-light" <% if (logo.height) { %>style="height: <%= logo.height %>;"<% } %>>
|
|
83
|
+
<img src="<%= relativePathToRoot %><%- logo.dark.startsWith('/') ? logo.dark.substring(1) : logo.dark %>" alt="<%= logo.alt || siteTitle %>" class="logo-dark" <% if (logo.height) { %>style="height: <%= logo.height %>;"<% } %>>
|
|
84
|
+
</a>
|
|
85
|
+
<% } else { %>
|
|
86
|
+
<h1><a href="<%= relativePathToRoot %>index.html"><%= siteTitle %></a></h1>
|
|
87
|
+
<% } %>
|
|
88
|
+
<span class="mobile-view sidebar-menu-button float-right">
|
|
89
|
+
<%- renderIcon("menu") %>
|
|
90
|
+
</span>
|
|
69
91
|
</div>
|
|
70
|
-
<% } %>
|
|
71
92
|
|
|
72
|
-
|
|
93
|
+
<div class="sidebar-top-group">
|
|
94
|
+
<% if (locals.optionsMenu && optionsMenu.position === 'sidebar-top') { %>
|
|
95
|
+
<div class="sidebar-options-wrapper">
|
|
96
|
+
<%- include('partials/options-menu', { optionsMenu }) %>
|
|
97
|
+
</div>
|
|
98
|
+
<% } %>
|
|
99
|
+
|
|
100
|
+
<% if (config.versions && config.versions.position === 'sidebar-top') { %>
|
|
101
|
+
<div class="sidebar-version-wrapper">
|
|
102
|
+
<%- include('partials/version-dropdown', { versions: config.versions, activeVersion: config._activeVersion, relativePathToRoot }) %>
|
|
103
|
+
</div>
|
|
104
|
+
<% } %>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<%- navigationHtml %>
|
|
108
|
+
|
|
109
|
+
<div class="sidebar-bottom-group mt-auto">
|
|
110
|
+
<% if (config.versions && config.versions.position === 'sidebar-bottom') { %>
|
|
111
|
+
<div class="sidebar-version-wrapper">
|
|
112
|
+
<%- include('partials/version-dropdown', { versions: config.versions, activeVersion: config._activeVersion, relativePathToRoot }) %>
|
|
113
|
+
</div>
|
|
114
|
+
<% } %>
|
|
73
115
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
116
|
+
<% if (locals.optionsMenu && optionsMenu.position === 'sidebar-bottom') { %>
|
|
117
|
+
<div class="sidebar-options-wrapper">
|
|
118
|
+
<%- include('partials/options-menu', { optionsMenu }) %>
|
|
119
|
+
</div>
|
|
120
|
+
<% } %>
|
|
77
121
|
</div>
|
|
78
|
-
|
|
79
|
-
|
|
122
|
+
</aside>
|
|
123
|
+
<% } %>
|
|
80
124
|
|
|
81
125
|
<div class="main-content-wrapper">
|
|
82
126
|
<% if (headerConfig?.enabled !== false) { %>
|
package/templates/no-style.ejs
CHANGED
|
@@ -40,18 +40,22 @@
|
|
|
40
40
|
|
|
41
41
|
<div class="footer-complete-bottom">
|
|
42
42
|
<div class="user-footer"><%- footerHtml || (footerConfig.copyright ? footerConfig.copyright : '') %></div>
|
|
43
|
+
<% if (footerConfig.branding !== false) { %>
|
|
43
44
|
<div class="branding-footer">
|
|
44
45
|
Built with <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"></path><path d="M12 5 9.04 7.96a2.17 2.17 0 0 0 0 3.08c.82.82 2.13.85 3 .07l2.07-1.9a2.82 2.82 0 0 1 3.79 0l2.96 2.66"></path><path d="m18 15-2-2"></path><path d="m15 18-2-2"></path></svg> <a href="https://docmd.io" target="_blank" rel="noopener">docmd.</a>
|
|
45
46
|
</div>
|
|
47
|
+
<% } %>
|
|
46
48
|
</div>
|
|
47
49
|
</footer>
|
|
48
50
|
<% } else { %>
|
|
49
51
|
<footer class="page-footer">
|
|
50
52
|
<div class="footer-content">
|
|
51
53
|
<div class="user-footer"><%- footerHtml || (footerConfig?.copyright ? footerConfig.copyright : '') %></div>
|
|
54
|
+
<% if (footerConfig?.branding !== false) { %>
|
|
52
55
|
<div class="branding-footer">
|
|
53
56
|
Built with <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"></path><path d="M12 5 9.04 7.96a2.17 2.17 0 0 0 0 3.08c.82.82 2.13.85 3 .07l2.07-1.9a2.82 2.82 0 0 1 3.79 0l2.96 2.66"></path><path d="m18 15-2-2"></path><path d="m15 18-2-2"></path></svg> <a href="https://docmd.io" target="_blank" rel="noopener">docmd.</a>
|
|
54
57
|
</div>
|
|
58
|
+
<% } %>
|
|
55
59
|
</div>
|
|
56
60
|
</footer>
|
|
57
61
|
<% } %>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<%# ---------------------------------------------------------------
|
|
2
|
+
# docmd : the minimalist, zero-config documentation generator.
|
|
3
|
+
# @website https://docmd.io
|
|
4
|
+
# [docmd-source] - Please do not remove this header.
|
|
5
|
+
# ---------------------------------------------------------------
|
|
6
|
+
%>
|
|
7
|
+
|
|
8
|
+
<% if (versions && versions.all.length > 0) {
|
|
9
|
+
const current = activeVersion || versions.all.find(v => v.id === versions.current);
|
|
10
|
+
|
|
11
|
+
// If we are in a sub-version (e.g. /v1/), we go up one level (../) to get to root.
|
|
12
|
+
const isCurrentSubVersion = current && current.id !== versions.current;
|
|
13
|
+
const upOneLevel = isCurrentSubVersion ? '../' : '';
|
|
14
|
+
%>
|
|
15
|
+
<div class="docmd-version-dropdown">
|
|
16
|
+
<button class="version-dropdown-toggle" aria-expanded="false" aria-label="Select Version">
|
|
17
|
+
<span class="version-label"><%= current ? current.label : 'Version' %></span>
|
|
18
|
+
<%- renderIcon('chevron-down', { class: 'version-chevron' }) %>
|
|
19
|
+
</button>
|
|
20
|
+
<ul class="version-dropdown-menu">
|
|
21
|
+
<% versions.all.forEach(v => {
|
|
22
|
+
const isCurrentActive = current && v.id === current.id;
|
|
23
|
+
const isTargetRootVersion = v.id === versions.current;
|
|
24
|
+
|
|
25
|
+
// We use the 'config' object which is available in all templates
|
|
26
|
+
|
|
27
|
+
const base = config.base || '/';
|
|
28
|
+
const normalizedBase = base.endsWith('/') ? base : base + '/';
|
|
29
|
+
|
|
30
|
+
const targetSuffix = isTargetRootVersion ? '' : v.id + '/';
|
|
31
|
+
const absoluteHref = normalizedBase + targetSuffix;
|
|
32
|
+
|
|
33
|
+
// Data Root for JS Sticky Logic
|
|
34
|
+
const dataRoot = absoluteHref;
|
|
35
|
+
%>
|
|
36
|
+
<li>
|
|
37
|
+
<a href="<%= absoluteHref %>"
|
|
38
|
+
class="version-dropdown-item <%= isCurrentActive ? 'active' : '' %>"
|
|
39
|
+
data-spa-ignore
|
|
40
|
+
data-version-root="<%= dataRoot %>">
|
|
41
|
+
<%= v.label %>
|
|
42
|
+
<% if (isCurrentActive) { %>
|
|
43
|
+
<%- renderIcon('check', { class: 'version-check' }) %>
|
|
44
|
+
<% } %>
|
|
45
|
+
</a>
|
|
46
|
+
</li>
|
|
47
|
+
<% }) %>
|
|
48
|
+
</ul>
|
|
49
|
+
</div>
|
|
50
|
+
<% } %>
|