@mgks/docmd 0.2.9 → 0.3.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/.github/workflows/{publish.yml → npm-publish.yml} +2 -2
- package/README.md +3 -1
- package/assets/images/preview-dark-welcome.png +0 -0
- package/config.js +133 -165
- package/docs/comparison.md +5 -0
- package/docs/configuration.md +7 -1
- package/docs/content/search.md +68 -0
- package/docs/overview.md +1 -0
- package/package.json +5 -2
- package/src/assets/css/docmd-main.css +262 -20
- package/src/assets/css/docmd-theme-sky.css +5 -1
- package/src/assets/js/docmd-main.js +1 -0
- package/src/assets/js/docmd-mermaid.js +4 -1
- package/src/assets/js/docmd-search.js +218 -0
- package/src/commands/build.js +116 -1
- package/src/commands/init.js +5 -0
- package/src/core/config-validator.js +2 -1
- package/src/core/file-processor.js +12 -1
- package/src/templates/layout.ejs +44 -3
package/src/commands/build.js
CHANGED
|
@@ -14,6 +14,7 @@ const MarkdownIt = require('markdown-it');
|
|
|
14
14
|
const hljs = require('highlight.js');
|
|
15
15
|
const CleanCSS = require('clean-css');
|
|
16
16
|
const esbuild = require('esbuild');
|
|
17
|
+
const MiniSearch = require('minisearch');
|
|
17
18
|
|
|
18
19
|
// Debug function to log navigation information
|
|
19
20
|
function logNavigationPaths(pagePath, navPath, normalizedPath) {
|
|
@@ -64,6 +65,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
64
65
|
const USER_ASSETS_DIR = path.resolve(CWD, 'assets');
|
|
65
66
|
const md = createMarkdownItInstance(config);
|
|
66
67
|
const shouldMinify = !options.isDev && config.minify !== false;
|
|
68
|
+
const searchIndexData = [];
|
|
67
69
|
|
|
68
70
|
if (!await fs.pathExists(SRC_DIR)) {
|
|
69
71
|
throw new Error(`Source directory not found: ${formatPathForDisplay(SRC_DIR, CWD)}`);
|
|
@@ -244,7 +246,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
// Destructure the valid data
|
|
247
|
-
const { frontmatter: pageFrontmatter, htmlContent, headings } = processedData;
|
|
249
|
+
const { frontmatter: pageFrontmatter, htmlContent, headings, searchData } = processedData;
|
|
248
250
|
|
|
249
251
|
const isIndexFile = path.basename(relativePath) === 'index.md';
|
|
250
252
|
|
|
@@ -333,9 +335,27 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
333
335
|
outputPath: sitemapOutputPath.replace(/\\/g, '/'),
|
|
334
336
|
frontmatter: pageFrontmatter
|
|
335
337
|
});
|
|
338
|
+
|
|
339
|
+
// Collect Search Data
|
|
340
|
+
if (searchData) {
|
|
341
|
+
let pageUrl = outputHtmlPath.replace(/\\/g, '/');
|
|
342
|
+
if (pageUrl.endsWith('/index.html')) {
|
|
343
|
+
pageUrl = pageUrl.substring(0, pageUrl.length - 10); // remove index.html
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Add to index array
|
|
347
|
+
searchIndexData.push({
|
|
348
|
+
id: pageUrl, // URL is the ID
|
|
349
|
+
title: searchData.title,
|
|
350
|
+
text: searchData.content,
|
|
351
|
+
headings: searchData.headings.join(' ')
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
336
355
|
} catch (error) {
|
|
337
356
|
console.error(`❌ An unexpected error occurred while processing file ${path.relative(CWD, filePath)}:`, error);
|
|
338
357
|
}
|
|
358
|
+
|
|
339
359
|
}
|
|
340
360
|
|
|
341
361
|
// Generate sitemap if enabled in config
|
|
@@ -347,6 +367,33 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
347
367
|
}
|
|
348
368
|
}
|
|
349
369
|
|
|
370
|
+
// Generate search index if enabled
|
|
371
|
+
if (config.search !== false) {
|
|
372
|
+
console.log('🔍 Generating search index...');
|
|
373
|
+
|
|
374
|
+
// Create MiniSearch instance
|
|
375
|
+
const miniSearch = new MiniSearch({
|
|
376
|
+
fields: ['title', 'headings', 'text'], // fields to index for full-text search
|
|
377
|
+
storeFields: ['title', 'id', 'text'], // fields to return with search results (don't store full text to keep JSON small)
|
|
378
|
+
searchOptions: {
|
|
379
|
+
boost: { title: 2, headings: 1.5 }, // title matches are more important
|
|
380
|
+
fuzzy: 0.2
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Add documents
|
|
385
|
+
miniSearch.addAll(searchIndexData);
|
|
386
|
+
|
|
387
|
+
// Serialize to JSON
|
|
388
|
+
const jsonIndex = JSON.stringify(miniSearch.toJSON());
|
|
389
|
+
const searchIndexPath = path.join(OUTPUT_DIR, 'search-index.json');
|
|
390
|
+
await fs.writeFile(searchIndexPath, jsonIndex);
|
|
391
|
+
|
|
392
|
+
if (!options.isDev) {
|
|
393
|
+
console.log(`✅ Search index generated (${(jsonIndex.length / 1024).toFixed(1)} KB)`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
350
397
|
// Print summary of preserved files at the end of build
|
|
351
398
|
if (preservedFiles.length > 0 && !options.isDev) {
|
|
352
399
|
console.log(`\n📋 Build Summary: ${preservedFiles.length} existing files were preserved:`);
|
|
@@ -364,6 +411,74 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
364
411
|
}
|
|
365
412
|
}
|
|
366
413
|
|
|
414
|
+
// Bundle third-party libraries into assets
|
|
415
|
+
const copyLibrary = async (packageName, fileToBundle, destFileName) => {
|
|
416
|
+
try {
|
|
417
|
+
let srcPath;
|
|
418
|
+
|
|
419
|
+
// 1. Resolve Source Path
|
|
420
|
+
try {
|
|
421
|
+
srcPath = require.resolve(`${packageName}/${fileToBundle}`);
|
|
422
|
+
} catch (e) {
|
|
423
|
+
const mainPath = require.resolve(packageName);
|
|
424
|
+
let currentDir = path.dirname(mainPath);
|
|
425
|
+
let packageRoot = null;
|
|
426
|
+
|
|
427
|
+
for (let i = 0; i < 5; i++) {
|
|
428
|
+
if (await fs.pathExists(path.join(currentDir, 'package.json'))) {
|
|
429
|
+
packageRoot = currentDir;
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
currentDir = path.dirname(currentDir);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (packageRoot) {
|
|
436
|
+
srcPath = path.join(packageRoot, fileToBundle);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 2. Process and Write
|
|
441
|
+
if (srcPath && await fs.pathExists(srcPath)) {
|
|
442
|
+
const destPath = path.join(OUTPUT_DIR, 'assets/js', destFileName);
|
|
443
|
+
|
|
444
|
+
// Read content
|
|
445
|
+
let content = await fs.readFile(srcPath, 'utf8');
|
|
446
|
+
|
|
447
|
+
// This prevents the browser from looking for index.js.map or similar files we didn't copy
|
|
448
|
+
content = content.replace(/\/\/# sourceMappingURL=.*$/gm, '');
|
|
449
|
+
|
|
450
|
+
// Minify if production build
|
|
451
|
+
if (shouldMinify) {
|
|
452
|
+
try {
|
|
453
|
+
const result = await esbuild.transform(content, {
|
|
454
|
+
minify: true,
|
|
455
|
+
loader: 'js',
|
|
456
|
+
target: 'es2015'
|
|
457
|
+
});
|
|
458
|
+
await fs.writeFile(destPath, result.code);
|
|
459
|
+
} catch (minErr) {
|
|
460
|
+
console.warn(`⚠️ Minification failed for ${packageName}, using sanitized original.`, minErr.message);
|
|
461
|
+
await fs.writeFile(destPath, content);
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
// Write sanitized original in dev mode
|
|
465
|
+
await fs.writeFile(destPath, content);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
} else {
|
|
469
|
+
console.warn(`⚠️ Could not locate ${fileToBundle} in ${packageName}`);
|
|
470
|
+
}
|
|
471
|
+
} catch (e) {
|
|
472
|
+
console.warn(`⚠️ Failed to bundle ${packageName}: ${e.message}`);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// Bundle MiniSearch
|
|
477
|
+
await copyLibrary('minisearch', 'dist/umd/index.js', 'minisearch.js');
|
|
478
|
+
|
|
479
|
+
// Bundle Mermaid
|
|
480
|
+
await copyLibrary('mermaid', 'dist/mermaid.min.js', 'mermaid.min.js');
|
|
481
|
+
|
|
367
482
|
return {
|
|
368
483
|
config,
|
|
369
484
|
processedPages,
|
package/src/commands/init.js
CHANGED
|
@@ -23,6 +23,11 @@ module.exports = {
|
|
|
23
23
|
// Directory Configuration
|
|
24
24
|
srcDir: 'docs', // Source directory for Markdown files
|
|
25
25
|
outputDir: 'site', // Directory for generated static site
|
|
26
|
+
|
|
27
|
+
// Search Configuration
|
|
28
|
+
search: true, // Enable/disable search functionality
|
|
29
|
+
|
|
30
|
+
// Build Options
|
|
26
31
|
minify: true, // Enable/disable HTML/CSS/JS minification
|
|
27
32
|
|
|
28
33
|
// Sidebar Configuration
|
|
@@ -6,7 +6,8 @@ const chalk = require('chalk');
|
|
|
6
6
|
const KNOWN_KEYS = [
|
|
7
7
|
'siteTitle', 'siteUrl', 'srcDir', 'outputDir', 'logo',
|
|
8
8
|
'sidebar', 'theme', 'customJs', 'autoTitleFromH1',
|
|
9
|
-
'copyCode', 'plugins', 'navigation', 'footer', 'sponsor', 'favicon'
|
|
9
|
+
'copyCode', 'plugins', 'navigation', 'footer', 'sponsor', 'favicon',
|
|
10
|
+
'search', 'minify', 'editLink', 'pageNavigation'
|
|
10
11
|
];
|
|
11
12
|
|
|
12
13
|
// Common typos mapping
|
|
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const matter = require('gray-matter');
|
|
6
6
|
const { createMarkdownItInstance } = require('./markdown/setup');
|
|
7
|
+
const striptags = require('striptags');
|
|
7
8
|
|
|
8
9
|
function decodeHtmlEntities(html) {
|
|
9
10
|
return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, ' ');
|
|
@@ -56,8 +57,18 @@ async function processMarkdownFile(filePath, md, config) {
|
|
|
56
57
|
htmlContent = md.render(markdownContent);
|
|
57
58
|
headings = extractHeadingsFromHtml(htmlContent);
|
|
58
59
|
}
|
|
60
|
+
|
|
61
|
+
let searchData = null;
|
|
62
|
+
if (!frontmatter.noindex) {
|
|
63
|
+
const rawText = decodeHtmlEntities(striptags(htmlContent));
|
|
64
|
+
searchData = {
|
|
65
|
+
title: frontmatter.title || 'Untitled',
|
|
66
|
+
content: rawText.slice(0, 5000), // Safety cap to prevent massive JSON
|
|
67
|
+
headings: headings.map(h => h.text)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
59
70
|
|
|
60
|
-
return { frontmatter, htmlContent, headings };
|
|
71
|
+
return { frontmatter, htmlContent, headings, searchData };
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
async function findMarkdownFiles(dir) {
|
package/src/templates/layout.ejs
CHANGED
|
@@ -66,6 +66,15 @@
|
|
|
66
66
|
</div>
|
|
67
67
|
<% if (theme && theme.enableModeToggle && theme.positionMode === 'top') { %>
|
|
68
68
|
<div class="header-right">
|
|
69
|
+
<% if (config.search !== false) { %>
|
|
70
|
+
<button class="docmd-search-trigger" aria-label="Search">
|
|
71
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><path d="m21 21-4.34-4.34"></path><circle cx="11" cy="11" r="8"></circle></svg>
|
|
72
|
+
<span class="search-label">Search</span>
|
|
73
|
+
<span class="search-keys">
|
|
74
|
+
<kbd class="docmd-kbd">⌘</kbd><kbd class="docmd-kbd">k</kbd>
|
|
75
|
+
</span>
|
|
76
|
+
</button>
|
|
77
|
+
<% } %>
|
|
69
78
|
<button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button theme-toggle-header">
|
|
70
79
|
<%# renderIcon is available in the global EJS scope from html-generator %>
|
|
71
80
|
<%- renderIcon('sun', { class: 'icon-sun' }) %>
|
|
@@ -137,16 +146,48 @@
|
|
|
137
146
|
<%- footerHtml || '' %>
|
|
138
147
|
</div>
|
|
139
148
|
<div class="branding-footer">
|
|
140
|
-
Build with <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"></path><path d="M12 5 9.04 7.96a2.17 2.17 0 0 0 0 3.08c.82.82 2.13.85 3 .07l2.07-1.9a2.82 2.82 0 0 1 3.79 0l2.96 2.66"></path><path d="m18 15-2-2"></path><path d="m15 18-2-2"></path></svg> <a href="https://
|
|
149
|
+
Build with <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"></path><path d="M12 5 9.04 7.96a2.17 2.17 0 0 0 0 3.08c.82.82 2.13.85 3 .07l2.07-1.9a2.82 2.82 0 0 1 3.79 0l2.96 2.66"></path><path d="m18 15-2-2"></path><path d="m15 18-2-2"></path></svg> <a href="https://github.com/mgks/docmd" target="_blank" rel="noopener">docmd.</a>
|
|
141
150
|
</div>
|
|
142
151
|
</div>
|
|
143
152
|
</footer>
|
|
144
153
|
</div>
|
|
145
154
|
|
|
155
|
+
<% if (config.search !== false) { %>
|
|
156
|
+
<!-- Search Modal -->
|
|
157
|
+
<div id="docmd-search-modal" class="docmd-search-modal" style="display: none;">
|
|
158
|
+
<div class="docmd-search-box">
|
|
159
|
+
<div class="docmd-search-header">
|
|
160
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1.25em" height="1.25em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><path d="m21 21-4.34-4.34"></path><circle cx="11" cy="11" r="8"></circle></svg>
|
|
161
|
+
<input type="text" id="docmd-search-input" placeholder="Search documentation..." autocomplete="off" spellcheck="false">
|
|
162
|
+
<button onclick="window.closeDocmdSearch()" class="docmd-search-close" aria-label="Close search">
|
|
163
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
<div id="docmd-search-results" class="docmd-search-results">
|
|
167
|
+
<!-- Results injected here -->
|
|
168
|
+
</div>
|
|
169
|
+
<div class="docmd-search-footer">
|
|
170
|
+
<span><kbd class="docmd-kbd">↑</kbd> <kbd class="docmd-kbd">↓</kbd> to navigate</span>
|
|
171
|
+
<span><kbd class="docmd-kbd">ESC</kbd> to close</span>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<% } %>
|
|
176
|
+
|
|
177
|
+
<script>
|
|
178
|
+
window.DOCMD_ROOT = "<%= relativePathToRoot %>";
|
|
179
|
+
</script>
|
|
180
|
+
|
|
146
181
|
<script src="<%= relativePathToRoot %>assets/js/docmd-main.js"></script>
|
|
147
|
-
|
|
182
|
+
|
|
183
|
+
<% if (config.search !== false) { %>
|
|
184
|
+
<!-- Search Scripts -->
|
|
185
|
+
<script src="<%= relativePathToRoot %>assets/js/minisearch.js"></script>
|
|
186
|
+
<script src="<%= relativePathToRoot %>assets/js/docmd-search.js"></script>
|
|
187
|
+
<% } %>
|
|
188
|
+
|
|
148
189
|
<!-- Mermaid.js for diagram rendering -->
|
|
149
|
-
<script src="
|
|
190
|
+
<script src="<%= relativePathToRoot %>assets/js/mermaid.min.js"></script>
|
|
150
191
|
<script src="<%= relativePathToRoot %>assets/js/docmd-mermaid.js"></script>
|
|
151
192
|
|
|
152
193
|
<% (customJsFiles || []).forEach(jsFile => { %>
|