@mgks/docmd 0.3.1 → 0.3.3
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 +5 -1
- package/package.json +28 -24
- package/src/templates/layout.ejs +16 -6
- package/src/templates/no-style.ejs +5 -1
- package/src/templates/partials/theme-init.js +20 -16
- package/src/templates/toc.ejs +1 -1
- package/.gitattributes +0 -2
- package/.github/CODE_OF_CONDUCT.md +0 -48
- package/.github/CONTRIBUTING.md +0 -129
- package/.github/FUNDING.yml +0 -15
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -58
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -27
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -16
- package/.github/SECURITY.md +0 -18
- package/.github/workflows/deploy-docmd.yml +0 -45
- package/.github/workflows/npm-publish.yml +0 -49
- package/assets/css/welcome.css +0 -6
- package/assets/images/preview-dark-1.webp +0 -0
- package/assets/images/preview-dark-2.webp +0 -0
- package/assets/images/preview-dark-3.webp +0 -0
- package/assets/images/preview-dark-welcome.png +0 -0
- package/assets/images/preview-light-1.webp +0 -0
- package/assets/images/preview-light-2.webp +0 -0
- package/assets/images/preview-light-3.webp +0 -0
- package/docs/cli-commands.md +0 -108
- package/docs/comparison.md +0 -56
- package/docs/configuration.md +0 -289
- package/docs/content/containers/buttons.md +0 -88
- package/docs/content/containers/callouts.md +0 -154
- package/docs/content/containers/cards.md +0 -93
- package/docs/content/containers/changelogs.md +0 -128
- package/docs/content/containers/collapsible.md +0 -89
- package/docs/content/containers/index.md +0 -35
- package/docs/content/containers/nested-containers.md +0 -329
- package/docs/content/containers/steps.md +0 -175
- package/docs/content/containers/tabs.md +0 -228
- package/docs/content/custom-containers.md +0 -24
- package/docs/content/frontmatter.md +0 -84
- package/docs/content/images.md +0 -205
- package/docs/content/index.md +0 -19
- package/docs/content/markdown-syntax.md +0 -309
- package/docs/content/mermaid.md +0 -723
- package/docs/content/no-style-example.md +0 -112
- package/docs/content/no-style-pages.md +0 -226
- package/docs/content/search.md +0 -68
- package/docs/contributing.md +0 -101
- package/docs/deployment.md +0 -120
- package/docs/getting-started/basic-usage.md +0 -89
- package/docs/getting-started/index.md +0 -21
- package/docs/getting-started/installation.md +0 -75
- package/docs/index.md +0 -168
- package/docs/overview.md +0 -63
- package/docs/plugins/analytics.md +0 -75
- package/docs/plugins/index.md +0 -70
- package/docs/plugins/seo.md +0 -127
- package/docs/plugins/sitemap.md +0 -87
- package/docs/recipes/custom-fonts.md +0 -43
- package/docs/recipes/favicon.md +0 -38
- package/docs/recipes/index.md +0 -12
- package/docs/recipes/landing-page.md +0 -46
- package/docs/theming/assets-management.md +0 -126
- package/docs/theming/available-themes.md +0 -77
- package/docs/theming/custom-css-js.md +0 -79
- package/docs/theming/icons.md +0 -92
- package/docs/theming/index.md +0 -19
- package/docs/theming/light-dark-mode.md +0 -114
- package/src/assets/css/docmd-highlight-dark.css +0 -1
- package/src/assets/css/docmd-highlight-light.css +0 -1
- package/src/assets/css/docmd-main.css +0 -1608
- package/src/assets/css/docmd-theme-retro.css +0 -868
- package/src/assets/css/docmd-theme-ruby.css +0 -629
- package/src/assets/css/docmd-theme-sky.css +0 -618
- package/src/assets/favicon.ico +0 -0
- package/src/assets/images/docmd-logo-dark.png +0 -0
- package/src/assets/images/docmd-logo-light.png +0 -0
- package/src/assets/images/docmd-logo.png +0 -0
- package/src/assets/images/docmd-preview.png +0 -0
- package/src/assets/js/docmd-image-lightbox.js +0 -74
- package/src/assets/js/docmd-main.js +0 -249
- package/src/assets/js/docmd-mermaid.js +0 -205
- package/src/assets/js/docmd-search.js +0 -218
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* Main client-side script for docmd UI interactions
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// --- Collapsible Navigation Logic ---
|
|
8
|
-
function initializeCollapsibleNav() {
|
|
9
|
-
const nav = document.querySelector('.sidebar-nav');
|
|
10
|
-
if (!nav) return;
|
|
11
|
-
|
|
12
|
-
let navStates = {};
|
|
13
|
-
try {
|
|
14
|
-
// Use sessionStorage to remember state only for the current session
|
|
15
|
-
navStates = JSON.parse(sessionStorage.getItem('docmd-nav-states')) || {};
|
|
16
|
-
} catch (e) { /* silent fail */ }
|
|
17
|
-
|
|
18
|
-
nav.querySelectorAll('li.collapsible').forEach(item => {
|
|
19
|
-
const navId = item.dataset.navId;
|
|
20
|
-
const anchor = item.querySelector('a');
|
|
21
|
-
const submenu = item.querySelector('.submenu');
|
|
22
|
-
|
|
23
|
-
if (!navId || !anchor || !submenu) return;
|
|
24
|
-
|
|
25
|
-
const isParentActive = item.classList.contains('active-parent');
|
|
26
|
-
// Default to expanded if it's a parent of the active page, otherwise check stored state.
|
|
27
|
-
let isExpanded = isParentActive || (navStates[navId] === true);
|
|
28
|
-
|
|
29
|
-
const toggleSubmenu = (expand) => {
|
|
30
|
-
item.setAttribute('aria-expanded', expand);
|
|
31
|
-
submenu.style.display = expand ? 'block' : 'none';
|
|
32
|
-
navStates[navId] = expand;
|
|
33
|
-
sessionStorage.setItem('docmd-nav-states', JSON.stringify(navStates));
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Set initial state on page load
|
|
37
|
-
toggleSubmenu(isExpanded);
|
|
38
|
-
|
|
39
|
-
anchor.addEventListener('click', (e) => {
|
|
40
|
-
const currentExpanded = item.getAttribute('aria-expanded') === 'true';
|
|
41
|
-
const href = anchor.getAttribute('href');
|
|
42
|
-
const isPlaceholder = !href || href === '#' || href === '';
|
|
43
|
-
|
|
44
|
-
if (!currentExpanded) {
|
|
45
|
-
toggleSubmenu(true);
|
|
46
|
-
} else if (isPlaceholder || e.target.closest('.collapse-icon')) {
|
|
47
|
-
toggleSubmenu(false);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (isPlaceholder || e.target.closest('.collapse-icon')) {
|
|
51
|
-
e.preventDefault();
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
/* anchor.addEventListener('click', (e) => {
|
|
56
|
-
// If the click target is the icon, ALWAYS prevent navigation and toggle.
|
|
57
|
-
if (e.target.closest('.collapse-icon')) {
|
|
58
|
-
e.preventDefault();
|
|
59
|
-
toggleSubmenu(item.getAttribute('aria-expanded') !== 'true');
|
|
60
|
-
}
|
|
61
|
-
// If the link is just a placeholder, also prevent navigation and toggle.
|
|
62
|
-
else if (anchor.getAttribute('href') === '#') {
|
|
63
|
-
e.preventDefault();
|
|
64
|
-
toggleSubmenu(item.getAttribute('aria-expanded') !== 'true');
|
|
65
|
-
}
|
|
66
|
-
// Otherwise, let the click proceed to navigate to the link.
|
|
67
|
-
});*/
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// --- Sidebar Scroll Preservation ---
|
|
72
|
-
function initializeSidebarScroll() {
|
|
73
|
-
const sidebar = document.querySelector('.sidebar');
|
|
74
|
-
if (!sidebar) return;
|
|
75
|
-
|
|
76
|
-
setTimeout(() => {
|
|
77
|
-
const activeElement = sidebar.querySelector('a.active') || sidebar.querySelector('.active-parent > a');
|
|
78
|
-
|
|
79
|
-
if (activeElement) {
|
|
80
|
-
const sidebarRect = sidebar.getBoundingClientRect();
|
|
81
|
-
const elementRect = activeElement.getBoundingClientRect();
|
|
82
|
-
|
|
83
|
-
// Check if the element's top or bottom is outside the sidebar's visible area
|
|
84
|
-
const isNotInView = elementRect.top < sidebarRect.top || elementRect.bottom > sidebarRect.bottom;
|
|
85
|
-
|
|
86
|
-
if (isNotInView) {
|
|
87
|
-
activeElement.scrollIntoView({
|
|
88
|
-
behavior: 'auto',
|
|
89
|
-
block: 'center',
|
|
90
|
-
inline: 'nearest'
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}, 10);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// --- Theme Toggle Logic ---
|
|
98
|
-
function setupThemeToggleListener() {
|
|
99
|
-
const themeToggleButton = document.getElementById('theme-toggle-button');
|
|
100
|
-
|
|
101
|
-
function applyTheme(theme) {
|
|
102
|
-
document.documentElement.setAttribute('data-theme', theme);
|
|
103
|
-
document.body.setAttribute('data-theme', theme);
|
|
104
|
-
localStorage.setItem('docmd-theme', theme);
|
|
105
|
-
|
|
106
|
-
// Switch highlight.js theme
|
|
107
|
-
const highlightThemeLink = document.getElementById('highlight-theme');
|
|
108
|
-
if (highlightThemeLink) {
|
|
109
|
-
const newHref = highlightThemeLink.getAttribute('data-base-href') + `docmd-highlight-${theme}.css`;
|
|
110
|
-
highlightThemeLink.setAttribute('href', newHref);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Add click listener to the toggle button
|
|
115
|
-
if (themeToggleButton) {
|
|
116
|
-
themeToggleButton.addEventListener('click', () => {
|
|
117
|
-
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
118
|
-
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
119
|
-
applyTheme(newTheme);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// --- Sidebar Collapse Logic ---
|
|
125
|
-
function initializeSidebarToggle() {
|
|
126
|
-
const toggleButton = document.getElementById('sidebar-toggle-button');
|
|
127
|
-
const body = document.body;
|
|
128
|
-
|
|
129
|
-
if (!body.classList.contains('sidebar-collapsible') || !toggleButton) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const defaultConfigCollapsed = body.dataset.defaultCollapsed === 'true';
|
|
134
|
-
let isCollapsed = localStorage.getItem('docmd-sidebar-collapsed');
|
|
135
|
-
|
|
136
|
-
if (isCollapsed === null) {
|
|
137
|
-
isCollapsed = defaultConfigCollapsed;
|
|
138
|
-
} else {
|
|
139
|
-
isCollapsed = isCollapsed === 'true';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (isCollapsed) {
|
|
143
|
-
body.classList.add('sidebar-collapsed');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
toggleButton.addEventListener('click', () => {
|
|
147
|
-
body.classList.toggle('sidebar-collapsed');
|
|
148
|
-
const currentlyCollapsed = body.classList.contains('sidebar-collapsed');
|
|
149
|
-
localStorage.setItem('docmd-sidebar-collapsed', currentlyCollapsed);
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// --- Tabs Container Logic ---
|
|
154
|
-
function initializeTabs() {
|
|
155
|
-
document.querySelectorAll('.docmd-tabs').forEach(tabsContainer => {
|
|
156
|
-
const navItems = tabsContainer.querySelectorAll('.docmd-tabs-nav-item');
|
|
157
|
-
const tabPanes = tabsContainer.querySelectorAll('.docmd-tab-pane');
|
|
158
|
-
|
|
159
|
-
navItems.forEach((navItem, index) => {
|
|
160
|
-
navItem.addEventListener('click', () => {
|
|
161
|
-
navItems.forEach(item => item.classList.remove('active'));
|
|
162
|
-
tabPanes.forEach(pane => pane.classList.remove('active'));
|
|
163
|
-
|
|
164
|
-
navItem.classList.add('active');
|
|
165
|
-
if (tabPanes[index]) {
|
|
166
|
-
tabPanes[index].classList.add('active');
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// --- Copy Code Button Logic ---
|
|
174
|
-
function initializeCopyCodeButtons() {
|
|
175
|
-
if (document.body.dataset.copyCodeEnabled !== 'true') {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
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>`;
|
|
180
|
-
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>`;
|
|
181
|
-
|
|
182
|
-
document.querySelectorAll('pre').forEach(preElement => {
|
|
183
|
-
const codeElement = preElement.querySelector('code');
|
|
184
|
-
if (!codeElement) return;
|
|
185
|
-
|
|
186
|
-
// Create a wrapper div around the pre element
|
|
187
|
-
const wrapper = document.createElement('div');
|
|
188
|
-
wrapper.style.position = 'relative';
|
|
189
|
-
wrapper.style.display = 'block';
|
|
190
|
-
|
|
191
|
-
// Insert the wrapper before the pre element
|
|
192
|
-
preElement.parentNode.insertBefore(wrapper, preElement);
|
|
193
|
-
|
|
194
|
-
// Move the pre element into the wrapper
|
|
195
|
-
wrapper.appendChild(preElement);
|
|
196
|
-
|
|
197
|
-
// Remove the relative positioning from pre since wrapper handles it
|
|
198
|
-
preElement.style.position = 'static';
|
|
199
|
-
|
|
200
|
-
const copyButton = document.createElement('button');
|
|
201
|
-
copyButton.className = 'copy-code-button';
|
|
202
|
-
copyButton.innerHTML = copyIconSvg;
|
|
203
|
-
copyButton.setAttribute('aria-label', 'Copy code to clipboard');
|
|
204
|
-
wrapper.appendChild(copyButton);
|
|
205
|
-
|
|
206
|
-
copyButton.addEventListener('click', () => {
|
|
207
|
-
navigator.clipboard.writeText(codeElement.innerText).then(() => {
|
|
208
|
-
copyButton.innerHTML = checkIconSvg;
|
|
209
|
-
copyButton.classList.add('copied');
|
|
210
|
-
setTimeout(() => {
|
|
211
|
-
copyButton.innerHTML = copyIconSvg;
|
|
212
|
-
copyButton.classList.remove('copied');
|
|
213
|
-
}, 2000);
|
|
214
|
-
}).catch(err => {
|
|
215
|
-
console.error('Failed to copy text: ', err);
|
|
216
|
-
copyButton.innerText = 'Error';
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// --- Theme Sync Function ---
|
|
223
|
-
function syncBodyTheme() {
|
|
224
|
-
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
225
|
-
if (currentTheme && document.body) {
|
|
226
|
-
document.body.setAttribute('data-theme', currentTheme);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Also ensure highlight CSS matches the current theme
|
|
230
|
-
const highlightThemeLink = document.getElementById('highlight-theme');
|
|
231
|
-
if (highlightThemeLink && currentTheme) {
|
|
232
|
-
const baseHref = highlightThemeLink.getAttribute('data-base-href');
|
|
233
|
-
if (baseHref) {
|
|
234
|
-
const newHref = baseHref + `docmd-highlight-${currentTheme}.css`;
|
|
235
|
-
highlightThemeLink.setAttribute('href', newHref);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// --- Main Execution ---
|
|
241
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
242
|
-
syncBodyTheme(); // Sync body theme with html theme
|
|
243
|
-
setupThemeToggleListener();
|
|
244
|
-
initializeSidebarToggle();
|
|
245
|
-
initializeTabs();
|
|
246
|
-
initializeCopyCodeButtons();
|
|
247
|
-
initializeCollapsibleNav();
|
|
248
|
-
initializeSidebarScroll();
|
|
249
|
-
});
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* Mermaid diagram integration with theme support
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
(function () {
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
// Configuration for mermaid based on current theme
|
|
11
|
-
function getMermaidConfig(theme) {
|
|
12
|
-
const isDark = theme === 'dark';
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
startOnLoad: false,
|
|
16
|
-
theme: isDark ? 'dark' : 'default',
|
|
17
|
-
flowchart: {
|
|
18
|
-
useMaxWidth: true,
|
|
19
|
-
htmlLabels: true
|
|
20
|
-
},
|
|
21
|
-
sequence: {
|
|
22
|
-
useMaxWidth: true
|
|
23
|
-
},
|
|
24
|
-
gantt: {
|
|
25
|
-
useMaxWidth: true
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Initialize mermaid when DOM is ready
|
|
31
|
-
function initializeMermaid() {
|
|
32
|
-
if (typeof mermaid === 'undefined') {
|
|
33
|
-
console.warn('Mermaid library not loaded');
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const currentTheme = document.body.getAttribute('data-theme') || 'light';
|
|
38
|
-
const config = getMermaidConfig(currentTheme);
|
|
39
|
-
|
|
40
|
-
mermaid.initialize(config);
|
|
41
|
-
|
|
42
|
-
// Render all mermaid diagrams
|
|
43
|
-
renderMermaidDiagrams();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Store for diagram codes
|
|
47
|
-
const diagramStore = new Map();
|
|
48
|
-
|
|
49
|
-
// Render all mermaid diagrams on the page
|
|
50
|
-
function renderMermaidDiagrams() {
|
|
51
|
-
if (typeof mermaid === 'undefined') {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const mermaidElements = document.querySelectorAll('pre.mermaid');
|
|
56
|
-
|
|
57
|
-
mermaidElements.forEach((element, index) => {
|
|
58
|
-
// Skip if already rendered
|
|
59
|
-
if (element.getAttribute('data-processed') === 'true') {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
// Get the diagram code
|
|
65
|
-
const code = element.textContent;
|
|
66
|
-
|
|
67
|
-
// Create a unique ID for this diagram
|
|
68
|
-
const id = `mermaid-diagram-${index}-${Date.now()}`;
|
|
69
|
-
|
|
70
|
-
// Store the original code for re-rendering
|
|
71
|
-
diagramStore.set(id, code);
|
|
72
|
-
|
|
73
|
-
// Create a container div
|
|
74
|
-
const container = document.createElement('div');
|
|
75
|
-
container.className = 'mermaid-container';
|
|
76
|
-
container.setAttribute('data-mermaid-id', id);
|
|
77
|
-
container.setAttribute('data-processed', 'true');
|
|
78
|
-
|
|
79
|
-
// Replace the pre element with the container
|
|
80
|
-
element.parentNode.replaceChild(container, element);
|
|
81
|
-
|
|
82
|
-
// Render the diagram
|
|
83
|
-
renderSingleDiagram(container, id, code);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error('Mermaid processing error:', error);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Render a single diagram
|
|
91
|
-
function renderSingleDiagram(container, id, code) {
|
|
92
|
-
if (typeof mermaid === 'undefined') {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Process the code to handle theme overrides
|
|
97
|
-
const currentTheme = document.body.getAttribute('data-theme') || 'light';
|
|
98
|
-
const processedCode = processThemeInCode(code, currentTheme);
|
|
99
|
-
|
|
100
|
-
// Render the diagram
|
|
101
|
-
mermaid.render(id, processedCode).then(result => {
|
|
102
|
-
container.innerHTML = result.svg;
|
|
103
|
-
}).catch(error => {
|
|
104
|
-
console.error('Mermaid rendering error:', error);
|
|
105
|
-
container.innerHTML = `<pre class="mermaid-error">Error rendering diagram: ${error.message}</pre>`;
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Process mermaid code to inject or override theme
|
|
110
|
-
function processThemeInCode(code, theme) {
|
|
111
|
-
const isDark = theme === 'dark';
|
|
112
|
-
const targetTheme = isDark ? 'dark' : 'default';
|
|
113
|
-
|
|
114
|
-
// Check if code has %%{init: config - match the entire init block including nested objects
|
|
115
|
-
const initRegex = /%%\{init:\s*\{.*?\}\s*\}%%/s;
|
|
116
|
-
const match = code.match(initRegex);
|
|
117
|
-
|
|
118
|
-
if (match) {
|
|
119
|
-
// Code has init config, replace only the theme property
|
|
120
|
-
const initBlock = match[0];
|
|
121
|
-
let updatedBlock = initBlock;
|
|
122
|
-
|
|
123
|
-
// Try to replace theme property
|
|
124
|
-
if (initBlock.includes("'theme'")) {
|
|
125
|
-
updatedBlock = initBlock.replace(/'theme'\s*:\s*'[^']*'/, `'theme':'${targetTheme}'`);
|
|
126
|
-
} else if (initBlock.includes('"theme"')) {
|
|
127
|
-
updatedBlock = initBlock.replace(/"theme"\s*:\s*"[^"]*"/, `"theme":"${targetTheme}"`);
|
|
128
|
-
} else {
|
|
129
|
-
// Add theme to the config - insert after the first {
|
|
130
|
-
updatedBlock = initBlock.replace(/%%\{init:\s*\{/, `%%{init: {'theme':'${targetTheme}',`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return code.replace(initRegex, updatedBlock);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// No init config, code will use global mermaid config
|
|
137
|
-
return code;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Re-render mermaid diagrams when theme changes
|
|
141
|
-
function handleThemeChange() {
|
|
142
|
-
if (typeof mermaid === 'undefined') {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const currentTheme = document.body.getAttribute('data-theme') || 'light';
|
|
147
|
-
const config = getMermaidConfig(currentTheme);
|
|
148
|
-
|
|
149
|
-
// Re-initialize mermaid with new theme
|
|
150
|
-
mermaid.initialize(config);
|
|
151
|
-
|
|
152
|
-
// Find all rendered diagrams and re-render them
|
|
153
|
-
const containers = document.querySelectorAll('.mermaid-container[data-processed="true"]');
|
|
154
|
-
|
|
155
|
-
containers.forEach((container) => {
|
|
156
|
-
const mermaidId = container.getAttribute('data-mermaid-id');
|
|
157
|
-
const code = diagramStore.get(mermaidId);
|
|
158
|
-
|
|
159
|
-
if (code) {
|
|
160
|
-
// Create a new unique ID for re-rendering
|
|
161
|
-
const newId = `${mermaidId}-${Date.now()}`;
|
|
162
|
-
|
|
163
|
-
// Clear the container and re-render
|
|
164
|
-
container.innerHTML = '';
|
|
165
|
-
renderSingleDiagram(container, newId, code);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Setup theme change observer
|
|
171
|
-
function setupThemeObserver() {
|
|
172
|
-
const observer = new MutationObserver((mutations) => {
|
|
173
|
-
mutations.forEach((mutation) => {
|
|
174
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
|
|
175
|
-
handleThemeChange();
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
observer.observe(document.body, {
|
|
181
|
-
attributes: true,
|
|
182
|
-
attributeFilter: ['data-theme']
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Initialize when DOM is ready
|
|
187
|
-
if (document.readyState === 'loading') {
|
|
188
|
-
document.addEventListener('DOMContentLoaded', function () {
|
|
189
|
-
initializeMermaid();
|
|
190
|
-
setupThemeObserver();
|
|
191
|
-
});
|
|
192
|
-
} else {
|
|
193
|
-
initializeMermaid();
|
|
194
|
-
setupThemeObserver();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Handle tab switches - render mermaid in newly visible tabs
|
|
198
|
-
document.addEventListener('click', function (e) {
|
|
199
|
-
if (e.target.classList.contains('docmd-tabs-nav-item')) {
|
|
200
|
-
// Wait a bit for tab content to be visible
|
|
201
|
-
setTimeout(renderMermaidDiagrams, 100);
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
})();
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* Client-side search functionality for docmd
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
(function() {
|
|
8
|
-
let miniSearch = null;
|
|
9
|
-
let isIndexLoaded = false;
|
|
10
|
-
let selectedIndex = -1; // Track keyboard selection
|
|
11
|
-
|
|
12
|
-
const searchModal = document.getElementById('docmd-search-modal');
|
|
13
|
-
const searchInput = document.getElementById('docmd-search-input');
|
|
14
|
-
const searchResults = document.getElementById('docmd-search-results');
|
|
15
|
-
|
|
16
|
-
const ROOT_PATH = window.DOCMD_ROOT || './';
|
|
17
|
-
|
|
18
|
-
if (!searchModal) return;
|
|
19
|
-
|
|
20
|
-
const emptyStateHtml = '<div class="search-initial">Type to start searching...</div>';
|
|
21
|
-
|
|
22
|
-
// 1. Open/Close Logic
|
|
23
|
-
function openSearch() {
|
|
24
|
-
searchModal.style.display = 'flex';
|
|
25
|
-
searchInput.focus();
|
|
26
|
-
|
|
27
|
-
if (!searchInput.value.trim()) {
|
|
28
|
-
searchResults.innerHTML = emptyStateHtml;
|
|
29
|
-
selectedIndex = -1;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!isIndexLoaded) loadIndex();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function closeSearch() {
|
|
36
|
-
searchModal.style.display = 'none';
|
|
37
|
-
selectedIndex = -1; // Reset selection
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 2. Keyboard Navigation & Shortcuts
|
|
41
|
-
document.addEventListener('keydown', (e) => {
|
|
42
|
-
// Open: Cmd+K / Ctrl+K
|
|
43
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
if (searchModal.style.display === 'flex') {
|
|
46
|
-
closeSearch();
|
|
47
|
-
} else {
|
|
48
|
-
openSearch();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Context: Only handle these if search is open
|
|
53
|
-
if (searchModal.style.display === 'flex') {
|
|
54
|
-
const items = searchResults.querySelectorAll('.search-result-item');
|
|
55
|
-
|
|
56
|
-
if (e.key === 'Escape') {
|
|
57
|
-
e.preventDefault();
|
|
58
|
-
closeSearch();
|
|
59
|
-
}
|
|
60
|
-
else if (e.key === 'ArrowDown') {
|
|
61
|
-
e.preventDefault();
|
|
62
|
-
if (items.length === 0) return;
|
|
63
|
-
selectedIndex = (selectedIndex + 1) % items.length;
|
|
64
|
-
updateSelection(items);
|
|
65
|
-
}
|
|
66
|
-
else if (e.key === 'ArrowUp') {
|
|
67
|
-
e.preventDefault();
|
|
68
|
-
if (items.length === 0) return;
|
|
69
|
-
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
70
|
-
updateSelection(items);
|
|
71
|
-
}
|
|
72
|
-
else if (e.key === 'Enter') {
|
|
73
|
-
e.preventDefault();
|
|
74
|
-
if (selectedIndex >= 0 && items[selectedIndex]) {
|
|
75
|
-
items[selectedIndex].click();
|
|
76
|
-
} else if (items.length > 0) {
|
|
77
|
-
// If nothing selected but results exist, click the first one on Enter
|
|
78
|
-
items[0].click();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
function updateSelection(items) {
|
|
85
|
-
items.forEach((item, index) => {
|
|
86
|
-
if (index === selectedIndex) {
|
|
87
|
-
item.classList.add('selected');
|
|
88
|
-
item.scrollIntoView({ block: 'nearest' });
|
|
89
|
-
} else {
|
|
90
|
-
item.classList.remove('selected');
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Click handlers
|
|
96
|
-
document.querySelectorAll('.docmd-search-trigger').forEach(btn => {
|
|
97
|
-
btn.addEventListener('click', openSearch);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
searchModal.addEventListener('click', (e) => {
|
|
101
|
-
if (e.target === searchModal) closeSearch();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// 3. Index Loading Logic
|
|
105
|
-
async function loadIndex() {
|
|
106
|
-
try {
|
|
107
|
-
|
|
108
|
-
const indexUrl = `${ROOT_PATH}search-index.json`;
|
|
109
|
-
const response = await fetch(indexUrl);
|
|
110
|
-
if (!response.ok) throw new Error(response.status);
|
|
111
|
-
|
|
112
|
-
const jsonString = await response.text();
|
|
113
|
-
|
|
114
|
-
miniSearch = MiniSearch.loadJSON(jsonString, {
|
|
115
|
-
fields: ['title', 'headings', 'text'],
|
|
116
|
-
storeFields: ['title', 'id', 'text'],
|
|
117
|
-
searchOptions: {
|
|
118
|
-
fuzzy: 0.2,
|
|
119
|
-
prefix: true,
|
|
120
|
-
boost: { title: 2, headings: 1.5 }
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
isIndexLoaded = true;
|
|
125
|
-
// console.log('Search index loaded');
|
|
126
|
-
|
|
127
|
-
if (searchInput.value.trim()) {
|
|
128
|
-
searchInput.dispatchEvent(new Event('input'));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
} catch (e) {
|
|
132
|
-
console.error('Failed to load search index', e);
|
|
133
|
-
searchResults.innerHTML = '<div class="search-error">Failed to load search index.</div>';
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Helper: Snippets (Same as before)
|
|
138
|
-
function getSnippet(text, query) {
|
|
139
|
-
if (!text) return '';
|
|
140
|
-
const terms = query.split(/\s+/).filter(t => t.length > 2);
|
|
141
|
-
const lowerText = text.toLowerCase();
|
|
142
|
-
let bestIndex = -1;
|
|
143
|
-
|
|
144
|
-
for (const term of terms) {
|
|
145
|
-
const idx = lowerText.indexOf(term.toLowerCase());
|
|
146
|
-
if (idx >= 0) { bestIndex = idx; break; }
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const contextSize = 60;
|
|
150
|
-
let start = 0;
|
|
151
|
-
let end = 120;
|
|
152
|
-
|
|
153
|
-
if (bestIndex >= 0) {
|
|
154
|
-
start = Math.max(0, bestIndex - contextSize);
|
|
155
|
-
end = Math.min(text.length, bestIndex + contextSize);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
let snippet = text.substring(start, end);
|
|
159
|
-
if (start > 0) snippet = '...' + snippet;
|
|
160
|
-
if (end < text.length) snippet = snippet + '...';
|
|
161
|
-
|
|
162
|
-
const safeTerms = terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
163
|
-
if (safeTerms) {
|
|
164
|
-
const highlightRegex = new RegExp(`(${safeTerms})`, 'gi');
|
|
165
|
-
snippet = snippet.replace(highlightRegex, '<mark>$1</mark>');
|
|
166
|
-
}
|
|
167
|
-
return snippet;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 4. Search Execution
|
|
171
|
-
searchInput.addEventListener('input', (e) => {
|
|
172
|
-
const query = e.target.value.trim();
|
|
173
|
-
selectedIndex = -1; // Reset selection on new input
|
|
174
|
-
|
|
175
|
-
if (!query) {
|
|
176
|
-
searchResults.innerHTML = emptyStateHtml;
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!isIndexLoaded) return;
|
|
181
|
-
|
|
182
|
-
const results = miniSearch.search(query);
|
|
183
|
-
renderResults(results, query);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
function renderResults(results, query) {
|
|
187
|
-
if (results.length === 0) {
|
|
188
|
-
searchResults.innerHTML = '<div class="search-no-results">No results found.</div>';
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const html = results.slice(0, 10).map((result, index) => {
|
|
193
|
-
const snippet = getSnippet(result.text, query);
|
|
194
|
-
|
|
195
|
-
const linkHref = `${ROOT_PATH}${result.id}`;
|
|
196
|
-
|
|
197
|
-
// Add data-index for mouse interaction tracking if needed
|
|
198
|
-
return `
|
|
199
|
-
<a href="${linkHref}" class="search-result-item" data-index="${index}" onclick="window.closeDocmdSearch()">
|
|
200
|
-
<div class="search-result-title">${result.title}</div>
|
|
201
|
-
<div class="search-result-preview">${snippet}</div>
|
|
202
|
-
</a>
|
|
203
|
-
`;
|
|
204
|
-
}).join('');
|
|
205
|
-
|
|
206
|
-
searchResults.innerHTML = html;
|
|
207
|
-
|
|
208
|
-
// Optional: Allow mouse hover to update selectedIndex
|
|
209
|
-
searchResults.querySelectorAll('.search-result-item').forEach((item, idx) => {
|
|
210
|
-
item.addEventListener('mouseenter', () => {
|
|
211
|
-
selectedIndex = idx;
|
|
212
|
-
updateSelection(searchResults.querySelectorAll('.search-result-item'));
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
window.closeDocmdSearch = closeSearch;
|
|
218
|
-
})();
|