@mgks/docmd 0.1.2 → 0.1.4

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.
Files changed (38) hide show
  1. package/.github/workflows/deploy-docmd.yml +2 -2
  2. package/README.md +3 -1
  3. package/assets/css/welcome.css +378 -0
  4. package/assets/images/preview-dark-1.png +0 -0
  5. package/assets/images/preview-dark-2.png +0 -0
  6. package/assets/images/preview-dark-3.png +0 -0
  7. package/assets/images/preview-light-1.png +0 -0
  8. package/assets/images/preview-light-2.png +0 -0
  9. package/assets/images/preview-light-3.png +0 -0
  10. package/config.js +8 -3
  11. package/docs/cli-commands.md +1 -2
  12. package/docs/configuration.md +34 -22
  13. package/docs/content/frontmatter.md +2 -2
  14. package/docs/content/index.md +5 -4
  15. package/docs/content/markdown-syntax.md +4 -4
  16. package/docs/content/no-style-example.md +110 -0
  17. package/docs/content/no-style-pages.md +202 -0
  18. package/docs/contributing.md +7 -0
  19. package/docs/deployment.md +22 -31
  20. package/docs/getting-started/basic-usage.md +3 -2
  21. package/docs/getting-started/index.md +3 -3
  22. package/docs/getting-started/installation.md +1 -1
  23. package/docs/index.md +137 -53
  24. package/docs/overview.md +56 -0
  25. package/docs/plugins/sitemap.md +1 -1
  26. package/docs/theming/assets-management.md +1 -1
  27. package/docs/theming/available-themes.md +29 -51
  28. package/package.json +1 -1
  29. package/src/assets/css/docmd-main.css +2 -1
  30. package/src/assets/css/docmd-theme-ruby.css +606 -0
  31. package/src/commands/build.js +239 -203
  32. package/src/commands/dev.js +75 -30
  33. package/src/commands/init.js +2 -0
  34. package/src/core/file-processor.js +67 -5
  35. package/src/core/html-generator.js +16 -3
  36. package/src/plugins/sitemap.js +15 -1
  37. package/src/templates/layout.ejs +1 -1
  38. package/src/templates/no-style.ejs +159 -0
@@ -8,6 +8,25 @@ const fs = require('fs-extra');
8
8
  const { buildSite } = require('./build'); // Re-use the build logic
9
9
  const { loadConfig } = require('../core/config-loader');
10
10
 
11
+ /**
12
+ * Format paths for display to make them relative to CWD
13
+ * @param {string} absolutePath - The absolute path to format
14
+ * @param {string} cwd - Current working directory
15
+ * @returns {string} - Formatted relative path
16
+ */
17
+ function formatPathForDisplay(absolutePath, cwd) {
18
+ // Get the relative path from CWD
19
+ const relativePath = path.relative(cwd, absolutePath);
20
+
21
+ // If it's not a subdirectory, prefix with ./ for clarity
22
+ if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
23
+ return `./${relativePath}`;
24
+ }
25
+
26
+ // Return the relative path
27
+ return relativePath;
28
+ }
29
+
11
30
  async function startDevServer(configPathOption, options = { preserve: false }) {
12
31
  let config = await loadConfig(configPathOption); // Load initial config
13
32
  const CWD = process.cwd(); // Current Working Directory where user runs `docmd dev`
@@ -25,6 +44,9 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
25
44
  let paths = resolveConfigPaths(config);
26
45
 
27
46
  // docmd's internal templates and assets (for live dev of docmd itself)
47
+ const DOCMD_COMMANDS_DIR = path.resolve(__dirname, '..', 'commands');
48
+ const DOCMD_CORE_DIR = path.resolve(__dirname, '..', 'core');
49
+ const DOCMD_PLUGINS_DIR = path.resolve(__dirname, '..', 'plugins');
28
50
  const DOCMD_TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
29
51
  const DOCMD_ASSETS_DIR = path.resolve(__dirname, '..', 'assets');
30
52
 
@@ -48,22 +70,24 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
48
70
  console.error('WebSocket Server error:', error);
49
71
  });
50
72
 
