@mgks/docmd 0.3.4 → 0.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgks/docmd",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Generate beautiful, lightweight static documentation sites directly from your Markdown files. Zero clutter, just content.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -15,6 +15,7 @@
15
15
  "scripts": {
16
16
  "start": "node ./bin/docmd.js dev",
17
17
  "build": "node ./bin/docmd.js build",
18
+ "live": "node scripts/build-live.js && npx serve dist",
18
19
  "postinstall": "node ./bin/postinstall.js",
19
20
  "lint": "eslint .",
20
21
  "format": "prettier --write .",
@@ -22,32 +23,32 @@
22
23
  },
23
24
  "dependencies": {
24
25
  "chalk": "^4.1.2",
25
- "chokidar": "^4.0.3",
26
+ "chokidar": "^5.0.0",
26
27
  "clean-css": "^5.3.3",
27
- "commander": "^14.0.0",
28
- "ejs": "^3.1.9",
29
- "esbuild": "^0.27.0",
30
- "express": "^5.1.0",
31
- "fs-extra": "^11.2.0",
28
+ "commander": "^14.0.2",
29
+ "ejs": "^3.1.10",
30
+ "express": "^5.2.1",
31
+ "fs-extra": "^11.3.3",
32
32
  "gray-matter": "^4.0.3",
33
33
  "highlight.js": "^11.11.1",
34
- "lucide-static": "^0.535.0",
34
+ "lucide-static": "^0.562.0",
35
+ "markdown-it": "^14.1.0",
35
36
  "markdown-it-abbr": "^2.0.0",
36
37
  "markdown-it-attrs": "^4.3.1",
37
38
  "markdown-it-container": "^4.0.0",
38
39
  "markdown-it-deflist": "^3.0.0",
39
40
  "markdown-it-footnote": "^4.0.0",
40
41
  "markdown-it-task-lists": "^2.1.1",
41
- "mermaid": "^11.12.1",
42
+ "mermaid": "^11.12.2",
42
43
  "minisearch": "^7.2.0",
43
44
  "striptags": "^3.2.0",
44
- "ws": "^8.17.0"
45
+ "ws": "^8.18.3"
45
46
  },
46
47
  "devDependencies": {
47
- "eslint": "^9.32.0",
48
- "eslint-config-prettier": "^10.1.8",
49
- "eslint-plugin-node": "^11.1.0",
50
- "prettier": "^3.2.5"
48
+ "buffer": "^6.0.3",
49
+ "esbuild": "^0.27.2",
50
+ "eslint": "^9.39.2",
51
+ "prettier": "^3.7.4"
51
52
  },
