mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 03:32:51 +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_VERSION:=0.6.3
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
LUCI_TITLE:=CPU utilization info for the LuCI status page
|
||||
LUCI_PKGARCH:=all
|
||||
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_VERSION:=26.7.16
|
||||
PKG_RELEASE:=190
|
||||
PKG_RELEASE:=191
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -590,6 +590,10 @@ if singbox_tags:find("with_utls") then
|
||||
o:depends({ [_n("reality")] = true })
|
||||
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:value("tcp", "TCP")
|
||||
o:value("http", "HTTP")
|
||||
|
||||
@@ -613,6 +613,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
idle_session_check_interval = "30s",
|
||||
idle_session_timeout = "30s",
|
||||
min_idle_session = 5,
|
||||
disable_reuse = (node.anytls_disable_reuse == "1") and true or nil,
|
||||
tls = tls
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1049,7 +1049,7 @@ function gen_config(var)
|
||||
|
||||
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
|
||||
local blc_node_id = blc_nodes[i]
|
||||
local blc_node_tag = "blc-" .. blc_node_id
|
||||
@@ -1069,14 +1069,14 @@ function gen_config(var)
|
||||
end
|
||||
-- Check if balancing node duplicates fallback node
|
||||
if _node.fallback_node == blc_node_id then
|
||||
valid_fallback = false
|
||||
_node.fallback_node = nil
|
||||
end
|
||||
end
|
||||
if #valid_nodes == 0 then return nil end
|
||||
|
||||
-- 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"
|
||||
if fallback_node_id and fallback_node_id ~= "_direct" then
|
||||
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 + 'reality', false);
|
||||
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'));
|
||||
if (queryParam.fp && queryParam.fp.trim() != "") {
|
||||
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 + 'reality', true);
|
||||
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() != "") {
|
||||
opt.set(dom_prefix + 'utls', true);
|
||||
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
||||
|
||||
@@ -2195,3 +2195,6 @@ msgstr "Gecko 包大小(最大)"
|
||||
|
||||
msgid "valid time (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"))
|
||||
end
|
||||
|
||||
if node["disable-reuse"] then table.insert(p, "disable_reuse=1") end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
@@ -1562,7 +1562,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
end
|
||||
if params.security == "tls" or params.security == "reality" then
|
||||
result.tls = "1"
|
||||
result.tls_serverName = params.sni
|
||||
result.tls_serverName = params.sni or params.peer
|
||||
result.alpn = params.alpn
|
||||
if params.fp and params.fp ~= "" then
|
||||
result.utls = "1"
|
||||
@@ -1580,6 +1580,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
end
|
||||
result.port = port
|
||||
result.tls_allowInsecure = params.allowinsecure or params.insecure
|
||||
result.anytls_disable_reuse = params.disable_reuse
|
||||
end
|
||||
elseif szType == 'naive+https' or szType == 'naive+quic' then
|
||||
if has_singbox then
|
||||
|
||||
+24
-19
@@ -1,13 +1,18 @@
|
||||
/**
|
||||
* Header route search — Spotlight-style palette, available in all three nav
|
||||
* types (sidebar / mega-menu / dropdown). The toggle sits at the header's
|
||||
* right edge next to #indicators; ⌘K / Ctrl+K toggles the panel globally.
|
||||
* Command palette (⌘K) — Spotlight-style, available in all three nav types
|
||||
* (sidebar / mega-menu / dropdown). The trigger sits at the header's right
|
||||
* 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 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;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* <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. */
|
||||
@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 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];
|
||||
}
|
||||
|
||||
& .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;
|
||||
}
|
||||
|
||||
& .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;
|
||||
}
|
||||
}
|
||||
|
||||
& .header-search-results {
|
||||
& .cmdk-list {
|
||||
@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;
|
||||
|
||||
& .header-search-result {
|
||||
& .cmdk-row {
|
||||
@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;
|
||||
}
|
||||
|
||||
& .result-group {
|
||||
& .cmdk-group-name {
|
||||
@apply text-text-subtle ml-auto shrink-0 text-xs;
|
||||
}
|
||||
|
||||
@@ -77,21 +82,21 @@
|
||||
&.is-selected {
|
||||
@apply bg-brand-subtle;
|
||||
|
||||
& .result-title {
|
||||
& .cmdk-title {
|
||||
@apply text-brand font-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .header-search-empty {
|
||||
& .cmdk-empty {
|
||||
@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;
|
||||
|
||||
& .header-search-hint-key {
|
||||
& .cmdk-hint-close {
|
||||
@apply ml-auto;
|
||||
}
|
||||
|
||||
@@ -103,13 +108,13 @@
|
||||
|
||||
/* The frost is decorative — both degradation paths go opaque. */
|
||||
@supports not (backdrop-filter: blur(1px)) {
|
||||
.header-search-panel {
|
||||
.cmdk-panel {
|
||||
@apply bg-surface;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.header-search-panel {
|
||||
.cmdk-panel {
|
||||
@apply bg-surface;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
@import "./components/_form.css";
|
||||
@import "./components/_segmented.css";
|
||||
@import "./components/_tab.css";
|
||||
@import "./components/_search.css";
|
||||
@import "./components/_palette.css";
|
||||
|
||||
@import "./_utilities.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
|
||||
// or the header toggle. The index 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 search is never
|
||||
// used pay nothing beyond this flat array.
|
||||
initSearch(items) {
|
||||
const toggle = document.querySelector("#header-search-toggle");
|
||||
if (!toggle || this.searchIndex) return;
|
||||
// Command palette (all nav types): a Spotlight-style panel on ⌘K / Ctrl+K
|
||||
// or the header trigger, holding routes and theme-mode commands. The index
|
||||
// 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) {
|
||||
const toggle = document.querySelector("#cmdk-trigger");
|
||||
if (!toggle || this.paletteIndex) return;
|
||||
|
||||
this.searchIndex = [];
|
||||
this.paletteIndex = [];
|
||||
items.forEach((item) => {
|
||||
if (item.isLogout) return;
|
||||
if (!item.hasChildren) {
|
||||
this.searchIndex.push({
|
||||
this.paletteIndex.push({
|
||||
title: item.title,
|
||||
name: item.name,
|
||||
group: null,
|
||||
@@ -517,97 +517,137 @@ return baseclass.extend({
|
||||
return;
|
||||
}
|
||||
item.pages.forEach((page) =>
|
||||
this.searchIndex.push({
|
||||
this.paletteIndex.push({
|
||||
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,
|
||||
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 —
|
||||
// the theme intentionally ships no translations of its own.
|
||||
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-expanded", "false");
|
||||
toggle.addEventListener("click", () => this.toggleSearch());
|
||||
this.searchToggle = toggle;
|
||||
toggle.addEventListener("click", () => this.togglePalette());
|
||||
this.paletteTrigger = toggle;
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
// An IME swallows these keys while composing (Esc cancels the
|
||||
// composition, not the panel); keyCode 229 covers engines that
|
||||
// don't set isComposing on the trailing keydown.
|
||||
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 (
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
(isMac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey) &&
|
||||
!e.altKey &&
|
||||
!e.shiftKey &&
|
||||
(e.key || "").toLowerCase() === "k"
|
||||
) {
|
||||
e.preventDefault();
|
||||
this.toggleSearch();
|
||||
this.togglePalette();
|
||||
} else if (
|
||||
e.key === "Escape" &&
|
||||
this.searchPanel &&
|
||||
!this.searchPanel.hidden
|
||||
this.palettePanel &&
|
||||
!this.palettePanel.hidden
|
||||
) {
|
||||
this.closeSearch();
|
||||
this.closePalette();
|
||||
}
|
||||
});
|
||||
|
||||
// Registered only while the panel is open (see openSearch/closeSearch).
|
||||
this.onSearchAway = (e) => {
|
||||
if (!this.searchPanel.contains(e.target) && !toggle.contains(e.target))
|
||||
this.closeSearch();
|
||||
// Registered only while the panel is open (see openPalette/closePalette).
|
||||
this.onPaletteAway = (e) => {
|
||||
if (!this.palettePanel.contains(e.target) && !toggle.contains(e.target))
|
||||
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", {
|
||||
class: "header-search-input",
|
||||
type: "text",
|
||||
class: "cmdk-input",
|
||||
type: "search",
|
||||
enterkeyhint: "go",
|
||||
placeholder: _("Type to filter…"),
|
||||
"aria-label": _("Type to filter…"),
|
||||
autocomplete: "off",
|
||||
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
|
||||
// and touch devices have no Escape) — hidden on md+ via CSS.
|
||||
const cancel = E(
|
||||
"button",
|
||||
{ class: "header-search-cancel", type: "button" },
|
||||
{ class: "cmdk-cancel", type: "button" },
|
||||
[_("Cancel")],
|
||||
);
|
||||
cancel.addEventListener("click", () => this.closeSearch());
|
||||
cancel.addEventListener("click", () => this.closePalette());
|
||||
const panel = E(
|
||||
"div",
|
||||
{
|
||||
id: "header-search-panel",
|
||||
class: "header-search-panel",
|
||||
id: "cmdk-panel",
|
||||
class: "cmdk-panel",
|
||||
role: "dialog",
|
||||
"aria-modal": "true",
|
||||
"aria-label": _("Navigation"),
|
||||
hidden: "",
|
||||
},
|
||||
[
|
||||
E("div", { class: "header-search-box" }, [input, cancel]),
|
||||
E("div", { class: "cmdk-inputrow" }, [input, cancel]),
|
||||
results,
|
||||
E("div", { class: "header-search-hint" }, [
|
||||
E("div", { class: "cmdk-footer" }, [
|
||||
E("kbd", {}, ["↑↓"]),
|
||||
E("kbd", {}, ["↵"]),
|
||||
E("kbd", {}, [">"]),
|
||||
E("kbd", {}, ["esc"]),
|
||||
E("span", { class: "header-search-hint-key" }, [
|
||||
E("kbd", {}, [this.searchKey]),
|
||||
E("span", { class: "cmdk-hint-close" }, [
|
||||
E("kbd", {}, [this.paletteKey]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
input.addEventListener("input", () =>
|
||||
this.renderSearchResults(input.value),
|
||||
this.renderPaletteResults(input.value),
|
||||
);
|
||||
input.addEventListener("keydown", (e) => {
|
||||
// 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.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
this.moveSearchSelection(e.key === "ArrowDown" ? 1 : -1);
|
||||
this.movePaletteSelection(e.key === "ArrowDown" ? 1 : -1);
|
||||
} else if (e.key === "Enter") {
|
||||
results.querySelector(".is-selected")?.click();
|
||||
}
|
||||
@@ -625,9 +665,24 @@ return baseclass.extend({
|
||||
// stationary pointer, which fires mouseover and would snap the
|
||||
// selection back to whatever the mouse happens to rest on.
|
||||
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"))
|
||||
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
|
||||
// cycling within it instead of escaping onto the page beneath.
|
||||
@@ -646,73 +701,176 @@ return baseclass.extend({
|
||||
});
|
||||
|
||||
document.body.appendChild(panel);
|
||||
this.searchToggle.setAttribute("aria-controls", panel.id);
|
||||
this.searchPanel = panel;
|
||||
this.searchInput = input;
|
||||
this.searchResults = results;
|
||||
this.paletteTrigger.setAttribute("aria-controls", panel.id);
|
||||
this.palettePanel = panel;
|
||||
this.paletteInput = input;
|
||||
this.paletteList = results;
|
||||
},
|
||||
|
||||
renderSearchResults(value) {
|
||||
const q = value.trim().toLowerCase();
|
||||
this.searchResults.replaceChildren();
|
||||
if (!q) return; // Spotlight manner: quiet until typed.
|
||||
// Greedy left-to-right subsequence scorer: adjacency runs and word starts
|
||||
// score up, longer targets score down. Retried from every occurrence of
|
||||
// the query's first character — a single greedy pass would anchor "dns"
|
||||
// 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(
|
||||
(page) =>
|
||||
page.title.toLowerCase().includes(q) ||
|
||||
page.name.toLowerCase().includes(q) ||
|
||||
page.group?.toLowerCase().includes(q),
|
||||
);
|
||||
for (
|
||||
let from = low.indexOf(q[0]);
|
||||
from >= 0;
|
||||
from = low.indexOf(q[0], from + 1)
|
||||
) {
|
||||
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) {
|
||||
this.searchResults.appendChild(
|
||||
E("div", { class: "header-search-empty" }, [_("No entries available")]),
|
||||
this.paletteList.appendChild(
|
||||
E("div", { class: "cmdk-empty" }, [_("No entries available")]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
matches.forEach((page, i) => {
|
||||
this.searchResults.appendChild(
|
||||
E(
|
||||
"a",
|
||||
{
|
||||
class: `header-search-result${i ? "" : " is-selected"}`,
|
||||
href: page.href,
|
||||
},
|
||||
[
|
||||
E(
|
||||
"span",
|
||||
{ class: "result-title" },
|
||||
this.highlightSearchMatch(page.title, q),
|
||||
),
|
||||
page.group ? E("span", { class: "result-group" }, [page.group]) : "",
|
||||
],
|
||||
),
|
||||
matches.forEach(({ page, ranges }, i) => {
|
||||
const current = page.mode && page.mode === theme;
|
||||
const attributes = {
|
||||
class: "cmdk-row",
|
||||
id: `cmdk-option-${i}`,
|
||||
role: "option",
|
||||
"aria-selected": "false",
|
||||
href: page.href || "#",
|
||||
};
|
||||
if (page.mode) attributes["data-mode"] = page.mode;
|
||||
if (current) attributes["aria-current"] = "true";
|
||||
|
||||
this.paletteList.appendChild(
|
||||
E("a", attributes, [
|
||||
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) {
|
||||
const at = title.toLowerCase().indexOf(q);
|
||||
if (at < 0) return [title];
|
||||
highlightPaletteMatch(title, ranges) {
|
||||
// Ranges were measured on the lowercased copy; case folding can change
|
||||
// string length ("İ" → "i̇"), skewing offsets into the original — skip
|
||||
// highlighting rather than mis-slice.
|
||||
if (!ranges || title.toLowerCase().length !== title.length) return [title];
|
||||
|
||||
return [
|
||||
title.slice(0, at),
|
||||
E("mark", {}, [title.slice(at, at + q.length)]),
|
||||
title.slice(at + q.length),
|
||||
];
|
||||
const parts = [];
|
||||
let last = 0;
|
||||
ranges.forEach(([from, to]) => {
|
||||
parts.push(
|
||||
title.slice(last, from),
|
||||
E("mark", {}, [title.slice(from, to)]),
|
||||
);
|
||||
last = to;
|
||||
});
|
||||
parts.push(title.slice(last));
|
||||
return parts;
|
||||
},
|
||||
|
||||
setSearchSelection(row) {
|
||||
this.searchResults
|
||||
.querySelector(".is-selected")
|
||||
?.classList.remove("is-selected");
|
||||
setPaletteSelection(row) {
|
||||
const prev = this.paletteList.querySelector(".is-selected");
|
||||
prev?.classList.remove("is-selected");
|
||||
prev?.setAttribute("aria-selected", "false");
|
||||
row.classList.add("is-selected");
|
||||
row.setAttribute("aria-selected", "true");
|
||||
this.paletteInput.setAttribute("aria-activedescendant", row.id);
|
||||
},
|
||||
|
||||
moveSearchSelection(delta) {
|
||||
movePaletteSelection(delta) {
|
||||
const rows = [
|
||||
...this.searchResults.querySelectorAll(".header-search-result"),
|
||||
...this.paletteList.querySelectorAll(".cmdk-row"),
|
||||
];
|
||||
if (!rows.length) return;
|
||||
|
||||
@@ -721,33 +879,33 @@ return baseclass.extend({
|
||||
);
|
||||
const next = rows[(current + delta + rows.length) % rows.length];
|
||||
|
||||
this.setSearchSelection(next);
|
||||
this.setPaletteSelection(next);
|
||||
next.scrollIntoView({ block: "nearest" });
|
||||
},
|
||||
|
||||
toggleSearch() {
|
||||
if (!this.searchPanel) this.buildSearchPanel();
|
||||
togglePalette() {
|
||||
if (!this.palettePanel) this.buildPalette();
|
||||
|
||||
if (this.searchPanel.hidden) this.openSearch();
|
||||
else this.closeSearch();
|
||||
if (this.palettePanel.hidden) this.openPalette();
|
||||
else this.closePalette();
|
||||
},
|
||||
|
||||
openSearch() {
|
||||
this.searchReturnFocus = document.activeElement;
|
||||
this.searchToggle.setAttribute("aria-expanded", "true");
|
||||
this.searchPanel.hidden = false;
|
||||
this.searchInput.value = "";
|
||||
this.searchResults.replaceChildren();
|
||||
this.searchInput.focus();
|
||||
document.addEventListener("pointerdown", this.onSearchAway);
|
||||
openPalette() {
|
||||
this.paletteReturnFocus = document.activeElement;
|
||||
this.paletteTrigger.setAttribute("aria-expanded", "true");
|
||||
this.palettePanel.hidden = false;
|
||||
this.paletteInput.value = "";
|
||||
this.renderPaletteResults("");
|
||||
this.paletteInput.focus();
|
||||
document.addEventListener("pointerdown", this.onPaletteAway);
|
||||
},
|
||||
|
||||
closeSearch() {
|
||||
this.searchToggle.setAttribute("aria-expanded", "false");
|
||||
this.searchPanel.hidden = true;
|
||||
document.removeEventListener("pointerdown", this.onSearchAway);
|
||||
if (this.searchReturnFocus?.isConnected) this.searchReturnFocus.focus();
|
||||
this.searchReturnFocus = null;
|
||||
closePalette() {
|
||||
this.paletteTrigger.setAttribute("aria-expanded", "false");
|
||||
this.palettePanel.hidden = true;
|
||||
document.removeEventListener("pointerdown", this.onPaletteAway);
|
||||
if (this.paletteReturnFocus?.isConnected) this.paletteReturnFocus.focus();
|
||||
this.paletteReturnFocus = null;
|
||||
},
|
||||
|
||||
// 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.renderMobileMenu(navigationItems);
|
||||
this.initSearch(navigationItems);
|
||||
this.initPalette(navigationItems);
|
||||
}
|
||||
|
||||
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 browserslist from "browserslist";
|
||||
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 { 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 { promisify } from "util";
|
||||
import {
|
||||
@@ -73,6 +73,30 @@ function createLuciJsCompressPlugin(): Plugin {
|
||||
const PATCH_PUBLIC_PREFIX = "/luci-static/aurora/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 {
|
||||
const cssRoutes: Record<string, string> = {
|
||||
"/luci-static/aurora/main.css": "/src/media/main.css",
|
||||
@@ -118,6 +142,13 @@ function createLocalServePlugin(): Plugin {
|
||||
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];
|
||||
if (jsPath) {
|
||||
try {
|
||||
|
||||
@@ -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.1.0
|
||||
PKG_RELEASE:=50
|
||||
PKG_VERSION:=1.1.1
|
||||
PKG_RELEASE:=51
|
||||
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-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 */
|
||||
@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'): %}
|
||||
<ol class="header-crumb" id="header-crumb"></ol>
|
||||
{% 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>
|
||||
|
||||
|
||||
Reference in new issue
Block a user