51
-
52
73
  function broadcastReload() {
53
- // console.log('Broadcasting reload to', wsClients.size, 'clients');
54
74
  wsClients.forEach(client => {
55
75
  if (client.readyState === WebSocket.OPEN) {
56
- client.send('reload');
76
+ try {
77
+ client.send('reload');
78
+ } catch (error) {
79
+ console.error('Error sending reload command to client:', error);
80
+ }
57
81
  }
58
82
  });
59
83
  }
60
84
 
61
- // Inject live reload script into HTML
85
+ // Inject live reload script into HTML responses
62
86
  app.use((req, res, next) => {
63
- if (req.path.endsWith('.html')) {
87
+ if (req.path.endsWith('.html') || !req.path.includes('.')) {
64
88
  const originalSend = res.send;
65
- res.send = function (body) {
66
- if (typeof body === 'string') {
89
+ res.send = function(body) {
90
+ if (typeof body === 'string' && body.includes('</body>')) {
67
91
  const liveReloadScript = `
68
92
  <script>
69
93
  (function() {
@@ -156,7 +180,7 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
156
180
  // Initial build
157
181
  console.log('🚀 Performing initial build for dev server...');
158
182
  try {
159
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve }); // Use the original config path option
183
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true }); // Use the original config path option
160
184
  console.log('✅ Initial build complete.');
161
185
  } catch (error) {
162
186
  console.error('❌ Initial build failed:', error.message, error.stack);
@@ -178,22 +202,22 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
178
202
  }
179
203
 
180
204
  // Add internal paths for docmd development (not shown to end users)
181
- const internalPaths = [DOCMD_TEMPLATES_DIR, DOCMD_ASSETS_DIR];
205
+ const internalPaths = [DOCMD_TEMPLATES_DIR, DOCMD_ASSETS_DIR, DOCMD_COMMANDS_DIR, DOCMD_CORE_DIR, DOCMD_PLUGINS_DIR];
182
206
 
183
207
  // Only in development environments, we might want to watch internal files too
184
208
  if (process.env.DOCMD_DEV === 'true') {
185
209
  watchedPaths.push(...internalPaths);
186
210
  }
187
-
211
+
188
212
  console.log(`👀 Watching for changes in:`);
189
- console.log(` - Source: ${paths.srcDirToWatch}`);
190
- console.log(` - Config: ${paths.configFileToWatch}`);
213
+ console.log(` - Source: ${formatPathForDisplay(paths.srcDirToWatch, CWD)}`);
214
+ console.log(` - Config: ${formatPathForDisplay(paths.configFileToWatch, CWD)}`);
191
215
  if (userAssetsDirExists) {
192
- console.log(` - Assets: ${paths.userAssetsDir}`);
216
+ console.log(` - Assets: ${formatPathForDisplay(paths.userAssetsDir, CWD)}`);
193
217
  }
194
218
  if (process.env.DOCMD_DEV === 'true') {
195
- console.log(` - docmd Templates: ${DOCMD_TEMPLATES_DIR} (internal)`);
196
- console.log(` - docmd Assets: ${DOCMD_ASSETS_DIR} (internal)`);
219
+ console.log(` - docmd Templates: ${formatPathForDisplay(DOCMD_TEMPLATES_DIR, CWD)} (internal)`);
220
+ console.log(` - docmd Assets: ${formatPathForDisplay(DOCMD_ASSETS_DIR, CWD)} (internal)`);
197
221
  }
198
222
 
199
223
  const watcher = chokidar.watch(watchedPaths, {
@@ -219,7 +243,7 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
219
243
  // For simplicity, we might need to restart the watcher or inform user to restart dev server if srcDir/outputDir change.
220
244
  // For now, we'll at least update the static server path.
221
245
  if (newPaths.outputDir !== paths.outputDir) {
222
- console.log(`Output directory changed from ${paths.outputDir} to ${newPaths.outputDir}. Updating static server.`);
246
+ console.log(`Output directory changed from ${formatPathForDisplay(paths.outputDir, CWD)} to ${formatPathForDisplay(newPaths.outputDir, CWD)}. Updating static server.`);
223
247
  staticMiddleware = express.static(newPaths.outputDir);
224
248
  }
225
249
  // If srcDirToWatch changes, chokidar won't automatically pick it up.
@@ -228,9 +252,9 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
228
252
  paths = newPaths; // Update paths for next build reference
229
253
  }
230
254
 
231
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve }); // Re-build using the potentially updated config path
255
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true }); // Re-build using the potentially updated config path
232
256
  broadcastReload();
233
- console.log('✅ Rebuild complete. Browser will refresh automatically.');
257
+ console.log('✅ Rebuild complete.');
234
258
  } catch (error) {
235
259
  console.error('❌ Rebuild failed:', error.message, error.stack);
236
260
  }
@@ -238,19 +262,40 @@ async function startDevServer(configPathOption, options = { preserve: false }) {
238
262
 
239
263
  watcher.on('error', error => console.error(`Watcher error: ${error}`));
240
264
 
265
+ // Try different ports if the default port is in use
241
266
  const PORT = process.env.PORT || 3000;
242
- server.listen(PORT, async () => {
243
- // Check if index.html exists after initial build
244
- const indexHtmlPath = path.join(paths.outputDir, 'index.html');
245
- if (!await fs.pathExists(indexHtmlPath)) {
246
- console.warn(`⚠️ Warning: ${indexHtmlPath} not found after initial build.
247
- The dev server is running, but you might see a 404 for the root page.
248
- Ensure your '${config.srcDir}' directory contains an 'index.md' or your navigation points to existing files.`);
249
- }
250
- console.log(`🎉 Dev server started at http://localhost:${PORT}`);
251
- console.log(`Serving content from: ${paths.outputDir}`);
252
- console.log(`Live reload is active. Browser will refresh automatically when files change.`);
253
- });
267
+ const MAX_PORT_ATTEMPTS = 10;
268
+ let currentPort = PORT;
269
+
270
+ // Function to try starting the server on different ports
271
+ function tryStartServer(port, attempt = 1) {
272
+ server.listen(port)
273
+ .on('listening', async () => {
274
+ // Check if index.html exists after initial build
275
+ const indexHtmlPath = path.join(paths.outputDir, 'index.html');
276
+ if (!await fs.pathExists(indexHtmlPath)) {
277
+ console.warn(`⚠️ Warning: ${formatPathForDisplay(indexHtmlPath, CWD)} not found after initial build.
278
+ The dev server is running, but you might see a 404 for the root page.
279
+ Ensure your '${config.srcDir}' directory contains an 'index.md' or your navigation points to existing files.`);
280
+ }
281
+ console.log(`🎉 Dev server started at http://localhost:${port}`);
282
+ console.log(`Serving content from: ${formatPathForDisplay(paths.outputDir, CWD)}`);
283
+ console.log(`Live reload is active. Browser will refresh automatically when files change.`);
284
+ })
285
+ .on('error', (err) => {
286
+ if (err.code === 'EADDRINUSE' && attempt < MAX_PORT_ATTEMPTS) {
287
+ console.log(`Port ${port} is in use, trying port ${port + 1}...`);
288
+ server.close();
289
+ tryStartServer(port + 1, attempt + 1);
290
+ } else {
291
+ console.error(`Failed to start server: ${err.message}`);
292
+ process.exit(1);
293
+ }
294
+ });
295
+ }
296
+
297
+ // Start the server with port fallback
298
+ tryStartServer(currentPort);
254
299
 
255
300
  // Graceful shutdown
256
301
  process.on('SIGINT', () => {
@@ -35,6 +35,7 @@ module.exports = {
35
35
  // Custom JavaScript Files
36
36
  customJs: [ // Array of paths to custom JS files, loaded at end of body
37
37
  // '/assets/js/custom-script.js', // Paths relative to outputDir root
38
+ '/assets/js/docmd-image-lightbox.js', // Image lightbox functionality
38
39
  ],
39
40
 
40
41
  // Plugins Configuration
@@ -85,6 +86,7 @@ module.exports = {
85
86
  { title: 'Documentation', path: 'https://docmd.mgks.dev', icon: 'scroll', external: true },
86
87
  { title: 'Installation', path: 'https://docmd.mgks.dev/getting-started/installation', icon: 'download', external: true },
87
88
  { title: 'Basic Usage', path: 'https://docmd.mgks.dev/getting-started/basic-usage', icon: 'play', external: true },
89
+ { title: 'Content', path: 'https://docmd.mgks.dev/content', icon: 'layout-template', external: true },
88
90
  ],
89
91
  },
90
92
  // External links:
@@ -5,6 +5,21 @@ const matter = require('gray-matter');
5
5
  const hljs = require('highlight.js');
6
6
  const container = require('markdown-it-container');
7
7
  const attrs = require('markdown-it-attrs');
8
+ const path = require('path'); // Add path module for findMarkdownFiles
9
+
10
+ // Function to format paths for display (relative to CWD)
11
+ function formatPathForDisplay(absolutePath) {
12
+ const CWD = process.cwd();
13
+ const relativePath = path.relative(CWD, absolutePath);
14
+
15
+ // If it's not a subdirectory, prefix with ./ for clarity
16
+ if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
17
+ return `./${relativePath}`;
18
+ }
19
+
20
+ // Return the relative path
21
+ return relativePath;
22
+ }
8
23
 
9
24
  const md = new MarkdownIt({
10
25
  html: true,
@@ -335,7 +350,7 @@ function extractHeadingsFromHtml(htmlContent) {
335
350
  return headings;
336
351
  }
337
352
 
338
- async function processMarkdownFile(filePath) {
353
+ async function processMarkdownFile(filePath, options = { isDev: false }) {
339
354
  const rawContent = await fs.readFile(filePath, 'utf8');
340
355
  let frontmatter, markdownContent;
341
356
 
@@ -346,21 +361,43 @@ async function processMarkdownFile(filePath) {
346
361
  } catch (e) {
347
362
  if (e.name === 'YAMLException') {
348
363
  // Provide more specific error for YAML parsing issues
349
- const errorMessage = `Error parsing YAML frontmatter in ${filePath}: ${e.reason || e.message}${e.mark ? ` at line ${e.mark.line + 1}, column ${e.mark.column + 1}` : ''}. Please check the syntax.`;
364
+ const errorMessage = `Error parsing YAML frontmatter in ${formatPathForDisplay(filePath)}: ${e.reason || e.message}${e.mark ? ` at line ${e.mark.line + 1}, column ${e.mark.column + 1}` : ''}. Please check the syntax.`;
350
365
  console.error(`❌ ${errorMessage}`);
351
366
  throw new Error(errorMessage); // Propagate error to stop build/dev
352
367
  }
353
368
  // For other errors from gray-matter or unknown errors
354
- console.error(`❌ Error processing frontmatter in ${filePath}: ${e.message}`);
369
+ console.error(`❌ Error processing frontmatter in ${formatPathForDisplay(filePath)}: ${e.message}`);
355
370
  throw e;
356
371
  }
357
372
 
358
373
  if (!frontmatter.title) {
359
- console.warn(`⚠️ Warning: Markdown file ${filePath} is missing a 'title' in its frontmatter. Using filename as fallback.`);
374
+ console.warn(`⚠️ Warning: Markdown file ${formatPathForDisplay(filePath)} is missing a 'title' in its frontmatter. Using filename as fallback.`);
360
375
  // Fallback title, or you could make it an error
361
376
  // frontmatter.title = path.basename(filePath, path.extname(filePath));
362
377
  }
363
378
 
379
+ // Special handling for no-style pages with HTML content
380
+ if (frontmatter.noStyle === true) {
381
+ // Only log when not in dev mode to reduce console output during dev
382
+ if (!options.isDev) {
383
+ console.log(`📄 Processing no-style page: ${formatPathForDisplay(filePath)} - Using raw HTML content`);
384
+ }
385
+
386
+ // For no-style pages, we'll use the raw content directly
387
+ // No markdown processing, no HTML escaping
388
+ const htmlContent = markdownContent;
389
+
390
+ // Extract headings for table of contents (if needed)
391
+ const headings = extractHeadingsFromHtml(htmlContent);
392
+
393
+ return {
394
+ frontmatter,
395
+ htmlContent,
396
+ headings,
397
+ };
398
+ }
399
+
400
+ // Regular processing for standard pages
364
401
  // Check if this is a documentation example showing how to use containers
365
402
  const isContainerDocumentation = markdownContent.includes('containerName [optionalTitleOrType]') ||
366
403
  markdownContent.includes('## Callouts') ||
@@ -413,4 +450,29 @@ async function processMarkdownFile(filePath) {
413
450
  };
414
451
  }
415
452
 
416
- module.exports = { processMarkdownFile, mdInstance: md, extractHeadingsFromHtml }; // Export mdInstance if needed by plugins for consistency
453
+ // Add findMarkdownFiles function
454
+ /**
455
+ * Recursively finds all Markdown files in a directory and its subdirectories
456
+ * @param {string} dir - Directory to search in
457
+ * @returns {Promise<string[]>} - Array of file paths
458
+ */
459
+ async function findMarkdownFiles(dir) {
460
+ let files = [];
461
+ const items = await fs.readdir(dir, { withFileTypes: true });
462
+ for (const item of items) {
463
+ const fullPath = path.join(dir, item.name);
464
+ if (item.isDirectory()) {
465
+ files = files.concat(await findMarkdownFiles(fullPath));
466
+ } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.markdown'))) {
467
+ files.push(fullPath);
468
+ }
469
+ }
470
+ return files;
471
+ }
472
+
473
+ module.exports = {
474
+ processMarkdownFile,
475
+ mdInstance: md,
476
+ extractHeadingsFromHtml,
477
+ findMarkdownFiles // Export the findMarkdownFiles function
478
+ };
@@ -18,7 +18,7 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
18
18
  // 1. Favicon (built-in handling)
19
19
  if (config.favicon) {
20
20
  const faviconPath = config.favicon.startsWith('/') ? config.favicon.substring(1) : config.favicon;
21
- faviconLinkHtml = ` <link rel="icon" href="${relativePathToRoot}${faviconPath}">\n`;
21
+ faviconLinkHtml = `<link rel="shortcut icon" href="${relativePathToRoot}${faviconPath}" type="image/x-icon">\n`;
22
22
  }
23
23
 
24
24
  // 2. Theme CSS (built-in handling for theme.name)
@@ -75,9 +75,21 @@ async function generateHtmlPage(templateData) {
75
75
  footerHtml = mdInstance.renderInline(config.footer);
76
76
  }
77
77
 
78
- const layoutTemplatePath = path.join(__dirname, '..', 'templates', 'layout.ejs');
78
+ // Determine which template to use based on frontmatter
79
+ let templateName = 'layout.ejs';
80
+ if (frontmatter.noStyle === true) {
81
+ templateName = 'no-style.ejs';
82
+
83
+ // For no-style pages, ensure we're passing the raw HTML content
84
+ // without any additional processing or escaping
85
+ if (content.includes('&lt;') || content.includes('&gt;')) {
86
+ console.warn(`⚠️ Warning: HTML content in no-style page appears to be escaped. This may cause rendering issues.`);
87
+ }
88
+ }
89
+
90
+ const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
79
91
  if (!await fs.pathExists(layoutTemplatePath)) {
80
- throw new Error(`Layout template not found: ${layoutTemplatePath}`);
92
+ throw new Error(`Template not found: ${layoutTemplatePath}`);
81
93
  }
82
94
  const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
83
95
 
@@ -105,6 +117,7 @@ async function generateHtmlPage(templateData) {
105
117
  currentPagePath, // Pass the current page path for active state detection
106
118
  headings: headings || [], // Pass headings for TOC, default to empty array if not provided
107
119
  isActivePage, // Flag to determine if TOC should be shown
120
+ frontmatter, // Pass the entire frontmatter for no-style template
108
121
  ...pluginOutputs, // Spread all plugin generated HTML strings
109
122
  };
110
123
 
@@ -1,6 +1,20 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
 
4
+ // Function to format paths for display (relative to CWD)
5
+ function formatPathForDisplay(absolutePath) {
6
+ const CWD = process.cwd();
7
+ const relativePath = path.relative(CWD, absolutePath);
8
+
9
+ // If it's not a subdirectory, prefix with ./ for clarity
10
+ if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
11
+ return `./${relativePath}`;
12
+ }
13
+
14
+ // Return the relative path
15
+ return relativePath;
16
+ }
17
+
4
18
  /**
5
19
  * Generate sitemap.xml in the output directory root
6
20
  * @param {Object} config - The full configuration object
@@ -100,7 +114,7 @@ async function generateSitemap(config, pages, outputDir, options = { isDev: fals
100
114
 
101
115
  // Only show sitemap generation message in production mode or if DOCMD_DEV is true
102
116
  if (!options.isDev || process.env.DOCMD_DEV === 'true') {
103
- console.log(`✅ Generated sitemap at ${sitemapPath}`);
117
+ console.log(`✅ Generated sitemap at ${formatPathForDisplay(sitemapPath)}`);
104
118
  }
105
119
  }
106
120
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  <%- metaTagsHtml || '' %> <%# SEO Plugin Meta Tags %>
8
8
 
9
- <title><%= pageTitle %> | <%= siteTitle %></title>
9
+ <title><%= pageTitle %> : <%= siteTitle %></title>
10
10
  <% if (description && !(metaTagsHtml && metaTagsHtml.includes('name="description"'))) { %>
11
11
  <meta name="description" content="<%= description %>">
12
12
  <% } %>
@@ -0,0 +1,159 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
7
+ <% if (frontmatter.components?.meta !== false) { %>
8
+ <%- metaTagsHtml || '' %>
9
+ <title><%= pageTitle %><% if (frontmatter.components?.siteTitle !== false) { %> | <%= siteTitle %><% } %></title>
10
+ <% if (description && !(metaTagsHtml && metaTagsHtml.includes('name="description"'))) { %>
11
+ <meta name="description" content="<%= description %>">
12
+ <% } %>
13
+ <% } %>
14
+
15
+ <% if (frontmatter.components?.favicon !== false) { %>
16
+ <%- faviconLinkHtml || '' %>
17
+ <% } %>
18
+
19
+ <% if (frontmatter.components?.css !== false) { %>
20
+ <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css">
21
+ <% if (frontmatter.components?.highlight !== false) { %>
22
+ <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css" id="highlight-theme">
23
+ <% } %>
24
+ <% } %>
25
+
26
+ <% if (frontmatter.components?.theme !== false) { %>
27
+ <%- themeCssLinkHtml || '' %>
28
+ <% } %>
29
+
30
+ <% if (frontmatter.components?.customCss !== false && customCssFiles && customCssFiles.length > 0) { %>
31
+ <% customCssFiles.forEach(cssFile => { %>
32
+ <link rel="stylesheet" href="<%= relativePathToRoot %><%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>">
33
+ <% }); %>
34
+ <% } %>
35
+
36
+ <% if (frontmatter.components?.pluginStyles !== false) { %>
37
+ <%- pluginStylesHtml || '' %>
38
+ <% } %>
39
+
40
+ <% if (frontmatter.components?.pluginHeadScripts !== false) { %>
41
+ <%- pluginHeadScriptsHtml || '' %>
42
+ <% } %>
43
+
44
+ <% if (frontmatter.customHead) { %>
45
+ <%- frontmatter.customHead %>
46
+ <% } %>
47
+ </head>
48
+ <body<% if (frontmatter.components?.theme !== false) { %> data-theme="<%= defaultMode %>"<% } %><% if (frontmatter.bodyClass) { %> class="<%= frontmatter.bodyClass %>"<% } %>>
49
+ <% if (frontmatter.components?.layout === true || frontmatter.components?.layout === 'full') { %>
50
+ <div class="main-content-wrapper">
51
+ <% if (frontmatter.components?.header !== false) { %>
52
+ <header class="page-header">
53
+ <% if (frontmatter.components?.pageTitle !== false) { %>
54
+ <h1><%= pageTitle %></h1>
55
+ <% } %>
56
+ </header>
57
+ <% } %>
58
+ <main class="content-area">
59
+ <div class="content-layout">
60
+ <div class="main-content">
61
+ <%- content %>
62
+ </div>
63
+ </div>
64
+ </main>
65
+ <% if (frontmatter.components?.footer !== false) { %>
66
+ <footer class="page-footer">
67
+ <div class="footer-content">
68
+ <div class="user-footer">
69
+ <%- footerHtml || '' %>
70
+ </div>
71
+ <% if (frontmatter.components?.branding !== false) { %>
72
+ <div class="branding-footer">
73
+ 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://docmd.mgks.dev" target="_blank" rel="noopener">docmd.</a>
74
+ </div>
75
+ <% } %>
76
+ </div>
77
+ </footer>
78
+ <% } %>
79
+ </div>
80
+ <% } else if (frontmatter.components?.sidebar === true) { %>
81
+ <aside class="sidebar">
82
+ <% if (frontmatter.components?.logo !== false && logo && logo.light && logo.dark) { %>
83
+ <div class="sidebar-header">
84
+ <a href="<%= logo.href || (relativePathToRoot + 'index.html') %>" class="logo-link">
85
+ <img src="<%= relativePathToRoot %><%- logo.light.startsWith('/') ? logo.light.substring(1) : logo.light %>" alt="<%= logo.alt || siteTitle %>" class="logo-light" <% if (logo.height) { %>style="height: <%= logo.height %>;"<% } %>>
86
+ <img src="<%= relativePathToRoot %><%- logo.dark.startsWith('/') ? logo.dark.substring(1) : logo.dark %>" alt="<%= logo.alt || siteTitle %>" class="logo-dark" <% if (logo.height) { %>style="height: <%= logo.height %>;"<% } %>>
87
+ </a>
88
+ </div>
89
+ <% } %>
90
+ <% if (frontmatter.components?.navigation !== false) { %>
91
+ <%- navigationHtml %>
92
+ <% } %>
93
+ <% if (frontmatter.components?.themeToggle !== false && theme && theme.enableModeToggle) { %>
94
+ <button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button">
95
+ <%- renderIcon('sun', { class: 'icon-sun' }) %>
96
+ <%- renderIcon('moon', { class: 'icon-moon' }) %>
97
+ </button>
98
+ <% } %>
99
+ </aside>
100
+ <div class="main-content-wrapper">
101
+ <% if (frontmatter.components?.header !== false) { %>
102
+ <header class="page-header">
103
+ <% if (frontmatter.components?.pageTitle !== false) { %>
104
+ <h1><%= pageTitle %></h1>
105
+ <% } %>
106
+ </header>
107
+ <% } %>
108
+ <main class="content-area">
109
+ <div class="content-layout">
110
+ <div class="main-content">
111
+ <%- content %>
112
+ </div>
113
+ <% if (frontmatter.components?.toc !== false && headings && headings.length > 0) { %>
114
+ <div class="toc-sidebar">
115
+ <%- include('toc', { content, headings, navigationHtml, isActivePage }) %>
116
+ </div>
117
+ <% } %>
118
+ </div>
119
+ </main>
120
+ <% if (frontmatter.components?.footer !== false) { %>
121
+ <footer class="page-footer">
122
+ <div class="footer-content">
123
+ <div class="user-footer">
124
+ <%- footerHtml || '' %>
125
+ </div>
126
+ <% if (frontmatter.components?.branding !== false) { %>
127
+ <div class="branding-footer">
128
+ 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://docmd.mgks.dev" target="_blank" rel="noopener">docmd.</a>
129
+ </div>
130
+ <% } %>
131
+ </div>
132
+ </footer>
133
+ <% } %>
134
+ </div>
135
+ <% } else { %>
136
+ <%- content %>
137
+ <% } %>
138
+
139
+ <% if (frontmatter.components?.scripts !== false) { %>
140
+ <% if (frontmatter.components?.themeToggle !== false) { %>
141
+ <script src="<%= relativePathToRoot %>assets/js/docmd-theme-toggle.js"></script>
142
+ <% } %>
143
+
144
+ <% if (frontmatter.components?.customJs !== false && customJsFiles && customJsFiles.length > 0) { %>
145
+ <% customJsFiles.forEach(jsFile => { %>
146
+ <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>"></script>
147
+ <% }); %>
148
+ <% } %>
149
+
150
+ <% if (frontmatter.components?.pluginBodyScripts !== false) { %>
151
+ <%- pluginBodyScriptsHtml || '' %>
152
+ <% } %>
153
+ <% } %>
154
+
155
+ <% if (frontmatter.customScripts) { %>
156
+ <%- frontmatter.customScripts %>
157
+ <% } %>
158
+ </body>
159
+ </html>