@mgks/docmd 0.1.4 → 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.
Files changed (52) hide show
  1. package/README.md +2 -4
  2. package/assets/css/welcome.css +5 -377
  3. package/assets/images/preview-dark-1.webp +0 -0
  4. package/assets/images/preview-dark-2.webp +0 -0
  5. package/assets/images/preview-dark-3.webp +0 -0
  6. package/assets/images/preview-light-1.webp +0 -0
  7. package/assets/images/preview-light-2.webp +0 -0
  8. package/assets/images/preview-light-3.webp +0 -0
  9. package/config.js +40 -6
  10. package/docs/configuration.md +82 -7
  11. package/docs/content/containers/buttons.md +88 -0
  12. package/docs/content/containers/callouts.md +154 -0
  13. package/docs/content/containers/cards.md +93 -0
  14. package/docs/content/containers/index.md +35 -0
  15. package/docs/content/containers/nested-containers.md +329 -0
  16. package/docs/content/containers/steps.md +175 -0
  17. package/docs/content/containers/tabs.md +228 -0
  18. package/docs/content/custom-containers.md +19 -124
  19. package/docs/content/frontmatter.md +2 -2
  20. package/docs/content/no-style-example.md +2 -0
  21. package/docs/content/no-style-pages.md +52 -28
  22. package/docs/index.md +55 -27
  23. package/docs/plugins/seo.md +80 -31
  24. package/docs/theming/available-themes.md +17 -2
  25. package/docs/theming/light-dark-mode.md +12 -3
  26. package/package.json +21 -9
  27. package/src/assets/css/docmd-main.css +5 -806
  28. package/src/assets/css/docmd-theme-retro.css +9 -0
  29. package/src/assets/css/docmd-theme-ruby.css +7 -604
  30. package/src/assets/css/docmd-theme-sky.css +7 -649
  31. package/src/assets/js/docmd-image-lightbox.js +4 -2
  32. package/src/assets/js/docmd-main.js +157 -0
  33. package/src/commands/build.js +62 -120
  34. package/src/commands/dev.js +2 -1
  35. package/src/commands/init.js +23 -1
  36. package/src/core/config-loader.js +2 -0
  37. package/src/core/file-processor.js +669 -373
  38. package/src/core/html-generator.js +49 -40
  39. package/src/core/icon-renderer.js +3 -2
  40. package/src/plugins/analytics.js +5 -1
  41. package/src/plugins/seo.js +114 -62
  42. package/src/plugins/sitemap.js +6 -0
  43. package/src/templates/layout.ejs +40 -8
  44. package/src/templates/no-style.ejs +23 -6
  45. package/src/templates/partials/theme-init.js +26 -0
  46. package/assets/images/preview-dark-1.png +0 -0
  47. package/assets/images/preview-dark-2.png +0 -0
  48. package/assets/images/preview-dark-3.png +0 -0
  49. package/assets/images/preview-light-1.png +0 -0
  50. package/assets/images/preview-light-2.png +0 -0
  51. package/assets/images/preview-light-3.png +0 -0
  52. package/src/assets/js/docmd-theme-toggle.js +0 -59
@@ -1,51 +1,57 @@
1
- // src/core/html-generator.js
1
+ //
2
+
2
3
  const ejs = require('ejs');
3
4
  const path = require('path');
4
5
  const fs = require('fs-extra');
5
- const { mdInstance } = require('./file-processor'); // Import mdInstance for footer
6
+ const { createMarkdownItInstance } = require('./file-processor');
6
7
  const { generateSeoMetaTags } = require('../plugins/seo');
7
8
  const { generateAnalyticsScripts } = require('../plugins/analytics');
