Compare commits
3
Commits
5ad715426d
...
c0b7ebf0c2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0b7ebf0c2 | ||
|
|
07bb3b002e | ||
|
|
96a58ca472 |
@@ -56,3 +56,14 @@ document.addEventListener('keydown', function (e) {
|
||||
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
@@ -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" />
|
||||
</form>
|
||||
{{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>
|
||||
<div class="shell">
|
||||
{{if not .EditMode}}<aside class="tree-sidebar col">
|
||||
|
||||
+1
-6
@@ -26,7 +26,7 @@
|
||||
|
||||
closeBtn = document.createElement('button');
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.className = 'btn btn-fab overlay-close';
|
||||
closeBtn.className = 'btn overlay-close';
|
||||
closeBtn.title = 'Close';
|
||||
closeBtn.setAttribute('aria-label', 'Close');
|
||||
closeBtn.textContent = '×';
|
||||
@@ -35,11 +35,6 @@
|
||||
|
||||
bodyEl = document.createElement('div');
|
||||
bodyEl.className = 'overlay-body';
|
||||
// Any navigation or anchor activation inside a rail dismisses the
|
||||
// overlay: tree/calendar links navigate away, TOC links scroll the
|
||||
// page underneath — either way the user should see the result, and
|
||||
// closing synchronously (before navigation) avoids a bfcache snapshot
|
||||
// that would restore the overlay open on back-nav.
|
||||
bodyEl.addEventListener('click', function (e) {
|
||||
if (e.target.closest('a')) close();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
})();
|
||||
+6
-14
@@ -1,21 +1,13 @@
|
||||
{{define "headScripts"}}<script src="/_/page/actions.js"></script>{{end}}
|
||||
|
||||
{{define "headerActions"}}{{if .CanEdit}}
|
||||
<span class="dropdown">
|
||||
<button type="button" class="btn" data-action="page-actions" aria-haspopup="true" aria-expanded="false" title="Page actions">ACTIONS</button>
|
||||
<div class="dropdown-menu align-right">
|
||||
<button class="btn btn-block" onclick="newPage()" title="New page (N)">NEW PAGE</button>
|
||||
<a class="btn btn-block" href="?edit" title="Edit page (E)">EDIT PAGE</a>
|
||||
<button class="btn btn-block" data-companion-reveal hidden title="Reveal in file manager">REVEAL ON CLIENT</button>
|
||||
<button class="btn btn-block" onclick="newPage()" title="New page (N)">NEW</button>
|
||||
<button class="btn btn-block" data-companion-reveal hidden title="Reveal in file manager">REVEAL</button>
|
||||
{{if not .IsRoot}}
|
||||
<button class="btn btn-block" onclick="movePage()" title="Move page (M)">MOVE PAGE</button>
|
||||
<button class="btn btn-block" onclick="movePage()" title="Move page (M)">MOVE</button>
|
||||
<button class="btn btn-block danger" onclick="deletePage()" title="Delete page">DELETE</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>
|
||||
<a class="btn btn-block" href="?edit" title="Edit page (E)">EDIT</a>
|
||||
{{end}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
@@ -63,7 +55,7 @@
|
||||
{{if .Content}}
|
||||
<script src="/_/page/sections.js"></script>
|
||||
{{end}}
|
||||
<script src="/_/page/sidebar-fab.js"></script>
|
||||
<script src="/_/page/contents.js"></script>
|
||||
{{end}}
|
||||
|
||||
{{define "aside"}}{{if .SidebarWidget}}<aside class="sidebar">{{.SidebarWidget}}</aside>{{end}}{{end}}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -53,7 +53,6 @@ function createSearchPage(name) {
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
wireDropdown(document.querySelector('[data-action="actions-drop"]'));
|
||||
|
||||
var createLink = document.querySelector('[data-create-page]');
|
||||
if (createLink) {
|
||||
|
||||
@@ -42,15 +42,10 @@
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "headerActions"}}
|
||||
<button class="btn btn-block" onclick="rebuildIndex()" title="Rebuild search index">REBUILD INDEX</button>
|
||||
{{end}}
|
||||
|
||||
{{define "footerExtras"}}
|
||||
{{if not .IndexBuiltAt.IsZero}}<span class="muted">· Index: {{.IndexBuiltAt.Format "2006-01-02 15:04"}}</span>{{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}}
|
||||
|
||||
+27
-41
@@ -217,22 +217,9 @@ footer {
|
||||
padding: 0.3rem var(--space-3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-fab {
|
||||
background: var(--bg-panel);
|
||||
border: var(--border);
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-fab::before, .btn-fab::after { content: none; }
|
||||
.btn-fab:hover { background: var(--bg-panel-hover); color: var(--primary-hover); }
|
||||
.btn[hidden] {display: none;}
|
||||
.danger { color: var(--danger); }
|
||||
.danger:hover { color: var(--danger-hover); }
|
||||
/* Selected segmented-toggle button (view-settings modal). */
|
||||
.btn.is-active { color: var(--primary-hover); }
|
||||
|
||||
/* === Form controls ===
|
||||
@@ -403,6 +390,8 @@ main > h2 {
|
||||
.dropdown-menu.is-open { display: block; }
|
||||
.dropdown-menu.scrollable { max-height: 23rem; overflow-y: auto; }
|
||||
|
||||
.actions-special > .btn {text-align: left;}
|
||||
|
||||
/* === Suggestion dropdown (header search + editor link picker) ===
|
||||
Anchored to a position:relative host. Mirrors .dropdown-menu visuals with
|
||||
a dashed border and bg-panel-hover for the active row. */
|
||||
@@ -501,15 +490,6 @@ body.edit-mode footer { max-width: none; }
|
||||
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 { margin-left: auto; }
|
||||
.companion-icon { font-size: 0.9rem; line-height: 1; padding: 0.1rem 0.3rem; }
|
||||
@@ -691,7 +671,13 @@ aside.sidebar:empty { display: none; }
|
||||
border-bottom: var(--border-dashed);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.overlay-close { width: 2.5rem; height: 2.5rem; font-size: 1.5rem; }
|
||||
.overlay-close {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.overlay-close::before, .overlay-close::after { content: none; }
|
||||
.overlay-close:hover { background: var(--bg-panel-hover); color: var(--primary-hover); }
|
||||
.overlay-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -755,12 +741,7 @@ aside.sidebar:empty { display: none; }
|
||||
}
|
||||
.tree-row.is-active > .tree-name { color: var(--primary-hover); }
|
||||
|
||||
/* === Tree sidebar (persistent left navigation rail) ===
|
||||
Reuses the .tree-row / .tree-children / .tree-name / .tree-chevron modules.
|
||||
Desktop: a full-height flex column in the app-shell (composes with .col)
|
||||
holding the tree's own scroll region. Mobile: not laid out inline — its
|
||||
content is surfaced through the Overlay via the stacked FABs (see
|
||||
responsive). */
|
||||
/* Tree sidebar (persistent navigation) */
|
||||
.tree-sidebar {
|
||||
width: var(--tree-width);
|
||||
flex-shrink: 0;
|
||||
@@ -851,20 +832,25 @@ aside.sidebar:empty { display: none; }
|
||||
|
||||
/* === Responsive === */
|
||||
@media (max-width: 1100px) {
|
||||
/* Rails are not laid out inline on mobile — their content is surfaced
|
||||
through the Overlay via the stacked FABs. The <aside> elements stay in
|
||||
the DOM (their JS renders into them and the Overlay moves them in/out);
|
||||
we only pull them out of the shell flow here. */
|
||||
.tree-sidebar, .sidebar { display: none; }
|
||||
/* Compact three-column header: logo left, search stretches the middle,
|
||||
actions right. */
|
||||
header { grid-template-columns: auto 1fr auto; }
|
||||
.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. */
|
||||
button.fab { display: inline-flex; }
|
||||
.fab.dropdown { bottom: calc(var(--space-4) + 3rem + var(--space-2)); }
|
||||
.actions-menu .btn-block {
|
||||
min-height: 2.75rem;
|
||||
padding: 0.8rem var(--space-4);
|
||||
border-top: var(--border-dashed);
|
||||
}
|
||||
.actions-menu > .btn-block:first-child,
|
||||
.actions-special > .btn-block:first-child { border-top: none; }
|
||||
.actions-menu .btn-block:hover,
|
||||
.actions-menu .btn-block:active { background: var(--bg-panel-hover); }
|
||||
}
|
||||
|
||||
/* Show the actions menu items directly in the bar in desktop size */
|
||||
@media (min-width: 1101px) {
|
||||
.actions-toggle, .actions-special {display: none;}
|
||||
.actions, .actions-menu, .actions.dropdown-menu.is-open {display: contents;}
|
||||
.actions-menu .btn-block { width: auto; padding: 0;}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
||||
+13
-24
@@ -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 () {
|
||||
// This script owns the .tree-scroll child of the aside, not the aside itself.
|
||||
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
|
||||
// opens the tree's scroll container in the full-viewport Overlay
|
||||
// (overlay.js). The container always exists when not editing, so — per the
|
||||
// layout spec — the FAB always renders; the overlay auto-closes when a
|
||||
// folder/file link inside it navigates. Hidden on desktop by .fab CSS.
|
||||
function setupFab() {
|
||||
var fab = document.createElement('button');
|
||||
fab.type = 'button';
|
||||
fab.className = 'btn btn-fab fab fab-tree';
|
||||
fab.title = 'Folder tree';
|
||||
fab.setAttribute('aria-label', 'Folder tree');
|
||||
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 () {
|
||||
if (typeof openOverlay === 'function') openOverlay(container);
|
||||
function setupMenuItem() {
|
||||
var specials = document.querySelector('.actions-special');
|
||||
if (!specials) return;
|
||||
|
||||
var item = document.createElement("button");
|
||||
item.type = "button";
|
||||
item.className = "btn btn-block";
|
||||
item.title = "Folders";
|
||||
item.textContent = "FOLDERS";
|
||||
item.addEventListener("click", function () {
|
||||
if (typeof openOverlay === "function") openOverlay(container);
|
||||
});
|
||||
document.body.appendChild(fab);
|
||||
specials.appendChild(item);
|
||||
}
|
||||
|
||||
render();
|
||||
setupFab();
|
||||
setupMenuItem();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user