@docmd/ui 0.4.7 → 0.4.8

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.
@@ -12,8 +12,6 @@
12
12
  <meta charset="UTF-8">
13
13
  <meta name="generator" content="docmd v0.4.x">
14
14
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
15
-
16
- <!-- Define Globals -->
17
15
  <script>
18
16
  var root = "<%= relativePathToRoot %>";
19
17
  if (root && !root.endsWith('/')) root += '/';
@@ -21,16 +19,9 @@
21
19
  window.DOCMD_ROOT = root;
22
20
  window.DOCMD_DEFAULT_MODE = "<%= defaultMode %>";
23
21
  </script>
24
-
25
22
  <title><%= pageTitle %></title>
26
-
27
- <!-- Favicon -->
28
23
  <%- faviconLinkHtml || '' %>
29
-
30
- <!-- 1. CORE STYLES (Foundation) -->
31
24
  <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css?v=<%= buildHash %>">
32
-
33
- <!-- 2. HIGHLIGHT.JS (Toggle Strategy) -->
34
25
  <% if (config.theme?.codeHighlight !== false) {
35
26
  const isDarkDefault = defaultMode === 'dark';
36
27
  %>
@@ -44,23 +35,19 @@
44
35
  id="hljs-dark"
45
36
  <%= isDarkDefault ? '' : 'disabled' %>>
46
37
  <% } %>
47
-
48
- <!-- 3. PLUGINS & THEMES (Overrides Core) -->
49
- <!-- This includes 'docmd-theme-sky.css' injected by build.js -->
50
38
  <%- pluginHeadScriptsHtml || '' %>
51
-
52
- <!-- 4. CUSTOM CSS (Overrides Everything) -->
53
39
  <% (customCssFiles || []).forEach(cssFile => { %>
54
40
  <link rel="stylesheet" href="<%= relativePathToRoot %><%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>?v=<%= buildHash %>">
55
41
  <% }); %>
56
-
57
- <!-- Theme Init Script (Must be after styles to prevent FOUC) -->
58
42
  <%- themeInitScript %>
59
43
  </head>
60
- <body class="<%= sidebarConfig.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>"
61
- data-default-collapsed="<%= sidebarConfig.defaultCollapsed %>"
62
- data-copy-code-enabled="<%= config.copyCode === true %>">
44
+ <body class="<%= sidebarConfig?.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>"
45
+ data-default-collapsed="<%= sidebarConfig?.defaultCollapsed %>"
46
+ data-copy-code-enabled="<%= config.copyCode === true %>"
47
+ data-spa-enabled="<%= config.layout?.spa !== false %>">
48
+
63
49
  <a href="#main-content" class="skip-link">Skip to main content</a>
50
+
64
51
  <aside class="sidebar">
65
52
  <div class="sidebar-header">
66
53
  <% if (logo && logo.light && logo.dark) { %>
@@ -72,61 +59,52 @@
72
59
  <h1><a href="<%= relativePathToRoot %>index.html"><%= siteTitle %></a></h1>
73
60
  <% } %>
74
61
  <span class="mobile-view sidebar-menu-button float-right">
75
- <%- renderIcon("ellipsis-vertical") %>
62
+ <%- renderIcon("menu") %>
76
63
  </span>
77
64
  </div>
78
65
 
66
+ <% if (optionsMenu?.position === 'sidebar-top') { %>
67
+ <div class="sidebar-options-wrapper">
68
+ <%- include('partials/options-menu', { optionsMenu }) %>
69
+ </div>
70
+ <% } %>
71
+
79
72
  <%- navigationHtml %>
80
73
 
