@docmd/ui 0.4.7 → 0.4.9
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 +430 -198
- package/assets/js/docmd-main.js +258 -241
- package/package.json +1 -1
- package/templates/layout.ejs +31 -85
- package/templates/navigation.ejs +41 -25
- package/templates/partials/footer.ejs +57 -0
- package/templates/partials/options-menu.ejs +30 -0
package/assets/js/docmd-main.js
CHANGED
|
@@ -12,277 +12,294 @@
|
|
|
12
12
|
* --------------------------------------------------------------------
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// 1. Initial Cleanup (ensure classes match aria states)
|
|
25
|
-
nav.querySelectorAll('li.collapsible').forEach(item => {
|
|
26
|
-
// If server rendered it as expanded, ensure aria matches
|
|
27
|
-
if (item.classList.contains('expanded')) {
|
|
28
|
-
item.setAttribute('aria-expanded', 'true');
|
|
29
|
-
} else {
|
|
30
|
-
item.setAttribute('aria-expanded', 'false');
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// 2. Event Delegation
|
|
35
|
-
nav.addEventListener('click', (e) => {
|
|
36
|
-
// Check if the click target is the arrow or inside the arrow wrapper
|
|
37
|
-
const arrow = e.target.closest('.collapse-icon-wrapper');
|
|
38
|
-
|
|
39
|
-
if (arrow) {
|
|
40
|
-
// STOP everything. Do not follow the link.
|
|
41
|
-
e.preventDefault();
|
|
42
|
-
e.stopPropagation();
|
|
43
|
-
|
|
44
|
-
const item = arrow.closest('li.collapsible');
|
|
15
|
+
(function() {
|
|
16
|
+
// =========================================================================
|
|
17
|
+
// 1. EVENT DELEGATION
|
|
18
|
+
// =========================================================================
|
|
19
|
+
document.addEventListener('click', (e) => {
|
|
20
|
+
// Collapsible Navigation
|
|
21
|
+
const navLabel = e.target.closest('.nav-label, .collapse-icon-wrapper');
|
|
22
|
+
if (navLabel) {
|
|
23
|
+
const item = navLabel.closest('li.collapsible');
|
|
45
24
|
if (item) {
|
|
46
|
-
|
|
25
|
+
e.preventDefault();
|
|
47
26
|
const isExpanded = item.classList.contains('expanded');
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
item.classList.remove('expanded');
|
|
51
|
-
item.setAttribute('aria-expanded', 'false');
|
|
52
|
-
} else {
|
|
53
|
-
item.classList.add('expanded');
|
|
54
|
-
item.setAttribute('aria-expanded', 'true');
|
|
55
|
-
}
|
|
27
|
+
item.classList.toggle('expanded', !isExpanded);
|
|
28
|
+
item.setAttribute('aria-expanded', !isExpanded);
|
|
56
29
|
}
|
|
57
|
-
return
|
|
30
|
+
if (navLabel.classList.contains('collapse-icon-wrapper')) return;
|
|
58
31
|
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// --- Mobile Menu Logic ---
|
|
63
|
-
function initializeMobileMenus() {
|
|
64
|
-
const sidebarBtn = document.querySelector('.sidebar-menu-button');
|
|
65
|
-
const sidebar = document.querySelector('.sidebar');
|
|
66
|
-
|
|
67
|
-
if (sidebarBtn && sidebar) {
|
|
68
|
-
sidebarBtn.addEventListener('click', (e) => {
|
|
69
|
-
e.stopPropagation();
|
|
70
|
-
sidebar.classList.toggle('mobile-expanded');
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const tocBtn = document.querySelector('.toc-menu-button');
|
|
75
|
-
const tocContainer = document.querySelector('.toc-container');
|
|
76
|
-
const tocTitle = document.querySelector('.toc-title');
|
|
77
|
-
|
|
78
|
-
const toggleToc = (e) => {
|
|
79
|
-
if (window.getComputedStyle(tocBtn).display === 'none') return;
|
|
80
|
-
e.stopPropagation();
|
|
81
|
-
tocContainer.classList.toggle('mobile-expanded');
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
if (tocBtn && tocContainer) {
|
|
85
|
-
tocBtn.addEventListener('click', toggleToc);
|
|
86
|
-
if (tocTitle) tocTitle.addEventListener('click', toggleToc);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// --- Sidebar Scroll Preservation (Instant Center) ---
|
|
91
|
-
function initializeSidebarScroll() {
|
|
92
|
-
const sidebar = document.querySelector('.sidebar');
|
|
93
|
-
if (!sidebar) return;
|
|
94
|
-
|
|
95
|
-
// Wait for the layout to be stable
|
|
96
|
-
requestAnimationFrame(() => {
|
|
97
|
-
// Find the active link
|
|
98
|
-
const activeElement = sidebar.querySelector('a.active');
|
|
99
32
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
33
|
+
// Toggles
|
|
34
|
+
if (e.target.closest('.toc-menu-button, .toc-title')) {
|
|
35
|
+
document.querySelector('.toc-container')?.classList.toggle('mobile-expanded');
|
|
36
|
+
}
|
|
37
|
+
if (e.target.closest('.sidebar-menu-button')) {
|
|
38
|
+
document.querySelector('.sidebar')?.classList.toggle('mobile-expanded');
|
|
39
|
+
}
|
|
40
|
+
if (e.target.closest('#sidebar-toggle-button')) {
|
|
41
|
+
document.body.classList.toggle('sidebar-collapsed');
|
|
42
|
+
localStorage.setItem('docmd-sidebar-collapsed', document.body.classList.contains('sidebar-collapsed'));
|
|
106
43
|
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// --- Theme Toggle Logic ---
|
|
111
|
-
function setupThemeToggleListener() {
|
|
112
|
-
const toggleButtons = document.querySelectorAll('.theme-toggle-button');
|
|
113
|
-
|
|
114
|
-
function applyTheme(theme) {
|
|
115
|
-
const validThemes = ['light', 'dark'];
|
|
116
|
-
const selectedTheme = validThemes.includes(theme) ? theme : 'light';
|
|
117
|
-
|
|
118
|
-
// 1. Update DOM & Storage
|
|
119
|
-
document.documentElement.setAttribute('data-theme', selectedTheme);
|
|
120
|
-
document.body.setAttribute('data-theme', selectedTheme);
|
|
121
|
-
localStorage.setItem('docmd-theme', selectedTheme);
|
|
122
44
|
|
|
123
|
-
//
|
|
124
|
-
const
|
|
125
|
-
|
|
45
|
+
// Tabs System
|
|
46
|
+
const tabItem = e.target.closest('.docmd-tabs-nav-item');
|
|
47
|
+
if (tabItem) {
|
|
48
|
+
const tabsContainer = tabItem.closest('.docmd-tabs');
|
|
49
|
+
const navItems = Array.from(tabsContainer.querySelectorAll('.docmd-tabs-nav-item'));
|
|
50
|
+
const tabPanes = Array.from(tabsContainer.querySelectorAll('.docmd-tab-pane'));
|
|
51
|
+
const index = navItems.indexOf(tabItem);
|
|
52
|
+
|
|
53
|
+
navItems.forEach(item => item.classList.remove('active'));
|
|
54
|
+
tabPanes.forEach(pane => pane.classList.remove('active'));
|
|
55
|
+
|
|
56
|
+
tabItem.classList.add('active');
|
|
57
|
+
if (tabPanes[index]) tabPanes[index].classList.add('active');
|
|
58
|
+
}
|
|
126
59
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
60
|
+
// Copy Code Button
|
|
61
|
+
const copyBtn = e.target.closest('.copy-code-button');
|
|
62
|
+
if (copyBtn) {
|
|
63
|
+
const code = copyBtn.closest('.code-wrapper')?.querySelector('code');
|
|
64
|
+
if (code) {
|
|
65
|
+
navigator.clipboard.writeText(code.innerText).then(() => {
|
|
66
|
+
copyBtn.classList.add('copied');
|
|
67
|
+
copyBtn.innerHTML = `<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-check"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
copyBtn.classList.remove('copied');
|
|
70
|
+
copyBtn.innerHTML = `<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>`;
|
|
71
|
+
}, 2000);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
135
74
|
}
|
|
136
|
-
}
|
|
75
|
+
});
|
|
137
76
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
77
|
+
// =========================================================================
|
|
78
|
+
// 2. COMPONENT INITIALIZERS
|
|
79
|
+
// =========================================================================
|
|
80
|
+
function injectCopyButtons() {
|
|
81
|
+
if (document.body.dataset.copyCodeEnabled !== 'true') return;
|
|
82
|
+
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>`;
|
|
142
83
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
84
|
+
document.querySelectorAll('pre').forEach(preElement => {
|
|
85
|
+
if (preElement.closest('.code-wrapper')) return;
|
|
86
|
+
const wrapper = document.createElement('div');
|
|
87
|
+
wrapper.className = 'code-wrapper';
|
|
88
|
+
wrapper.style.position = 'relative';
|
|
89
|
+
preElement.parentNode.insertBefore(wrapper, preElement);
|
|
90
|
+
wrapper.appendChild(preElement);
|
|
91
|
+
|
|
92
|
+
const copyButton = document.createElement('button');
|
|
93
|
+
copyButton.className = 'copy-code-button';
|
|
94
|
+
copyButton.innerHTML = svg;
|
|
95
|
+
wrapper.appendChild(copyButton);
|
|
147
96
|
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// --- Sidebar Collapse Logic ---
|
|
152
|
-
function initializeSidebarToggle() {
|
|
153
|
-
const toggleButton = document.getElementById('sidebar-toggle-button');
|
|
154
|
-
const body = document.body;
|
|
97
|
+
}
|
|
155
98
|
|
|
156
|
-
|
|
99
|
+
let scrollObserver = null;
|
|
100
|
+
function initializeScrollSpy() {
|
|
101
|
+
if (scrollObserver) scrollObserver.disconnect();
|
|
102
|
+
const tocLinks = document.querySelectorAll('.toc-link');
|
|
103
|
+
const headings = document.querySelectorAll('.main-content h2, .main-content h3, .main-content h4');
|
|
104
|
+
const tocContainer = document.querySelector('.toc-list');
|
|
105
|
+
|
|
106
|
+
if (tocLinks.length === 0 || headings.length === 0) return;
|
|
107
|
+
|
|
108
|
+
scrollObserver = new IntersectionObserver((entries) => {
|
|
109
|
+
entries.forEach(entry => {
|
|
110
|
+
if (entry.isIntersecting) {
|
|
111
|
+
tocLinks.forEach(link => link.classList.remove('active'));
|
|
112
|
+
const id = entry.target.getAttribute('id');
|
|
113
|
+
const activeLink = document.querySelector(`.toc-link[href="#${id}"]`);
|
|
114
|
+
|
|
115
|
+
if (activeLink) {
|
|
116
|
+
activeLink.classList.add('active');
|
|
117
|
+
if (tocContainer) {
|
|
118
|
+
const linkRect = activeLink.getBoundingClientRect();
|
|
119
|
+
const containerRect = tocContainer.getBoundingClientRect();
|
|
120
|
+
if (linkRect.bottom > containerRect.bottom || linkRect.top < containerRect.top) {
|
|
121
|
+
tocContainer.scrollTo({ top: activeLink.offsetTop - (containerRect.height / 2) + (linkRect.height / 2), behavior: 'smooth' });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}, { rootMargin: '-15% 0px -80% 0px', threshold: 0 });
|
|
157
128
|
|
|
158
|
-
|
|
159
|
-
|
|
129
|
+
headings.forEach(h => scrollObserver.observe(h));
|
|
130
|
+
}
|
|
160
131
|
|
|
161
|
-
|
|
162
|
-
|
|
132
|
+
function executeScripts(container) {
|
|
133
|
+
container.querySelectorAll('script').forEach(oldScript => {
|
|
134
|
+
const newScript = document.createElement('script');
|
|
135
|
+
Array.from(oldScript.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value));
|
|
136
|
+
newScript.text = oldScript.innerHTML;
|
|
137
|
+
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
163
140
|
|
|
164
|
-
|
|
141
|
+
// =========================================================================
|
|
142
|
+
// 3. TARGETED SPA ROUTER
|
|
143
|
+
// =========================================================================
|
|
144
|
+
function initializeSPA() {
|
|
145
|
+
if (location.protocol === 'file:') return;
|
|
146
|
+
if (document.body.dataset.spaEnabled !== 'true') return;
|
|
165
147
|
|
|
166
|
-
|
|
167
|
-
body.classList.toggle('sidebar-collapsed');
|
|
168
|
-
const currentlyCollapsed = body.classList.contains('sidebar-collapsed');
|
|
169
|
-
localStorage.setItem('docmd-sidebar-collapsed', currentlyCollapsed);
|
|
170
|
-
});
|
|
171
|
-
}
|
|
148
|
+
let currentPath = window.location.pathname;
|
|
172
149
|
|
|
173
|
-
|
|
174
|
-
function initializeTabs() {
|
|
175
|
-
document.querySelectorAll('.docmd-tabs').forEach(tabsContainer => {
|
|
176
|
-
const navItems = tabsContainer.querySelectorAll('.docmd-tabs-nav-item');
|
|
177
|
-
const tabPanes = tabsContainer.querySelectorAll('.docmd-tab-pane');
|
|
150
|
+
document.addEventListener('click', async (e) => {
|
|
178
151
|
|
|
179
|
-
|
|
180
|
-
navItem.addEventListener('click', () => {
|
|
181
|
-
navItems.forEach(item => item.classList.remove('active'));
|
|
182
|
-
tabPanes.forEach(pane => pane.classList.remove('active'));
|
|
152
|
+
if (e.target.closest('.collapse-icon-wrapper')) return;
|
|
183
153
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
154
|
+
const link = e.target.closest('.sidebar-nav a, .page-navigation a');
|
|
155
|
+
if (!link || link.target === '_blank' || link.hasAttribute('download')) return;
|
|
156
|
+
|
|
157
|
+
const url = new URL(link.href);
|
|
158
|
+
if (url.origin !== location.origin) return;
|
|
159
|
+
if (url.pathname === window.location.pathname && url.hash) return;
|
|
160
|
+
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
await navigateTo(url.href);
|
|
187
163
|
});
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// --- Copy Code Button Logic ---
|
|
192
|
-
function initializeCopyCodeButtons() {
|
|
193
|
-
if (document.body.dataset.copyCodeEnabled !== 'true') return;
|
|
194
|
-
|
|
195
|
-
const copyIconSvg = `<svg xmlns="http://www.w3.org/2000/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>`;
|
|
196
|
-
const checkIconSvg = `<svg xmlns="http://www.w3.org/2000/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-check"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
197
164
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
165
|
+
// Handle Back/Forward browser buttons & TOC Hash clicks
|
|
166
|
+
window.addEventListener('popstate', () => {
|
|
167
|
+
// If the path is identical, it means ONLY the #hash changed. Do not reload!
|
|
168
|
+
if (window.location.pathname === currentPath) return;
|
|
169
|
+
|
|
170
|
+
navigateTo(window.location.href, false);
|
|
171
|
+
});
|
|
205
172
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
173
|
+
async function navigateTo(url, pushHistory = true) {
|
|
174
|
+
const mainContentWrapper = document.querySelector('.main-content-wrapper');
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
if (mainContentWrapper) mainContentWrapper.style.opacity = '0.5';
|
|
178
|
+
|
|
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
|
+
const parser = new DOMParser();
|
|
184
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
185
|
+
|
|
186
|
+
// 1. UPDATE URL FIRST
|
|
187
|
+
if (pushHistory) history.pushState({}, '', finalUrl);
|
|
188
|
+
currentPath = new URL(finalUrl).pathname;
|
|
189
|
+
document.title = doc.title;
|
|
190
|
+
|
|
191
|
+
// 2. SAFELY SYNC HEAD ASSETS (Favicon & CSS)
|
|
192
|
+
const assetSelectors = 'link[rel="stylesheet"], link[rel="icon"], link[rel="shortcut icon"]';
|
|
193
|
+
const oldAssets = Array.from(document.head.querySelectorAll(assetSelectors));
|
|
194
|
+
const newAssets = Array.from(doc.head.querySelectorAll(assetSelectors));
|
|
195
|
+
|
|
196
|
+
newAssets.forEach((newAsset, index) => {
|
|
197
|
+
if (oldAssets[index]) {
|
|
198
|
+
// Only update if the relative path actually changed
|
|
199
|
+
if (oldAssets[index].getAttribute('href') !== newAsset.getAttribute('href')) {
|
|
200
|
+
oldAssets[index].setAttribute('href', newAsset.getAttribute('href'));
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
document.head.appendChild(newAsset.cloneNode(true));
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 3. MEMORIZE SIDEBAR STATE
|
|
208
|
+
const openMenus = new Set();
|
|
209
|
+
document.querySelectorAll('.sidebar-nav li.collapsible.expanded > .nav-label .nav-item-title, .sidebar-nav li.collapsible.expanded > a .nav-item-title').forEach(el => {
|
|
210
|
+
openMenus.add(el.textContent.trim());
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// 4. SWAP BODY COMPONENTS
|
|
214
|
+
const selectorsToSwap =[
|
|
215
|
+
'.main-content', '.toc-sidebar', '.sidebar-nav',
|
|
216
|
+
'.page-header .header-title', '.page-footer', '.footer-complete',
|
|
217
|
+
'.page-footer-actions'
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
selectorsToSwap.forEach(selector => {
|
|
221
|
+
const oldEl = document.querySelector(selector);
|
|
222
|
+
const newEl = doc.querySelector(selector);
|
|
223
|
+
if (oldEl && newEl) oldEl.innerHTML = newEl.innerHTML;
|
|
224
|
+
});
|
|
225
|
+
|
|
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
|
+
const hash = new URL(finalUrl).hash;
|
|
237
|
+
if (hash) {
|
|
238
|
+
document.querySelector(hash)?.scrollIntoView();
|
|
239
|
+
} else {
|
|
240
|
+
if (mainContentWrapper) mainContentWrapper.scrollTo(0, 0);
|
|
241
|
+
window.scrollTo(0, 0);
|
|
242
|
+
}
|
|
209
243
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
244
|
+
if (mainContentWrapper) mainContentWrapper.style.opacity = '1';
|
|
245
|
+
injectCopyButtons();
|
|
246
|
+
initializeScrollSpy();
|
|
247
|
+
|
|
248
|
+
const newMainContent = document.querySelector('.main-content');
|
|
249
|
+
if (newMainContent) executeScripts(newMainContent);
|
|
215
250
|
|
|
216
|
-
|
|
217
|
-
navigator.clipboard.writeText(codeElement.innerText).then(() => {
|
|
218
|
-
copyButton.innerHTML = checkIconSvg;
|
|
219
|
-
copyButton.classList.add('copied');
|
|
220
|
-
setTimeout(() => {
|
|
221
|
-
copyButton.innerHTML = copyIconSvg;
|
|
222
|
-
copyButton.classList.remove('copied');
|
|
223
|
-
}, 2000);
|
|
224
|
-
}).catch(err => {
|
|
225
|
-
console.error('Failed to copy text: ', err);
|
|
226
|
-
copyButton.innerText = 'Error';
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
}
|
|
251
|
+
document.dispatchEvent(new CustomEvent('docmd:page-mounted', { detail: { url: finalUrl } }));
|
|
231
252
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
document.body.setAttribute('data-theme', currentTheme);
|
|
253
|
+
} catch(e) {
|
|
254
|
+
window.location.assign(url);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
237
257
|
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// --- Scroll Spy Logic ---
|
|
241
|
-
function initializeScrollSpy() {
|
|
242
|
-
const tocLinks = document.querySelectorAll('.toc-link');
|
|
243
|
-
const headings = document.querySelectorAll('.main-content h2, .main-content h3');
|
|
244
|
-
|
|
245
|
-
if (tocLinks.length === 0 || headings.length === 0) return;
|
|
246
|
-
|
|
247
|
-
const observerOptions = {
|
|
248
|
-
root: null,
|
|
249
|
-
// Trigger when heading crosses the top 10% of screen
|
|
250
|
-
rootMargin: '-10% 0px -80% 0px',
|
|
251
|
-
threshold: 0
|
|
252
|
-
};
|
|
253
258
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
// =========================================================================
|
|
260
|
+
// 4. BOOTSTRAP
|
|
261
|
+
// =========================================================================
|
|
262
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
263
|
+
if (localStorage.getItem('docmd-sidebar-collapsed') === 'true') {
|
|
264
|
+
document.body.classList.add('sidebar-collapsed');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
document.querySelectorAll('.theme-toggle-button').forEach(btn => {
|
|
268
|
+
btn.addEventListener('click', () => {
|
|
269
|
+
const t = document.documentElement.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
|
|
270
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
271
|
+
document.body.setAttribute('data-theme', t);
|
|
272
|
+
localStorage.setItem('docmd-theme', t);
|
|
263
273
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
274
|
+
// Highlight.js CSS swap
|
|
275
|
+
const lightLink = document.getElementById('hljs-light');
|
|
276
|
+
const darkLink = document.getElementById('hljs-dark');
|
|
277
|
+
if (lightLink && darkLink) {
|
|
278
|
+
lightLink.disabled = t === 'dark';
|
|
279
|
+
darkLink.disabled = t === 'light';
|
|
269
280
|
}
|
|
270
|
-
}
|
|
281
|
+
});
|
|
271
282
|
});
|
|
272
|
-
}, observerOptions);
|
|
273
283
|
|
|
274
|
-
|
|
275
|
-
|
|
284
|
+
injectCopyButtons();
|
|
285
|
+
initializeScrollSpy();
|
|
286
|
+
initializeSPA();
|
|
287
|
+
|
|
288
|
+
// Auto-scroll sidebar safely
|
|
289
|
+
setTimeout(() => {
|
|
290
|
+
const activeNav = document.querySelector('.sidebar-nav a.active');
|
|
291
|
+
const sidebarNav = document.querySelector('.sidebar-nav');
|
|
292
|
+
if (activeNav && sidebarNav) {
|
|
293
|
+
// Calculate scroll top safely instead of scrollIntoView which causes page jump
|
|
294
|
+
sidebarNav.scrollTo({ top: activeNav.offsetTop - (sidebarNav.clientHeight / 2), behavior: 'instant' });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Ensure Hash anchors work on direct link visits (New Tab)
|
|
298
|
+
if (window.location.hash) {
|
|
299
|
+
const el = document.querySelector(window.location.hash);
|
|
300
|
+
if (el) el.scrollIntoView();
|
|
301
|
+
}
|
|
302
|
+
}, 100);
|
|
303
|
+
});
|
|
276
304
|
|
|
277
|
-
|
|
278
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
279
|
-
syncBodyTheme();
|
|
280
|
-
setupThemeToggleListener();
|
|
281
|
-
initializeSidebarToggle();
|
|
282
|
-
initializeTabs();
|
|
283
|
-
initializeCopyCodeButtons();
|
|
284
|
-
initializeCollapsibleNav();
|
|
285
|
-
initializeMobileMenus();
|
|
286
|
-
initializeSidebarScroll();
|
|
287
|
-
initializeScrollSpy();
|
|
288
|
-
});
|
|
305
|
+
})();
|