mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 18:41:15 +08:00
⛄ Sync 2026-07-11 20:20:47
This commit is contained in:
parent
97e279bd1a
commit
f02387c190
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
luci-theme-aurora/.dev/src/assets/icons/search.svg
Normal file
4
luci-theme-aurora/.dev/src/assets/icons/search.svg
Normal file
@ -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;
|
||||
}
|
||||
|
||||
115
luci-theme-aurora/.dev/src/media/components/_search.css
Normal file
115
luci-theme-aurora/.dev/src/media/components/_search.css
Normal file
@ -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:=
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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>
|
||||
|
||||
@ -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,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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user