mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 14:43:56 +08:00
🎄 Sync 2026-07-23 00:00:40
This commit is contained in:
1 parent
9df7ccb682
commit
14f5c9168a
20 files changed
+582
-143
No files matched your search
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-cpu-status
|
PKG_NAME:=luci-app-cpu-status
|
||||||
PKG_VERSION:=0.6.3
|
PKG_VERSION:=0.6.3
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
LUCI_TITLE:=CPU utilization info for the LuCI status page
|
LUCI_TITLE:=CPU utilization info for the LuCI status page
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: \n"
|
||||||
|
"PO-Revision-Date: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||||
|
"Language: pl\n"
|
||||||
|
|
||||||
|
msgid "(%d minute window, %d second interval)"
|
||||||
|
msgstr "(%d minutowe okno, %d sekundowy interwał)"
|
||||||
|
|
||||||
|
msgid "All CPUs"
|
||||||
|
msgstr "Wszystkie procesory"
|
||||||
|
|
||||||
|
msgid "Average:"
|
||||||
|
msgstr "Średnia:"
|
||||||
|
|
||||||
|
msgid "Calculating"
|
||||||
|
msgstr "Obliczanie"
|
||||||
|
|
||||||
|
msgid "CPU"
|
||||||
|
msgstr "Procesor"
|
||||||
|
|
||||||
|
msgid "CPU Load"
|
||||||
|
msgstr "Obciążenie procesora"
|
||||||
|
|
||||||
|
msgid "CPU load"
|
||||||
|
msgstr "Obciążenie procesora"
|
||||||
|
|
||||||
|
msgid "Detailed load"
|
||||||
|
msgstr "Szczegółowe obciążenie"
|
||||||
|
|
||||||
|
msgid "Detailed load of each CPU"
|
||||||
|
msgstr "Szczegółowe obciążenie każdego procesora"
|
||||||
|
|
||||||
|
msgid "Load"
|
||||||
|
msgstr "Obciążenie"
|
||||||
|
|
||||||
|
msgid "Load of each CPU"
|
||||||
|
msgstr "Obciążenie każdego procesora"
|
||||||
|
|
||||||
|
msgid "Peak:"
|
||||||
|
msgstr "Szczytowe:"
|
||||||
|
|
||||||
|
msgid "This page displays the average CPU load at %d second interval."
|
||||||
|
msgstr "Ta strona wyświetla średnie obciążenie procesora w %d sekundowych odstępach."
|
||||||
|
|
||||||
|
msgid "Total"
|
||||||
|
msgstr "Łącznie"
|
||||||
|
|
||||||
|
msgid "Total load"
|
||||||
|
msgstr "Łączne obciążenie"
|
||||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.7.16
|
PKG_VERSION:=26.7.16
|
||||||
PKG_RELEASE:=190
|
PKG_RELEASE:=191
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -590,6 +590,10 @@ if singbox_tags:find("with_utls") then
|
|||||||
o:depends({ [_n("reality")] = true })
|
o:depends({ [_n("reality")] = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
o = s:option(Flag, _n("anytls_disable_reuse"), translate("Disable TLS Reuse"))
|
||||||
|
o.default = 0
|
||||||
|
o:depends({ [_n("protocol")] = "anytls" })
|
||||||
|
|
||||||
o = s:option(ListValue, _n("transport"), translate("Transport"))
|
o = s:option(ListValue, _n("transport"), translate("Transport"))
|
||||||
o:value("tcp", "TCP")
|
o:value("tcp", "TCP")
|
||||||
o:value("http", "HTTP")
|
o:value("http", "HTTP")
|
||||||
|
|||||||
@@ -613,6 +613,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
idle_session_check_interval = "30s",
|
idle_session_check_interval = "30s",
|
||||||
idle_session_timeout = "30s",
|
idle_session_timeout = "30s",
|
||||||
min_idle_session = 5,
|
min_idle_session = 5,
|
||||||
|
disable_reuse = (node.anytls_disable_reuse == "1") and true or nil,
|
||||||
tls = tls
|
tls = tls
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1049,7 +1049,7 @@ function gen_config(var)
|
|||||||
|
|
||||||
api.log(" - 加载 Xray 负载均衡 节点【" .. (_node.remarks or "") .. "】,子节点数量:" .. #(blc_nodes or {}))
|
api.log(" - 加载 Xray 负载均衡 节点【" .. (_node.remarks or "") .. "】,子节点数量:" .. #(blc_nodes or {}))
|
||||||
|
|
||||||
local valid_nodes, valid_fallback = {}, true
|
local valid_nodes = {}
|
||||||
for i = 1, #(blc_nodes or {}) do
|
for i = 1, #(blc_nodes or {}) do
|
||||||
local blc_node_id = blc_nodes[i]
|
local blc_node_id = blc_nodes[i]
|
||||||
local blc_node_tag = "blc-" .. blc_node_id
|
local blc_node_tag = "blc-" .. blc_node_id
|
||||||
@@ -1069,14 +1069,14 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
-- Check if balancing node duplicates fallback node
|
-- Check if balancing node duplicates fallback node
|
||||||
if _node.fallback_node == blc_node_id then
|
if _node.fallback_node == blc_node_id then
|
||||||
valid_fallback = false
|
_node.fallback_node = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #valid_nodes == 0 then return nil end
|
if #valid_nodes == 0 then return nil end
|
||||||
|
|
||||||
-- fallback node
|
-- fallback node
|
||||||
local fallback_node_id = _node.fallback_node
|
local fallback_node_id = _node.fallback_node
|
||||||
fallback_node_id = (valid_fallback and fallback_node_id and fallback_node_id ~= "") and fallback_node_id or nil
|
fallback_node_id = (fallback_node_id and fallback_node_id ~= "") and fallback_node_id or nil
|
||||||
local fallback_node_tag = (fallback_node_id == "_direct") and "direct" or "blackhole"
|
local fallback_node_tag = (fallback_node_id == "_direct") and "direct" or "blackhole"
|
||||||
if fallback_node_id and fallback_node_id ~= "_direct" then
|
if fallback_node_id and fallback_node_id ~= "_direct" then
|
||||||
local is_new_node = true
|
local is_new_node = true
|
||||||
|
|||||||
@@ -1749,7 +1749,7 @@ local current_node = map:get(section)
|
|||||||
opt.set(dom_prefix + 'tls', true);
|
opt.set(dom_prefix + 'tls', true);
|
||||||
opt.set(dom_prefix + 'reality', false);
|
opt.set(dom_prefix + 'reality', false);
|
||||||
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
|
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
|
||||||
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
|
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || queryParam.peer || '');
|
||||||
opt.set(dom_prefix + 'tls_allowInsecure', !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0'));
|
opt.set(dom_prefix + 'tls_allowInsecure', !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0'));
|
||||||
if (queryParam.fp && queryParam.fp.trim() != "") {
|
if (queryParam.fp && queryParam.fp.trim() != "") {
|
||||||
opt.set(dom_prefix + 'utls', true);
|
opt.set(dom_prefix + 'utls', true);
|
||||||
@@ -1760,7 +1760,7 @@ local current_node = map:get(section)
|
|||||||
opt.set(dom_prefix + 'tls', true);
|
opt.set(dom_prefix + 'tls', true);
|
||||||
opt.set(dom_prefix + 'reality', true);
|
opt.set(dom_prefix + 'reality', true);
|
||||||
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
|
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
|
||||||
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
|
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || queryParam.peer || '');
|
||||||
if (queryParam.fp && queryParam.fp.trim() != "") {
|
if (queryParam.fp && queryParam.fp.trim() != "") {
|
||||||
opt.set(dom_prefix + 'utls', true);
|
opt.set(dom_prefix + 'utls', true);
|
||||||
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
||||||
|
|||||||
@@ -2195,3 +2195,6 @@ msgstr "Gecko 包大小(最大)"
|
|||||||
|
|
||||||
msgid "valid time (hh:mm)"
|
msgid "valid time (hh:mm)"
|
||||||
msgstr "有效时间(hh:mm)"
|
msgstr "有效时间(hh:mm)"
|
||||||
|
|
||||||
|
msgid "Disable TLS Reuse"
|
||||||
|
msgstr "禁用 TLS 连接复用"
|
||||||
@@ -459,6 +459,8 @@ local function encode_anytls(node)
|
|||||||
table.insert(p, "insecure=" .. (o.tls.insecure and "1" or "0"))
|
table.insert(p, "insecure=" .. (o.tls.insecure and "1" or "0"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if node["disable-reuse"] then table.insert(p, "disable_reuse=1") end
|
||||||
|
|
||||||
if #p > 0 then
|
if #p > 0 then
|
||||||
link = link .. "?" .. table.concat(p, "&")
|
link = link .. "?" .. table.concat(p, "&")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1562,7 +1562,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
end
|
end
|
||||||
if params.security == "tls" or params.security == "reality" then
|
if params.security == "tls" or params.security == "reality" then
|
||||||
result.tls = "1"
|
result.tls = "1"
|
||||||
result.tls_serverName = params.sni
|
result.tls_serverName = params.sni or params.peer
|
||||||
result.alpn = params.alpn
|
result.alpn = params.alpn
|
||||||
if params.fp and params.fp ~= "" then
|
if params.fp and params.fp ~= "" then
|
||||||
result.utls = "1"
|
result.utls = "1"
|
||||||
@@ -1580,6 +1580,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
end
|
end
|
||||||
result.port = port
|
result.port = port
|
||||||
result.tls_allowInsecure = params.allowinsecure or params.insecure
|
result.tls_allowInsecure = params.allowinsecure or params.insecure
|
||||||
|
result.anytls_disable_reuse = params.disable_reuse
|
||||||
end
|
end
|
||||||
elseif szType == 'naive+https' or szType == 'naive+quic' then
|
elseif szType == 'naive+https' or szType == 'naive+quic' then
|
||||||
if has_singbox then
|
if has_singbox then
|
||||||
|
|||||||
+24
-19
@@ -1,13 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* Header route search — Spotlight-style palette, available in all three nav
|
* Command palette (⌘K) — Spotlight-style, available in all three nav types
|
||||||
* types (sidebar / mega-menu / dropdown). The toggle sits at the header's
|
* (sidebar / mega-menu / dropdown). The trigger sits at the header's right
|
||||||
* right edge next to #indicators; ⌘K / Ctrl+K toggles the panel globally.
|
* edge next to #indicators; ⌘K / Ctrl+K toggles the panel globally. Class
|
||||||
|
* names mirror luci-theme-shadcn's palette so the two themes stay legible
|
||||||
|
* side by side.
|
||||||
*
|
*
|
||||||
* The panel is built lazily by menu-aurora.js on first open; its index is
|
* The panel is built lazily by menu-aurora.js on first open; its index is
|
||||||
* the navigation model the menus already render from (no extra requests).
|
* the navigation model the menus already render from (no extra requests)
|
||||||
|
* plus three theme-mode command rows. An empty query browses the whole
|
||||||
|
* index; typed queries rank via a fuzzy subsequence scorer; a leading ">"
|
||||||
|
* (or full-width ">") scopes the list to the command rows.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.header-search-toggle {
|
.cmdk-trigger {
|
||||||
@apply text-text relative mr-4 grid size-8 shrink-0 cursor-pointer place-items-center rounded-lg border-0 bg-transparent p-0 shadow-none transition-colors duration-150;
|
@apply text-text relative mr-4 grid size-8 shrink-0 cursor-pointer place-items-center rounded-lg border-0 bg-transparent p-0 shadow-none transition-colors duration-150;
|
||||||
|
|
||||||
/* Non-sidebar headers have no mr-auto crumb absorbing the slack — pull the
|
/* Non-sidebar headers have no mr-auto crumb absorbing the slack — pull the
|
||||||
@@ -30,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-search-panel {
|
.cmdk-panel {
|
||||||
@apply border-hairline bg-surface/85 fixed top-24 left-1/2 z-120 w-[min(36rem,calc(100vw-2.5rem))] -translate-x-1/2 overflow-hidden rounded-2xl border shadow-xl backdrop-blur-xl backdrop-saturate-150 transition-[opacity,translate] duration-150 ease-out starting:-translate-y-2 starting:opacity-0;
|
@apply border-hairline bg-surface/85 fixed top-24 left-1/2 z-120 w-[min(36rem,calc(100vw-2.5rem))] -translate-x-1/2 overflow-hidden rounded-2xl border shadow-xl backdrop-blur-xl backdrop-saturate-150 transition-[opacity,translate] duration-150 ease-out starting:-translate-y-2 starting:opacity-0;
|
||||||
|
|
||||||
/* <md: the same panel becomes a full-screen takeover (Algolia DocSearch
|
/* <md: the same panel becomes a full-screen takeover (Algolia DocSearch
|
||||||
@@ -38,7 +43,7 @@
|
|||||||
input bar pinned top with a Cancel exit, results fill the rest. */
|
input bar pinned top with a Cancel exit, results fill the rest. */
|
||||||
@apply max-md:top-0 max-md:right-0 max-md:bottom-0 max-md:left-0 max-md:flex max-md:w-auto max-md:translate-none max-md:flex-col max-md:rounded-none max-md:border-0 max-md:bg-bg max-md:shadow-none max-md:backdrop-blur-none max-md:backdrop-saturate-100;
|
@apply max-md:top-0 max-md:right-0 max-md:bottom-0 max-md:left-0 max-md:flex max-md:w-auto max-md:translate-none max-md:flex-col max-md:rounded-none max-md:border-0 max-md:bg-bg max-md:shadow-none max-md:backdrop-blur-none max-md:backdrop-saturate-100;
|
||||||
|
|
||||||
& .header-search-box {
|
& .cmdk-inputrow {
|
||||||
@apply flex items-center gap-3 px-5 py-3.5;
|
@apply flex items-center gap-3 px-5 py-3.5;
|
||||||
@apply max-md:border-hairline max-md:bg-surface max-md:border-b max-md:px-4 max-md:py-2.5;
|
@apply max-md:border-hairline max-md:bg-surface max-md:border-b max-md:px-4 max-md:py-2.5;
|
||||||
|
|
||||||
@@ -46,27 +51,27 @@
|
|||||||
@apply text-text-subtle size-5 shrink-0 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat];
|
@apply text-text-subtle size-5 shrink-0 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat];
|
||||||
}
|
}
|
||||||
|
|
||||||
& .header-search-input {
|
& .cmdk-input {
|
||||||
@apply text-text placeholder-text-subtle w-full min-w-0 flex-1 rounded-none border-0 bg-transparent p-0 text-base shadow-none focus:ring-0 focus:outline-none;
|
@apply text-text placeholder-text-subtle w-full min-w-0 flex-1 rounded-none border-0 bg-transparent p-0 text-base shadow-none focus:ring-0 focus:outline-none;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .header-search-cancel {
|
& .cmdk-cancel {
|
||||||
@apply text-brand hidden shrink-0 cursor-pointer border-0 bg-transparent p-1 text-sm shadow-none max-md:block;
|
@apply text-brand hidden shrink-0 cursor-pointer border-0 bg-transparent p-1 text-sm shadow-none max-md:block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .header-search-results {
|
& .cmdk-list {
|
||||||
@apply border-hairline max-h-80 overflow-y-auto border-t p-2 empty:hidden;
|
@apply border-hairline max-h-80 overflow-y-auto border-t p-2 empty:hidden;
|
||||||
@apply max-md:max-h-none max-md:flex-1 max-md:overscroll-contain;
|
@apply max-md:max-h-none max-md:flex-1 max-md:overscroll-contain;
|
||||||
|
|
||||||
& .header-search-result {
|
& .cmdk-row {
|
||||||
@apply text-text flex items-center gap-3 rounded-lg px-3 py-2 text-sm no-underline;
|
@apply text-text flex items-center gap-3 rounded-lg px-3 py-2 text-sm no-underline;
|
||||||
|
|
||||||
& .result-title {
|
& .cmdk-title {
|
||||||
@apply min-w-0 truncate;
|
@apply min-w-0 truncate;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .result-group {
|
& .cmdk-group-name {
|
||||||
@apply text-text-subtle ml-auto shrink-0 text-xs;
|
@apply text-text-subtle ml-auto shrink-0 text-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,21 +82,21 @@
|
|||||||
&.is-selected {
|
&.is-selected {
|
||||||
@apply bg-brand-subtle;
|
@apply bg-brand-subtle;
|
||||||
|
|
||||||
& .result-title {
|
& .cmdk-title {
|
||||||
@apply text-brand font-medium;
|
@apply text-brand font-medium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .header-search-empty {
|
& .cmdk-empty {
|
||||||
@apply text-text-subtle px-3 py-6 text-center text-sm;
|
@apply text-text-subtle px-3 py-6 text-center text-sm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .header-search-hint {
|
& .cmdk-footer {
|
||||||
@apply border-hairline text-text-subtle flex items-center gap-4 border-t px-4 py-2 text-xs max-md:hidden;
|
@apply border-hairline text-text-subtle flex items-center gap-4 border-t px-4 py-2 text-xs max-md:hidden;
|
||||||
|
|
||||||
& .header-search-hint-key {
|
& .cmdk-hint-close {
|
||||||
@apply ml-auto;
|
@apply ml-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +108,13 @@
|
|||||||
|
|
||||||
/* The frost is decorative — both degradation paths go opaque. */
|
/* The frost is decorative — both degradation paths go opaque. */
|
||||||
@supports not (backdrop-filter: blur(1px)) {
|
@supports not (backdrop-filter: blur(1px)) {
|
||||||
.header-search-panel {
|
.cmdk-panel {
|
||||||
@apply bg-surface;
|
@apply bg-surface;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-transparency: reduce) {
|
@media (prefers-reduced-transparency: reduce) {
|
||||||
.header-search-panel {
|
.cmdk-panel {
|
||||||
@apply bg-surface;
|
@apply bg-surface;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
@import "./components/_form.css";
|
@import "./components/_form.css";
|
||||||
@import "./components/_segmented.css";
|
@import "./components/_segmented.css";
|
||||||
@import "./components/_tab.css";
|
@import "./components/_tab.css";
|
||||||
@import "./components/_search.css";
|
@import "./components/_palette.css";
|
||||||
|
|
||||||
@import "./_utilities.css";
|
@import "./_utilities.css";
|
||||||
@import "./_reduced-motion.css";
|
@import "./_reduced-motion.css";
|
||||||
|
|||||||
@@ -495,20 +495,20 @@ return baseclass.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Route search (all nav types): a Spotlight-style palette on ⌘K / Ctrl+K
|
// Command palette (all nav types): a Spotlight-style panel on ⌘K / Ctrl+K
|
||||||
// or the header toggle. The index is the navigation model the menus
|
// or the header trigger, holding routes and theme-mode commands. The index
|
||||||
// already render from — no extra requests, no DOM scraping — and the
|
// is the navigation model the menus already render from — no extra
|
||||||
// panel DOM is built lazily on first open, so pages where search is never
|
// requests, no DOM scraping — and the panel DOM is built lazily on first
|
||||||
// used pay nothing beyond this flat array.
|
// open, so pages where it is never used pay nothing beyond this flat array.
|
||||||
initSearch(items) {
|
initPalette(items) {
|
||||||
const toggle = document.querySelector("#header-search-toggle");
|
const toggle = document.querySelector("#cmdk-trigger");
|
||||||
if (!toggle || this.searchIndex) return;
|
if (!toggle || this.paletteIndex) return;
|
||||||
|
|
||||||
this.searchIndex = [];
|
this.paletteIndex = [];
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
if (item.isLogout) return;
|
if (item.isLogout) return;
|
||||||
if (!item.hasChildren) {
|
if (!item.hasChildren) {
|
||||||
this.searchIndex.push({
|
this.paletteIndex.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
group: null,
|
group: null,
|
||||||
@@ -517,97 +517,137 @@ return baseclass.extend({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
item.pages.forEach((page) =>
|
item.pages.forEach((page) =>
|
||||||
this.searchIndex.push({
|
this.paletteIndex.push({
|
||||||
title: page.title,
|
title: page.title,
|
||||||
name: page.name,
|
// 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,
|
group: item.title,
|
||||||
href: page.href,
|
href: page.href,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The only non-navigation commands: theme modes. They ride the same
|
||||||
|
// index — matched and rendered like pages, grouped under _("Design")
|
||||||
|
// (the System → Language and Style label) — but execute header.ut's
|
||||||
|
// global setTheme() instead of navigating, so the panel stays open and
|
||||||
|
// previews the switch live. luci-base has no Light/Dark msgids — they
|
||||||
|
// stay English literals, wrapped in _() so a future catalog entry would
|
||||||
|
// take effect (the same trade the icon-only switcher makes).
|
||||||
|
[
|
||||||
|
["light", _("Light")],
|
||||||
|
["dark", _("Dark")],
|
||||||
|
["device", _("Automatic")],
|
||||||
|
].forEach(([mode, title]) =>
|
||||||
|
this.paletteIndex.push({
|
||||||
|
title,
|
||||||
|
name: `theme ${mode}`,
|
||||||
|
group: _("Design"),
|
||||||
|
mode,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Only msgids that already exist in the luci-base catalog are used —
|
// Only msgids that already exist in the luci-base catalog are used —
|
||||||
// the theme intentionally ships no translations of its own.
|
// the theme intentionally ships no translations of its own.
|
||||||
const isMac = /Mac|iP(ad|hone|od)/.test(navigator.platform);
|
const isMac = /Mac|iP(ad|hone|od)/.test(navigator.platform);
|
||||||
this.searchKey = isMac ? "⌘K" : "Ctrl+K";
|
this.paletteKey = isMac ? "⌘K" : "Ctrl+K";
|
||||||
toggle.setAttribute("aria-keyshortcuts", isMac ? "Meta+K" : "Control+K");
|
toggle.setAttribute("aria-keyshortcuts", isMac ? "Meta+K" : "Control+K");
|
||||||
toggle.setAttribute("aria-expanded", "false");
|
toggle.setAttribute("aria-expanded", "false");
|
||||||
toggle.addEventListener("click", () => this.toggleSearch());
|
toggle.addEventListener("click", () => this.togglePalette());
|
||||||
this.searchToggle = toggle;
|
this.paletteTrigger = toggle;
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
document.addEventListener("keydown", (e) => {
|
||||||
// An IME swallows these keys while composing (Esc cancels the
|
// An IME swallows these keys while composing (Esc cancels the
|
||||||
// composition, not the panel); keyCode 229 covers engines that
|
// composition, not the panel); keyCode 229 covers engines that
|
||||||
// don't set isComposing on the trailing keydown.
|
// don't set isComposing on the trailing keydown.
|
||||||
if (e.isComposing || e.keyCode === 229) return;
|
if (e.isComposing || e.keyCode === 229) return;
|
||||||
|
// Platform-exact modifier — only the advertised shortcut, with every
|
||||||
|
// other modifier rejected so combinations like Ctrl+Cmd+K fall through.
|
||||||
|
// On macOS, Ctrl+K is kill-to-end-of-line in text fields and must keep
|
||||||
|
// working.
|
||||||
if (
|
if (
|
||||||
(e.metaKey || e.ctrlKey) &&
|
(isMac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey) &&
|
||||||
!e.altKey &&
|
!e.altKey &&
|
||||||
!e.shiftKey &&
|
!e.shiftKey &&
|
||||||
(e.key || "").toLowerCase() === "k"
|
(e.key || "").toLowerCase() === "k"
|
||||||
) {
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.toggleSearch();
|
this.togglePalette();
|
||||||
} else if (
|
} else if (
|
||||||
e.key === "Escape" &&
|
e.key === "Escape" &&
|
||||||
this.searchPanel &&
|
this.palettePanel &&
|
||||||
!this.searchPanel.hidden
|
!this.palettePanel.hidden
|
||||||
) {
|
) {
|
||||||
this.closeSearch();
|
this.closePalette();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Registered only while the panel is open (see openSearch/closeSearch).
|
// Registered only while the panel is open (see openPalette/closePalette).
|
||||||
this.onSearchAway = (e) => {
|
this.onPaletteAway = (e) => {
|
||||||
if (!this.searchPanel.contains(e.target) && !toggle.contains(e.target))
|
if (!this.palettePanel.contains(e.target) && !toggle.contains(e.target))
|
||||||
this.closeSearch();
|
this.closePalette();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
buildSearchPanel() {
|
buildPalette() {
|
||||||
|
// type=search gets WebKit's native clear button for free; the combobox
|
||||||
|
// wiring (with role=option rows) is what makes arrow-key selection
|
||||||
|
// audible to screen readers — visually it's CSS-only .is-selected.
|
||||||
const input = E("input", {
|
const input = E("input", {
|
||||||
class: "header-search-input",
|
class: "cmdk-input",
|
||||||
type: "text",
|
type: "search",
|
||||||
|
enterkeyhint: "go",
|
||||||
placeholder: _("Type to filter…"),
|
placeholder: _("Type to filter…"),
|
||||||
"aria-label": _("Type to filter…"),
|
"aria-label": _("Type to filter…"),
|
||||||
autocomplete: "off",
|
autocomplete: "off",
|
||||||
spellcheck: "false",
|
spellcheck: "false",
|
||||||
|
role: "combobox",
|
||||||
|
"aria-expanded": "true",
|
||||||
|
"aria-autocomplete": "list",
|
||||||
|
"aria-controls": "cmdk-list",
|
||||||
|
});
|
||||||
|
const results = E("div", {
|
||||||
|
class: "cmdk-list",
|
||||||
|
id: "cmdk-list",
|
||||||
|
role: "listbox",
|
||||||
});
|
});
|
||||||
const results = E("div", { class: "header-search-results" });
|
|
||||||
// Mobile-only exit (the full-screen takeover leaves no outside to tap
|
// Mobile-only exit (the full-screen takeover leaves no outside to tap
|
||||||
// and touch devices have no Escape) — hidden on md+ via CSS.
|
// and touch devices have no Escape) — hidden on md+ via CSS.
|
||||||
const cancel = E(
|
const cancel = E(
|
||||||
"button",
|
"button",
|
||||||
{ class: "header-search-cancel", type: "button" },
|
{ class: "cmdk-cancel", type: "button" },
|
||||||
[_("Cancel")],
|
[_("Cancel")],
|
||||||
);
|
);
|
||||||
cancel.addEventListener("click", () => this.closeSearch());
|
cancel.addEventListener("click", () => this.closePalette());
|
||||||
const panel = E(
|
const panel = E(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
id: "header-search-panel",
|
id: "cmdk-panel",
|
||||||
class: "header-search-panel",
|
class: "cmdk-panel",
|
||||||
role: "dialog",
|
role: "dialog",
|
||||||
"aria-modal": "true",
|
"aria-modal": "true",
|
||||||
"aria-label": _("Navigation"),
|
"aria-label": _("Navigation"),
|
||||||
hidden: "",
|
hidden: "",
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
E("div", { class: "header-search-box" }, [input, cancel]),
|
E("div", { class: "cmdk-inputrow" }, [input, cancel]),
|
||||||
results,
|
results,
|
||||||
E("div", { class: "header-search-hint" }, [
|
E("div", { class: "cmdk-footer" }, [
|
||||||
E("kbd", {}, ["↑↓"]),
|
E("kbd", {}, ["↑↓"]),
|
||||||
E("kbd", {}, ["↵"]),
|
E("kbd", {}, ["↵"]),
|
||||||
|
E("kbd", {}, [">"]),
|
||||||
E("kbd", {}, ["esc"]),
|
E("kbd", {}, ["esc"]),
|
||||||
E("span", { class: "header-search-hint-key" }, [
|
E("span", { class: "cmdk-hint-close" }, [
|
||||||
E("kbd", {}, [this.searchKey]),
|
E("kbd", {}, [this.paletteKey]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
input.addEventListener("input", () =>
|
input.addEventListener("input", () =>
|
||||||
this.renderSearchResults(input.value),
|
this.renderPaletteResults(input.value),
|
||||||
);
|
);
|
||||||
input.addEventListener("keydown", (e) => {
|
input.addEventListener("keydown", (e) => {
|
||||||
// Mid-composition these keys belong to the IME: Enter commits the
|
// Mid-composition these keys belong to the IME: Enter commits the
|
||||||
@@ -616,7 +656,7 @@ return baseclass.extend({
|
|||||||
if (e.isComposing || e.keyCode === 229) return;
|
if (e.isComposing || e.keyCode === 229) return;
|
||||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.moveSearchSelection(e.key === "ArrowDown" ? 1 : -1);
|
this.movePaletteSelection(e.key === "ArrowDown" ? 1 : -1);
|
||||||
} else if (e.key === "Enter") {
|
} else if (e.key === "Enter") {
|
||||||
results.querySelector(".is-selected")?.click();
|
results.querySelector(".is-selected")?.click();
|
||||||
}
|
}
|
||||||
@@ -625,9 +665,24 @@ return baseclass.extend({
|
|||||||
// stationary pointer, which fires mouseover and would snap the
|
// stationary pointer, which fires mouseover and would snap the
|
||||||
// selection back to whatever the mouse happens to rest on.
|
// selection back to whatever the mouse happens to rest on.
|
||||||
results.addEventListener("mousemove", (e) => {
|
results.addEventListener("mousemove", (e) => {
|
||||||
const row = e.target?.closest?.(".header-search-result");
|
const row = e.target?.closest?.(".cmdk-row");
|
||||||
if (row && !row.classList.contains("is-selected"))
|
if (row && !row.classList.contains("is-selected"))
|
||||||
this.setSearchSelection(row);
|
this.setPaletteSelection(row);
|
||||||
|
});
|
||||||
|
// Theme rows execute instead of navigate: hand the mode to header.ut's
|
||||||
|
// global setTheme/syncSwitchers, keep the panel open so the switch
|
||||||
|
// previews live, and re-render so the ✓ follows. Refocus the input —
|
||||||
|
// the clicked anchor is replaced by the re-render, which would strand
|
||||||
|
// focus on <body> with the dialog still open.
|
||||||
|
results.addEventListener("click", (e) => {
|
||||||
|
const mode = e.target?.closest?.(".cmdk-row")?.dataset.mode;
|
||||||
|
if (!mode) return;
|
||||||
|
e.preventDefault();
|
||||||
|
setTheme(mode);
|
||||||
|
syncSwitchers();
|
||||||
|
this.renderPaletteResults(input.value);
|
||||||
|
this.setPaletteSelection(results.querySelector(`[data-mode="${mode}"]`));
|
||||||
|
input.focus();
|
||||||
});
|
});
|
||||||
// The dialog is modal (full-screen takeover on mobile): keep Tab
|
// The dialog is modal (full-screen takeover on mobile): keep Tab
|
||||||
// cycling within it instead of escaping onto the page beneath.
|
// cycling within it instead of escaping onto the page beneath.
|
||||||
@@ -646,73 +701,176 @@ return baseclass.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.body.appendChild(panel);
|
document.body.appendChild(panel);
|
||||||
this.searchToggle.setAttribute("aria-controls", panel.id);
|
this.paletteTrigger.setAttribute("aria-controls", panel.id);
|
||||||
this.searchPanel = panel;
|
this.palettePanel = panel;
|
||||||
this.searchInput = input;
|
this.paletteInput = input;
|
||||||
this.searchResults = results;
|
this.paletteList = results;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSearchResults(value) {
|
// Greedy left-to-right subsequence scorer: adjacency runs and word starts
|
||||||
const q = value.trim().toLowerCase();
|
// score up, longer targets score down. Retried from every occurrence of
|
||||||
this.searchResults.replaceChildren();
|
// the query's first character — a single greedy pass would anchor "dns"
|
||||||
if (!q) return; // Spotlight manner: quiet until typed.
|
// to the first d of "Dhcp and dNS" and scatter, scoring it below shorter
|
||||||
|
// near-misses like "Diagnostics". Query arrives lowercased; spaces only
|
||||||
|
// reset the adjacency run.
|
||||||
|
fuzzyMatch(q, text) {
|
||||||
|
const low = String(text).toLowerCase();
|
||||||
|
let best = null;
|
||||||
|
|
||||||
const matches = this.searchIndex.filter(
|
for (
|
||||||
(page) =>
|
let from = low.indexOf(q[0]);
|
||||||
page.title.toLowerCase().includes(q) ||
|
from >= 0;
|
||||||
page.name.toLowerCase().includes(q) ||
|
from = low.indexOf(q[0], from + 1)
|
||||||
page.group?.toLowerCase().includes(q),
|
) {
|
||||||
);
|
let ti = from;
|
||||||
|
let score = 0;
|
||||||
|
let run = 0;
|
||||||
|
const ranges = [];
|
||||||
|
|
||||||
|
for (const c of q) {
|
||||||
|
if (c === " ") {
|
||||||
|
run = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const at = low.indexOf(c, ti);
|
||||||
|
if (at < 0) {
|
||||||
|
ranges.length = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
run = at === ti && ranges.length ? run + 1 : 1;
|
||||||
|
score +=
|
||||||
|
1 +
|
||||||
|
run +
|
||||||
|
(at === ti && ranges.length ? 4 : 0) +
|
||||||
|
(at === 0 || low[at - 1] === " " || low[at - 1] === "/" ? 3 : 0);
|
||||||
|
|
||||||
|
// c is a code point, so it can be two UTF-16 units wide (emoji, CJK
|
||||||
|
// Ext-B); advancing by c.length keeps the ranges — which index the
|
||||||
|
// original string — from slicing a surrogate pair in half.
|
||||||
|
const end = at + c.length;
|
||||||
|
const last = ranges[ranges.length - 1];
|
||||||
|
if (last && last[1] === at) last[1] = end;
|
||||||
|
else ranges.push([at, end]);
|
||||||
|
ti = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
score -= low.length * 0.02;
|
||||||
|
if (ranges.length && (!best || score > best.score))
|
||||||
|
best = { score, ranges };
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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 };
|
||||||
|
|
||||||
|
const rest =
|
||||||
|
this.fuzzyMatch(q, page.name) ||
|
||||||
|
(page.group ? this.fuzzyMatch(q, page.group) : null);
|
||||||
|
return rest && { score: rest.score, ranges: null };
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPaletteResults(value) {
|
||||||
|
let q = value.trim().toLowerCase();
|
||||||
|
// A leading ">" scopes the list to the command rows (the prototype's
|
||||||
|
// command-only mode); ">" covers CJK IMEs emitting the full-width form.
|
||||||
|
const cmdOnly = q[0] === ">" || q[0] === ">";
|
||||||
|
if (cmdOnly) q = q.slice(1).trim();
|
||||||
|
const theme = localStorage.getItem("aurora.theme") || "device";
|
||||||
|
const matches = [];
|
||||||
|
|
||||||
|
for (const page of this.paletteIndex) {
|
||||||
|
if (cmdOnly && !page.mode) continue;
|
||||||
|
const m = this.matchPaletteEntry(q, page);
|
||||||
|
if (m) matches.push({ page, score: m.score, ranges: m.ranges });
|
||||||
|
}
|
||||||
|
// sort() is spec-stable: equal scores keep menu order (and the browse
|
||||||
|
// list stays in menu order by skipping the sort entirely).
|
||||||
|
if (q) matches.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
this.paletteInput.removeAttribute("aria-activedescendant");
|
||||||
|
this.paletteList.replaceChildren();
|
||||||
|
|
||||||
if (!matches.length) {
|
if (!matches.length) {
|
||||||
this.searchResults.appendChild(
|
this.paletteList.appendChild(
|
||||||
E("div", { class: "header-search-empty" }, [_("No entries available")]),
|
E("div", { class: "cmdk-empty" }, [_("No entries available")]),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
matches.forEach((page, i) => {
|
matches.forEach(({ page, ranges }, i) => {
|
||||||
this.searchResults.appendChild(
|
const current = page.mode && page.mode === theme;
|
||||||
E(
|
const attributes = {
|
||||||
"a",
|
class: "cmdk-row",
|
||||||
{
|
id: `cmdk-option-${i}`,
|
||||||
class: `header-search-result${i ? "" : " is-selected"}`,
|
role: "option",
|
||||||
href: page.href,
|
"aria-selected": "false",
|
||||||
},
|
href: page.href || "#",
|
||||||
[
|
};
|
||||||
E(
|
if (page.mode) attributes["data-mode"] = page.mode;
|
||||||
"span",
|
if (current) attributes["aria-current"] = "true";
|
||||||
{ class: "result-title" },
|
|
||||||
this.highlightSearchMatch(page.title, q),
|
this.paletteList.appendChild(
|
||||||
),
|
E("a", attributes, [
|
||||||
page.group ? E("span", { class: "result-group" }, [page.group]) : "",
|
E(
|
||||||
],
|
"span",
|
||||||
),
|
{ class: "cmdk-title" },
|
||||||
|
this.highlightPaletteMatch(page.title, ranges),
|
||||||
|
),
|
||||||
|
current
|
||||||
|
? // The ✓ is decorative (aria-current carries the state); mark
|
||||||
|
// reuses the highlight colour without any new CSS.
|
||||||
|
E("span", { class: "cmdk-group-name", "aria-hidden": "true" }, [
|
||||||
|
E("mark", {}, ["✓"]),
|
||||||
|
])
|
||||||
|
: page.group
|
||||||
|
? E("span", { class: "cmdk-group-name" }, [page.group])
|
||||||
|
: "",
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setPaletteSelection(this.paletteList.firstChild);
|
||||||
},
|
},
|
||||||
|
|
||||||
highlightSearchMatch(title, q) {
|
highlightPaletteMatch(title, ranges) {
|
||||||
const at = title.toLowerCase().indexOf(q);
|
// Ranges were measured on the lowercased copy; case folding can change
|
||||||
if (at < 0) return [title];
|
// string length ("İ" → "i̇"), skewing offsets into the original — skip
|
||||||
|
// highlighting rather than mis-slice.
|
||||||
|
if (!ranges || title.toLowerCase().length !== title.length) return [title];
|
||||||
|
|
||||||
return [
|
const parts = [];
|
||||||
title.slice(0, at),
|
let last = 0;
|
||||||
E("mark", {}, [title.slice(at, at + q.length)]),
|
ranges.forEach(([from, to]) => {
|
||||||
title.slice(at + q.length),
|
parts.push(
|
||||||
];
|
title.slice(last, from),
|
||||||
|
E("mark", {}, [title.slice(from, to)]),
|
||||||
|
);
|
||||||
|
last = to;
|
||||||
|
});
|
||||||
|
parts.push(title.slice(last));
|
||||||
|
return parts;
|
||||||
},
|
},
|
||||||
|
|
||||||
setSearchSelection(row) {
|
setPaletteSelection(row) {
|
||||||
this.searchResults
|
const prev = this.paletteList.querySelector(".is-selected");
|
||||||
.querySelector(".is-selected")
|
prev?.classList.remove("is-selected");
|
||||||
?.classList.remove("is-selected");
|
prev?.setAttribute("aria-selected", "false");
|
||||||
row.classList.add("is-selected");
|
row.classList.add("is-selected");
|
||||||
|
row.setAttribute("aria-selected", "true");
|
||||||
|
this.paletteInput.setAttribute("aria-activedescendant", row.id);
|
||||||
},
|
},
|
||||||
|
|
||||||
moveSearchSelection(delta) {
|
movePaletteSelection(delta) {
|
||||||
const rows = [
|
const rows = [
|
||||||
...this.searchResults.querySelectorAll(".header-search-result"),
|
...this.paletteList.querySelectorAll(".cmdk-row"),
|
||||||
];
|
];
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
|
|
||||||
@@ -721,33 +879,33 @@ return baseclass.extend({
|
|||||||
);
|
);
|
||||||
const next = rows[(current + delta + rows.length) % rows.length];
|
const next = rows[(current + delta + rows.length) % rows.length];
|
||||||
|
|
||||||
this.setSearchSelection(next);
|
this.setPaletteSelection(next);
|
||||||
next.scrollIntoView({ block: "nearest" });
|
next.scrollIntoView({ block: "nearest" });
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSearch() {
|
togglePalette() {
|
||||||
if (!this.searchPanel) this.buildSearchPanel();
|
if (!this.palettePanel) this.buildPalette();
|
||||||
|
|
||||||
if (this.searchPanel.hidden) this.openSearch();
|
if (this.palettePanel.hidden) this.openPalette();
|
||||||
else this.closeSearch();
|
else this.closePalette();
|
||||||
},
|
},
|
||||||
|
|
||||||
openSearch() {
|
openPalette() {
|
||||||
this.searchReturnFocus = document.activeElement;
|
this.paletteReturnFocus = document.activeElement;
|
||||||
this.searchToggle.setAttribute("aria-expanded", "true");
|
this.paletteTrigger.setAttribute("aria-expanded", "true");
|
||||||
this.searchPanel.hidden = false;
|
this.palettePanel.hidden = false;
|
||||||
this.searchInput.value = "";
|
this.paletteInput.value = "";
|
||||||
this.searchResults.replaceChildren();
|
this.renderPaletteResults("");
|
||||||
this.searchInput.focus();
|
this.paletteInput.focus();
|
||||||
document.addEventListener("pointerdown", this.onSearchAway);
|
document.addEventListener("pointerdown", this.onPaletteAway);
|
||||||
},
|
},
|
||||||
|
|
||||||
closeSearch() {
|
closePalette() {
|
||||||
this.searchToggle.setAttribute("aria-expanded", "false");
|
this.paletteTrigger.setAttribute("aria-expanded", "false");
|
||||||
this.searchPanel.hidden = true;
|
this.palettePanel.hidden = true;
|
||||||
document.removeEventListener("pointerdown", this.onSearchAway);
|
document.removeEventListener("pointerdown", this.onPaletteAway);
|
||||||
if (this.searchReturnFocus?.isConnected) this.searchReturnFocus.focus();
|
if (this.paletteReturnFocus?.isConnected) this.paletteReturnFocus.focus();
|
||||||
this.searchReturnFocus = null;
|
this.paletteReturnFocus = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Shared scaffolding for the two desktop dropdown modes (mega-menu and
|
// Shared scaffolding for the two desktop dropdown modes (mega-menu and
|
||||||
@@ -1152,7 +1310,7 @@ return baseclass.extend({
|
|||||||
);
|
);
|
||||||
this.renderMainMenu(activeChild, activeChild.name, 0, navigationItems);
|
this.renderMainMenu(activeChild, activeChild.name, 0, navigationItems);
|
||||||
this.renderMobileMenu(navigationItems);
|
this.renderMobileMenu(navigationItems);
|
||||||
this.initSearch(navigationItems);
|
this.initPalette(navigationItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ul?.children.length > 1) {
|
if (ul?.children.length > 1) {
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
const source = await readFile(
|
||||||
|
new URL("../src/resource/menu-aurora.js", import.meta.url),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const E = (tagName, attributes, children) => ({
|
||||||
|
tagName,
|
||||||
|
attributes,
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadMenuModule = () => {
|
||||||
|
const baseclass = {
|
||||||
|
extend(module) {
|
||||||
|
return module;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const ui = { menu: { getChildren: () => [] } };
|
||||||
|
const L = {
|
||||||
|
env: { dispatchpath: [], requestpath: [] },
|
||||||
|
url: (...segments) => `/${segments.join("/")}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Function(
|
||||||
|
"baseclass",
|
||||||
|
"ui",
|
||||||
|
"E",
|
||||||
|
"L",
|
||||||
|
"_",
|
||||||
|
"document",
|
||||||
|
"window",
|
||||||
|
"localStorage",
|
||||||
|
source,
|
||||||
|
)(baseclass, ui, E, L, (value) => value, {}, {}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const menu = loadMenuModule();
|
||||||
|
|
||||||
|
const score = (query, text) => menu.fuzzyMatch(query, text)?.score ?? null;
|
||||||
|
|
||||||
|
const sliced = (text, ranges) =>
|
||||||
|
ranges.map(([from, to]) => text.slice(from, to));
|
||||||
|
|
||||||
|
test("matches a scattered subsequence and reports its ranges", () => {
|
||||||
|
const match = menu.fuzzyMatch("dns", "DHCP and DNS");
|
||||||
|
|
||||||
|
assert.ok(match);
|
||||||
|
assert.deepEqual(sliced("DHCP and DNS", match.ranges), ["DNS"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns null when the query is not a subsequence", () => {
|
||||||
|
assert.equal(menu.fuzzyMatch("zzz", "Firewall"), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("retries from every first-character occurrence instead of staying greedy", () => {
|
||||||
|
// A single greedy pass anchors "dns" to the leading D of "DHCP and DNS" and
|
||||||
|
// scatters, scoring the real hit below shorter near-misses.
|
||||||
|
assert.ok(score("dns", "DHCP and DNS") > score("dns", "Diagnostics"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("scores an adjacency run above the same characters scattered", () => {
|
||||||
|
assert.ok(score("fir", "Firewall") > score("fir", "Failover in Routing"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rewards word starts", () => {
|
||||||
|
assert.ok(score("s", "Status") > score("s", "Wireless"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("penalises longer targets so the tighter title wins", () => {
|
||||||
|
assert.ok(score("log", "Logout") > score("log", "Logging and reporting"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("matches CJK titles", () => {
|
||||||
|
const match = menu.fuzzyMatch("防火墙", "防火墙设置");
|
||||||
|
|
||||||
|
assert.ok(match);
|
||||||
|
assert.deepEqual(sliced("防火墙设置", match.ranges), ["防火墙"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("keeps astral characters whole in the reported ranges", () => {
|
||||||
|
// `at` is a UTF-16 offset while for...of yields code points: advancing by 1
|
||||||
|
// would end the range inside the surrogate pair and slice it into mojibake.
|
||||||
|
const title = "🚀 Passwall";
|
||||||
|
const match = menu.fuzzyMatch("🚀", title);
|
||||||
|
|
||||||
|
assert.ok(match);
|
||||||
|
assert.deepEqual(match.ranges, [[0, 2]]);
|
||||||
|
assert.deepEqual(sliced(title, match.ranges), ["🚀"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("merges adjacent astral matches into one range", () => {
|
||||||
|
const title = "𠀀𠀁 Extension";
|
||||||
|
const match = menu.fuzzyMatch("𠀀𠀁", title);
|
||||||
|
|
||||||
|
assert.ok(match);
|
||||||
|
assert.deepEqual(match.ranges, [[0, 4]]);
|
||||||
|
assert.deepEqual(sliced(title, match.ranges), ["𠀀𠀁"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("an empty query matches every entry unranked and unhighlighted", () => {
|
||||||
|
assert.deepEqual(menu.matchPaletteEntry("", { title: "Status" }), {
|
||||||
|
score: 0,
|
||||||
|
ranges: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("title hits outrank name hits and are the only highlighted ones", () => {
|
||||||
|
const title = menu.matchPaletteEntry("dns", {
|
||||||
|
title: "DNS",
|
||||||
|
name: "dns",
|
||||||
|
group: "Network",
|
||||||
|
});
|
||||||
|
const viaName = menu.matchPaletteEntry("dhcp", {
|
||||||
|
title: "DNS",
|
||||||
|
name: "dhcp",
|
||||||
|
group: "Network",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(title.ranges);
|
||||||
|
assert.equal(viaName.ranges, null);
|
||||||
|
assert.ok(title.score > viaName.score);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("falls back to the group when neither title nor name matches", () => {
|
||||||
|
const match = menu.matchPaletteEntry("netw", {
|
||||||
|
title: "DNS",
|
||||||
|
name: "dns",
|
||||||
|
group: "Network",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(match);
|
||||||
|
assert.equal(match.ranges, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns falsy when nothing on the entry matches", () => {
|
||||||
|
assert.ok(
|
||||||
|
!menu.matchPaletteEntry("zzz", {
|
||||||
|
title: "DNS",
|
||||||
|
name: "dns",
|
||||||
|
group: "Network",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("wraps matched ranges in <mark> and keeps the surrounding text", () => {
|
||||||
|
const parts = menu.highlightPaletteMatch("DHCP and DNS", [[9, 12]]);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
parts.map((part) => (typeof part === "string" ? part : part.children[0])),
|
||||||
|
["DHCP and ", "DNS", ""],
|
||||||
|
);
|
||||||
|
assert.equal(parts[1].tagName, "mark");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("highlights astral characters without splitting the surrogate pair", () => {
|
||||||
|
const parts = menu.highlightPaletteMatch("🚀 Passwall", [[0, 2]]);
|
||||||
|
|
||||||
|
assert.equal(parts[1].children[0], "🚀");
|
||||||
|
assert.ok(!parts.some((part) => String(part).includes("�")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("skips highlighting when case folding would skew the offsets", () => {
|
||||||
|
// "İ".toLowerCase() is two code units, so ranges measured on the lowercased
|
||||||
|
// copy no longer index the original.
|
||||||
|
assert.deepEqual(menu.highlightPaletteMatch("İnterface", [[0, 1]]), [
|
||||||
|
"İnterface",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders the plain title when there are no ranges", () => {
|
||||||
|
assert.deepEqual(menu.highlightPaletteMatch("Status", null), ["Status"]);
|
||||||
|
});
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import browserslist from "browserslist";
|
import browserslist from "browserslist";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import { existsSync, readdirSync } from "fs";
|
import { existsSync, readdirSync, statSync } from "fs";
|
||||||
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
||||||
import { browserslistToTargets } from "lightningcss";
|
import { browserslistToTargets } from "lightningcss";
|
||||||
import { basename, dirname, join, relative, resolve } from "path";
|
import { basename, dirname, join, relative, resolve, sep } from "path";
|
||||||
import { minify as terserMinify } from "terser";
|
import { minify as terserMinify } from "terser";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import {
|
import {
|
||||||
@@ -73,6 +73,30 @@ function createLuciJsCompressPlugin(): Plugin {
|
|||||||
const PATCH_PUBLIC_PREFIX = "/luci-static/aurora/patches/";
|
const PATCH_PUBLIC_PREFIX = "/luci-static/aurora/patches/";
|
||||||
const PATCH_SRC_DIR = resolve(CURRENT_DIR, "src/media/patches");
|
const PATCH_SRC_DIR = resolve(CURRENT_DIR, "src/media/patches");
|
||||||
|
|
||||||
|
// Theme assets (public/aurora/**: images, fonts): serve the copy in this
|
||||||
|
// checkout at /luci-static/aurora/<path>. Without this, header.ut's logo,
|
||||||
|
// favicon, webmanifest and font references fall through to the OpenWrt proxy
|
||||||
|
// and resolve against the *installed* package, so an icon or font added here
|
||||||
|
// 404s until the package is rebuilt and reinstalled. Only files that exist
|
||||||
|
// locally are rewritten — everything else still proxies through.
|
||||||
|
const ASSET_PUBLIC_PREFIX = "/luci-static/aurora/";
|
||||||
|
const ASSET_SRC_DIR = resolve(CURRENT_DIR, "public/aurora");
|
||||||
|
|
||||||
|
// Resolves a request path under public/aurora/, or null when it escapes the
|
||||||
|
// directory (../) or names something that is not a file there.
|
||||||
|
function localAsset(relPath: string): string | null {
|
||||||
|
let decoded: string;
|
||||||
|
try {
|
||||||
|
decoded = decodeURIComponent(relPath);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const file = resolve(ASSET_SRC_DIR, decoded);
|
||||||
|
if (!file.startsWith(ASSET_SRC_DIR + sep)) return null;
|
||||||
|
|
||||||
|
return existsSync(file) && statSync(file).isFile() ? decoded : null;
|
||||||
|
}
|
||||||
|
|
||||||
function createLocalServePlugin(): Plugin {
|
function createLocalServePlugin(): Plugin {
|
||||||
const cssRoutes: Record<string, string> = {
|
const cssRoutes: Record<string, string> = {
|
||||||
"/luci-static/aurora/main.css": "/src/media/main.css",
|
"/luci-static/aurora/main.css": "/src/media/main.css",
|
||||||
@@ -118,6 +142,13 @@ function createLocalServePlugin(): Plugin {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (pathname.startsWith(ASSET_PUBLIC_PREFIX)) {
|
||||||
|
const asset = localAsset(pathname.slice(ASSET_PUBLIC_PREFIX.length));
|
||||||
|
if (asset) {
|
||||||
|
req.url = `/aurora/${asset}` + (search ? `?${search}` : "");
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
const jsPath = jsRoutes[pathname];
|
const jsPath = jsRoutes[pathname];
|
||||||
if (jsPath) {
|
if (jsPath) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
|||||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||||
LUCI_DEPENDS:=+luci-base
|
LUCI_DEPENDS:=+luci-base
|
||||||
|
|
||||||
PKG_VERSION:=1.1.0
|
PKG_VERSION:=1.1.1
|
||||||
PKG_RELEASE:=50
|
PKG_RELEASE:=51
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
|
|
||||||
LUCI_MINIFY_CSS:=
|
LUCI_MINIFY_CSS:=
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
/*! 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--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-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--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-duration:initial;--tw-content:"";--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-tracking:initial}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--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);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--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);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@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-spacinLine 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--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-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--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-duration:initial;--tw-content:"";--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-tracking:initial}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--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);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--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);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@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-spacinLine truncated
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
/*! 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--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-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--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-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-scrollbar-thumb:#0000;--tw-scrollbar-track:#0000;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y: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;--radius-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--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-lg:16px;--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);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@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-mLine 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--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-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--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-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-scrollbar-thumb:#0000;--tw-scrollbar-track:#0000;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y: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;--radius-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--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-lg:16px;--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);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@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-mLine truncated
|
||||||
@@ -1 +1 @@
|
|||||||
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const mobileList=overlay.querySelector("#mobile-nav-list");const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const updateToggleState=expanded=>{const desktopSidebar=isDesktopSidebar();navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded?"true":"false");navigationToggle.setAttribute("aria-controls",desktopSidebar?"sidebar-panel":"mobile-menu-overlay");const label=expanded&&!desktopSidebar?_("Close"):_("Navigation");navigationToggle.setAttribute("title",label);navigationToggle.setAttribute("aria-label",label)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.classList.remove("mobile-navigation-open");document.body.style.overflow="";this.resetNavigationGroups(mobileList)};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.classList.toggle("mobile-navigation-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)this.expandActiveNavigationGroup(mobileList);else this.resetNavigationGroups(mobileList);updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const destination=e.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");if(destination&&overlay.contains(destination)){setNavigationExpanded(false)}})},renderMobileMenu(items){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");if(list)list.innerHTML="";if(footerAction)footerAction.innerHTML="";if(!list)return;items.forEach(item=>{if(item.isLogout){if(footerAction){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:item.href},[item.title]))}return}list.appendChild(this.renderNavigationItem(item,"mobile"))});this.bindNavigationAccordion(list)},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,level=0){const container=document.querySelector("#tabmenu");const ul=E("ul",{class:"tabs"});const children=ui.menu.getChildren(tree);let activeNode=null;children.forEach(child=>{const isActive=L.env.dispatchpath[3+level]===child.name;ul.appendChild(E("li",{class:`tabmenu-item-${child.name}${isActive?" active":""}`},[E("a",{href:L.url(url,child.name)},[_(child.title)])]));if(isActive)activeNode=child});if(!ul.children.length)return E([]);container.appendChild(ul);container.style.display="";if(activeNode){this.renderTabMenu(activeNode,`${url}/${activeNode.name}`,level+1)}return ul},renderMainMenu(tree,url,level=0,navigationItems=null){const ul=level?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu");const children=ui.menu.getChildren(tree);if(level>1)return E([]);if(level===0){const navType=document.body?.dataset?.navType||"mega-menu";if(navType==="sidebar"){this.renderSidebLine truncated
|
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const mobileList=overlay.querySelector("#mobile-nav-list");const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const updateToggleState=expanded=>{const desktopSidebar=isDesktopSidebar();navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded?"true":"false");navigationToggle.setAttribute("aria-controls",desktopSidebar?"sidebar-panel":"mobile-menu-overlay");const label=expanded&&!desktopSidebar?_("Close"):_("Navigation");navigationToggle.setAttribute("title",label);navigationToggle.setAttribute("aria-label",label)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.classList.remove("mobile-navigation-open");document.body.style.overflow="";this.resetNavigationGroups(mobileList)};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.classList.toggle("mobile-navigation-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)this.expandActiveNavigationGroup(mobileList);else this.resetNavigationGroups(mobileList);updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const destination=e.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");if(destination&&overlay.contains(destination)){setNavigationExpanded(false)}})},renderMobileMenu(items){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");if(list)list.innerHTML="";if(footerAction)footerAction.innerHTML="";if(!list)return;items.forEach(item=>{if(item.isLogout){if(footerAction){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:item.href},[item.title]))}return}list.appendChild(this.renderNavigationItem(item,"mobile"))});this.bindNavigationAccordion(list)},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,level=0){const container=document.querySelector("#tabmenu");const ul=E("ul",{class:"tabs"});const children=ui.menu.getChildren(tree);let activeNode=null;children.forEach(child=>{const isActive=L.env.dispatchpath[3+level]===child.name;ul.appendChild(E("li",{class:`tabmenu-item-${child.name}${isActive?" active":""}`},[E("a",{href:L.url(url,child.name)},[_(child.title)])]));if(isActive)activeNode=child});if(!ul.children.length)return E([]);container.appendChild(ul);container.style.display="";if(activeNode){this.renderTabMenu(activeNode,`${url}/${activeNode.name}`,level+1)}return ul},renderMainMenu(tree,url,level=0,navigationItems=null){const ul=level?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu");const children=ui.menu.getChildren(tree);if(level>1)return E([]);if(level===0){const navType=document.body?.dataset?.navType||"mega-menu";if(navType==="sidebar"){this.renderSidebLine truncated
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
{% if (nav_type == 'sidebar'): %}
|
{% if (nav_type == 'sidebar'): %}
|
||||||
<ol class="header-crumb" id="header-crumb"></ol>
|
<ol class="header-crumb" id="header-crumb"></ol>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="header-search-toggle" id="header-search-toggle" type="button" aria-label="{{ _('Navigation') }}"></button>
|
<button class="cmdk-trigger" id="cmdk-trigger" type="button" aria-label="{{ _('Navigation') }}"></button>
|
||||||
<div id="indicators" class="pull-right"></div>
|
<div id="indicators" class="pull-right"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user