mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 02:35:13 +08:00
⛄ Sync 2026-08-16 09:02:12
This commit is contained in:
1 parent
570286daf3
commit
4a9528feed
23 files changed
+214
-76
No files matched your search
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.8.14
|
||||
PKG_RELEASE:=84
|
||||
PKG_RELEASE:=85
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+33
-24
@@ -11,6 +11,10 @@ if not s1.fields["type"].default then
|
||||
s1.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
@@ -29,6 +33,27 @@ local header_type_list = {
|
||||
|
||||
o = s:option(Flag, "custom", translate("Use Custom Config"))
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
o:value("vmess", "Vmess")
|
||||
o:value("vless", "VLESS")
|
||||
@@ -41,6 +66,12 @@ o:value("wireguard", "WireGuard")
|
||||
o:value("dokodemo-door", "dokodemo-door")
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(DummyValue, "is_endpoint", "")
|
||||
o.not_rewrite = true
|
||||
o.template = m:template_path("/cbi/hidevalue")
|
||||
o.value = "1"
|
||||
o:depends({ custom = false, protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ custom = false })
|
||||
@@ -385,7 +416,7 @@ end
|
||||
--[[acceptProxyProtocol]]
|
||||
o = s:option(Flag, "acceptProxyProtocol", translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
o:depends({ custom = false, is_endpoint = "" })
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
|
||||
@@ -444,8 +475,7 @@ o.datatype = "base64"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(DummyValue, "gen_wireguard_key")
|
||||
o.prefix = option_prefix
|
||||
o.template = m:template_path("/server/server_wireguard")
|
||||
o.template = m:template_path("/server/gen_wireguard_key")
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
|
||||
@@ -505,27 +535,6 @@ for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
+33
-24
@@ -13,6 +13,10 @@ if not s1.fields["type"].default then
|
||||
s1.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
@@ -30,6 +34,27 @@ local ss_method_list = {
|
||||
|
||||
o = s:option(Flag, "custom", translate("Use Custom Config"))
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
o:value("mixed", "Mixed")
|
||||
o:value("socks", "Socks")
|
||||
@@ -55,6 +80,12 @@ end
|
||||
o:value("direct", "Direct")
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(DummyValue, "is_endpoint", "")
|
||||
o.not_rewrite = true
|
||||
o.template = m:template_path("/cbi/hidevalue")
|
||||
o.value = "1"
|
||||
o:depends({ custom = false, protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ custom = false })
|
||||
@@ -420,14 +451,13 @@ if singbox_tags:find("with_wireguard") then
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(DummyValue, "gen_wireguard_key")
|
||||
o.prefix = option_prefix
|
||||
o.template = m:template_path("/server/server_wireguard")
|
||||
o.template = m:template_path("/server/gen_wireguard_key")
|
||||
o:depends({ protocol = "wireguard" })
|
||||
end
|
||||
|
||||
o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
o:depends({ custom = false, is_endpoint = "" })
|
||||
|
||||
o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
|
||||
o.default = "0"
|
||||
@@ -482,27 +512,6 @@ for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
+4
@@ -8,6 +8,10 @@ local type_name = "Hysteria2"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Hysteria2")
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
+4
@@ -8,6 +8,10 @@ local type_name = "SS-Rust"
|
||||
|
||||
s1.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
+4
@@ -8,6 +8,10 @@ local type_name = "SSR"
|
||||
|
||||
s1.fields["type"]:value(type_name, translate("ShadowsocksR"))
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
@@ -1554,6 +1554,7 @@ function luci_types(s, s2)
|
||||
for i, v in ipairs(s2.children) do
|
||||
local o = s2.children[i]
|
||||
o.config_option = o.option
|
||||
o.option_prefix = option_prefix
|
||||
o.option = option_prefix .. o.option
|
||||
if not o.not_rewrite then
|
||||
o.cfgvalue = function(self, section)
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<%
|
||||
local map = self.map
|
||||
local api = map.api
|
||||
local prefix = self.prefix
|
||||
local prefix = self.option_prefix or ""
|
||||
-%>
|
||||
<div class="cbi-value" id="cbi-<%=self.config.."-"..section.."-"..self.option%>" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>">
|
||||
<label class="cbi-value-title"><%:Generate key%></label>
|
||||
@@ -535,6 +535,7 @@ local function parseClashNode(node, add_mode, group, sub_cfg)
|
||||
result = set_ss_implementation(sub_ss_type, result)
|
||||
if not result then return nil end
|
||||
result.method = node.cipher
|
||||
result.ss_method = node.cipher
|
||||
result.password = node.password
|
||||
if node.plugin == "obfs" then
|
||||
result.plugin = "obfs-local"
|
||||
@@ -1114,6 +1115,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
(_method == "xchacha20-poly1305" and "xchacha20-ietf-poly1305") or _method
|
||||
|
||||
result.method = method
|
||||
result.ss_method = method
|
||||
result.password = password
|
||||
result.tcp_fast_open = params.tfo
|
||||
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
|
||||
@@ -1482,6 +1484,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.port = content.port
|
||||
result.password = content.password
|
||||
result.method = content.encryption
|
||||
result.ss_method = content.encryption
|
||||
result.plugin = content.plugin
|
||||
result.plugin_opts = content.plugin_options
|
||||
result.group = content.airport
|
||||
@@ -1770,6 +1773,13 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
|
||||
elseif has_hysteria2 then
|
||||
result.type = "Hysteria2"
|
||||
for k1, v1 in pairs(result) do
|
||||
if k1:find("hysteria2_") == 1 then
|
||||
local s = split(k1, "hysteria2_")
|
||||
result[s[2]] = v1
|
||||
result[k1] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Hysteria2", "Hysteria2"))
|
||||
return nil
|
||||
|
||||
@@ -17,12 +17,12 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=19
|
||||
PKG_RELEASE:=20
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
# +luci-base is the WHOLE list, and keeping it that way is the constraint. Nothing this package
|
||||
# ships fetches anything at run time any more (the self-updater is retired), so there is no second
|
||||
# ships fetches anything at run time (the self-update script is retired), so there is no second
|
||||
# name to justify — and the rule that got it here stands: the one convenience tool that ever wanted
|
||||
# a dep was `curl`, which is NOT in OpenWrt's default set, on an 8 MB device, for one API call. Fall
|
||||
# back to what the base image has (uclient-fetch, jsonfilter, sha256sum) or do without.
|
||||
@@ -168,8 +168,8 @@ endef
|
||||
# what Weblate can see (it translates `po/`, and only `po/`).
|
||||
#
|
||||
# It was `i18n/` here for a while, with a po2lmo loop in this hook bundling the catalogue into the
|
||||
# theme package, because a multi-package release was mis-picked by a self-updater that resolved the
|
||||
# theme by name and took `head -1` (issue #6). That updater is retired and the release is built by
|
||||
# theme package, because a multi-package release was mis-picked by a self-update script that resolved
|
||||
# the theme by name and took `head -1` (issue #6). That script is retired and the release is built by
|
||||
# owfeed, which packages this theme as exactly one artifact per format regardless of what luci.mk
|
||||
# would do — so the constraint that bought the rename is gone, and the cost of keeping it (a
|
||||
# catalogue invisible to the project's own translation platform) is not.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
'require fs-fit as fit';
|
||||
'require fs-prefs as prefs';
|
||||
'require fs-menutree as tree';
|
||||
'require fs-version as version';
|
||||
|
||||
/* The chrome AROUND the content: the mode menu, the section tabs, the rail toggle, and the
|
||||
* measurements that decide how much room any of it gets. The MAIN menu is not here — it is injected
|
||||
@@ -277,6 +278,40 @@ function publishBarHeight(bar) {
|
||||
root.style.setProperty('--fs-bar-live', `${h}px`);
|
||||
}
|
||||
|
||||
/* ---- the theme names itself in the footer, beside LuCI and the distribution ----
|
||||
*
|
||||
* The footer's sentence is about what renders this page: LuCI's version, the distribution's, and —
|
||||
* since the markup is the theme's — the theme's. Each of the other two is a link to where that
|
||||
* software lives, so this one is too.
|
||||
*
|
||||
* WRITTEN FROM THE CLIENT, and that is the only reason it is here rather than in footer.ut: the
|
||||
* version is stamped into fs-version.js by file name — the Makefile, tools/stage.sh and dev-sync.sh
|
||||
* all sed that one literal — so a second copy in a template would be a second stamp site to keep in
|
||||
* step, and a footer that quietly disagrees with the Appearance tab is worse than no version at all.
|
||||
* The label is the one fs-version already prints there, dev builds included.
|
||||
*
|
||||
* Once per document: the footer survives a client navigation (navigate() sweeps the children of
|
||||
* .fs-content, and the footer is its sibling), and the guard makes a second call a no-op rather
|
||||
* than a second copy. No request, no translated string: a proper noun and a number. */
|
||||
function nameThemeInFooter() {
|
||||
const host = document.querySelector('footer.fs-footer span');
|
||||
if (!host || host.querySelector('.fs-footer-self')) return;
|
||||
const self = E('a', {
|
||||
class: 'fs-footer-self',
|
||||
href: version.REPO_URL,
|
||||
target: '_blank',
|
||||
rel: 'noreferrer',
|
||||
}, version.label());
|
||||
/* AFTER THE DISTRIBUTION, not at the end. The sentence lists the software that renders the page
|
||||
* — LuCI, the distribution, the theme — and footer.ut appends one more fragment after them that
|
||||
* is not a component but a state: "Lua compatibility mode active". Landing behind it read as if
|
||||
* the theme were part of that notice. Two links means the second is the distribution's; with
|
||||
* anything else there, appending is still correct. */
|
||||
const links = host.querySelectorAll('a');
|
||||
if (links.length >= 2) links[1].after(document.createTextNode(' / '), self);
|
||||
else host.append(document.createTextNode(' / '), self);
|
||||
}
|
||||
|
||||
/* ---- does the right-hand cluster still fit beside the brand? ----
|
||||
*
|
||||
* The same question as the menu's, one row up, and it cannot be asked the same way. The cluster is
|
||||
@@ -459,6 +494,7 @@ function wireIndicatorCounts() {
|
||||
return baseclass.extend({
|
||||
setRenderMain,
|
||||
renderChrome,
|
||||
nameThemeInFooter,
|
||||
wireIndicatorCounts,
|
||||
/* registered with fs-fit by the theme's init(): the bar's "does the menu fit beside the brand"
|
||||
* measurement rides the same engine as the data tables' */
|
||||
|
||||
@@ -125,10 +125,14 @@ function warnPollUnreadable(msg) {
|
||||
* itself to L's prototype when the FIRST requirer compiles it, so this sees the instance the pages
|
||||
* actually use, and a router that required it would both bind it to its own prototypal L (the two-L
|
||||
* trap, docs/spa-router.md) and pull uci.js onto pages that never touch uci. No instance means no
|
||||
* cache to flush. */
|
||||
* cache to flush.
|
||||
*
|
||||
* Returns the refill below as a promise the caller must wait on, or null when there is nothing to
|
||||
* wait for. It never rejects — a failed refill is reported and swallowed, because a navigation is
|
||||
* not the place to lose a page over a config the incoming view may not even read. */
|
||||
function flushUciCache() {
|
||||
const uci = window.L ? window.L.uci : null;
|
||||
if (!uci || typeof uci.unload !== 'function') return;
|
||||
if (!uci || typeof uci.unload !== 'function') return null;
|
||||
/* `state.values` and `loaded` are private, so the same rule as L.Poll.timer applies: a shape we
|
||||
* do not recognise is a reason to do nothing, once, loudly — never to guess. The two hold
|
||||
* different halves of the cache (a package whose load is still in flight is in `loaded` alone),
|
||||
@@ -141,14 +145,60 @@ function flushUciCache() {
|
||||
+ 'will report a missing config on the second SPA visit. fs-router.js needs updating for '
|
||||
+ 'this luci-base.');
|
||||
}
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
const names = Object.keys(uci.state.values).concat(Object.keys(uci.loaded));
|
||||
if (names.length) uci.unload(names);
|
||||
if (!names.length) return null;
|
||||
uci.unload(names);
|
||||
/* `state.reorder` is the one half unload() does not clear, and it is deliberately left alone
|
||||
* rather than reset by hand: with `values` gone, reorderSections() finds no sections to order,
|
||||
* emits no call and clears the map itself on the next save. Writing to that field would be us
|
||||
* editing another module's private state, for a difference nothing can observe. */
|
||||
|
||||
/* …and put back the three packages luci-base's network.js reads but will never load again.
|
||||
*
|
||||
* initNetworkState() loads `network`, `wireless` and `luci` ONCE, fills its own `_state`, and
|
||||
* from then on answers every caller with `return (_state != null ? Promise.resolve(_state) : _init)`
|
||||
* — no uci call, ever again, for the life of the document. What it answers WITH, though, is the
|
||||
* uci cache: `getWifiDevices()` is `uci.sections('wireless', 'wifi-device')`, and a view like
|
||||
* network/switch reads `uci.sections('network', 'switch')` in its own render(). So dropping those
|
||||
* packages does not make network.js refetch them — it makes every consumer read an EMPTY config
|
||||
* until the next full load.
|
||||
*
|
||||
* Measured on 24.10, one SPA navigation away from Interfaces: `uci.state.values` {}, and
|
||||
* `network.getWifiDevices()` 2 devices -> 0. That is Status -> Channel Analysis painting its title
|
||||
* and its button with no band tabs under them, and Network -> Switch painting its description and
|
||||
* Save/Apply with no VLAN sections — both correct again after F5, which is exactly how it was
|
||||
* reported upstream ("only work after reloading the page"). tools/spa-parity.mjs reproduces it in
|
||||
* one command, and tools/upstream-contract.mjs is what notices if that list of three ever moves.
|
||||
*
|
||||
* Refilling them is what a full load hands the incoming view, so navigate() WAITS for it (see the
|
||||
* Promise.all below): a cached module resolves within a microtask, well before this request lands,
|
||||
* and the view would read the empty cache we just left it. Only when network.js is really in the
|
||||
* document — no module, no derived state to keep in step, and no request to spend. The ubus half
|
||||
* of `_state` stays as stale as upstream leaves it; a view that needs it fresh calls
|
||||
* network.flushCache() itself, and that is not ours to decide.
|
||||
*
|
||||
* WHAT IT COSTS, and why the cheaper shape was not taken. Awaiting this puts one uci `get` in
|
||||
* front of the render on a navigation whose module is already cached — measured on the stand over
|
||||
* 12 alternating navigations between two warm views: 136 ms median without the wait, 159 ms with
|
||||
* it. `uci.load()` also dispatches `uci-loaded`, which luci-base wires to the change indicator, so
|
||||
* a second, unawaited `uci.changes()` rides along that `unload()` alone never triggered.
|
||||
*
|
||||
* The free alternative is to leave these three OUT of the unload rather than drop and refetch
|
||||
* them, and it was rejected on what it does to the pages people edit: `network.flushCache()` — the
|
||||
* one call a view makes when it wants fresh data — reloads its ubus half but calls `uci.load()`
|
||||
* for the uci half, which is a no-op while the package is still cached. Interfaces and Wireless
|
||||
* would then render fresh device state over config values from whenever the document first
|
||||
* touched them, which a full load never does. Dropping and refilling keeps both halves as fresh as
|
||||
* a full load, and keeps pending local edits behaving as they do everywhere else: discarded by the
|
||||
* navigation, exactly like the poll queue and the view's intervals above. */
|
||||
if (!window.L.network) return null;
|
||||
const refill = [ 'network', 'wireless', 'luci' ].filter((p) => names.indexOf(p) !== -1);
|
||||
if (!refill.length) return null;
|
||||
return uci.load(refill).catch((e) => {
|
||||
console.error('footstrap: reloading uci ' + refill.join(', ') + ' after a navigation failed', e);
|
||||
});
|
||||
}
|
||||
let _uciCacheWarned = false;
|
||||
|
||||
@@ -676,8 +726,9 @@ function navigate(pathname, push, kbd) {
|
||||
* would have. L.Poll's own tick survives. */
|
||||
clearViewIntervals();
|
||||
/* and drop uci's document-scoped config cache, which a full load would not have carried into the
|
||||
* incoming page either (see flushUciCache) */
|
||||
flushUciCache();
|
||||
* incoming page either (see flushUciCache). What it hands back is the refill of the packages
|
||||
* network.js will never load again — awaited below, before the view renders. */
|
||||
const uciWarm = flushUciCache();
|
||||
/* the outgoing page's links are about to become a detached tree — do not hold one of them */
|
||||
_lastHovered = null;
|
||||
/* run every registered navigation callback — today the search palette's recent-pages record and
|
||||
@@ -819,7 +870,10 @@ function navigate(pathname, push, kbd) {
|
||||
* and the wait introduces a window in which we may never get there. Marking it up front would
|
||||
* make the NEXT navigation take the cached branch — `new view.constructor()` on a class whose
|
||||
* require() is what renders it — i.e. two renders and two pollers for one page. */
|
||||
warmedThen(className).then(() => {
|
||||
/* `uciWarm` rides alongside the prefetch wait rather than after it: both are in flight already, and
|
||||
* a view must not be constructed until the uci cache it reads is back (see flushUciCache). It never
|
||||
* rejects — the refill catches its own failure — so it cannot cost the navigation a full reload. */
|
||||
Promise.all([ warmedThen(className), uciWarm ]).then(() => {
|
||||
/* superseded while waiting: never start the require. On a first visit the require IS the
|
||||
* render, so starting it here would paint a page the user has already left and hand
|
||||
* repairStaleRender() a mess that only exists because a require in flight cannot be stopped. */
|
||||
|
||||
@@ -591,7 +591,7 @@ const HOLDS_CONTROLS = '.cbi-dropdown, .cbi-dynlist, .cbi-tooltip-container, .cb
|
||||
* `grid`/`treegrid`), so overwriting it with `group` drops every cell to generic — a screen reader
|
||||
* would then read a flat run of text with no rows and no columns, which is the very structure the
|
||||
* SC 1.4.10 exception exists to preserve. `aria-label` works on either, so the name and the tab stop
|
||||
* are unaffected. Reported in review.
|
||||
* are unaffected.
|
||||
*
|
||||
* WHAT IS REMOVED IS WHAT WAS WRITTEN, remembered per element rather than inferred from the value:
|
||||
* an app that had its own `tabindex="0"` or `role="group"` on that table got them taken away when the
|
||||
@@ -639,18 +639,12 @@ function fitScrollables() {
|
||||
if (scroll === was) { if (was) t.classList.add('fs-xscroll'); return; }
|
||||
if (scroll) {
|
||||
t.classList.add('fs-xscroll');
|
||||
/* A SCROLL BOX THE KEYBOARD CANNOT REACH IS CONTENT THE KEYBOARD CANNOT READ.
|
||||
/* A SCROLL BOX THE KEYBOARD CANNOT REACH IS CONTENT THE KEYBOARD CANNOT READ, and the
|
||||
* tab stop, the name and the conditional role that say so are all reach()'s business —
|
||||
* its header above carries the reasoning.
|
||||
*
|
||||
* Firefox has made scrollers focusable for years and Chrome since 132, but only when they
|
||||
* hold no focusable child — a table row full of buttons disqualifies itself — and WebKit
|
||||
* has not shipped it at all (bug 190870, open since 2018). So say it in the markup: a
|
||||
* tab stop (SC 2.1.1), a role and a name so what receives focus can be announced
|
||||
* (SC 4.1.2). `group`, not `region`: a region is a landmark, and a status page with four
|
||||
* scrolling tables would put four of them in the landmark list.
|
||||
*
|
||||
* The name comes from the heading the table sits under, which is what a sighted user
|
||||
* reads it as; only the fallback is ours to translate. Never overwritten: an app that
|
||||
* labelled its own table knows better. */
|
||||
* `group`, not `region`: a region is a landmark, and a status page with four scrolling
|
||||
* tables would put four of them in the landmark list. */
|
||||
reach(t);
|
||||
}
|
||||
else if (was) unreach(t);
|
||||
|
||||
@@ -30,7 +30,7 @@ function isReal() { return ((/^\d+\.\d+/).test(FS_VERSION)) && !((/-dev$/).test(
|
||||
return baseclass.extend({
|
||||
VERSION: FS_VERSION,
|
||||
/* the project's page, for the link at the foot of the Appearance tab. One string, used once —
|
||||
* it was assembled from a separate FS_REPO constant that the retired updater needed to build
|
||||
* it was assembled from a separate FS_REPO constant that the retired self-update script needed to build
|
||||
* API paths from, and nothing has needed the bare `owner/name` since. */
|
||||
REPO_URL: 'https://github.com/VizzleTF/luci-theme-footstrap',
|
||||
isReal,
|
||||
|
||||
@@ -58,6 +58,8 @@ return baseclass.extend({
|
||||
fit.add(chrome.fitChrome);
|
||||
|
||||
chrome.renderChrome();
|
||||
/* once per document — the footer outlives every client navigation */
|
||||
chrome.nameThemeInFooter();
|
||||
/* Both of these ADD to a stock page rather than owning a route: each watches
|
||||
* body[data-page] and does nothing anywhere else. A theme may not register a
|
||||
* dispatcher node — it outlives the theme that registered it. */
|
||||
|
||||
@@ -677,6 +677,17 @@
|
||||
* this rule made them dead.) */
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
/* AND SPACE THE LINES, because the two links in it become adjacent TARGETS the moment the
|
||||
* line wraps. The footer is one sentence carrying the LuCI link and the OpenWrt link; on a
|
||||
* wide screen they sit side by side and nothing is close to anything, but at the width where
|
||||
* the sentence breaks they land on consecutive lines. With the inherited 1.5 that is a 16px
|
||||
* line box, i.e. 16px between the two links' centres — inside the 24px envelope WCAG 2.2
|
||||
* SC 2.5.8 requires around a target smaller than 24x24, which an inline link always is.
|
||||
* Found by tools/live-audit.mjs on CI's runner and not on the maintainer's stands: the wrap
|
||||
* width depends on text metrics, so the same 568px viewport breaks the line there and not
|
||||
* here. 26px, not 24: the criterion is that the two 24px circles must not meet, so 24 is the
|
||||
* boundary rather than a pass, and 26 leaves the footer visually unchanged on one line. */
|
||||
line-height: 26px;
|
||||
}
|
||||
footer a { color: var(--fs-dim); }
|
||||
footer a:hover { color: var(--fs-accent); }
|
||||
|
||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=natflow
|
||||
PKG_VERSION:=20260531
|
||||
PKG_RELEASE:=69
|
||||
PKG_RELEASE:=70
|
||||
|
||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=114c4a602c66cec71fe268455643e95c6df14695
|
||||
PKG_SOURCE_VERSION:=bbd26afc851575c8c1b326a5d0bdfa3e6c2723c1
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
Reference in new issue
Block a user