52
53
  "directories": {
53
54
  "doc": "docs"
@@ -77,4 +78,4 @@
77
78
  "homepage": "https://github.com/mgks/docmd#readme",
78
79
  "funding": "https://github.com/sponsors/mgks",
79
80
  "license": "MIT"
80
- }
81
+ }
@@ -0,0 +1,157 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const esbuild = require('esbuild');
4
+
5
+ async function build() {
6
+ console.log('📦 Building @docmd/live core...');
7
+
8
+ const SRC_DIR = path.join(__dirname, '../src');
9
+ const LIVE_SRC_DIR = path.join(SRC_DIR, 'live');
10
+ const DIST_DIR = path.join(__dirname, '../dist');
11
+
12
+ // Ensure dist exists and is empty
13
+ fs.emptyDirSync(DIST_DIR);
14
+
15
+ // 1. Read Templates
16
+ const templatesDir = path.join(SRC_DIR, 'templates');
17
+ const files = fs.readdirSync(templatesDir);
18
+ const templates = {};
19
+
20
+ for (const file of files) {
21
+ if (file.endsWith('.ejs')) {
22
+ templates[file] = fs.readFileSync(path.join(templatesDir, file), 'utf8');
23
+ }
24
+ }
25
+
26
+ // 2. Generate templates.js
27
+ const templatesJsPath = path.join(LIVE_SRC_DIR, 'templates.js');
28
+ const templatesContent = `
29
+ const templates = ${JSON.stringify(templates, null, 2)};
30
+ if (typeof globalThis !== 'undefined') globalThis.__DOCMD_TEMPLATES__ = templates;
31
+ module.exports = templates;
32
+ `;
33
+ fs.writeFileSync(templatesJsPath, templatesContent);
34
+
35
+ // 3. Generate Shim for Buffer
36
+ const shimPath = path.join(LIVE_SRC_DIR, 'shims.js');
37
+ fs.writeFileSync(shimPath, `import { Buffer } from 'buffer'; globalThis.Buffer = Buffer;`);
38
+
39
+ // 4. Bundle JS
40
+ try {
41
+ await esbuild.build({
42
+ entryPoints: [path.join(LIVE_SRC_DIR, 'core.js')],
43
+ bundle: true,
44
+ outfile: path.join(DIST_DIR, 'docmd-live.js'),
45
+ platform: 'browser',
46
+ format: 'iife',
47
+ globalName: 'docmd',
48
+ minify: true,
49
+ define: { 'process.env.NODE_ENV': '"production"' },
50
+ inject: [shimPath],
51
+ plugins: [
52
+ {
53
+ name: 'node-deps-shim',
54
+ setup(build) {
55
+ build.onResolve({ filter: /^path$/ }, args => ({ path: args.path, namespace: 'path-shim' }));
56
+ build.onLoad({ filter: /.*/, namespace: 'path-shim' }, () => ({
57
+ contents: `
58
+ module.exports = {
59
+ join: (...args) => args.filter(Boolean).join('/'),
60
+ resolve: (...args) => '/' + args.filter(Boolean).join('/'),
61
+ basename: (p) => p ? p.split(/[\\\\/]/).pop() : '',
62
+ dirname: (p) => p ? p.split(/[\\\\/]/).slice(0, -1).join('/') || '.' : '.',
63
+ extname: (p) => {
64
+ if (!p) return '';
65
+ const parts = p.split('.');
66
+ return parts.length > 1 ? '.' + parts.pop() : '';
67
+ },
68
+ isAbsolute: (p) => p.startsWith('/'),
69
+ normalize: (p) => p,
70
+ sep: '/'
71
+ };
72
+ `,
73
+ loader: 'js'
74
+ }));
75
+
76
+ build.onResolve({ filter: /^(fs|fs-extra)$/ }, args => ({ path: args.path, namespace: 'fs-shim' }));
77
+ build.onLoad({ filter: /.*/, namespace: 'fs-shim' }, () => ({
78
+ contents: `
79
+ module.exports = {
80
+ existsSync: (p) => {
81
+ if (!globalThis.__DOCMD_TEMPLATES__) return false;
82
+ let name = p.split(/[\\\\/]/).pop();
83
+ if (globalThis.__DOCMD_TEMPLATES__[name]) return true;
84
+ if (!name.endsWith('.ejs') && globalThis.__DOCMD_TEMPLATES__[name + '.ejs']) return true;
85
+ return false;
86
+ },
87
+ readFileSync: (p) => {
88
+ if (!globalThis.__DOCMD_TEMPLATES__) return '';
89
+ let name = p.split(/[\\\\/]/).pop();
90
+ if (globalThis.__DOCMD_TEMPLATES__[name]) return globalThis.__DOCMD_TEMPLATES__[name];
91
+ if (!name.endsWith('.ejs')) name += '.ejs';
92
+ return globalThis.__DOCMD_TEMPLATES__[name] || '';
93
+ },
94
+ statSync: () => ({ isFile: () => true, isDirectory: () => false }),
95
+ constants: { F_OK: 0, R_OK: 4 }
96
+ };
97
+ `,
98
+ loader: 'js'
99
+ }));
100
+ }
101
+ }
102
+ ]
103
+ });
104
+ console.log('✅ Bundled JS to dist/docmd-live.js');
105
+
106
+ // 5. Copy Assets
107
+ console.log('📂 Copying assets...');
108
+ await fs.copy(path.join(SRC_DIR, 'assets'), path.join(DIST_DIR, 'assets'));
109
+
110
+ // 5.5 Bundle Third-Party Libraries (The FIX)
111
+ // We need to manually copy these from node_modules because they aren't in src/assets
112
+ const copyLibrary = async (packageName, fileToBundle, destFileName) => {
113
+ try {
114
+ // Try to resolve the package path
115
+ let srcPath;
116
+ try {
117
+ srcPath = require.resolve(`${packageName}/${fileToBundle}`);
118
+ } catch (e) {
119
+ // Fallback search if require.resolve fails
120
+ const mainPath = require.resolve(packageName);
121
+ let currentDir = path.dirname(mainPath);
122
+ for (let i = 0; i < 5; i++) {
123
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) break;
124
+ currentDir = path.dirname(currentDir);
125
+ }
126
+ srcPath = path.join(currentDir, fileToBundle);
127
+ }
128
+
129
+ if (srcPath && fs.existsSync(srcPath)) {
130
+ const destPath = path.join(DIST_DIR, 'assets/js', destFileName);
131
+ await fs.ensureDir(path.dirname(destPath));
132
+ await fs.copy(srcPath, destPath);
133
+ console.log(` └─ Copied ${packageName} -> assets/js/${destFileName}`);
134
+ } else {
135
+ console.warn(`⚠️ Could not locate ${fileToBundle} in ${packageName}`);
136
+ }
137
+ } catch (e) {
138
+ console.warn(`⚠️ Failed to bundle ${packageName}: ${e.message}`);
139
+ }
140
+ };
141
+
142
+ await copyLibrary('minisearch', 'dist/umd/index.js', 'minisearch.js');
143
+ await copyLibrary('mermaid', 'dist/mermaid.min.js', 'mermaid.min.js');
144
+ console.log('✅ Assets & Libraries copied.');
145
+
146
+ // 6. Copy Demo HTML
147
+ await fs.copy(path.join(LIVE_SRC_DIR, 'index.html'), path.join(DIST_DIR, 'index.html'));
148
+ await fs.copy(path.join(LIVE_SRC_DIR, 'live.css'), path.join(DIST_DIR, 'live.css'));
149
+ console.log('✅ Demo HTML copied.');
150
+
151
+ } catch (e) {
152
+ console.error('❌ Build failed:', e);
153
+ process.exit(1);
154
+ }
155
+ }
156
+
157
+ build();
@@ -0,0 +1,54 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const vm = require('vm');
4
+
5
+ const bundlePath = path.join(__dirname, '../dist/docmd-live.js');
6
+
7
+ if (!fs.existsSync(bundlePath)) {
8
+ console.error('❌ Bundle not found. Run "npm run live" or "node scripts/build-live.js" first.');
9
+ process.exit(1);
10
+ }
11
+
12
+ const bundleCode = fs.readFileSync(bundlePath, 'utf8');
13
+
14
+ const sandbox = {
15
+ console: console,
16
+ setTimeout: setTimeout,
17
+ clearTimeout: clearTimeout,
18
+ window: {},
19
+ self: {},
20
+ globalThis: {}
21
+ };
22
+ sandbox.window = sandbox;
23
+ sandbox.self = sandbox;
24
+ sandbox.globalThis = sandbox;
25
+
26
+ vm.createContext(sandbox);
27
+
28
+ console.log('🧪 Testing Live Bundle...');
29
+
30
+ try {
31
+ vm.runInContext(bundleCode, sandbox);
32
+
33
+ if (!sandbox.docmd) {
34
+ throw new Error('docmd global not found in bundle');
35
+ }
36
+
37
+ const markdown = '# Hello Live\nThis is a test.';
38
+ const config = { siteTitle: 'Live Test' };
39
+
40
+ console.log('Compiling markdown...');
41
+ const result = sandbox.docmd.compile(markdown, config);
42
+
43
+ if (result.includes('<h1>Hello Live</h1>') && result.includes('Live Test')) {
44
+ console.log('✅ Bundle works! Output contains expected HTML.');
45
+ } else {
46
+ console.error('❌ Bundle produced unexpected output.');
47
+ console.log('Output snippet:', result.substring(0, 200));
48
+ process.exit(1);
49
+ }
50
+
51
+ } catch (e) {
52
+ console.error('❌ Test failed:', e);
53
+ process.exit(1);
54
+ }
@@ -9,124 +9,82 @@ function initializeCollapsibleNav() {
9
9
  const nav = document.querySelector('.sidebar-nav');
10
10
  if (!nav) return;
11
11
 
12
- let navStates = {};
13
- try {
14
- // Use sessionStorage to remember state only for the current session
15
- navStates = JSON.parse(sessionStorage.getItem('docmd-nav-states')) || {};
16
- } catch (e) { /* silent fail */ }
12
+ // We NO LONGER set initial state here.
13
+ // The HTML arrives with style="display: block" and aria-expanded="true"
14
+ // pre-rendered by the build process. This eliminates the FOUC/Jitter.
17
15
 
18
16
  nav.querySelectorAll('li.collapsible').forEach(item => {
19
- const navId = item.dataset.navId;
20
17
  const anchor = item.querySelector('a');
21
18
  const submenu = item.querySelector('.submenu');
22
19
 
23
- if (!navId || !anchor || !submenu) return;
24
-
25
- const isParentActive = item.classList.contains('active-parent');
26
- // Default to expanded if it's a parent of the active page, otherwise check stored state.
27
- let isExpanded = isParentActive || (navStates[navId] === true);
28
-
29
- const toggleSubmenu = (expand) => {
30
- item.setAttribute('aria-expanded', expand);
31
- submenu.style.display = expand ? 'block' : 'none';
32
- navStates[navId] = expand;
33
- sessionStorage.setItem('docmd-nav-states', JSON.stringify(navStates));
34
- };
35
-
36
- // Set initial state on page load
37
- toggleSubmenu(isExpanded);
20
+ if (!anchor || !submenu) return;
38
21
 
22
+ // Only handle CLICK events to toggle state
39
23
  anchor.addEventListener('click', (e) => {
40
- const currentExpanded = item.getAttribute('aria-expanded') === 'true';
41
24
  const href = anchor.getAttribute('href');
42
- const isPlaceholder = !href || href === '#' || href === '';
43
-
44
- if (!currentExpanded) {
45
- toggleSubmenu(true);
46
- } else if (isPlaceholder || e.target.closest('.collapse-icon')) {
47
- toggleSubmenu(false);
48
- }
25
+ // If it's a placeholder link (#) OR the user clicked the arrow icon
26
+ const isToggleAction = !href || href === '#' || e.target.closest('.collapse-icon');
49
27
 
50
- if (isPlaceholder || e.target.closest('.collapse-icon')) {
28
+ if (isToggleAction) {
51
29
  e.preventDefault();
30
+
31
+ // Toggle Logic
32
+ const isExpanded = item.getAttribute('aria-expanded') === 'true';
33
+ const newState = !isExpanded;
34
+
35
+ item.setAttribute('aria-expanded', newState);
36
+ submenu.style.display = newState ? 'block' : 'none';
52
37
  }
53
38
  });
54
-
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
- });*/
68
39
  });
