@docmd/ui 0.4.9 → 0.4.11
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 +21 -7
- package/assets/js/docmd-main.js +81 -43
- package/package.json +1 -1
- package/templates/navigation.ejs +1 -1
|
@@ -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 {
|
|
@@ -203,9 +204,10 @@ a:any-link {
|
|
|
203
204
|
margin: 0
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
.sidebar nav li a,
|
|
207
|
+
.sidebar nav li a,
|
|
208
|
+
.sidebar nav li .nav-label {
|
|
207
209
|
display: block;
|
|
208
|
-
padding: .5em;
|
|
210
|
+
padding: .4em .5em;
|
|
209
211
|
margin: .15em 0;
|
|
210
212
|
text-decoration: none;
|
|
211
213
|
color: var(--sidebar-text);
|
|
@@ -214,6 +216,10 @@ a:any-link {
|
|
|
214
216
|
font-size: .9em;
|
|
215
217
|
}
|
|
216
218
|
|
|
219
|
+
.sidebar nav li a {
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
}
|
|
222
|
+
|
|
217
223
|
.sidebar nav li>a.active {
|
|
218
224
|
color: var(--link-color);
|
|
219
225
|
font-weight: 600;
|
|
@@ -237,11 +243,18 @@ a:any-link {
|
|
|
237
243
|
opacity: 1;
|
|
238
244
|
}
|
|
239
245
|
|
|
246
|
+
.sidebar nav li .nav-label {
|
|
247
|
+
cursor: default;
|
|
248
|
+
padding: 0.25em .5em;
|
|
249
|
+
color: var(--text-color);
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
}
|
|
252
|
+
|
|
240
253
|
.sidebar nav .submenu {
|
|
241
254
|
display: none;
|
|
242
255
|
padding-left: .25em;
|
|
243
256
|
border-left: 1px solid var(--link-color);
|
|
244
|
-
margin-left: .
|
|
257
|
+
margin-left: .85em;
|
|
245
258
|
}
|
|
246
259
|
|
|
247
260
|
.sidebar nav li[aria-expanded="true"]>.submenu,
|
|
@@ -1604,7 +1617,7 @@ hr {
|
|
|
1604
1617
|
|
|
1605
1618
|
.footer-complete-top {
|
|
1606
1619
|
display: flex;
|
|
1607
|
-
justify-content:
|
|
1620
|
+
justify-content: center;
|
|
1608
1621
|
flex-wrap: wrap;
|
|
1609
1622
|
gap: 2rem;
|
|
1610
1623
|
max-width: 1200px;
|
|
@@ -1625,7 +1638,7 @@ hr {
|
|
|
1625
1638
|
width: fit-content;
|
|
1626
1639
|
}
|
|
1627
1640
|
|
|
1628
|
-
.footer-brand .logo-link img{
|
|
1641
|
+
.footer-brand .logo-link img {
|
|
1629
1642
|
max-height: 30px;
|
|
1630
1643
|
width: auto;
|
|
1631
1644
|
}
|
|
@@ -1725,7 +1738,8 @@ hr {
|
|
|
1725
1738
|
color: #fb3a3a
|
|
1726
1739
|
}
|
|
1727
1740
|
|
|
1728
|
-
.branding-footer a,
|
|
1741
|
+
.branding-footer a,
|
|
1742
|
+
.page-footer a {
|
|
1729
1743
|
color: var(--link-color);
|
|
1730
1744
|
text-decoration: none
|
|
1731
1745
|
}
|
package/assets/js/docmd-main.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* [docmd-source] - Please do not remove this header.
|
|
12
12
|
* --------------------------------------------------------------------
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* --------------------------------------------------------------------
|
|
16
|
+
* docmd : Client-Side Application Logic (SPA Router & UI)
|
|
17
|
+
* --------------------------------------------------------------------
|
|
18
|
+
*/
|
|
14
19
|
|
|
15
20
|
(function() {
|
|
16
21
|
// =========================================================================
|
|
@@ -138,7 +143,7 @@
|
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
// =========================================================================
|
|
146
|
+
// =========================================================================
|
|
142
147
|
// 3. TARGETED SPA ROUTER
|
|
143
148
|
// =========================================================================
|
|
144
149
|
function initializeSPA() {
|
|
@@ -146,9 +151,32 @@
|
|
|
146
151
|
if (document.body.dataset.spaEnabled !== 'true') return;
|
|
147
152
|
|
|
148
153
|
let currentPath = window.location.pathname;
|
|
154
|
+
const pageCache = new Map();
|
|
155
|
+
let prefetchTimer = null;
|
|
149
156
|
|
|
150
|
-
|
|
157
|
+
// Intent-based Hover Prefetching
|
|
158
|
+
document.addEventListener('mouseover', (e) => {
|
|
159
|
+
const link = e.target.closest('.sidebar-nav a, .page-navigation a');
|
|
160
|
+
if (!link || link.target === '_blank' || link.hasAttribute('download')) return;
|
|
161
|
+
|
|
162
|
+
const url = new URL(link.href).href;
|
|
163
|
+
if (new URL(url).origin !== location.origin) return;
|
|
164
|
+
if (pageCache.has(url)) return;
|
|
165
|
+
|
|
166
|
+
// Wait 65ms to ensure the user actually intends to click
|
|
167
|
+
clearTimeout(prefetchTimer);
|
|
168
|
+
prefetchTimer = setTimeout(() => {
|
|
169
|
+
pageCache.set(url, fetch(url).then(res => {
|
|
170
|
+
if (!res.ok) throw new Error('Prefetch failed');
|
|
171
|
+
return { html: res.text(), finalUrl: res.url };
|
|
172
|
+
}).catch(() => pageCache.delete(url)));
|
|
173
|
+
}, 65);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Cancel prefetch if the mouse leaves before the 65ms "intent" delay
|
|
177
|
+
document.addEventListener('mouseout', () => clearTimeout(prefetchTimer));
|
|
151
178
|
|
|
179
|
+
document.addEventListener('click', async (e) => {
|
|
152
180
|
if (e.target.closest('.collapse-icon-wrapper')) return;
|
|
153
181
|
|
|
154
182
|
const link = e.target.closest('.sidebar-nav a, .page-navigation a');
|
|
@@ -162,40 +190,45 @@
|
|
|
162
190
|
await navigateTo(url.href);
|
|
163
191
|
});
|
|
164
192
|
|
|
165
|
-
// Handle Back/Forward browser buttons & TOC Hash clicks
|
|
166
193
|
window.addEventListener('popstate', () => {
|
|
167
|
-
|
|
168
|
-
if (window.location.pathname === currentPath) return;
|
|
169
|
-
|
|
194
|
+
if (window.location.pathname === currentPath) return;
|
|
170
195
|
navigateTo(window.location.href, false);
|
|
171
196
|
});
|
|
172
197
|
|
|
173
198
|
async function navigateTo(url, pushHistory = true) {
|
|
174
|
-
const
|
|
199
|
+
const layout = document.querySelector('.content-layout');
|
|
175
200
|
|
|
176
201
|
try {
|
|
177
|
-
if (
|
|
202
|
+
if (layout) layout.style.minHeight = layout.getBoundingClientRect().height + 'px';
|
|
203
|
+
|
|
204
|
+
let data;
|
|
205
|
+
if (pageCache.has(url)) {
|
|
206
|
+
data = await pageCache.get(url);
|
|
207
|
+
data.html = await data.html;
|
|
208
|
+
} else {
|
|
209
|
+
const res = await fetch(url);
|
|
210
|
+
if (!res.ok) throw new Error('Fetch failed');
|
|
211
|
+
data = { html: await res.text(), finalUrl: res.url };
|
|
212
|
+
pageCache.set(url, Promise.resolve(data));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const finalUrl = data.finalUrl;
|
|
216
|
+
const html = data.html;
|
|
178
217
|
|
|
179
|
-
const res = await fetch(url);
|
|
180
|
-
if (!res.ok) throw new Error('Fetch failed');
|
|
181
|
-
const finalUrl = res.url;
|
|
182
|
-
const html = await res.text();
|
|
183
218
|
const parser = new DOMParser();
|
|
184
219
|
const doc = parser.parseFromString(html, 'text/html');
|
|
185
220
|
|
|
186
|
-
// 1. UPDATE URL FIRST
|
|
187
221
|
if (pushHistory) history.pushState({}, '', finalUrl);
|
|
188
222
|
currentPath = new URL(finalUrl).pathname;
|
|
189
223
|
document.title = doc.title;
|
|
190
224
|
|
|
191
|
-
//
|
|
225
|
+
// Sync Assets
|
|
192
226
|
const assetSelectors = 'link[rel="stylesheet"], link[rel="icon"], link[rel="shortcut icon"]';
|
|
193
227
|
const oldAssets = Array.from(document.head.querySelectorAll(assetSelectors));
|
|
194
228
|
const newAssets = Array.from(doc.head.querySelectorAll(assetSelectors));
|
|
195
229
|
|
|
196
230
|
newAssets.forEach((newAsset, index) => {
|
|
197
231
|
if (oldAssets[index]) {
|
|
198
|
-
// Only update if the relative path actually changed
|
|
199
232
|
if (oldAssets[index].getAttribute('href') !== newAsset.getAttribute('href')) {
|
|
200
233
|
oldAssets[index].setAttribute('href', newAsset.getAttribute('href'));
|
|
201
234
|
}
|
|
@@ -204,16 +237,35 @@
|
|
|
204
237
|
}
|
|
205
238
|
});
|
|
206
239
|
|
|
207
|
-
//
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
openMenus.add(el.textContent.trim());
|
|
211
|
-
});
|
|
240
|
+
// Sync Sidebar State
|
|
241
|
+
const oldLis = Array.from(document.querySelectorAll('.sidebar-nav li'));
|
|
242
|
+
const newLis = Array.from(doc.querySelectorAll('.sidebar-nav li'));
|
|
212
243
|
|
|
213
|
-
|
|
244
|
+
oldLis.forEach((oldLi, i) => {
|
|
245
|
+
const newLi = newLis[i];
|
|
246
|
+
if (newLi) {
|
|
247
|
+
oldLi.classList.toggle('active', newLi.classList.contains('active'));
|
|
248
|
+
oldLi.classList.toggle('active-parent', newLi.classList.contains('active-parent'));
|
|
249
|
+
|
|
250
|
+
if (newLi.classList.contains('expanded')) {
|
|
251
|
+
oldLi.classList.add('expanded');
|
|
252
|
+
oldLi.setAttribute('aria-expanded', 'true');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const oldA = oldLi.querySelector('a');
|
|
256
|
+
const newA = newLi.querySelector('a');
|
|
257
|
+
if (oldA && newA) {
|
|
258
|
+
oldA.setAttribute('href', newA.getAttribute('href'));
|
|
259
|
+
oldA.classList.toggle('active', newA.classList.contains('active'));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
214
264
|
const selectorsToSwap =[
|
|
215
|
-
'.
|
|
216
|
-
'.page-header .header-title',
|
|
265
|
+
'.content-layout',
|
|
266
|
+
'.page-header .header-title',
|
|
267
|
+
'.page-footer',
|
|
268
|
+
'.footer-complete',
|
|
217
269
|
'.page-footer-actions'
|
|
218
270
|
];
|
|
219
271
|
|
|
@@ -223,33 +275,25 @@
|
|
|
223
275
|
if (oldEl && newEl) oldEl.innerHTML = newEl.innerHTML;
|
|
224
276
|
});
|
|
225
277
|
|
|
226
|
-
// 5. RESTORE SIDEBAR STATE
|
|
227
|
-
document.querySelectorAll('.sidebar-nav li.collapsible').forEach(li => {
|
|
228
|
-
const title = li.querySelector('.nav-item-title')?.textContent.trim();
|
|
229
|
-
if (openMenus.has(title)) {
|
|
230
|
-
li.classList.add('expanded');
|
|
231
|
-
li.setAttribute('aria-expanded', 'true');
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// 6. SCROLL & RE-INIT
|
|
236
278
|
const hash = new URL(finalUrl).hash;
|
|
237
279
|
if (hash) {
|
|
238
280
|
document.querySelector(hash)?.scrollIntoView();
|
|
239
281
|
} else {
|
|
240
|
-
if (mainContentWrapper) mainContentWrapper.scrollTo(0, 0);
|
|
241
282
|
window.scrollTo(0, 0);
|
|
242
283
|
}
|
|
243
284
|
|
|
244
|
-
if (mainContentWrapper) mainContentWrapper.style.opacity = '1';
|
|
245
285
|
injectCopyButtons();
|
|
246
286
|
initializeScrollSpy();
|
|
247
|
-
|
|
248
287
|
const newMainContent = document.querySelector('.main-content');
|
|
249
288
|
if (newMainContent) executeScripts(newMainContent);
|
|
250
289
|
|
|
251
290
|
document.dispatchEvent(new CustomEvent('docmd:page-mounted', { detail: { url: finalUrl } }));
|
|
252
291
|
|
|
292
|
+
setTimeout(() => {
|
|
293
|
+
const newLayout = document.querySelector('.content-layout');
|
|
294
|
+
if (newLayout) newLayout.style.minHeight = '';
|
|
295
|
+
}, 100);
|
|
296
|
+
|
|
253
297
|
} catch(e) {
|
|
254
298
|
window.location.assign(url);
|
|
255
299
|
}
|
|
@@ -271,7 +315,6 @@
|
|
|
271
315
|
document.body.setAttribute('data-theme', t);
|
|
272
316
|
localStorage.setItem('docmd-theme', t);
|
|
273
317
|
|
|
274
|
-
// Highlight.js CSS swap
|
|
275
318
|
const lightLink = document.getElementById('hljs-light');
|
|
276
319
|
const darkLink = document.getElementById('hljs-dark');
|
|
277
320
|
if (lightLink && darkLink) {
|
|
@@ -285,19 +328,14 @@
|
|
|
285
328
|
initializeScrollSpy();
|
|
286
329
|
initializeSPA();
|
|
287
330
|
|
|
288
|
-
// Auto-scroll sidebar safely
|
|
289
331
|
setTimeout(() => {
|
|
290
332
|
const activeNav = document.querySelector('.sidebar-nav a.active');
|
|
291
333
|
const sidebarNav = document.querySelector('.sidebar-nav');
|
|
292
334
|
if (activeNav && sidebarNav) {
|
|
293
|
-
// Calculate scroll top safely instead of scrollIntoView which causes page jump
|
|
294
335
|
sidebarNav.scrollTo({ top: activeNav.offsetTop - (sidebarNav.clientHeight / 2), behavior: 'instant' });
|
|
295
336
|
}
|
|
296
|
-
|
|
297
|
-
// Ensure Hash anchors work on direct link visits (New Tab)
|
|
298
337
|
if (window.location.hash) {
|
|
299
|
-
|
|
300
|
-
if (el) el.scrollIntoView();
|
|
338
|
+
document.querySelector(window.location.hash)?.scrollIntoView();
|
|
301
339
|
}
|
|
302
340
|
}, 100);
|
|
303
341
|
});
|
package/package.json
CHANGED
package/templates/navigation.ejs
CHANGED
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
%>
|
|
85
85
|
<li class="<%= liClasses.join(' ') %>" <% if(isInteractive) { %> aria-expanded="<%= isOpen %>" <% } %>>
|
|
86
86
|
<% if (isDummyLink) { %>
|
|
87
|
-
<span class="nav-label"
|
|
87
|
+
<span class="nav-label">
|
|
88
88
|
<% if (item.icon) { %> <%- renderIcon(item.icon) %> <% } %>
|
|
89
89
|
<span class="nav-item-title"><%= item.title %></span>
|
|
90
90
|
<% if (isInteractive) { %>
|