mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 20:42:52 +08:00
⛄ Sync 2026-07-11 20:20:47
This commit is contained in:
1 parent
7bc9b61a62
commit
40c9b82c9c
15 files changed
+521
-40
No files matched your search
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.7.1
|
||||
PKG_RELEASE:=176
|
||||
PKG_RELEASE:=177
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -514,6 +514,7 @@ o:value("ios")
|
||||
o:value("android")
|
||||
o:value("random")
|
||||
o:value("randomized")
|
||||
o:value("unsafe")
|
||||
o.default = "chrome"
|
||||
o:depends({ [_n("tls")] = true, [_n("utls")] = true })
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = true })
|
||||
|
||||
@@ -14,6 +14,8 @@ local GLOBAL = {
|
||||
|
||||
local xray_version = api.get_app_version("xray")
|
||||
|
||||
local xray_min_version = "26.3.27"
|
||||
|
||||
local function get_domain_excluded()
|
||||
local path = string.format("/usr/share/%s/rules/domains_excluded", appname)
|
||||
local content = fs.readfile(path)
|
||||
@@ -789,7 +791,10 @@ function gen_config_server(node)
|
||||
},
|
||||
-- 传出连接
|
||||
outbounds = outbounds,
|
||||
routing = routing
|
||||
routing = routing,
|
||||
version = {
|
||||
min = xray_min_version
|
||||
}
|
||||
}
|
||||
|
||||
local alpn = {}
|
||||
@@ -1935,6 +1940,10 @@ function gen_config(var)
|
||||
|
||||
if inbounds or outbounds then
|
||||
local config = {
|
||||
env = (function()
|
||||
local asset_location = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
|
||||
return { XRAY_LOCATION_ASSET = asset_location }
|
||||
end)(),
|
||||
log = {
|
||||
-- error = string.format("/tmp/etc/%s/%s.log", appname, node[".name"]),
|
||||
loglevel = loglevel
|
||||
@@ -1968,6 +1977,9 @@ function gen_config(var)
|
||||
-- statsInboundUplink = false,
|
||||
-- statsInboundDownlink = false
|
||||
-- }
|
||||
},
|
||||
version = {
|
||||
min = xray_min_version
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" stroke="#0F162B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 21l-6 -6" stroke="#0F162B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -1,45 +1,80 @@
|
||||
.floating-toolbar {
|
||||
@apply fixed right-4 bottom-4 z-40 flex flex-col items-center max-md:right-3 max-md:bottom-3;
|
||||
/* Detached-bubble speed dial: every shortcut is its own floating circle
|
||||
(elevation only, no border), collapsing drops the bubbles into the
|
||||
toggle one after another and the toggle's X rotates into a +.
|
||||
|
||||
All motion is translate/scale/opacity/rotate — compositor-only (R1).
|
||||
The stagger math runs on custom properties set by header.ut:
|
||||
--fab-n (on .toolbar-list) item count
|
||||
--fab-i (on each .toolbar-btn) index, 0 = top bubble
|
||||
--fab-icon (on each .toolbar-btn) configured icon URL, drawn via
|
||||
mask + bg-current so it follows the theme text color in both
|
||||
modes (replaces the old <img> + dark:invert).
|
||||
Travel per step = bubble 44px + 10px gap = 3.375rem (40px + 8px =
|
||||
3rem below md). Expanding pops bottom-first ("jump up"), collapsing
|
||||
drops top-first; --fab-d only ever delays the motion properties, so
|
||||
hover color feedback stays instant. */
|
||||
/* The stack keeps its layout height when collapsed (the bubbles must
|
||||
travel a real distance into the toggle), so the container itself must
|
||||
not catch clicks — only the buttons do. */
|
||||
@apply pointer-events-none fixed right-4 bottom-4 z-40 flex flex-col items-center max-md:right-3 max-md:bottom-3;
|
||||
|
||||
.toolbar-list {
|
||||
@apply visible mb-2 grid origin-bottom grid-rows-[1fr] opacity-100 transition-[grid-template-rows,transform,opacity,visibility] duration-[250ms] ease-in-out max-md:mb-1.5;
|
||||
|
||||
.toolbar-list-inner {
|
||||
@apply border-hairline bg-surface-overlay flex min-h-0 flex-col items-center gap-2 overflow-hidden rounded-full border p-1 shadow-sm max-md:gap-1.5 max-md:p-0.5;
|
||||
}
|
||||
@apply mb-2.5 flex flex-col items-center gap-2.5 max-md:mb-2 max-md:gap-2;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
@apply flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-full p-2 transition-[background-color,transform,opacity] duration-150 ease-in-out active:scale-95 max-md:h-9 max-md:w-9 max-md:p-1.5;
|
||||
@apply text-text-muted bg-surface-overlay pointer-events-auto grid size-11 cursor-pointer place-items-center rounded-full shadow-md max-md:size-10;
|
||||
/* `visibility` rides the same transition so collapsed bubbles leave the
|
||||
tab order, but only after the drop finishes (it flips at the end of
|
||||
its transition window, so the motion itself is never clipped). */
|
||||
@apply [transition:translate_320ms_cubic-bezier(0.34,1.25,0.5,1),scale_320ms_cubic-bezier(0.34,1.25,0.5,1),opacity_220ms_ease,visibility_320ms_ease,background-color_150ms_ease,color_150ms_ease];
|
||||
@apply [transition-delay:var(--fab-d,0s),var(--fab-d,0s),var(--fab-d,0s),var(--fab-d,0s),0s,0s];
|
||||
|
||||
.icon {
|
||||
@apply h-5 w-5 shrink-0 transition-transform duration-150 ease-in-out max-md:h-4 max-md:w-4;
|
||||
&:hover {
|
||||
@apply text-text bg-hover-faint;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@apply scale-[0.92];
|
||||
}
|
||||
|
||||
&:not(.toggle) {
|
||||
@apply hover:bg-hover-faint no-underline hover:scale-110;
|
||||
@apply [--fab-d:calc((var(--fab-n,1)-1-var(--fab-i,0))*60ms)] no-underline;
|
||||
|
||||
/* The stagger delay must not defer press feedback: `scale` shares the
|
||||
delayed transition channel, so without this a press on the top
|
||||
bubble would acknowledge (n-1)*60ms late. Nested (not merged into
|
||||
the base `:active`) so it outranks the delay set just above. */
|
||||
&:active {
|
||||
@apply [--fab-d:0s];
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply object-contain dark:invert;
|
||||
@apply size-5 shrink-0 bg-current [mask:var(--fab-icon)_center/contain_no-repeat] max-md:size-4.5;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggle {
|
||||
@apply bg-surface border-0 shadow-sm;
|
||||
@apply text-text-subtle hover:text-text border-0;
|
||||
|
||||
.icon {
|
||||
@apply transition-transform duration-150 ease-out;
|
||||
@apply grid size-5 place-items-center transition-[rotate] duration-[250ms] ease-out max-md:size-4.5;
|
||||
|
||||
svg {
|
||||
@apply size-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
.toolbar-list {
|
||||
@apply pointer-events-none invisible translate-y-2 scale-90 grid-rows-[0fr] opacity-0;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
&:not(.toggle) {
|
||||
@apply scale-50 opacity-0;
|
||||
@apply pointer-events-none invisible scale-[0.35] opacity-0;
|
||||
@apply [--fab-d:calc(var(--fab-i,0)*60ms)] translate-y-[calc((var(--fab-n,1)-var(--fab-i,0))*3.375rem)] max-md:translate-y-[calc((var(--fab-n,1)-var(--fab-i,0))*3rem)];
|
||||
}
|
||||
|
||||
&.toggle .icon {
|
||||
@apply rotate-45;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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).
|
||||
*/
|
||||
|
||||
.header-search-toggle {
|
||||
@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
|
||||
toggle right so it docks next to #indicators instead of landing in the
|
||||
middle of the justify-between row. */
|
||||
body:not([data-nav-type="sidebar"]) & {
|
||||
@apply ml-auto;
|
||||
}
|
||||
|
||||
&::before {
|
||||
@apply size-4.5 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply bg-hover-faint;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@apply ring-focus-ring ring-2 outline-none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-search-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
|
||||
manner) — opaque page background (no full-screen blur cost on phones),
|
||||
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 {
|
||||
@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;
|
||||
|
||||
&::before {
|
||||
@apply text-text-subtle size-5 shrink-0 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .header-search-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 {
|
||||
@apply text-brand hidden shrink-0 cursor-pointer border-0 bg-transparent p-1 text-sm shadow-none max-md:block;
|
||||
}
|
||||
}
|
||||
|
||||
& .header-search-results {
|
||||
@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 {
|
||||
@apply text-text flex items-center gap-3 rounded-lg px-3 py-2 text-sm no-underline;
|
||||
|
||||
& .result-title {
|
||||
@apply min-w-0 truncate;
|
||||
}
|
||||
|
||||
& .result-group {
|
||||
@apply text-text-subtle ml-auto shrink-0 text-xs;
|
||||
}
|
||||
|
||||
& mark {
|
||||
@apply text-brand bg-transparent font-semibold;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
@apply bg-brand-subtle;
|
||||
|
||||
& .result-title {
|
||||
@apply text-brand font-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .header-search-empty {
|
||||
@apply text-text-subtle px-3 py-6 text-center text-sm;
|
||||
}
|
||||
}
|
||||
|
||||
& .header-search-hint {
|
||||
@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 {
|
||||
@apply ml-auto;
|
||||
}
|
||||
|
||||
& kbd {
|
||||
@apply border-hairline bg-surface-sunken text-text-muted rounded-md border px-1.5 py-px font-mono text-[11px];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The frost is decorative — both degradation paths go opaque. */
|
||||
@supports not (backdrop-filter: blur(1px)) {
|
||||
.header-search-panel {
|
||||
@apply bg-surface;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.header-search-panel {
|
||||
@apply bg-surface;
|
||||
}
|
||||
}
|
||||
@@ -32,3 +32,48 @@
|
||||
@apply visible scale-100 opacity-100 inset-s-auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generic label tooltip — a CSS-only inverse pill for icon buttons and other
|
||||
compact controls; any element opts in via attributes:
|
||||
data-tip="label" tooltip text (empty value renders nothing)
|
||||
data-tip-pos="left" placement: top (default) | bottom | left | right
|
||||
Named `data-tip` because `data-tooltip` above is claimed by LuCI's cbi
|
||||
help bubbles. Show is delayed so cursor sweeps don't flash labels; hide is
|
||||
immediate. Motion is opacity/scale only (compositor-safe), and hover is
|
||||
gated to hover-capable pointers so a tap on touch doesn't strand a label —
|
||||
keyboard focus still shows it everywhere. */
|
||||
[data-tip]:not([data-tip='']) {
|
||||
@apply relative;
|
||||
|
||||
&::after {
|
||||
content: attr(data-tip);
|
||||
@apply bg-text text-bg pointer-events-none absolute z-110 w-max max-w-xs scale-95 rounded-lg px-2 py-1 text-xs break-normal whitespace-normal opacity-0 transition-[opacity,scale] duration-150 ease-out;
|
||||
}
|
||||
|
||||
&:not([data-tip-pos])::after,
|
||||
&[data-tip-pos='top']::after {
|
||||
@apply bottom-[calc(100%+0.5rem)] left-1/2 -translate-x-1/2;
|
||||
}
|
||||
|
||||
&[data-tip-pos='bottom']::after {
|
||||
@apply top-[calc(100%+0.5rem)] left-1/2 -translate-x-1/2;
|
||||
}
|
||||
|
||||
&[data-tip-pos='left']::after {
|
||||
@apply top-1/2 right-[calc(100%+0.625rem)] -translate-y-1/2;
|
||||
}
|
||||
|
||||
&[data-tip-pos='right']::after {
|
||||
@apply top-1/2 left-[calc(100%+0.625rem)] -translate-y-1/2;
|
||||
}
|
||||
|
||||
&:focus-visible::after {
|
||||
@apply scale-100 opacity-100 delay-300;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover::after {
|
||||
@apply scale-100 opacity-100 delay-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@
|
||||
@import "./components/_form.css";
|
||||
@import "./components/_segmented.css";
|
||||
@import "./components/_tab.css";
|
||||
@import "./components/_search.css";
|
||||
|
||||
@import "./_utilities.css";
|
||||
@import "./_reduced-motion.css";
|
||||
|
||||
@@ -495,6 +495,261 @@ 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;
|
||||
|
||||
this.searchIndex = [];
|
||||
items.forEach((item) => {
|
||||
if (item.isLogout) return;
|
||||
if (!item.hasChildren) {
|
||||
this.searchIndex.push({
|
||||
title: item.title,
|
||||
name: item.name,
|
||||
group: null,
|
||||
href: item.href,
|
||||
});
|
||||
return;
|
||||
}
|
||||
item.pages.forEach((page) =>
|
||||
this.searchIndex.push({
|
||||
title: page.title,
|
||||
name: page.name,
|
||||
group: item.title,
|
||||
href: page.href,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// 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";
|
||||
toggle.setAttribute("aria-keyshortcuts", isMac ? "Meta+K" : "Control+K");
|
||||
toggle.setAttribute("aria-expanded", "false");
|
||||
toggle.addEventListener("click", () => this.toggleSearch());
|
||||
this.searchToggle = 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;
|
||||
if (
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
!e.altKey &&
|
||||
!e.shiftKey &&
|
||||
(e.key || "").toLowerCase() === "k"
|
||||
) {
|
||||
e.preventDefault();
|
||||
this.toggleSearch();
|
||||
} else if (
|
||||
e.key === "Escape" &&
|
||||
this.searchPanel &&
|
||||
!this.searchPanel.hidden
|
||||
) {
|
||||
this.closeSearch();
|
||||
}
|
||||
});
|
||||
|
||||
// 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();
|
||||
};
|
||||
},
|
||||
|
||||
buildSearchPanel() {
|
||||
const input = E("input", {
|
||||
class: "header-search-input",
|
||||
type: "text",
|
||||
placeholder: _("Type to filter…"),
|
||||
"aria-label": _("Type to filter…"),
|
||||
autocomplete: "off",
|
||||
spellcheck: "false",
|
||||
});
|
||||
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" },
|
||||
[_("Cancel")],
|
||||
);
|
||||
cancel.addEventListener("click", () => this.closeSearch());
|
||||
const panel = E(
|
||||
"div",
|
||||
{
|
||||
id: "header-search-panel",
|
||||
class: "header-search-panel",
|
||||
role: "dialog",
|
||||
"aria-modal": "true",
|
||||
"aria-label": _("Navigation"),
|
||||
hidden: "",
|
||||
},
|
||||
[
|
||||
E("div", { class: "header-search-box" }, [input, cancel]),
|
||||
results,
|
||||
E("div", { class: "header-search-hint" }, [
|
||||
E("kbd", {}, ["↑↓"]),
|
||||
E("kbd", {}, ["↵"]),
|
||||
E("kbd", {}, ["esc"]),
|
||||
E("span", { class: "header-search-hint-key" }, [
|
||||
E("kbd", {}, [this.searchKey]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
input.addEventListener("input", () =>
|
||||
this.renderSearchResults(input.value),
|
||||
);
|
||||
input.addEventListener("keydown", (e) => {
|
||||
// Mid-composition these keys belong to the IME: Enter commits the
|
||||
// buffer (navigating away for pinyin users) and arrows move inside
|
||||
// the candidate list, not the results.
|
||||
if (e.isComposing || e.keyCode === 229) return;
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
this.moveSearchSelection(e.key === "ArrowDown" ? 1 : -1);
|
||||
} else if (e.key === "Enter") {
|
||||
results.querySelector(".is-selected")?.click();
|
||||
}
|
||||
});
|
||||
// mousemove, not mouseover: scrollIntoView() slides rows under a
|
||||
// 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");
|
||||
if (row && !row.classList.contains("is-selected"))
|
||||
this.setSearchSelection(row);
|
||||
});
|
||||
// The dialog is modal (full-screen takeover on mobile): keep Tab
|
||||
// cycling within it instead of escaping onto the page beneath.
|
||||
panel.addEventListener("keydown", (e) => {
|
||||
if (e.key !== "Tab") return;
|
||||
const focusables = [
|
||||
input,
|
||||
...results.querySelectorAll("a"),
|
||||
cancel,
|
||||
].filter((el) => el.getClientRects().length);
|
||||
const edge = e.shiftKey ? focusables[0] : focusables.at(-1);
|
||||
if (document.activeElement === edge) {
|
||||
e.preventDefault();
|
||||
(e.shiftKey ? focusables.at(-1) : focusables[0]).focus();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(panel);
|
||||
this.searchToggle.setAttribute("aria-controls", panel.id);
|
||||
this.searchPanel = panel;
|
||||
this.searchInput = input;
|
||||
this.searchResults = results;
|
||||
},
|
||||
|
||||
renderSearchResults(value) {
|
||||
const q = value.trim().toLowerCase();
|
||||
this.searchResults.replaceChildren();
|
||||
if (!q) return; // Spotlight manner: quiet until typed.
|
||||
|
||||
const matches = this.searchIndex.filter(
|
||||
(page) =>
|
||||
page.title.toLowerCase().includes(q) ||
|
||||
page.name.toLowerCase().includes(q) ||
|
||||
page.group?.toLowerCase().includes(q),
|
||||
);
|
||||
|
||||
if (!matches.length) {
|
||||
this.searchResults.appendChild(
|
||||
E("div", { class: "header-search-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]) : "",
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
highlightSearchMatch(title, q) {
|
||||
const at = title.toLowerCase().indexOf(q);
|
||||
if (at < 0) return [title];
|
||||
|
||||
return [
|
||||
title.slice(0, at),
|
||||
E("mark", {}, [title.slice(at, at + q.length)]),
|
||||
title.slice(at + q.length),
|
||||
];
|
||||
},
|
||||
|
||||
setSearchSelection(row) {
|
||||
this.searchResults
|
||||
.querySelector(".is-selected")
|
||||
?.classList.remove("is-selected");
|
||||
row.classList.add("is-selected");
|
||||
},
|
||||
|
||||
moveSearchSelection(delta) {
|
||||
const rows = [
|
||||
...this.searchResults.querySelectorAll(".header-search-result"),
|
||||
];
|
||||
if (!rows.length) return;
|
||||
|
||||
const current = rows.findIndex((row) =>
|
||||
row.classList.contains("is-selected"),
|
||||
);
|
||||
const next = rows[(current + delta + rows.length) % rows.length];
|
||||
|
||||
this.setSearchSelection(next);
|
||||
next.scrollIntoView({ block: "nearest" });
|
||||
},
|
||||
|
||||
toggleSearch() {
|
||||
if (!this.searchPanel) this.buildSearchPanel();
|
||||
|
||||
if (this.searchPanel.hidden) this.openSearch();
|
||||
else this.closeSearch();
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
// Shared scaffolding for the two desktop dropdown modes (mega-menu and
|
||||
// dropdown): builds the top-level `.menu` link + its `.desktop-nav`
|
||||
// panel. Hover/activation behaviour differs per mode and is wired by the
|
||||
@@ -897,6 +1152,7 @@ return baseclass.extend({
|
||||
);
|
||||
this.renderMainMenu(activeChild, activeChild.name, 0, navigationItems);
|
||||
this.renderMobileMenu(navigationItems);
|
||||
this.initSearch(navigationItems);
|
||||
}
|
||||
|
||||
if (ul?.children.length > 1) {
|
||||
|
||||
@@ -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.0.14
|
||||
PKG_RELEASE:=47
|
||||
PKG_VERSION:=1.0.15
|
||||
PKG_RELEASE:=48
|
||||
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-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;--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-familyLine 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
|
||||
@@ -140,6 +140,8 @@
|
||||
if (toolbarToggle && floatingToolbar) {
|
||||
toolbarToggle.addEventListener('click', () => {
|
||||
const collapsed = floatingToolbar.classList.toggle('collapsed');
|
||||
toolbarToggle.dataset.tip = collapsed ? toolbarToggle.dataset.tipShow : toolbarToggle.dataset.tipHide;
|
||||
toolbarToggle.setAttribute('aria-expanded', !collapsed);
|
||||
localStorage.setItem('aurora.toolbarCollapsed', collapsed);
|
||||
});
|
||||
}
|
||||
@@ -217,6 +219,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>
|
||||
<div id="indicators" class="pull-right"></div>
|
||||
</div>
|
||||
|
||||
@@ -311,19 +314,23 @@
|
||||
|
||||
{% if (toolbar_enabled): %}
|
||||
<div id="floating-toolbar" class="floating-toolbar">
|
||||
<div class="toolbar-list">
|
||||
<div class="toolbar-list-inner">
|
||||
{% for (let i = 0; i < length(toolbar_items); i++): %}
|
||||
{% let item = toolbar_items[i]; %}
|
||||
<a href="{{ item.url }}" class="toolbar-btn" title="{{ item.title }}">
|
||||
<img src="{{ media }}/images/{{ item.icon }}?v={{ icon_cache_version }}" class="icon" alt="{{ item.title }}"/>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="toolbar-list" style="--fab-n:{{ length(toolbar_items) }}">
|
||||
{% for (let i = 0; i < length(toolbar_items); i++): %}
|
||||
{% let item = toolbar_items[i]; %}
|
||||
<a href="{{ item.url }}" class="toolbar-btn" data-tip="{{ entityencode(item.title, true) }}" data-tip-pos="left" aria-label="{{ entityencode(item.title, true) }}" style="--fab-i:{{ i }};--fab-icon:url('{{ entityencode(`${media}/images/${item.icon}?v=${icon_cache_version}`, true) }}')">
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<button class="toolbar-btn toggle" id="toolbar-toggle" title="{{ _('Toggle floating buttons') }}" aria-label="{{ _('Toggle floating buttons') }}">
|
||||
<span class="icon">
|
||||
|
||||
{#
|
||||
The tooltip is a short state-dependent verb — "Hide" while the
|
||||
shortcuts are shown, "Show" once collapsed. Both strings ship
|
||||
in luci-base's po (the theme carries none of its own); the
|
||||
verbose static label stays on aria-label only.
|
||||
#}
|
||||
<button class="toolbar-btn toggle" id="toolbar-toggle" aria-expanded="true" data-tip="{{ _('Hide') }}" data-tip-hide="{{ _('Hide') }}" data-tip-show="{{ _('Show') }}" data-tip-pos="left" aria-label="{{ _('Toggle floating buttons') }}">
|
||||
<span class="icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
@@ -336,6 +343,11 @@
|
||||
const toolbar = document.getElementById('floating-toolbar');
|
||||
if (toolbar && localStorage.getItem('aurora.toolbarCollapsed') === 'true') {
|
||||
toolbar.classList.add('collapsed');
|
||||
const toggle = document.getElementById('toolbar-toggle');
|
||||
if (toggle) {
|
||||
toggle.dataset.tip = toggle.dataset.tipShow;
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=natflow
|
||||
PKG_VERSION:=20260531
|
||||
PKG_RELEASE:=41
|
||||
PKG_RELEASE:=42
|
||||
|
||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=fdcb3b742a5d988f926b5cd668225a2dcece5382
|
||||
PKG_SOURCE_VERSION:=2e495d4fbf59b9b91d02789fedd66ddea334df1f
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=tun2socks
|
||||
PKG_VERSION:=2.6.0
|
||||
PKG_RELEASE:=8
|
||||
PKG_RELEASE:=9
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/xjasonlyu/tun2socks.git
|
||||
PKG_SOURCE_VERSION:=dda1b1058db86dd0ef40d1b007de0ce86cf16a46
|
||||
PKG_SOURCE_VERSION:=e8bd4553c483b1284bf30ca65544002f10f1ceeb
|
||||
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlako@132lan.ru>
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
|
||||
Reference in new issue
Block a user