69
40
  }
70
41
 
71
42
  // --- Mobile Menu Logic ---
72
43
  function initializeMobileMenus() {
73
- // 1. Sidebar Toggle
74
44
  const sidebarBtn = document.querySelector('.sidebar-menu-button');
75
45
  const sidebar = document.querySelector('.sidebar');
76
46
 
77
47
  if (sidebarBtn && sidebar) {
78
48
  sidebarBtn.addEventListener('click', (e) => {
79
- e.stopPropagation(); // Prevent bubbling
49
+ e.stopPropagation();
80
50
  sidebar.classList.toggle('mobile-expanded');
81
51
  });
82
52
  }
83
53
 
84
- // 2. TOC Toggle
85
54
  const tocBtn = document.querySelector('.toc-menu-button');
86
55
  const tocContainer = document.querySelector('.toc-container');
87
- // Also allow clicking the title text to toggle
88
56
  const tocTitle = document.querySelector('.toc-title');
89
57
 
90
58
  const toggleToc = (e) => {
91
- // Only engage on mobile view (check if button is visible)
92
59
  if (window.getComputedStyle(tocBtn).display === 'none') return;
93
-
94
60
  e.stopPropagation();
95
61
  tocContainer.classList.toggle('mobile-expanded');
96
62
  };
97
63
 
98
64
  if (tocBtn && tocContainer) {
99
65
  tocBtn.addEventListener('click', toggleToc);
100
- if (tocTitle) {
101
- tocTitle.addEventListener('click', toggleToc);
102
- }
66
+ if (tocTitle) tocTitle.addEventListener('click', toggleToc);
103
67
  }
104
68
  }
