mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 07:03:56 +08:00
🎨 Sync 2026-09-17 01:27:32
This commit is contained in:
1 parent
fe7c1ed189
commit
d9eabd07fb
19 files changed
+988
-70
No files matched your search
@@ -11,12 +11,12 @@ address, so they stay local).
|
||||
|
||||
| Metric | Budget | Track | Source |
|
||||
|---|---|---|---|
|
||||
| main.css (identity/raw) | ≤ 193 KB | size | production build, 2026-09-16 (189,650 B; −0.77 KB sidebar push as two transitions instead of run keyframes; +42 B replayed nav skips the fade; +~2 KB custom-background mode, +~0.5 KB router progress bar / live region; the bar is now Turbo-shaped — width/opacity only, inserted per navigation; −1.24 KB unblurred mega-menu curtain, delayed visibility, instant nav-pill colours) |
|
||||
| main.css (identity/raw) | ≤ 193 KB | size | production build, 2026-09-17 (190,230 B; +0.6 KB palette tab rows "Parent › Title", unnested short selectors; −0.77 KB sidebar push as two transitions instead of run keyframes; +42 B replayed nav skips the fade; +~2 KB custom-background mode, +~0.5 KB router progress bar / live region; the bar is now Turbo-shaped — width/opacity only, inserted per navigation; −1.24 KB unblurred mega-menu curtain, delayed visibility, instant nav-pill colours) |
|
||||
| login.css (identity/raw) | ≤ 12 KB | size | production build, 2026-07 (10,935 B, token-pruned) |
|
||||
| menu-aurora.js (identity/raw) | ≤ 22 KB | size | production build, 2026-09-16 (21,362 B; −0.24 KB sidebar toggle is a class flip; −0.09 KB pre-paint nav cache, its crumb renderer moved into header.ut (the replay itself is per-response HTML, see below); +0.8 KB palette recents: record on pick, pure-LRU browse order, storage validation; +0.2 KB ">" logout command) |
|
||||
| router-aurora.js (identity/raw, Navigation-API browsers only) | ≤ 15 KB | size | production build, 2026-08 (14,844 B: template shells fetched, per-render listener teardown, timeout-as-failure, expiry gate, readonly folding, node css, wildcard actions, trickling progress bar, same-URL reload rule, visibility gate, contract check) |
|
||||
| menu-aurora.js (identity/raw) | ≤ 23 KB | size | production build, 2026-09-17 (22,665 B; +1.3 KB palette tabs: third-level nodes, redirect parents folded into their tabs, legacy recents mapped, parent-aware scoring, path words matched segment by segment; −0.24 KB sidebar toggle is a class flip; −0.09 KB pre-paint nav cache, its crumb renderer moved into header.ut (the replay itself is per-response HTML, see below); +0.8 KB palette recents: record on pick, pure-LRU browse order, storage validation; +0.2 KB ">" logout command) |
|
||||
| router-aurora.js (identity/raw, Navigation-API browsers only) | ≤ 16 KB | size | production build, 2026-09-13 (15,771 B, shipped from @eamonxg/luci-theme-devkit; 2026-08: 14,844 B: template shells fetched, per-render listener teardown, timeout-as-failure, expiry gate, readonly folding, node css, wildcard actions, trickling progress bar, same-URL reload rule, visibility gate, contract check) |
|
||||
| Default logo (identity/raw) | ≤ 16 KB | size | production build, 2026-07 (15,057 B) |
|
||||
| Core admin cold theme assets (identity/raw) | ≤ 267.5 KB | size | main CSS + menu JS + router JS + default font + logo, 2026-09-16 (265,420 B; −1.1 KB sidebar push and nav cache; the router is a one-time cost that removes per-click dispatcher work; +1.0 KB palette recents and logout command; −1.24 KB mega-menu curtain) |
|
||||
| Core admin cold theme assets (identity/raw) | ≤ 268.5 KB | size | main CSS + menu JS + router JS + default font + logo, 2026-09-17 (267,303 B; +1.9 KB palette tabs; −1.1 KB sidebar push and nav cache; the router is a one-time cost that removes per-click dispatcher work; +1.0 KB palette recents and logout command; −1.24 KB mega-menu curtain) |
|
||||
| Login cold theme assets, excluding configured background (identity/raw) | ≤ 55 KB | size | login CSS + default font + logo, 2026-07 (49,572 B) |
|
||||
| Admin HTML inline scripts added per full load (identity, never cached) | — | size | header.ut, 2026-09-16: pre-paint nav replay ≈ +0.5 KB (mega-menu/dropdown) / ≈ +1.9 KB (sidebar, with the shared crumb renderer); login page +0.07 KB. Template shells the router fetches carry it too. |
|
||||
| Blocking requests before first paint | ≤ 4 | count | current waterfall |
|
||||
|
||||
@@ -57,8 +57,8 @@ No `@layer` wrappers in partials; `main.css` import order is cascade order; toke
|
||||
from `@eamonxg/luci-theme-tokens/dist/aurora/tokens.css` (change colours in that repo).
|
||||
Budgets (`tests/build-performance.test.js`, mirrored in
|
||||
`.claude/skills/aurora-performance/references/aurora-budgets.md`): main.css ≤ 193 KB,
|
||||
login.css ≤ 12 KB, menu-aurora.js ≤ 22 KB, router-aurora.js ≤ 15 KB, admin cold set
|
||||
≤ 267.5 KB, login set ≤ 55 KB, SVG data URLs ≤ 17 KB and unique.
|
||||
login.css ≤ 12 KB, menu-aurora.js ≤ 23 KB, router-aurora.js ≤ 16 KB, admin cold set
|
||||
≤ 268.5 KB, login set ≤ 55 KB, SVG data URLs ≤ 17 KB and unique.
|
||||
|
||||
## Patches contract (third-party compatibility)
|
||||
File name = the page's `data-page` prefix; prefix matching on segment boundaries; both
|
||||
|
||||
@@ -130,6 +130,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* A tab row reads "Parent › Title": the parent gives way first, and keeps
|
||||
at least an ellipsis so the "›" never leads the row. Like the section
|
||||
label, it stays subtle on the selected row. Unnested: the classes exist
|
||||
only inside palette rows, and the short selectors keep main.css lean. */
|
||||
.cmdk-title:has(> .cmdk-parent) {
|
||||
@apply flex items-baseline;
|
||||
}
|
||||
|
||||
.cmdk-parent {
|
||||
@apply text-text-subtle min-w-0 truncate font-normal;
|
||||
}
|
||||
|
||||
.cmdk-label {
|
||||
@apply max-w-[calc(100%-1.5rem)] shrink-0 truncate;
|
||||
|
||||
&::before {
|
||||
@apply text-text-subtle px-1.5 font-normal content-['›'];
|
||||
}
|
||||
}
|
||||
|
||||
/* The frost is decorative — both degradation paths go opaque. */
|
||||
@supports not (backdrop-filter: blur(1px)) {
|
||||
.cmdk-panel {
|
||||
|
||||
@@ -584,11 +584,12 @@ return baseclass.extend({
|
||||
// is the navigation model the menus already render from — no extra
|
||||
// requests, no DOM scraping — and the panel DOM is built lazily on first
|
||||
// open, so pages where it is never used pay nothing beyond this flat array.
|
||||
initPalette(items) {
|
||||
initPalette(items, root) {
|
||||
const toggle = document.querySelector("#cmdk-trigger");
|
||||
if (!toggle || this.paletteIndex) return;
|
||||
|
||||
this.paletteIndex = [];
|
||||
this.paletteAliases = {};
|
||||
let logout = null;
|
||||
items.forEach((item) => {
|
||||
if (item.isLogout) {
|
||||
@@ -604,17 +605,44 @@ return baseclass.extend({
|
||||
});
|
||||
return;
|
||||
}
|
||||
item.pages.forEach((page) =>
|
||||
this.paletteIndex.push({
|
||||
title: page.title,
|
||||
// Section-qualified: "status/overview" keeps English dispatch
|
||||
// segments matchable under any UI language, and the "/" is what
|
||||
// arms the scorer's segment-start bonus.
|
||||
name: `${item.name}/${page.name}`,
|
||||
group: item.title,
|
||||
href: page.href,
|
||||
}),
|
||||
);
|
||||
item.pages.forEach((page) => {
|
||||
// Dispatch paths keep English segments matchable under any UI
|
||||
// language.
|
||||
const name = `${item.name}/${page.name}`;
|
||||
// The raw node: getChildren() hands out alias nodes carrying their
|
||||
// target's children, which would hide an alias parent's tabs.
|
||||
const node = root?.children?.[item.name]?.children?.[page.name] ?? {};
|
||||
const tabs = ui.menu.getChildren(node);
|
||||
const type = node.action?.type;
|
||||
const target =
|
||||
type === "alias"
|
||||
? node.action.path.replace(`${root.name}/${name}/`, "")
|
||||
: type === "firstchild" &&
|
||||
tabs.find((tab) => !tab.firstchild_ineligible)?.name;
|
||||
|
||||
// A parent that only redirects to one of its tabs is that tab; its
|
||||
// row gives way to the tabs, and its name (stored by older recents)
|
||||
// resolves to where it redirects.
|
||||
if (tabs.some((tab) => tab.name === target))
|
||||
this.paletteAliases[name] = `${name}/${target}`;
|
||||
else
|
||||
this.paletteIndex.push({
|
||||
title: page.title,
|
||||
name,
|
||||
group: item.title,
|
||||
href: page.href,
|
||||
});
|
||||
|
||||
tabs.forEach((tab) =>
|
||||
this.paletteIndex.push({
|
||||
title: _(tab.title),
|
||||
parent: page.title,
|
||||
name: `${name}/${tab.name}`,
|
||||
group: item.title,
|
||||
href: L.url(root.name, item.name, page.name, tab.name),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// The only non-navigation commands: theme modes. They ride the same
|
||||
@@ -887,17 +915,20 @@ return baseclass.extend({
|
||||
return best;
|
||||
},
|
||||
|
||||
// Empty query matches everything at score 0 (the browse list); title hits
|
||||
// outrank name/path and group hits and are the only ones highlighted.
|
||||
// localStorage can be unavailable (privacy modes) or hold anything after
|
||||
// a downgrade — both read as "no history". No size cap: dedupe bounds the
|
||||
// list by the pages actually visited, i.e. the menu's own scale.
|
||||
readPaletteRecents() {
|
||||
try {
|
||||
const list = JSON.parse(localStorage.getItem(PALETTE_RECENTS_KEY));
|
||||
return Array.isArray(list)
|
||||
? list.filter((name) => typeof name === "string")
|
||||
: [];
|
||||
if (!Array.isArray(list)) return [];
|
||||
return [
|
||||
...new Set(
|
||||
list
|
||||
.filter((name) => typeof name === "string")
|
||||
.map((name) => this.paletteAliases?.[name] ?? name),
|
||||
),
|
||||
];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
@@ -929,18 +960,66 @@ return baseclass.extend({
|
||||
);
|
||||
},
|
||||
|
||||
// Empty query matches everything at score 0 (the browse list). Title hits
|
||||
// rank first, then parent hits, which score the parent alone so its tabs
|
||||
// tie and keep menu order; "parent title" pairs split on a space. Then the
|
||||
// path, then the group.
|
||||
matchPaletteEntry(q, page) {
|
||||
if (!q) return { score: 0, ranges: null };
|
||||
|
||||
const title = this.fuzzyMatch(q, page.title);
|
||||
if (title) return { score: title.score + 12, ranges: title.ranges };
|
||||
|
||||
if (page.parent) {
|
||||
const parent = this.fuzzyMatch(q, page.parent);
|
||||
if (parent)
|
||||
return {
|
||||
score: parent.score + 8,
|
||||
ranges: null,
|
||||
parentRanges: parent.ranges,
|
||||
};
|
||||
|
||||
const words = q.split(/\s+/);
|
||||
for (let i = 1; i < words.length; i++) {
|
||||
const head = this.fuzzyMatch(words.slice(0, i).join(" "), page.parent);
|
||||
const tail =
|
||||
head && this.fuzzyMatch(words.slice(i).join(" "), page.title);
|
||||
if (tail)
|
||||
return {
|
||||
score: head.score + tail.score + 8,
|
||||
ranges: tail.ranges,
|
||||
parentRanges: head.ranges,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const rest =
|
||||
this.fuzzyMatch(q, page.name) ||
|
||||
this.matchPalettePath(q, page.name) ||
|
||||
(page.group ? this.fuzzyMatch(q, page.group) : null);
|
||||
return rest && { score: rest.score, ranges: null };
|
||||
},
|
||||
|
||||
// Each query word (split on spaces or "/") lands inside one segment, in
|
||||
// path order: "network wireless" reaches network/wireless, while a lone
|
||||
// word can't scatter across segments ("ssh" in services/passwall2/other).
|
||||
matchPalettePath(q, name) {
|
||||
const words = q.split(/[\s/]+/).filter(Boolean);
|
||||
const segments = name.split("/");
|
||||
let at = 0;
|
||||
let score = 0;
|
||||
|
||||
for (const word of words) {
|
||||
let hit = null;
|
||||
for (; at < segments.length; at++) {
|
||||
hit = this.fuzzyMatch(word, segments[at]);
|
||||
if (hit) break;
|
||||
}
|
||||
if (!hit) return null;
|
||||
score += hit.score;
|
||||
}
|
||||
return words.length ? { score } : null;
|
||||
},
|
||||
|
||||
collectPaletteMatches(value) {
|
||||
let q = value.trim().toLowerCase();
|
||||
// A leading ">" scopes the list to the command rows (the prototype's
|
||||
@@ -963,6 +1042,7 @@ return baseclass.extend({
|
||||
page,
|
||||
score: at < 0 ? m.score : recents.length - at,
|
||||
ranges: m.ranges,
|
||||
parentRanges: m.parentRanges,
|
||||
});
|
||||
}
|
||||
if (q || recents.length) matches.sort((a, b) => b.score - a.score);
|
||||
@@ -983,7 +1063,7 @@ return baseclass.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
matches.forEach(({ page, ranges }, i) => {
|
||||
matches.forEach(({ page, ranges, parentRanges }, i) => {
|
||||
const current = page.mode && page.mode === theme;
|
||||
const attributes = {
|
||||
class: "cmdk-row",
|
||||
@@ -1001,7 +1081,21 @@ return baseclass.extend({
|
||||
E(
|
||||
"span",
|
||||
{ class: "cmdk-title" },
|
||||
this.highlightPaletteMatch(page.title, ranges),
|
||||
// A tab named after its parent is the page the parent opens.
|
||||
page.parent && page.parent !== page.title
|
||||
? [
|
||||
E(
|
||||
"span",
|
||||
{ class: "cmdk-parent" },
|
||||
this.highlightPaletteMatch(page.parent, parentRanges),
|
||||
),
|
||||
E(
|
||||
"span",
|
||||
{ class: "cmdk-label" },
|
||||
this.highlightPaletteMatch(page.title, ranges),
|
||||
),
|
||||
]
|
||||
: this.highlightPaletteMatch(page.title, ranges),
|
||||
),
|
||||
current
|
||||
? // The ✓ is decorative (aria-current carries the state); mark
|
||||
@@ -1488,7 +1582,7 @@ return baseclass.extend({
|
||||
);
|
||||
this.renderMainMenu(activeChild, activeChild.name, 0, navigationItems);
|
||||
this.renderMobileMenu(navigationItems);
|
||||
this.initPalette(navigationItems);
|
||||
this.initPalette(navigationItems, activeChild);
|
||||
}
|
||||
|
||||
if (ul?.children.length > 1) {
|
||||
|
||||
@@ -26,7 +26,10 @@ test("production assets stay within raw-transfer budgets", () => {
|
||||
// 22K: palette recents (record on pick, pure-LRU browse order, storage
|
||||
// validation) added ~0.8 KB and the ">" logout command ~0.2 KB. The total
|
||||
// budget below moved by the same amount.
|
||||
assert.ok(menu <= 22_000, "menu-aurora.js exceeds 22 KB");
|
||||
// 23K: palette tabs (third-level nodes, redirect-parent folding, legacy
|
||||
// recents mapping, parent-aware scoring, per-segment path words) added
|
||||
// ~1.3 KB; the total budget below still holds.
|
||||
assert.ok(menu <= 23_000, "menu-aurora.js exceeds 23 KB");
|
||||
// 15K: the expiry gate, readonly folding, menu.d node css, wildcard
|
||||
// actions, progress bar, visibility gate and contract check added ~3 KB
|
||||
// over the first cut; the total-transfer budget below moved by the same
|
||||
|
||||
@@ -15,14 +15,17 @@ const E = (tagName, attributes, children) => ({
|
||||
|
||||
const loadMenuModule = (
|
||||
localStorage = {},
|
||||
{ document = {}, navigator = { platform: "" } } = {},
|
||||
{
|
||||
document = {},
|
||||
navigator = { platform: "" },
|
||||
ui = { menu: { getChildren: () => [] } },
|
||||
} = {},
|
||||
) => {
|
||||
const baseclass = {
|
||||
extend(module) {
|
||||
return module;
|
||||
},
|
||||
};
|
||||
const ui = { menu: { getChildren: () => [] } };
|
||||
const L = {
|
||||
env: { dispatchpath: [], requestpath: [] },
|
||||
url: (...segments) => `/${segments.join("/")}`,
|
||||
@@ -462,3 +465,328 @@ test("initPalette appends logout as the last command row", () => {
|
||||
palette.paletteIndex.indexOf(last),
|
||||
);
|
||||
});
|
||||
|
||||
// ---- Tabs: third-level menu nodes ----
|
||||
|
||||
// ui.menu.getChildren() as luci-base ships it, down to handing out alias
|
||||
// nodes with their target's children (none, for a tab target).
|
||||
const menuUi = {
|
||||
menu: {
|
||||
getChildren: (node) =>
|
||||
Object.entries(node?.children ?? {})
|
||||
.filter(([, child]) => child.satisfied && "title" in child)
|
||||
.map(([name, child]) =>
|
||||
child.action?.type === "alias"
|
||||
? { ...child, name, children: undefined }
|
||||
: Object.assign(child, { name }),
|
||||
)
|
||||
.sort((a, b) => (a.order ?? 1000) - (b.order ?? 1000)),
|
||||
},
|
||||
};
|
||||
|
||||
const node = (title, order, action, children) => ({
|
||||
satisfied: true,
|
||||
title,
|
||||
order,
|
||||
action,
|
||||
children,
|
||||
});
|
||||
const view = { type: "view" };
|
||||
|
||||
const menuRoot = () => ({
|
||||
name: "admin",
|
||||
children: {
|
||||
status: node("Status", 1, view, {
|
||||
overview: node("Overview", 1, view),
|
||||
logs: node(
|
||||
"System Log",
|
||||
2,
|
||||
{ type: "alias", path: "admin/status/logs/syslog" },
|
||||
{
|
||||
dmesg: node("Kernel Log", 2, view),
|
||||
syslog: node("System Log", 1, view),
|
||||
},
|
||||
),
|
||||
}),
|
||||
system: node("System", 2, view, {
|
||||
admin: node(
|
||||
"Administration",
|
||||
1,
|
||||
{ type: "firstchild" },
|
||||
{
|
||||
dropbear: node("SSH Access", 2, view),
|
||||
hidden: { satisfied: true, order: 0, action: view },
|
||||
password: node("Router Password", 1, view),
|
||||
},
|
||||
),
|
||||
bmx: node("BMX", 2, view, { nodes: node("Nodes", 1, view) }),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const menuItems = () => [
|
||||
{
|
||||
name: "status",
|
||||
title: "Status",
|
||||
hasChildren: true,
|
||||
pages: [
|
||||
{ name: "overview", title: "Overview", href: "/admin/status/overview" },
|
||||
{ name: "logs", title: "System Log", href: "/admin/status/logs" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "system",
|
||||
title: "System",
|
||||
hasChildren: true,
|
||||
pages: [
|
||||
{ name: "admin", title: "Administration", href: "/admin/system/admin" },
|
||||
{ name: "bmx", title: "BMX", href: "/admin/system/bmx" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const initTabbedPalette = (storage = fakeStorage()) => {
|
||||
const doc = {
|
||||
querySelector: (sel) =>
|
||||
sel === "#cmdk-trigger"
|
||||
? { setAttribute() {}, addEventListener() {} }
|
||||
: null,
|
||||
addEventListener() {},
|
||||
};
|
||||
const palette = loadMenuModule(storage, { document: doc, ui: menuUi });
|
||||
palette.initPalette(menuItems(), menuRoot());
|
||||
return palette;
|
||||
};
|
||||
|
||||
const navRows = (palette) =>
|
||||
palette.paletteIndex.filter((page) => !page.mode && !page.isLogout);
|
||||
|
||||
test("tabs join the index; a redirecting parent gives way to them", () => {
|
||||
const rows = navRows(initTabbedPalette());
|
||||
|
||||
assert.deepEqual(
|
||||
rows.map((page) => [page.name, page.parent ?? null]),
|
||||
[
|
||||
["status/overview", null],
|
||||
["status/logs/syslog", "System Log"],
|
||||
["status/logs/dmesg", "System Log"],
|
||||
["system/admin/password", "Administration"],
|
||||
["system/admin/dropbear", "Administration"],
|
||||
["system/bmx", null],
|
||||
["system/bmx/nodes", "BMX"],
|
||||
],
|
||||
);
|
||||
assert.equal(rows[2].href, "/admin/status/logs/dmesg");
|
||||
assert.equal(rows[2].group, "Status");
|
||||
});
|
||||
|
||||
test("redirecting parents map to the tab they open", () => {
|
||||
assert.deepEqual(initTabbedPalette().paletteAliases, {
|
||||
"status/logs": "status/logs/syslog",
|
||||
"system/admin": "system/admin/password",
|
||||
});
|
||||
});
|
||||
|
||||
test("a stored parent name reads as its tab, deduplicated", () => {
|
||||
const palette = initTabbedPalette(
|
||||
fakeStorage(
|
||||
stored(["system/admin", "system/admin/password", "status/logs"]),
|
||||
),
|
||||
);
|
||||
|
||||
assert.deepEqual(palette.readPaletteRecents(), [
|
||||
"system/admin/password",
|
||||
"status/logs/syslog",
|
||||
]);
|
||||
assert.deepEqual(names(palette.collectPaletteMatches("")).slice(0, 3), [
|
||||
"system/admin/password",
|
||||
"status/logs/syslog",
|
||||
"status/overview",
|
||||
]);
|
||||
});
|
||||
|
||||
const tab = (name, title, parent = "Firewall") => ({
|
||||
title,
|
||||
parent,
|
||||
name: `network/firewall/${name}`,
|
||||
group: "Network",
|
||||
href: `/${name}`,
|
||||
});
|
||||
|
||||
const firewallIndex = () => [
|
||||
navPage("status/nftables", "Firewall", "Status"),
|
||||
tab("zones", "General Settings"),
|
||||
tab("forwards", "Port Forwards"),
|
||||
tab("rules", "Traffic Rules"),
|
||||
tab("ipsets", "IP Sets"),
|
||||
];
|
||||
|
||||
test("a parent-only query keeps its tabs in menu order", () => {
|
||||
const palette = paletteWith(fakeStorage(), firewallIndex());
|
||||
|
||||
assert.deepEqual(names(palette.collectPaletteMatches("firewall")), [
|
||||
"status/nftables",
|
||||
"network/firewall/zones",
|
||||
"network/firewall/forwards",
|
||||
"network/firewall/rules",
|
||||
"network/firewall/ipsets",
|
||||
]);
|
||||
});
|
||||
|
||||
test("a parent hit highlights the parent, not the title", () => {
|
||||
const match = menu.matchPaletteEntry(
|
||||
"fire",
|
||||
tab("forwards", "Port Forwards"),
|
||||
);
|
||||
|
||||
assert.equal(match.ranges, null);
|
||||
assert.deepEqual(sliced("Firewall", match.parentRanges), ["Fire"]);
|
||||
});
|
||||
|
||||
test("a spaced query pairs parent words with title words", () => {
|
||||
const palette = paletteWith(fakeStorage(), firewallIndex());
|
||||
const [hit, ...rest] = palette.collectPaletteMatches("firewall port");
|
||||
|
||||
assert.equal(hit.page.name, "network/firewall/forwards");
|
||||
assert.deepEqual(rest, []);
|
||||
assert.deepEqual(sliced("Port Forwards", hit.ranges), ["Port"]);
|
||||
assert.deepEqual(sliced("Firewall", hit.parentRanges), ["Firewall"]);
|
||||
});
|
||||
|
||||
test("a query no longer scatters across path segments", () => {
|
||||
const other = {
|
||||
title: "Other Settings",
|
||||
parent: "PassWall 2",
|
||||
name: "services/passwall2/other",
|
||||
group: "Services",
|
||||
};
|
||||
|
||||
assert.ok(!menu.matchPaletteEntry("ssh", other));
|
||||
assert.ok(
|
||||
menu.matchPaletteEntry("sshkeys", {
|
||||
title: "SSH 密钥",
|
||||
parent: "管理权",
|
||||
name: "system/admin/sshkeys",
|
||||
group: "系统",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test("path words land in segments in order, never scattered", () => {
|
||||
const wireless = { title: "无线", name: "network/wireless", group: "网络" };
|
||||
const forwards = tab("forwards", "端口转发", "防火墙");
|
||||
|
||||
assert.ok(menu.matchPaletteEntry("network wireless", wireless));
|
||||
assert.ok(
|
||||
menu.matchPaletteEntry(
|
||||
"status/overview",
|
||||
navPage("status/overview", "概览"),
|
||||
),
|
||||
);
|
||||
assert.ok(menu.matchPaletteEntry("network firewall", forwards));
|
||||
assert.ok(menu.matchPaletteEntry("netw forw", forwards));
|
||||
assert.ok(!menu.matchPaletteEntry("wireless network", wireless));
|
||||
assert.ok(!menu.matchPaletteEntry("netwire", wireless));
|
||||
assert.ok(!menu.matchPaletteEntry("/", wireless));
|
||||
});
|
||||
|
||||
test("a parent keeps its row unless it redirects to one of its own tabs", () => {
|
||||
const doc = {
|
||||
querySelector: (sel) =>
|
||||
sel === "#cmdk-trigger"
|
||||
? { setAttribute() {}, addEventListener() {} }
|
||||
: null,
|
||||
addEventListener() {},
|
||||
};
|
||||
const palette = loadMenuModule(fakeStorage(), { document: doc, ui: menuUi });
|
||||
const root = {
|
||||
name: "admin",
|
||||
children: {
|
||||
services: node("Services", 1, view, {
|
||||
// Redirects to an untitled page, so no tab stands in for it.
|
||||
foo: node(
|
||||
"Foo",
|
||||
1,
|
||||
{ type: "alias", path: "admin/services/foo/main" },
|
||||
{
|
||||
main: { satisfied: true, order: 1, action: view },
|
||||
log: node("Log", 2, view),
|
||||
},
|
||||
),
|
||||
bar: node(
|
||||
"Bar",
|
||||
2,
|
||||
{ type: "firstchild" },
|
||||
{ only: { ...node("Only", 1, view), firstchild_ineligible: true } },
|
||||
),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
palette.initPalette(
|
||||
[
|
||||
{
|
||||
name: "services",
|
||||
title: "Services",
|
||||
hasChildren: true,
|
||||
pages: [
|
||||
{ name: "foo", title: "Foo", href: "/admin/services/foo" },
|
||||
{ name: "bar", title: "Bar", href: "/admin/services/bar" },
|
||||
],
|
||||
},
|
||||
],
|
||||
root,
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
navRows(palette).map((page) => page.name),
|
||||
["services/foo", "services/foo/log", "services/bar", "services/bar/only"],
|
||||
);
|
||||
assert.deepEqual(palette.paletteAliases, {});
|
||||
});
|
||||
|
||||
test("recording after a legacy read writes the mapped names back", () => {
|
||||
const storage = fakeStorage(stored(["status/logs", "status/overview"]));
|
||||
const palette = initTabbedPalette(storage);
|
||||
|
||||
palette.recordPaletteRecent("system/admin/dropbear");
|
||||
|
||||
assert.deepEqual(JSON.parse(storage.map.get(RECENTS_KEY)), [
|
||||
"system/admin/dropbear",
|
||||
"status/logs/syslog",
|
||||
"status/overview",
|
||||
]);
|
||||
});
|
||||
|
||||
test("a tab row renders its parent before the title unless they share a name", () => {
|
||||
const rows = [];
|
||||
const palette = paletteWith(fakeStorage(), [
|
||||
tab("forwards", "Port Forwards"),
|
||||
{ ...tab("firewall", "Firewall"), name: "network/firewall/firewall" },
|
||||
]);
|
||||
palette.paletteInput = { removeAttribute() {}, setAttribute() {} };
|
||||
palette.paletteList = {
|
||||
replaceChildren() {},
|
||||
appendChild: (row) => rows.push(row),
|
||||
};
|
||||
palette.setPaletteSelection = () => {};
|
||||
|
||||
palette.renderPaletteResults("fire");
|
||||
|
||||
const title = (name) =>
|
||||
rows.find((row) => row.attributes["data-name"] === name).children[0]
|
||||
.children;
|
||||
const forwards = title("network/firewall/forwards");
|
||||
const same = title("network/firewall/firewall");
|
||||
assert.deepEqual(
|
||||
forwards.map((part) => part.attributes.class),
|
||||
["cmdk-parent", "cmdk-label"],
|
||||
);
|
||||
const marked = (parts) => parts.some((part) => part.tagName === "mark");
|
||||
// The parent hit is highlighted inside the parent, the title stays plain.
|
||||
assert.ok(marked(forwards[0].children));
|
||||
assert.deepEqual(forwards[1].children, ["Port Forwards"]);
|
||||
// Same name: a plain highlighted title, no prefix.
|
||||
assert.ok(marked(same));
|
||||
assert.ok(!same.some((part) => part.attributes?.class === "cmdk-parent"));
|
||||
});
|
||||
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=1.3.7
|
||||
PKG_RELEASE:=81
|
||||
PKG_VERSION:=1.3.8
|
||||
PKG_RELEASE:=82
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_MINIFY_CSS:=
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-content:"";--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (notLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-content:"";--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (notLine truncated
|
||||
@@ -1 +1 @@
|
||||
"use strict";"require baseclass";"require ui";const PALETTE_RECENTS_KEY="aurora.paletteRecents";return baseclass.extend({__init__(){ui.menu.load().then(e=>this.render(e)).finally(()=>{for(const e of document.querySelectorAll("[data-restored]"))e.inert=!1}),this.initNavigationControls(),this.initUciIndicator()},initUciIndicator(){const e=ui.changes?.setIndicator;e&&(ui.changes.setIndicator=function(t){e.call(this,t),document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",t||0)})},initNavigationControls(){const e=document.querySelector("#mobile-menu-overlay"),t=document.querySelector("#navigation-toggle");if(!t||!e)return;const a=e.querySelector("#mobile-nav-list"),n=window.matchMedia("(min-width: 768px)"),i="aurora.sidebarCollapsed",s=()=>n.matches&&"sidebar"===document.body.dataset.navType,o=e=>{const a=s();t.classList.toggle("is-expanded",e&&!n.matches),t.setAttribute("aria-expanded",e?"true":"false"),t.setAttribute("aria-controls",a?"sidebar-panel":"mobile-menu-overlay");const i=e&&!a?_("Close"):_("Navigation");t.setAttribute("title",i),t.setAttribute("aria-label",i)},r=()=>{e.classList.contains("mobile-menu-open")&&(e.classList.remove("mobile-menu-open"),document.body.classList.remove("mobile-navigation-open"),document.body.style.overflow="",this.resetNavigationGroups(a),o(!1))};this.closeMobileNavigation=r;const l=t=>{if(s()){r();const e=!t;return document.body.classList.toggle("sidebar-collapsed",e),localStorage.setItem(i,e),void o(t)}if(n.matches)return r(),void o(!1);e.classList.toggle("mobile-menu-open",t),document.body.classList.toggle("mobile-navigation-open",t),document.body.style.overflow=t?"hidden":"",t?this.expandActiveNavigationGroup(a):this.resetNavigationGroups(a),o(t)},c=()=>{if(r(),s()){const e="true"===localStorage.getItem(i);return document.body.classList.toggle("sidebar-collapsed",e),void o(!e)}o(!1)};t.addEventListener("click",t=>{t.stopPropagation(),l(!(s()?!document.body.classList.contains("sidebar-collapsed"):!n.matches&&e.classList.contains("mobile-menu-open")))}),e.addEventListener("click",t=>{t.target===e&&l(!1)}),document.addEventListener("keydown",t=>{"Escape"===t.key&&e.classList.contains("mobile-menu-open")&&l(!1)}),n.addEventListener("change",c),c(),document.addEventListener("click",t=>{const a=t.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");a&&e.contains(a)&&l(!1)})},renderMobileMenu(e){const t=document.querySelector("#mobile-nav-list"),a=document.querySelector("#mobile-nav-footer-action");t&&(t.innerHTML=""),a&&(a.innerHTML=""),t&&(e.forEach(e=>{e.isLogout?a&&a.appendChild(E("a",{class:"mobile-nav-logout",href:e.href},[e.title])):t.appendChild(this.renderNavigationItem(e,"mobile"))}),this.bindNavigationAccordion(t))},render(e){this.tree=e,this.renderModeMenu(e),this.renderTabs(e),this.cacheNav()},cacheNav(){const e={};for(const t of document.querySelectorAll("#topmenu, #sidebar-list, #sidebar-footer"))t.firstChild&&(e[t.id]=t.innerHTML);try{sessionStorage.setItem("aurora.nav",JSON.stringify([document.body.dataset.navStamp,e]))}catch{}},renderTabs(e){if(L.env.dispatchpath.length<3)return;let t=e,a="";for(let e=0;e<3&&t;e++){const n=L.env.dispatchpath[e];t=t.children?.[n],a+=(a?"/":"")+n}t&&this.renderTabMenu(t,a)},syncRoute(){if(!this.tree)return;const e=new URL(L.url(...L.env.dispatchpath.slice(0,3)),location.href).pathname;document.querySelectorAll("#topmenu a, .desktop-menu-canvas a, #sidebar-list a, #mobile-nav-list a").forEach(t=>{const a=t.pathname===e&&!t.getAttribute("href").startsWith("#");t.classList.toggle("is-active-page",a),a?t.setAttribute("aria-current","page"):t.removeAttribute("aria-current")}),document.querySelectorAll(".navigation-group").forEach(e=>{const t=!!e.querySelector("a.is-active-page"),a=e.querySelector(".navigation-group-toggle");e.classList.toggle("is-active-group",t),t?a?.setAttribute("aria-current","location"):a?.removeAttribute("aria-current")});for(const e of[document.querySelector("#sidebar-list"),document.querySelector("#mobile-nav-list")])this.resetNavigationGroups(e),this.expandActiveNavigationGroup(e);window.auroraCrumb?.();const t=document.querySelector("#tabmenu");t&&(t.innerHTML="",t.style.display="none"),this.renderTabs(this.tree)},closeSurfaces(){this.hideDesktopNav(),this.paletteList&&this.closePalette(),this.closeMobileNavigation?.()},renderTabMenu(e,t,a=0){const n=document.querySelector("#tabmenu"),i=E("ul",{class:"tabs"}),s=ui.menu.getChildren(e);let o=null;return s.forEach(e=>{const n=L.env.dispatchpath[3+a]===e.name;i.appendChild(E("li",{class:`tabmenu-item-${e.name}${n?" active":""}`},[E("a",{href:L.url(t,e.name)},[_(e.title)])])),n&&(o=e)}),i.children.length?(n.appendChild(i),n.style.display="",o&&this.renderTabMenu(o,`${t}/${o.name}`,a+1),i):E([])},renderMainMenu(e,t,a=0,n=null){const i=a?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu"),s=ui.menu.getChildren(e);if(a>1)return E([]);if(0===a){const e=document.body?.dataset?.navType||"megaLine truncated
|
||||
"use strict";"require baseclass";"require ui";const PALETTE_RECENTS_KEY="aurora.paletteRecents";return baseclass.extend({__init__(){ui.menu.load().then(e=>this.render(e)).finally(()=>{for(const e of document.querySelectorAll("[data-restored]"))e.inert=!1}),this.initNavigationControls(),this.initUciIndicator()},initUciIndicator(){const e=ui.changes?.setIndicator;e&&(ui.changes.setIndicator=function(t){e.call(this,t),document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",t||0)})},initNavigationControls(){const e=document.querySelector("#mobile-menu-overlay"),t=document.querySelector("#navigation-toggle");if(!t||!e)return;const a=e.querySelector("#mobile-nav-list"),n=window.matchMedia("(min-width: 768px)"),i="aurora.sidebarCollapsed",s=()=>n.matches&&"sidebar"===document.body.dataset.navType,o=e=>{const a=s();t.classList.toggle("is-expanded",e&&!n.matches),t.setAttribute("aria-expanded",e?"true":"false"),t.setAttribute("aria-controls",a?"sidebar-panel":"mobile-menu-overlay");const i=e&&!a?_("Close"):_("Navigation");t.setAttribute("title",i),t.setAttribute("aria-label",i)},r=()=>{e.classList.contains("mobile-menu-open")&&(e.classList.remove("mobile-menu-open"),document.body.classList.remove("mobile-navigation-open"),document.body.style.overflow="",this.resetNavigationGroups(a),o(!1))};this.closeMobileNavigation=r;const l=t=>{if(s()){r();const e=!t;return document.body.classList.toggle("sidebar-collapsed",e),localStorage.setItem(i,e),void o(t)}if(n.matches)return r(),void o(!1);e.classList.toggle("mobile-menu-open",t),document.body.classList.toggle("mobile-navigation-open",t),document.body.style.overflow=t?"hidden":"",t?this.expandActiveNavigationGroup(a):this.resetNavigationGroups(a),o(t)},c=()=>{if(r(),s()){const e="true"===localStorage.getItem(i);return document.body.classList.toggle("sidebar-collapsed",e),void o(!e)}o(!1)};t.addEventListener("click",t=>{t.stopPropagation(),l(!(s()?!document.body.classList.contains("sidebar-collapsed"):!n.matches&&e.classList.contains("mobile-menu-open")))}),e.addEventListener("click",t=>{t.target===e&&l(!1)}),document.addEventListener("keydown",t=>{"Escape"===t.key&&e.classList.contains("mobile-menu-open")&&l(!1)}),n.addEventListener("change",c),c(),document.addEventListener("click",t=>{const a=t.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");a&&e.contains(a)&&l(!1)})},renderMobileMenu(e){const t=document.querySelector("#mobile-nav-list"),a=document.querySelector("#mobile-nav-footer-action");t&&(t.innerHTML=""),a&&(a.innerHTML=""),t&&(e.forEach(e=>{e.isLogout?a&&a.appendChild(E("a",{class:"mobile-nav-logout",href:e.href},[e.title])):t.appendChild(this.renderNavigationItem(e,"mobile"))}),this.bindNavigationAccordion(t))},render(e){this.tree=e,this.renderModeMenu(e),this.renderTabs(e),this.cacheNav()},cacheNav(){const e={};for(const t of document.querySelectorAll("#topmenu, #sidebar-list, #sidebar-footer"))t.firstChild&&(e[t.id]=t.innerHTML);try{sessionStorage.setItem("aurora.nav",JSON.stringify([document.body.dataset.navStamp,e]))}catch{}},renderTabs(e){if(L.env.dispatchpath.length<3)return;let t=e,a="";for(let e=0;e<3&&t;e++){const n=L.env.dispatchpath[e];t=t.children?.[n],a+=(a?"/":"")+n}t&&this.renderTabMenu(t,a)},syncRoute(){if(!this.tree)return;const e=new URL(L.url(...L.env.dispatchpath.slice(0,3)),location.href).pathname;document.querySelectorAll("#topmenu a, .desktop-menu-canvas a, #sidebar-list a, #mobile-nav-list a").forEach(t=>{const a=t.pathname===e&&!t.getAttribute("href").startsWith("#");t.classList.toggle("is-active-page",a),a?t.setAttribute("aria-current","page"):t.removeAttribute("aria-current")}),document.querySelectorAll(".navigation-group").forEach(e=>{const t=!!e.querySelector("a.is-active-page"),a=e.querySelector(".navigation-group-toggle");e.classList.toggle("is-active-group",t),t?a?.setAttribute("aria-current","location"):a?.removeAttribute("aria-current")});for(const e of[document.querySelector("#sidebar-list"),document.querySelector("#mobile-nav-list")])this.resetNavigationGroups(e),this.expandActiveNavigationGroup(e);window.auroraCrumb?.();const t=document.querySelector("#tabmenu");t&&(t.innerHTML="",t.style.display="none"),this.renderTabs(this.tree)},closeSurfaces(){this.hideDesktopNav(),this.paletteList&&this.closePalette(),this.closeMobileNavigation?.()},renderTabMenu(e,t,a=0){const n=document.querySelector("#tabmenu"),i=E("ul",{class:"tabs"}),s=ui.menu.getChildren(e);let o=null;return s.forEach(e=>{const n=L.env.dispatchpath[3+a]===e.name;i.appendChild(E("li",{class:`tabmenu-item-${e.name}${n?" active":""}`},[E("a",{href:L.url(t,e.name)},[_(e.title)])])),n&&(o=e)}),i.children.length?(n.appendChild(i),n.style.display="",o&&this.renderTabMenu(o,`${t}/${o.name}`,a+1),i):E([])},renderMainMenu(e,t,a=0,n=null){const i=a?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu"),s=ui.menu.getChildren(e);if(a>1)return E([]);if(0===a){const e=document.body?.dataset?.navType||"megaLine truncated
|
||||
Reference in new issue
Block a user