@mgks/docmd 0.1.0 → 0.1.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.
@@ -8,7 +8,7 @@ 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
- async function startDevServer(configPathOption) {
11
+ async function startDevServer(configPathOption, options = { preserve: false }) {
12
12
  let config = await loadConfig(configPathOption); // Load initial config
13
13
  const CWD = process.cwd(); // Current Working Directory where user runs `docmd dev`
14
14
 
@@ -88,7 +88,7 @@ async function startDevServer(configPathOption) {
88
88
  // Initial build
89
89
  console.log('🚀 Performing initial build for dev server...');
90
90
  try {
91
- await buildSite(configPathOption, { isDev: true }); // Use the original config path option
91
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve }); // Use the original config path option
92
92
  console.log('✅ Initial build complete.');
93
93
  } catch (error) {
94
94
  console.error('❌ Initial build failed:', error.message, error.stack);
@@ -143,7 +143,7 @@ async function startDevServer(configPathOption) {
143
143
  paths = newPaths; // Update paths for next build reference
144
144
  }
145
145
 
146
- await buildSite(configPathOption, { isDev: true }); // Re-build using the potentially updated config path
146
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve }); // Re-build using the potentially updated config path
147
147
  broadcastReload();
148
148
  console.log('✅ Rebuild complete. Browser should refresh.');