8
- const { renderIcon } = require('./icon-renderer'); // Import icon renderer
9
+ const { renderIcon } = require('./icon-renderer');
10
+
11
+ // Create a markdown instance for inline rendering
12
+ let mdInstance = null;
13
+
14
+ let themeInitScript = '';
15
+ (async () => {
16
+ const themeInitPath = path.join(__dirname, '..', 'templates', 'partials', 'theme-init.js');
17
+ if (await fs.pathExists(themeInitPath)) {
18
+ const scriptContent = await fs.readFile(themeInitPath, 'utf8');
19
+ themeInitScript = `<script>${scriptContent}</script>`;
20
+ }
21
+ })();
9
22
 
10
23
  async function processPluginHooks(config, pageData, relativePathToRoot) {
11
24
  let metaTagsHtml = '';
12
25
  let faviconLinkHtml = '';
13
- let themeCssLinkHtml = ''; // For theme.name CSS file
14
- let pluginStylesHtml = ''; // For plugin-specific CSS
26
+ let themeCssLinkHtml = '';
27
+ let pluginStylesHtml = '';
15
28
  let pluginHeadScriptsHtml = '';
16
29
  let pluginBodyScriptsHtml = '';
17
30
 
18
- // 1. Favicon (built-in handling)
31
+ // Favicon (built-in handling)
19
32
  if (config.favicon) {
20
33
  const faviconPath = config.favicon.startsWith('/') ? config.favicon.substring(1) : config.favicon;
21
34
  faviconLinkHtml = `<link rel="shortcut icon" href="${relativePathToRoot}${faviconPath}" type="image/x-icon">\n`;
22
35
  }
23
36
 
24
- // 2. Theme CSS (built-in handling for theme.name)
37
+ // Theme CSS (built-in handling for theme.name)
25
38
  if (config.theme && config.theme.name && config.theme.name !== 'default') {
26
- // Assumes theme CSS files are like 'docmd-theme-yourthemename.css' in assets/css
27
39
  const themeCssPath = `assets/css/docmd-theme-${config.theme.name}.css`;
28
- // Check if theme file exists before linking (optional, good practice)
29
- // For now, assume it will exist if specified.
30
40
  themeCssLinkHtml = ` <link rel="stylesheet" href="${relativePathToRoot}${themeCssPath}">\n`;
31
41
  }
32
42
 
33
-
34
- // 3. SEO Plugin (if configured)
43
+ // SEO Plugin (if configured)
35
44
  if (config.plugins?.seo) {
36
45
  metaTagsHtml += generateSeoMetaTags(config, pageData, relativePathToRoot);
37
46
  }
38
47
 
39
- // 4. Analytics Plugin (if configured)
48
+ // Analytics Plugin (if configured)
40
49
  if (config.plugins?.analytics) {
41
50
  const analyticsScripts = generateAnalyticsScripts(config, pageData);
42
51
  pluginHeadScriptsHtml += analyticsScripts.headScriptsHtml;
43
52
  pluginBodyScriptsHtml += analyticsScripts.bodyScriptsHtml;
44
53
  }
45
54
 
46
- // Future: Loop through a more generic plugin array if you evolve the system
47
- // for (const plugin of config.activePlugins) { /* plugin.runHook('meta', ...) */ }
48
-
49
55
  return {
50
56
  metaTagsHtml,
51
57
  faviconLinkHtml,
@@ -58,33 +64,32 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
58
64
 
59
65
  async function generateHtmlPage(templateData) {
60
66
  const {
61
- content, pageTitle, siteTitle, navigationHtml,
67
+ content, siteTitle, navigationHtml,
62
68
  relativePathToRoot, config, frontmatter, outputPath,
63
69
  prevPage, nextPage, currentPagePath, headings
64
70
  } = templateData;
65
71
 
72
+ const pageTitle = frontmatter.title;
73
+
66
74
  // Process plugins to get their HTML contributions
67
75
  const pluginOutputs = await processPluginHooks(
68
76
  config,
69
- { frontmatter, outputPath }, // pageData object
77
+ { frontmatter, outputPath },
70
78
  relativePathToRoot
71
79
  );
72
80
 
73
81
  let footerHtml = '';
74
82
  if (config.footer) {
83
+ // Initialize mdInstance if not already done
84
+ if (!mdInstance) {
85
+ mdInstance = createMarkdownItInstance(config);
86
+ }
75
87
  footerHtml = mdInstance.renderInline(config.footer);
76
88
  }
77
89
 
78
- // Determine which template to use based on frontmatter
79
90
  let templateName = 'layout.ejs';
80
91
  if (frontmatter.noStyle === true) {
81
92
  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
93
  }
89
94
 
90
95
  const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
@@ -93,42 +98,47 @@ async function generateHtmlPage(templateData) {
93
98
  }
94
99
  const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
95
100
 
96
- // Determine if this is an active page for TOC display
97
- // The currentPagePath exists and has content
98
101
  const isActivePage = currentPagePath && content && content.trim().length > 0;
99
102
 
100
103
  const ejsData = {
101
104
  content,
102
- pageTitle: frontmatter.title || pageTitle || 'Untitled', // Ensure pageTitle is robust
103
- description: frontmatter.description, // Used by layout if no SEO plugin overrides
105
+ pageTitle,
106
+ themeInitScript,
107
+ description: frontmatter.description,
104
108
  siteTitle,
105
109
  navigationHtml,
106
110
  defaultMode: config.theme?.defaultMode || 'light',
107
111
  relativePathToRoot,
108
112
  logo: config.logo,
113
+ sidebarConfig: {
114
+ collapsible: config.sidebar?.collapsible ?? false,
115
+ defaultCollapsed: config.sidebar?.defaultCollapsed ?? false,
116
+ },
109
117
  theme: config.theme,
110
118
  customCssFiles: config.theme?.customCss || [],
111
119
  customJsFiles: config.customJs || [],
120
+ sponsor: config.sponsor,
112
121
  footer: config.footer,
113
122
  footerHtml,
114
123
  renderIcon,
115
124
  prevPage,
116
125
  nextPage,
117
- currentPagePath, // Pass the current page path for active state detection
118
- headings: headings || [], // Pass headings for TOC, default to empty array if not provided
119
- isActivePage, // Flag to determine if TOC should be shown
120
- frontmatter, // Pass the entire frontmatter for no-style template
121
- ...pluginOutputs, // Spread all plugin generated HTML strings
126
+ currentPagePath,
127
+ headings: headings || [],
128
+ isActivePage,
129
+ frontmatter,
130
+ config: config,
131
+ ...pluginOutputs,
122
132
  };
123
133
 
124
134
  try {
125
135
  return ejs.render(layoutTemplate, ejsData, {
126
- filename: layoutTemplatePath // Add filename for proper include resolution
136
+ filename: layoutTemplatePath
127
137
  });
128
138
  } catch (e) {
129
139
  console.error(`❌ Error rendering EJS template for ${outputPath}: ${e.message}`);
130
- console.error("EJS Data:", JSON.stringify(ejsData, null, 2).substring(0, 1000) + "..."); // Log partial data
131
- throw e; // Re-throw to stop build
140
+ console.error("EJS Data:", JSON.stringify(ejsData, null, 2).substring(0, 1000) + "...");
141
+ throw e;
132
142
  }
133
143
  }
134
144
 
@@ -139,17 +149,16 @@ async function generateNavigationHtml(navItems, currentPagePath, relativePathToR
139
149
  }
140
150
  const navTemplate = await fs.readFile(navTemplatePath, 'utf8');
141
151
 
142
- // Make renderIcon available to the EJS template
143
152
  const ejsHelpers = { renderIcon };
144
153
 
145
154
  return ejs.render(navTemplate, {
146
155
  navItems,
147
156
  currentPagePath,
148
157
  relativePathToRoot,
149
- config, // Pass full config if needed by nav (e.g. for base path)
158
+ config,
150
159
  ...ejsHelpers
151
160
  }, {
152
- filename: navTemplatePath // Add filename for proper include resolution
161
+ filename: navTemplatePath
153
162
  });
154
163
  }
155
164
 
@@ -1,5 +1,6 @@
1
- // src/core/icon-renderer.js
2
- const lucideStatic = require('lucide-static'); // Access the raw icon data
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ const lucideStatic = require('lucide-static');
3
4
 
4
5
  // On first load, log debug information about a specific icon to understand its structure
5
6
  let debugRun = false;
@@ -1,4 +1,8 @@
1
- // src/plugins/analytics.js
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Generate analytics scripts for a page
5
+ */
2
6
 
3
7
  function generateAnalyticsScripts(config, pageData) {
4
8
  let headScriptsHtml = '';
@@ -1,65 +1,117 @@
1
- // src/plugins/seo.js
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Generate SEO meta tags for a page
5
+ */
6
+
2
7
  function generateSeoMetaTags(config, pageData, relativePathToRoot) {
3
- let metaTagsHtml = '';
4
- const { frontmatter, outputPath } = pageData;
5
-
6
- if (frontmatter.noindex) {
7
- metaTagsHtml += ' <meta name="robots" content="noindex">\n';
8
- return metaTagsHtml; // No other SEO tags if noindex
9
- }
10
-
11
- const siteTitle = config.siteTitle;
12
- const pageTitle = frontmatter.title || 'Untitled'; // Ensure pageTitle is always defined
13
- const description = frontmatter.description || config.plugins?.seo?.defaultDescription || '';
14
-
15
- // Construct pageUrl - ensure siteUrl in config has no trailing slash
16
- const siteUrl = config.siteUrl ? config.siteUrl.replace(/\/$/, '') : '';
17
- const pageSegment = outputPath.replace(/index\.html$/, '').replace(/\.html$/, '');
18
- const pageUrl = `${siteUrl}${pageSegment.startsWith('/') ? pageSegment : '/' + pageSegment}`;
19
-
20
- metaTagsHtml += ` <meta name="description" content="${description}">\n`;
21
-
22
- // Open Graph
23
- metaTagsHtml += ` <meta property="og:title" content="${pageTitle} | ${siteTitle}">\n`;
24
- metaTagsHtml += ` <meta property="og:description" content="${description}">\n`;
25
- metaTagsHtml += ` <meta property="og:url" content="${pageUrl}">\n`;
26
- metaTagsHtml += ` <meta property="og:site_name" content="${config.plugins?.seo?.openGraph?.siteName || siteTitle}">\n`;
27
-
28
- const ogImage = frontmatter.image || frontmatter.ogImage || config.plugins?.seo?.openGraph?.defaultImage;
29
- if (ogImage) {
30
- const ogImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
31
- metaTagsHtml += ` <meta property="og:image" content="${ogImageUrl}">\n`;
32
- }
33
- metaTagsHtml += ` <meta property="og:type" content="${frontmatter.ogType || 'website'}">\n`;
34
-
35
- // Twitter Card
36
- const twitterCardType = frontmatter.twitterCard || config.plugins?.seo?.twitter?.cardType || 'summary';
37
- metaTagsHtml += ` <meta name="twitter:card" content="${twitterCardType}">\n`;
38
- if (config.plugins?.seo?.twitter?.siteUsername) {
39
- metaTagsHtml += ` <meta name="twitter:site" content="${config.plugins.seo.twitter.siteUsername}">\n`;
40
- }
41
- const twitterCreator = frontmatter.twitterCreator || config.plugins?.seo?.twitter?.creatorUsername;
42
- if (twitterCreator) {
43
- metaTagsHtml += ` <meta name="twitter:creator" content="${twitterCreator}">\n`;
44
- }
45
- // Twitter title, description, image often fallback to OG tags if not explicitly set by Twitter.
46
- // For explicitness:
47
- metaTagsHtml += ` <meta name="twitter:title" content="${pageTitle} | ${siteTitle}">\n`;
48
- metaTagsHtml += ` <meta name="twitter:description" content="${description}">\n`;
49
- if (ogImage) { // Re-use ogImage for twitter:image if not specified differently
50
- const twitterImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
51
- metaTagsHtml += ` <meta name="twitter:image" content="${twitterImageUrl}">\n`;
52
- }
53
-
54
-
55
- // Keywords (optional, less impact nowadays)
56
- if (frontmatter.keywords) {
57
- const keywordsString = Array.isArray(frontmatter.keywords) ? frontmatter.keywords.join(', ') : frontmatter.keywords;
58
- metaTagsHtml += ` <meta name="keywords" content="${keywordsString}">\n`;
8
+ let metaTagsHtml = '';
9
+ const { frontmatter, outputPath } = pageData;
10
+ const seoFrontmatter = frontmatter.seo || {}; // Use nested seo object, fallback to empty
11
+
12
+ if (frontmatter.noindex || seoFrontmatter.noindex) {
13
+ metaTagsHtml += ' <meta name="robots" content="noindex">\n';
14
+ return metaTagsHtml; // No other SEO tags if noindex
15
+ }
16
+
17
+ const siteTitle = config.siteTitle;
18
+ const pageTitle = frontmatter.title || 'Untitled';
19
+ const description = seoFrontmatter.description || frontmatter.description || config.plugins?.seo?.defaultDescription || '';
20
+
21
+ const siteUrl = config.siteUrl ? config.siteUrl.replace(/\/$/, '') : '';
22
+ const pageSegment = outputPath.replace(/index\.html$/, '').replace(/\.html$/, '');
23
+ const pageUrl = `${siteUrl}${pageSegment.startsWith('/') ? pageSegment : '/' + pageSegment}`;
24
+
25
+ metaTagsHtml += ` <meta name="description" content="${description}">\n`;
26
+
27
+ const canonicalUrl = seoFrontmatter.permalink || frontmatter.permalink || seoFrontmatter.canonicalUrl || frontmatter.canonicalUrl || pageUrl;
28
+ metaTagsHtml += ` <link rel="canonical" href="${canonicalUrl}">\n`;
29
+
30
+ // Open Graph
31
+ metaTagsHtml += ` <meta property="og:title" content="${pageTitle} | ${siteTitle}">\n`;
32
+ metaTagsHtml += ` <meta property="og:description" content="${description}">\n`;
33
+ metaTagsHtml += ` <meta property="og:url" content="${pageUrl}">\n`;
34
+ metaTagsHtml += ` <meta property="og:site_name" content="${config.plugins?.seo?.openGraph?.siteName || siteTitle}">\n`;
35
+
36
+ const ogImage = seoFrontmatter.image || frontmatter.image || seoFrontmatter.ogImage || frontmatter.ogImage || config.plugins?.seo?.openGraph?.defaultImage;
37
+ if (ogImage) {
38
+ const ogImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
39
+ metaTagsHtml += ` <meta property="og:image" content="${ogImageUrl}">\n`;
40
+ }
41
+ metaTagsHtml += ` <meta property="og:type" content="${seoFrontmatter.ogType || frontmatter.ogType || 'website'}">\n`;
42
+
43
+ // Twitter Card
44
+ const twitterCardType = seoFrontmatter.twitterCard || frontmatter.twitterCard || config.plugins?.seo?.twitter?.cardType || 'summary';
45
+ metaTagsHtml += ` <meta name="twitter:card" content="${twitterCardType}">\n`;
46
+ if (config.plugins?.seo?.twitter?.siteUsername) {
47
+ metaTagsHtml += ` <meta name="twitter:site" content="${config.plugins.seo.twitter.siteUsername}">\n`;
48
+ }
49
+ const twitterCreator = seoFrontmatter.twitterCreator || frontmatter.twitterCreator || config.plugins?.seo?.twitter?.creatorUsername;
50
+ if (twitterCreator) {
51
+ metaTagsHtml += ` <meta name="twitter:creator" content="${twitterCreator}">\n`;
52
+ }
53
+ metaTagsHtml += ` <meta name="twitter:title" content="${pageTitle} | ${siteTitle}">\n`;
54
+ metaTagsHtml += ` <meta name="twitter:description" content="${description}">\n`;
55
+ if (ogImage) {
56
+ const twitterImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
57
+ metaTagsHtml += ` <meta name="twitter:image" content="${twitterImageUrl}">\n`;
58
+ }
59
+
60
+ // Keywords
61
+ const keywords = seoFrontmatter.keywords || frontmatter.keywords;
62
+ if (keywords) {
63
+ const keywordsString = Array.isArray(keywords) ? keywords.join(', ') : keywords;
64
+ metaTagsHtml += ` <meta name="keywords" content="${keywordsString}">\n`;
65
+ }
66
+
67
+ // LD+JSON Structured Data
68
+ const ldJsonConfig = seoFrontmatter.ldJson || frontmatter.ldJson;
69
+ if (ldJsonConfig) {
70
+ try {
71
+ const baseLdJson = {
72
+ '@context': 'https://schema.org',
73
+ '@type': 'Article',
74
+ mainEntityOfPage: {
75
+ '@type': 'WebPage',
76
+ '@id': canonicalUrl,
77
+ },
78
+ headline: pageTitle,
79
+ description: description,
80
+ url: canonicalUrl,
81
+ };
82
+
83
+ if (config.siteTitle) {
84
+ baseLdJson.publisher = {
85
+ '@type': 'Organization',
86
+ name: config.siteTitle,
87
+ };
88
+ if (config.logo?.light) {
89
+ baseLdJson.publisher.logo = {
90
+ '@type': 'ImageObject',
91
+ url: `${siteUrl}${config.logo.light.startsWith('/') ? config.logo.light : '/' + config.logo.light}`
92
+ };
93
+ }
94
+ }
95
+
96
+ if (ogImage) {
97
+ baseLdJson.image = `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
98
+ }
99
+
100
+ const finalLdJson = typeof ldJsonConfig === 'object'
101
+ ? { ...baseLdJson, ...ldJsonConfig }
102
+ : baseLdJson;
103
+
104
+ metaTagsHtml += ` <script type="application/ld+json">\n`;
105
+ metaTagsHtml += ` ${JSON.stringify(finalLdJson, null, 2)}\n`;
106
+ metaTagsHtml += ` </script>\n`;
107
+ } catch (e) {
108
+ console.error(`❌ Error generating LD+JSON for page: ${outputPath}`);
109
+ console.error(` Could not stringify the ldJson object. Please check its structure in the frontmatter.`);
110
+ console.error(` ${e.message}`);
59
111
  }
60
-
61
-
62
- return metaTagsHtml;
63
112
  }
64
-
65
- module.exports = { generateSeoMetaTags };
113
+
114
+ return metaTagsHtml;
115
+ }
116
+
117
+ module.exports = { generateSeoMetaTags };
@@ -1,3 +1,9 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Generate sitemap.xml in the output directory root
5
+ */
6
+
1
7
  const fs = require('fs-extra');
2
8
  const path = require('path');
3
9
 
@@ -13,9 +13,13 @@
13
13
 
14
14
  <%- faviconLinkHtml || '' %> <%# Favicon %>
15
15
 
16
+ <%- themeInitScript %>
17
+
16
18
  <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css">
17
19
 
18
- <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css" id="highlight-theme">
20
+ <% if (config.theme?.codeHighlight !== false) { %>
21
+ <link rel="stylesheet" id="highlight-theme" href="<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css" data-base-href="<%= relativePathToRoot %>assets/css/">
22
+ <% } %>
19
23
 
20
24
  <%- themeCssLinkHtml || '' %> <%# For theme.name specific CSS %>
21
25
 
@@ -27,7 +31,9 @@
27
31
 
28
32
  <%- pluginHeadScriptsHtml || '' %> <%# Plugin specific head scripts (e.g., Analytics) %>
29
33
  </head>
30
- <body data-theme="<%= defaultMode %>">
34
+ <body class="<%= sidebarConfig.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>"
35
+ data-theme="<%= defaultMode %>" data-default-collapsed="<%= sidebarConfig.defaultCollapsed %>"
36
+ data-copy-code-enabled="<%= config.copyCode === true %>">
31
37
  <aside class="sidebar">
32
38
  <div class="sidebar-header">
33
39
  <% if (logo && logo.light && logo.dark) { %>
@@ -40,7 +46,7 @@
40
46
  <% } %>
41
47
  </div>
42
48
  <%- navigationHtml %>
43
- <% if (theme && theme.enableModeToggle) { %>
49
+ <% if (theme && theme.enableModeToggle && theme.positionMode !== 'top') { %>
44
50
  <button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button">
45
51
  <%# renderIcon is available in the global EJS scope from html-generator %>
46
52
  <%- renderIcon('sun', { class: 'icon-sun' }) %>
@@ -49,9 +55,25 @@
49
55
  <% } %>
50
56
  </aside>
51
57
  <div class="main-content-wrapper">
52
- <header class="page-header">
53
- <h1><%= pageTitle %></h1>
54
- </header>
58
+ <div class="page-header">
59
+ <div class="header-left">
60
+ <% if (sidebarConfig.collapsible) { %>
61
+ <button id="sidebar-toggle-button" class="sidebar-toggle-button" aria-label="Toggle Sidebar">
62
+ <%- renderIcon('panel-left-close') %>
63
+ </button>
64
+ <% } %>
65
+ <h1><%= pageTitle %></h1>
66
+ </div>
67
+ <% if (theme && theme.enableModeToggle && theme.positionMode === 'top') { %>
68
+ <div class="header-right">
69
+ <button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button theme-toggle-header">
70
+ <%# renderIcon is available in the global EJS scope from html-generator %>
71
+ <%- renderIcon('sun', { class: 'icon-sun' }) %>
72
+ <%- renderIcon('moon', { class: 'icon-moon' }) %>
73
+ </button>
74
+ </div>
75
+ <% } %>
76
+ </div>
55
77
  <main class="content-area">
56
78
  <div class="content-layout">
57
79
  <div class="main-content">
@@ -106,11 +128,21 @@
106
128
  </footer>
107
129
  </div>
108
130
 
109
- <script src="<%= relativePathToRoot %>assets/js/docmd-theme-toggle.js"></script>
131
+ <script src="<%= relativePathToRoot %>assets/js/docmd-main.js"></script>
132
+
110
133
  <% (customJsFiles || []).forEach(jsFile => { %>
111
134
  <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>"></script>
112
135
  <% }); %>
113
136
 
114
- <%- pluginBodyScriptsHtml || '' %> <%# Plugin specific body scripts %>
137
+ <%- pluginBodyScriptsHtml || '' %>
138
+
139
+ <% if (sponsor && sponsor.enabled) { %>
140
+ <div class="sponsor-ribbon">
141
+ <a href="<%= sponsor.link %>" target="_blank" rel="noopener noreferrer" class="sponsor-link">
142
+ <%- renderIcon('heart', { class: 'sponsor-icon' }) %>
143
+ <span class="sponsor-text"><%= sponsor.title %></span>
144
+ </a>
145
+ </div>
146
+ <% } %>
115
147
  </body>
116
148
  </html>
@@ -16,6 +16,10 @@
16
16
  <%- faviconLinkHtml || '' %>
17
17
  <% } %>
18
18
 
19
+ <% if (frontmatter.components?.themeMode !== false) { %>
20
+ <%- themeInitScript %>
21
+ <% } %>
22
+
19
23
  <% if (frontmatter.components?.css !== false) { %>
20
24
  <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css">
21
25
  <% if (frontmatter.components?.highlight !== false) { %>
@@ -45,7 +49,16 @@
45
49
  <%- frontmatter.customHead %>
46
50
  <% } %>
47
51
  </head>
48
- <body<% if (frontmatter.components?.theme !== false) { %> data-theme="<%= defaultMode %>"<% } %><% if (frontmatter.bodyClass) { %> class="<%= frontmatter.bodyClass %>"<% } %>>
52
+ <body
53
+ <%
54
+ if (frontmatter.components?.theme !== false) {
55
+ %> data-theme="<%= defaultMode %>"<%
56
+ }
57
+ %><%
58
+ if (frontmatter.bodyClass) {
59
+ %> class="<%= frontmatter.bodyClass %>"<%
60
+ }
61
+ %> data-copy-code-enabled="<%= config.copyCode === true %>">
49
62
  <% if (frontmatter.components?.layout === true || frontmatter.components?.layout === 'full') { %>
50
63
  <div class="main-content-wrapper">
51
64
  <% if (frontmatter.components?.header !== false) { %>
@@ -136,18 +149,22 @@
136
149
  <%- content %>
137
150
  <% } %>
138
151
 
139
- <% if (frontmatter.components?.scripts !== false) { %>
140
- <% if (frontmatter.components?.themeToggle !== false) { %>
141
- <script src="<%= relativePathToRoot %>assets/js/docmd-theme-toggle.js"></script>
152
+ <% if (frontmatter.components?.scripts === true) { %>
153
+ <% if (frontmatter.components?.mainScripts === true) { %>
154
+ <script src="<%= relativePathToRoot %>assets/js/docmd-main.js"></script>
155
+ <% } %>
156
+
157
+ <% if (frontmatter.components?.lightbox === true && frontmatter.components?.mainScripts === true) { %>
158
+ <script src="<%= relativePathToRoot %>assets/js/docmd-image-lightbox.js"></script>
142
159
  <% } %>
143
160
 
144
- <% if (frontmatter.components?.customJs !== false && customJsFiles && customJsFiles.length > 0) { %>
161
+ <% if (frontmatter.components?.customJs === true && customJsFiles && customJsFiles.length > 0) { %>
145
162
  <% customJsFiles.forEach(jsFile => { %>
146
163
  <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>"></script>
147
164
  <% }); %>
148
165
  <% } %>
149
166
 
150
- <% if (frontmatter.components?.pluginBodyScripts !== false) { %>
167
+ <% if (frontmatter.components?.pluginBodyScripts === true) { %>
151
168
  <%- pluginBodyScriptsHtml || '' %>
152
169
  <% } %>
153
170
  <% } %>
@@ -0,0 +1,26 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Initialize the theme from localStorage
5
+ */
6
+
7
+ (function() {
8
+ try {
9
+ const storedTheme = localStorage.getItem('docmd-theme');
10
+ if (storedTheme) {
11
+ document.documentElement.setAttribute('data-theme', storedTheme);
12
+
13
+ // Also update highlight CSS link to match the stored theme
14
+ const highlightThemeLink = document.getElementById('highlight-theme');
15
+ if (highlightThemeLink) {
16
+ const baseHref = highlightThemeLink.getAttribute('data-base-href');
17
+ if (baseHref) {
18
+ const newHref = baseHref + `docmd-highlight-${storedTheme}.css`;
19
+ highlightThemeLink.setAttribute('href', newHref);
20
+ }
21
+ }
22
+ }
23
+ } catch (e) {
24
+ console.error('Error applying theme from localStorage', e);
25
+ }
26
+ })();
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file