@mgks/docmd 0.2.8 → 0.3.0
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 +3 -1
- package/assets/images/preview-dark-welcome.png +0 -0
- package/config.js +139 -161
- package/docs/comparison.md +5 -0
- package/docs/configuration.md +35 -1
- package/docs/content/markdown-syntax.md +32 -0
- package/docs/content/search.md +68 -0
- package/docs/contributing.md +8 -0
- package/docs/overview.md +1 -0
- package/package.json +7 -2
- package/src/assets/css/docmd-main.css +681 -341
- package/src/assets/css/docmd-theme-retro.css +860 -1
- package/src/assets/css/docmd-theme-ruby.css +621 -1
- package/src/assets/css/docmd-theme-sky.css +610 -1
- package/src/assets/js/docmd-image-lightbox.js +13 -13
- package/src/assets/js/docmd-main.js +24 -23
- package/src/assets/js/docmd-mermaid.js +32 -30
- package/src/assets/js/docmd-search.js +212 -0
- package/src/commands/build.js +195 -41
- package/src/commands/init.js +15 -0
- package/src/core/file-processor.js +12 -1
- package/src/core/html-generator.js +23 -0
- package/src/core/markdown/setup.js +27 -11
- package/src/templates/layout.ejs +55 -3
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/*
|
|
4
4
|
* Main client-side script for docmd UI interactions
|
|
5
5
|
*/
|
|
6
|
+
|
|
6
7
|
// --- Collapsible Navigation Logic ---
|
|
7
8
|
function initializeCollapsibleNav() {
|
|
8
9
|
const nav = document.querySelector('.sidebar-nav');
|
|
@@ -20,7 +21,7 @@ function initializeCollapsibleNav() {
|
|
|
20
21
|
const submenu = item.querySelector('.submenu');
|
|
21
22
|
|
|
22
23
|
if (!navId || !anchor || !submenu) return;
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
const isParentActive = item.classList.contains('active-parent');
|
|
25
26
|
// Default to expanded if it's a parent of the active page, otherwise check stored state.
|
|
26
27
|
let isExpanded = isParentActive || (navStates[navId] === true);
|
|
@@ -51,19 +52,19 @@ function initializeCollapsibleNav() {
|
|
|
51
52
|
}
|
|
52
53
|
});
|
|
53
54
|
|
|
54
|
-
/* anchor.addEventListener('click', (e) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
});*/
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -101,7 +102,7 @@ function setupThemeToggleListener() {
|
|
|
101
102
|
document.documentElement.setAttribute('data-theme', theme);
|
|
102
103
|
document.body.setAttribute('data-theme', theme);
|
|
103
104
|
localStorage.setItem('docmd-theme', theme);
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
// Switch highlight.js theme
|
|
106
107
|
const highlightThemeLink = document.getElementById('highlight-theme');
|
|
107
108
|
if (highlightThemeLink) {
|
|
@@ -131,7 +132,7 @@ function initializeSidebarToggle() {
|
|
|
131
132
|
|
|
132
133
|
const defaultConfigCollapsed = body.dataset.defaultCollapsed === 'true';
|
|
133
134
|
let isCollapsed = localStorage.getItem('docmd-sidebar-collapsed');
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
if (isCollapsed === null) {
|
|
136
137
|
isCollapsed = defaultConfigCollapsed;
|
|
137
138
|
} else {
|
|
@@ -161,8 +162,8 @@ function initializeTabs() {
|
|
|
161
162
|
tabPanes.forEach(pane => pane.classList.remove('active'));
|
|
162
163
|
|
|
163
164
|
navItem.classList.add('active');
|
|
164
|
-
if(tabPanes[index]) {
|
|
165
|
-
|
|
165
|
+
if (tabPanes[index]) {
|
|
166
|
+
tabPanes[index].classList.add('active');
|
|
166
167
|
}
|
|
167
168
|
});
|
|
168
169
|
});
|
|
@@ -186,16 +187,16 @@ function initializeCopyCodeButtons() {
|
|
|
186
187
|
const wrapper = document.createElement('div');
|
|
187
188
|
wrapper.style.position = 'relative';
|
|
188
189
|
wrapper.style.display = 'block';
|
|
189
|
-
|
|
190
|
+
|
|
190
191
|
// Insert the wrapper before the pre element
|
|
191
192
|
preElement.parentNode.insertBefore(wrapper, preElement);
|
|
192
|
-
|
|
193
|
+
|
|
193
194
|
// Move the pre element into the wrapper
|
|
194
195
|
wrapper.appendChild(preElement);
|
|
195
|
-
|
|
196
|
+
|
|
196
197
|
// Remove the relative positioning from pre since wrapper handles it
|
|
197
198
|
preElement.style.position = 'static';
|
|
198
|
-
|
|
199
|
+
|
|
199
200
|
const copyButton = document.createElement('button');
|
|
200
201
|
copyButton.className = 'copy-code-button';
|
|
201
202
|
copyButton.innerHTML = copyIconSvg;
|
|
@@ -224,7 +225,7 @@ function syncBodyTheme() {
|
|
|
224
225
|
if (currentTheme && document.body) {
|
|
225
226
|
document.body.setAttribute('data-theme', currentTheme);
|
|
226
227
|
}
|
|
227
|
-
|
|
228
|
+
|
|
228
229
|
// Also ensure highlight CSS matches the current theme
|
|
229
230
|
const highlightThemeLink = document.getElementById('highlight-theme');
|
|
230
231
|
if (highlightThemeLink && currentTheme) {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
-
// Mermaid diagram integration with theme support
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
/*
|
|
4
|
+
* Mermaid diagram integration with theme support
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function () {
|
|
5
8
|
'use strict';
|
|
6
9
|
|
|
7
10
|
// Configuration for mermaid based on current theme
|
|
8
11
|
function getMermaidConfig(theme) {
|
|
9
12
|
const isDark = theme === 'dark';
|
|
10
|
-
|
|
13
|
+
|
|
11
14
|
return {
|
|
12
15
|
startOnLoad: false,
|
|
13
16
|
theme: isDark ? 'dark' : 'default',
|
|
@@ -33,16 +36,16 @@
|
|
|
33
36
|
|
|
34
37
|
const currentTheme = document.body.getAttribute('data-theme') || 'light';
|
|
35
38
|
const config = getMermaidConfig(currentTheme);
|
|
36
|
-
|
|
39
|
+
|
|
37
40
|
mermaid.initialize(config);
|
|
38
|
-
|
|
41
|
+
|
|
39
42
|
// Render all mermaid diagrams
|
|
40
43
|
renderMermaidDiagrams();
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
// Store for diagram codes
|
|
44
47
|
const diagramStore = new Map();
|
|
45
|
-
|
|
48
|
+
|
|
46
49
|
// Render all mermaid diagrams on the page
|
|
47
50
|
function renderMermaidDiagrams() {
|
|
48
51
|
if (typeof mermaid === 'undefined') {
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
const mermaidElements = document.querySelectorAll('pre.mermaid');
|
|
53
|
-
|
|
56
|
+
|
|
54
57
|
mermaidElements.forEach((element, index) => {
|
|
55
58
|
// Skip if already rendered
|
|
56
59
|
if (element.getAttribute('data-processed') === 'true') {
|
|
@@ -60,22 +63,22 @@
|
|
|
60
63
|
try {
|
|
61
64
|
// Get the diagram code
|
|
62
65
|
const code = element.textContent;
|
|
63
|
-
|
|
66
|
+
|
|
64
67
|
// Create a unique ID for this diagram
|
|
65
68
|
const id = `mermaid-diagram-${index}-${Date.now()}`;
|
|
66
|
-
|
|
69
|
+
|
|
67
70
|
// Store the original code for re-rendering
|
|
68
71
|
diagramStore.set(id, code);
|
|
69
|
-
|
|
72
|
+
|
|
70
73
|
// Create a container div
|
|
71
74
|
const container = document.createElement('div');
|
|
72
75
|
container.className = 'mermaid-container';
|
|
73
76
|
container.setAttribute('data-mermaid-id', id);
|
|
74
77
|
container.setAttribute('data-processed', 'true');
|
|
75
|
-
|
|
78
|
+
|
|
76
79
|
// Replace the pre element with the container
|
|
77
80
|
element.parentNode.replaceChild(container, element);
|
|
78
|
-
|
|
81
|
+
|
|
79
82
|
// Render the diagram
|
|
80
83
|
renderSingleDiagram(container, id, code);
|
|
81
84
|
} catch (error) {
|
|
@@ -83,17 +86,17 @@
|
|
|
83
86
|
}
|
|
84
87
|
});
|
|
85
88
|
}
|
|
86
|
-
|
|
89
|
+
|
|
87
90
|
// Render a single diagram
|
|
88
91
|
function renderSingleDiagram(container, id, code) {
|
|
89
92
|
if (typeof mermaid === 'undefined') {
|
|
90
93
|
return;
|
|
91
94
|
}
|
|
92
|
-
|
|
95
|
+
|
|
93
96
|
// Process the code to handle theme overrides
|
|
94
97
|
const currentTheme = document.body.getAttribute('data-theme') || 'light';
|
|
95
98
|
const processedCode = processThemeInCode(code, currentTheme);
|
|
96
|
-
|
|
99
|
+
|
|
97
100
|
// Render the diagram
|
|
98
101
|
mermaid.render(id, processedCode).then(result => {
|
|
99
102
|
container.innerHTML = result.svg;
|
|
@@ -102,21 +105,21 @@
|
|
|
102
105
|
container.innerHTML = `<pre class="mermaid-error">Error rendering diagram: ${error.message}</pre>`;
|
|
103
106
|
});
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
|
|
106
109
|
// Process mermaid code to inject or override theme
|
|
107
110
|
function processThemeInCode(code, theme) {
|
|
108
111
|
const isDark = theme === 'dark';
|
|
109
112
|
const targetTheme = isDark ? 'dark' : 'default';
|
|
110
|
-
|
|
113
|
+
|
|
111
114
|
// Check if code has %%{init: config - match the entire init block including nested objects
|
|
112
115
|
const initRegex = /%%\{init:\s*\{.*?\}\s*\}%%/s;
|
|
113
116
|
const match = code.match(initRegex);
|
|
114
|
-
|
|
117
|
+
|
|
115
118
|
if (match) {
|
|
116
119
|
// Code has init config, replace only the theme property
|
|
117
120
|
const initBlock = match[0];
|
|
118
121
|
let updatedBlock = initBlock;
|
|
119
|
-
|
|
122
|
+
|
|
120
123
|
// Try to replace theme property
|
|
121
124
|
if (initBlock.includes("'theme'")) {
|
|
122
125
|
updatedBlock = initBlock.replace(/'theme'\s*:\s*'[^']*'/, `'theme':'${targetTheme}'`);
|
|
@@ -126,10 +129,10 @@
|
|
|
126
129
|
// Add theme to the config - insert after the first {
|
|
127
130
|
updatedBlock = initBlock.replace(/%%\{init:\s*\{/, `%%{init: {'theme':'${targetTheme}',`);
|
|
128
131
|
}
|
|
129
|
-
|
|
132
|
+
|
|
130
133
|
return code.replace(initRegex, updatedBlock);
|
|
131
134
|
}
|
|
132
|
-
|
|
135
|
+
|
|
133
136
|
// No init config, code will use global mermaid config
|
|
134
137
|
return code;
|
|
135
138
|
}
|
|
@@ -142,21 +145,21 @@
|
|
|
142
145
|
|
|
143
146
|
const currentTheme = document.body.getAttribute('data-theme') || 'light';
|
|
144
147
|
const config = getMermaidConfig(currentTheme);
|
|
145
|
-
|
|
148
|
+
|
|
146
149
|
// Re-initialize mermaid with new theme
|
|
147
150
|
mermaid.initialize(config);
|
|
148
|
-
|
|
151
|
+
|
|
149
152
|
// Find all rendered diagrams and re-render them
|
|
150
153
|
const containers = document.querySelectorAll('.mermaid-container[data-processed="true"]');
|
|
151
|
-
|
|
154
|
+
|
|
152
155
|
containers.forEach((container) => {
|
|
153
156
|
const mermaidId = container.getAttribute('data-mermaid-id');
|
|
154
157
|
const code = diagramStore.get(mermaidId);
|
|
155
|
-
|
|
158
|
+
|
|
156
159
|
if (code) {
|
|
157
160
|
// Create a new unique ID for re-rendering
|
|
158
161
|
const newId = `${mermaidId}-${Date.now()}`;
|
|
159
|
-
|
|
162
|
+
|
|
160
163
|
// Clear the container and re-render
|
|
161
164
|
container.innerHTML = '';
|
|
162
165
|
renderSingleDiagram(container, newId, code);
|
|
@@ -182,7 +185,7 @@
|
|
|
182
185
|
|
|
183
186
|
// Initialize when DOM is ready
|
|
184
187
|
if (document.readyState === 'loading') {
|
|
185
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
188
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
186
189
|
initializeMermaid();
|
|
187
190
|
setupThemeObserver();
|
|
188
191
|
});
|
|
@@ -192,12 +195,11 @@
|
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
// Handle tab switches - render mermaid in newly visible tabs
|
|
195
|
-
document.addEventListener('click', function(e) {
|
|
198
|
+
document.addEventListener('click', function (e) {
|
|
196
199
|
if (e.target.classList.contains('docmd-tabs-nav-item')) {
|
|
197
200
|
// Wait a bit for tab content to be visible
|
|
198
201
|
setTimeout(renderMermaidDiagrams, 100);
|
|
199
202
|
}
|
|
200
203
|
});
|
|
201
204
|
|
|
202
|
-
})();
|
|
203
|
-
|
|
205
|
+
})();
|
|
@@ -0,0 +1,212 @@
|
|
|
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
|
+
if (!searchModal) return;
|
|
17
|
+
|
|
18
|
+
const emptyStateHtml = '<div class="search-initial">Type to start searching...</div>';
|
|
19
|
+
|
|
20
|
+
// 1. Open/Close Logic
|
|
21
|
+
function openSearch() {
|
|
22
|
+
searchModal.style.display = 'flex';
|
|
23
|
+
searchInput.focus();
|
|
24
|
+
|
|
25
|
+
if (!searchInput.value.trim()) {
|
|
26
|
+
searchResults.innerHTML = emptyStateHtml;
|
|
27
|
+
selectedIndex = -1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!isIndexLoaded) loadIndex();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function closeSearch() {
|
|
34
|
+
searchModal.style.display = 'none';
|
|
35
|
+
selectedIndex = -1; // Reset selection
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Keyboard Navigation & Shortcuts
|
|
39
|
+
document.addEventListener('keydown', (e) => {
|
|
40
|
+
// Open: Cmd+K / Ctrl+K
|
|
41
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
if (searchModal.style.display === 'flex') {
|
|
44
|
+
closeSearch();
|
|
45
|
+
} else {
|
|
46
|
+
openSearch();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Context: Only handle these if search is open
|
|
51
|
+
if (searchModal.style.display === 'flex') {
|
|
52
|
+
const items = searchResults.querySelectorAll('.search-result-item');
|
|
53
|
+
|
|
54
|
+
if (e.key === 'Escape') {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
closeSearch();
|
|
57
|
+
}
|
|
58
|
+
else if (e.key === 'ArrowDown') {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
if (items.length === 0) return;
|
|
61
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
62
|
+
updateSelection(items);
|
|
63
|
+
}
|
|
64
|
+
else if (e.key === 'ArrowUp') {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
if (items.length === 0) return;
|
|
67
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
68
|
+
updateSelection(items);
|
|
69
|
+
}
|
|
70
|
+
else if (e.key === 'Enter') {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
if (selectedIndex >= 0 && items[selectedIndex]) {
|
|
73
|
+
items[selectedIndex].click();
|
|
74
|
+
} else if (items.length > 0) {
|
|
75
|
+
// If nothing selected but results exist, click the first one on Enter
|
|
76
|
+
items[0].click();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function updateSelection(items) {
|
|
83
|
+
items.forEach((item, index) => {
|
|
84
|
+
if (index === selectedIndex) {
|
|
85
|
+
item.classList.add('selected');
|
|
86
|
+
item.scrollIntoView({ block: 'nearest' });
|
|
87
|
+
} else {
|
|
88
|
+
item.classList.remove('selected');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Click handlers
|
|
94
|
+
document.querySelectorAll('.docmd-search-trigger').forEach(btn => {
|
|
95
|
+
btn.addEventListener('click', openSearch);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
searchModal.addEventListener('click', (e) => {
|
|
99
|
+
if (e.target === searchModal) closeSearch();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 3. Index Loading Logic
|
|
103
|
+
async function loadIndex() {
|
|
104
|
+
try {
|
|
105
|
+
const basePath = document.documentElement.getAttribute('data-base-url') || '/';
|
|
106
|
+
const response = await fetch(`${basePath}search-index.json`);
|
|
107
|
+
if (!response.ok) throw new Error(response.status);
|
|
108
|
+
|
|
109
|
+
const jsonString = await response.text();
|
|
110
|
+
|
|
111
|
+
miniSearch = MiniSearch.loadJSON(jsonString, {
|
|
112
|
+
fields: ['title', 'headings', 'text'],
|
|
113
|
+
storeFields: ['title', 'id', 'text'],
|
|
114
|
+
searchOptions: {
|
|
115
|
+
fuzzy: 0.2,
|
|
116
|
+
prefix: true,
|
|
117
|
+
boost: { title: 2, headings: 1.5 }
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
isIndexLoaded = true;
|
|
122
|
+
// console.log('Search index loaded');
|
|
123
|
+
|
|
124
|
+
if (searchInput.value.trim()) {
|
|
125
|
+
searchInput.dispatchEvent(new Event('input'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error('Failed to load search index', e);
|
|
130
|
+
searchResults.innerHTML = '<div class="search-error">Failed to load search index.</div>';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Helper: Snippets (Same as before)
|
|
135
|
+
function getSnippet(text, query) {
|
|
136
|
+
if (!text) return '';
|
|
137
|
+
const terms = query.split(/\s+/).filter(t => t.length > 2);
|
|
138
|
+
const lowerText = text.toLowerCase();
|
|
139
|
+
let bestIndex = -1;
|
|
140
|
+
|
|
141
|
+
for (const term of terms) {
|
|
142
|
+
const idx = lowerText.indexOf(term.toLowerCase());
|
|
143
|
+
if (idx >= 0) { bestIndex = idx; break; }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const contextSize = 60;
|
|
147
|
+
let start = 0;
|
|
148
|
+
let end = 120;
|
|
149
|
+
|
|
150
|
+
if (bestIndex >= 0) {
|
|
151
|
+
start = Math.max(0, bestIndex - contextSize);
|
|
152
|
+
end = Math.min(text.length, bestIndex + contextSize);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let snippet = text.substring(start, end);
|
|
156
|
+
if (start > 0) snippet = '...' + snippet;
|
|
157
|
+
if (end < text.length) snippet = snippet + '...';
|
|
158
|
+
|
|
159
|
+
const safeTerms = terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
160
|
+
if (safeTerms) {
|
|
161
|
+
const highlightRegex = new RegExp(`(${safeTerms})`, 'gi');
|
|
162
|
+
snippet = snippet.replace(highlightRegex, '<mark>$1</mark>');
|
|
163
|
+
}
|
|
164
|
+
return snippet;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 4. Search Execution
|
|
168
|
+
searchInput.addEventListener('input', (e) => {
|
|
169
|
+
const query = e.target.value.trim();
|
|
170
|
+
selectedIndex = -1; // Reset selection on new input
|
|
171
|
+
|
|
172
|
+
if (!query) {
|
|
173
|
+
searchResults.innerHTML = emptyStateHtml;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!isIndexLoaded) return;
|
|
178
|
+
|
|
179
|
+
const results = miniSearch.search(query);
|
|
180
|
+
renderResults(results, query);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
function renderResults(results, query) {
|
|
184
|
+
if (results.length === 0) {
|
|
185
|
+
searchResults.innerHTML = '<div class="search-no-results">No results found.</div>';
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const html = results.slice(0, 10).map((result, index) => {
|
|
190
|
+
const snippet = getSnippet(result.text, query);
|
|
191
|
+
// Add data-index for mouse interaction tracking if needed
|
|
192
|
+
return `
|
|
193
|
+
<a href="/${result.id}" class="search-result-item" data-index="${index}" onclick="window.closeDocmdSearch()">
|
|
194
|
+
<div class="search-result-title">${result.title}</div>
|
|
195
|
+
<div class="search-result-preview">${snippet}</div>
|
|
196
|
+
</a>
|
|
197
|
+
`;
|
|
198
|
+
}).join('');
|
|
199
|
+
|
|
200
|
+
searchResults.innerHTML = html;
|
|
201
|
+
|
|
202
|
+
// Optional: Allow mouse hover to update selectedIndex
|
|
203
|
+
searchResults.querySelectorAll('.search-result-item').forEach((item, idx) => {
|
|
204
|
+
item.addEventListener('mouseenter', () => {
|
|
205
|
+
selectedIndex = idx;
|
|
206
|
+
updateSelection(searchResults.querySelectorAll('.search-result-item'));
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
window.closeDocmdSearch = closeSearch;
|
|
212
|
+
})();
|