81
- <% if (theme && theme.enableModeToggle && theme.positionMode !== 'top') { %>
82
- <button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button">
83
- <%- renderIcon('sun', { class: 'icon-sun' }) %> <%- renderIcon('moon', { class: 'icon-moon' }) %>
84
- </button>
74
+ <% if (optionsMenu?.position === 'sidebar-bottom') { %>
75
+ <div class="sidebar-options-wrapper mt-auto">
76
+ <%- include('partials/options-menu', { optionsMenu }) %>
77
+ </div>
85
78
  <% } %>
86
79
  </aside>
87
80
 
88
81
  <div class="main-content-wrapper">
89
- <div class="page-header">
82
+ <% if (headerConfig?.enabled !== false) { %>
83
+ <header class="page-header">
90
84
  <div class="header-left">
91
- <% if (sidebarConfig.collapsible) { %>
85
+ <% if (sidebarConfig?.collapsible) { %>
92
86
  <button id="sidebar-toggle-button" class="sidebar-toggle-button" aria-label="Toggle Sidebar">
93
87
  <%- renderIcon('panel-left-close') %>
94
88
  </button>
95
89
  <% } %>
96
- <h1><%= pageTitle %></h1>
90
+ <span class="header-title"><%= pageTitle %></span>
97
91
  </div>
98
- <% if (theme && theme.enableModeToggle && theme.positionMode === 'top') { %>
92
+
99
93
  <div class="header-right">
100
-
101
- <!-- SEARCH BUTTON -->
102
- <% if (config.search !== false) { %>
103
- <button class="docmd-search-trigger" aria-label="Search">
104
- <%- renderIcon('search') %>
105
- <span class="search-label">Search</span>
106
- <span class="search-keys"><kbd class="docmd-kbd">⌘</kbd><kbd class="docmd-kbd">k</kbd></span>
107
- </button>
108
- <% } %>
109
-
110
- <!-- THEME TOGGLE -->
111
- <button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button theme-toggle-header">
112
- <%- renderIcon('sun', { class: 'icon-sun' }) %> <%- renderIcon('moon', { class: 'icon-moon' }) %>
113
- </button>
114
-
115
- <!-- SPONSOR BUTTON -->
116
- <% if (sponsor && sponsor.enabled) { %>
117
- <a href="<%= sponsor.link %>" target="_blank" rel="noopener noreferrer" aria-label="Sponsor Project">
118
- <button class="sponsor-link-button" aria-label="Sponsor">
119
- <%- renderIcon('heart', { class: 'sponsor-icon' }) %>
120
- </button>
121
- </a>
94
+ <% if (optionsMenu?.position === 'header') { %>
95
+ <%- include('partials/options-menu', { optionsMenu }) %>
122
96
  <% } %>
123
97
  </div>
124
- <% } %>
125
- </div>
98
+ </header>
99
+ <% } %>
126
100
 
127
101
  <main class="content-area" id="main-content">
128
102
  <div class="content-layout">
129
103
  <div class="main-content">
104
+ <% if (headerConfig?.enabled === false) { %>
105
+ <h1><%= pageTitle %></h1>
106
+ <% } %>
107
+
130
108
  <%- content %>
131
109
 
132
110
  <% if (config.pageNavigation && (prevPage || nextPage)) { %>
@@ -161,43 +139,11 @@
161
139
  </div>
162
140
  </main>
163
141
 
164
- <footer class="page-footer">
165
- <div class="footer-content">
166
- <div class="user-footer"><%- footerHtml || '' %></div>
167
- <div class="branding-footer">
168
- 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.io" target="_blank" rel="noopener">docmd.</a>
169
- </div>
170
- </div>
171
- </footer>
172
- </div>
173
-
174
- <% if (config.search !== false) { %>
175
- <!-- Search Modal -->
176
- <div id="docmd-search-modal" class="docmd-search-modal" style="display: none;">
177
- <div class="docmd-search-box">
178
- <div class="docmd-search-header">
179
- <%- renderIcon('search') %>
180
- <input type="text" id="docmd-search-input" placeholder="Search documentation..." autocomplete="off" spellcheck="false">
181
- <button onclick="window.closeDocmdSearch()" class="docmd-search-close" aria-label="Close search">
182
- <%- renderIcon('x') %>
183
- </button>
184
- </div>
185
- <div id="docmd-search-results" class="docmd-search-results"></div>
186
- <div class="docmd-search-footer">
187
- <span><kbd class="docmd-kbd">↑</kbd> <kbd class="docmd-kbd">↓</kbd> to navigate</span>
188
- <span><kbd class="docmd-kbd">ESC</kbd> to close</span>
189
- </div>
190
- </div>
142
+ <%- include('partials/footer', { footerConfig, config, relativePathToRoot, logo, siteTitle, footerHtml }) %>
191
143
  </div>
192
- <% } %>
193
144
 
194
- <!-- CORE JS -->
195
145
  <script src="<%= relativePathToRoot %>assets/js/docmd-main.js?v=<%= buildHash %>"></script>
196
-
197
- <!-- PLUGINS -->
198
146
  <%- pluginBodyScriptsHtml || '' %>
199
-
200
- <!-- CUSTOM JS -->
201
147
  <% (customJsFiles || []).forEach(jsFile => {
202
148
  if (jsFile && jsFile.trim() !== '') { %>
203
149
  <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>?v=<%= buildHash %>"></script>
@@ -18,7 +18,7 @@
18
18
  // 2. Remove extensions and index
19
19
  path = path.replace(/(\/index\.html|index\.html|\.html|\.md)$/, '');
20
20
 
21
- // 3. CRITICAL: Remove trailing slash for consistency
21
+ // 3. Remove trailing slash
22
22
  path = path.replace(/\/$/, '');
23
23
 
24
24
  // 4. Return with leading slash for comparison
@@ -30,7 +30,6 @@
30
30
  const currentNorm = normalizePath(currentPath);
31
31
  return item.children.some(child => {
32
32
  const childNorm = normalizePath(child.path);
33
- // Exact match or if the child itself has active children (recursion)
34
33
  if (childNorm === currentNorm) return true;
35
34
  return isChildActive(child, currentPath);
36
35
  });
@@ -41,27 +40,38 @@
41
40
 
42
41
  items.forEach(item => {
43
42
  const isExternal = item.external || false;
44
- // An item is collapsible if it has children, unless explicitly set to false
45
- const isCollapsible = item.children && item.children.length > 0 && item.collapsible !== false;
43
+ const hasChildren = item.children && item.children.length > 0;
44
+
45
+ // Determine interactions
46
+ // It is collapsible ONLY if it has children AND config doesn't say 'false'
47
+ const isInteractive = hasChildren && item.collapsible !== false;
46
48
 
47
49
  const itemNorm = normalizePath(item.path);
48
50
  const currentNorm = normalizePath(currentPagePath);
49
51
 
50
- const isActive = itemNorm === currentNorm && itemNorm !== '#';
52
+ const isDummyLink = !item.path || item.path === '#' || item.path === 'javascript:;';
53
+ const isActive = itemNorm === currentNorm;
51
54
  const isParentOfActive = !isActive && isChildActive(item, currentPagePath);
52
55
 
53
- // Auto-expand if it's a parent of the active page
54
- const isOpen = isParentOfActive || (isActive && isCollapsible);
56
+ // Expand Logic:
57
+ // 1. If Interactive: Expand if parent of active, or if it was previously toggled open.
58
+ // 2. If NOT Interactive (collapsible: false): ALWAYS expand.
59
+ const isOpen = isInteractive
60
+ ? (isParentOfActive || (isActive && isInteractive))
61
+ : true;
55
62
 
56
63
  const liClasses = [];
57
64
  if (isActive) liClasses.push('active');
58
65
  if (isParentOfActive) liClasses.push('active-parent');
59
- if (isCollapsible) liClasses.push('collapsible');
60
- if (isOpen) liClasses.push('expanded');
61
66
 
62
- // Construct HREF (Same as before)
67
+ // Only add 'collapsible' class if we want the arrow and click behavior
68
+ if (isInteractive) liClasses.push('collapsible');
69
+
70
+ // Add 'expanded' to show children (via CSS)
71
+ if (hasChildren && isOpen) liClasses.push('expanded');
72
+
63
73
  let href = item.path || '#';
64
- if (!isExternal && href !== '#' && !href.startsWith('http')) {
74
+ if (!isExternal && !isDummyLink && !href.startsWith('http')) {
65
75
  let cleanPath = href.replace(/^(\.\/|\/)+/, '');
66
76
  href = relativePathToRoot + cleanPath;
67
77
  if (isOfflineMode) {
@@ -72,21 +82,27 @@
72
82
  }
73
83
  }
74
84
  %>
75
- <li class="<%= liClasses.join(' ') %>" <% if(isCollapsible) { %> aria-expanded="<%= isOpen %>" <% } %>>
76
- <a href="<%= href %>" class="<%= isActive ? 'active' : '' %>" <%= isExternal ? 'target="_blank" rel="noopener"' : '' %>>
77
- <% if (item.icon) { %> <%- renderIcon(item.icon) %> <% } %>
78
- <span class="nav-item-title"><%= item.title %></span>
79
-
80
- <% if (isCollapsible) { %>
81
- <span class="collapse-icon-wrapper">
82
- <%- renderIcon('chevron-right', { class: 'collapse-icon' }) %>
83
- </span>
84
- <% } %>
85
-
86
- <% if (isExternal) { %> <%- renderIcon('external-link', { class: 'nav-external-icon' }) %> <% } %>
87
- </a>
85
+ <li class="<%= liClasses.join(' ') %>" <% if(isInteractive) { %> aria-expanded="<%= isOpen %>" <% } %>>
86
+ <% if (isDummyLink) { %>
87
+ <span class="nav-label" style="cursor: <%= isInteractive ? 'pointer' : 'default' %>;">
88
+ <% if (item.icon) { %> <%- renderIcon(item.icon) %> <% } %>
89
+ <span class="nav-item-title"><%= item.title %></span>
90
+ <% if (isInteractive) { %>
91
+ <span class="collapse-icon-wrapper"><%- renderIcon('chevron-right', { class: 'collapse-icon' }) %></span>
92
+ <% } %>
93
+ </span>
94
+ <% } else { %>
95
+ <a href="<%= href %>" class="<%= isActive ? 'active' : '' %>" <%= isExternal ? 'target="_blank" rel="noopener"' : '' %>>
96
+ <% if (item.icon) { %> <%- renderIcon(item.icon) %> <% } %>
97
+ <span class="nav-item-title"><%= item.title %></span>
98
+ <% if (isInteractive) { %>
99
+ <span class="collapse-icon-wrapper"><%- renderIcon('chevron-right', { class: 'collapse-icon' }) %></span>
100
+ <% } %>
101
+ <% if (isExternal) { %> <%- renderIcon('external-link', { class: 'nav-external-icon' }) %> <% } %>
102
+ </a>
103
+ <% } %>
88
104
 
89
- <% if (item.children) { %>
105
+ <% if (hasChildren) { %>
90
106
  <ul class="submenu">
91
107
  <% renderNav(item.children); %>
92
108
  </ul>
@@ -0,0 +1,57 @@
1
+ <%# ---------------------------------------------------------------
2
+ # docmd : the minimalist, zero-config documentation generator.
3
+ # @website https://docmd.io
4
+ # [docmd-source] - Please do not remove this header.
5
+ # ---------------------------------------------------------------
6
+ %>
7
+
8
+ <% if (footerConfig?.style === 'complete') { %>
9
+ <footer class="footer-complete">
10
+ <div class="footer-complete-top">
11
+ <div class="footer-brand">
12
+ <% if (logo && logo.light && logo.dark) { %>
13
+ <a href="<%= logo.href || relativePathToRoot %>" class="logo-link">
14
+ <img src="<%= relativePathToRoot %><%- logo.light.startsWith('/') ? logo.light.substring(1) : logo.light %>" alt="<%= logo.alt || siteTitle %>" class="logo-light">
15
+ <img src="<%= relativePathToRoot %><%- logo.dark.startsWith('/') ? logo.dark.substring(1) : logo.dark %>" alt="<%= logo.alt || siteTitle %>" class="logo-dark">
16
+ </a>
17
+ <% } else { %>
18
+ <span class="footer-title"><%= siteTitle %></span>
19
+ <% } %>
20
+ <% if (footerConfig.description) { %>
21
+ <p class="footer-desc"><%= footerConfig.description %></p>
22
+ <% } %>
23
+ </div>
24
+
25
+ <% if (footerConfig.columns && footerConfig.columns.length > 0) { %>
26
+ <div class="footer-columns">
27
+ <% footerConfig.columns.forEach(col => { %>
28
+ <div class="footer-column">
29
+ <span class="footer-column-title"><%= col.title %></span>
30
+ <ul>
31
+ <% col.links.forEach(link => { %>
32
+ <li><a href="<%= link.url %>" <%= link.external ? 'target="_blank" rel="noopener"' : '' %>><%= link.text %></a></li>
33
+ <% }) %>
34
+ </ul>
35
+ </div>
36
+ <% }) %>
37
+ </div>
38
+ <% } %>
39
+ </div>
40
+
41
+ <div class="footer-complete-bottom">
42
+ <div class="user-footer"><%- footerHtml || (footerConfig.copyright ? footerConfig.copyright : '') %></div>
43
+ <div class="branding-footer">
44
+ Built 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.io" target="_blank" rel="noopener">docmd.</a>
45
+ </div>
46
+ </div>
47
+ </footer>
48
+ <% } else { %>
49
+ <footer class="page-footer">
50
+ <div class="footer-content">
51
+ <div class="user-footer"><%- footerHtml || (footerConfig?.copyright ? footerConfig.copyright : '') %></div>
52
+ <div class="branding-footer">
53
+ Built 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.io" target="_blank" rel="noopener">docmd.</a>
54
+ </div>
55
+ </div>
56
+ </footer>
57
+ <% } %>
@@ -0,0 +1,30 @@
1
+ <%# ---------------------------------------------------------------
2
+ # docmd : the minimalist, zero-config documentation generator.
3
+ # @website https://docmd.io
4
+ # [docmd-source] - Please do not remove this header.
5
+ # ---------------------------------------------------------------
6
+ %>
7
+
8
+ <% if (optionsMenu && optionsMenu.components) { %>
9
+ <div class="docmd-options-menu">
10
+ <% if (optionsMenu.components.search !== false) { %>
11
+ <button class="docmd-search-trigger" aria-label="Search">
12
+ <%- renderIcon('search') %>
13
+ <span class="search-label">Search</span>
14
+ <span class="search-keys"><kbd class="docmd-kbd">⌘</kbd><kbd class="docmd-kbd">K</kbd></span>
15
+ </button>
16
+ <% } %>
17
+
18
+ <% if (optionsMenu.components.themeSwitch !== false) { %>
19
+ <button class="theme-toggle-button" aria-label="Toggle theme">
20
+ <%- renderIcon('sun', { class: 'icon-sun' }) %> <%- renderIcon('moon', { class: 'icon-moon' }) %>
21
+ </button>
22
+ <% } %>
23
+
24
+ <% if (optionsMenu.components.sponsor) { %>
25
+ <a href="<%= optionsMenu.components.sponsor %>" target="_blank" rel="noopener noreferrer" class="sponsor-link-button" aria-label="Sponsor">
26
+ <%- renderIcon('heart', { class: 'sponsor-icon' }) %>
27
+ </a>
28
+ <% } %>
29
+ </div>
30
+ <% } %>