105
69
 
106
- // --- Sidebar Scroll Preservation ---
70
+ // --- Sidebar Scroll Preservation (Instant Center) ---
107
71
  function initializeSidebarScroll() {
108
72
  const sidebar = document.querySelector('.sidebar');
109
73
  if (!sidebar) return;
110
74
 
111
- setTimeout(() => {
112
- const activeElement = sidebar.querySelector('a.active') || sidebar.querySelector('.active-parent > a');
75
+ // Wait for the layout to be stable
76
+ requestAnimationFrame(() => {
77
+ // Find the active link
78
+ const activeElement = sidebar.querySelector('a.active');
113
79
 
114
80
  if (activeElement) {
115
- const sidebarRect = sidebar.getBoundingClientRect();
116
- const elementRect = activeElement.getBoundingClientRect();
117
-
118
- // Check if the element's top or bottom is outside the sidebar's visible area
119
- const isNotInView = elementRect.top < sidebarRect.top || elementRect.bottom > sidebarRect.bottom;
120
-
121
- if (isNotInView) {
122
- activeElement.scrollIntoView({
123
- behavior: 'auto',
124
- block: 'center',
125
- inline: 'nearest'
126
- });
127
- }
81
+ activeElement.scrollIntoView({
82
+ behavior: 'auto', // INSTANT jump (prevents scrolling animation jitter)
83
+ block: 'center', // Center it vertically in the sidebar
84
+ inline: 'nearest'
85
+ });
128
86
  }
129
- }, 10);
87
+ });
130
88
  }
