@mgks/docmd 0.1.3 → 0.2.0

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 (56) hide show
  1. package/.github/workflows/deploy-docmd.yml +2 -2
  2. package/README.md +2 -4
  3. package/assets/css/welcome.css +62 -358
  4. package/assets/images/preview-dark-1.webp +0 -0
  5. package/assets/images/preview-dark-2.webp +0 -0
  6. package/assets/images/preview-dark-3.webp +0 -0
  7. package/assets/images/preview-light-1.webp +0 -0
  8. package/assets/images/preview-light-2.webp +0 -0
  9. package/assets/images/preview-light-3.webp +0 -0
  10. package/config.js +35 -4
  11. package/docs/cli-commands.md +1 -2
  12. package/docs/configuration.md +104 -23
  13. package/docs/content/containers/buttons.md +88 -0
  14. package/docs/content/containers/callouts.md +154 -0
  15. package/docs/content/containers/cards.md +93 -0
  16. package/docs/content/containers/index.md +35 -0
  17. package/docs/content/containers/nested-containers.md +329 -0
  18. package/docs/content/containers/steps.md +175 -0
  19. package/docs/content/containers/tabs.md +228 -0
  20. package/docs/content/frontmatter.md +4 -4
  21. package/docs/content/index.md +5 -4
  22. package/docs/content/markdown-syntax.md +4 -4
  23. package/docs/content/no-style-example.md +1 -1
  24. package/docs/contributing.md +7 -0
  25. package/docs/deployment.md +22 -31
  26. package/docs/getting-started/basic-usage.md +3 -2
  27. package/docs/getting-started/index.md +3 -3
  28. package/docs/getting-started/installation.md +1 -1
  29. package/docs/index.md +14 -20
  30. package/docs/plugins/seo.md +2 -0
  31. package/docs/plugins/sitemap.md +1 -1
  32. package/docs/theming/assets-management.md +1 -1
  33. package/docs/theming/available-themes.md +45 -52
  34. package/docs/theming/light-dark-mode.md +12 -3
  35. package/package.json +9 -3
  36. package/src/assets/css/docmd-main.css +935 -573
  37. package/src/assets/css/docmd-theme-retro.css +812 -0
  38. package/src/assets/css/docmd-theme-ruby.css +619 -0
  39. package/src/assets/css/docmd-theme-sky.css +606 -605
  40. package/src/assets/js/docmd-image-lightbox.js +1 -3
  41. package/src/assets/js/docmd-main.js +97 -0
  42. package/src/commands/build.js +1 -1
  43. package/src/commands/dev.js +5 -2
  44. package/src/commands/init.js +21 -1
  45. package/src/core/file-processor.js +626 -363
  46. package/src/core/html-generator.js +20 -30
  47. package/src/plugins/seo.js +4 -0
  48. package/src/templates/layout.ejs +34 -8
  49. package/assets/images/preview-dark-1.png +0 -0
  50. package/assets/images/preview-dark-2.png +0 -0
  51. package/assets/images/preview-dark-3.png +0 -0
  52. package/assets/images/preview-light-1.png +0 -0
  53. package/assets/images/preview-light-2.png +0 -0
  54. package/assets/images/preview-light-3.png +0 -0
  55. package/docs/content/custom-containers.md +0 -129
  56. package/src/assets/js/docmd-theme-toggle.js +0 -59
@@ -23,14 +23,10 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
23
23
 
24
24
  // 2. Theme CSS (built-in handling for theme.name)
25
25
  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
26
  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
27
  themeCssLinkHtml = ` <link rel="stylesheet" href="${relativePathToRoot}${themeCssPath}">\n`;
31
28
  }
32
29
 
33
-
34
30
  // 3. SEO Plugin (if configured)
35
31
  if (config.plugins?.seo) {
36
32
  metaTagsHtml += generateSeoMetaTags(config, pageData, relativePathToRoot);
@@ -43,9 +39,6 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
43
39
  pluginBodyScriptsHtml += analyticsScripts.bodyScriptsHtml;
44
40
  }