149
149
  } catch (error) {
@@ -1,23 +1,93 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
 
4
- const defaultConfigContent = `// config.js
4
+ const defaultConfigContent = `// config.js: basic config for docmd
5
5
  module.exports = {
6
- siteTitle: 'My Awesome Project Docs',
7
- srcDir: 'docs',
8
- outputDir: 'site',
6
+ // Core Site Metadata
7
+ siteTitle: 'docmd',
8
+ // Define a base URL for your site, crucial for SEO and absolute paths
9
+ // No trailing slash
10
+ siteUrl: '', // Replace with your actual deployed URL
11
+
12
+ // Logo Configuration
13
+ logo: {
14
+ light: '/assets/images/docmd-logo-light.png', // Path relative to outputDir root
15
+ dark: '/assets/images/docmd-logo-dark.png', // Path relative to outputDir root
16
+ alt: 'docmd logo', // Alt text for the logo
17
+ href: '/', // Link for the logo, defaults to site root
18
+ },
19
+
20
+ // Directory Configuration
21
+ srcDir: 'docs', // Source directory for Markdown files
22
+ outputDir: 'site', // Directory for generated static site
23
+
24
+ // Theme Configuration
9
25
  theme: {
10
- defaultMode: 'light', // 'light' or 'dark'
26
+ name: 'sky', // Themes: 'default', 'sky'
27
+ defaultMode: 'light', // Initial color mode: 'light' or 'dark'
28
+ enableModeToggle: true, // Show UI button to toggle light/dark modes
29
+ customCss: [ // Array of paths to custom CSS files
30
+ // '/assets/css/custom.css', // Custom TOC styles
31
+ ]
11
32
  },
33
+
34
+ // Custom JavaScript Files
35
+ customJs: [ // Array of paths to custom JS files, loaded at end of body
36
+ // '/assets/js/custom-script.js', // Paths relative to outputDir root
37
+ ],
38
+
39
+ // Plugins Configuration
40
+ // Plugins are configured here. docmd will look for these keys.
41
+ plugins: {
42
+ // SEO Plugin Configuration
43
+ // Most SEO data is pulled from page frontmatter (title, description, image, etc.)
44
+ // These are fallbacks or site-wide settings.
45
+ seo: {
46
+ // Default meta description if a page doesn't have one in its frontmatter
47
+ defaultDescription: 'docmd is a Node.js command-line tool for generating beautiful, lightweight static documentation sites from Markdown files.',
48
+ openGraph: { // For Facebook, LinkedIn, etc.
49
+ // siteName: 'docmd Documentation', // Optional, defaults to config.siteTitle
50
+ // Default image for og:image if not specified in page frontmatter
51
+ // Path relative to outputDir root
52
+ defaultImage: '/assets/images/docmd-preview.png',
53
+ },
54
+ twitter: { // For Twitter Cards
55
+ cardType: 'summary_large_image', // 'summary', 'summary_large_image'
56
+ // siteUsername: '@docmd_handle', // Your site's Twitter handle (optional)
57
+ // creatorUsername: '@your_handle', // Default author handle (optional, can be overridden in frontmatter)
58
+ }
59
+ },
60
+ // Analytics Plugin Configuration
61
+ analytics: {
62
+ // Google Analytics 4 (GA4)
63
+ googleV4: {
64
+ measurementId: 'G-8QVBDQ4KM1' // Replace with your actual GA4 Measurement ID
65
+ }
66
+ },
67
+ // Enable Sitemap plugin
68
+ sitemap: {
69
+ defaultChangefreq: 'weekly',
70
+ defaultPriority: 0.8
71
+ }
72
+ // Add other future plugin configurations here by their key
73
+ },
74
+
75
+ // Navigation Structure (Sidebar)
76
+ // Icons are kebab-case names from Lucide Icons (https://lucide.dev/)
12
77
  navigation: [
13
- { title: 'Home', path: '/' }, // Corresponds to docs/index.md
14
- // {
15
- // title: 'Category',
16
- // children: [
17
- // { title: 'Page 1', path: '/category/page1' },
18
- // ],
19
- // },
78
+ { title: 'Welcome', path: '/', icon: 'home' }, // Corresponds to docs/index.md
79
+ // External links:
80
+ { title: 'GitHub', path: 'https://github.com/mgks/docmd', icon: 'github', external: true },
81
+ { title: 'Documentation', path: 'https://github.com/mgks/docmd', icon: 'scroll', external: true }
20
82
  ],
83
+
84
+ // Footer Configuration
85
+ // Markdown is supported here.
86
+ footer: '© ' + new Date().getFullYear() + ' Project.',
87
+
88
+ // Favicon Configuration
89
+ // Path relative to outputDir root
90
+ favicon: '/assets/favicon.ico',
21
91
  };
22
92
  `;
23
93
 
@@ -4,6 +4,7 @@ const MarkdownIt = require('markdown-it');
4
4
  const matter = require('gray-matter');
5
5
  const hljs = require('highlight.js');
6
6
  const container = require('markdown-it-container');
7
+ const attrs = require('markdown-it-attrs');
7
8
 
8
9
  const md = new MarkdownIt({
9
10
  html: true,
@@ -23,6 +24,45 @@ const md = new MarkdownIt({
23
24
  }
24
25
  });
25
26
 
27
+ // Add markdown-it-attrs for image styling and other element attributes
28
+ // This allows for {.class} syntax after elements
29
+ md.use(attrs, {
30
+ // Allow attributes on images and other elements
31
+ leftDelimiter: '{',
32
+ rightDelimiter: '}',
33
+ allowedAttributes: ['class', 'id', 'width', 'height', 'style']
34
+ });
35
+
36
+ // Custom image renderer to ensure attributes are properly applied
37
+ const defaultImageRenderer = md.renderer.rules.image;
38
+ md.renderer.rules.image = function(tokens, idx, options, env, self) {
39
+ // Get the rendered HTML from the default renderer
40
+ const renderedImage = defaultImageRenderer(tokens, idx, options, env, self);
41
+
42
+ // Check if the next token is an attrs_block
43
+ const nextToken = tokens[idx + 1];
44
+ if (nextToken && nextToken.type === 'attrs_block') {
45
+ // Extract attributes from the attrs_block token
46
+ const attrs = nextToken.attrs || [];
47
+
48
+ // Build the attributes string
49
+ const attrsStr = attrs.map(([name, value]) => {
50
+ if (name === 'class') {
51
+ return `class="${value}"`;
52
+ } else if (name.startsWith('data-')) {
53
+ return `${name}="${value}"`;
54
+ } else {
55
+ return `${name}="${value}"`;
56
+ }
57
+ }).join(' ');
58
+
59
+ // Insert attributes into the image tag
60
+ return renderedImage.replace('<img ', `<img ${attrsStr} `);
61
+ }
62
+
63
+ return renderedImage;
64
+ };
65
+
26
66
  // Add anchors to headings for TOC linking
27
67
  md.use((md) => {
28
68
  // Original renderer
@@ -23,8 +23,8 @@ 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 'theme-yourthemename.css' in assets/css
27
- const themeCssPath = `assets/css/theme-${config.theme.name}.css`;
26
+ // Assumes theme CSS files are like 'docmd-theme-yourthemename.css' in assets/css
27
+ const themeCssPath = `assets/css/docmd-theme-${config.theme.name}.css`;
28
28
  // Check if theme file exists before linking (optional, good practice)
29
29
  // For now, assume it will exist if specified.
30
30
  themeCssLinkHtml = ` <link rel="stylesheet" href="${relativePathToRoot}${themeCssPath}">\n`;
@@ -109,7 +109,9 @@ async function generateHtmlPage(templateData) {
109
109
  };
110
110
 
111
111
  try {
112
- return ejs.render(layoutTemplate, ejsData);
112
+ return ejs.render(layoutTemplate, ejsData, {
113
+ filename: layoutTemplatePath // Add filename for proper include resolution
114
+ });
113
115
  } catch (e) {
114
116
  console.error(`❌ Error rendering EJS template for ${outputPath}: ${e.message}`);
115
117
  console.error("EJS Data:", JSON.stringify(ejsData, null, 2).substring(0, 1000) + "..."); // Log partial data
@@ -133,6 +135,8 @@ async function generateNavigationHtml(navItems, currentPagePath, relativePathToR
133
135
  relativePathToRoot,
134
136
  config, // Pass full config if needed by nav (e.g. for base path)
135
137
  ...ejsHelpers
138
+ }, {
139
+ filename: navTemplatePath // Add filename for proper include resolution
136
140
  });
137
141
  }
138
142
 
@@ -13,9 +13,9 @@
13
13
 
14
14
  <%- faviconLinkHtml || '' %> <%# Favicon %>
15
15
 
16
- <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/main.css">
16
+ <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css">
17
17
 
18
- <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css" id="highlight-theme">
18
+ <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css" id="highlight-theme">
19
19
 
20
20
  <%- themeCssLinkHtml || '' %> <%# For theme.name specific CSS %>
21
21
 
@@ -90,65 +90,7 @@
90
90
 
91
91
  <!-- TOC sidebar -->
92
92
  <div class="toc-sidebar">
93
- <%
94
- // Helper function to decode HTML entities
95
- function decodeHtmlEntities(html) {
96
- return html
97
- .replace(/&amp;/g, '&')
98
- .replace(/&lt;/g, '<')
99
- .replace(/&gt;/g, '>')
100
- .replace(/&quot;/g, '"')
101
- .replace(/&#39;/g, "'")
102
- .replace(/&nbsp;/g, ' ');
103
- }
104
-
105
- // Only show TOC on active pages
106
- const isActive = navigationHtml && navigationHtml.includes('class="active"');
107
-
108
- if (isActive) {
109
- // Extract headings directly from content - match with or without id attribute
110
- const headingRegex = /<h([2-4])[^>]*?(?:id="([^"]*)")?[^>]*?>([\s\S]*?)<\/h\1>/g;
111
- const tocHeadings = [];
112
- let match;
113
- let contentStr = content.toString();
114
-
115
- while ((match = headingRegex.exec(contentStr)) !== null) {
116
- const level = parseInt(match[1], 10);
117
- // Use ID if available, or generate one from the text
118
- let id = match[2];
119
- // Remove any HTML tags inside the heading text and decode HTML entities
120
- const textWithTags = match[3].replace(/<\/?[^>]+(>|$)/g, '');
121
- // Decode HTML entities
122
- const text = decodeHtmlEntities(textWithTags);
123
-
124
- if (!id) {
125
- // Generate an ID from the heading text if none exists
126
- id = text
127
- .toLowerCase()
128
- .replace(/\s+/g, '-')
129
- .replace(/[^\w-]/g, '')
130
- .replace(/--+/g, '-')
131
- .replace(/^-+|-+$/g, '');
132
- }
133
-
134
- tocHeadings.push({ id, level, text });
135
- }
136
-
137
- // Only show TOC if there are enough headings
138
- if (tocHeadings.length > 1) {
139
- %>
140
- <div class="toc-container">
141
- <h2 class="toc-title">On This Page</h2>
142
- <ul class="toc-list">
143
- <% tocHeadings.forEach(heading => { %>
144
- <li class="toc-item toc-level-<%= heading.level %>">
145
- <a href="#<%= heading.id %>" class="toc-link"><%- heading.text %></a>
146
- </li>
147
- <% }); %>
148
- </ul>
149
- </div>
150
- <% }
151
- } %>
93
+ <%- include('toc', { content, headings, navigationHtml, isActivePage }) %>
152
94
  </div>
153
95
  </div>
154
96
  </main>
@@ -164,7 +106,7 @@
164
106
  </footer>
165
107
  </div>
166
108
 
167
- <script src="<%= relativePathToRoot %>assets/js/theme-toggle.js"></script>
109
+ <script src="<%= relativePathToRoot %>assets/js/docmd-theme-toggle.js"></script>
168
110
  <% (customJsFiles || []).forEach(jsFile => { %>
169
111
  <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>"></script>
170
112
  <% }); %>
@@ -1,34 +1,67 @@
1
1
  <%# src/templates/toc.ejs %>
2
2
  <%
3
- // If direct headings aren't available, we'll try to extract them from the content
4
- let tocHeadings = [];
5
- if (headings && headings.length > 0) {
6
- // Use provided headings if available
7
- tocHeadings = headings.filter(h => h.level >= 2 && h.level <= 4);
8
- } else if (content) {
9
- // Basic regex to extract headings from HTML content
10
- const headingRegex = /<h([2-4])[^>]*?id="([^"]*)"[^>]*?>([\s\S]*?)<\/h\1>/g;
11
- let match;
12
- while ((match = headingRegex.exec(content)) !== null) {
13
- const level = parseInt(match[1], 10);
14
- const id = match[2];
15
- // Remove any HTML tags inside the heading text
16
- const text = match[3].replace(/<\/?[^>]+(>|$)/g, '');
17
- tocHeadings.push({ id, level, text });
18
- }
3
+ // Helper function to decode HTML entities
4
+ function decodeHtmlEntities(html) {
5
+ return html
6
+ .replace(/&amp;/g, '&')
7
+ .replace(/&lt;/g, '<')
8
+ .replace(/&gt;/g, '>')
9
+ .replace(/&quot;/g, '"')
10
+ .replace(/&#39;/g, "'")
11
+ .replace(/&nbsp;/g, ' ');
19
12
  }
20
13
 
21
- // Only show TOC if there are enough headings
22
- if (tocHeadings.length > 1) {
14
+ // Use the isActivePage flag if provided, otherwise fall back to checking navigationHtml
15
+ const shouldShowToc = typeof isActivePage !== 'undefined' ? isActivePage :
16
+ (typeof navigationHtml !== 'undefined' && navigationHtml && navigationHtml.includes('class="active"'));
17
+
18
+ if (shouldShowToc) {
19
+ // If direct headings aren't available, we'll try to extract them from the content
20
+ let tocHeadings = [];
21
+ if (headings && headings.length > 0) {
22
+ // Use provided headings if available
23
+ tocHeadings = headings.filter(h => h.level >= 2 && h.level <= 4);
24
+ } else if (content) {
25
+ // Basic regex to extract headings from HTML content
26
+ const headingRegex = /<h([2-4])[^>]*?(?:id="([^"]*)")?[^>]*?>([\s\S]*?)<\/h\1>/g;
27
+ let match;
28
+ let contentStr = content.toString();
29
+
30
+ while ((match = headingRegex.exec(contentStr)) !== null) {
31
+ const level = parseInt(match[1], 10);
32
+ // Use ID if available, or generate one from the text
33
+ let id = match[2];
34
+ // Remove any HTML tags inside the heading text
35
+ const textWithTags = match[3].replace(/<\/?[^>]+(>|$)/g, '');
36
+ // Decode HTML entities
37
+ const text = decodeHtmlEntities(textWithTags);
38
+
39
+ if (!id) {
40
+ // Generate an ID from the heading text if none exists
41
+ id = text
42
+ .toLowerCase()
43
+ .replace(/\s+/g, '-')
44
+ .replace(/[^\w-]/g, '')
45
+ .replace(/--+/g, '-')
46
+ .replace(/^-+|-+$/g, '');
47
+ }
48
+
49
+ tocHeadings.push({ id, level, text });
50
+ }
51
+ }
52
+
53
+ // Only show TOC if there are enough headings
54
+ if (tocHeadings.length > 1) {
23
55
  %>
24
56
  <div class="toc-container">
25
57
  <h2 class="toc-title">On This Page</h2>
26
58
  <ul class="toc-list">
27
59
  <% tocHeadings.forEach(heading => { %>
28
60
  <li class="toc-item toc-level-<%= heading.level %>">
29
- <a href="#<%= heading.id %>" class="toc-link"><%= heading.text %></a>
61
+ <a href="#<%= heading.id %>" class="toc-link"><%- heading.text %></a>
30
62
  </li>
31
63
  <% }); %>
32
64
  </ul>
33
65
  </div>
34
- <% } %>
66
+ <% }
67
+ } %>
@@ -1,17 +0,0 @@
1
- ---
2
- title: "Writing Content with docmd"
3
- description: "Learn how to write effective documentation using Markdown, frontmatter, and custom components in docmd."
4
- ---
5
-
6
- # Writing Content
7
-
8
- `docmd` is designed to let you focus on your content, written in standard Markdown. This section covers the essentials of creating pages, structuring your content, and leveraging `docmd`'s features to enhance your documentation.
9
-
10
- ## Key Aspects:
11
-
12
- * **[Frontmatter](/writing-content/frontmatter/):** Define page-specific metadata like titles and descriptions using simple YAML at the top of your Markdown files.
13
- * **[Markdown Syntax](/writing-content/markdown-syntax/):** Utilize standard CommonMark and GitHub Flavored Markdown for formatting your text, code blocks, lists, tables, and more.
14
- * **[Custom Containers](/writing-content/custom-containers/):** Enhance your pages with special components like callouts, cards, and steps using a simple `::: name :::` syntax.
15
- * **[Steps Container](/writing-content/steps-test/):** Create sequential guides with the specialized steps container.
16
-
17
- By understanding these elements, you can create rich, well-structured, and engaging documentation.
@@ -1,76 +0,0 @@
1
- /* TOC Styles - Simplified Hyperlink Style */
2
- .toc-container {
3
- margin: 0;
4
- padding: 0;
5
- border: none;
6
- background-color: transparent;
7
- }
8
-
9
- .toc-title {
10
- margin-top: 0;
11
- margin-bottom: 0.5rem;
12
- font-size: 1rem;
13
- font-weight: bold;
14
- color: var(--text-muted);
15
-
16
- }
17
-
18
- .toc-list {
19
- list-style: none;
20
- padding-left: 0;
21
- margin: 0;
22
- }
23
-
24
- .toc-item {
25
- margin-bottom: 0.25rem;
26
- line-height: 1.4;
27
- }
28
-
29
- .toc-link {
30
- text-decoration: none;
31
- color: var(--link-color);
32
- display: inline-block;
33
- padding: 0.1rem 0;
34
- font-size: 0.9rem;
35
- font-weight: 500;
36
-
37
- }
38
-
39
- .toc-link:hover {
40
- text-decoration: underline;
41
- }
42
-
43
- /* Indentation for different heading levels */
44
- .toc-level-2 {
45
- margin-left: 0;
46
- }
47
-
48
- .toc-level-3 {
49
- margin-left: 0.75rem;
50
- font-size: 0.85rem;
51
- }
52
-
53
- .toc-level-4 {
54
- margin-left: 1.5rem;
55
- font-size: 0.8rem;
56
- }
57
-
58
- /* TOC sidebar should only display on active pages */
59
- .toc-sidebar {
60
- width: 180px;
61
- position: sticky;
62
- top: 2rem;
63
- max-height: calc(100vh - 4rem);
64
- overflow-y: auto;
65
- align-self: flex-start;
66
-
67
- }
68
-
69
- /* Hide TOC on mobile */
70
- @media (max-width: 1024px) {
71
- .toc-sidebar {
72
- width: 100%;
73
- position: static;
74
- margin-bottom: 1rem;
75
- }
76
- }