@mgks/docmd 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,167 @@
1
+ :root {
2
+ --header-height: 50px;
3
+ --border-color: #e0e0e0;
4
+ --bg-color: #f9fafb;
5
+ --primary-color: #007bff;
6
+ --resizer-width: 8px;
7
+ }
8
+
9
+ body {
10
+ margin: 0;
11
+ height: 100vh;
12
+ display: flex;
13
+ flex-direction: column;
14
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
15
+ overflow: hidden;
16
+ }
17
+
18
+ /* --- Top Bar --- */
19
+ .top-bar {
20
+ height: var(--header-height);
21
+ background: #fff;
22
+ border-bottom: 1px solid var(--border-color);
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ padding: 0 1rem;
27
+ flex-shrink: 0;
28
+ }
29
+
30
+ .logo {
31
+ font-weight: 700;
32
+ font-size: 1.1rem;
33
+ display: flex;
34
+ align-items: center;
35
+ gap: 12px;
36
+ }
37
+
38
+ .logo span {
39
+ background: var(--primary-color);
40
+ color: white;
41
+ padding: 2px 6px;
42
+ border-radius: 4px;
43
+ font-size: 0.75rem;
44
+ text-transform: uppercase;
45
+ }
46
+
47
+ .back-link {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ color: #666;
52
+ transition: color 0.2s, transform 0.2s;
53
+ text-decoration: none;
54
+ padding: 4px;
55
+ border-radius: 4px;
56
+ }
57
+
58
+ .back-link:hover {
59
+ color: var(--primary-color);
60
+ background: #f0f0f0;
61
+ transform: translateX(-2px);
62
+ }
63
+
64
+ .view-controls { display: flex; gap: 8px; background: #f0f0f0; padding: 3px; border-radius: 6px; }
65
+ .view-btn { border: none; background: transparent; padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; color: #666; font-weight: 500; }
66
+ .view-btn.active { background: white; color: black; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
67
+
68
+ /* --- Main Layout --- */
69
+ .workspace {
70
+ flex: 1;
71
+ display: flex;
72
+ position: relative;
73
+ overflow: hidden;
74
+ }
75
+
76
+ .pane {
77
+ height: 100%;
78
+ display: flex;
79
+ flex-direction: column;
80
+ min-width: 300px; /* Minimum width constraint */
81
+ background: white;
82
+ }
83
+
84
+ .pane-header {
85
+ padding: 8px 16px;
86
+ font-size: 0.75rem;
87
+ font-weight: 600;
88
+ text-transform: uppercase;
89
+ color: #888;
90
+ background: var(--bg-color);
91
+ border-bottom: 1px solid var(--border-color);
92
+ flex-shrink: 0;
93
+ }
94
+
95
+ /* Editor Pane */
96
+ .editor-pane { width: 50%; }
97
+ textarea#input {
98
+ flex: 1;
99
+ border: none;
100
+ resize: none;
101
+ padding: 20px;
102
+ font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
103
+ font-size: 14px;
104
+ line-height: 1.6;
105
+ outline: none;
106
+ background: var(--bg-color);
107
+ }
108
+
109
+ /* Preview Pane */
110
+ .preview-pane { flex: 1; background: white; }
111
+ iframe#preview { width: 100%; height: 100%; border: none; display: block; }
112
+
113
+ /* --- Resizer Handle --- */
114
+ .resizer {
115
+ width: var(--resizer-width);
116
+ background: var(--bg-color);
117
+ border-left: 1px solid var(--border-color);
118
+ border-right: 1px solid var(--border-color);
119
+ cursor: col-resize;
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ transition: background 0.2s;
124
+ z-index: 10;
125
+ }
126
+ .resizer:hover, .resizer.resizing { background: #e0e0e0; }
127
+ .resizer::after { content: "||"; color: #aaa; font-size: 10px; letter-spacing: 1px; }
128
+
129
+ /* --- View Modes (Single vs Split) --- */
130
+
131
+ /* Single Mode (Mobile style on Desktop) */
132
+ body.mode-single .resizer { display: none; }
133
+ body.mode-single .pane { width: 100% !important; min-width: 0; }
134
+ body.mode-single .editor-pane { display: none; }
135
+ body.mode-single .preview-pane { display: none; }
136
+ body.mode-single.show-editor .editor-pane { display: flex; }
137
+ body.mode-single.show-preview .preview-pane { display: flex; }
138
+
139
+ /* --- Mobile Responsive Overrides --- */
140
+ @media (max-width: 768px) {
141
+ /* Force single mode on mobile, hide desktop view controls */
142
+ .desktop-only { display: none !important; }
143
+ .resizer { display: none !important; }
144
+
145
+ .pane { width: 100% !important; }
146
+ .editor-pane { display: none; }
147
+ .preview-pane { display: none; }
148
+
149
+ body.mobile-tab-editor .editor-pane { display: flex; }
150
+ body.mobile-tab-preview .preview-pane { display: flex; }
151
+
152
+ .mobile-tabs {
153
+ display: flex !important;
154
+ position: fixed;
155
+ bottom: 0; left: 0; right: 0;
156
+ height: 50px;
157
+ background: white;
158
+ border-top: 1px solid var(--border-color);
159
+ z-index: 100;
160
+ }
161
+ .mobile-tab-btn { flex: 1; border: none; background: transparent; font-weight: 600; color: #888; cursor: pointer; }
162
+ .mobile-tab-btn.active { color: var(--primary-color); border-top: 2px solid var(--primary-color); }
163
+
164
+ .workspace { padding-bottom: 50px; } /* Space for tabs */
165
+ }
166
+
167
+ .mobile-tabs { display: none; } /* Hidden on desktop */
@@ -0,0 +1 @@
1
+ import { Buffer } from 'buffer'; globalThis.Buffer = Buffer;
@@ -0,0 +1,9 @@
1
+
2
+ const templates = {
3
+ "layout.ejs": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n <%- metaTagsHtml || '' %> <%# SEO Plugin Meta Tags %>\n\n <title><%= pageTitle %> : <%= siteTitle %></title>\n <% if (description && !(metaTagsHtml && metaTagsHtml.includes('name=\"description\"'))) { %>\n <meta name=\"description\" content=\"<%= description %>\">\n <% } %>\n\n <%- faviconLinkHtml || '' %> <%# Favicon %>\n\n <!-- Define Default Mode (Variable for script) -->\n <script>\n window.DOCMD_DEFAULT_MODE = \"<%= defaultMode %>\";\n </script>\n\n <!-- Highlight.js Theme -->\n <% if (config.theme?.codeHighlight !== false) { %>\n <link rel=\"stylesheet\" id=\"highlight-theme\" href=\"<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css\" data-base-href=\"<%= relativePathToRoot %>assets/css/\">\n <% } %>\n\n <!-- Main Theme -->\n <link rel=\"stylesheet\" href=\"<%= relativePathToRoot %>assets/css/docmd-main.css\">\n \n <%- themeCssLinkHtml || '' %> <%# For theme.name specific CSS %>\n\n <% (customCssFiles || []).forEach(cssFile => { %>\n <link rel=\"stylesheet\" href=\"<%= relativePathToRoot %><%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>\">\n <% }); %>\n\n <%- pluginStylesHtml || '' %> <%# Plugin specific CSS %>\n\n <!-- This checks storage and updates HTML attr + Highlight CSS before final paint -->\n <%- themeInitScript %>\n\n <%- pluginHeadScriptsHtml || '' %> <%# Plugin specific head scripts %>\n</head>\n<body class=\"<%= sidebarConfig.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>\"\n data-default-collapsed=\"<%= sidebarConfig.defaultCollapsed %>\"\n data-copy-code-enabled=\"<%= config.copyCode === true %>\">\n <aside class=\"sidebar\">\n <div class=\"sidebar-header\">\n <% if (logo && logo.light && logo.dark) { %>\n <a href=\"<%= logo.href || relativePathToRoot %>\" class=\"logo-link\">\n <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 %>;\"<% } %>>\n <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 %>;\"<% } %>>\n </a>\n <% } else { %>\n <h1><a href=\"<%= relativePathToRoot %>index.html\"><%= siteTitle %></a></h1>\n <% } %>\n <span class=\"mobile-view sidebar-menu-button float-right\">\n <%- renderIcon(\"ellipsis-vertical\") %>\n </span>\n </div>\n <%- navigationHtml %>\n <% if (theme && theme.enableModeToggle && theme.positionMode !== 'top') { %>\n <button id=\"theme-toggle-button\" aria-label=\"Toggle theme\" class=\"theme-toggle-button\">\n <%- renderIcon('sun', { class: 'icon-sun' }) %>\n <%- renderIcon('moon', { class: 'icon-moon' }) %>\n </button>\n <% } %>\n </aside>\n <div class=\"main-content-wrapper\">\n <div class=\"page-header\">\n <div class=\"header-left\">\n <% if (sidebarConfig.collapsible) { %>\n <button id=\"sidebar-toggle-button\" class=\"sidebar-toggle-button\" aria-label=\"Toggle Sidebar\">\n <%- renderIcon('panel-left-close') %>\n </button>\n <% } %>\n <h1><%= pageTitle %></h1>\n </div>\n <% if (theme && theme.enableModeToggle && theme.positionMode === 'top') { %>\n <div class=\"header-right\">\n <% if (config.search !== false) { %>\n <button class=\"docmd-search-trigger\" aria-label=\"Search\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-search-icon lucide-search\"><path d=\"m21 21-4.34-4.34\"></path><circle cx=\"11\" cy=\"11\" r=\"8\"></circle></svg>\n <span class=\"search-label\">Search</span>\n <span class=\"search-keys\">\n <kbd class=\"docmd-kbd\">⌘</kbd><kbd class=\"docmd-kbd\">k</kbd>\n </span>\n </button>\n <% } %>\n <button id=\"theme-toggle-button\" aria-label=\"Toggle theme\" class=\"theme-toggle-button theme-toggle-header\">\n <%# renderIcon is available in the global EJS scope from html-generator %>\n <%- renderIcon('sun', { class: 'icon-sun' }) %>\n <%- renderIcon('moon', { class: 'icon-moon' }) %>\n </button>\n </div>\n <% } %>\n </div>\n <main class=\"content-area\">\n <div class=\"content-layout\">\n <div class=\"main-content\">\n <%- content %>\n \n <% if (config.pageNavigation && (prevPage || nextPage)) { %>\n <div class=\"page-navigation\">\n <% if (prevPage) { %>\n <a href=\"<%= prevPage.url %>\" class=\"prev-page\">\n <%- renderIcon('arrow-left', { class: 'page-nav-icon' }) %>\n <span>\n <small>Previous</small>\n <strong><%= prevPage.title %></strong>\n </span>\n </a>\n <% } else { %>\n <div class=\"prev-page-placeholder\"></div>\n <% } %>\n \n <% if (nextPage) { %>\n <a href=\"<%= nextPage.url %>\" class=\"next-page\">\n <span>\n <small>Next</small>\n <strong><%= nextPage.title %></strong>\n </span>\n <%- renderIcon('arrow-right', { class: 'page-nav-icon' }) %>\n </a>\n <% } else { %>\n <div class=\"next-page-placeholder\"></div>\n <% } %>\n </div>\n <% } %>\n </div>\n \n <!-- DEBUG: <%- JSON.stringify({headingsLength: headings ? headings.length : 0}) %> -->\n \n <!-- TOC sidebar -->\n <div class=\"toc-sidebar\">\n <%- include('toc', { content, headings, navigationHtml, isActivePage }) %>\n </div>\n </div>\n\n <!-- Page footer actions -->\n <div class=\"page-footer-actions\">\n <% if (locals.editUrl) { %>\n <a href=\"<%= editUrl %>\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"edit-link\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-pencil\"><path d=\"M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z\"/><path d=\"m15 5 4 4\"/></svg>\n <%= editLinkText %>\n </a>\n <% } %>\n\n <% if (locals.lastUpdated) { %>\n <!-- Placeholder for future Last Updated feature -->\n <% } %>\n </div>\n </main>\n\n <footer class=\"page-footer\">\n <div class=\"footer-content\">\n <div class=\"user-footer\">\n <%- footerHtml || '' %>\n </div>\n <div class=\"branding-footer\">\n 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://github.com/mgks/docmd\" target=\"_blank\" rel=\"noopener\">docmd.</a>\n </div>\n </div>\n </footer>\n </div>\n\n <% if (config.search !== false) { %>\n <!-- Search Modal -->\n <div id=\"docmd-search-modal\" class=\"docmd-search-modal\" style=\"display: none;\">\n <div class=\"docmd-search-box\">\n <div class=\"docmd-search-header\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1.25em\" height=\"1.25em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-search-icon lucide-search\"><path d=\"m21 21-4.34-4.34\"></path><circle cx=\"11\" cy=\"11\" r=\"8\"></circle></svg>\n <input type=\"text\" id=\"docmd-search-input\" placeholder=\"Search documentation...\" autocomplete=\"off\" spellcheck=\"false\">\n <button onclick=\"window.closeDocmdSearch()\" class=\"docmd-search-close\" aria-label=\"Close search\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"2em\" height=\"2em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-x-icon lucide-x\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>\n </button>\n </div>\n <div id=\"docmd-search-results\" class=\"docmd-search-results\">\n <!-- Results injected here -->\n </div>\n <div class=\"docmd-search-footer\">\n <span><kbd class=\"docmd-kbd\">↑</kbd> <kbd class=\"docmd-kbd\">↓</kbd> to navigate</span>\n <span><kbd class=\"docmd-kbd\">ESC</kbd> to close</span>\n </div>\n </div>\n </div>\n <% } %>\n\n <script>\n window.DOCMD_ROOT = \"<%= relativePathToRoot %>\";\n </script>\n\n <script src=\"<%= relativePathToRoot %>assets/js/docmd-main.js\"></script>\n\n <% if (config.search !== false) { %>\n <!-- Search Scripts -->\n <script src=\"<%= relativePathToRoot %>assets/js/minisearch.js\"></script>\n <script src=\"<%= relativePathToRoot %>assets/js/docmd-search.js\"></script>\n <% } %>\n\n <!-- Mermaid.js for diagram rendering -->\n <script src=\"<%= relativePathToRoot %>assets/js/mermaid.min.js\"></script>\n <script src=\"<%= relativePathToRoot %>assets/js/docmd-mermaid.js\"></script>\n\n <% (customJsFiles || []).forEach(jsFile => { %>\n <script src=\"<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>\"></script>\n <% }); %>\n\n <%- pluginBodyScriptsHtml || '' %>\n \n <% if (sponsor && sponsor.enabled) { %>\n <div class=\"sponsor-ribbon\">\n <a href=\"<%= sponsor.link %>\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"sponsor-link\">\n <%- renderIcon('heart', { class: 'sponsor-icon' }) %>\n <span class=\"sponsor-text\"><%= sponsor.title %></span>\n </a>\n </div>\n <% } %>\n</body>\n</html>",
4
+ "navigation.ejs": "<%# navigation.ejs - Renders the sidebar navigation %>\n<nav class=\"sidebar-nav\" aria-label=\"Main navigation\">\n <ul>\n <%\n // Helper: Normalize paths for comparison\n function normalizePath(p) {\n if (!p) return '#';\n let path = p.replace(/\\\\/g, '/');\n \n // Strip leading ./ if present\n if (path.startsWith('./')) {\n path = path.substring(2);\n }\n\n // Handle file extensions\n if (path.endsWith('index.md')) {\n path = path.slice(0, -'index.md'.length);\n } else {\n path = path.replace(/\\.md$/, '');\n }\n\n // Ensure leading slash\n if (!path.startsWith('/')) path = '/' + path;\n\n // Ensure trailing slash\n if (path.length > 1 && !path.endsWith('/')) {\n path += '/';\n }\n \n // Handle root\n if (path === '') path = '/';\n \n return path;\n }\n\n // Helper: Recursively check if an item contains the active page\n function hasActiveChild(item, currentPagePath) {\n if (!item.children || !Array.isArray(item.children)) return false;\n return item.children.some(child => {\n if (!child.path || child.external) return false;\n const childPath = normalizePath(child.path);\n \n if (currentPagePath === childPath) return true;\n \n return hasActiveChild(child, currentPagePath);\n });\n }\n\n // Recursive function to render navigation items\n function renderNav(items) {\n if (!items || !Array.isArray(items)) return;\n \n items.forEach(item => {\n const isExternal = item.external || false;\n let itemPath = item.path || '#';\n const normalizedItemPath = isExternal ? itemPath : normalizePath(itemPath);\n\n // 1. Determine State\n const isActive = !isExternal && currentPagePath === normalizedItemPath;\n const isParentOfActive = !isActive && hasActiveChild(item, currentPagePath);\n const isCollapsible = item.children && item.collapsible;\n \n // 2. Open State (Server-Side Calculation)\n const shouldBeOpen = isParentOfActive || (isActive && item.children && item.children.length > 0);\n\n // 3. Build Attributes String\n const liClasses = [];\n if (isActive) liClasses.push('active');\n if (isParentOfActive) liClasses.push('active-parent');\n if (isCollapsible) liClasses.push('collapsible');\n \n let liAttributes = `class=\"${liClasses.join(' ')}\"`;\n if (isCollapsible) {\n liAttributes += ` data-nav-id=\"${item.path}\"`;\n if (shouldBeOpen) {\n liAttributes += ` aria-expanded=\"true\"`;\n }\n }\n\n // 4. Build Link URL\n let finalHref = item.path;\n \n if (!isExternal) {\n if (!itemPath || itemPath === '#') {\n finalHref = '#';\n } else {\n let cleanPath = item.path;\n if (cleanPath.startsWith('/')) cleanPath = cleanPath.substring(1);\n \n finalHref = relativePathToRoot + cleanPath;\n\n const offline = (typeof locals !== 'undefined' && locals.isOfflineMode) === true;\n\n if (offline) {\n // Offline Mode Logic\n if (finalHref.endsWith('/')) {\n finalHref += 'index.html';\n } else if (finalHref === './') {\n finalHref = './index.html';\n } else if (!finalHref.includes('#')) {\n // FIX: Check only the last segment for a dot (extension)\n // e.g. \"../../guide\" -> last segment \"guide\" (no dot) -> add index.html\n // e.g. \"../../guide.html\" -> last segment \"guide.html\" (has dot) -> skip\n \n const parts = finalHref.split('?')[0].split('/'); // Remove query, split by slash\n const lastSegment = parts[parts.length - 1];\n \n if (!lastSegment.includes('.')) {\n finalHref += '/index.html';\n }\n }\n } else {\n // Clean URL Mode\n if (finalHref.endsWith('/index.html')) {\n finalHref = finalHref.substring(0, finalHref.length - 10);\n }\n }\n }\n }\n %>\n <li <%- liAttributes %>>\n <a href=\"<%- finalHref %>\" class=\"<%- isActive ? 'active' : '' %>\" <%- isExternal ? 'target=\"_blank\" rel=\"noopener\"' : '' %>>\n <% if (item.icon) { %> <%- renderIcon(item.icon) %> <% } %>\n <span class=\"nav-item-title\"><%= item.title %></span>\n <% if (isCollapsible) { %> <%- renderIcon('chevron-right', { class: 'collapse-icon' }) %> <% } %>\n <% if (isExternal) { %> <%- renderIcon('external-link', { class: 'nav-external-icon' }) %> <% } %>\n </a>\n <% if (item.children) { %>\n <ul class=\"submenu\" style=\"display: <%= (isCollapsible && shouldBeOpen) ? 'block' : 'none' %>;\">\n <% renderNav(item.children); %>\n </ul>\n <% } %>\n </li>\n <%\n });\n }\n renderNav(navItems);\n %>\n </ul>\n</nav>",
5
+ "no-style.ejs": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n <% if (frontmatter.components?.meta !== false) { %>\n <%- metaTagsHtml || '' %>\n <title><%= pageTitle %><% if (frontmatter.components?.siteTitle !== false) { %> | <%= siteTitle %><% } %></title>\n <% if (description && !(metaTagsHtml && metaTagsHtml.includes('name=\"description\"'))) { %>\n <meta name=\"description\" content=\"<%= description %>\">\n <% } %>\n <% } %>\n\n <% if (frontmatter.components?.favicon !== false) { %>\n <%- faviconLinkHtml || '' %>\n <% } %>\n\n <% if (frontmatter.components?.themeMode !== false) { %>\n <script>window.DOCMD_DEFAULT_MODE = \"<%= defaultMode %>\";</script>\n <% } %>\n\n <% if (frontmatter.components?.css !== false) { %>\n <link rel=\"stylesheet\" href=\"<%= relativePathToRoot %>assets/css/docmd-main.css\">\n <% if (frontmatter.components?.highlight !== false) { %>\n <link rel=\"stylesheet\" href=\"<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css\" id=\"highlight-theme\">\n <% } %>\n <% } %>\n\n <% if (frontmatter.components?.themeMode !== false) { %>\n <%- themeInitScript %>\n <% } %>\n\n <% if (frontmatter.components?.theme !== false) { %>\n <%- themeCssLinkHtml || '' %>\n <% } %>\n\n <% if (frontmatter.components?.customCss !== false && customCssFiles && customCssFiles.length > 0) { %>\n <% customCssFiles.forEach(cssFile => { %>\n <link rel=\"stylesheet\" href=\"<%= relativePathToRoot %><%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>\">\n <% }); %>\n <% } %>\n\n <% if (frontmatter.components?.pluginStyles !== false) { %>\n <%- pluginStylesHtml || '' %>\n <% } %>\n\n <% if (frontmatter.components?.pluginHeadScripts !== false) { %>\n <%- pluginHeadScriptsHtml || '' %>\n <% } %>\n \n <% if (frontmatter.customHead) { %>\n <%- frontmatter.customHead %>\n <% } %>\n</head>\n<body\n <%\n if (frontmatter.components?.theme !== false) {\n %> data-theme=\"<%= defaultMode %>\"<%\n }\n %><%\n if (frontmatter.bodyClass) {\n %> class=\"<%= frontmatter.bodyClass %>\"<%\n }\n %> data-copy-code-enabled=\"<%= config.copyCode === true %>\">\n <% if (frontmatter.components?.layout === true || frontmatter.components?.layout === 'full') { %>\n <div class=\"main-content-wrapper\">\n <% if (frontmatter.components?.header !== false) { %>\n <header class=\"page-header\">\n <% if (frontmatter.components?.pageTitle !== false) { %>\n <h1><%= pageTitle %></h1>\n <% } %>\n </header>\n <% } %>\n <main class=\"content-area\">\n <div class=\"content-layout\">\n <div class=\"main-content\">\n <%- content %>\n </div>\n </div>\n </main>\n <% if (frontmatter.components?.footer !== false) { %>\n <footer class=\"page-footer\">\n <div class=\"footer-content\">\n <div class=\"user-footer\">\n <%- footerHtml || '' %>\n </div>\n <% if (frontmatter.components?.branding !== false) { %>\n <div class=\"branding-footer\">\n 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>\n </div>\n <% } %>\n </div>\n </footer>\n <% } %>\n </div>\n <% } else if (frontmatter.components?.sidebar === true) { %>\n <aside class=\"sidebar\">\n <% if (frontmatter.components?.logo !== false && logo && logo.light && logo.dark) { %>\n <div class=\"sidebar-header\">\n <a href=\"<%= logo.href || (relativePathToRoot + 'index.html') %>\" class=\"logo-link\">\n <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 %>;\"<% } %>>\n <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 %>;\"<% } %>>\n </a>\n </div>\n <% } %>\n <% if (frontmatter.components?.navigation !== false) { %>\n <%- navigationHtml %>\n <% } %>\n <% if (frontmatter.components?.themeToggle !== false && theme && theme.enableModeToggle) { %>\n <button id=\"theme-toggle-button\" aria-label=\"Toggle theme\" class=\"theme-toggle-button\">\n <%- renderIcon('sun', { class: 'icon-sun' }) %>\n <%- renderIcon('moon', { class: 'icon-moon' }) %>\n </button>\n <% } %>\n </aside>\n <div class=\"main-content-wrapper\">\n <% if (frontmatter.components?.header !== false) { %>\n <header class=\"page-header\">\n <% if (frontmatter.components?.pageTitle !== false) { %>\n <h1><%= pageTitle %></h1>\n <% } %>\n </header>\n <% } %>\n <main class=\"content-area\">\n <div class=\"content-layout\">\n <div class=\"main-content\">\n <%- content %>\n </div>\n <% if (frontmatter.components?.toc !== false && headings && headings.length > 0) { %>\n <div class=\"toc-sidebar\">\n <%- include('toc', { content, headings, navigationHtml, isActivePage }) %>\n </div>\n <% } %>\n </div>\n </main>\n <% if (frontmatter.components?.footer !== false) { %>\n <footer class=\"page-footer\">\n <div class=\"footer-content\">\n <div class=\"user-footer\">\n <%- footerHtml || '' %>\n </div>\n <% if (frontmatter.components?.branding !== false) { %>\n <div class=\"branding-footer\">\n 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>\n </div>\n <% } %>\n </div>\n </footer>\n <% } %>\n </div>\n <% } else { %>\n <%- content %>\n <% } %>\n\n <% if (frontmatter.components?.scripts === true) { %>\n <% if (frontmatter.components?.mainScripts === true) { %>\n <script src=\"<%= relativePathToRoot %>assets/js/docmd-main.js\"></script>\n <% } %>\n \n <% if (frontmatter.components?.lightbox === true && frontmatter.components?.mainScripts === true) { %>\n <script src=\"<%= relativePathToRoot %>assets/js/docmd-image-lightbox.js\"></script>\n <% } %>\n \n <% if (frontmatter.components?.customJs === true && customJsFiles && customJsFiles.length > 0) { %>\n <% customJsFiles.forEach(jsFile => { %>\n <script src=\"<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>\"></script>\n <% }); %>\n <% } %>\n \n <% if (frontmatter.components?.pluginBodyScripts === true) { %>\n <%- pluginBodyScriptsHtml || '' %>\n <% } %>\n <% } %>\n \n <% if (frontmatter.customScripts) { %>\n <%- frontmatter.customScripts %>\n <% } %>\n</body>\n</html> ",
6
+ "toc.ejs": "<%# src/templates/toc.ejs %>\n<% \n// Helper function to decode HTML entities\nfunction decodeHtmlEntities(html) {\n return html\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&nbsp;/g, ' ');\n}\n\n// Use the isActivePage flag if provided, otherwise fall back to checking navigationHtml\nconst shouldShowToc = typeof isActivePage !== 'undefined' ? isActivePage : \n (typeof navigationHtml !== 'undefined' && navigationHtml && navigationHtml.includes('class=\"active\"'));\n\nif (shouldShowToc && !frontmatter?.toc || frontmatter?.toc !== 'false') {\n // If direct headings aren't available, we'll try to extract them from the content\n let tocHeadings = [];\n if (headings && headings.length > 0) {\n // Use provided headings if available\n tocHeadings = headings.filter(h => h.level >= 2 && h.level <= 4);\n } else if (content) {\n // Basic regex to extract headings from HTML content\n const headingRegex = /<h([2-4])[^>]*?(?:id=\"([^\"]*)\")?[^>]*?>([\\s\\S]*?)<\\/h\\1>/g;\n let match;\n let contentStr = content.toString();\n \n while ((match = headingRegex.exec(contentStr)) !== null) {\n const level = parseInt(match[1], 10);\n // Use ID if available, or generate one from the text\n let id = match[2];\n // Remove any HTML tags inside the heading text\n const textWithTags = match[3].replace(/<\\/?[^>]+(>|$)/g, '');\n // Decode HTML entities\n const text = decodeHtmlEntities(textWithTags);\n \n if (!id) {\n // Generate an ID from the heading text if none exists\n id = text\n .toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[^\\w-]/g, '')\n .replace(/--+/g, '-')\n .replace(/^-+|-+$/g, '');\n }\n \n tocHeadings.push({ id, level, text });\n }\n }\n\n // Only show TOC if there are enough headings\n if (tocHeadings.length > 1) { \n%>\n <div class=\"toc-container\">\n <h2 class=\"toc-title\">On This Page<span class=\"mobile-view toc-menu-button float-right\"><%- renderIcon(\"chevrons-down-up\") %></span></h2>\n <ul class=\"toc-list\">\n <% tocHeadings.forEach(heading => { %>\n <li class=\"toc-item toc-level-<%= heading.level %>\">\n <a href=\"#<%= heading.id %>\" class=\"toc-link\"><%- heading.text %></a>\n </li>\n <% }); %>\n </ul>\n </div>\n<% } \n} %> "
7
+ };
8
+ if (typeof globalThis !== 'undefined') globalThis.__DOCMD_TEMPLATES__ = templates;
9
+ module.exports = templates;
@@ -44,17 +44,17 @@
44
44
  data-copy-code-enabled="<%= config.copyCode === true %>">
45
45
  <aside class="sidebar">
46
46
  <div class="sidebar-header">
47
- <% if (logo && logo.light && logo.dark) { %>
48
- <a href="<%= logo.href || (relativePathToRoot + 'index.html') %>" class="logo-link">
49
- <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 %>;"<% } %>>
50
- <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 %>;"<% } %>>
51
- </a>
52
- <% } else { %>
53
- <h1><a href="<%= relativePathToRoot %>index.html"><%= siteTitle %></a></h1>
54
- <% } %>
55
- <span class="mobile-view sidebar-menu-button float-right">
56
- <%- renderIcon("ellipsis-vertical") %>
57
- </span>
47
+ <% if (logo && logo.light && logo.dark) { %>
48
+ <a href="<%= logo.href || relativePathToRoot %>" class="logo-link">
49
+ <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 %>;"<% } %>>
50
+ <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 %>;"<% } %>>
51
+ </a>
52
+ <% } else { %>
53
+ <h1><a href="<%= relativePathToRoot %>index.html"><%= siteTitle %></a></h1>
54
+ <% } %>
55
+ <span class="mobile-view sidebar-menu-button float-right">
56
+ <%- renderIcon("ellipsis-vertical") %>
57
+ </span>
58
58
  </div>
59
59
  <%- navigationHtml %>
60
60
  <% if (theme && theme.enableModeToggle && theme.positionMode !== 'top') { %>
@@ -2,36 +2,46 @@
2
2
  <nav class="sidebar-nav" aria-label="Main navigation">
3
3
  <ul>
4
4
  <%
5
- // A robust, centralized function to normalize navigation paths
5
+ // Helper: Normalize paths for comparison
6
6
  function normalizePath(p) {
7
7
  if (!p) return '#';
8
8
  let path = p.replace(/\\/g, '/');
9
9
 
10
+ // Strip leading ./ if present
11
+ if (path.startsWith('./')) {
12
+ path = path.substring(2);
13
+ }
14
+
15
+ // Handle file extensions
10
16
  if (path.endsWith('index.md')) {
11
17
  path = path.slice(0, -'index.md'.length);
12
18
  } else {
13
19
  path = path.replace(/\.md$/, '');
14
20
  }
15
21
 
22
+ // Ensure leading slash
16
23
  if (!path.startsWith('/')) path = '/' + path;
17
24
 
25
+ // Ensure trailing slash
18
26
  if (path.length > 1 && !path.endsWith('/')) {
19
27
  path += '/';
20
28
  }
21
29
 
22
- // Handle root case where path might be "" after stripping index.md
30
+ // Handle root
23
31
  if (path === '') path = '/';
24
32
 
25
33
  return path;
26
34
  }
27
35
 
28
- // Helper to check for active children recursively
36
+ // Helper: Recursively check if an item contains the active page
29
37
  function hasActiveChild(item, currentPagePath) {
30
38
  if (!item.children || !Array.isArray(item.children)) return false;
31
39
  return item.children.some(child => {
32
40
  if (!child.path || child.external) return false;
33
41
  const childPath = normalizePath(child.path);
42
+
34
43
  if (currentPagePath === childPath) return true;
44
+
35
45
  return hasActiveChild(child, currentPagePath);
36
46
  });
37
47
  }
@@ -39,24 +49,76 @@
39
49
  // Recursive function to render navigation items
40
50
  function renderNav(items) {
41
51
  if (!items || !Array.isArray(items)) return;
52
+
42
53
  items.forEach(item => {
43
54
  const isExternal = item.external || false;
44
55
  let itemPath = item.path || '#';
45
-
46
56
  const normalizedItemPath = isExternal ? itemPath : normalizePath(itemPath);
47
57
 
58
+ // 1. Determine State
48
59
  const isActive = !isExternal && currentPagePath === normalizedItemPath;
49
60
  const isParentOfActive = !isActive && hasActiveChild(item, currentPagePath);
50
61
  const isCollapsible = item.children && item.collapsible;
62
+
63
+ // 2. Open State (Server-Side Calculation)
64
+ const shouldBeOpen = isParentOfActive || (isActive && item.children && item.children.length > 0);
51
65
 
66
+ // 3. Build Attributes String
52
67
  const liClasses = [];
53
68
  if (isActive) liClasses.push('active');
54
69
  if (isParentOfActive) liClasses.push('active-parent');
55
70
  if (isCollapsible) liClasses.push('collapsible');
56
71
 
57
- const finalHref = isExternal ? item.path : (itemPath === '#' ? '#' : (relativePathToRoot + (item.path.startsWith('/') ? item.path.substring(1) : item.path)));
72
+ let liAttributes = `class="${liClasses.join(' ')}"`;
73
+ if (isCollapsible) {
74
+ liAttributes += ` data-nav-id="${item.path}"`;
75
+ if (shouldBeOpen) {
76
+ liAttributes += ` aria-expanded="true"`;
77
+ }
78
+ }
79
+
80
+ // 4. Build Link URL
81
+ let finalHref = item.path;
82
+
83
+ if (!isExternal) {
84
+ if (!itemPath || itemPath === '#') {
85
+ finalHref = '#';
86
+ } else {
87
+ let cleanPath = item.path;
88
+ if (cleanPath.startsWith('/')) cleanPath = cleanPath.substring(1);
89
+
90
+ finalHref = relativePathToRoot + cleanPath;
91
+
92
+ const offline = (typeof locals !== 'undefined' && locals.isOfflineMode) === true;
93
+
94
+ if (offline) {
95
+ // Offline Mode Logic
96
+ if (finalHref.endsWith('/')) {
97
+ finalHref += 'index.html';
98
+ } else if (finalHref === './') {
99
+ finalHref = './index.html';
100
+ } else if (!finalHref.includes('#')) {
101
+ // FIX: Check only the last segment for a dot (extension)
102
+ // e.g. "../../guide" -> last segment "guide" (no dot) -> add index.html
103
+ // e.g. "../../guide.html" -> last segment "guide.html" (has dot) -> skip
104
+
105
+ const parts = finalHref.split('?')[0].split('/'); // Remove query, split by slash
106
+ const lastSegment = parts[parts.length - 1];
107
+
108
+ if (!lastSegment.includes('.')) {
109
+ finalHref += '/index.html';
110
+ }
111
+ }
112
+ } else {
113
+ // Clean URL Mode
114
+ if (finalHref.endsWith('/index.html')) {
115
+ finalHref = finalHref.substring(0, finalHref.length - 10);
116
+ }
117
+ }
118
+ }
119
+ }
58
120
  %>
59
- <li class="<%= liClasses.join(' ') %>" <%- isCollapsible ? `data-nav-id="${item.path}"` : '' %>>
121
+ <li <%- liAttributes %>>
60
122
  <a href="<%- finalHref %>" class="<%- isActive ? 'active' : '' %>" <%- isExternal ? 'target="_blank" rel="noopener"' : '' %>>
61
123
  <% if (item.icon) { %> <%- renderIcon(item.icon) %> <% } %>
62
124
  <span class="nav-item-title"><%= item.title %></span>
@@ -64,7 +126,7 @@
64
126
  <% if (isExternal) { %> <%- renderIcon('external-link', { class: 'nav-external-icon' }) %> <% } %>
65
127
  </a>
66
128
  <% if (item.children) { %>
67
- <ul class="submenu">
129
+ <ul class="submenu" style="display: <%= (isCollapsible && shouldBeOpen) ? 'block' : 'none' %>;">
68
130
  <% renderNav(item.children); %>
69
131
  </ul>
70
132
  <% } %>
package/config.js DELETED
@@ -1,175 +0,0 @@
1
- // Source file from the docmd project — https://github.com/mgks/docmd
2
-
3
- module.exports = {
4
- // --- Core Metadata ---
5
- siteTitle: 'docmd',
6
- siteUrl: 'https://docmd.mgks.dev', // No trailing slash
7
-
8
- // --- Branding ---
9
- logo: {
10
- light: '/assets/images/docmd-logo-light.png',
11
- dark: '/assets/images/docmd-logo-dark.png',
12
- alt: 'docmd Logo',
13
- href: '/',
14
- },
15
- favicon: '/assets/favicon.ico',
16
-
17
- // --- Structure ---
18
- srcDir: 'docs', // Source markdown files directory
19
- outputDir: 'site', // Output directory for generated site
20
-
21
- // --- Features & UX ---
22
- search: true, // Built-in offline search
23
- minify: true, // Production build optimization
24
- autoTitleFromH1: true, // Auto-generate title from first H1 if frontmatter title is missing
25
- copyCode: true, // Enable "copy to clipboard" on code blocks
26
- pageNavigation: true, // Next/Prev links
27
-
28
- // --- Sidebar & Theme ---
29
- sidebar: {
30
- collapsible: true,
31
- defaultCollapsed: false,
32
- },
33
- theme: {
34
- name: 'sky', // 'default', 'sky', 'ruby', 'retro'
35
- defaultMode: 'light', // 'light' or 'dark'
36
- enableModeToggle: true, // Show theme mode toggle button
37
- positionMode: 'top', // 'top' or 'bottom' of header
38
- codeHighlight: true, // Enable code syntax highlighting
39
- customCss: [], // Add paths relative to outputDir here
40
- },
41
- customJs: [
42
- '/assets/js/docmd-image-lightbox.js',
43
- ],
44
-
45
- // --- Plugins ---
46
- plugins: {
47
- seo: {
48
- defaultDescription: 'The minimalist, zero-config documentation generator for Node.js developers.',
49
- openGraph: {
50
- defaultImage: '/assets/images/docmd-preview.png',
51
- },
52
- twitter: {
53
- cardType: 'summary_large_image',
54
- }
55
- },
56
- analytics: {
57
- googleV4: {
58
- measurementId: 'G-8QVBDQ4KM1'
59
- }
60
- },
61
- sitemap: {
62
- defaultChangefreq: 'weekly',
63
- defaultPriority: 0.8
64
- }
65
- },
66
-
67
- // --- Doc Source Link ---
68
- editLink: {
69
- enabled: true,
70
- baseUrl: 'https://github.com/mgks/docmd/edit/main/docs',
71
- text: 'Edit this page on GitHub'
72
- },
73
-
74
- // --- Navigation ---
75
- navigation: [
76
- { title: 'Welcome', path: '/', icon: 'feather' },
77
- { title: 'Overview', path: '/overview', icon: 'home' },
78
-
79
- {
80
- title: 'Getting Started',
81
- icon: 'rocket',
82
- path: '/getting-started/',
83
- children: [
84
- { title: 'Installation', path: '/getting-started/installation', icon: 'download' },
85
- { title: 'Basic Usage', path: '/getting-started/basic-usage', icon: 'play' },
86
- ],
87
- },
88
-
89
- { title: 'Configuration', path: '/configuration', icon: 'settings' },
90
-
91
- {
92
- title: 'Content',
93
- icon: 'layout-template',
94
- path: '/content/',
95
- collapsible: true,
96
- children: [
97
- { title: 'Markdown Syntax', path: '/content/markdown-syntax', icon: 'code-2' },
98
- { title: 'Frontmatter', path: '/content/frontmatter', icon: 'file-text' },
99
- { title: 'Images & Lightbox', path: '/content/images', icon: 'image' },
100
- { title: 'Search', path: '/content/search', icon: 'search' },
101
- { title: 'Mermaid Diagrams', path: '/content/mermaid', icon: 'network' },
102
- {
103
- title: 'Containers',
104
- path: '/content/containers/',
105
- icon: 'box',
106
- collapsible: true,
107
- children: [
108
- { title: 'Callouts', path: '/content/containers/callouts', icon: 'megaphone' },
109
- { title: 'Cards', path: '/content/containers/cards', icon: 'panel-top' },
110
- { title: 'Steps', path: '/content/containers/steps', icon: 'list-ordered' },
111
- { title: 'Tabs', path: '/content/containers/tabs', icon: 'columns-3' },
112
- { title: 'Collapsible', path: '/content/containers/collapsible', icon: 'chevrons-down' },
113
- { title: 'Changelogs', path: '/content/containers/changelogs', icon: 'history' },
114
- { title: 'Buttons', path: '/content/containers/buttons', icon: 'mouse-pointer-click' },
115
- { title: 'Nested Containers', path: '/content/containers/nested-containers', icon: 'folder-tree' },
116
- ]
117
- },
118
- { title: 'No-Style Pages', path: '/content/no-style-pages', icon: 'layout' },
119
- ],
120
- },
121
-
122
- {
123
- title: 'Theming',
124
- icon: 'palette',
125
- path: '/theming/',
126
- collapsible: true,
127
- children: [
128
- { title: 'Available Themes', path: '/theming/available-themes', icon: 'layout-grid' },
129
- { title: 'Light & Dark Mode', path: '/theming/light-dark-mode', icon: 'sun-moon' },
130
- { title: 'Custom CSS & JS', path: '/theming/custom-css-js', icon: 'file-code' },
131
- { title: 'Icons', path: '/theming/icons', icon: 'pencil-ruler' },
132
- ],
133
- },
134
-
135
- {
136
- title: 'Plugins',
137
- icon: 'puzzle',
138
- path: '/plugins/',
139
- collapsible: true,
140
- children: [
141
- { title: 'SEO & Meta', path: '/plugins/seo', icon: 'search' },
142
- { title: 'Analytics', path: '/plugins/analytics', icon: 'bar-chart' },
143
- { title: 'Sitemap', path: '/plugins/sitemap', icon: 'map' },
144
- ],
145
- },
146
-
147
- {
148
- title: 'Recipes',
149
- icon: 'chef-hat',
150
- path: '/recipes/',
151
- collapsible: true,
152
- children: [
153
- { title: 'Landing Page', path: '/recipes/landing-page', icon: 'layout-template' },
154
- { title: 'Custom Fonts', path: '/recipes/custom-fonts', icon: 'type' },
155
- { title: 'Favicon', path: '/recipes/favicon', icon: 'image-plus' },
156
- ],
157
- },
158
-
159
- { title: 'CLI Commands', path: '/cli-commands', icon: 'terminal' },
160
- { title: 'Deployment', path: '/deployment', icon: 'upload-cloud' },
161
- { title: 'Comparison', path: '/comparison', icon: 'scale' },
162
- { title: 'Contributing', path: '/contributing', icon: 'git-pull-request' },
163
-
164
- { title: 'GitHub', path: 'https://github.com/mgks/docmd', icon: 'github', external: true },
165
- { title: 'Discussions', path: 'https://github.com/mgks/docmd/discussions', icon: 'message-circle', external: true },
166
- ],
167
-
168
- // --- Footer & Sponsor ---
169
- footer: '© ' + new Date().getFullYear() + ' Project docmd.',
170
- sponsor: {
171
- enabled: true,
172
- title: 'Sponsor',
173
- link: 'https://github.com/sponsors/mgks',
174
- },
175
- };