Sync 2026-07-11 20:20:47

This commit is contained in:
github-actions[bot] 2026-07-11 20:20:47 +08:00
parent 97e279bd1a
commit f02387c190
15 changed files with 521 additions and 40 deletions

View File

@ -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:= \

View File

@ -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 })

View File

@ -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
}
}

View 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

View File

@ -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;
}

View 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;
}
}

View File

@ -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;
}
}
}

View File

@ -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";

View File

@ -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) {

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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