@mgks/docmd 0.1.1 โ 0.1.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/.github/workflows/publish.yml +1 -1
- package/README.md +3 -1
- package/assets/css/welcome.css +362 -0
- package/assets/images/preview-dark-1.png +0 -0
- package/assets/images/preview-dark-2.png +0 -0
- package/assets/images/preview-dark-3.png +0 -0
- package/assets/images/preview-light-1.png +0 -0
- package/assets/images/preview-light-2.png +0 -0
- package/assets/images/preview-light-3.png +0 -0
- package/bin/docmd.js +4 -2
- package/config.js +5 -2
- package/docs/content/no-style-example.md +110 -0
- package/docs/content/no-style-pages.md +202 -0
- package/docs/index.md +140 -53
- package/docs/overview.md +56 -0
- package/docs/theming/assets-management.md +126 -0
- package/docs/theming/custom-css-js.md +2 -36
- package/package.json +1 -2
- package/src/assets/css/docmd-main.css +3 -1
- package/src/commands/build.js +282 -205
- package/src/commands/dev.js +163 -35
- package/src/commands/init.js +117 -6
- package/src/core/file-processor.js +67 -5
- package/src/core/html-generator.js +16 -3
- package/src/plugins/sitemap.js +24 -3
- package/src/templates/layout.ejs +1 -1
- package/src/templates/no-style.ejs +159 -0
package/src/commands/build.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { loadConfig } = require('../core/config-loader');
|
|
5
|
-
const { processMarkdownFile } = require('../core/file-processor');
|
|
5
|
+
const { processMarkdownFile, findMarkdownFiles } = require('../core/file-processor');
|
|
6
6
|
const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
|
|
7
7
|
const { renderIcon, clearWarnedIcons } = require('../core/icon-renderer'); // Update import
|
|
8
8
|
const { generateSitemap } = require('../plugins/sitemap'); // Import our sitemap plugin
|
|
9
9
|
const { version } = require('../../package.json'); // Import package version
|
|
10
|
+
const matter = require('gray-matter'); // Use gray-matter instead of front-matter
|
|
10
11
|
|
|
11
12
|
// Debug function to log navigation information
|
|
12
13
|
function logNavigationPaths(pagePath, navPath, normalizedPath) {
|
|
@@ -28,16 +29,36 @@ const ASSET_VERSIONS = {
|
|
|
28
29
|
// Add other assets here with their versions
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Format paths for display to make them relative to CWD
|
|
34
|
+
* @param {string} absolutePath - The absolute path to format
|
|
35
|
+
* @param {string} cwd - Current working directory
|
|
36
|
+
* @returns {string} - Formatted relative path
|
|
37
|
+
*/
|
|
38
|
+
function formatPathForDisplay(absolutePath, cwd) {
|
|
39
|
+
// Get the relative path from CWD
|
|
40
|
+
const relativePath = path.relative(cwd, absolutePath);
|
|
41
|
+
|
|
42
|
+
// If it's not a subdirectory, prefix with ./ for clarity
|
|
43
|
+
if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
|
|
44
|
+
return `./${relativePath}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Return the relative path
|
|
48
|
+
return relativePath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function buildSite(configPath, options = { isDev: false, preserve: false, noDoubleProcessing: false }) {
|
|
32
52
|
clearWarnedIcons(); // Clear warnings at the start of every build
|
|
33
53
|
|
|
34
54
|
const config = await loadConfig(configPath);
|
|
35
55
|
const CWD = process.cwd();
|
|
36
56
|
const SRC_DIR = path.resolve(CWD, config.srcDir);
|
|
37
57
|
const OUTPUT_DIR = path.resolve(CWD, config.outputDir);
|
|
58
|
+
const USER_ASSETS_DIR = path.resolve(CWD, 'assets'); // User's custom assets directory
|
|
38
59
|
|
|
39
60
|
if (!await fs.pathExists(SRC_DIR)) {
|
|
40
|
-
throw new Error(`Source directory not found: ${SRC_DIR}`);
|
|
61
|
+
throw new Error(`Source directory not found: ${formatPathForDisplay(SRC_DIR, CWD)}`);
|
|
41
62
|
}
|
|
42
63
|
|
|
43
64
|
// Create output directory if it doesn't exist
|
|
@@ -51,12 +72,39 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
51
72
|
await fs.remove(file);
|
|
52
73
|
}
|
|
53
74
|
if (!options.isDev) {
|
|
54
|
-
console.log(`๐งน Cleaned HTML files from output directory: ${OUTPUT_DIR}`);
|
|
75
|
+
console.log(`๐งน Cleaned HTML files from output directory: ${formatPathForDisplay(OUTPUT_DIR, CWD)}`);
|
|
55
76
|
}
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
// Track preserved files for summary report
|
|
59
80
|
const preservedFiles = [];
|
|
81
|
+
const userAssetsCopied = [];
|
|
82
|
+
|
|
83
|
+
// Copy user assets from root assets/ directory if it exists
|
|
84
|
+
if (await fs.pathExists(USER_ASSETS_DIR)) {
|
|
85
|
+
const assetsDestDir = path.join(OUTPUT_DIR, 'assets');
|
|
86
|
+
await fs.ensureDir(assetsDestDir);
|
|
87
|
+
|
|
88
|
+
if (!options.isDev) {
|
|
89
|
+
console.log(`๐ Copying user assets from ${formatPathForDisplay(USER_ASSETS_DIR, CWD)} to ${formatPathForDisplay(assetsDestDir, CWD)}...`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const userAssetFiles = await getAllFiles(USER_ASSETS_DIR);
|
|
93
|
+
|
|
94
|
+
for (const srcFile of userAssetFiles) {
|
|
95
|
+
const relativePath = path.relative(USER_ASSETS_DIR, srcFile);
|
|
96
|
+
const destFile = path.join(assetsDestDir, relativePath);
|
|
97
|
+
|
|
98
|
+
// Ensure directory exists
|
|
99
|
+
await fs.ensureDir(path.dirname(destFile));
|
|
100
|
+
await fs.copyFile(srcFile, destFile);
|
|
101
|
+
userAssetsCopied.push(relativePath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!options.isDev && userAssetsCopied.length > 0) {
|
|
105
|
+
console.log(`๐ฆ Copied ${userAssetsCopied.length} user assets`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
60
108
|
|
|
61
109
|
// Copy assets
|
|
62
110
|
const assetsSrcDir = path.join(__dirname, '..', 'assets');
|
|
@@ -64,7 +112,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
64
112
|
|
|
65
113
|
if (await fs.pathExists(assetsSrcDir)) {
|
|
66
114
|
if (!options.isDev) {
|
|
67
|
-
console.log(`๐ Copying assets to ${assetsDestDir}...`);
|
|
115
|
+
console.log(`๐ Copying docmd assets to ${formatPathForDisplay(assetsDestDir, CWD)}...`);
|
|
68
116
|
}
|
|
69
117
|
|
|
70
118
|
// Create destination directory if it doesn't exist
|
|
@@ -81,10 +129,13 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
81
129
|
// Check if destination file already exists
|
|
82
130
|
const fileExists = await fs.pathExists(destFile);
|
|
83
131
|
|
|
84
|
-
if
|
|
132
|
+
// Skip if the file exists and either:
|
|
133
|
+
// 1. The preserve flag is set, OR
|
|
134
|
+
// 2. The file was copied from user assets (user assets take precedence)
|
|
135
|
+
if (fileExists && (options.preserve || userAssetsCopied.includes(relativePath))) {
|
|
85
136
|
// Skip file and add to preserved list
|
|
86
137
|
preservedFiles.push(relativePath);
|
|
87
|
-
if (!options.isDev) {
|
|
138
|
+
if (!options.isDev && options.preserve) {
|
|
88
139
|
console.log(` Preserving existing file: ${relativePath}`);
|
|
89
140
|
}
|
|
90
141
|
} else {
|
|
@@ -94,7 +145,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
94
145
|
}
|
|
95
146
|
}
|
|
96
147
|
} else {
|
|
97
|
-
console.warn(`โ ๏ธ Assets source directory not found: ${assetsSrcDir}`);
|
|
148
|
+
console.warn(`โ ๏ธ Assets source directory not found: ${formatPathForDisplay(assetsSrcDir, CWD)}`);
|
|
98
149
|
}
|
|
99
150
|
|
|
100
151
|
// Check for Highlight.js themes
|
|
@@ -108,8 +159,8 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
108
159
|
// For 'docmd dev', show only once per session if not already shown.
|
|
109
160
|
if (!options.isDev || (options.isDev && !highlightWarningShown)) {
|
|
110
161
|
console.warn(`โ ๏ธ Highlight.js themes not found in assets. Please ensure these files exist:
|
|
111
|
-
- ${lightThemePath}
|
|
112
|
-
- ${darkThemePath}
|
|
162
|
+
- ${path.relative(CWD, lightThemePath)}
|
|
163
|
+
- ${path.relative(CWD, darkThemePath)}
|
|
113
164
|
Syntax highlighting may not work correctly.`);
|
|
114
165
|
if (options.isDev) {
|
|
115
166
|
highlightWarningShown = true; // Mark as shown for this dev session
|
|
@@ -117,217 +168,242 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
117
168
|
}
|
|
118
169
|
}
|
|
119
170
|
|
|
171
|
+
// Array to collect information about all processed pages for sitemap
|
|
172
|
+
const processedPages = [];
|
|
120
173
|
|
|
174
|
+
// Find all Markdown files in the source directory
|
|
121
175
|
const markdownFiles = await findMarkdownFiles(SRC_DIR);
|
|
122
|
-
if (markdownFiles.length === 0) {
|
|
123
|
-
console.warn(`โ ๏ธ No Markdown files found in ${SRC_DIR}. Nothing to build.`);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
176
|
if (!options.isDev) {
|
|
127
177
|
console.log(`๐ Found ${markdownFiles.length} markdown files.`);
|
|
128
178
|
}
|
|
129
|
-
|
|
130
|
-
// Array to collect information about all processed pages for sitemap
|
|
131
|
-
const processedPages = [];
|
|
132
179
|
|
|
133
|
-
//
|
|
134
|
-
const
|
|
180
|
+
// Process each Markdown file
|
|
181
|
+
const processedFiles = new Set(); // Track processed files to avoid double processing
|
|
135
182
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function extractNavigationItems(items, parentPath = '') {
|
|
143
|
-
if (!items || !Array.isArray(items)) return;
|
|
144
|
-
|
|
145
|
-
for (const item of items) {
|
|
146
|
-
if (item.external) continue; // Skip external links
|
|
183
|
+
for (const filePath of markdownFiles) {
|
|
184
|
+
try {
|
|
185
|
+
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
186
|
+
const { data: frontmatter, content } = matter(fileContent);
|
|
147
187
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// For parent items with children, ensure path ends with / (folders)
|
|
154
|
-
// This helps with matching in the navigation template
|
|
155
|
-
if (item.children && item.children.length > 0) {
|
|
156
|
-
// If path from config doesn't end with slash, add it
|
|
157
|
-
if (!item.path.endsWith('/') && !normalizedPath.endsWith('/')) {
|
|
158
|
-
normalizedPath += '/';
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
flatNavigation.push({
|
|
163
|
-
title: item.title,
|
|
164
|
-
path: normalizedPath,
|
|
165
|
-
fullPath: item.path, // Original path as defined in config
|
|
166
|
-
isParent: item.children && item.children.length > 0 // Mark if it's a parent with children
|
|
167
|
-
});
|
|
188
|
+
// Skip this file if it's already been processed and noDoubleProcessing is true
|
|
189
|
+
const relativePath = path.relative(SRC_DIR, filePath);
|
|
190
|
+
if (options.noDoubleProcessing && processedFiles.has(relativePath)) {
|
|
191
|
+
continue;
|
|
168
192
|
}
|
|
193
|
+
processedFiles.add(relativePath);
|
|
194
|
+
|
|
195
|
+
// Pretty URL handling - properly handle index.md files in subfolders
|
|
196
|
+
let outputHtmlPath;
|
|
197
|
+
const fileName = path.basename(relativePath);
|
|
198
|
+
const isIndexFile = fileName === 'index.md';
|
|
169
199
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
if (isIndexFile) {
|
|
201
|
+
// For any index.md file (in root or subfolder), convert to index.html in the same folder
|
|
202
|
+
const dirPath = path.dirname(relativePath);
|
|
203
|
+
outputHtmlPath = path.join(dirPath, 'index.html');
|
|
204
|
+
} else {
|
|
205
|
+
// For non-index files, create a folder with index.html
|
|
206
|
+
outputHtmlPath = relativePath.replace(/\.md$/, '/index.html');
|
|
173
207
|
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Extract navigation items into flat array
|
|
178
|
-
extractNavigationItems(config.navigation);
|
|
179
208
|
|
|
180
|
-
|
|
181
|
-
const relativeMdPath = path.relative(SRC_DIR, mdFilePath);
|
|
182
|
-
|
|
183
|
-
// Pretty URL handling - properly handle index.md files in subfolders
|
|
184
|
-
let outputHtmlPath;
|
|
185
|
-
const fileName = path.basename(relativeMdPath);
|
|
186
|
-
const isIndexFile = fileName === 'index.md';
|
|
187
|
-
|
|
188
|
-
if (isIndexFile) {
|
|
189
|
-
// For any index.md file (in root or subfolder), convert to index.html in the same folder
|
|
190
|
-
const dirPath = path.dirname(relativeMdPath);
|
|
191
|
-
outputHtmlPath = path.join(dirPath, 'index.html');
|
|
192
|
-
} else {
|
|
193
|
-
// For non-index files, create a folder with index.html
|
|
194
|
-
outputHtmlPath = relativeMdPath.replace(/\.md$/, '/index.html');
|
|
195
|
-
}
|
|
209
|
+
const finalOutputHtmlPath = path.join(OUTPUT_DIR, outputHtmlPath);
|
|
196
210
|
|
|
197
|
-
|
|
211
|
+
const depth = outputHtmlPath.split(path.sep).length - 1;
|
|
212
|
+
const relativePathToRoot = depth > 0 ? '../'.repeat(depth) : './';
|
|
198
213
|
|
|
199
|
-
|
|
200
|
-
|
|
214
|
+
const { frontmatter: pageFrontmatter, htmlContent, headings } = await processMarkdownFile(filePath, { isDev: options.isDev });
|
|
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
|
+
}
|
|
201
228
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
229
|
+
// Get the URL path for navigation
|
|
230
|
+
let currentPagePathForNav;
|
|
231
|
+
let normalizedPath;
|
|
232
|
+
|
|
233
|
+
if (isIndexFile) {
|
|
234
|
+
// For index.md files, the nav path should be the directory itself with trailing slash
|
|
235
|
+
const dirPath = path.dirname(relativePath);
|
|
236
|
+
if (dirPath === '.') {
|
|
237
|
+
// Root index.md
|
|
238
|
+
currentPagePathForNav = 'index.html';
|
|
239
|
+
normalizedPath = '/';
|
|
240
|
+
} else {
|
|
241
|
+
// Subfolder index.md - simple format: directory-name/
|
|
242
|
+
currentPagePathForNav = dirPath + '/';
|
|
243
|
+
normalizedPath = '/' + dirPath;
|
|
244
|
+
}
|
|
215
245
|
} else {
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
} else {
|
|
221
|
-
// For non-index files, the path should be the file name with trailing slash
|
|
222
|
-
const pathWithoutExt = relativeMdPath.replace(/\.md$/, '');
|
|
223
|
-
currentPagePathForNav = pathWithoutExt + '/';
|
|
224
|
-
normalizedPath = '/' + pathWithoutExt;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Convert Windows backslashes to forward slashes for web paths
|
|
228
|
-
currentPagePathForNav = currentPagePathForNav.replace(/\\/g, '/');
|
|
229
|
-
|
|
230
|
-
// Log navigation paths for debugging
|
|
231
|
-
// Uncomment this line when debugging:
|
|
232
|
-
// logNavigationPaths(mdFilePath, currentPagePathForNav, normalizedPath);
|
|
233
|
-
|
|
234
|
-
const navigationHtml = await generateNavigationHtml(
|
|
235
|
-
config.navigation,
|
|
236
|
-
currentPagePathForNav,
|
|
237
|
-
relativePathToRoot,
|
|
238
|
-
config
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
// Find current page in navigation for prev/next links
|
|
242
|
-
let prevPage = null;
|
|
243
|
-
let nextPage = null;
|
|
244
|
-
let currentPageIndex = -1;
|
|
245
|
-
|
|
246
|
-
// Find the current page in flatNavigation
|
|
247
|
-
currentPageIndex = flatNavigation.findIndex(item => {
|
|
248
|
-
// Direct path match
|
|
249
|
-
if (item.path === normalizedPath) {
|
|
250
|
-
return true;
|
|
246
|
+
// For non-index files, the path should be the file name with trailing slash
|
|
247
|
+
const pathWithoutExt = relativePath.replace(/\.md$/, '');
|
|
248
|
+
currentPagePathForNav = pathWithoutExt + '/';
|
|
249
|
+
normalizedPath = '/' + pathWithoutExt;
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
252
|
+
// Convert Windows backslashes to forward slashes for web paths
|
|
253
|
+
currentPagePathForNav = currentPagePathForNav.replace(/\\/g, '/');
|
|
254
|
+
|
|
255
|
+
// Log navigation paths for debugging
|
|
256
|
+
// Uncomment this line when debugging:
|
|
257
|
+
// logNavigationPaths(filePath, currentPagePathForNav, normalizedPath);
|
|
258
|
+
|
|
259
|
+
const navigationHtml = await generateNavigationHtml(
|
|
260
|
+
config.navigation,
|
|
261
|
+
currentPagePathForNav,
|
|
262
|
+
relativePathToRoot,
|
|
263
|
+
config
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Find current page in navigation for prev/next links
|
|
267
|
+
let prevPage = null;
|
|
268
|
+
let nextPage = null;
|
|
269
|
+
let currentPageIndex = -1;
|
|
270
|
+
|
|
271
|
+
// Extract a flattened navigation array for prev/next links
|
|
272
|
+
const flatNavigation = [];
|
|
273
|
+
|
|
274
|
+
// Helper function to create a normalized path for navigation matching
|
|
275
|
+
function createNormalizedPath(item) {
|
|
276
|
+
if (!item.path) return null;
|
|
277
|
+
return item.path.startsWith('/') ? item.path : '/' + item.path;
|
|
258
278
|
}
|
|
259
279
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
280
|
+
function extractNavigationItems(items, parentPath = '') {
|
|
281
|
+
if (!items || !Array.isArray(items)) return;
|
|
282
|
+
|
|
283
|
+
for (const item of items) {
|
|
284
|
+
if (item.external) continue; // Skip external links
|
|
285
|
+
|
|
286
|
+
// Only include items with paths (not section headers without links)
|
|
287
|
+
if (item.path) {
|
|
288
|
+
// Normalize path - ensure leading slash
|
|
289
|
+
let normalizedPath = createNormalizedPath(item);
|
|
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
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
flatNavigation.push({
|
|
301
|
+
title: item.title,
|
|
302
|
+
path: normalizedPath,
|
|
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
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Process children (depth first to maintain document outline order)
|
|
309
|
+
if (item.children && Array.isArray(item.children)) {
|
|
310
|
+
extractNavigationItems(item.children, item.path || parentPath);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
267
313
|
}
|
|
268
314
|
|
|
269
|
-
|
|
270
|
-
|
|
315
|
+
// Extract navigation items into flat array
|
|
316
|
+
extractNavigationItems(config.navigation);
|
|
317
|
+
|
|
318
|
+
// Find the current page in flatNavigation
|
|
319
|
+
currentPageIndex = flatNavigation.findIndex(item => {
|
|
320
|
+
// Direct path match
|
|
321
|
+
if (item.path === normalizedPath) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Special handling for parent folders
|
|
326
|
+
if (isIndexFile && item.path.endsWith('/')) {
|
|
327
|
+
// Remove trailing slash for comparison
|
|
328
|
+
const itemPathWithoutSlash = item.path.slice(0, -1);
|
|
329
|
+
return itemPathWithoutSlash === normalizedPath;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return false;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (currentPageIndex >= 0) {
|
|
336
|
+
// Get previous and next pages if they exist
|
|
337
|
+
if (currentPageIndex > 0) {
|
|
338
|
+
prevPage = flatNavigation[currentPageIndex - 1];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (currentPageIndex < flatNavigation.length - 1) {
|
|
342
|
+
nextPage = flatNavigation[currentPageIndex + 1];
|
|
343
|
+
}
|
|
271
344
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
345
|
+
|
|
346
|
+
// Convert page paths to proper URLs for links
|
|
347
|
+
if (prevPage) {
|
|
348
|
+
// Format the previous page URL, avoiding double slashes
|
|
349
|
+
if (prevPage.path === '/') {
|
|
350
|
+
prevPage.url = relativePathToRoot + 'index.html';
|
|
351
|
+
} else {
|
|
352
|
+
// Remove leading slash and ensure clean path
|
|
353
|
+
const cleanPath = prevPage.path.substring(1).replace(/\/+$/, '');
|
|
354
|
+
prevPage.url = relativePathToRoot + cleanPath + '/';
|
|
355
|
+
}
|
|
283
356
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
357
|
+
|
|
358
|
+
if (nextPage) {
|
|
359
|
+
// Format the next page URL, avoiding double slashes
|
|
360
|
+
if (nextPage.path === '/') {
|
|
361
|
+
nextPage.url = relativePathToRoot + 'index.html';
|
|
362
|
+
} else {
|
|
363
|
+
// Remove leading slash and ensure clean path
|
|
364
|
+
const cleanPath = nextPage.path.substring(1).replace(/\/+$/, '');
|
|
365
|
+
nextPage.url = relativePathToRoot + cleanPath + '/';
|
|
366
|
+
}
|
|
294
367
|
}
|
|
295
|
-
}
|
|
296
368
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
369
|
+
const pageDataForTemplate = {
|
|
370
|
+
content: finalHtmlContent,
|
|
371
|
+
pageTitle: pageFrontmatter.title || 'Untitled',
|
|
372
|
+
siteTitle: config.siteTitle,
|
|
373
|
+
navigationHtml,
|
|
374
|
+
relativePathToRoot: relativePathToRoot,
|
|
375
|
+
config: config, // Pass full config
|
|
376
|
+
frontmatter: pageFrontmatter,
|
|
377
|
+
outputPath: outputHtmlPath, // Relative path from outputDir root
|
|
378
|
+
prettyUrl: true, // Flag to indicate we're using pretty URLs
|
|
379
|
+
prevPage: prevPage, // Previous page in navigation
|
|
380
|
+
nextPage: nextPage, // Next page in navigation
|
|
381
|
+
currentPagePath: normalizedPath, // Pass the normalized path for active state detection
|
|
382
|
+
headings: headings || [], // Pass headings for TOC
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const pageHtml = await generateHtmlPage(pageDataForTemplate);
|
|
386
|
+
|
|
387
|
+
await fs.ensureDir(path.dirname(finalOutputHtmlPath));
|
|
388
|
+
await fs.writeFile(finalOutputHtmlPath, pageHtml);
|
|
389
|
+
|
|
390
|
+
// Add to processed pages for sitemap
|
|
391
|
+
const processedPage = {
|
|
392
|
+
outputPath: isIndexFile
|
|
393
|
+
? (path.dirname(relativePath) === '.' ? 'index.html' : path.dirname(relativePath) + '/')
|
|
394
|
+
: outputHtmlPath.replace(/\\/g, '/').replace(/\/index\.html$/, '/'),
|
|
395
|
+
frontmatter: pageFrontmatter
|
|
396
|
+
};
|
|
397
|
+
processedPages.push(processedPage);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error(`Error processing file ${path.relative(CWD, filePath)}:`, error);
|
|
400
|
+
}
|
|
325
401
|
}
|
|
326
402
|
|
|
327
403
|
// Generate sitemap if enabled in config
|
|
328
404
|
if (config.plugins?.sitemap !== false) {
|
|
329
405
|
try {
|
|
330
|
-
await generateSitemap(config, processedPages, OUTPUT_DIR);
|
|
406
|
+
await generateSitemap(config, processedPages, OUTPUT_DIR, { isDev: options.isDev });
|
|
331
407
|
} catch (error) {
|
|
332
408
|
console.error(`โ Error generating sitemap: ${error.message}`);
|
|
333
409
|
}
|
|
@@ -339,6 +415,22 @@ async function buildSite(configPath, options = { isDev: false, preserve: false }
|
|
|
339
415
|
preservedFiles.forEach(file => console.log(` - assets/${file}`));
|
|
340
416
|
console.log(`\nTo update these files in future builds, run without the --preserve flag.`);
|
|
341
417
|
}
|
|
418
|
+
|
|
419
|
+
if (userAssetsCopied.length > 0 && !options.isDev) {
|
|
420
|
+
console.log(`\n๐ User Assets: ${userAssetsCopied.length} files were copied from your assets/ directory:`);
|
|
421
|
+
if (userAssetsCopied.length <= 10) {
|
|
422
|
+
userAssetsCopied.forEach(file => console.log(` - assets/${file}`));
|
|
423
|
+
} else {
|
|
424
|
+
userAssetsCopied.slice(0, 5).forEach(file => console.log(` - assets/${file}`));
|
|
425
|
+
console.log(` - ... and ${userAssetsCopied.length - 5} more files`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
config,
|
|
431
|
+
processedPages,
|
|
432
|
+
markdownFiles,
|
|
433
|
+
};
|
|
342
434
|
}
|
|
343
435
|
|
|
344
436
|
// Helper function to find HTML files and sitemap.xml to clean up
|
|
@@ -383,19 +475,4 @@ async function getAllFiles(dir) {
|
|
|
383
475
|
return files;
|
|
384
476
|
}
|
|
385
477
|
|
|
386
|
-
// findMarkdownFiles function remains the same
|
|
387
|
-
async function findMarkdownFiles(dir) {
|
|
388
|
-
let files = [];
|
|
389
|
-
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
390
|
-
for (const item of items) {
|
|
391
|
-
const fullPath = path.join(dir, item.name);
|
|
392
|
-
if (item.isDirectory()) {
|
|
393
|
-
files = files.concat(await findMarkdownFiles(fullPath));
|
|
394
|
-
} else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.markdown'))) {
|
|
395
|
-
files.push(fullPath);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return files;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
478
|
module.exports = { buildSite };
|