reorganize header menu

This commit is contained in:
2026-09-01 19:44:08 +02:00
parent 5ad715426d
commit 96a58ca472
10 changed files with 73 additions and 97 deletions
+11
View File
@@ -56,3 +56,14 @@ document.addEventListener('keydown', function (e) {
menu.classList.remove('is-open'); menu.classList.remove('is-open');
}); });
}); });
document.addEventListener('DOMContentLoaded', function () {
var trigger = document.querySelector('[data-action="actions-menu"]');
if (!trigger) return;
wireDropdown(trigger);
var menu = trigger.parentElement.querySelector('.dropdown-menu');
if (!menu) return;
trigger.addEventListener('click', function () {
trigger.setAttribute('aria-expanded', menu.classList.contains('is-open') ? true : false);
});
});
+11 -1
View File
@@ -29,7 +29,17 @@
<input class="input search-input" type="search" name="q" value="{{block "searchQuery" .}}{{end}}" placeholder="Search…" title="Search (F)" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> <input class="input search-input" type="search" name="q" value="{{block "searchQuery" .}}{{end}}" placeholder="Search…" title="Search (F)" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</form> </form>
{{end}} {{end}}
<div class="header-actions row">{{block "headerActions" .}}{{end}}</div> <div class="header-actions row">
{{if .EditMode}}{{block "headerActions" .}}{{end}}{{else}}
<span class="dropdown actions">
<button type="button" class="btn actions-toggle" data-action="actions-menu" aria-haspopup="true" aria-expanded="false" title="Actions">ACTIONS</button>
<div class="dropdown-menu align-right actions-menu">
<div class="actions-special"></div>
{{template "headerActions" . }}
</div>
</span>
{{end}}
</div>
</header> </header>
<div class="shell"> <div class="shell">
{{if not .EditMode}}<aside class="tree-sidebar col"> {{if not .EditMode}}<aside class="tree-sidebar col">
+15
View File
@@ -0,0 +1,15 @@
document.addEventListener("DOMContentLoaded", function () {
var aside = document.querySelector("aside.sidebar");
var specials = document.querySelector(".actions-special");
if (!aside || !aside.children.length || !specials) return;
var item = document.createElement("button");
item.type = "button";
item.className = "btn btn-block";
item.title = "Contents";
item.textContent = "CONTENTS";
item.addEventListener("click", function () {
if (typeof openOverlay === "function") openOverlay(aside);
});
specials.appendChild(item);
});
-16
View File
@@ -1,16 +0,0 @@
// Wires the header-right [ACTIONS] dropdown. The buttons inside keep calling
// the existing page/actions.js handlers (newPage / movePage / deletePage) and
// the ?edit link, so the N/E/M keyboard shortcuts continue to work whether or
// not the menu is open. Toggling reuses the generic wireDropdown mechanism
// (global-shortcuts.js), which also handles outside-click / Escape close.
(function () {
var trigger = document.querySelector('[data-action="page-actions"]');
if (!trigger || typeof wireDropdown !== 'function') return;
wireDropdown(trigger);
var menu = trigger.parentElement.querySelector('.dropdown-menu');
if (!menu) return;
// Keep aria-expanded in sync with the menu's open state.
trigger.addEventListener('click', function () {
trigger.setAttribute('aria-expanded', menu.classList.contains('is-open') ? 'true' : 'false');
});
})();
+8 -16
View File
@@ -1,21 +1,13 @@
{{define "headScripts"}}<script src="/_/page/actions.js"></script>{{end}} {{define "headScripts"}}<script src="/_/page/actions.js"></script>{{end}}
{{define "headerActions"}}{{if .CanEdit}} {{define "headerActions"}}{{if .CanEdit}}
<span class="dropdown"> <button class="btn btn-block" onclick="newPage()" title="New page (N)">NEW</button>
<button type="button" class="btn" data-action="page-actions" aria-haspopup="true" aria-expanded="false" title="Page actions">ACTIONS</button> <a class="btn btn-block" href="?edit" title="Edit page (E)">EDIT</a>
<div class="dropdown-menu align-right"> <button class="btn btn-block" data-companion-reveal hidden title="Reveal in file manager">REVEAL</button>
<button class="btn btn-block" onclick="newPage()" title="New page (N)">NEW PAGE</button> {{if not .IsRoot}}
<a class="btn btn-block" href="?edit" title="Edit page (E)">EDIT PAGE</a> <button class="btn btn-block" onclick="movePage()" title="Move page (M)">MOVE</button>
<button class="btn btn-block" data-companion-reveal hidden title="Reveal in file manager">REVEAL ON CLIENT</button> <button class="btn btn-block danger" onclick="deletePage()" title="Delete page">DELETE</button>
{{if not .IsRoot}} {{end}}
<button class="btn btn-block" onclick="movePage()" title="Move page (M)">MOVE PAGE</button>
{{end}}
{{if not .IsRoot}}
<button class="btn btn-block danger" onclick="deletePage()" title="Delete page">DELETE PAGE</button>
{{end}}
</div>
</span>
<script src="/_/page/header-actions.js"></script>
{{end}}{{end}} {{end}}{{end}}
{{define "content"}} {{define "content"}}
@@ -63,7 +55,7 @@
{{if .Content}} {{if .Content}}
<script src="/_/page/sections.js"></script> <script src="/_/page/sections.js"></script>
{{end}} {{end}}
<script src="/_/page/sidebar-fab.js"></script> <script src="/_/page/contents.js"></script>
{{end}} {{end}}
{{define "aside"}}{{if .SidebarWidget}}<aside class="sidebar">{{.SidebarWidget}}</aside>{{end}}{{end}} {{define "aside"}}{{if .SidebarWidget}}<aside class="sidebar">{{.SidebarWidget}}</aside>{{end}}{{end}}
-15
View File
@@ -1,15 +0,0 @@
document.addEventListener("DOMContentLoaded", function () {
var aside = document.querySelector("aside.sidebar");
if (!aside || !aside.children.length) return;
var fab = document.createElement("button");
fab.type = "button";
fab.className = "btn btn-fab fab fab-rail";
fab.title = "Contents";
fab.setAttribute("aria-label", "Contents");
fab.textContent = "≡";
fab.addEventListener("click", function () {
if (typeof openOverlay === "function") openOverlay(aside);
});
document.body.appendChild(fab);
});
-1
View File
@@ -53,7 +53,6 @@ function createSearchPage(name) {
} }
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
wireDropdown(document.querySelector('[data-action="actions-drop"]'));
var createLink = document.querySelector('[data-create-page]'); var createLink = document.querySelector('[data-create-page]');
if (createLink) { if (createLink) {
+4 -9
View File
@@ -42,15 +42,10 @@
{{end}} {{end}}
{{end}} {{end}}
{{define "headerActions"}}
<button class="btn btn-block" onclick="rebuildIndex()" title="Rebuild search index">REBUILD INDEX</button>
{{end}}
{{define "footerExtras"}} {{define "footerExtras"}}
{{if not .IndexBuiltAt.IsZero}}<span class="muted">· Index: {{.IndexBuiltAt.Format "2006-01-02 15:04"}}</span>{{end}} {{if not .IndexBuiltAt.IsZero}}<span class="muted">· Index: {{.IndexBuiltAt.Format "2006-01-02 15:04"}}</span>{{end}}
{{end}} {{end}}
{{define "extras"}}
<div class="fab dropdown">
<button class="btn btn-fab" data-action="actions-drop" title="Actions" aria-label="Actions">≡</button>
<div class="dropdown-menu align-right open-up">
<button class="btn btn-block" onclick="rebuildIndex()" title="Rebuild search index">REBUILD INDEX</button>
</div>
</div>
{{end}}
+10 -14
View File
@@ -230,6 +230,7 @@ footer {
} }
.btn-fab::before, .btn-fab::after { content: none; } .btn-fab::before, .btn-fab::after { content: none; }
.btn-fab:hover { background: var(--bg-panel-hover); color: var(--primary-hover); } .btn-fab:hover { background: var(--bg-panel-hover); color: var(--primary-hover); }
.btn[hidden] {display: none;}
.danger { color: var(--danger); } .danger { color: var(--danger); }
.danger:hover { color: var(--danger-hover); } .danger:hover { color: var(--danger-hover); }
/* Selected segmented-toggle button (view-settings modal). */ /* Selected segmented-toggle button (view-settings modal). */
@@ -403,6 +404,8 @@ main > h2 {
.dropdown-menu.is-open { display: block; } .dropdown-menu.is-open { display: block; }
.dropdown-menu.scrollable { max-height: 23rem; overflow-y: auto; } .dropdown-menu.scrollable { max-height: 23rem; overflow-y: auto; }
.actions-special > .btn {text-align: left;}
/* === Suggestion dropdown (header search + editor link picker) === /* === Suggestion dropdown (header search + editor link picker) ===
Anchored to a position:relative host. Mirrors .dropdown-menu visuals with Anchored to a position:relative host. Mirrors .dropdown-menu visuals with
a dashed border and bg-panel-hover for the active row. */ a dashed border and bg-panel-hover for the active row. */
@@ -501,15 +504,6 @@ body.edit-mode footer { max-width: none; }
border-bottom: var(--border-dashed); border-bottom: var(--border-dashed);
} }
/* === Floating action button ===
Standalone FAB buttons (the tree rail, the right rail) are mobile-only and
stack bottom-right: the tree FAB anchors the bottom, the right-rail FAB sits
above it. Wrapped FABs (the search actions dropdown) stay visible on desktop
and, on mobile, sit in the upper slot so they never overlap the tree FAB. */
.fab { position: fixed; bottom: var(--space-4); right: var(--space-4); z-index: 50; }
button.fab { display: none; }
.fab-rail { bottom: calc(var(--space-4) + 3rem + var(--space-2)); }
/* === Companion status === */ /* === Companion status === */
.companion-status { margin-left: auto; } .companion-status { margin-left: auto; }
.companion-icon { font-size: 0.9rem; line-height: 1; padding: 0.1rem 0.3rem; } .companion-icon { font-size: 0.9rem; line-height: 1; padding: 0.1rem 0.3rem; }
@@ -860,11 +854,13 @@ aside.sidebar:empty { display: none; }
actions right. */ actions right. */
header { grid-template-columns: auto 1fr auto; } header { grid-template-columns: auto 1fr auto; }
.search-form { width: 100%; max-width: none; justify-self: stretch; } .search-form { width: 100%; max-width: none; justify-self: stretch; }
/* Reveal the mobile FAB stack (tree at the bottom, right rail above it). }
The search actions dropdown FAB moves to the upper slot too so it never
overlaps the tree FAB. */ /* Show the actions menu items directly in the bar in desktop size */
button.fab { display: inline-flex; } @media (min-width: 1101px) {
.fab.dropdown { bottom: calc(var(--space-4) + 3rem + var(--space-2)); } .actions-toggle, .actions-special {display: none;}
.actions, .actions-menu, .actions.menu.is-open {display: contents;}
.actions-menu .btn-block { width: auto; padding: 0;}
} }
@media (max-width: 600px) { @media (max-width: 600px) {
+14 -25
View File
@@ -1,11 +1,3 @@
// Persistent left navigation rail. Renders the wiki folder/file tree, expanded
// to the current page's ancestor chain, and lets the user navigate by clicking
// folders (links) or open files locally via the companion (a.tree-file, wired
// in companion.js). Shares the .tree-* CSS and the ?tree endpoint with
// tree-picker.js but does not reuse its row builder — that one is modal-select
// behavior, this one is navigation behavior. Hidden in edit mode (the server
// omits the container).
(function () { (function () {
// This script owns the .tree-scroll child of the aside, not the aside itself. // This script owns the .tree-scroll child of the aside, not the aside itself.
var container = document.querySelector('aside.tree-sidebar .tree-scroll'); var container = document.querySelector('aside.tree-sidebar .tree-scroll');
@@ -188,24 +180,21 @@
}); });
} }
// Mobile: the tree FAB sits at the bottom of the stacked FAB group and function setupMenuItem() {
// opens the tree's scroll container in the full-viewport Overlay var specials = document.querySelector('.actions-special');
// (overlay.js). The container always exists when not editing, so — per the if (!specials) return;
// layout spec — the FAB always renders; the overlay auto-closes when a
// folder/file link inside it navigates. Hidden on desktop by .fab CSS. var item = document.createElement("button");
function setupFab() { item.type = "button";
var fab = document.createElement('button'); item.className = "btn btn-block";
fab.type = 'button'; item.title = "Folders";
fab.className = 'btn btn-fab fab fab-tree'; item.textContent = "FOLDERS";
fab.title = 'Folder tree'; item.addEventListener("click", function () {
fab.setAttribute('aria-label', 'Folder tree'); if (typeof openOverlay === "function") openOverlay(aside);
fab.innerHTML = '<svg viewBox="0 0 16 16" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="miter"><path d="M1 6h14v8H1zm0 0V4h5l1 2"/></svg>'; });
fab.addEventListener('click', function () { specials.appendChild(item);
if (typeof openOverlay === 'function') openOverlay(container);
});
document.body.appendChild(fab);
} }
render(); render();
setupFab(); setupMenuItem();
})(); })();