99 lines
3.9 KiB
Plaintext
99 lines
3.9 KiB
Plaintext
<%# ---------------------------------------------------------------
|
|
# docmd : the zero-config documentation engine.
|
|
# @website https://docmd.io
|
|
# [docmd-source] - Please do not remove this header.
|
|
# ---------------------------------------------------------------
|
|
%>
|
|
|
|
<nav class="sidebar-nav" aria-label="<%= typeof t === 'function' ? t('mainNavigation') : 'Main Navigation' %>">
|
|
<ul>
|
|
<%
|
|
// normalizePath: used ONLY for active-state comparison (not for href generation).
|
|
// All href generation goes through buildRelativeUrl() which delegates to the
|
|
// centralized URL engine in @docmd/parser.
|
|
function normalizePath(p) {
|
|
if (!p || p === '#') return '#';
|
|
if (p.startsWith('http')) return p;
|
|
let path = p.replace(/^(\.\/|\/)+/, '');
|
|
path = path.replace(/(\/index\.html|index\.html|\/README\.md|README\.md|\.html|\.md)$/i, '');
|
|
path = path.replace(/\/$/, '');
|
|
return '/' + path;
|
|
}
|
|
function isChildActive(item, currentPath) { if
|
|
(!item.children) return false; const currentNorm=normalizePath(currentPath); return item.children.some(child=> {
|
|
const childNorm = normalizePath(child.path);
|
|
if (childNorm === currentNorm) return true;
|
|
return isChildActive(child, currentPath);
|
|
});
|
|
}
|
|
|
|
function renderNav(items) {
|
|
if (!items || !Array.isArray(items)) return;
|
|
|
|
items.forEach(item => {
|
|
const isExternal = item.external || false;
|
|
const hasChildren = item.children && item.children.length > 0;
|
|
const isInteractive = hasChildren && item.collapsible !== false;
|
|
|
|
const itemNorm = normalizePath(item.path);
|
|
const currentNorm = normalizePath(currentPagePath);
|
|
|
|
const isDummyLink = !item.path || item.path === '#' || item.path === 'javascript:;';
|
|
const isActive = itemNorm === currentNorm;
|
|
const isParentOfActive = !isActive && isChildActive(item, currentPagePath);
|
|
|
|
const isOpen = isInteractive
|
|
? (isParentOfActive || (isActive && isInteractive))
|
|
: true;
|
|
|
|
const liClasses = [];
|
|
if (isActive) liClasses.push('active');
|
|
if (isParentOfActive) liClasses.push('active-parent');
|
|
if (isInteractive) liClasses.push('collapsible');
|
|
if (hasChildren && isOpen) liClasses.push('expanded');
|
|
if (hasChildren) liClasses.push('nav-group');
|
|
|
|
let href = item.path || '#';
|
|
if (!isExternal && !isDummyLink) {
|
|
href = buildRelativeUrl(href);
|
|
}
|
|
%>
|
|
<li class="<%= liClasses.join(' ') %>" <% if(isInteractive) { %> aria-expanded="<%= isOpen %>" <% } %>>
|
|
<% if (isDummyLink) { %>
|
|
<span class="nav-label <%= hasChildren ? 'nav-group' : '' %>">
|
|
<% if (item.icon) { %> <%- renderIcon(item.icon) %>
|
|
<% } %>
|
|
<span class="nav-item-title">
|
|
<%= item.title %>
|
|
</span>
|
|
<% if (isInteractive) { %>
|
|
<span class="collapse-icon-wrapper"><%- renderIcon('chevron-right', { class: 'collapse-icon'
|
|
}) %></span>
|
|
<% } %>
|
|
</span>
|
|
<% } else { %>
|
|
<a href="<%= href %>" class="<%= isActive ? 'active' : '' %> <%= hasChildren ? 'nav-group' : '' %>"
|
|
<%=isExternal ? 'target="_blank" rel="noopener"' : '' %>>
|
|
<% if (item.icon) { %> <%- renderIcon(item.icon) %>
|
|
<% } %>
|
|
<span class="nav-item-title">
|
|
<%= item.title %>
|
|
</span>
|
|
<% if (isInteractive) { %>
|
|
<span class="collapse-icon-wrapper"><%- renderIcon('chevron-right', { class: 'collapse-icon'
|
|
}) %></span>
|
|
<% } %>
|
|
<% if (isExternal) { %> <%- renderIcon('external-link', { class: 'nav-external-icon' }) %>
|
|
<% } %>
|
|
</a>
|
|
<% } %>
|
|
|
|
<% if (hasChildren) { %>
|
|
<ul class="submenu">
|
|
<% renderNav(item.children); %>
|
|
</ul>
|
|
<% } %>
|
|
</li>
|
|
<% }); } renderNav(navItems); %>
|
|
</ul>
|
|
</nav> |