@docmd/ui 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -7,7 +7,7 @@ Contains:
7
7
  - **Assets:** Core CSS and JavaScript.
8
8
  - **Icons:** SVG icon sets.
9
9
 
10
- ## The docmd Ecosystem
10
+ ## The `docmd` Ecosystem
11
11
 
12
12
  `docmd` is a modular system. Here are the official packages:
13
13
 
@@ -22,6 +22,7 @@ Contains:
22
22
 
23
23
  **Plugins**
24
24
  * [**@docmd/plugin-search**](https://www.npmjs.com/package/@docmd/plugin-search) - Offline full-text search.
25
+ * [**@docmd/plugin-pwa**](https://www.npmjs.com/package/@docmd/plugin-pwa) - Progressive Web App support.
25
26
  * [**@docmd/plugin-mermaid**](https://www.npmjs.com/package/@docmd/plugin-mermaid) - Diagrams and flowcharts.
26
27
  * [**@docmd/plugin-seo**](https://www.npmjs.com/package/@docmd/plugin-seo) - Meta tags and Open Graph data.
27
28
  * [**@docmd/plugin-sitemap**](https://www.npmjs.com/package/@docmd/plugin-sitemap) - Automatic sitemap generation.
@@ -427,7 +427,7 @@ body.sidebar-collapsed .main-content-wrapper {
427
427
  background-color: var(--header-bg);
428
428
  border-bottom: 1px solid var(--header-border);
429
429
  position: sticky;
430
- top: 0;
430
+ top: var(--header-top-offset);
431
431
  z-index: 50;
432
432
  backdrop-filter: blur(8px);
433
433
  -webkit-backdrop-filter: blur(8px);
@@ -448,6 +448,265 @@ body.sidebar-collapsed .main-content-wrapper {
448
448
  gap: 5px
449
449
  }
450
450
 
451
+ :root {
452
+ --menubar-h: 52px;
453
+ --header-h: 54px;
454
+
455
+ --fixed-top-offset: 0px;
456
+ --header-top-offset: 0px;
457
+ --sticky-top-offset: 0px;
458
+
459
+ --menubar-bg: var(--bg-color);
460
+ --menubar-border: var(--border-color);
461
+ --menubar-text: var(--text-color);
462
+ --menubar-link-hover-bg: var(--sidebar-link-active-bg);
463
+ }
464
+
465
+ body.has-menubar-top {
466
+ --fixed-top-offset: var(--menubar-h);
467
+ --header-top-offset: var(--menubar-h);
468
+ --sticky-top-offset: var(--menubar-h);
469
+ padding-top: var(--menubar-h);
470
+ }
471
+
472
+ body:not(.no-header) {
473
+ --sticky-top-offset: var(--header-h);
474
+ }
475
+
476
+ body.has-menubar-top:not(.no-header) {
477
+ --sticky-top-offset: calc(var(--menubar-h) + var(--header-h));
478
+ }
479
+
480
+ body.has-menubar-header {
481
+ --header-top-offset: var(--menubar-h);
482
+ --sticky-top-offset: var(--menubar-h);
483
+ }
484
+
485
+ body.has-menubar-header:not(.no-header) {
486
+ --sticky-top-offset: calc(var(--menubar-h) + var(--header-h));
487
+ }
488
+
489
+ html {
490
+ scroll-padding-top: calc(var(--sticky-top-offset) + 1.5rem);
491
+ }
492
+
493
+ .docmd-menubar {
494
+ z-index: 200;
495
+ height: var(--menubar-h);
496
+ background-color: var(--menubar-bg);
497
+ border-bottom: 1px solid var(--menubar-border);
498
+ backdrop-filter: blur(8px);
499
+ -webkit-backdrop-filter: blur(8px);
500
+ width: 100%;
501
+ box-sizing: border-box;
502
+ transition: background-color 0.3s;
503
+ }
504
+
505
+ .menubar-options {
506
+ margin-left: 0.5rem;
507
+ padding-left: 0.5rem;
508
+ border-left: 1px solid var(--menubar-border);
509
+ display: flex;
510
+ align-items: center;
511
+ }
512
+
513
+ body.has-menubar-top .docmd-menubar {
514
+ position: fixed;
515
+ top: 0;
516
+ left: 0;
517
+ right: 0;
518
+ }
519
+
520
+ body.has-menubar-header .docmd-menubar {
521
+ position: sticky;
522
+ top: 0;
523
+ z-index: 150;
524
+ }
525
+
526
+ .menubar-inner {
527
+ display: flex;
528
+ align-items: center;
529
+ justify-content: space-between;
530
+ height: 100%;
531
+ padding: 0 1.5rem;
532
+ gap: 1rem;
533
+ }
534
+
535
+ .menubar-left,
536
+ .menubar-right {
537
+ display: flex;
538
+ align-items: center;
539
+ gap: 0.25rem;
540
+ flex-shrink: 0;
541
+ }
542
+
543
+ .menubar-brand {
544
+ display: inline-flex;
545
+ align-items: center;
546
+ gap: 0.5rem;
547
+ text-decoration: none;
548
+ color: var(--text-heading);
549
+ font-weight: 700;
550
+ font-size: 1rem;
551
+ padding: 0.25rem 0.5rem;
552
+ border-radius: var(--ui-border-radius);
553
+ transition: opacity 0.2s;
554
+ }
555
+
556
+ .menubar-brand:hover {
557
+ opacity: 0.8;
558
+ text-decoration: none;
559
+ }
560
+
561
+ .menubar-brand-text {
562
+ white-space: nowrap;
563
+ }
564
+
565
+ .menubar-link {
566
+ display: inline-flex;
567
+ align-items: center;
568
+ gap: 0.35rem;
569
+ padding: 0.35rem 0.65rem;
570
+ border-radius: var(--ui-border-radius);
571
+ font-size: 0.875rem;
572
+ color: var(--menubar-text);
573
+ text-decoration: none;
574
+ transition: background-color 0.15s, color 0.15s;
575
+ white-space: nowrap;
576
+ }
577
+
578
+ .menubar-link:hover {
579
+ background-color: var(--menubar-link-hover-bg);
580
+ color: var(--text-heading);
581
+ text-decoration: none;
582
+ }
583
+
584
+ .menubar-icon {
585
+ width: 1em;
586
+ height: 1em;
587
+ flex-shrink: 0;
588
+ }
589
+
590
+ .menubar-ext-icon {
591
+ width: 0.75em;
592
+ height: 0.75em;
593
+ opacity: 0.6;
594
+ flex-shrink: 0;
595
+ }
596
+
597
+ .menubar-dropdown {
598
+ position: relative;
599
+ }
600
+
601
+ .menubar-dropdown-toggle {
602
+ display: inline-flex;
603
+ align-items: center;
604
+ gap: 0.35rem;
605
+ padding: 0.35rem 0.65rem;
606
+ border-radius: var(--ui-border-radius);
607
+ font-size: 0.875rem;
608
+ color: var(--menubar-text);
609
+ background: none;
610
+ border: none;
611
+ cursor: pointer;
612
+ font-family: inherit;
613
+ transition: background-color 0.15s, color 0.15s;
614
+ white-space: nowrap;
615
+ }
616
+
617
+ .menubar-dropdown-toggle:hover {
618
+ background-color: var(--menubar-link-hover-bg);
619
+ color: var(--text-heading);
620
+ }
621
+
622
+ .menubar-chevron {
623
+ width: 0.85em;
624
+ height: 0.85em;
625
+ opacity: 0.7;
626
+ transition: transform 0.2s ease;
627
+ }
628
+
629
+ .menubar-dropdown:hover .menubar-chevron,
630
+ .menubar-dropdown:focus-within .menubar-chevron {
631
+ transform: rotate(180deg);
632
+ }
633
+
634
+ .menubar-dropdown-menu {
635
+ position: absolute;
636
+ top: calc(100% + 6px);
637
+ left: 0;
638
+ min-width: 180px;
639
+ margin: 0;
640
+ padding: 0.25rem;
641
+ list-style: none;
642
+ background-color: var(--bg-color);
643
+ border: 1px solid var(--border-color);
644
+ border-radius: var(--ui-border-radius);
645
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.05);
646
+ z-index: 300;
647
+ opacity: 0;
648
+ visibility: hidden;
649
+ transform: translateY(-4px);
650
+ transition: opacity 0.15s, transform 0.15s, visibility 0.15s;
651
+ }
652
+
653
+ .menubar-dropdown-menu.menubar-dropdown-right {
654
+ left: auto;
655
+ right: 0;
656
+ }
657
+
658
+ .menubar-dropdown:hover .menubar-dropdown-menu,
659
+ .menubar-dropdown:focus-within .menubar-dropdown-menu {
660
+ opacity: 1;
661
+ visibility: visible;
662
+ transform: translateY(0);
663
+ }
664
+
665
+ .menubar-dropdown-menu li a {
666
+ display: flex;
667
+ align-items: center;
668
+ gap: 0.5rem;
669
+ padding: 0.4rem 0.6rem;
670
+ font-size: 0.875rem;
671
+ color: var(--text-color);
672
+ text-decoration: none;
673
+ border-radius: 4px;
674
+ transition: background-color 0.15s, color 0.15s;
675
+ }
676
+
677
+ .menubar-dropdown-menu li a:hover {
678
+ background-color: var(--sidebar-link-active-bg);
679
+ color: var(--text-heading);
680
+ }
681
+
682
+ .sidebar {
683
+ top: var(--fixed-top-offset);
684
+ height: calc(100vh - var(--fixed-top-offset));
685
+ }
686
+
687
+ .toc-sidebar {
688
+ position: sticky;
689
+ top: calc(var(--sticky-top-offset) + 2rem);
690
+ width: 240px;
691
+ flex-shrink: 0;
692
+ margin: 1rem 0;
693
+ }
694
+
695
+ @media (max-width: 768px) {
696
+ .docmd-menubar {
697
+ padding: 0;
698
+ }
699
+
700
+ .menubar-brand-text {
701
+ display: none;
702
+ }
703
+
704
+ .menubar-link span,
705
+ .menubar-dropdown-toggle span:not(.menubar-chevron) {
706
+ display: none;
707
+ }
708
+ }
709
+
451
710
  .docmd-options-menu {
452
711
  display: flex;
453
712
  align-items: center;
@@ -540,17 +799,12 @@ body.sidebar-collapsed .main-content-wrapper {
540
799
  /* --- Version Dropdown (Flat UI) --- */
541
800
  .sidebar-version-wrapper {
542
801
  padding: 0.25rem .5em;
543
- border-bottom: 1px solid var(--border-color);
544
802
  }
545
803
 
546
804
  .sidebar-bottom-group.mt-auto {
547
805
  margin-top: auto;
548
806
  }
549
807
 
550
- .sidebar-bottom-group .sidebar-version-wrapper {
551
- border-bottom: none;
552
- }
553
-
554
808
  .docmd-version-dropdown {
555
809
  position: relative;
556
810
  width: 100%;
@@ -659,7 +913,7 @@ body.sidebar-collapsed .main-content-wrapper {
659
913
  }
660
914
 
661
915
  .content-area {
662
- padding: 0 2.5em 2em 2.5rem;
916
+ padding: 1rem 5%;
663
917
  max-width: 1200px;
664
918
  margin: 0 auto;
665
919
  width: 100%;
@@ -1251,15 +1505,6 @@ details[open]>.collapsible-summary .collapsible-arrow svg {
1251
1505
  background-color: rgba(59, 130, 246, 0.1)
1252
1506
  }
1253
1507
 
1254
- .toc-sidebar {
1255
- position: -webkit-sticky;
1256
- position: sticky;
1257
- top: 2rem;
1258
- width: 240px;
1259
- flex-shrink: 0;
1260
- margin: 1rem 0
1261
- }
1262
-
1263
1508
  .toc-title {
1264
1509
  font-size: .85em;
1265
1510
  display: contents;
@@ -1984,6 +2229,7 @@ hr {
1984
2229
  width: 100%;
1985
2230
  height: auto;
1986
2231
  position: relative;
2232
+ top: 0;
1987
2233
  border-right-width: medium;
1988
2234
  border-right-style: none;
1989
2235
  border-right-color: currentcolor;
@@ -2041,7 +2287,7 @@ hr {
2041
2287
  }
2042
2288
 
2043
2289
  .content-area {
2044
- padding: 15px
2290
+ padding: 1.5rem 15px 15px
2045
2291
  }
2046
2292
 
2047
2293
  .page-navigation {
@@ -18,7 +18,7 @@
18
18
  * --------------------------------------------------------------------
19
19
  */
20
20
 
21
- (function() {
21
+ (function () {
22
22
 
23
23
  // 1. EVENT DELEGATION
24
24
  document.addEventListener('click', (e) => {
@@ -32,7 +32,7 @@
32
32
  item.classList.toggle('expanded', !isExpanded);
33
33
  item.setAttribute('aria-expanded', !isExpanded);
34
34
  }
35
- if (navLabel.classList.contains('collapse-icon-wrapper')) return;
35
+ if (navLabel.classList.contains('collapse-icon-wrapper')) return;
36
36
  }
37
37
 
38
38
  // Toggles
@@ -54,14 +54,14 @@
54
54
  const navItems = Array.from(tabsContainer.querySelectorAll('.docmd-tabs-nav-item'));
55
55
  const tabPanes = Array.from(tabsContainer.querySelectorAll('.docmd-tab-pane'));
56
56
  const index = navItems.indexOf(tabItem);
57
-
57
+
58
58
  navItems.forEach(item => item.classList.remove('active'));
59
59
  tabPanes.forEach(pane => pane.classList.remove('active'));
60
-
60
+
61
61
  tabItem.classList.add('active');
62
62
  if (tabPanes[index]) tabPanes[index].classList.add('active');
63
63
  }
64
-
64
+
65
65
  // Version Dropdown Toggle
66
66
  const versionToggle = e.target.closest('.version-dropdown-toggle');
67
67
  if (versionToggle) {
@@ -75,28 +75,28 @@
75
75
  // Sticky Version Switching (Path Preservation)
76
76
  const versionLink = e.target.closest('.version-dropdown-item');
77
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
- }
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
98
97
  }
99
- // If sticky logic skipped (e.g. on 404 page or outside root), default <a> click handles it
98
+ }
99
+ // If sticky logic skipped (e.g. on 404 page or outside root), default <a> click handles it
100
100
  }
101
101
 
102
102
  // Close Dropdown if clicked outside
@@ -128,15 +128,15 @@
128
128
  function injectCopyButtons() {
129
129
  if (document.body.dataset.copyCodeEnabled !== 'true') return;
130
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>`;
131
-
131
+
132
132
  document.querySelectorAll('pre').forEach(preElement => {
133
- if (preElement.closest('.code-wrapper')) return;
133
+ if (preElement.closest('.code-wrapper')) return;
134
134
  const wrapper = document.createElement('div');
135
135
  wrapper.className = 'code-wrapper';
136
136
  wrapper.style.position = 'relative';
137
137
  preElement.parentNode.insertBefore(wrapper, preElement);
138
138
  wrapper.appendChild(preElement);
139
-
139
+
140
140
  const copyButton = document.createElement('button');
141
141
  copyButton.className = 'copy-code-button';
142
142
  copyButton.innerHTML = svg;
@@ -150,7 +150,7 @@
150
150
  const tocLinks = document.querySelectorAll('.toc-link');
151
151
  const headings = document.querySelectorAll('.main-content h2, .main-content h3, .main-content h4');
152
152
  const tocContainer = document.querySelector('.toc-list');
153
-
153
+
154
154
  if (tocLinks.length === 0 || headings.length === 0) return;
155
155
 
156
156
  scrollObserver = new IntersectionObserver((entries) => {
@@ -159,7 +159,7 @@
159
159
  tocLinks.forEach(link => link.classList.remove('active'));
160
160
  const id = entry.target.getAttribute('id');
161
161
  const activeLink = document.querySelector(`.toc-link[href="#${id}"]`);
162
-
162
+
163
163
  if (activeLink) {
164
164
  activeLink.classList.add('active');
165
165
  if (tocContainer) {
@@ -197,9 +197,9 @@
197
197
 
198
198
  // Intent-based Hover Prefetching
199
199
  document.addEventListener('mouseover', (e) => {
200
- const link = e.target.closest('.sidebar-nav a, .page-navigation a');
200
+ const link = e.target.closest('.sidebar-nav a, .page-navigation a, .page-footer a, .main-content a');
201
201
  if (!link || link.target === '_blank' || link.hasAttribute('download')) return;
202
-
202
+
203
203
  const url = new URL(link.href).href;
204
204
  if (new URL(url).origin !== location.origin) return;
205
205
  if (pageCache.has(url)) return;
@@ -207,10 +207,10 @@
207
207
  // Wait 65ms to ensure the user actually intends to click
208
208
  clearTimeout(prefetchTimer);
209
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)));
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
214
  }, 65);
215
215
  });
216
216
 
@@ -222,42 +222,42 @@
222
222
 
223
223
  if (e.target.closest('[data-spa-ignore]')) return;
224
224
 
225
- const link = e.target.closest('.sidebar-nav a, .page-navigation a');
225
+ const link = e.target.closest('.sidebar-nav a, .page-navigation a, .page-footer a, .main-content a');
226
226
  if (!link || link.target === '_blank' || link.hasAttribute('download')) return;
227
-
227
+
228
228
  const url = new URL(link.href);
229
229
  if (url.origin !== location.origin) return;
230
230
  if (url.pathname === window.location.pathname && url.hash) return;
231
-
231
+
232
232
  e.preventDefault();
233
233
  await navigateTo(url.href);
234
234
  });
235
235
 
236
236
  window.addEventListener('popstate', () => {
237
- if (window.location.pathname === currentPath) return;
237
+ if (window.location.pathname === currentPath) return;
238
238
  navigateTo(window.location.href, false);
239
239
  });
240
240
 
241
241
  async function navigateTo(url, pushHistory = true) {
242
242
  const layout = document.querySelector('.content-layout');
243
-
243
+
244
244
  try {
245
245
  if (layout) layout.style.minHeight = layout.getBoundingClientRect().height + 'px';
246
-
246
+
247
247
  let data;
248
248
  if (pageCache.has(url)) {
249
- data = await pageCache.get(url);
250
- data.html = await data.html;
249
+ data = await pageCache.get(url);
250
+ data.html = await data.html;
251
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));
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
256
  }
257
257
 
258
258
  const finalUrl = data.finalUrl;
259
259
  const html = data.html;
260
-
260
+
261
261
  const parser = new DOMParser();
262
262
  const doc = parser.parseFromString(html, 'text/html');
263
263
 
@@ -271,58 +271,62 @@
271
271
  const newAssets = Array.from(doc.head.querySelectorAll(assetSelectors));
272
272
 
273
273
  newAssets.forEach((newAsset, index) => {
274
- if (oldAssets[index]) {
275
- if (oldAssets[index].getAttribute('href') !== newAsset.getAttribute('href')) {
276
- oldAssets[index].setAttribute('href', newAsset.getAttribute('href'));
277
- }
278
- } else {
279
- document.head.appendChild(newAsset.cloneNode(true));
274
+ if (oldAssets[index]) {
275
+ if (oldAssets[index].getAttribute('href') !== newAsset.getAttribute('href')) {
276
+ oldAssets[index].setAttribute('href', newAsset.getAttribute('href'));
280
277
  }
278
+ } else {
279
+ document.head.appendChild(newAsset.cloneNode(true));
280
+ }
281
281
  });
282
282
 
283
283
  // Sync Sidebar State
284
284
  const oldLis = Array.from(document.querySelectorAll('.sidebar-nav li'));
285
285
  const newLis = Array.from(doc.querySelectorAll('.sidebar-nav li'));
286
-
286
+
287
287
  oldLis.forEach((oldLi, i) => {
288
- const newLi = newLis[i];
289
- if (newLi) {
290
- oldLi.classList.toggle('active', newLi.classList.contains('active'));
291
- oldLi.classList.toggle('active-parent', newLi.classList.contains('active-parent'));
292
-
293
- if (newLi.classList.contains('expanded')) {
294
- oldLi.classList.add('expanded');
295
- oldLi.setAttribute('aria-expanded', 'true');
296
- }
297
-
298
- const oldA = oldLi.querySelector('a');
299
- const newA = newLi.querySelector('a');
300
- if (oldA && newA) {
301
- oldA.setAttribute('href', newA.getAttribute('href'));
302
- oldA.classList.toggle('active', newA.classList.contains('active'));
303
- }
288
+ const newLi = newLis[i];
289
+ if (newLi) {
290
+ oldLi.classList.toggle('active', newLi.classList.contains('active'));
291
+ oldLi.classList.toggle('active-parent', newLi.classList.contains('active-parent'));
292
+
293
+ if (newLi.classList.contains('expanded')) {
294
+ oldLi.classList.add('expanded');
295
+ oldLi.setAttribute('aria-expanded', 'true');
296
+ }
297
+
298
+ const oldA = oldLi.querySelector('a');
299
+ const newA = newLi.querySelector('a');
300
+ if (oldA && newA) {
301
+ oldA.setAttribute('href', newA.getAttribute('href'));
302
+ oldA.classList.toggle('active', newA.classList.contains('active'));
304
303
  }
304
+ }
305
305
  });
306
306
 
307
- const selectorsToSwap =[
308
- '.content-layout',
309
- '.page-header .header-title',
310
- '.page-footer',
307
+ const selectorsToSwap = [
308
+ '.content-layout',
309
+ '.page-header .header-title',
310
+ '.page-footer',
311
311
  '.footer-complete',
312
312
  '.page-footer-actions'
313
313
  ];
314
314
 
315
315
  selectorsToSwap.forEach(selector => {
316
- const oldEl = document.querySelector(selector);
317
- const newEl = doc.querySelector(selector);
318
- if (oldEl && newEl) oldEl.innerHTML = newEl.innerHTML;
316
+ const oldEl = document.querySelector(selector);
317
+ const newEl = doc.querySelector(selector);
318
+ if (oldEl && newEl) oldEl.innerHTML = newEl.innerHTML;
319
319
  });
320
320
 
321
321
  const hash = new URL(finalUrl).hash;
322
322
  if (hash) {
323
+ try {
323
324
  document.querySelector(hash)?.scrollIntoView();
325
+ } catch (e) {
326
+ document.getElementById(hash.substring(1))?.scrollIntoView();
327
+ }
324
328
  } else {
325
- window.scrollTo(0, 0);
329
+ window.scrollTo(0, 0);
326
330
  }
327
331
 
328
332
  injectCopyButtons();
@@ -333,11 +337,11 @@
333
337
  document.dispatchEvent(new CustomEvent('docmd:page-mounted', { detail: { url: finalUrl } }));
334
338
 
335
339
  setTimeout(() => {
336
- const newLayout = document.querySelector('.content-layout');
337
- if (newLayout) newLayout.style.minHeight = '';
340
+ const newLayout = document.querySelector('.content-layout');
341
+ if (newLayout) newLayout.style.minHeight = '';
338
342
  }, 100);
339
343
 
340
- } catch(e) {
344
+ } catch (e) {
341
345
  window.location.assign(url);
342
346
  }
343
347
  }
@@ -346,21 +350,21 @@
346
350
  // 4. BOOTSTRAP
347
351
  document.addEventListener('DOMContentLoaded', () => {
348
352
  if (localStorage.getItem('docmd-sidebar-collapsed') === 'true') {
349
- document.body.classList.add('sidebar-collapsed');
353
+ document.body.classList.add('sidebar-collapsed');
350
354
  }
351
-
355
+
352
356
  document.querySelectorAll('.theme-toggle-button').forEach(btn => {
353
357
  btn.addEventListener('click', () => {
354
358
  const t = document.documentElement.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
355
359
  document.documentElement.setAttribute('data-theme', t);
356
360
  document.body.setAttribute('data-theme', t);
357
361
  localStorage.setItem('docmd-theme', t);
358
-
362
+
359
363
  const lightLink = document.getElementById('hljs-light');
360
364
  const darkLink = document.getElementById('hljs-dark');
361
365
  if (lightLink && darkLink) {
362
- lightLink.disabled = t === 'dark';
363
- darkLink.disabled = t === 'light';
366
+ lightLink.disabled = t === 'dark';
367
+ darkLink.disabled = t === 'light';
364
368
  }
365
369
  });
366
370
  });
@@ -368,16 +372,29 @@
368
372
  injectCopyButtons();
369
373
  initializeScrollSpy();
370
374
  initializeSPA();
371
-
375
+
372
376
  setTimeout(() => {
373
- const activeNav = document.querySelector('.sidebar-nav a.active');
374
- const sidebarNav = document.querySelector('.sidebar-nav');
375
- if (activeNav && sidebarNav) {
376
- sidebarNav.scrollTo({ top: activeNav.offsetTop - (sidebarNav.clientHeight / 2), behavior: 'instant' });
377
- }
378
- if (window.location.hash) {
379
- document.querySelector(window.location.hash)?.scrollIntoView();
377
+ // PWA Unregistration Safety Net:
378
+ // If the PWA plugin is removed from docmd.config.js, the <link rel="manifest"> disappears.
379
+ // We explicitly unregister all ghost service workers to safely kill the offline cache.
380
+ if ('serviceWorker' in navigator && !document.querySelector('link[rel="manifest"]')) {
381
+ navigator.serviceWorker.getRegistrations().then(registrations => {
382
+ registrations.forEach(reg => reg.unregister().catch(() => { }));
383
+ });
384
+ }
385
+
386
+ const activeNav = document.querySelector('.sidebar-nav a.active');
387
+ const sidebarNav = document.querySelector('.sidebar-nav');
388
+ if (activeNav && sidebarNav) {
389
+ sidebarNav.scrollTo({ top: activeNav.offsetTop - (sidebarNav.clientHeight / 2), behavior: 'instant' });
390
+ }
391
+ if (window.location.hash) {
392
+ try {
393
+ document.querySelector(window.location.hash)?.scrollIntoView();
394
+ } catch (e) {
395
+ document.getElementById(window.location.hash.substring(1))?.scrollIntoView();
380
396
  }
397
+ }
381
398
  }, 100);
382
399
  });
383
400
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@docmd/ui",
3
- "version": "0.5.0",
4
- "description": "Base UI templates and assets for docmd",
3
+ "version": "0.5.2",
4
+ "description": "Base UI templates and assets for docmd.",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "templates",
@@ -8,189 +8,225 @@
8
8
 
9
9
  <!DOCTYPE html>
10
10
  <html lang="en">
11
+
11
12
  <head>
12
13
  <meta charset="UTF-8">
13
14
  <meta name="generator" content="docmd v0.5.x">
14
15
  <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
- %>
34
- <script>
35
- var root = "<%= relativePathToRoot %>";
36
- if (root && !root.endsWith('/')) root += '/';
37
- if (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
-
45
- window.DOCMD_DEFAULT_MODE = "<%= defaultMode %>";
46
- window.DOCMD_VERSION_ROOT = "<%- versionRoot %>";
47
- </script>
48
- <title><%= pageTitle %></title>
49
- <%- faviconLinkHtml || '' %>
50
- <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css?v=<%= buildHash %>">
51
- <% if (config.theme?.codeHighlight !== false) {
52
- const isDarkDefault = defaultMode === 'dark';
53
- %>
54
- <link rel="stylesheet"
55
- href="<%= relativePathToRoot %>assets/css/docmd-highlight-light.css?v=<%= buildHash %>"
56
- id="hljs-light"
57
- <%= isDarkDefault ? 'disabled' : '' %>>
58
-
59
- <link rel="stylesheet"
60
- href="<%= relativePathToRoot %>assets/css/docmd-highlight-dark.css?v=<%= buildHash %>"
61
- id="hljs-dark"
62
- <%= isDarkDefault ? '' : 'disabled' %>>
63
- <% } %>
64
- <%- pluginHeadScriptsHtml || '' %>
65
- <% (customCssFiles || []).forEach(cssFile => { %>
66
- <link rel="stylesheet" href="<%= relativePathToRoot %><%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>?v=<%= buildHash %>">
67
- <% }); %>
68
- <%- themeInitScript %>
16
+ <% let versionRoot='/' ; let siteRoot=relativePathToRoot; if (config.versions?.current && config._activeVersion?.id)
17
+ { const isSubVersion=config.versions.current !==config._activeVersion.id; versionRoot=isSubVersion ? '/' +
18
+ config._activeVersion.id + '/' : '/' ; if (isSubVersion) { siteRoot=(relativePathToRoot==='./' ? '' :
19
+ relativePathToRoot) + '../' ; } } if (locals.isOfflineMode) { versionRoot='' ; } %>
20
+ <script>
21
+ var root = "<%= relativePathToRoot %>";
22
+ if (root && !root.endsWith('/')) root += '/';
23
+ if (root === '') root = './';
24
+ window.DOCMD_ROOT = root;
25
+
26
+ var siteRoot = "<%= siteRoot %>";
27
+ if (siteRoot && !siteRoot.endsWith('/')) siteRoot += '/';
28
+ if (siteRoot === '') siteRoot = './';
29
+ window.DOCMD_SITE_ROOT = siteRoot;
30
+
31
+ window.DOCMD_DEFAULT_MODE = "<%= defaultMode %>";
32
+ window.DOCMD_VERSION_ROOT = "<%- versionRoot %>";
33
+ </script>
34
+ <title>
35
+ <%= pageTitle %>
36
+ </title>
37
+ <%- faviconLinkHtml || '' %>
38
+ <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css?v=<%= buildHash %>">
39
+ <% if (config.theme?.codeHighlight !==false) { const isDarkDefault=defaultMode==='dark' ; %>
40
+ <link rel="stylesheet"
41
+ href="<%= relativePathToRoot %>assets/css/docmd-highlight-light.css?v=<%= buildHash %>"
42
+ id="hljs-light" <%=isDarkDefault ? 'disabled' : '' %>>
43
+
44
+ <link rel="stylesheet"
45
+ href="<%= relativePathToRoot %>assets/css/docmd-highlight-dark.css?v=<%= buildHash %>"
46
+ id="hljs-dark" <%=isDarkDefault ? '' : 'disabled' %>>
47
+ <% } %>
48
+ <%- pluginHeadScriptsHtml || '' %>
49
+ <% (customCssFiles || []).forEach(cssFile=> { %>
50
+ <link rel="stylesheet"
51
+ href="<%= relativePathToRoot %><%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>?v=<%= buildHash %>">
52
+ <% }); %>
53
+ <%- themeInitScript %>
69
54
  </head>
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 %>">
74
-
55
+
56
+ <body
57
+ class="<%= sidebarConfig?.enabled === false ? 'no-sidebar' : (sidebarConfig?.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible') %><%= (locals.menubarConfig && menubarConfig?.enabled !== false) ? ' has-menubar has-menubar-' + (menubarConfig.position || 'top') : '' %><%= headerConfig?.enabled === false ? ' no-header' : '' %>"
58
+ data-default-collapsed="<%= sidebarConfig?.defaultCollapsed %>"
59
+ data-copy-code-enabled="<%= config.copyCode === true %>" data-spa-enabled="<%= config.layout?.spa !== false %>">
60
+
75
61
  <a href="#main-content" class="skip-link">Skip to main content</a>
76
62
 
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>
91
- </div>
92
-
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>
63
+ <% if (locals.menubarConfig && menubarConfig?.enabled !==false && menubarConfig.position !=='header' ) { %>
64
+ <%- include('partials/menubar', { menubarConfig, relativePathToRoot, renderIcon, optionsMenu: locals.optionsMenu
65
+ }) %>
66
+ <% } %>
106
67
 
107
- <%- navigationHtml %>
68
+ <% if (sidebarConfig?.enabled !==false) { %>
69
+ <aside class="sidebar">
70
+ <div class="sidebar-header">
71
+ <% if (logo && logo.light && logo.dark) { %>
72
+ <a href="<%= logo.href || relativePathToRoot %>" class="logo-link">
73
+ <img src="<%= relativePathToRoot %><%- logo.light.startsWith('/') ? logo.light.substring(1) : logo.light %>"
74
+ alt="<%= logo.alt || siteTitle %>" class="logo-light" <% if (logo.height) {
75
+ %>style="height: <%= logo.height %>;"<% } %>>
76
+ <img src="<%= relativePathToRoot %><%- logo.dark.startsWith('/') ? logo.dark.substring(1) : logo.dark %>"
77
+ alt="<%= logo.alt || siteTitle %>" class="logo-dark" <% if (logo.height)
78
+ { %>style="height: <%= logo.height %>;"<% } %>>
79
+ </a>
80
+ <% } else { %>
81
+ <h1><a href="<%= relativePathToRoot %>index.html">
82
+ <%= siteTitle %>
83
+ </a></h1>
84
+ <% } %>
85
+ <span class="mobile-view sidebar-menu-button float-right">
86
+ <%- renderIcon("menu") %>
87
+ </span>
88
+ </div>
108
89
 
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
- <% } %>
90
+ <div class="sidebar-top-group">
91
+ <% if (locals.optionsMenu && optionsMenu.position==='sidebar-top' ) { %>
92
+ <div class="sidebar-options-wrapper">
93
+ <%- include('partials/options-menu', { optionsMenu }) %>
94
+ </div>
95
+ <% } %>
115
96
 
116
- <% if (locals.optionsMenu && optionsMenu.position === 'sidebar-bottom') { %>
117
- <div class="sidebar-options-wrapper">
118
- <%- include('partials/options-menu', { optionsMenu }) %>
119
- </div>
120
- <% } %>
121
- </div>
122
- </aside>
123
- <% } %>
124
-
125
- <div class="main-content-wrapper">
126
- <% if (headerConfig?.enabled !== false) { %>
127
- <header class="page-header">
128
- <div class="header-left">
129
- <% if (sidebarConfig?.collapsible) { %>
130
- <button id="sidebar-toggle-button" class="sidebar-toggle-button" aria-label="Toggle Sidebar">
131
- <%- renderIcon('panel-left-close') %>
132
- </button>
133
- <% } %>
134
- <span class="header-title"><%= pageTitle %></span>
135
- </div>
136
-
137
- <div class="header-right">
138
- <% if (optionsMenu?.position === 'header') { %>
139
- <%- include('partials/options-menu', { optionsMenu }) %>
140
- <% } %>
141
- </div>
142
- </header>
143
- <% } %>
144
-
145
- <main class="content-area" id="main-content">
146
- <div class="content-layout">
147
- <div class="main-content">
148
- <% if (headerConfig?.enabled === false) { %>
149
- <h1><%= pageTitle %></h1>
150
- <% } %>
151
-
152
- <%- content %>
153
-
154
- <% if (config.pageNavigation && (prevPage || nextPage)) { %>
155
- <div class="page-navigation">
156
- <% if (prevPage) { %>
157
- <a href="<%= prevPage.url %>" class="prev-page">
158
- <%- renderIcon('arrow-left', { class: 'page-nav-icon' }) %>
159
- <span><small>Previous</small><strong><%= prevPage.title %></strong></span>
160
- </a>
161
- <% } else { %><div class="prev-page-placeholder"></div><% } %>
162
- <% if (nextPage) { %>
163
- <a href="<%= nextPage.url %>" class="next-page">
164
- <span><small>Next</small><strong><%= nextPage.title %></strong></span>
165
- <%- renderIcon('arrow-right', { class: 'page-nav-icon' }) %>
166
- </a>
167
- <% } else { %><div class="next-page-placeholder"></div><% } %>
168
- </div>
97
+ <% if (config.versions && config.versions.position==='sidebar-top' ) { %>
98
+ <div class="sidebar-version-wrapper">
99
+ <%- include('partials/version-dropdown', { versions: config.versions,
100
+ activeVersion: config._activeVersion, relativePathToRoot }) %>
101
+ </div>
102
+ <% } %>
103
+ </div>
104
+
105
+ <%- navigationHtml %>
106
+
107
+ <div class="sidebar-bottom-group mt-auto">
108
+ <% if (config.versions && config.versions.position==='sidebar-bottom' ) { %>
109
+ <div class="sidebar-version-wrapper">
110
+ <%- include('partials/version-dropdown', { versions: config.versions,
111
+ activeVersion: config._activeVersion, relativePathToRoot }) %>
112
+ </div>
113
+ <% } %>
114
+
115
+ <% if (locals.optionsMenu && optionsMenu.position==='sidebar-bottom' ) { %>
116
+ <div class="sidebar-options-wrapper">
117
+ <%- include('partials/options-menu', { optionsMenu }) %>
118
+ </div>
119
+ <% } %>
120
+ </div>
121
+ </aside>
169
122
  <% } %>
170
- </div>
171
-
172
- <div class="toc-sidebar">
173
- <%- include('toc', { content, headings, navigationHtml, isActivePage }) %>
174
- </div>
175
- </div>
176
-
177
- <div class="page-footer-actions">
178
- <% if (locals.editUrl) { %>
179
- <a href="<%= editUrl %>" target="_blank" rel="noopener noreferrer" class="edit-link">
180
- <%- renderIcon('pencil') %> <%= editLinkText %>
181
- </a>
182
- <% } %>
183
- </div>
184
- </main>
185
-
186
- <%- include('partials/footer', { footerConfig, config, relativePathToRoot, logo, siteTitle, footerHtml }) %>
187
- </div>
188
-
189
- <script src="<%= relativePathToRoot %>assets/js/docmd-main.js?v=<%= buildHash %>"></script>
190
- <%- pluginBodyScriptsHtml || '' %>
191
- <% (customJsFiles || []).forEach(jsFile => {
192
- if (jsFile && jsFile.trim() !== '') { %>
193
- <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>?v=<%= buildHash %>"></script>
194
- <% } }); %>
123
+
124
+ <div class="main-content-wrapper">
125
+ <% if (locals.menubarConfig && menubarConfig?.enabled !==false &&
126
+ menubarConfig.position==='header' ) { %>
127
+ <%- include('partials/menubar', { menubarConfig, relativePathToRoot, renderIcon,
128
+ optionsMenu: locals.optionsMenu }) %>
129
+ <% } %>
130
+ <% if (headerConfig?.enabled !==false) { %>
131
+ <header class="page-header">
132
+ <div class="header-left">
133
+ <% if (sidebarConfig?.collapsible) { %>
134
+ <button id="sidebar-toggle-button" class="sidebar-toggle-button"
135
+ aria-label="Toggle Sidebar">
136
+ <%- renderIcon('panel-left-close') %>
137
+ </button>
138
+ <% } %>
139
+ <span class="header-title">
140
+ <%= pageTitle %>
141
+ </span>
142
+ </div>
143
+
144
+ <div class="header-right">
145
+ <% if (optionsMenu?.position==='header' ) { %>
146
+ <%- include('partials/options-menu', { optionsMenu }) %>
147
+ <% } %>
148
+ </div>
149
+ </header>
150
+ <% } %>
151
+
152
+ <main class="content-area" id="main-content">
153
+ <div class="content-layout">
154
+ <div class="main-content">
155
+ <% if (headerConfig?.enabled===false) { %>
156
+ <h1>
157
+ <%= pageTitle %>
158
+ </h1>
159
+ <% } %>
160
+
161
+ <%- content %>
162
+
163
+ <% if (config.pageNavigation && (prevPage ||
164
+ nextPage)) { %>
165
+ <div class="page-navigation">
166
+ <% if (prevPage) { %>
167
+ <a href="<%= prevPage.url %>"
168
+ class="prev-page">
169
+ <%- renderIcon('arrow-left', {
170
+ class: 'page-nav-icon' }) %>
171
+ <span><small>Previous</small><strong>
172
+ <%= prevPage.title
173
+ %>
174
+ </strong></span>
175
+ </a>
176
+ <% } else { %>
177
+ <div
178
+ class="prev-page-placeholder">
179
+ </div>
180
+ <% } %>
181
+ <% if (nextPage) { %>
182
+ <a href="<%= nextPage.url %>"
183
+ class="next-page">
184
+ <span><small>Next</small><strong>
185
+ <%= nextPage.title
186
+ %>
187
+ </strong></span>
188
+ <%- renderIcon('arrow-right',
189
+ {
190
+ class: 'page-nav-icon'
191
+ }) %>
192
+ </a>
193
+ <% } else { %>
194
+ <div
195
+ class="next-page-placeholder">
196
+ </div>
197
+ <% } %>
198
+ </div>
199
+ <% } %>
200
+ </div>
201
+
202
+ <div class="toc-sidebar">
203
+ <%- include('toc', { content, headings, navigationHtml,
204
+ isActivePage }) %>
205
+ </div>
206
+ </div>
207
+
208
+ <div class="page-footer-actions">
209
+ <% if (locals.editUrl) { %>
210
+ <a href="<%= editUrl %>" target="_blank"
211
+ rel="noopener noreferrer" class="edit-link">
212
+ <%- renderIcon('pencil') %>
213
+ <%= editLinkText %>
214
+ </a>
215
+ <% } %>
216
+ </div>
217
+ </main>
218
+
219
+ <%- include('partials/footer', { footerConfig, config,
220
+ relativePathToRoot, logo, siteTitle, footerHtml }) %>
221
+ </div>
222
+
223
+ <script src="<%= relativePathToRoot %>assets/js/docmd-main.js?v=<%= buildHash %>"></script>
224
+ <%- pluginBodyScriptsHtml || '' %>
225
+ <% (customJsFiles || []).forEach(jsFile=> {
226
+ if (jsFile && jsFile.trim() !== '') { %>
227
+ <script
228
+ src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>?v=<%= buildHash %>"></script>
229
+ <% } }); %>
195
230
  </body>
231
+
196
232
  </html>
@@ -0,0 +1,129 @@
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
+ <% function menubarHref(item) { if (!item.url) return '#' ; if (item.external || item.url.startsWith('http')) return
9
+ item.url; let clean=item.url.replace(/^\//, '' ); return relativePathToRoot + clean; } function menubarTarget(item)
10
+ { return (item.external || (item.url && item.url.startsWith('http'))) ? ' target="_blank" rel="noopener noreferrer"'
11
+ : '' ; } %>
12
+
13
+ <div class="docmd-menubar">
14
+ <div class="menubar-inner">
15
+ <div class="menubar-left">
16
+ <% if (menubarConfig.left && menubarConfig.left.length> 0) { %>
17
+ <% menubarConfig.left.forEach(item=> { %>
18
+ <% if (item.type==='title' ) { %>
19
+ <a href="<%= menubarHref(item) %>" class="menubar-brand" <%- menubarTarget(item) %>>
20
+ <% if (item.icon) { %>
21
+ <%- renderIcon(item.icon, { class: 'menubar-icon' }) %>
22
+ <% } %>
23
+ <% if (item.text) { %>
24
+ <span class="menubar-brand-text">
25
+ <%= item.text %>
26
+ </span>
27
+ <% } %>
28
+ </a>
29
+ <% } else if (item.type==='dropdown' ) { %>
30
+ <div class="menubar-dropdown">
31
+ <button class="menubar-dropdown-toggle" type="button" aria-expanded="false">
32
+ <% if (item.icon) { %>
33
+ <%- renderIcon(item.icon, { class: 'menubar-icon' }) %>
34
+ <% } %>
35
+ <span>
36
+ <%= item.text %>
37
+ </span>
38
+ <%- renderIcon('chevron-down', { class: 'menubar-chevron' }) %>
39
+ </button>
40
+ <ul class="menubar-dropdown-menu">
41
+ <% if (item.items && item.items.length> 0) { %>
42
+ <% item.items.forEach(sub=> { %>
43
+ <li>
44
+ <a href="<%= menubarHref(sub) %>" <%- menubarTarget(sub) %>>
45
+ <% if (sub.icon) { %>
46
+ <%- renderIcon(sub.icon, { class: 'menubar-icon' }) %>
47
+ <% } %>
48
+ <%= sub.text %>
49
+ <% if (sub.external || (sub.url && sub.url.startsWith('http'))) { %>
50
+ <%- renderIcon('external-link', { class: 'menubar-ext-icon' }) %>
51
+ <% } %>
52
+ </a>
53
+ </li>
54
+ <% }) %>
55
+ <% } %>
56
+ </ul>
57
+ </div>
58
+ <% } else { %>
59
+ <a href="<%= menubarHref(item) %>" class="menubar-link" <%- menubarTarget(item) %>>
60
+ <% if (item.icon) { %>
61
+ <%- renderIcon(item.icon, { class: 'menubar-icon' }) %>
62
+ <% } %>
63
+ <span>
64
+ <%= item.text %>
65
+ </span>
66
+ <% if (item.external || (item.url && item.url.startsWith('http'))) { %>
67
+ <%- renderIcon('external-link', { class: 'menubar-ext-icon' }) %>
68
+ <% } %>
69
+ </a>
70
+ <% } %>
71
+ <% }) %>
72
+ </div>
73
+ <% } %>
74
+ </div>
75
+ <div class="menubar-right">
76
+ <% if (menubarConfig.right && menubarConfig.right.length> 0) { %>
77
+ <% menubarConfig.right.forEach(item=> { %>
78
+ <% if (item.type==='dropdown' ) { %>
79
+ <div class="menubar-dropdown">
80
+ <button class="menubar-dropdown-toggle" type="button" aria-expanded="false">
81
+ <% if (item.icon) { %>
82
+ <%- renderIcon(item.icon, { class: 'menubar-icon' }) %>
83
+ <% } %>
84
+ <span>
85
+ <%= item.text %>
86
+ </span>
87
+ <%- renderIcon('chevron-down', { class: 'menubar-chevron' }) %>
88
+ </button>
89
+ <ul class="menubar-dropdown-menu menubar-dropdown-right">
90
+ <% if (item.items && item.items.length> 0) { %>
91
+ <% item.items.forEach(sub=> { %>
92
+ <li>
93
+ <a href="<%= menubarHref(sub) %>" <%- menubarTarget(sub) %>>
94
+ <% if (sub.icon) { %>
95
+ <%- renderIcon(sub.icon, { class: 'menubar-icon' }) %>
96
+ <% } %>
97
+ <%= sub.text %>
98
+ <% if (sub.external || (sub.url && sub.url.startsWith('http'))) { %>
99
+ <%- renderIcon('external-link', { class: 'menubar-ext-icon' }) %>
100
+ <% } %>
101
+ </a>
102
+ </li>
103
+ <% }) %>
104
+ <% } %>
105
+ </ul>
106
+ </div>
107
+ <% } else { %>
108
+ <a href="<%= menubarHref(item) %>" class="menubar-link" <%- menubarTarget(item) %>>
109
+ <% if (item.icon) { %>
110
+ <%- renderIcon(item.icon, { class: 'menubar-icon' }) %>
111
+ <% } %>
112
+ <span>
113
+ <%= item.text %>
114
+ </span>
115
+ <% if (item.external || (item.url && item.url.startsWith('http'))) { %>
116
+ <%- renderIcon('external-link', { class: 'menubar-ext-icon' }) %>
117
+ <% } %>
118
+ </a>
119
+ <% } %>
120
+ <% }) %>
121
+ <% } %>
122
+ <% if (optionsMenu && optionsMenu.position==='menubar' ) { %>
123
+ <div class="menubar-options">
124
+ <%- include('options-menu', { optionsMenu }) %>
125
+ </div>
126
+ <% } %>
127
+ </div>
128
+ </div>
129
+ </div>