45
41
 
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
42
  return {
50
43
  metaTagsHtml,
51
44
  faviconLinkHtml,
@@ -58,11 +51,13 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
58
51
 
59
52
  async function generateHtmlPage(templateData) {
60
53
  const {
61
- content, pageTitle, siteTitle, navigationHtml,
54
+ content, siteTitle, navigationHtml,
62
55
  relativePathToRoot, config, frontmatter, outputPath,
63
56
  prevPage, nextPage, currentPagePath, headings
64
57
  } = templateData;
65
58
 
59
+ const pageTitle = frontmatter.title; // Get title from frontmatter (already processed in file-processor)
60
+
66
61
  // Process plugins to get their HTML contributions
67
62
  const pluginOutputs = await processPluginHooks(
68
63
  config,
@@ -75,16 +70,9 @@ async function generateHtmlPage(templateData) {
75
70
  footerHtml = mdInstance.renderInline(config.footer);
76
71
  }
77
72
 
78
- // Determine which template to use based on frontmatter
79
73
  let templateName = 'layout.ejs';
80
74
  if (frontmatter.noStyle === true) {
81
75
  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
76
  }
89
77
 
90
78
  const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
@@ -93,42 +81,45 @@ async function generateHtmlPage(templateData) {
93
81
  }
94
82
  const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
95
83
 
96
- // Determine if this is an active page for TOC display
97
- // The currentPagePath exists and has content
98
84
  const isActivePage = currentPagePath && content && content.trim().length > 0;
99
85
 
100
86
  const ejsData = {
101
87
  content,
102
- pageTitle: frontmatter.title || pageTitle || 'Untitled', // Ensure pageTitle is robust
103
- description: frontmatter.description, // Used by layout if no SEO plugin overrides
88
+ pageTitle, // Pass the potentially undefined title
89
+ description: frontmatter.description,
104
90
  siteTitle,
105
91
  navigationHtml,
106
92
  defaultMode: config.theme?.defaultMode || 'light',
107
93
  relativePathToRoot,
108
94
  logo: config.logo,
95
+ sidebarConfig: {
96
+ collapsible: config.sidebar?.collapsible ?? false,
97
+ defaultCollapsed: config.sidebar?.defaultCollapsed ?? false,
98
+ },
109
99
  theme: config.theme,
110
100
  customCssFiles: config.theme?.customCss || [],
111
101
  customJsFiles: config.customJs || [],
102
+ sponsor: config.sponsor,
112
103
  footer: config.footer,
113
104
  footerHtml,
114
105
  renderIcon,
115
106
  prevPage,
116
107
  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
108
+ currentPagePath,
109
+ headings: headings || [],
110
+ isActivePage,
111
+ frontmatter,
112
+ ...pluginOutputs,
122
113
  };
123
114
 
124
115
  try {
125
116
  return ejs.render(layoutTemplate, ejsData, {
126
- filename: layoutTemplatePath // Add filename for proper include resolution
117
+ filename: layoutTemplatePath
127
118
  });
128
119
  } catch (e) {
129
120
  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
121
+ console.error("EJS Data:", JSON.stringify(ejsData, null, 2).substring(0, 1000) + "...");
122
+ throw e;
132
123
  }
133
124
  }
134
125
 
@@ -139,17 +130,16 @@ async function generateNavigationHtml(navItems, currentPagePath, relativePathToR
139
130
  }
140
131
  const navTemplate = await fs.readFile(navTemplatePath, 'utf8');
141
132
 
142
- // Make renderIcon available to the EJS template
143
133
  const ejsHelpers = { renderIcon };
144
134
 
145
135
  return ejs.render(navTemplate, {
146
136
  navItems,
147
137
  currentPagePath,
148
138
  relativePathToRoot,
149
- config, // Pass full config if needed by nav (e.g. for base path)
139
+ config,
150
140
  ...ejsHelpers
151
141
  }, {
152
- filename: navTemplatePath // Add filename for proper include resolution
142
+ filename: navTemplatePath
153
143
  });
154
144
  }
155
145
 
@@ -19,6 +19,10 @@ function generateSeoMetaTags(config, pageData, relativePathToRoot) {
19
19
 
20
20
  metaTagsHtml += ` <meta name="description" content="${description}">\n`;
21
21
 
22
+ // Canonical URL - use permalink first, fall back to canonicalUrl, then default to pageUrl
23
+ const canonicalUrl = frontmatter.permalink || frontmatter.canonicalUrl || pageUrl;
24
+ metaTagsHtml += ` <link rel="canonical" href="${canonicalUrl}">\n`;
25
+
22
26
  // Open Graph
23
27
  metaTagsHtml += ` <meta property="og:title" content="${pageTitle} | ${siteTitle}">\n`;
24
28
  metaTagsHtml += ` <meta property="og:description" content="${description}">\n`;
@@ -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
  <% } %>
@@ -27,7 +27,7 @@
27
27
 
28
28
  <%- pluginHeadScriptsHtml || '' %> <%# Plugin specific head scripts (e.g., Analytics) %>
29
29
  </head>
30
- <body data-theme="<%= defaultMode %>">
30
+ <body class="<%= sidebarConfig.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>" data-theme="<%= defaultMode %>" data-default-collapsed="<%= sidebarConfig.defaultCollapsed %>">
31
31
  <aside class="sidebar">
32
32
  <div class="sidebar-header">
33
33
  <% if (logo && logo.light && logo.dark) { %>
@@ -40,7 +40,7 @@
40
40
  <% } %>
41
41
  </div>
42
42
  <%- navigationHtml %>
43
- <% if (theme && theme.enableModeToggle) { %>
43
+ <% if (theme && theme.enableModeToggle && theme.positionMode !== 'top') { %>
44
44
  <button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button">
45
45
  <%# renderIcon is available in the global EJS scope from html-generator %>
46
46
  <%- renderIcon('sun', { class: 'icon-sun' }) %>
@@ -49,9 +49,25 @@
49
49
  <% } %>
50
50
  </aside>
51
51
  <div class="main-content-wrapper">
52
- <header class="page-header">
53
- <h1><%= pageTitle %></h1>
54
- </header>
52
+ <div class="page-header">
53
+ <div class="header-left">
54
+ <% if (sidebarConfig.collapsible) { %>
55
+ <button id="sidebar-toggle-button" class="sidebar-toggle-button" aria-label="Toggle Sidebar">
56
+ <%- renderIcon('panel-left-close') %>
57
+ </button>
58
+ <% } %>
59
+ <h1><%= pageTitle %></h1>
60
+ </div>
61
+ <% if (theme && theme.enableModeToggle && theme.positionMode === 'top') { %>
62
+ <div class="header-right">
63
+ <button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button theme-toggle-header">
64
+ <%# renderIcon is available in the global EJS scope from html-generator %>
65
+ <%- renderIcon('sun', { class: 'icon-sun' }) %>
66
+ <%- renderIcon('moon', { class: 'icon-moon' }) %>
67
+ </button>
68
+ </div>
69
+ <% } %>
70
+ </div>
55
71
  <main class="content-area">
56
72
  <div class="content-layout">
57
73
  <div class="main-content">
@@ -106,11 +122,21 @@
106
122
  </footer>
107
123
  </div>
108
124
 
109
- <script src="<%= relativePathToRoot %>assets/js/docmd-theme-toggle.js"></script>
125
+ <script src="<%= relativePathToRoot %>assets/js/docmd-main.js"></script>
126
+
110
127
  <% (customJsFiles || []).forEach(jsFile => { %>
111
128
  <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>"></script>
112
129
  <% }); %>
113
130
 
114
- <%- pluginBodyScriptsHtml || '' %> <%# Plugin specific body scripts %>
131
+ <%- pluginBodyScriptsHtml || '' %>
132
+
133
+ <% if (sponsor && sponsor.enabled) { %>
134
+ <div class="sponsor-ribbon">
135
+ <a href="<%= sponsor.link %>" target="_blank" rel="noopener noreferrer" class="sponsor-link">
136
+ <%- renderIcon('heart', { class: 'sponsor-icon' }) %>
137
+ <span class="sponsor-text"><%= sponsor.title %></span>
138
+ </a>
139
+ </div>
140
+ <% } %>
115
141
  </body>
116
142
  </html>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,129 +0,0 @@
1
- ---
2
- title: "Custom Containers"
3
- description: "Enhance your documentation with special components like callouts, cards, and steps using docmd's custom container syntax."
4
- ---
5
-
6
- # Custom Containers
7
-
8
- `docmd` provides a simple syntax for adding richer, pre-styled components to your Markdown content using "custom containers." These are powered by the `markdown-it-container` plugin.
9
-
10
- The general syntax is:
11
-
12
- ```
13
- ::: containerName [optionalTitleOrType]
14
- Content for the container goes here.
15
- It can span **multiple lines** and include other *Markdown* elements.
16
- :::
17
- ```
18
-
19
- ## Callouts
20
-
21
- Callouts are useful for highlighting important information, warnings, tips, or notes.
22
-
23
- **Syntax:**
24
- ```
25
- ::: callout type
26
- Content of the callout.
27
- :::
28
- ```
29
- * `type`: Can be `info`, `warning`, `tip`, or `danger`.
30
-
31
- **Examples:**
32
-
33
- ::: callout info
34
- This is an informational message. It's good for general notes or neutral supplementary details.
35
- :::
36
-
37
- ::: callout warning
38
- **Watch out!** This indicates something that requires caution or might lead to unexpected results if ignored.
39
- :::
40
-
41
- ::: callout tip
42
- Here's a helpful tip or a best practice suggestion to improve a process or understanding.
43
- :::
44
-
45
- ::: callout danger
46
- **Critical!** This highlights a potential risk, a destructive action, or something that must be avoided.
47
- :::
48
-
49
- ## Cards
50
-
51
- Cards provide a visually distinct block for grouping related content, often with a title.
52
-
53
- **Syntax:**
54
- ```
55
- ::: card Optional Card Title
56
- The main body content of the card.
57
- Supports **Markdown** formatting.
58
- - List item 1
59
- - List item 2
60
- :::
61
- ```
62
- * `Optional Card Title`: If provided after `card`, it becomes the title of the card.
63
-
64
- **Example:**
65
-
66
- ::: card My Feature Overview
67
- This card describes an amazing feature.
68
- * It's easy to use.
69
- * It solves a common problem.
70
-
71
- Learn more by reading the full guide.
72
- :::
73
-
74
- ::: card
75
- This is a card without an explicit title. The content starts directly.
76
- Ideal for small, self-contained snippets.
77
- :::
78
-
79
- ## Steps
80
-
81
- The "steps" container is designed for presenting a sequence of actions or instructions, often in a numbered or ordered fashion.
82
-
83
- **Syntax:**
84
-
85
- ```
86
- ::: steps
87
- > 1. **First Step Title:**
88
- > Description of the first step.
89
-
90
- > 2. **Second Step Title:**
91
- > Description of the second step.
92
-
93
- > *. **Using Asterisk:**
94
- > Alternative numbering style.
95
-
96
- > **No Number:**
97
- > Without a number.
98
- :::
99
- ```
100
-
101
- **How it works:**
102
-
103
- The steps container uses blockquotes (`>`) to define individual steps. Start each step with a number or asterisk followed by a period, then the step title in bold. The content after the title becomes the step content.
104
-
105
- **Example:**
106
-
107
- ::: steps
108
- > 1. **Install Dependencies:**
109
- > First, make sure you have Node.js and npm installed. Then, run:
110
- >
111
- > ```bash
112
- > npm install my-package
113
- > ```
114
-
115
- > 2. **Configure the Settings:**
116
- > Open the `config.json` file and update the `apiKey` with your credentials.
117
-
118
- > 3. **Run the Application:**
119
- > Start the application using:
120
- >
121
- > ```bash
122
- > npm start
123
- > ```
124
- >
125
- > You should see "Application started successfully!" in your console.
126
- :::
127
- :::
128
-
129
- These custom containers allow you to create more engaging and structured documentation without needing to write custom HTML or CSS for common patterns. Experiment with them to see how they can improve your content's clarity and visual appeal.
@@ -1,59 +0,0 @@
1
- // src/assets/js/theme-toggle.js
2
- function applyTheme(theme, isInitialLoad = false) {
3
- document.body.setAttribute('data-theme', theme);
4
- if (!isInitialLoad) {
5
- localStorage.setItem('docmd-theme', theme);
6
- }
7
-
8
- // Change highlight.js theme dynamically (if separate themes are loaded)
9
- const highlightThemeLink = document.getElementById('highlight-theme');
10
- if (highlightThemeLink) {
11
- const isDark = theme.includes('dark');
12
- const isLight = theme.includes('light');
13
- const currentHref = highlightThemeLink.href;
14
-
15
- if (isDark && currentHref.includes('docmd-highlight-light.css')) {
16
- highlightThemeLink.href = currentHref.replace('docmd-highlight-light.css', 'docmd-highlight-dark.css');
17
- } else if (isLight && currentHref.includes('docmd-highlight-dark.css')) {
18
- highlightThemeLink.href = currentHref.replace('docmd-highlight-dark.css', 'docmd-highlight-light.css');
19
- }
20
- }
21
- }
22
-
23
- function getInitialTheme() {
24
- const storedTheme = localStorage.getItem('docmd-theme');
25
- if (storedTheme) {
26
- return storedTheme;
27
- }
28
- // The server sets the initial data-theme based on config.theme.defaultMode.
29
- // We respect localStorage first, then what the server provided.
30
- // Optionally, could check prefers-color-scheme if neither is set, but defaultMode covers this.
31
- return document.body.getAttribute('data-theme') || 'light';
32
- }
33
-
34
- document.addEventListener('DOMContentLoaded', () => {
35
- // Apply initial theme (respecting localStorage over server-rendered if set)
36
- const initialTheme = getInitialTheme();
37
- applyTheme(initialTheme, true); // true indicates it's the initial load
38
-
39
- const themeToggleButton = document.getElementById('theme-toggle-button');
40
- if (themeToggleButton) {
41
- themeToggleButton.addEventListener('click', () => {
42
- let currentTheme = document.body.getAttribute('data-theme');
43
-
44
- // Handle both regular themes and sky theme variants
45
- let newTheme;
46
- if (currentTheme === 'light') {
47
- newTheme = 'dark';
48
- } else if (currentTheme === 'dark') {
49
- newTheme = 'light';
50
- } else if (currentTheme === 'sky-light') {
51
- newTheme = 'sky-dark';
52
- } else if (currentTheme === 'sky-dark') {
53
- newTheme = 'sky-light';
54
- }
55
-
56
- applyTheme(newTheme); // isInitialLoad is false here, so it saves to localStorage
57
- });
58
- }
59
- });