@docmd/ui 0.5.0 → 0.5.1
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 +2 -1
- package/assets/css/docmd-main.css +0 -5
- package/assets/js/docmd-main.js +114 -97
- package/package.json +2 -2
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.
|
|
@@ -540,17 +540,12 @@ body.sidebar-collapsed .main-content-wrapper {
|
|
|
540
540
|
/* --- Version Dropdown (Flat UI) --- */
|
|
541
541
|
.sidebar-version-wrapper {
|
|
542
542
|
padding: 0.25rem .5em;
|
|
543
|
-
border-bottom: 1px solid var(--border-color);
|
|
544
543
|
}
|
|
545
544
|
|
|
546
545
|
.sidebar-bottom-group.mt-auto {
|
|
547
546
|
margin-top: auto;
|
|
548
547
|
}
|
|
549
548
|
|
|
550
|
-
.sidebar-bottom-group .sidebar-version-wrapper {
|
|
551
|
-
border-bottom: none;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
549
|
.docmd-version-dropdown {
|
|
555
550
|
position: relative;
|
|
556
551
|
width: 100%;
|
package/assets/js/docmd-main.js
CHANGED
|
@@ -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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
250
|
-
|
|
249
|
+
data = await pageCache.get(url);
|
|
250
|
+
data.html = await data.html;
|
|
251
251
|
} else {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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