aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/frontend/html/nav.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenserver/frontend/html/nav.js')
-rw-r--r--src/zenserver/frontend/html/nav.js52
1 files changed, 49 insertions, 3 deletions
diff --git a/src/zenserver/frontend/html/nav.js b/src/zenserver/frontend/html/nav.js
index a5de203f2..759942186 100644
--- a/src/zenserver/frontend/html/nav.js
+++ b/src/zenserver/frontend/html/nav.js
@@ -11,6 +11,9 @@
*
* Each child <a> becomes a nav link. The current page is
* highlighted automatically based on the href.
+ *
+ * Links may be added or removed dynamically — the component
+ * re-renders automatically via MutationObserver.
*/
class ZenNav extends HTMLElement {
@@ -18,17 +21,56 @@ class ZenNav extends HTMLElement {
connectedCallback() {
if (!this.shadowRoot) this.attachShadow({ mode: 'open' });
this._render();
+
+ this._observer = new MutationObserver(() => this._render());
+ this._observer.observe(this, { childList: true });
+ }
+
+ disconnectedCallback() {
+ if (this._observer) {
+ this._observer.disconnect();
+ this._observer = null;
+ }
}
_render() {
const currentPath = window.location.pathname;
+ const currentSearch = window.location.search;
const items = Array.from(this.querySelectorAll(':scope > a'));
+ const currentParams = new URLSearchParams(currentSearch);
+
+ let spacerInserted = false;
const links = items.map(a => {
const href = a.getAttribute('href') || '';
const label = a.textContent.trim();
- const active = currentPath.endsWith(href);
- return `<a class="nav-link${active ? ' active' : ''}" href="${href}">${label}</a>`;
+ const alignRight = a.hasAttribute('data-align') && a.getAttribute('data-align') === 'right';
+ let active = false;
+
+ try {
+ const linkUrl = new URL(href, window.location.origin);
+ if (linkUrl.pathname === currentPath || currentPath.endsWith(href)) {
+ // All of the link's query params must be present in the current URL
+ const linkParams = linkUrl.searchParams;
+ active = Array.from(linkParams.entries()).every(
+ ([k, v]) => currentParams.get(k) === v
+ );
+ // A bare path with no params only matches when the current URL also has no page param
+ if (linkParams.toString() === '' && currentParams.has('page')) {
+ active = false;
+ }
+ }
+ } catch (e) {
+ active = currentPath.endsWith(href);
+ }
+
+ let prefix = '';
+ if (alignRight && !spacerInserted) {
+ prefix = '<span class="nav-spacer"></span>';
+ spacerInserted = true;
+ }
+
+ return `${prefix}<a class="nav-link${active ? ' active' : ''}" href="${href}">${label}</a>`;
}).join('');
this.shadowRoot.innerHTML = `
@@ -70,8 +112,12 @@ class ZenNav extends HTMLElement {
color: var(--theme_bright);
background: var(--theme_g2);
}
+
+ .nav-spacer {
+ flex: 1;
+ }
</style>
- <nav class="nav-bar">${links}</nav>
+ <nav class="nav-bar" part="nav-bar">${links}</nav>
`;
}
}