131
89
 
132
90
  // --- Theme Toggle Logic ---
@@ -138,7 +96,6 @@ function setupThemeToggleListener() {
138
96
  document.body.setAttribute('data-theme', theme);
139
97
  localStorage.setItem('docmd-theme', theme);
140
98
 
141
- // Switch highlight.js theme
142
99
  const highlightThemeLink = document.getElementById('highlight-theme');
143
100
  if (highlightThemeLink) {
144
101
  const newHref = highlightThemeLink.getAttribute('data-base-href') + `docmd-highlight-${theme}.css`;
@@ -146,7 +103,6 @@ function setupThemeToggleListener() {
146
103
  }
147
104
  }
148
105
 
149
- // Add click listener to the toggle button
150
106
  if (themeToggleButton) {
151
107
  themeToggleButton.addEventListener('click', () => {
152
108
  const currentTheme = document.documentElement.getAttribute('data-theme');
@@ -161,22 +117,15 @@ function initializeSidebarToggle() {
161
117
  const toggleButton = document.getElementById('sidebar-toggle-button');
162
118
  const body = document.body;
163
119
 
164
- if (!body.classList.contains('sidebar-collapsible') || !toggleButton) {
165
- return;
166
- }
120
+ if (!body.classList.contains('sidebar-collapsible') || !toggleButton) return;
167
121
 
168
122
  const defaultConfigCollapsed = body.dataset.defaultCollapsed === 'true';
169
123
  let isCollapsed = localStorage.getItem('docmd-sidebar-collapsed');
170
124
 
171
- if (isCollapsed === null) {
172
- isCollapsed = defaultConfigCollapsed;
173
- } else {
174
- isCollapsed = isCollapsed === 'true';
175
- }
125
+ if (isCollapsed === null) isCollapsed = defaultConfigCollapsed;
126
+ else isCollapsed = isCollapsed === 'true';
176
127
 
177
- if (isCollapsed) {
178
- body.classList.add('sidebar-collapsed');
179
- }
128
+ if (isCollapsed) body.classList.add('sidebar-collapsed');
180
129
 
181
130
  toggleButton.addEventListener('click', () => {
182
131
  body.classList.toggle('sidebar-collapsed');
@@ -197,9 +146,7 @@ function initializeTabs() {
197
146
  tabPanes.forEach(pane => pane.classList.remove('active'));
198
147
 
199
148
  navItem.classList.add('active');
200
- if (tabPanes[index]) {
201
- tabPanes[index].classList.add('active');
202
- }
149
+ if (tabPanes[index]) tabPanes[index].classList.add('active');
203
150
  });
204
151
  });
205
152
  });
@@ -207,9 +154,7 @@ function initializeTabs() {
207
154
 
208
155
  // --- Copy Code Button Logic ---
209
156
  function initializeCopyCodeButtons() {
210
- if (document.body.dataset.copyCodeEnabled !== 'true') {
211
- return;
212
- }
157
+ if (document.body.dataset.copyCodeEnabled !== 'true') return;
213
158
 
214
159
  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>`;
215
160
  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>`;
@@ -218,18 +163,12 @@ function initializeCopyCodeButtons() {
218
163
  const codeElement = preElement.querySelector('code');
219
164
  if (!codeElement) return;
220
165
 
221
- // Create a wrapper div around the pre element
222
166
  const wrapper = document.createElement('div');
223
167
  wrapper.style.position = 'relative';
224
168
  wrapper.style.display = 'block';
225
169
 
226
- // Insert the wrapper before the pre element
227
170
  preElement.parentNode.insertBefore(wrapper, preElement);
228
-
229
- // Move the pre element into the wrapper
230
171
  wrapper.appendChild(preElement);
231
-
232
- // Remove the relative positioning from pre since wrapper handles it
233
172
  preElement.style.position = 'static';
234
173
 
235
174
  const copyButton = document.createElement('button');
@@ -260,21 +199,11 @@ function syncBodyTheme() {
260
199
  if (currentTheme && document.body) {
261
200
  document.body.setAttribute('data-theme', currentTheme);
262
201
  }
263
-
264
- // Also ensure highlight CSS matches the current theme
265
- const highlightThemeLink = document.getElementById('highlight-theme');
266
- if (highlightThemeLink && currentTheme) {
267
- const baseHref = highlightThemeLink.getAttribute('data-base-href');
268
- if (baseHref) {
269
- const newHref = baseHref + `docmd-highlight-${currentTheme}.css`;
270
- highlightThemeLink.setAttribute('href', newHref);
271
- }
272
- }
273
202
  }
274
203
 
275
204
  // --- Main Execution ---
276
205
  document.addEventListener('DOMContentLoaded', () => {
277
- syncBodyTheme(); // Sync body theme with html theme
206
+ syncBodyTheme();
278
207
  setupThemeToggleListener();
279
208
  initializeSidebarToggle();
280
209
  initializeTabs();
@@ -66,6 +66,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
66
66
  const md = createMarkdownItInstance(config);
67
67
  const shouldMinify = !options.isDev && config.minify !== false;
68
68
  const searchIndexData = [];
69
+ const isOfflineMode = options.offline === true;
69
70
 
70
71
  if (!await fs.pathExists(SRC_DIR)) {
71
72
  throw new Error(`Source directory not found: ${formatPathForDisplay(SRC_DIR, CWD)}`);
@@ -287,10 +288,11 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
287
288
  const currentPagePathForNav = normalizedPath;
288
289
 
289
290
  const navigationHtml = await generateNavigationHtml(
290
- config.navigation,
291
- currentPagePathForNav,
292
- relativePathToRoot,
293
- config
291
+ config.navigation,
292
+ normalizedPath,
293
+ relativePathToRoot,
294
+ config,
295
+ isOfflineMode
294
296
  );
295
297
 
296
298
  // Find previous and next pages for navigation
@@ -320,9 +322,10 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
320
322
  nextPage: nextPage,
321
323
  currentPagePath: normalizedPath,
322
324
  headings: headings || [],
325
+ isOfflineMode,
323
326
  };
324
327
 
325
- const pageHtml = await generateHtmlPage(pageDataForTemplate);
328
+ const pageHtml = await generateHtmlPage(pageDataForTemplate, isOfflineMode);
326
329
 
327
330
  await fs.ensureDir(path.dirname(finalOutputHtmlPath));
328
331
  await fs.writeFile(finalOutputHtmlPath, pageHtml);
@@ -369,7 +372,9 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
369
372
 
370
373
  // Generate search index if enabled
371
374
  if (config.search !== false) {
372
- console.log('🔍 Generating search index...');
375
+ if (!options.isDev) {
376
+ console.log('🔍 Generating search index...');
377
+ }
373
378
 
374
379
  // Create MiniSearch instance
375
380
  const miniSearch = new MiniSearch({