@mgks/docmd 0.2.5 → 0.2.7
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/CODE_OF_CONDUCT.md +48 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +27 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +16 -0
- package/.github/SECURITY.md +18 -0
- package/.github/workflows/publish.yml +19 -56
- package/README.md +82 -44
- package/bin/docmd.js +7 -2
- package/bin/postinstall.js +14 -0
- package/config.js +13 -1
- package/docs/comparison.md +51 -0
- package/docs/overview.md +7 -1
- package/docs/recipes/custom-fonts.md +43 -0
- package/docs/recipes/favicon.md +38 -0
- package/docs/recipes/index.md +12 -0
- package/docs/recipes/landing-page.md +46 -0
- package/package.json +4 -1
- package/src/assets/css/docmd-main.css +1151 -3
- package/src/assets/js/docmd-main.js +17 -1
- package/src/commands/build.js +15 -64
- package/src/core/config-loader.js +9 -3
- package/src/core/config-validator.js +79 -0
- package/src/core/file-processor.js +9 -1
- package/src/core/html-generator.js +1 -1
- package/src/core/logger.js +21 -0
- package/src/core/navigation-helper.js +62 -0
|
@@ -36,6 +36,22 @@ function initializeCollapsibleNav() {
|
|
|
36
36
|
toggleSubmenu(isExpanded);
|
|
37
37
|
|
|
38
38
|
anchor.addEventListener('click', (e) => {
|
|
39
|
+
const currentExpanded = item.getAttribute('aria-expanded') === 'true';
|
|
40
|
+
const href = anchor.getAttribute('href');
|
|
41
|
+
const isPlaceholder = !href || href === '#' || href === '';
|
|
42
|
+
|
|
43
|
+
if (!currentExpanded) {
|
|
44
|
+
toggleSubmenu(true);
|
|
45
|
+
} else if (isPlaceholder || e.target.closest('.collapse-icon')) {
|
|
46
|
+
toggleSubmenu(false);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isPlaceholder || e.target.closest('.collapse-icon')) {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/* anchor.addEventListener('click', (e) => {
|
|
39
55
|
// If the click target is the icon, ALWAYS prevent navigation and toggle.
|
|
40
56
|
if (e.target.closest('.collapse-icon')) {
|
|
41
57
|
e.preventDefault();
|
|
@@ -47,7 +63,7 @@ function initializeCollapsibleNav() {
|
|
|
47
63
|
toggleSubmenu(item.getAttribute('aria-expanded') !== 'true');
|
|
48
64
|
}
|
|
49
65
|
// Otherwise, let the click proceed to navigate to the link.
|
|
50
|
-
})
|
|
66
|
+
});*/
|
|
51
67
|
});
|
|
52
68
|
}
|
|
53
69
|
|
package/src/commands/build.js
CHANGED
|
@@ -6,6 +6,7 @@ const { loadConfig } = require('../core/config-loader');
|
|
|
6
6
|
const { createMarkdownItInstance, processMarkdownFile, findMarkdownFiles } = require('../core/file-processor');
|
|
7
7
|
const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
|
|
8
8
|
const { renderIcon, clearWarnedIcons } = require('../core/icon-renderer');
|
|
9
|
+
const { findPageNeighbors } = require('../core/navigation-helper');
|
|
9
10
|
const { generateSitemap } = require('../plugins/sitemap');
|
|
10
11
|
const { version } = require('../../package.json');
|
|
11
12
|
const matter = require('gray-matter');
|
|
@@ -218,8 +219,12 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
218
219
|
|
|
219
220
|
const finalOutputHtmlPath = path.join(OUTPUT_DIR, outputHtmlPath);
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
let relativePathToRoot = path.relative(path.dirname(finalOutputHtmlPath), OUTPUT_DIR);
|
|
223
|
+
if (relativePathToRoot === '') {
|
|
224
|
+
relativePathToRoot = './';
|
|
225
|
+
} else {
|
|
226
|
+
relativePathToRoot = relativePathToRoot.replace(/\\/g, '/') + '/';
|
|
227
|
+
}
|
|
223
228
|
|
|
224
229
|
let normalizedPath = path.relative(SRC_DIR, filePath).replace(/\\/g, '/');
|
|
225
230
|
if (path.basename(normalizedPath) === 'index.md') {
|
|
@@ -247,74 +252,20 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
247
252
|
config
|
|
248
253
|
);
|
|
249
254
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const flatNavigation = [];
|
|
255
|
-
|
|
256
|
-
function createNormalizedPath(item) {
|
|
257
|
-
if (!item.path) return null;
|
|
258
|
-
return item.path.startsWith('/') ? item.path : '/' + item.path;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function extractNavigationItems(items) {
|
|
262
|
-
if (!items || !Array.isArray(items)) return;
|
|
263
|
-
|
|
264
|
-
for (const item of items) {
|
|
265
|
-
if (item.external) continue;
|
|
266
|
-
|
|
267
|
-
if (item.path) {
|
|
268
|
-
let normalizedItemPath = createNormalizedPath(item);
|
|
269
|
-
if (item.children && !normalizedItemPath.endsWith('/')) {
|
|
270
|
-
normalizedItemPath += '/';
|
|
271
|
-
}
|
|
272
|
-
flatNavigation.push({
|
|
273
|
-
title: item.title,
|
|
274
|
-
path: normalizedItemPath,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (item.children && Array.isArray(item.children)) {
|
|
279
|
-
extractNavigationItems(item.children);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
extractNavigationItems(config.navigation);
|
|
285
|
-
|
|
286
|
-
currentPageIndex = flatNavigation.findIndex(item => {
|
|
287
|
-
const itemPath = item.path;
|
|
288
|
-
const currentPagePath = normalizedPath;
|
|
289
|
-
if (itemPath === currentPagePath) {
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
if (itemPath.endsWith('/') && itemPath.slice(0, -1) === currentPagePath) {
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
if (currentPagePath.endsWith('/') && currentPagePath.slice(0, -1) === itemPath) {
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
if (currentPageIndex >= 0) {
|
|
302
|
-
if (currentPageIndex > 0) prevPage = flatNavigation[currentPageIndex - 1];
|
|
303
|
-
if (currentPageIndex < flatNavigation.length - 1) nextPage = flatNavigation[currentPageIndex + 1];
|
|
304
|
-
}
|
|
305
|
-
|
|
255
|
+
// Find previous and next pages for navigation
|
|
256
|
+
const { prevPage, nextPage } = findPageNeighbors(config.navigation, normalizedPath);
|
|
257
|
+
|
|
306
258
|
if (prevPage) {
|
|
307
|
-
const cleanPath = prevPage.path.
|
|
308
|
-
prevPage.url = relativePathToRoot +
|
|
309
|
-
if (prevPage.path === '/') prevPage.url = relativePathToRoot;
|
|
259
|
+
const cleanPath = prevPage.path.substring(1);
|
|
260
|
+
prevPage.url = relativePathToRoot + cleanPath;
|
|
310
261
|
}
|
|
311
262
|
|
|
312
263
|
if (nextPage) {
|
|
313
|
-
const cleanPath = nextPage.path.
|
|
314
|
-
nextPage.url = relativePathToRoot +
|
|
315
|
-
if (nextPage.path === '/') nextPage.url = relativePathToRoot;
|
|
264
|
+
const cleanPath = nextPage.path.substring(1);
|
|
265
|
+
nextPage.url = relativePathToRoot + cleanPath;
|
|
316
266
|
}
|
|
317
267
|
|
|
268
|
+
// Log navigation paths for debugging
|
|
318
269
|
const pageDataForTemplate = {
|
|
319
270
|
content: htmlContent,
|
|
320
271
|
pageTitle: pageFrontmatter.title || 'Untitled',
|
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
|
+
const { validateConfig } = require('./config-validator');
|
|
5
6
|
|
|
6
7
|
async function loadConfig(configPath) {
|
|
7
8
|
const absoluteConfigPath = path.resolve(process.cwd(), configPath);
|
|
8
9
|
if (!await fs.pathExists(absoluteConfigPath)) {
|
|
9
|
-
throw new Error(`Configuration file not found: ${absoluteConfigPath}
|
|
10
|
+
throw new Error(`Configuration file not found at: ${absoluteConfigPath}\nRun "docmd init" to create one.`);
|
|
10
11
|
}
|
|
11
12
|
try {
|
|
12
13
|
// Clear require cache to always get the freshest config
|
|
13
14
|
delete require.cache[require.resolve(absoluteConfigPath)];
|
|
14
15
|
const config = require(absoluteConfigPath);
|
|
15
16
|
|
|
17
|
+
// Validate configuration call
|
|
18
|
+
validateConfig(config);
|
|
19
|
+
|
|
16
20
|
// Basic validation and defaults
|
|
17
|
-
if (!config.siteTitle) throw new Error('`siteTitle` is missing in config file');
|
|
18
21
|
config.srcDir = config.srcDir || 'docs';
|
|
19
22
|
config.outputDir = config.outputDir || 'site';
|
|
20
23
|
config.theme = config.theme || {};
|
|
@@ -24,7 +27,10 @@ async function loadConfig(configPath) {
|
|
|
24
27
|
|
|
25
28
|
return config;
|
|
26
29
|
} catch (e) {
|
|
27
|
-
|
|
30
|
+
if (e.message === 'Invalid configuration file.') {
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Error parsing config file: ${e.message}`);
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
// Known configuration keys for typo detection
|
|
6
|
+
const KNOWN_KEYS = [
|
|
7
|
+
'siteTitle', 'siteUrl', 'srcDir', 'outputDir', 'logo',
|
|
8
|
+
'sidebar', 'theme', 'customJs', 'autoTitleFromH1',
|
|
9
|
+
'copyCode', 'plugins', 'navigation', 'footer', 'sponsor', 'favicon'
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
// Common typos mapping
|
|
13
|
+
const TYPO_MAPPING = {
|
|
14
|
+
'site_title': 'siteTitle',
|
|
15
|
+
'sitetitle': 'siteTitle',
|
|
16
|
+
'baseUrl': 'siteUrl',
|
|
17
|
+
'source': 'srcDir',
|
|
18
|
+
'out': 'outputDir',
|
|
19
|
+
'customCSS': 'theme.customCss',
|
|
20
|
+
'customcss': 'theme.customCss',
|
|
21
|
+
'customJS': 'customJs',
|
|
22
|
+
'customjs': 'customJs',
|
|
23
|
+
'nav': 'navigation',
|
|
24
|
+
'menu': 'navigation'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function validateConfig(config) {
|
|
28
|
+
const errors = [];
|
|
29
|
+
const warnings = [];
|
|
30
|
+
|
|
31
|
+
// 1. Required Fields
|
|
32
|
+
if (!config.siteTitle) {
|
|
33
|
+
errors.push('Missing required property: "siteTitle"');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Type Checking
|
|
37
|
+
if (config.navigation && !Array.isArray(config.navigation)) {
|
|
38
|
+
errors.push('"navigation" must be an Array');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (config.customJs && !Array.isArray(config.customJs)) {
|
|
42
|
+
errors.push('"customJs" must be an Array of strings');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (config.theme) {
|
|
46
|
+
if (config.theme.customCss && !Array.isArray(config.theme.customCss)) {
|
|
47
|
+
errors.push('"theme.customCss" must be an Array of strings');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Typos and Unknown Keys (Top Level)
|
|
52
|
+
Object.keys(config).forEach(key => {
|
|
53
|
+
if (TYPO_MAPPING[key]) {
|
|
54
|
+
warnings.push(`Found unknown property "${key}". Did you mean "${TYPO_MAPPING[key]}"?`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 4. Theme specific typos
|
|
59
|
+
if (config.theme) {
|
|
60
|
+
if (config.theme.customCSS) {
|
|
61
|
+
warnings.push('Found "theme.customCSS". Did you mean "theme.customCss"?');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Output results
|
|
66
|
+
if (warnings.length > 0) {
|
|
67
|
+
console.log(chalk.yellow('\n⚠️ Configuration Warnings:'));
|
|
68
|
+
warnings.forEach(w => console.log(chalk.yellow(` - ${w}`)));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (errors.length > 0) {
|
|
72
|
+
console.log(chalk.red('\n❌ Configuration Errors:'));
|
|
73
|
+
errors.forEach(e => console.log(chalk.red(` - ${e}`)));
|
|
74
|
+
console.log('');
|
|
75
|
+
throw new Error('Invalid configuration file.');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { validateConfig };
|
|
@@ -88,7 +88,15 @@ function createMarkdownItInstance(config) {
|
|
|
88
88
|
md.renderer.rules.ordered_list_open = customOrderedListOpenRenderer;
|
|
89
89
|
md.renderer.rules.list_item_open = customListItemOpenRenderer;
|
|
90
90
|
md.renderer.rules.image = customImageRenderer;
|
|
91
|
-
|
|
91
|
+
|
|
92
|
+
// Wrap tables in a container for horizontal scrolling
|
|
93
|
+
md.renderer.rules.table_open = function(tokens, idx, options, env, self) {
|
|
94
|
+
return '<div class="table-wrapper">' + self.renderToken(tokens, idx, options);
|
|
95
|
+
};
|
|
96
|
+
md.renderer.rules.table_close = function(tokens, idx, options, env, self) {
|
|
97
|
+
return self.renderToken(tokens, idx, options) + '</div>';
|
|
98
|
+
};
|
|
99
|
+
|
|
92
100
|
// Register tabs renderers
|
|
93
101
|
md.renderer.rules.tabs_open = tabsOpenRenderer;
|
|
94
102
|
md.renderer.rules.tabs_nav_open = tabsNavOpenRenderer;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const { version } = require('../../package.json');
|
|
6
|
+
|
|
7
|
+
const printBanner = () => {
|
|
8
|
+
const logo = `
|
|
9
|
+
|
|
10
|
+
${chalk.blue(' _ _ ')}
|
|
11
|
+
${chalk.blue(' _| |___ ___ _____ _| |')}
|
|
12
|
+
${chalk.blue(' | . | . | _| | . |')}
|
|
13
|
+
${chalk.blue(' |___|___|___|_|_|_|___|')}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
console.log(logo);
|
|
17
|
+
console.log(` ${chalk.dim(`v${version}`)}`);
|
|
18
|
+
console.log(`\n`);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = { printBanner };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Flattens the navigation tree and finds the previous and next pages relative to the current page.
|
|
5
|
+
* @param {Array} navItems - The navigation array from config.
|
|
6
|
+
* @param {string} currentPagePath - The normalized path of the current page.
|
|
7
|
+
* @returns {{prevPage: object|null, nextPage: object|null}}
|
|
8
|
+
*/
|
|
9
|
+
function findPageNeighbors(navItems, currentPagePath) {
|
|
10
|
+
const flatNavigation = [];
|
|
11
|
+
|
|
12
|
+
// Recursive function to flatten the navigation tree
|
|
13
|
+
function extractNavigationItems(items) {
|
|
14
|
+
if (!items || !Array.isArray(items)) return;
|
|
15
|
+
|
|
16
|
+
for (const item of items) {
|
|
17
|
+
if (item.external || !item.path || item.path === '#') {
|
|
18
|
+
// If it's a category with no path but has children, recurse into them
|
|
19
|
+
if (item.children && Array.isArray(item.children)) {
|
|
20
|
+
extractNavigationItems(item.children);
|
|
21
|
+
}
|
|
22
|
+
continue; // Skip external links and parent items without a direct path
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let normalizedItemPath = item.path;
|
|
26
|
+
|
|
27
|
+
// Ensure it starts with a slash
|
|
28
|
+
if (!normalizedItemPath.startsWith('/')) {
|
|
29
|
+
normalizedItemPath = '/' + normalizedItemPath;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Ensure it ends with a slash (unless it's the root path)
|
|
33
|
+
if (normalizedItemPath.length > 1 && !normalizedItemPath.endsWith('/')) {
|
|
34
|
+
normalizedItemPath += '/';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
flatNavigation.push({
|
|
38
|
+
title: item.title,
|
|
39
|
+
path: normalizedItemPath,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (item.children && Array.isArray(item.children)) {
|
|
43
|
+
extractNavigationItems(item.children);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
extractNavigationItems(navItems);
|
|
49
|
+
|
|
50
|
+
const currentPageIndex = flatNavigation.findIndex(item => item.path === currentPagePath);
|
|
51
|
+
|
|
52
|
+
if (currentPageIndex === -1) {
|
|
53
|
+
return { prevPage: null, nextPage: null };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const prevPage = currentPageIndex > 0 ? flatNavigation[currentPageIndex - 1] : null;
|
|
57
|
+
const nextPage = currentPageIndex < flatNavigation.length - 1 ? flatNavigation[currentPageIndex + 1] : null;
|
|
58
|
+
|
|
59
|
+
return { prevPage, nextPage };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { findPageNeighbors };
|