@mgks/docmd 0.2.0 → 0.2.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/assets/css/welcome.css +6 -66
- package/config.js +10 -5
- package/docs/configuration.md +11 -5
- package/docs/content/custom-containers.md +24 -0
- package/docs/content/no-style-example.md +2 -0
- package/docs/content/no-style-pages.md +52 -28
- package/docs/index.md +49 -18
- package/docs/plugins/seo.md +80 -33
- package/package.json +13 -7
- package/src/assets/css/docmd-main.css +5 -1167
- package/src/assets/css/docmd-theme-retro.css +3 -806
- package/src/assets/css/docmd-theme-ruby.css +7 -617
- package/src/assets/css/docmd-theme-sky.css +7 -650
- package/src/assets/js/docmd-image-lightbox.js +5 -1
- package/src/assets/js/docmd-main.js +89 -29
- package/src/commands/build.js +62 -120
- package/src/commands/dev.js +2 -1
- package/src/commands/init.js +4 -0
- package/src/core/config-loader.js +2 -0
- package/src/core/file-processor.js +130 -97
- package/src/core/html-generator.js +31 -12
- package/src/core/icon-renderer.js +3 -2
- package/src/plugins/analytics.js +5 -1
- package/src/plugins/seo.js +114 -66
- package/src/plugins/sitemap.js +6 -0
- package/src/templates/layout.ejs +8 -2
- package/src/templates/no-style.ejs +23 -6
- package/src/templates/partials/theme-init.js +26 -0
|
@@ -1,52 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* 1. Light/Dark theme toggling and persistence.
|
|
6
|
-
* 2. Sidebar expand/collapse functionality and persistence.
|
|
7
|
-
* 3. Tabs container interaction.
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Main client-side script for docmd UI interactions
|
|
8
5
|
*/
|
|
9
6
|
|
|
10
|
-
// ---
|
|
11
|
-
function
|
|
7
|
+
// --- Theme Toggle Logic ---
|
|
8
|
+
function setupThemeToggleListener() {
|
|
12
9
|
const themeToggleButton = document.getElementById('theme-toggle-button');
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
function applyTheme(theme) {
|
|
12
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
16
13
|
document.body.setAttribute('data-theme', theme);
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
localStorage.setItem('docmd-theme', theme);
|
|
15
|
+
|
|
16
|
+
// Switch highlight.js theme
|
|
17
|
+
const highlightThemeLink = document.getElementById('highlight-theme');
|
|
18
|
+
if (highlightThemeLink) {
|
|
19
|
+
const newHref = highlightThemeLink.getAttribute('data-base-href') + `docmd-highlight-${theme}.css`;
|
|
20
|
+
highlightThemeLink.setAttribute('href', newHref);
|
|
19
21
|
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Set the initial theme on page load
|
|
23
|
-
const storedTheme = localStorage.getItem('docmd-theme');
|
|
24
|
-
const initialTheme = storedTheme || document.body.getAttribute('data-theme') || 'light';
|
|
25
|
-
applyTheme(initialTheme, true);
|
|
22
|
+
}
|
|
26
23
|
|
|
27
24
|
// Add click listener to the toggle button
|
|
28
25
|
if (themeToggleButton) {
|
|
29
26
|
themeToggleButton.addEventListener('click', () => {
|
|
30
|
-
const currentTheme = document.
|
|
31
|
-
const newTheme = currentTheme
|
|
32
|
-
? currentTheme.replace('dark', 'light')
|
|
33
|
-
: currentTheme.replace('light', 'dark');
|
|
27
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
28
|
+
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
34
29
|
applyTheme(newTheme);
|
|
35
30
|
});
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
33
|
|
|
39
|
-
// ---
|
|
34
|
+
// --- Sidebar Collapse Logic ---
|
|
40
35
|
function initializeSidebarToggle() {
|
|
41
36
|
const toggleButton = document.getElementById('sidebar-toggle-button');
|
|
42
37
|
const body = document.body;
|
|
43
38
|
|
|
44
|
-
// Only run if the sidebar is configured to be collapsible
|
|
45
39
|
if (!body.classList.contains('sidebar-collapsible') || !toggleButton) {
|
|
46
40
|
return;
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
// Set initial state from localStorage or config default
|
|
50
43
|
const defaultConfigCollapsed = body.dataset.defaultCollapsed === 'true';
|
|
51
44
|
let isCollapsed = localStorage.getItem('docmd-sidebar-collapsed');
|
|
52
45
|
|
|
@@ -60,7 +53,6 @@ function initializeSidebarToggle() {
|
|
|
60
53
|
body.classList.add('sidebar-collapsed');
|
|
61
54
|
}
|
|
62
55
|
|
|
63
|
-
// Add click listener to the toggle button
|
|
64
56
|
toggleButton.addEventListener('click', () => {
|
|
65
57
|
body.classList.toggle('sidebar-collapsed');
|
|
66
58
|
const currentlyCollapsed = body.classList.contains('sidebar-collapsed');
|
|
@@ -68,7 +60,7 @@ function initializeSidebarToggle() {
|
|
|
68
60
|
});
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
// ---
|
|
63
|
+
// --- Tabs Container Logic ---
|
|
72
64
|
function initializeTabs() {
|
|
73
65
|
document.querySelectorAll('.docmd-tabs').forEach(tabsContainer => {
|
|
74
66
|
const navItems = tabsContainer.querySelectorAll('.docmd-tabs-nav-item');
|
|
@@ -88,10 +80,78 @@ function initializeTabs() {
|
|
|
88
80
|
});
|
|
89
81
|
}
|
|
90
82
|
|
|
83
|
+
// --- Copy Code Button Logic ---
|
|
84
|
+
function initializeCopyCodeButtons() {
|
|
85
|
+
if (document.body.dataset.copyCodeEnabled !== 'true') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
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>`;
|
|
90
|
+
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>`;
|
|
91
|
+
|
|
92
|
+
document.querySelectorAll('pre').forEach(preElement => {
|
|
93
|
+
const codeElement = preElement.querySelector('code');
|
|
94
|
+
if (!codeElement) return;
|
|
95
|
+
|
|
96
|
+
// Create a wrapper div around the pre element
|
|
97
|
+
const wrapper = document.createElement('div');
|
|
98
|
+
wrapper.style.position = 'relative';
|
|
99
|
+
wrapper.style.display = 'block';
|
|
100
|
+
|
|
101
|
+
// Insert the wrapper before the pre element
|
|
102
|
+
preElement.parentNode.insertBefore(wrapper, preElement);
|
|
103
|
+
|
|
104
|
+
// Move the pre element into the wrapper
|
|
105
|
+
wrapper.appendChild(preElement);
|
|
106
|
+
|
|
107
|
+
// Remove the relative positioning from pre since wrapper handles it
|
|
108
|
+
preElement.style.position = 'static';
|
|
109
|
+
|
|
110
|
+
const copyButton = document.createElement('button');
|
|
111
|
+
copyButton.className = 'copy-code-button';
|
|
112
|
+
copyButton.innerHTML = copyIconSvg;
|
|
113
|
+
copyButton.setAttribute('aria-label', 'Copy code to clipboard');
|
|
114
|
+
wrapper.appendChild(copyButton);
|
|
115
|
+
|
|
116
|
+
copyButton.addEventListener('click', () => {
|
|
117
|
+
navigator.clipboard.writeText(codeElement.innerText).then(() => {
|
|
118
|
+
copyButton.innerHTML = checkIconSvg;
|
|
119
|
+
copyButton.classList.add('copied');
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
copyButton.innerHTML = copyIconSvg;
|
|
122
|
+
copyButton.classList.remove('copied');
|
|
123
|
+
}, 2000);
|
|
124
|
+
}).catch(err => {
|
|
125
|
+
console.error('Failed to copy text: ', err);
|
|
126
|
+
copyButton.innerText = 'Error';
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- Theme Sync Function ---
|
|
133
|
+
function syncBodyTheme() {
|
|
134
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
135
|
+
if (currentTheme && document.body) {
|
|
136
|
+
document.body.setAttribute('data-theme', currentTheme);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Also ensure highlight CSS matches the current theme
|
|
140
|
+
const highlightThemeLink = document.getElementById('highlight-theme');
|
|
141
|
+
if (highlightThemeLink && currentTheme) {
|
|
142
|
+
const baseHref = highlightThemeLink.getAttribute('data-base-href');
|
|
143
|
+
if (baseHref) {
|
|
144
|
+
const newHref = baseHref + `docmd-highlight-${currentTheme}.css`;
|
|
145
|
+
highlightThemeLink.setAttribute('href', newHref);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
91
149
|
|
|
92
150
|
// --- Main Execution ---
|
|
93
151
|
document.addEventListener('DOMContentLoaded', () => {
|
|
94
|
-
|
|
152
|
+
syncBodyTheme(); // Sync body theme with html theme
|
|
153
|
+
setupThemeToggleListener();
|
|
95
154
|
initializeSidebarToggle();
|
|
96
155
|
initializeTabs();
|
|
156
|
+
initializeCopyCodeButtons();
|
|
97
157
|
});
|
package/src/commands/build.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
2
3
|
const fs = require('fs-extra');
|
|
3
4
|
const path = require('path');
|
|
4
5
|
const { loadConfig } = require('../core/config-loader');
|
|
5
|
-
const { processMarkdownFile, findMarkdownFiles } = require('../core/file-processor');
|
|
6
|
+
const { createMarkdownItInstance, processMarkdownFile, findMarkdownFiles } = require('../core/file-processor');
|
|
6
7
|
const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
|
|
7
|
-
const { renderIcon, clearWarnedIcons } = require('../core/icon-renderer');
|
|
8
|
-
const { generateSitemap } = require('../plugins/sitemap');
|
|
9
|
-
const { version } = require('../../package.json');
|
|
10
|
-
const matter = require('gray-matter');
|
|
8
|
+
const { renderIcon, clearWarnedIcons } = require('../core/icon-renderer');
|
|
9
|
+
const { generateSitemap } = require('../plugins/sitemap');
|
|
10
|
+
const { version } = require('../../package.json');
|
|
11
|
+
const matter = require('gray-matter');
|
|
12
|
+
const MarkdownIt = require('markdown-it');
|
|
13
|
+
const hljs = require('highlight.js');
|
|
11
14
|
|
|
12
15
|
// Debug function to log navigation information
|
|
13
16
|
function logNavigationPaths(pagePath, navPath, normalizedPath) {
|
|
@@ -55,7 +58,8 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
55
58
|
const CWD = process.cwd();
|
|
56
59
|
const SRC_DIR = path.resolve(CWD, config.srcDir);
|
|
57
60
|
const OUTPUT_DIR = path.resolve(CWD, config.outputDir);
|
|
58
|
-
const USER_ASSETS_DIR = path.resolve(CWD, 'assets');
|
|
61
|
+
const USER_ASSETS_DIR = path.resolve(CWD, 'assets');
|
|
62
|
+
const md = createMarkdownItInstance(config);
|
|
59
63
|
|
|
60
64
|
if (!await fs.pathExists(SRC_DIR)) {
|
|
61
65
|
throw new Error(`Source directory not found: ${formatPathForDisplay(SRC_DIR, CWD)}`);
|
|
@@ -171,38 +175,44 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
171
175
|
// Array to collect information about all processed pages for sitemap
|
|
172
176
|
const processedPages = [];
|
|
173
177
|
|
|
178
|
+
// Set to track processed files for dev mode
|
|
179
|
+
const processedFiles = new Set();
|
|
180
|
+
|
|
174
181
|
// Find all Markdown files in the source directory
|
|
175
182
|
const markdownFiles = await findMarkdownFiles(SRC_DIR);
|
|
176
183
|
if (!options.isDev) {
|
|
177
184
|
console.log(`📄 Found ${markdownFiles.length} markdown files.`);
|
|
178
185
|
}
|
|
179
|
-
|
|
186
|
+
|
|
180
187
|
// Process each Markdown file
|
|
181
|
-
const processedFiles = new Set(); // Track processed files to avoid double processing
|
|
182
|
-
|
|
183
188
|
for (const filePath of markdownFiles) {
|
|
184
189
|
try {
|
|
185
|
-
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
186
|
-
const { data: frontmatter, content } = matter(fileContent);
|
|
187
|
-
|
|
188
|
-
// Skip this file if it's already been processed and noDoubleProcessing is true
|
|
189
190
|
const relativePath = path.relative(SRC_DIR, filePath);
|
|
191
|
+
|
|
192
|
+
// Skip file if already processed in this dev build cycle
|
|
190
193
|
if (options.noDoubleProcessing && processedFiles.has(relativePath)) {
|
|
191
194
|
continue;
|
|
192
195
|
}
|
|
193
196
|
processedFiles.add(relativePath);
|
|
197
|
+
|
|
198
|
+
// Pass the md instance to the processor
|
|
199
|
+
const processedData = await processMarkdownFile(filePath, md, config);
|
|
200
|
+
|
|
201
|
+
// If processing failed (e.g., bad frontmatter), skip this file.
|
|
202
|
+
if (!processedData) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
194
205
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const isIndexFile =
|
|
206
|
+
// Destructure the valid data
|
|
207
|
+
const { frontmatter: pageFrontmatter, htmlContent, headings } = processedData;
|
|
208
|
+
|
|
209
|
+
const isIndexFile = path.basename(relativePath) === 'index.md';
|
|
199
210
|
|
|
211
|
+
let outputHtmlPath;
|
|
200
212
|
if (isIndexFile) {
|
|
201
|
-
// For any index.md file (in root or subfolder), convert to index.html in the same folder
|
|
202
213
|
const dirPath = path.dirname(relativePath);
|
|
203
214
|
outputHtmlPath = path.join(dirPath, 'index.html');
|
|
204
215
|
} else {
|
|
205
|
-
// For non-index files, create a folder with index.html
|
|
206
216
|
outputHtmlPath = relativePath.replace(/\.md$/, '/index.html');
|
|
207
217
|
}
|
|
208
218
|
|
|
@@ -211,51 +221,26 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
211
221
|
const depth = outputHtmlPath.split(path.sep).length - 1;
|
|
212
222
|
const relativePathToRoot = depth > 0 ? '../'.repeat(depth) : './';
|
|
213
223
|
|
|
214
|
-
const { frontmatter: pageFrontmatter, htmlContent, headings } = await processMarkdownFile(filePath, { isDev: options.isDev }, config);
|
|
215
|
-
|
|
216
|
-
// Special handling for no-style pages
|
|
217
|
-
let finalHtmlContent = htmlContent;
|
|
218
|
-
if (pageFrontmatter.noStyle === true) {
|
|
219
|
-
// For no-style pages, ensure the HTML content is not escaped
|
|
220
|
-
// This is critical for the landing page and custom pages
|
|
221
|
-
finalHtmlContent = htmlContent;
|
|
222
|
-
|
|
223
|
-
// Log a message for debugging - but only for non-dev mode or verbose logging
|
|
224
|
-
if (!options.isDev) {
|
|
225
|
-
console.log(`📄 Processing no-style page: ${path.relative(CWD, filePath)}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Get the URL path for navigation
|
|
230
224
|
let currentPagePathForNav;
|
|
231
225
|
let normalizedPath;
|
|
232
226
|
|
|
233
227
|
if (isIndexFile) {
|
|
234
|
-
// For index.md files, the nav path should be the directory itself with trailing slash
|
|
235
228
|
const dirPath = path.dirname(relativePath);
|
|
236
229
|
if (dirPath === '.') {
|
|
237
|
-
// Root index.md
|
|
238
230
|
currentPagePathForNav = 'index.html';
|
|
239
231
|
normalizedPath = '/';
|
|
240
232
|
} else {
|
|
241
|
-
// Subfolder index.md - simple format: directory-name/
|
|
242
233
|
currentPagePathForNav = dirPath + '/';
|
|
243
234
|
normalizedPath = '/' + dirPath;
|
|
244
235
|
}
|
|
245
236
|
} else {
|
|
246
|
-
// For non-index files, the path should be the file name with trailing slash
|
|
247
237
|
const pathWithoutExt = relativePath.replace(/\.md$/, '');
|
|
248
238
|
currentPagePathForNav = pathWithoutExt + '/';
|
|
249
239
|
normalizedPath = '/' + pathWithoutExt;
|
|
250
240
|
}
|
|
251
241
|
|
|
252
|
-
// Convert Windows backslashes to forward slashes for web paths
|
|
253
242
|
currentPagePathForNav = currentPagePathForNav.replace(/\\/g, '/');
|
|
254
243
|
|
|
255
|
-
// Log navigation paths for debugging
|
|
256
|
-
// Uncomment this line when debugging:
|
|
257
|
-
// logNavigationPaths(filePath, currentPagePathForNav, normalizedPath);
|
|
258
|
-
|
|
259
244
|
const navigationHtml = await generateNavigationHtml(
|
|
260
245
|
config.navigation,
|
|
261
246
|
currentPagePathForNav,
|
|
@@ -263,123 +248,80 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
263
248
|
config
|
|
264
249
|
);
|
|
265
250
|
|
|
266
|
-
// Find current page in navigation for prev/next links
|
|
267
251
|
let prevPage = null;
|
|
268
252
|
let nextPage = null;
|
|
269
253
|
let currentPageIndex = -1;
|
|
270
254
|
|
|
271
|
-
// Extract a flattened navigation array for prev/next links
|
|
272
255
|
const flatNavigation = [];
|
|
273
256
|
|
|
274
|
-
// Helper function to create a normalized path for navigation matching
|
|
275
257
|
function createNormalizedPath(item) {
|
|
276
258
|
if (!item.path) return null;
|
|
277
259
|
return item.path.startsWith('/') ? item.path : '/' + item.path;
|
|
278
260
|
}
|
|
279
261
|
|
|
280
|
-
function extractNavigationItems(items
|
|
262
|
+
function extractNavigationItems(items) {
|
|
281
263
|
if (!items || !Array.isArray(items)) return;
|
|
282
264
|
|
|
283
265
|
for (const item of items) {
|
|
284
|
-
if (item.external) continue;
|
|
266
|
+
if (item.external) continue;
|
|
285
267
|
|
|
286
|
-
// Only include items with paths (not section headers without links)
|
|
287
268
|
if (item.path) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// For parent items with children, ensure path ends with / (folders)
|
|
292
|
-
// This helps with matching in the navigation template
|
|
293
|
-
if (item.children && item.children.length > 0) {
|
|
294
|
-
// If path from config doesn't end with slash, add it
|
|
295
|
-
if (!item.path.endsWith('/') && !normalizedPath.endsWith('/')) {
|
|
296
|
-
normalizedPath += '/';
|
|
297
|
-
}
|
|
269
|
+
let normalizedItemPath = createNormalizedPath(item);
|
|
270
|
+
if (item.children && !normalizedItemPath.endsWith('/')) {
|
|
271
|
+
normalizedItemPath += '/';
|
|
298
272
|
}
|
|
299
|
-
|
|
300
273
|
flatNavigation.push({
|
|
301
274
|
title: item.title,
|
|
302
|
-
path:
|
|
303
|
-
fullPath: item.path, // Original path as defined in config
|
|
304
|
-
isParent: item.children && item.children.length > 0 // Mark if it's a parent with children
|
|
275
|
+
path: normalizedItemPath,
|
|
305
276
|
});
|
|
306
277
|
}
|
|
307
278
|
|
|
308
|
-
// Process children (depth first to maintain document outline order)
|
|
309
279
|
if (item.children && Array.isArray(item.children)) {
|
|
310
|
-
extractNavigationItems(item.children
|
|
280
|
+
extractNavigationItems(item.children);
|
|
311
281
|
}
|
|
312
282
|
}
|
|
313
283
|
}
|
|
314
284
|
|
|
315
|
-
// Extract navigation items into flat array
|
|
316
285
|
extractNavigationItems(config.navigation);
|
|
317
286
|
|
|
318
|
-
// Find the current page in flatNavigation
|
|
319
287
|
currentPageIndex = flatNavigation.findIndex(item => {
|
|
320
|
-
|
|
321
|
-
if (item.path === normalizedPath) {
|
|
322
|
-
return true;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Special handling for parent folders
|
|
288
|
+
if (item.path === normalizedPath) return true;
|
|
326
289
|
if (isIndexFile && item.path.endsWith('/')) {
|
|
327
|
-
|
|
328
|
-
const itemPathWithoutSlash = item.path.slice(0, -1);
|
|
329
|
-
return itemPathWithoutSlash === normalizedPath;
|
|
290
|
+
return item.path.slice(0, -1) === normalizedPath;
|
|
330
291
|
}
|
|
331
|
-
|
|
332
292
|
return false;
|
|
333
293
|
});
|
|
334
294
|
|
|
335
295
|
if (currentPageIndex >= 0) {
|
|
336
|
-
|
|
337
|
-
if (currentPageIndex
|
|
338
|
-
prevPage = flatNavigation[currentPageIndex - 1];
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (currentPageIndex < flatNavigation.length - 1) {
|
|
342
|
-
nextPage = flatNavigation[currentPageIndex + 1];
|
|
343
|
-
}
|
|
296
|
+
if (currentPageIndex > 0) prevPage = flatNavigation[currentPageIndex - 1];
|
|
297
|
+
if (currentPageIndex < flatNavigation.length - 1) nextPage = flatNavigation[currentPageIndex + 1];
|
|
344
298
|
}
|
|
345
299
|
|
|
346
|
-
// Convert page paths to proper URLs for links
|
|
347
300
|
if (prevPage) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
} else {
|
|
352
|
-
// Remove leading slash and ensure clean path
|
|
353
|
-
const cleanPath = prevPage.path.substring(1).replace(/\/+$/, '');
|
|
354
|
-
prevPage.url = relativePathToRoot + cleanPath + '/';
|
|
355
|
-
}
|
|
301
|
+
const cleanPath = prevPage.path.startsWith('/') ? prevPage.path.substring(1) : prevPage.path;
|
|
302
|
+
prevPage.url = relativePathToRoot + (cleanPath.endsWith('/') ? cleanPath : cleanPath + '/');
|
|
303
|
+
if (prevPage.path === '/') prevPage.url = relativePathToRoot;
|
|
356
304
|
}
|
|
357
305
|
|
|
358
306
|
if (nextPage) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
} else {
|
|
363
|
-
// Remove leading slash and ensure clean path
|
|
364
|
-
const cleanPath = nextPage.path.substring(1).replace(/\/+$/, '');
|
|
365
|
-
nextPage.url = relativePathToRoot + cleanPath + '/';
|
|
366
|
-
}
|
|
307
|
+
const cleanPath = nextPage.path.startsWith('/') ? nextPage.path.substring(1) : nextPage.path;
|
|
308
|
+
nextPage.url = relativePathToRoot + (cleanPath.endsWith('/') ? cleanPath : cleanPath + '/');
|
|
309
|
+
if (nextPage.path === '/') nextPage.url = relativePathToRoot;
|
|
367
310
|
}
|
|
368
311
|
|
|
369
312
|
const pageDataForTemplate = {
|
|
370
|
-
content:
|
|
313
|
+
content: htmlContent,
|
|
371
314
|
pageTitle: pageFrontmatter.title || 'Untitled',
|
|
372
315
|
siteTitle: config.siteTitle,
|
|
373
316
|
navigationHtml,
|
|
374
317
|
relativePathToRoot: relativePathToRoot,
|
|
375
|
-
config: config,
|
|
318
|
+
config: config,
|
|
376
319
|
frontmatter: pageFrontmatter,
|
|
377
|
-
outputPath: outputHtmlPath,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
headings: headings || [], // Pass headings for TOC
|
|
320
|
+
outputPath: outputHtmlPath.replace(/\\/g, '/'),
|
|
321
|
+
prevPage: prevPage,
|
|
322
|
+
nextPage: nextPage,
|
|
323
|
+
currentPagePath: normalizedPath,
|
|
324
|
+
headings: headings || [],
|
|
383
325
|
};
|
|
384
326
|
|
|
385
327
|
const pageHtml = await generateHtmlPage(pageDataForTemplate);
|
|
@@ -387,16 +329,16 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
387
329
|
await fs.ensureDir(path.dirname(finalOutputHtmlPath));
|
|
388
330
|
await fs.writeFile(finalOutputHtmlPath, pageHtml);
|
|
389
331
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
332
|
+
const sitemapOutputPath = isIndexFile
|
|
333
|
+
? (path.dirname(relativePath) === '.' ? '' : path.dirname(relativePath) + '/')
|
|
334
|
+
: relativePath.replace(/\.md$/, '/');
|
|
335
|
+
|
|
336
|
+
processedPages.push({
|
|
337
|
+
outputPath: sitemapOutputPath.replace(/\\/g, '/'),
|
|
395
338
|
frontmatter: pageFrontmatter
|
|
396
|
-
};
|
|
397
|
-
processedPages.push(processedPage);
|
|
339
|
+
});
|
|
398
340
|
} catch (error) {
|
|
399
|
-
console.error(
|
|
341
|
+
console.error(`❌ An unexpected error occurred while processing file ${path.relative(CWD, filePath)}:`, error);
|
|
400
342
|
}
|
|
401
343
|
}
|
|
402
344
|
|
package/src/commands/dev.js
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
1
3
|
const fs = require('fs-extra');
|
|
2
4
|
const path = require('path');
|
|
3
5
|
const readline = require('readline');
|
|
@@ -34,6 +36,7 @@ module.exports = {
|
|
|
34
36
|
defaultMode: 'light', // Initial color mode: 'light' or 'dark'
|
|
35
37
|
enableModeToggle: true, // Show UI button to toggle light/dark modes
|
|
36
38
|
positionMode: 'bottom', // 'top' or 'bottom' for the theme toggle
|
|
39
|
+
codeHighlight: true, // Enable/disable codeblock highlighting and import of highlight.js
|
|
37
40
|
customCss: [ // Array of paths to custom CSS files
|
|
38
41
|
// '/assets/css/custom.css', // Custom TOC styles
|
|
39
42
|
]
|
|
@@ -47,6 +50,7 @@ module.exports = {
|
|
|
47
50
|
|
|
48
51
|
// Content Processing
|
|
49
52
|
autoTitleFromH1: true, // Set to true to automatically use the first H1 as page title
|
|
53
|
+
copyCode: true, // Enable/disable the copy code button on code blocks
|
|
50
54
|
|
|
51
55
|
// Plugins Configuration
|
|
52
56
|
// Plugins are configured here. docmd will look for these keys.
|