mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 05:35:13 +08:00
🛸 Sync 2026-08-19 20:38:21
This commit is contained in:
1 parent
7549efdb5e
commit
7cb3d8fcff
55 files changed
+403
-252
No files matched your search
+2
-2
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_BAIDUDRIVE:=$(ARCH)
|
||||
|
||||
PKG_NAME:=baidudrive
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.9
|
||||
PKG_RELEASE:=12
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.10
|
||||
PKG_RELEASE:=13
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/baidudrive-runtime-v$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ddns-go
|
||||
PKG_VERSION:=6.17.5
|
||||
PKG_RELEASE:=22
|
||||
PKG_VERSION:=6.17.6
|
||||
PKG_RELEASE:=23
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkease-common-bin
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=7
|
||||
PKG_RELEASE:=8
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=3cdc853c91c55560e118f1f7e343d1ae0bd5e566
|
||||
PKG_SOURCE_VERSION:=e0457eac600cd37b128eddf251ff9344702b1d03
|
||||
PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkease
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=10
|
||||
PKG_RELEASE:=11
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=3cdc853c91c55560e118f1f7e343d1ae0bd5e566
|
||||
PKG_SOURCE_VERSION:=e0457eac600cd37b128eddf251ff9344702b1d03
|
||||
PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
@@ -11,7 +11,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkeasefull
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=3.0.5
|
||||
PKG_RELEASE:=9
|
||||
PKG_RELEASE:=10
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64
|
||||
PKG_HASH:=skip
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=3cdc853c91c55560e118f1f7e343d1ae0bd5e566
|
||||
PKG_SOURCE_VERSION:=e0457eac600cd37b128eddf251ff9344702b1d03
|
||||
PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH)
|
||||
|
||||
@@ -451,7 +451,7 @@ start_service() {
|
||||
}
|
||||
|
||||
service_started() {
|
||||
procd_set_config_changed dnsmasq
|
||||
procd_set_config_changed dhcp
|
||||
procd_set_config_changed firewall
|
||||
}
|
||||
|
||||
@@ -528,7 +528,7 @@ service_stopped() {
|
||||
[ -n "$(/etc/init.d/$CONF info | jsonfilter -q -e '@.'"$CONF"'.instances["mihomo-c"]')" ] || client_stopped
|
||||
# Server
|
||||
|
||||
procd_set_config_changed dnsmasq
|
||||
procd_set_config_changed dhcp
|
||||
procd_set_config_changed firewall
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.8.12
|
||||
PKG_RELEASE:=229
|
||||
PKG_VERSION:=26.8.19
|
||||
PKG_RELEASE:=230
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -466,12 +466,12 @@ function socks_status()
|
||||
local index = http.formvalue("index")
|
||||
local id = http.formvalue("id")
|
||||
e.index = index
|
||||
e.socks_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' > /dev/null", appname, id)) == 0
|
||||
e.socks_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '/%s' > /dev/null", appname, id)) == 0
|
||||
local use_http = uci_get(id, "http_port") or 0
|
||||
e.use_http = 0
|
||||
if tonumber(use_http) > 0 then
|
||||
e.use_http = 1
|
||||
e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E '\\+http|_http' > /dev/null", appname, id)) == 0
|
||||
e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '/%s' | grep -E '\\+http|_http' > /dev/null", appname, id)) == 0
|
||||
end
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
@@ -63,15 +63,9 @@ o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("trojan", "Trojan")
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
o:value("wireguard", "WireGuard")
|
||||
o:value("dokodemo-door", "dokodemo-door")
|
||||
o:value("tunnel", "Tunnel")
|
||||
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 })
|
||||
@@ -93,14 +87,14 @@ o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
o:depends({ protocol = "tunnel" })
|
||||
|
||||
o = s:option(Value, "d_address", translate("Destination address"))
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
o:depends({ protocol = "tunnel" })
|
||||
|
||||
o = s:option(Value, "d_port", translate("Destination port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
o:depends({ protocol = "tunnel" })
|
||||
|
||||
o = s:option(Value, "decryption", translate("Encrypt Method") .. " (decryption)")
|
||||
o.default = "none"
|
||||
@@ -411,7 +405,8 @@ 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, is_endpoint = "" })
|
||||
o:depends({ transport = "raw" })
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
|
||||
|
||||
@@ -1748,6 +1748,7 @@ function apply_redirect(m)
|
||||
end
|
||||
end
|
||||
else
|
||||
uci:revert(c_config .. "_redirect")
|
||||
sys.call("/bin/rm -f " .. tmp_uci_file)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -567,11 +567,11 @@ function gen_config_server(node)
|
||||
version = 2,
|
||||
users = users
|
||||
}
|
||||
elseif node.protocol == "dokodemo-door" then
|
||||
elseif node.protocol == "tunnel" then
|
||||
settings = {
|
||||
network = node.d_protocol,
|
||||
address = node.d_address,
|
||||
port = tonumber(node.d_port)
|
||||
allowedNetwork = node.d_protocol,
|
||||
rewriteAddress = node.d_address,
|
||||
rewritePort = tonumber(node.d_port)
|
||||
}
|
||||
elseif node.protocol == "wireguard" then
|
||||
settings = {
|
||||
@@ -1231,8 +1231,8 @@ function gen_config(var)
|
||||
tag = in_tag,
|
||||
listen = "127.0.0.1",
|
||||
port = new_port,
|
||||
protocol = "dokodemo-door",
|
||||
settings = {network = "tcp,udp", address = to_node.address, port = tonumber(to_node.port)}
|
||||
protocol = "tunnel",
|
||||
settings = {allowedNetwork = "tcp,udp", rewriteAddress = to_node.address, rewritePort = tonumber(to_node.port)}
|
||||
})
|
||||
if to_node.tls_serverName == nil then
|
||||
to_node.tls_serverName = to_node.address
|
||||
@@ -1587,8 +1587,8 @@ function gen_config(var)
|
||||
|
||||
if tcp_redir_port or udp_redir_port then
|
||||
local inbound = {
|
||||
protocol = "dokodemo-door",
|
||||
settings = {network = "tcp,udp", followRedirect = true},
|
||||
protocol = "tunnel",
|
||||
settings = {allowedNetwork = "tcp,udp", followRedirect = true},
|
||||
streamSettings = {sockopt = {tproxy = "tproxy"}},
|
||||
sniffing = {
|
||||
enabled = (xray_settings.sniffing_override_dest == "1") or (node and node.protocol == "_shunt") or false
|
||||
@@ -1614,7 +1614,7 @@ function gen_config(var)
|
||||
if tcp_redir_port then
|
||||
local tcp_inbound = api.clone(inbound)
|
||||
tcp_inbound.tag = "tcp_redir"
|
||||
tcp_inbound.settings.network = "tcp"
|
||||
tcp_inbound.settings.allowedNetwork = "tcp"
|
||||
tcp_inbound.port = tonumber(tcp_redir_port)
|
||||
tcp_inbound.streamSettings.sockopt.tproxy = tcp_proxy_way
|
||||
table.insert(inbounds, tcp_inbound)
|
||||
@@ -1623,7 +1623,7 @@ function gen_config(var)
|
||||
if udp_redir_port then
|
||||
local udp_inbound = api.clone(inbound)
|
||||
udp_inbound.tag = "udp_redir"
|
||||
udp_inbound.settings.network = "udp"
|
||||
udp_inbound.settings.allowedNetwork = "udp"
|
||||
udp_inbound.port = tonumber(udp_redir_port)
|
||||
table.insert(inbounds, udp_inbound)
|
||||
end
|
||||
@@ -1790,11 +1790,10 @@ function gen_config(var)
|
||||
table.insert(inbounds, {
|
||||
listen = "127.0.0.1",
|
||||
port = tonumber(dns_listen_port),
|
||||
protocol = "dokodemo-door",
|
||||
protocol = "tunnel",
|
||||
tag = "dns-in",
|
||||
settings = {
|
||||
address = "0.0.0.0",
|
||||
network = "tcp,udp"
|
||||
allowedNetwork = "tcp,udp"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ window.lv_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>;
|
||||
<!-- 模拟 ListValue 控件外观 -->
|
||||
<div class="cbi-input-value cbi-input-select lv-dropdown-display" id="<%=cbid%>.display" tabindex="0">
|
||||
<span id="<%=cbid%>.label" class="lv-dropdown-label" title="<%=pcdata(current_label)%>">
|
||||
<%=pcdata("("..translate("Not set")..")")%>
|
||||
<%=pcdata("( "..translate("Not Set").." )")%>
|
||||
</span>
|
||||
<span class="lv-arrow-down"></span>
|
||||
</div>
|
||||
@@ -139,7 +139,7 @@ window.lv_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>;
|
||||
});
|
||||
lv_registerAdaptive(cbid);
|
||||
setTimeout(() => {
|
||||
labelSpan.textContent = lv_ellipsisByWidth(cbid, '<%=pcdata(current_label ~= "" and current_label or ("("..translate("Not set")..")"))%>');
|
||||
labelSpan.textContent = lv_ellipsisByWidth(cbid, '<%=pcdata(current_label ~= "" and current_label or ("( "..translate("Not Set").." )"))%>');
|
||||
}, 100);
|
||||
})();
|
||||
//]]>
|
||||
|
||||
@@ -1639,6 +1639,7 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'hysteria2_hop', queryParam.mport || "");
|
||||
opt.set(dom_prefix + 'use_finalmask', !!queryParam.fm);
|
||||
opt.set(dom_prefix + 'finalmask', queryParam.fm || "");
|
||||
if (is_singbox && (queryParam.pcs || queryParam.pinSHA256)) queryParam.allowinsecure = '1';
|
||||
} else if (has_hysteria2) {
|
||||
opt.set('type', "Hysteria2");
|
||||
dom_prefix = "hysteria2_"
|
||||
@@ -1652,15 +1653,14 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'obfs_MaxPacketSize', queryParam.maxPacketSize || "1200");
|
||||
}
|
||||
opt.set(dom_prefix + 'hop', queryParam.mport || "");
|
||||
if (queryParam.pcs || queryParam.pinSHA256) queryParam.allowinsecure = '0';
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
|
||||
opt.set(dom_prefix + 'port', m.port || "443");
|
||||
|
||||
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || "");
|
||||
opt.set(
|
||||
dom_prefix + 'tls_allowInsecure',
|
||||
!((queryParam.allowinsecure ?? '0') === '0' && (queryParam.allowInsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0')
|
||||
(queryParam.allowinsecure ?? queryParam.allowInsecure ?? queryParam.insecure ?? '0') === '1'
|
||||
);
|
||||
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || queryParam.pinSHA256 || '');
|
||||
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
||||
|
||||
@@ -39,6 +39,10 @@ for sid in $(uci -q show passwall_server | sed -n 's/^passwall_server\.\([^.=]*\
|
||||
uci -q set "passwall_server.${sid}=server"
|
||||
fi
|
||||
done
|
||||
for sid in $(uci -q show passwall_server | sed -n 's/^passwall_server\.\([^.=]*\)\.protocol=dokodemo-door$/\1/p'); do
|
||||
uci -q set "passwall_server.${sid}.protocol=tunnel"
|
||||
done
|
||||
|
||||
uci -q commit passwall_server
|
||||
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-indexcache.*
|
||||
|
||||
@@ -27,6 +27,8 @@ while [ 1 -eq 1 ]; do
|
||||
*dns2socks*) cmd_check=${cmd_check//:/ } ;;
|
||||
esac
|
||||
|
||||
cmd_check=$(printf '%s' "$cmd_check" | sed 's/[^a-zA-Z0-9 ]/\\&/g')
|
||||
|
||||
filename=$(basename "$file")
|
||||
stats_file="${RESTART_STATS_DIR}/${filename}.count"
|
||||
if [ -s "$stats_file" ]; then
|
||||
@@ -38,11 +40,11 @@ while [ 1 -eq 1 ]; do
|
||||
# 检查是否超过最大重启次数
|
||||
[ "$restart_count" -ge "$MAX_RESTART_COUNT" ] && continue
|
||||
|
||||
if ! busybox pgrep -f "$cmd_check" >/dev/null; then
|
||||
if ! busybox pgrep -f "${cmd_check}" >/dev/null; then
|
||||
restart_count=$((restart_count + 1))
|
||||
echo "$restart_count" > "$stats_file"
|
||||
#echo "${cmd} 进程挂掉,重启" >> /tmp/log/passwall.log
|
||||
sh -c "nohup $cmd 2>&1 &"
|
||||
sh -c "nohup ${cmd} 2>&1 &"
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -145,13 +145,14 @@ do
|
||||
CONFIG[#CONFIG + 1] = {
|
||||
log = true,
|
||||
remarks = name .. "节点",
|
||||
currentNode = node_id and (function()
|
||||
local section = uci_get(node_id) or {}
|
||||
currentNode = (function(id)
|
||||
local section = id and uci_get(id) or nil
|
||||
if not section then return nil end
|
||||
if section[".type"] == "socks" then
|
||||
return { Socks = node_id }
|
||||
return { Socks = id }
|
||||
end
|
||||
return section
|
||||
end)() or nil,
|
||||
end)(node_id),
|
||||
set = function(o, server)
|
||||
uci_set(szType, option, server)
|
||||
o.newNodeId = server
|
||||
@@ -264,13 +265,14 @@ do
|
||||
log = true,
|
||||
id = t[".name"],
|
||||
remarks = "访问控制列表[" .. i .. "]",
|
||||
currentNode = node_id and (function()
|
||||
local section = uci_get(node_id) or {}
|
||||
currentNode = (function(id)
|
||||
local section = id and uci_get(id) or nil
|
||||
if not section then return nil end
|
||||
if section[".type"] == "socks" then
|
||||
return { Socks = node_id }
|
||||
return { Socks = id }
|
||||
end
|
||||
return section
|
||||
end)() or nil,
|
||||
end)(node_id),
|
||||
set = function(o, server)
|
||||
uci_set(t[".name"], option, server)
|
||||
o.newNodeId = server
|
||||
@@ -1469,8 +1471,6 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.tls_serverName = params.sni
|
||||
result.tls_pinSHA256 = params.pcs or params.pinsha256
|
||||
result.tls_CertByName = params.vcn
|
||||
local insecure = params.allowinsecure or params.insecure
|
||||
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
|
||||
result.hysteria2_up_mbps = params.upmbps or (sub_cfg and sub_hy_up_mbps or nil)
|
||||
result.hysteria2_down_mbps = params.downmbps or (sub_cfg and sub_hy_down_mbps or nil)
|
||||
result.hysteria2_hop = params.mport
|
||||
@@ -1482,19 +1482,26 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.hysteria2_obfs_MinPacketSize = params.minpacketsize or "512"
|
||||
result.hysteria2_obfs_MaxPacketSize = params.maxpacketsize or "1200"
|
||||
end
|
||||
|
||||
if (sub_hysteria2_type == "sing-box" and has_singbox) or (sub_hysteria2_type == "xray" and has_xray) then
|
||||
local is_singbox = sub_hysteria2_type == "sing-box" and has_singbox
|
||||
result.type = is_singbox and 'sing-box' or 'Xray'
|
||||
result.protocol = "hysteria2"
|
||||
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
|
||||
result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
|
||||
if is_singbox and (params.pcs or params.pinsha256) then
|
||||
params.allowinsecure = "1"
|
||||
end
|
||||
elseif has_hysteria2 then
|
||||
result.type = "Hysteria2"
|
||||
if params.pcs or params.pinsha256 then
|
||||
params.allowinsecure = "0"
|
||||
end
|
||||
else
|
||||
log("跳过 Hysteria2 节点,因未适配到 Hysteria2 核心程序,或未正确设置节点使用类型。")
|
||||
return nil
|
||||
end
|
||||
local insecure = params.allowinsecure or params.insecure
|
||||
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
|
||||
elseif szType == 'tuic' then
|
||||
if has_singbox then
|
||||
result.type = 'sing-box'
|
||||
|
||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.8.17
|
||||
PKG_RELEASE:=90
|
||||
PKG_RELEASE:=91
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -63,15 +63,9 @@ o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("trojan", "Trojan")
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
o:value("wireguard", "WireGuard")
|
||||
o:value("dokodemo-door", "dokodemo-door")
|
||||
o:value("tunnel", "Tunnel")
|
||||
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 })
|
||||
@@ -93,14 +87,14 @@ o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
o:depends({ protocol = "tunnel" })
|
||||
|
||||
o = s:option(Value, "d_address", translate("Destination address"))
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
o:depends({ protocol = "tunnel" })
|
||||
|
||||
o = s:option(Value, "d_port", translate("Destination port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
o:depends({ protocol = "tunnel" })
|
||||
|
||||
o = s:option(Value, "decryption", translate("Encrypt Method") .. " (decryption)")
|
||||
o.default = "none"
|
||||
@@ -416,7 +410,8 @@ 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, is_endpoint = "" })
|
||||
o:depends({ transport = "raw" })
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
|
||||
|
||||
@@ -575,11 +575,11 @@ function gen_config_server(node)
|
||||
version = 2,
|
||||
users = users
|
||||
}
|
||||
elseif node.protocol == "dokodemo-door" then
|
||||
elseif node.protocol == "tunnel" then
|
||||
settings = {
|
||||
network = node.d_protocol,
|
||||
address = node.d_address,
|
||||
port = tonumber(node.d_port)
|
||||
allowedNetwork = node.d_protocol,
|
||||
rewriteAddress = node.d_address,
|
||||
rewritePort = tonumber(node.d_port)
|
||||
}
|
||||
elseif node.protocol == "wireguard" then
|
||||
settings = {
|
||||
@@ -1227,8 +1227,8 @@ function gen_config(var)
|
||||
tag = in_tag,
|
||||
listen = "127.0.0.1",
|
||||
port = new_port,
|
||||
protocol = "dokodemo-door",
|
||||
settings = {network = "tcp,udp", address = to_node.address, port = tonumber(to_node.port)}
|
||||
protocol = "tunnel",
|
||||
settings = {allowedNetwork = "tcp,udp", rewriteAddress = to_node.address, rewritePort = tonumber(to_node.port)}
|
||||
})
|
||||
if to_node.tls_serverName == nil then
|
||||
to_node.tls_serverName = to_node.address
|
||||
@@ -1712,11 +1712,10 @@ function gen_config(var)
|
||||
table.insert(inbounds, {
|
||||
listen = "127.0.0.1",
|
||||
port = tonumber(dns_listen_port),
|
||||
protocol = "dokodemo-door",
|
||||
protocol = "tunnel",
|
||||
tag = "dns-in",
|
||||
settings = {
|
||||
address = "0.0.0.0",
|
||||
network = "tcp,udp"
|
||||
allowedNetwork = "tcp,udp"
|
||||
}
|
||||
})
|
||||
local direct_type_dns = {
|
||||
@@ -1949,8 +1948,8 @@ function gen_config(var)
|
||||
if redir_port then
|
||||
local inbound = {
|
||||
port = tonumber(redir_port),
|
||||
protocol = "dokodemo-door",
|
||||
settings = {network = "tcp,udp", followRedirect = true},
|
||||
protocol = "tunnel",
|
||||
settings = {allowedNetwork = "tcp,udp", followRedirect = true},
|
||||
streamSettings = {sockopt = {tproxy = "tproxy"}},
|
||||
sniffing = {
|
||||
enabled = xray_settings.sniffing_override_dest == "1" or node.protocol == "_shunt"
|
||||
@@ -1975,13 +1974,13 @@ function gen_config(var)
|
||||
|
||||
local tcp_inbound = api.clone(inbound)
|
||||
tcp_inbound.tag = "tcp_redir"
|
||||
tcp_inbound.settings.network = "tcp"
|
||||
tcp_inbound.settings.allowedNetwork = "tcp"
|
||||
tcp_inbound.streamSettings.sockopt.tproxy = tcp_proxy_way
|
||||
table.insert(inbounds, tcp_inbound)
|
||||
|
||||
local udp_inbound = api.clone(inbound)
|
||||
udp_inbound.tag = "udp_redir"
|
||||
udp_inbound.settings.network = "udp"
|
||||
udp_inbound.settings.allowedNetwork = "udp"
|
||||
table.insert(inbounds, udp_inbound)
|
||||
end
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@ while [ "$ENABLED" -eq 1 ]; do
|
||||
for filename in $(ls ${TMP_SCRIPT_FUNC_PATH} | grep -v "^_"); do
|
||||
cmd=$(cat ${TMP_SCRIPT_FUNC_PATH}/${filename})
|
||||
cmd_check=$(echo $cmd | awk -F '>' '{print $1}')
|
||||
icount=$(busybox pgrep -f "$(echo $cmd_check)" | wc -l)
|
||||
escape_cmd="$(echo $cmd_check | sed 's/[^a-zA-Z0-9]/\\&/g')"
|
||||
icount=$(busybox pgrep -f "${escape_cmd}" | wc -l)
|
||||
if [ $icount = 0 ]; then
|
||||
#echo "${cmd} crashed, restarting." >> /tmp/log/passwall2.log
|
||||
log 0 "${cmd} crashed, restarting."
|
||||
eval $(echo "nohup ${cmd} 2>&1 &") >/dev/null 2>&1 &
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -556,7 +556,7 @@ case $action in
|
||||
;;
|
||||
"link_meta")
|
||||
[[ -d "$META_DIR/meta" ]] || mkdir -p "$META_DIR/meta"
|
||||
ln -sf "$META_DIR" /tmp/run/istore-meta
|
||||
[ -e /tmp/run/istore-meta ] || ln -sf "$META_DIR" /tmp/run/istore-meta
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
|
||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=25
|
||||
PKG_RELEASE:=26
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -8,12 +8,6 @@
|
||||
"background_color": "#f6f8fa",
|
||||
"theme_color": "#f6f8fa",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/luci-static/footstrap/app-icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/luci-static/footstrap/app-icon-512.png",
|
||||
"sizes": "512x512",
|
||||
|
||||
@@ -149,7 +149,7 @@ function resolveLen(token, dflt) {
|
||||
* re-measured against the widths of the density before it. */
|
||||
/* The last resort, stated ONCE so the fallbacks and the sanity net below cannot restate the
|
||||
* stylesheet's widths in two different places. Reaching for these means the measurement failed. */
|
||||
const GEOM_DFLT = { contentMin: 500, sidebarW: 224, railW: 68, contentPad: 56 };
|
||||
const GEOM_DFLT = { contentMin: 500, sidebarW: 224, railW: 68, contentPad: 56, contentMax: 1280 };
|
||||
|
||||
let _geom = null, _geomDensity = null, _geomWarned = false;
|
||||
function shellGeometry() {
|
||||
@@ -162,10 +162,14 @@ function shellGeometry() {
|
||||
contentMin: px('--fs-content-min', GEOM_DFLT.contentMin),
|
||||
sidebarW: px('--fs-sidebar-w', GEOM_DFLT.sidebarW),
|
||||
railW: px('--fs-rail-w', GEOM_DFLT.railW),
|
||||
/* the token is ONE side's padding; the column loses it twice. Kept as `tokenPad` because it
|
||||
* is only the FALLBACK — the gutter that counts is measured, see measureShell() */
|
||||
tokenPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2,
|
||||
contentPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2
|
||||
/* the token is ONE side's padding; the column loses it twice. It is also only the FALLBACK:
|
||||
* measureShell() overwrites this field with the gutter the column actually got, and until it
|
||||
* has (the login page has no `.fs-content`, and nothing has measured before the first
|
||||
* fitter) the token is the honest answer. */
|
||||
contentPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2,
|
||||
/* the cap the column stops growing at, so the model knows where the surplus becomes margin
|
||||
* rather than gutter — see columnWidth() */
|
||||
contentMax: px('--fs-content-max', GEOM_DFLT.contentMax)
|
||||
};
|
||||
/* Plausibility, and it costs one comparison: the rail IS the sidebar collapsed, so
|
||||
* 0 < railW < sidebarW holds by construction. Both known ways this measurement fails destroy
|
||||
@@ -186,7 +190,6 @@ function shellGeometry() {
|
||||
+ 'renamed, or a foreign sheet is reaching the measurement probe.');
|
||||
}
|
||||
_geom = sane ? g : Object.assign({}, GEOM_DFLT);
|
||||
if (_geom.tokenPad === undefined) _geom.tokenPad = _geom.contentPad;
|
||||
/* the token is only the fallback; the gutter that counts is the one measureShell() read */
|
||||
if (_shellPad != null) _geom.contentPad = _shellPad;
|
||||
return _geom;
|
||||
@@ -210,21 +213,34 @@ function shellGeometry() {
|
||||
* under the reader's thumb). So the numbers are taken when a fitter runs — which is on every resize
|
||||
* and every content mutation, and never during a flick, because `fitChrome()` defers exactly like
|
||||
* every other measuring pass — and everything after that reads what was stored. Nothing they
|
||||
* describe can change without a resize, and a resize schedules a fit.
|
||||
* describe can change without a resize, and a resize schedules a fit; a resize that lands DURING a
|
||||
* scroll is deferred with the rest, so a mid-scroll answer is the geometry as of the last still
|
||||
* moment, which is the trade this whole file is built on.
|
||||
*
|
||||
* Unlike the token probe above, a hostile app's declaration is not a threat here: `.fs-content`
|
||||
* carries no chrome mark, so an app CAN restyle it, and if it did then that padding IS the column's
|
||||
* real gutter. What the probe must not report is a foreign answer for OUR layout; this reports the
|
||||
* layout as it stands. Before any fitter has run — the login page has no `.fs-content` at all — the
|
||||
* token stands in. */
|
||||
let _shellOuter = 0, _shellPad = null;
|
||||
let _shellOuter = 0, _shellPad = null, _padAt = null;
|
||||
function measureShell() {
|
||||
/* the window's own width, every time: fitShell read it before this function existed and the
|
||||
* value is what everything downstream is measured against */
|
||||
_shellOuter = document.documentElement.clientWidth;
|
||||
/* The GUTTER, though, is resolved style, and this runs on every mutation batch — once a second
|
||||
* on any polled page. The two things that move it are the width (a media query re-paddings the
|
||||
* column below 767px) and the density (the token is `calc(28px * var(--fs-density-space))`), so
|
||||
* both are the key and an unchanged page resolves nothing. Same trade as the token memo above,
|
||||
* for the same reason. */
|
||||
const key = (document.documentElement.getAttribute('data-density') || '') + '|' + _shellOuter;
|
||||
if (_padAt === key && _shellPad != null) return;
|
||||
const host = document.querySelector('.fs-content');
|
||||
const cs = host ? getComputedStyle(host) : null;
|
||||
const v = cs ? parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight) : NaN;
|
||||
/* a failed read leaves the key unset, so the next pass tries again rather than caching a miss */
|
||||
if (Number.isFinite(v) && v >= 0) {
|
||||
_shellPad = v;
|
||||
_padAt = key;
|
||||
if (_geom) _geom.contentPad = v;
|
||||
}
|
||||
}
|
||||
@@ -232,7 +248,15 @@ function measureShell() {
|
||||
function columnWidth(g, state) {
|
||||
/* narrow OR top: in both the chrome is above the content, not beside it */
|
||||
const cut = (state.narrow || state.top) ? 0 : (state.rail ? g.railW : g.sidebarW);
|
||||
return Math.max(0, state.outerW - cut - g.contentPad);
|
||||
/* AND THE COLUMN STOPS GROWING. `.fs-content` is `max-width: var(--fs-content-max); margin: 0
|
||||
* auto`, so past roughly a 1500px window the surplus becomes margin and not column: without the
|
||||
* cap this answered ~2280 on a 2560px sidebar layout for a column that is 1224 wide. No caller
|
||||
* can reach that today — both ask a LOWER bound (`< --fs-content-min` here, `< CRAMPED` in
|
||||
* fs-select) and the cap only binds where both are clear by a factor of two — but this is the
|
||||
* exported answer to "how wide is the content column", and an answer that is only true below
|
||||
* 1500px is the kind of thing the next caller inherits without being told. */
|
||||
const room = Math.min(state.outerW - cut, g.contentMax);
|
||||
return Math.max(0, room - g.contentPad);
|
||||
}
|
||||
|
||||
function fitShell() {
|
||||
@@ -546,9 +570,10 @@ return baseclass.extend({
|
||||
});
|
||||
},
|
||||
|
||||
/* exported for the unit suite (tests/chrome-geometry.test.mjs): the cut is pure arithmetic over
|
||||
* four measured numbers, and driving it directly is the only way to hold every combination of
|
||||
* layout, rail and width without a browser */
|
||||
/* exported for the unit suite in the theme's own repository (tests/chrome-geometry.test.mjs — no
|
||||
* tests ship in the package): the cut is pure arithmetic over a handful of measured numbers, and
|
||||
* driving it directly is the only way to hold every combination of layout, rail and width
|
||||
* without a browser */
|
||||
columnWidth,
|
||||
wireRail
|
||||
});
|
||||
@@ -86,6 +86,9 @@ function fittersEnabled() {
|
||||
function run() {
|
||||
if (!fittersEnabled()) return;
|
||||
runAll(_fitters, 'fitter');
|
||||
/* the page is settled now, so this is the position the next mutation must be measured against —
|
||||
* unless a correction is already on its way, which would make this reference the drifted one */
|
||||
if (!_anchorPending) rememberRest();
|
||||
}
|
||||
|
||||
/* ---- IS THE PAGE MOVING RIGHT NOW? — ASKED OF THE POSITION, NEVER OF THE EVENTS ----
|
||||
@@ -190,6 +193,8 @@ function sampleMotion() {
|
||||
}
|
||||
if (scrolling()) { requestAnimationFrame(sampleMotion); return; }
|
||||
_sampling = false;
|
||||
/* the reader has stopped: this is a still moment, and the reference for the next tick */
|
||||
rememberRest();
|
||||
/* the page has held still for SCROLL_IDLE: whatever was put off may run now */
|
||||
if (_deferred) {
|
||||
_deferred = false;
|
||||
@@ -295,53 +300,100 @@ function watch(el) {
|
||||
*
|
||||
* It never fights the user: a page at the very top has no offset to give back and is left alone, and
|
||||
* a drift under a pixel is rounding rather than movement. */
|
||||
/* DOES THE ENGINE ANCHOR AT ALL? Chromium and Firefox compensate a height change above the viewport
|
||||
* by themselves — the reader stays where they were and the scroll offset moves under them. WebKit
|
||||
* has never implemented it, so on Safari and on every iPhone the same poll tick moves the page:
|
||||
* measured with the engine's anchoring suppressed, a 120px growth above the fold moves the reader
|
||||
* 120px, and 0px with it on.
|
||||
*
|
||||
* The theme cannot do this job everywhere, only where nobody else is: correcting the offset in an
|
||||
* engine that also corrects it means two corrections and a page that jumps the other way. So it is
|
||||
* asked of the platform rather than of a browser name — `overflow-anchor` is the property that turns
|
||||
* the feature off, and an engine that does not know the property does not have the feature. */
|
||||
const ENGINE_ANCHORS = (() => {
|
||||
/* DEV SWITCH, and the reason the fallback can be gated at all: `localStorage.fsEngineAnchor =
|
||||
* 'off'` makes any engine take the path Safari takes. Without it the branch could only be
|
||||
* exercised on a machine with Safari on it, which is neither CI nor most of the people who work
|
||||
* on this — the same reason `fsFit` and `fsAnchor` exist. */
|
||||
try { if (localStorage.getItem('fsEngineAnchor') === 'off') return false; }
|
||||
catch (e) { /* no storage, no switch */ }
|
||||
try { return typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('overflow-anchor', 'auto'); }
|
||||
catch (e) { return true; } /* unreadable: assume it is handled rather than fight it */
|
||||
})();
|
||||
|
||||
/* WHAT THE READER WAS LOOKING AT, CAPTURED WHILE THE PAGE WAS STILL.
|
||||
*
|
||||
* `anchorRef()` below is called from the mutation observer, which runs AFTER the DOM changed: it
|
||||
* captures a position the poll has already moved, which is exactly right for the FITTERS (they have
|
||||
* not run yet) and blind to the mutation itself. On an engine that anchors, that split does not
|
||||
* matter — the engine covers the other half. On one that does not, this is the half nobody covers,
|
||||
* so the reference is kept from the last still moment instead. */
|
||||
let _rest = null;
|
||||
function rememberRest() {
|
||||
if (ENGINE_ANCHORS || scrolling()) return;
|
||||
const ref = anchorRef();
|
||||
/* the offset it was taken at travels with it: a reference is only about the page, and the page
|
||||
* moving under the reader is a different fact from the reader moving through it */
|
||||
_rest = ref ? { el: ref.el, top: ref.top, at: scrollTop() } : null;
|
||||
}
|
||||
|
||||
/* -> the reference to correct against: the pre-mutation one where the engine leaves that to us, the
|
||||
* post-mutation one everywhere else, which is what this file has always used. A remembered
|
||||
* reference is worth using only while it still describes the reader's position — an element that
|
||||
* left the document, or that the reader has since scrolled a screen away from, is not one. */
|
||||
function anchorFor() {
|
||||
if (ENGINE_ANCHORS) return anchorRef();
|
||||
if (!_rest || !_rest.el.isConnected) return anchorRef();
|
||||
/* THE READER MOVED, NOT THE PAGE. A reference taken at one offset says nothing about a document
|
||||
* seen from another: correcting against it would drag the page back to where the reader had
|
||||
* scrolled FROM. Anything but the offset it was captured at means take a fresh one — which, on
|
||||
* the mutation path, is the same as not correcting this tick. */
|
||||
if (_rest.at !== scrollTop()) return anchorRef();
|
||||
return _rest;
|
||||
}
|
||||
|
||||
function anchorRef() {
|
||||
/* NOT WHILE THE READER SCROLLS. Every rect read here is a forced layout, and this runs on every
|
||||
* content mutation — i.e. once a second, in the middle of a flick, which is the work iOS holds
|
||||
* the main thread back to avoid. The compensation exists for a page the reader is looking at,
|
||||
* not for one they are already moving; while they scroll there is nothing to keep still. */
|
||||
if (scrolling()) return null;
|
||||
const frames = document.querySelectorAll('#view .cbi-section, #view > .cbi-section-node, #view > div');
|
||||
for (const el of frames) {
|
||||
const r = el.getBoundingClientRect();
|
||||
/* the frame the viewport's top edge cuts through */
|
||||
if (r.top <= 0 && r.bottom > 0) return { el, top: r.top };
|
||||
|
||||
/* WHAT THE READER IS LOOKING AT, ASKED OF THE PAGE RATHER THAN OF A SELECTOR LIST.
|
||||
*
|
||||
* This walked a list of frames and took the one the fold cut through, which misses the case that
|
||||
* matters: a poll tick that grows something INSIDE that frame, above the viewport, leaves the
|
||||
* frame's own top exactly where it was — drift 0, measured — while everything after it moves.
|
||||
* Taking the deepest element AT the fold instead is both cheaper (one hit test, no rect walk)
|
||||
* and the same thing the engine's own anchoring picks, so the two agree about what "still" means.
|
||||
*
|
||||
* A data table is never the anchor: the fit pass deliberately falsifies its layout inside one
|
||||
* pass, so the theme excludes it from the engine's anchoring too (`overflow-anchor: none`,
|
||||
* theme/30-tables.css). Climbing to the nearest ancestor that is not one keeps a reference that
|
||||
* does not lie. */
|
||||
const host = document.getElementById('view');
|
||||
if (!host) return null;
|
||||
const box = host.getBoundingClientRect();
|
||||
const x = Math.round(box.left + (Math.min(box.width, window.innerWidth || box.width) / 2));
|
||||
/* BELOW THE CHROME, not at y=1: the bar is sticky and owns the first rows of the viewport, so a
|
||||
* hit test at the very top returns the chrome and the page gets no anchor at all (measured: at
|
||||
* 390px the hit was `nav.fs-sidebar` every time and no correction ever ran). The chrome says
|
||||
* where it ends — `[data-fs-chrome]` is the mark it already carries, so nothing here names a
|
||||
* height or a selector that a layout change could move. */
|
||||
let y = 1;
|
||||
let el = document.elementFromPoint(x, y);
|
||||
const chrome = el && el.closest ? el.closest('[data-fs-chrome]') : null;
|
||||
if (chrome) {
|
||||
y = Math.max(1, Math.round(chrome.getBoundingClientRect().bottom) + 1);
|
||||
el = document.elementFromPoint(x, y);
|
||||
}
|
||||
return null;
|
||||
if (!el || !host.contains(el)) return null;
|
||||
const table = el.closest('.table.fs-dt');
|
||||
if (table) el = table.parentElement;
|
||||
if (!el || !host.contains(el)) return null;
|
||||
return { el, top: el.getBoundingClientRect().top };
|
||||
}
|
||||
/* THE CORRECTION IS UNCONDITIONAL, AND IT IS A NO-OP WHERE THE ENGINE DOES THE JOB.
|
||||
*
|
||||
* There used to be a watching phase here: the first three mutations were only measured, and a
|
||||
* reference that had held still meant "this engine anchors, never touch the offset again". It was
|
||||
* wrong in the way that mattered. WebKit does not anchor, but the drift it leaves is often small —
|
||||
* a value grew a line, a row appeared — so three quiet samples were easy to come by, the
|
||||
* compensation switched itself off for the life of the document, and the page walked downwards a
|
||||
* few pixels at a time, once per poll tick. That is exactly how it was reported: "jerks a little
|
||||
* downwards, all the time".
|
||||
*
|
||||
* Correcting always is safe because the correction is computed from the REFERENCE, not from the
|
||||
* scroll offset: an engine that anchors has already put the reference back by the time this reads
|
||||
* it — the read forces layout, and the adjustment happens during layout — so the drift is zero and
|
||||
* nothing is written. Measuring the OFFSET instead reads an anchoring adjustment as a fault and
|
||||
* corrects a correction, which is how an earlier version made Chromium worse (10 movements up to
|
||||
* 1616px against 0 without it); that mistake is not in this shape.
|
||||
*
|
||||
* Two guards remain, and both are about not fighting the reader: a page at the very top has no
|
||||
* offset to give back, and a drift under a pixel is rounding rather than movement. */
|
||||
/* ONCE PER FRAME, AFTER EVERYTHING HAS LANDED — not once per mutation batch.
|
||||
*
|
||||
* A poll tick does not arrive as one mutation: luci-mod-status fills its sections one after another,
|
||||
* and each fill wakes this observer. Correcting on each of them measures a page that is still being
|
||||
* written: the reference is read between two batches, the drift is computed against a layout that
|
||||
* the next batch immediately invalidates, and the correction is wrong by whatever the rest of the
|
||||
* tick brought. The next tick corrects the other way. That is a page that jerks while nobody
|
||||
* touches it, and it is what was reported — with a telling detail: it went away whenever a
|
||||
* diagnostic probe that forced layout on EVERY frame was loaded, because then every reading this
|
||||
* code took happened to be against a settled layout.
|
||||
*
|
||||
* So the reference is taken at the first mutation of a batch and the correction is applied in a
|
||||
* requestAnimationFrame — after the batch, after style and layout have settled, once. A newer
|
||||
* reference is not taken while one is pending: the first one is where the reader actually was. */
|
||||
|
||||
let _anchorPending = null;
|
||||
let _anchorFrame = 0;
|
||||
/* DEV SWITCH, for the device that has the problem: `localStorage.fsAnchor = 'off'` stops the theme
|
||||
@@ -362,6 +414,10 @@ function scheduleAnchor(ref) {
|
||||
const pending = _anchorPending;
|
||||
_anchorPending = null;
|
||||
applyAnchor(pending);
|
||||
/* AFTER the correction, never before: the reference this file keeps for engines with no
|
||||
* anchoring of their own must describe the page as the reader now sees it, or the next tick
|
||||
* measures a drift that has already been paid and pays it twice. */
|
||||
rememberRest();
|
||||
});
|
||||
}
|
||||
function applyAnchor(ref) {
|
||||
@@ -373,6 +429,11 @@ function applyAnchor(ref) {
|
||||
if (at <= 0) return;
|
||||
const drift = ref.el.getBoundingClientRect().top - ref.top;
|
||||
if (Math.abs(drift) < 1) return;
|
||||
/* A CORRECTION IS A SCROLL THE READER DID NOT ASK FOR, so an absurd one is a bug rather than a
|
||||
* fix: a view that replaced its whole subtree can move a reference by thousands of pixels, and
|
||||
* jumping there is worse than leaving the page where it is. One viewport is the most a single
|
||||
* tick can honestly account for. */
|
||||
if (Math.abs(drift) > (window.innerHeight || 0) + 200) return;
|
||||
if (sc) sc.scrollTop = at + drift;
|
||||
else window.scrollTo(0, at + drift);
|
||||
}
|
||||
@@ -393,8 +454,11 @@ function applyAnchor(ref) {
|
||||
function observeContent() {
|
||||
if (_mo) return;
|
||||
_mo = new MutationObserver(() => {
|
||||
/* what the reader is looking at, before the poll's mutation and the fitters move anything */
|
||||
const ref = anchorRef();
|
||||
/* what the reader is looking at, before the poll's mutation and the fitters move anything —
|
||||
* see anchorFor(): where the engine does no anchoring of its own that reference comes from
|
||||
* the last still moment, because by the time this callback runs the poll has already moved
|
||||
* the page */
|
||||
const ref = anchorFor();
|
||||
run();
|
||||
scheduleAnchor(ref);
|
||||
});
|
||||
|
||||
@@ -22,7 +22,12 @@
|
||||
* what a full load does anyway. docs/spa-router.md.
|
||||
*
|
||||
* The path->node half lives in fs-menutree.js (the chrome needs it too); the "has a view poisoned
|
||||
* this document with its CSS?" half in fs-sheets.js. */
|
||||
* this document with its CSS?" half in fs-sheets.js.
|
||||
*
|
||||
* WHERE THE PATHS IN THESE COMMENTS RESOLVE: `docs/…`, `tools/…` and `tests/…` name the theme's own
|
||||
* repository (https://github.com/VizzleTF/luci-theme-footstrap), not the tree this file is read in —
|
||||
* the package ships the runtime and nothing else, so none of the three directories travels with it.
|
||||
* Anything a user is asked to quote carries a URL instead; a comment may name the path. */
|
||||
|
||||
/* --- stray-interval teardown for SPA nav ---
|
||||
* A full load kills every window.setInterval the outgoing page set; SPA nav does not, so a view's
|
||||
@@ -1393,9 +1398,13 @@ function wireRouter() {
|
||||
|
||||
const broken = contractBreaks();
|
||||
if (broken.length) {
|
||||
/* the URL, not a repository path: this package ships inside openwrt/luci as well, where a
|
||||
* `docs/` directory does not exist — and this is the one line a stranger is asked to quote,
|
||||
* on the branch where the theme has already switched half of itself off. */
|
||||
console.error('footstrap: this luci-base has no ' + broken.join(', ') +
|
||||
' — the client router stays off and every link is a full page load, which is what the ' +
|
||||
'theme did before it existed. Please report this line: docs/spa-router.md');
|
||||
'theme did before it existed. Please report this line: ' +
|
||||
'https://github.com/VizzleTF/luci-theme-footstrap/blob/main/docs/spa-router.md');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1546,12 +1555,14 @@ return baseclass.extend({
|
||||
wire: wireRouter,
|
||||
wireVisibility,
|
||||
onNavigate,
|
||||
/* exported for the unit suite (tests/router-contract.test.mjs), which drives it against a
|
||||
* hand-broken `L` — the one way to see the OFF branch without a router that ships one */
|
||||
/* exported for the unit suite in the theme's own repository (tests/router-contract.test.mjs —
|
||||
* it is not part of the package, which ships no tests), where it is driven against a
|
||||
* hand-broken `L`: the one way to see the OFF branch without a router that ships one */
|
||||
contractBreaks,
|
||||
/* likewise: tests/interval-pause.test.mjs drives the navigation sweep around a visibilitychange,
|
||||
* and tests/session-expiry.test.mjs reads the verdict the interceptors reached. navigate() is
|
||||
* the real caller of the first and `_expired` gates the second — nothing else may call either. */
|
||||
/* likewise, and likewise out-of-package: interval-pause.test.mjs drives the navigation sweep
|
||||
* around a visibilitychange and session-expiry.test.mjs reads the verdict the interceptors
|
||||
* reached. navigate() is the real caller of the first and `_expired` gates the second — nothing
|
||||
* else may call either. */
|
||||
clearViewIntervals,
|
||||
sessionExpired,
|
||||
/* fs-search warms the pages this admin actually uses (its recents) and the arrow-key-highlighted
|
||||
|
||||
@@ -612,8 +612,8 @@ function fitTables() {
|
||||
/* the column's width, not the window's: in the sidebar layout they differ by the sidebar,
|
||||
* and an 800px window whose column is 520px used to clear CRAMPED and leave the table
|
||||
* unstacked at full width, clipped by `.fs-main { overflow-x: clip }` until the reader
|
||||
* held still. `chrome.contentWidth()` answers from memoised tokens and attributes — no
|
||||
* layout is read here either. */
|
||||
* held still. `chrome.contentWidth()` answers from what the last fitter measured plus the
|
||||
* layout attributes — no layout is read here either. */
|
||||
applyDecision(t, known || { stack: chrome.contentWidth() < CRAMPED, drop: false, breakCol: -1 });
|
||||
fit.deferMeasurement();
|
||||
return;
|
||||
|
||||
@@ -5,12 +5,52 @@
|
||||
'require fs-menutree as tree';
|
||||
'require fs-chrome as chrome';
|
||||
'require fs-router as router';
|
||||
'require fs-appearance as appearance';
|
||||
'require fs-overview as overview';
|
||||
'require fs-prefs as prefs';
|
||||
'require fs-sheets as sheets';
|
||||
'require fs-search as search';
|
||||
|
||||
/* PAGE MODULES: TWO OF THESE MODULES ARE FOR ONE PAGE EACH, AND USED TO SHIP WITH EVERY PAGE.
|
||||
*
|
||||
* `fs-appearance` draws the Appearance controls on System -> System and `fs-overview` reshapes
|
||||
* Status -> Overview; each watches `body[data-page]` and does nothing anywhere else, which is the
|
||||
* right design — a theme may not register a dispatcher node, so it cannot own a route. But they
|
||||
* were `require`d in the directive prologue, and a pragma is a hard dependency: luci.js fetches and
|
||||
* evaluates them before this file's factory runs, on EVERY admin page. Measured after terser: 11.5
|
||||
* KB for the Appearance panel and 3.8 KB for the Overview one, on every cold visit to a page that
|
||||
* has neither.
|
||||
*
|
||||
* So the pragma is replaced by the same observation they were doing for themselves, once, here: on
|
||||
* the page they belong to — and only there — the module is required and wired. What each of them
|
||||
* then does is unchanged, including its own observer: `wire()` re-checks the page synchronously, so
|
||||
* a module that arrives after the stamp still starts watching. The dependency EDGE is what moved,
|
||||
* not the behaviour.
|
||||
*
|
||||
* The map duplicates a page name that also lives inside each module, and `npm run page-modules`
|
||||
* derives both sides and fails if they drift — the same trade as the Appearance axes, which are
|
||||
* implemented twice on purpose and held by a gate. */
|
||||
const PAGE_MODULES = {
|
||||
'admin-system-system': 'fs-appearance',
|
||||
'admin-status-overview': 'fs-overview'
|
||||
};
|
||||
const _pageModules = new Map();
|
||||
function wirePageModules() {
|
||||
/* through window.L, never the factory's `L`: that one carries no require() of its own */
|
||||
const RT = window.L;
|
||||
const load = () => {
|
||||
const name = PAGE_MODULES[document.body.getAttribute('data-page') || ''];
|
||||
if (!name || _pageModules.has(name)) return;
|
||||
_pageModules.set(name, RT.require(name).then((m) => m.wire()).catch((e) => {
|
||||
/* a page module that will not load costs its own page's extras and nothing else, so it
|
||||
* is dropped from the map and retried the next time that page comes up */
|
||||
_pageModules.delete(name);
|
||||
console.error('footstrap: ' + name + ' did not load', e);
|
||||
}));
|
||||
};
|
||||
/* the server's stamp is already in the DOM; every later one is the router's */
|
||||
new MutationObserver(load).observe(document.body, { attributes: true, attributeFilter: [ 'data-page' ] });
|
||||
load();
|
||||
}
|
||||
|
||||
/* The chrome BOOTSTRAP: load the menu tree once, hand it to the parts that need it, and wire them
|
||||
* in the right order. It renders nothing itself — every piece lives in its own module:
|
||||
*
|
||||
@@ -58,21 +98,17 @@ return baseclass.extend({
|
||||
fit.add(chrome.fitChrome);
|
||||
|
||||
chrome.renderChrome();
|
||||
/* 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. */
|
||||
appearance.wire();
|
||||
/* after setTree(): the palette indexes that tree — lazily, on its first open, but its
|
||||
* recent-pages list is recorded from the first navigation onwards */
|
||||
search.wire();
|
||||
chrome.wireRail();
|
||||
chrome.wireIndicatorCounts();
|
||||
/* BEFORE router.wire(): the router restamps body[data-page] on every SPA navigation,
|
||||
* and that attribute is what fs-overview keys off. Wiring its observer after the
|
||||
* and that attribute is what the page modules key off. Wiring the observer after the
|
||||
* router's would still work today (the first stamp is the server's, already in the
|
||||
* DOM), but it would make a route change racy against a listener that is not attached
|
||||
* yet — cheap to order correctly, expensive to debug. */
|
||||
overview.wire();
|
||||
wirePageModules();
|
||||
router.wire();
|
||||
router.wireVisibility();
|
||||
/* fs-chrome's renderTabMenu warns about exactly this, and the root chain was left bare: a
|
||||
|
||||
@@ -69,6 +69,18 @@
|
||||
* tools/mirror.mjs; the third restated the literal until it had a name. */
|
||||
--fs-row-rail: inset 2px 0 0 var(--fs-accent);
|
||||
|
||||
/* THE HAIRLINE, named because it is a decision and not an accident. Every separator in
|
||||
* this theme is the same one-pixel line in the same border colour — 69 declarations said
|
||||
* so in longhand, which is 69 places to miss when the answer changes (a 2px line for a
|
||||
* high-contrast mode, a softer colour, a logical-property rewrite). The shorthand is the
|
||||
* token, so `border`, `border-top` and friends all take it, and the private tier means it
|
||||
* costs two characters in the shipped sheet rather than twenty-six. */
|
||||
--fs-hairline: 1px solid var(--fs-border);
|
||||
/* A FIELD REACTING TO FOCUS: the ring arrives and the edge changes colour together, and
|
||||
* they must stay together — a transition list that drifts apart shows the border moving
|
||||
* before the ring. Eleven controls ride this. */
|
||||
--fs-field-transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
|
||||
/* the theme's ONE colour literal, deliberately: black at .7 is the absence of light
|
||||
* behind a dialog, not a tint of any token — a palette-tinted scrim over a dark page
|
||||
* dims nothing. A token so the RULES stay literal-free. */
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
hr {
|
||||
margin: var(--fs-space-5) 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--fs-border);
|
||||
border-bottom: var(--fs-hairline);
|
||||
}
|
||||
|
||||
strong {
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
/* was 17px 20px 18px 17px — an asymmetric inset nobody chose; it is a plate, so it takes
|
||||
* the card's own gutter on every side. */
|
||||
padding: var(--fs-card-pad);
|
||||
border-top: 1px solid var(--fs-border);
|
||||
border-top: var(--fs-hairline);
|
||||
border-radius: 0 0 var(--fs-radius-lg) var(--fs-radius-lg);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
padding: var(--fs-space-2) var(--fs-space-3);
|
||||
margin: 0 0 var(--fs-space-4);
|
||||
background: linear-gradient(to bottom, var(--fs-panel), var(--fs-panel2));
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
display: flex;
|
||||
flex: 0;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
/* a modal is a CARD — the large radius, like every other panel */
|
||||
border-radius: var(--fs-radius-lg);
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
/* box-shadow: theme/70-modal.css — absorbed. It was `0 0 3px var(--fs-panel2)`, a shadow in
|
||||
* the colour of the page (in footstrap light panel2 and bg are the same hex), so the theme
|
||||
* gives every .modal --fs-shadow-pop instead and this one was dead. */
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
}
|
||||
|
||||
.ifacebox {
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
margin: 0 var(--fs-space-3);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
@@ -190,7 +190,7 @@
|
||||
}
|
||||
|
||||
.ifacebox .ifacebox-head {
|
||||
border-bottom: 1px solid var(--fs-border);
|
||||
border-bottom: var(--fs-hairline);
|
||||
/* the head sits INSIDE the box's 1px border, so its own radius is the box's less that
|
||||
* border — restating a smaller literal is how the two drifted apart before. */
|
||||
border-radius: calc(var(--fs-radius-sm) - 1px) calc(var(--fs-radius-sm) - 1px) 0 0;
|
||||
@@ -233,7 +233,7 @@
|
||||
.ifacebadge {
|
||||
display: inline-block;
|
||||
flex-direction: row;
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
padding: var(--fs-space-0-5);
|
||||
/* surface: theme/45-misc.css (flat) — absorbed */
|
||||
color: var(--fs-text);
|
||||
@@ -409,7 +409,7 @@
|
||||
.uci-dialog div > ins,
|
||||
.uci-dialog div > var {
|
||||
margin-bottom: var(--fs-space-0-5);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
line-height: var(--fs-leading-tight);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -571,7 +571,7 @@
|
||||
min-width: min(210px, 100%);
|
||||
max-width: 100%;
|
||||
/* width/style only; radius: theme/60-inputs.css — absorbed */
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
opacity: 0;
|
||||
@@ -587,7 +587,7 @@
|
||||
text-overflow: ellipsis;
|
||||
margin: .25em .25em 0 .25em;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid var(--fs-border);
|
||||
border-bottom: var(--fs-hairline);
|
||||
}
|
||||
|
||||
.cbi-filebrowser .cbi-button-positive {
|
||||
@@ -625,7 +625,7 @@
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.125em .25em -.125em;
|
||||
padding: 0 0 .125em 0;
|
||||
border-bottom: 1px solid var(--fs-border);
|
||||
border-bottom: var(--fs-hairline);
|
||||
}
|
||||
|
||||
.cbi-filebrowser .upload > * {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* min-content, ~131px, and the description wrapped one word per line. */
|
||||
width: min(400px, calc(100% - 32px));
|
||||
margin: 7vh auto var(--fs-space-10);
|
||||
background: var(--fs-panel); border: 1px solid var(--fs-border); border-radius: var(--fs-radius-lg);
|
||||
background: var(--fs-panel); border: var(--fs-hairline); border-radius: var(--fs-radius-lg);
|
||||
/* the token, not a literal 45%-black 60px halo: that ignored the palette and drew
|
||||
* a heavy grey cloud under a white card in light mode. */
|
||||
padding: var(--fs-space-6) var(--fs-space-6) var(--fs-space-6); box-shadow: var(--fs-shadow-pop);
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
align-self: stretch;
|
||||
text-align: start;
|
||||
background: var(--fs-panel2);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
padding: var(--fs-space-2) var(--fs-space-3);
|
||||
/* NO `container-type` HERE, and its absence is the fix for a page that jumped every five
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* their own entry here. */
|
||||
body[data-page="admin-system-admin-sshkeys"] #view > div {
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
padding: var(--fs-space-4);
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
margin: 0;
|
||||
font-size: var(--fs-type); font-weight: var(--fs-weight-bold); color: var(--fs-text);
|
||||
}
|
||||
.fs-ap-section + .fs-ap-section { padding-top: var(--fs-space-4); border-top: 1px solid var(--fs-border); }
|
||||
.fs-ap-section + .fs-ap-section { padding-top: var(--fs-space-4); border-top: var(--fs-hairline); }
|
||||
/* THE ROWS ARE STOCK `.cbi-value` NOW and this page states no layout for them at all — caption
|
||||
* column, field column and the hairline between rows come from base/30-forms.css and
|
||||
* theme/60-inputs.css, the same rules every other LuCI form is drawn by. What was here before was
|
||||
@@ -117,7 +117,7 @@
|
||||
* border. Fixed 34px square so it lines up with the field beside it at every density. */
|
||||
input[type="color"].fs-color-swatch {
|
||||
flex: 0 0 auto; inline-size: 34px; block-size: 34px; padding: 0; cursor: pointer;
|
||||
background: none; border: 1px solid var(--fs-border); border-radius: var(--fs-radius-sm);
|
||||
background: none; border: var(--fs-hairline); border-radius: var(--fs-radius-sm);
|
||||
}
|
||||
input[type="color"].fs-color-swatch::-webkit-color-swatch-wrapper { padding: 0; }
|
||||
input[type="color"].fs-color-swatch::-webkit-color-swatch { border: 0; border-radius: calc(var(--fs-radius-sm) - 1px); }
|
||||
@@ -162,7 +162,7 @@
|
||||
* letterboxed photograph. */
|
||||
.fs-ap-bgprev {
|
||||
display: block; width: 100%; max-width: var(--fs-field-max); height: 84px; object-fit: cover;
|
||||
border: 1px solid var(--fs-border); border-radius: var(--fs-radius);
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius);
|
||||
/* the preview follows the buttons in the field, so the gap belongs ABOVE it */
|
||||
margin-top: var(--fs-space-2);
|
||||
}
|
||||
@@ -189,7 +189,7 @@
|
||||
/* the version line at the foot of the page */
|
||||
.fs-ap-footer {
|
||||
display: flex; flex-direction: column; gap: var(--fs-space-2);
|
||||
margin-top: var(--fs-space-0-5); padding-top: var(--fs-space-2-5); border-top: 1px solid var(--fs-border);
|
||||
margin-top: var(--fs-space-0-5); padding-top: var(--fs-space-2-5); border-top: var(--fs-hairline);
|
||||
}
|
||||
.fs-ap-verrow { display: flex; align-items: center; justify-content: space-between; gap: var(--fs-space-2-5); }
|
||||
/* links to the project repo — reads as muted text until hovered */
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
/* No accent fill — a bordered box, same look as the square buttons in the right
|
||||
* cluster (.fs-logout, .fs-themerow: 1px var(--fs-border)). The glyph is the OpenWrt
|
||||
* favicon mark (brand.ut), baked-colour, so color/currentColor is unused here. */
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
background: var(--fs-bar-bg);
|
||||
backdrop-filter: var(--fs-blur);
|
||||
-webkit-backdrop-filter: var(--fs-blur);
|
||||
border-inline-end: 0; border-bottom: 1px solid var(--fs-border);
|
||||
border-inline-end: 0; border-bottom: var(--fs-hairline);
|
||||
/* the submenu popups anchor to this bar and must escape it */
|
||||
overflow: visible;
|
||||
}
|
||||
@@ -175,7 +175,7 @@
|
||||
margin: 0; padding: var(--fs-space-2);
|
||||
background: var(--fs-glass);
|
||||
backdrop-filter: var(--fs-blur); -webkit-backdrop-filter: var(--fs-blur);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg); box-shadow: var(--fs-shadow-pop);
|
||||
}
|
||||
/* an invisible bridge across the 8px gap, so travelling from the item down into the panel
|
||||
@@ -210,7 +210,7 @@
|
||||
/* see the :not(:empty) rule below — two auto margins would SPLIT the free space between
|
||||
* them instead of pushing one cluster to the bar's far edge */
|
||||
width: var(--fs-btn-size); height: var(--fs-btn-size); padding: 0;
|
||||
border: 1px solid var(--fs-border); border-radius: var(--fs-radius);
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius);
|
||||
background: var(--fs-panel2); color: var(--fs-dim);
|
||||
font: inherit; font-weight: var(--fs-weight); font-size: var(--fs-type); line-height: 1.1;
|
||||
text-align: start; cursor: pointer;
|
||||
@@ -280,7 +280,7 @@
|
||||
.fs-logout {
|
||||
flex: 0 0 auto; order: 2; justify-content: center;
|
||||
width: var(--fs-btn-size); height: var(--fs-btn-size); padding: 0;
|
||||
border: 1px solid var(--fs-border); border-radius: var(--fs-radius);
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius);
|
||||
background: var(--fs-panel2);
|
||||
}
|
||||
/* on a square button the BORDER carries the state — the fill and the ink come from the
|
||||
@@ -351,7 +351,7 @@
|
||||
gap: normal; padding: var(--fs-space-5) var(--fs-space-3-5);
|
||||
background: var(--fs-panel);
|
||||
backdrop-filter: none; -webkit-backdrop-filter: none;
|
||||
border-inline-end: 1px solid var(--fs-border); border-bottom: 0;
|
||||
border-inline-end: var(--fs-hairline); border-bottom: 0;
|
||||
box-shadow: none;
|
||||
/* `overflow: auto`, not overflow-y alone: the bar sets `overflow: visible` for its
|
||||
* popups, and a lone overflow-y here would leave overflow-x visible — which the
|
||||
@@ -440,7 +440,7 @@
|
||||
display: flex;
|
||||
margin: var(--fs-space-1) 0 var(--fs-space-3) var(--fs-space-7);
|
||||
padding-inline-start: var(--fs-space-1);
|
||||
border-inline-start: 1px solid var(--fs-border);
|
||||
border-inline-start: var(--fs-hairline);
|
||||
}
|
||||
:root[data-layout="sidebar"]:not([data-narrow]) .fs-sidebar ul.nav > li.has-sub.open > ul { display: flex; }
|
||||
:root[data-layout="sidebar"]:not([data-narrow]) .fs-sidebar ul.nav ul a { padding: var(--fs-space-2) var(--fs-space-3); }
|
||||
@@ -544,7 +544,7 @@
|
||||
position: absolute; inset-inline-start: 100%; top: 50%; transform: translateY(-50%);
|
||||
margin-inline-start: var(--fs-space-2-5); z-index: var(--fs-z-flyout);
|
||||
padding: var(--fs-space-1-5) var(--fs-space-2-5); border-radius: var(--fs-radius-sm);
|
||||
background: var(--fs-panel); border: 1px solid var(--fs-border);
|
||||
background: var(--fs-panel); border: var(--fs-hairline);
|
||||
box-shadow: var(--fs-shadow-pop);
|
||||
color: var(--fs-text); font-size: var(--fs-type); font-weight: var(--fs-weight); white-space: nowrap;
|
||||
opacity: 0; pointer-events: none; transition: opacity var(--fs-dur);
|
||||
@@ -559,7 +559,7 @@
|
||||
display: none;
|
||||
position: absolute; inset-inline-start: 100%; top: -8px; margin: 0; margin-inline-start: var(--fs-space-2-5);
|
||||
min-width: 196px; padding: var(--fs-space-2); z-index: var(--fs-z-flyout);
|
||||
background: var(--fs-panel); border: 1px solid var(--fs-border); border-radius: var(--fs-radius-lg);
|
||||
background: var(--fs-panel); border: var(--fs-hairline); border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow-pop);
|
||||
animation: fs-fade var(--fs-dur);
|
||||
}
|
||||
@@ -733,7 +733,7 @@
|
||||
* from base, which keeps only the flex skeleton), so a third-party view emitting its own
|
||||
* <footer> stays themed too. */
|
||||
footer {
|
||||
padding: var(--fs-space-3-5) var(--fs-content-pad); border-top: 1px solid var(--fs-border);
|
||||
padding: var(--fs-space-3-5) var(--fs-content-pad); border-top: var(--fs-hairline);
|
||||
color: var(--fs-dim); font-size: var(--fs-type); background: var(--fs-bg);
|
||||
/* CENTRE THE FLEX LINE, not just the text: `text-align: center` alone did nothing.
|
||||
* base makes the footer a flex container and used to add `justify-content:
|
||||
@@ -822,7 +822,7 @@
|
||||
text-transform: none;
|
||||
font-weight: var(--fs-weight); font-size: var(--fs-type);
|
||||
background: var(--fs-panel2); background-image: none;
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
color: var(--fs-dim);
|
||||
border-radius: var(--fs-radius); padding: 0 var(--fs-space-3); box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/* ---------- panels / cards (cbi) ---------- */
|
||||
.cbi-section, .cbi-map > .cbi-section-node, fieldset.cbi-section, .panel {
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
}
|
||||
@@ -96,7 +96,7 @@
|
||||
.cbi-section .table .td,
|
||||
.cbi-section .table .th {
|
||||
padding: var(--fs-space-2-5) var(--fs-space-4) var(--fs-space-2-5) 0;
|
||||
border: 0; border-bottom: 1px solid var(--fs-border);
|
||||
border: 0; border-bottom: var(--fs-hairline);
|
||||
line-height: 1.45; vertical-align: middle;
|
||||
text-align: start;
|
||||
}
|
||||
@@ -269,7 +269,7 @@
|
||||
.cbi-section .table[id], #view .table[id],
|
||||
.cbi-section .table.fs-dt, .cbi-section table.fs-dt, table.fs-dt {
|
||||
border-collapse: separate; border-spacing: 0;
|
||||
border: 1px solid var(--fs-border); border-radius: var(--fs-radius-lg);
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius-lg);
|
||||
/* EXPLICIT, not an omission: the generic `.table` rule below still says `overflow: hidden`
|
||||
* — which clipped the last column of a wide table — and not repeating it here does not
|
||||
* stop it applying, which is what kept the bug alive. The corner cells (below) round the
|
||||
@@ -439,7 +439,7 @@
|
||||
/* Standalone data tables (Startup, Processes, Routes) -> rounded card. Inside a CBI section
|
||||
* the section already IS the card, so the frame is stripped there (above): no double border. */
|
||||
.table {
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
overflow: hidden;
|
||||
background: var(--fs-panel);
|
||||
@@ -458,9 +458,9 @@
|
||||
* line the full width and pokes past the frame's rounded bottom corners (overflow: visible). */
|
||||
.table .tr.table-titles > .th,
|
||||
.table .tr.cbi-section-table-titles > .th,
|
||||
.table .tr.cbi-section-table-titles::before { border-bottom: 1px solid var(--fs-border); }
|
||||
.table .tr.cbi-section-table-titles::before { border-bottom: var(--fs-hairline); }
|
||||
.table .tr:not(.table-titles):not(.cbi-section-table-titles):not(:last-child) > .td,
|
||||
.table .tr.cbi-section-table-row:not(:last-child)::before { border-bottom: 1px solid var(--fs-border); }
|
||||
.table .tr.cbi-section-table-row:not(:last-child)::before { border-bottom: var(--fs-hairline); }
|
||||
/* descendant combinator (not >) so a real <table>'s browser-inserted <tbody> is covered
|
||||
* too — one rule themes every data table, div- or table-based. */
|
||||
.table .tr.table-titles, .table .tr.cbi-section-table-titles {
|
||||
@@ -767,7 +767,7 @@
|
||||
#modal_overlay .table.fs-xscroll tr > :first-child {
|
||||
position: sticky; left: 0; z-index: 2;
|
||||
background: var(--fs-panel);
|
||||
border-inline-end: 1px solid var(--fs-border);
|
||||
border-inline-end: var(--fs-hairline);
|
||||
}
|
||||
/* a keyboard user reaches the scroll box by tabbing to it (fs-select.js gives it tabindex and a
|
||||
* name); it has to show that it has focus — outline, not box-shadow, so forced-colors keeps it */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.alert-message {
|
||||
padding: var(--fs-space-3-5) var(--fs-space-4);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
/* logical: the stripe marks the READING start of the alert, so it swaps sides with the
|
||||
* document (LuCI ships ar/fa/he catalogues) — physical, it sat on the far edge in RTL. */
|
||||
border-inline-start: 3px solid var(--fs-accent);
|
||||
@@ -201,7 +201,7 @@
|
||||
.label {
|
||||
display: inline-block; padding: var(--fs-space-1) var(--fs-space-2); border-radius: var(--fs-radius-pill);
|
||||
font-size: var(--fs-type-xs); font-weight: var(--fs-weight); line-height: 1.4;
|
||||
background: var(--fs-panel2); border: 1px solid var(--fs-border);
|
||||
background: var(--fs-panel2); border: var(--fs-hairline);
|
||||
color: var(--fs-dim); text-shadow: none; text-transform: none;
|
||||
/* carried over from the absorbed styles/base .label rule */
|
||||
white-space: nowrap; margin: .125em 0 .125em .4em;
|
||||
@@ -228,7 +228,7 @@
|
||||
.cbi-title .label {
|
||||
font-size: var(--fs-type-xs); font-weight: var(--fs-weight);
|
||||
padding: var(--fs-space-1) var(--fs-space-3); border-radius: var(--fs-radius-pill);
|
||||
background: var(--fs-panel2); border: 1px solid var(--fs-border);
|
||||
background: var(--fs-panel2); border: var(--fs-hairline);
|
||||
color: var(--fs-dim); cursor: pointer; line-height: 1.4;
|
||||
text-transform: none; letter-spacing: 0;
|
||||
margin: 0; min-width: 0; box-shadow: none;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
margin: 0 0 var(--fs-card-gap);
|
||||
padding: var(--fs-space-2);
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
/* kills a folder-tab baseline gradient (the 1px x 29px background image the strip
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
.cbi-map > h2,
|
||||
#view > h2 {
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
margin: 0 0 var(--fs-card-gap);
|
||||
@@ -209,7 +209,7 @@
|
||||
.cbi-map > h2 + .cbi-map-descr,
|
||||
#view > h2 + .cbi-map-descr {
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-top: 0;
|
||||
border-radius: 0 0 var(--fs-radius-lg) var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
font-family: var(--fs-font-sans); font-weight: var(--fs-weight); line-height: 1.1;
|
||||
color: var(--fs-text);
|
||||
background: var(--fs-panel2);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
box-shadow: none; text-shadow: none;
|
||||
/* border-COLOR, not `border`: the shorthand animates border-width too — a layout
|
||||
* animation on every hover instead of a paint one (absorbed from base with the ring) */
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
/* bare `button` rides along: a third-party app's unclassed <button> hovered the same border
|
||||
* cue in base's old glow rule, and border-color is a no-op on its border: 0 box — harmless
|
||||
@@ -111,7 +111,7 @@
|
||||
.cbi-section .table[id] .td .cbi-button { padding: var(--fs-space-1) var(--fs-space-2-5); font-size: var(--fs-type); }
|
||||
.alert-message .btn, .alert-message .cbi-button {
|
||||
border-radius: var(--fs-radius); padding: var(--fs-space-2) var(--fs-space-3); font-weight: var(--fs-weight);
|
||||
background: var(--fs-panel2); border: 1px solid var(--fs-border); color: var(--fs-text);
|
||||
background: var(--fs-panel2); border: var(--fs-hairline); color: var(--fs-text);
|
||||
}
|
||||
.alert-message .btn:hover { border-color: var(--fs-accent); color: var(--fs-accent); }
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
margin-inline-end: var(--fs-space-3); padding: var(--fs-space-2) var(--fs-space-3-5);
|
||||
font-family: var(--fs-font-sans); font-size: var(--fs-type); font-weight: var(--fs-weight);
|
||||
color: var(--fs-text); background: var(--fs-panel2);
|
||||
border: 1px solid var(--fs-border); border-radius: var(--fs-radius-sm); cursor: pointer;
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius-sm); cursor: pointer;
|
||||
}
|
||||
input[type="file"]::file-selector-button:hover { border-color: var(--fs-accent); color: var(--fs-accent); }
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
}
|
||||
.td .cbi-button, .cbi-section-actions .cbi-button, .cbi-section-table .cbi-button {
|
||||
background: transparent; background-image: none;
|
||||
border: 1px solid var(--fs-border); color: var(--fs-dim);
|
||||
border: var(--fs-hairline); color: var(--fs-dim);
|
||||
}
|
||||
.td .cbi-button:hover { border-color: var(--fs-dim); color: var(--fs-text); }
|
||||
|
||||
@@ -276,7 +276,7 @@
|
||||
* is the drift this ladder exists to prevent, and it also sat out the Density axis. */
|
||||
flex: 0 0 auto; width: auto; min-width: var(--fs-ctl-h);
|
||||
min-height: var(--fs-ctl-h-sm); padding: 0 var(--fs-space-3); margin: 0;
|
||||
border: 0; border-inline-start: 1px solid var(--fs-border);
|
||||
border: 0; border-inline-start: var(--fs-hairline);
|
||||
border-radius: 0;
|
||||
background: transparent; color: var(--fs-dim);
|
||||
}
|
||||
@@ -286,7 +286,7 @@
|
||||
border: 0; border-radius: 0; margin: 0;
|
||||
background: transparent; color: var(--fs-dim);
|
||||
padding: 0 var(--fs-space-3); min-height: var(--fs-ctl-h);
|
||||
border-inline-start: 1px solid var(--fs-border);
|
||||
border-inline-start: var(--fs-hairline);
|
||||
}
|
||||
.control-group:has(> .cbi-input-password) > button.cbi-button-neutral:hover { color: var(--fs-accent); }
|
||||
/* SQUARE, ON THE LADDER: these were the only buttons in the theme set in bare pixels
|
||||
@@ -296,7 +296,7 @@
|
||||
* --fs-density-box and the pager did not. Square because the glyph is an arrow, not a label. */
|
||||
.pager .btn.prev, .pager .btn.next {
|
||||
flex: 0 0 auto; width: var(--fs-ctl-h); min-width: var(--fs-ctl-h); height: var(--fs-ctl-h); padding: 0;
|
||||
border-radius: var(--fs-radius); border: 1px solid var(--fs-border);
|
||||
border-radius: var(--fs-radius); border: var(--fs-hairline);
|
||||
background: var(--fs-panel2); color: var(--fs-text);
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* sits under it, and center then pushed the label to the middle of control+description,
|
||||
* visibly BELOW the control. baseline lines the label up with the field's FIRST line for
|
||||
* every widget (select, dropdown, toggle, dynlist, textarea). */
|
||||
.cbi-value { justify-content: space-between; gap: var(--fs-space-4); padding: var(--fs-space-2) 0; margin: 0; border-bottom: 1px solid var(--fs-border); }
|
||||
.cbi-value { justify-content: space-between; gap: var(--fs-space-4); padding: var(--fs-space-2) 0; margin: 0; border-bottom: var(--fs-hairline); }
|
||||
/* stacked controls in a field (Local Time: date input + Sync buttons) need breathing room —
|
||||
* the raw <br> collapses them. Exclude the password reveal group: it must stay seamless. */
|
||||
.cbi-value-field > .control-group { margin-top: var(--fs-space-2); }
|
||||
@@ -28,7 +28,7 @@
|
||||
* 10.8 -> 13px on the firewall). border-COLOR, not `border`: the shorthand animates
|
||||
* border-width too — a layout animation on every hover instead of a paint one. */
|
||||
input, button {
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
|
||||
/* keep base's padding-top (--fs-space-1) — it is tuned for base's align-items: baseline. */
|
||||
@@ -181,7 +181,7 @@
|
||||
--sw-pad: calc(2px * var(--fs-density-box));
|
||||
display: inline-block; position: relative; margin: 0;
|
||||
width: var(--sw-w); height: var(--sw-h); border-radius: var(--fs-radius-pill);
|
||||
background: var(--fs-track); border: 1px solid var(--fs-border);
|
||||
background: var(--fs-track); border: var(--fs-hairline);
|
||||
cursor: pointer; transition: background var(--fs-dur), border-color var(--fs-dur); vertical-align: middle;
|
||||
}
|
||||
/* The input is opacity:0/0x0, so this label IS the whole hit box of LuCI's most common
|
||||
@@ -308,7 +308,7 @@
|
||||
height: var(--box);
|
||||
background: linear-gradient(var(--fs-panel), var(--fs-panel2));
|
||||
border: 1px solid var(--bd-color);
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
/* each box states its own radius — one shared 2px re-declared to 50% for the radio would
|
||||
* be a same-selector shadow inside this layer */
|
||||
@@ -376,7 +376,7 @@
|
||||
padding: var(--fs-space-1) var(--fs-space-2);
|
||||
background: var(--fs-panel);
|
||||
color: var(--fs-text);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
}
|
||||
/* base's old `#localtime { width: 300px }` media rule was NOT moved here: the elastic
|
||||
@@ -410,7 +410,7 @@
|
||||
* position and two spellings of one number is how they would drift apart */
|
||||
background-repeat: no-repeat; background-position: right var(--fs-space-3) center; background-size: 12px;
|
||||
padding: var(--fs-space-2) var(--fs-space-8) var(--fs-space-2) var(--fs-space-3);
|
||||
border: 1px solid var(--fs-border); border-radius: var(--fs-radius);
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius);
|
||||
color: var(--fs-text); font-family: var(--fs-font-sans); font-size: var(--fs-type);
|
||||
line-height: 1.3; min-height: var(--fs-ctl-h); cursor: pointer;
|
||||
/* width/display absorbed from base's generic field box: a select's natural width is its
|
||||
@@ -419,7 +419,7 @@
|
||||
* inline-block the widget has always computed */
|
||||
width: 210px;
|
||||
display: inline-block;
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
/* group-then-refine, directly under its group: the listbox variant must NOT take the closed
|
||||
* control's one-line height. A SURFACE token, not an ink one: this once read --fs-on-accent
|
||||
@@ -449,7 +449,7 @@
|
||||
.cbi-value-field select::picker-icon { display: none; }
|
||||
::picker(select) {
|
||||
appearance: base-select;
|
||||
background: var(--fs-panel); border: 1px solid var(--fs-border);
|
||||
background: var(--fs-panel); border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg); box-shadow: var(--fs-shadow-pop);
|
||||
padding: var(--fs-space-1-5); margin-top: var(--fs-space-1-5);
|
||||
}
|
||||
@@ -500,7 +500,7 @@
|
||||
height: var(--fs-ctl-h);
|
||||
color: var(--fs-text);
|
||||
font-size: var(--fs-type);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
padding: 0;
|
||||
position: relative;
|
||||
@@ -509,7 +509,7 @@
|
||||
background-repeat: no-repeat;
|
||||
background-position: right var(--fs-space-3) center;
|
||||
background-size: 12px;
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
/* The disabled cue used to live ENTIRELY on the ▾ plate: base/30-forms.css dims
|
||||
* `.cbi-select[disabled]::before` and never the shell itself. With the plate gone the state had
|
||||
@@ -571,13 +571,13 @@
|
||||
input[type="color"],
|
||||
textarea {
|
||||
background: var(--fs-panel2);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
color: var(--fs-text);
|
||||
padding: var(--fs-space-2) var(--fs-space-3);
|
||||
min-height: var(--fs-ctl-h);
|
||||
box-shadow: none;
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
/* width/font-size absorbed from base's generic field box, same numbers — a textarea has
|
||||
* always sat at the fixed 210px (the elastic rule above names only text/url/search) */
|
||||
@@ -707,12 +707,12 @@
|
||||
}
|
||||
.cbi-dynlist > .item {
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-sm);
|
||||
color: var(--fs-text);
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
/* a chip is keyboard-removable (ui.js gives it a tabindex), so it needs the ring */
|
||||
.cbi-dynlist > .item:focus {
|
||||
@@ -730,9 +730,9 @@
|
||||
padding: 0 var(--fs-space-3);
|
||||
color: var(--fs-text);
|
||||
line-height: 2em;
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
/* the hover cue is the lift, not a colour: the plate's fill is a gradient (see
|
||||
* --fs-hover-lift in 02-tokens.css) — replaces base's old focus-glow-on-hover */
|
||||
@@ -748,9 +748,9 @@
|
||||
/* text add-item = one bordered input group (input + + button attached) */
|
||||
.cbi-dynlist > .add-item {
|
||||
align-items: stretch;
|
||||
background: var(--fs-panel2); border: 1px solid var(--fs-border);
|
||||
background: var(--fs-panel2); border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-sm); overflow: hidden;
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
.cbi-dynlist > .add-item:focus-within { border-color: var(--fs-accent); box-shadow: var(--fs-focus-ring); }
|
||||
.cbi-dynlist > .add-item > input,
|
||||
@@ -768,10 +768,10 @@
|
||||
.control-group:has(> .cbi-input-password) {
|
||||
display: inline-flex; align-items: stretch; gap: 0;
|
||||
background: var(--fs-panel2);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
overflow: hidden;
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
}
|
||||
.control-group:has(> .cbi-input-password) > .cbi-input-password {
|
||||
border: 0; border-radius: 0;
|
||||
@@ -829,7 +829,7 @@
|
||||
* to the card's right edge. Every stretching control in a form row now ends at one x. */
|
||||
.cbi-range-slider { display: inline-flex; align-items: center; gap: var(--fs-space-3); width: 100%; max-width: var(--fs-field-max); }
|
||||
.cbi-range-slider-value {
|
||||
background: var(--fs-panel2); border: 1px solid var(--fs-border); border-radius: var(--fs-radius-sm);
|
||||
background: var(--fs-panel2); border: var(--fs-hairline); border-radius: var(--fs-radius-sm);
|
||||
padding: var(--fs-space-1) var(--fs-space-2); color: var(--fs-accent); font-family: var(--fs-font-mono);
|
||||
/* NOT 600: no face is bundled, so 600 either drags a 20 KB file back in or is faked by
|
||||
* synthetic emboldening, which smears a monospace grid.
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
.cbi-dropdown > ul > li .hide-close { display: none; }
|
||||
/* the chip divider on a BUTTON dropdown's preview; the form dropdown zeroes it below */
|
||||
.cbi-dropdown > ul > li[display]:not([display="0"]) {
|
||||
border-inline-start: 1px solid var(--fs-border);
|
||||
border-inline-start: var(--fs-hairline);
|
||||
}
|
||||
.cbi-dropdown[empty] > ul {
|
||||
max-width: 1px;
|
||||
@@ -206,7 +206,7 @@
|
||||
|
||||
.cbi-dropdown:not(.btn):not(.cbi-button) {
|
||||
background: var(--fs-panel2);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
color: var(--fs-text);
|
||||
min-height: var(--fs-ctl-h);
|
||||
@@ -216,7 +216,7 @@
|
||||
* 38 -> 42px on the gallery). */
|
||||
height: var(--fs-control-h);
|
||||
font-size: var(--fs-type);
|
||||
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
|
||||
transition: var(--fs-field-transition);
|
||||
/* Width absorbed from base/30-forms.css, where it was grouped with .cbi-dynlist's — so the
|
||||
* theme sized the dynlist and base still sized the dropdown, two halves of the same
|
||||
* decision in two layers. Same numbers, and they are NOT the dynlist's (240/440): a
|
||||
@@ -325,7 +325,7 @@
|
||||
transition: max-height var(--fs-dur-move);
|
||||
overflow-y: auto;
|
||||
background: var(--fs-panel);
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow-pop);
|
||||
padding: var(--fs-space-1-5);
|
||||
@@ -410,10 +410,10 @@
|
||||
box-shadow: inset 0 0 0 2px var(--fs-accent-soft);
|
||||
}
|
||||
|
||||
.cbi-dropdown[open] > ul.dropdown > li.create-item { border-top: 1px solid var(--fs-border); margin-top: var(--fs-space-1); }
|
||||
.cbi-dropdown[open] > ul.dropdown > li.create-item { border-top: var(--fs-hairline); margin-top: var(--fs-space-1); }
|
||||
/* The dropdown's "add item" field is a text input and takes the same radius as every other
|
||||
* one (theme/60-inputs.css). It said 8px here, and won or lost on source order. */
|
||||
.cbi-dropdown .create-item-input { background: var(--fs-panel2); border: 1px solid var(--fs-border); border-radius: var(--fs-radius); color: var(--fs-text); }
|
||||
.cbi-dropdown .create-item-input { background: var(--fs-panel2); border: var(--fs-hairline); border-radius: var(--fs-radius); color: var(--fs-text); }
|
||||
/* dropdown add-item (e.g. Community Lists) — the .cbi-dropdown styles itself, so the
|
||||
* wrapper stays transparent: no double frame. */
|
||||
.cbi-dynlist > .add-item:has(> .cbi-dropdown) {
|
||||
@@ -457,7 +457,7 @@
|
||||
* same height when one wraps; see the data card's .tr in theme/30-tables.css. */
|
||||
.table.cbi-section-table .tr {
|
||||
display: flex; flex-wrap: wrap; width: auto;
|
||||
border: 1px solid var(--fs-border);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
background: var(--fs-panel2);
|
||||
padding: var(--fs-space-2) var(--fs-space-3-5) var(--fs-space-3);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
width: 100%; max-width: 560px; max-height: min(70dvh, 560px);
|
||||
background: var(--fs-glass);
|
||||
backdrop-filter: var(--fs-blur); -webkit-backdrop-filter: var(--fs-blur);
|
||||
border: 1px solid var(--fs-border); border-radius: var(--fs-radius-lg);
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow-pop);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
.fs-search-row {
|
||||
display: flex; align-items: center; gap: var(--fs-space-2-5); flex: 0 0 auto;
|
||||
padding: var(--fs-space-3) var(--fs-space-3-5);
|
||||
border-bottom: 1px solid var(--fs-border);
|
||||
border-bottom: var(--fs-hairline);
|
||||
}
|
||||
.fs-search-ico { display: inline-flex; color: var(--fs-faint); }
|
||||
.fs-search-ico .fs-ico { width: 18px; height: 18px; }
|
||||
|
||||
@@ -463,7 +463,10 @@
|
||||
what every browser accepts a manifest as. There is no per-extension setting to fix from
|
||||
here: the map is compiled into uhttpd. #}
|
||||
<link rel="manifest" href="{{ media }}/manifest.json?v={{ pkgs_update_time }}">
|
||||
<link rel="apple-touch-icon" href="{{ media }}/apple-touch-icon.png">
|
||||
{# iOS reads THIS link and never the manifest's icons array — and it is happy with the 512
|
||||
square the manifest also uses, which it scales. One raster serves both, see
|
||||
tools/build-icons.mjs. #}
|
||||
<link rel="apple-touch-icon" href="{{ media }}/app-icon-512.png">
|
||||
{% if (dispatched?.css): %}
|
||||
{# The `css` property of the dispatched menu.d node — an app naming a stylesheet the server
|
||||
should link for its page, instead of the view injecting one at module eval (which is what
|
||||
|
||||
@@ -10,7 +10,7 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=my-default-settings
|
||||
PKG_VERSION:=2
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
|
||||
@@ -155,8 +155,7 @@ if [ -z "$version" ] || [ "$version" -lt 1 ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].rebind_protection='0'
|
||||
uci -q set dhcp.@dnsmasq[0].rebind_localhost='1'
|
||||
uci commit dhcp
|
||||
|
||||
uci -q set system.@system[0].zram_comp_algo='zstd'
|
||||
|
||||
uci -q set system.@system[0].zram_size_mb="$((memtotal / 1024 / 3))"
|
||||
uci commit system
|
||||
|
||||
|
||||
+9
-5
@@ -24,7 +24,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
@@ -58,7 +58,7 @@ CMAKE_OPTIONS += \
|
||||
|
||||
# every install section takes the ucode tree from here: the bytecode output of
|
||||
# the cmake build, or the plain sources when CONFIG_WWAND_UCODE_SOURCE is set.
|
||||
WWAND_UCODE=$(if $(CONFIG_WWAND_UCODE_SOURCE),$(PKG_BUILD_DIR)/src-ucode,$(PKG_BUILD_DIR)/$(CMAKE_BINARY_SUBDIR)/ucode/wwand)
|
||||
WWAND_UCODE=$(if $(CONFIG_WWAND_UCODE_SOURCE),$(PKG_BUILD_DIR)/src-ucode,$(CMAKE_BINARY_DIR)/ucode/wwand)
|
||||
|
||||
UCDIR:=/usr/share/ucode/wwand
|
||||
|
||||
@@ -188,7 +188,8 @@ endef
|
||||
define Package/wwand-qmi/description
|
||||
QMI control backend for wwand: talks QMI natively over /dev/cdc-wdmX (qmi_wwan
|
||||
driver) with QMAP multiplexing (rmnet), without spawning uqmi/qmicli. This is
|
||||
the common case for most cellular routers.
|
||||
the common case for most cellular routers. Pair with wwand-mhi for modems
|
||||
attached over PCIe/MHI.
|
||||
endef
|
||||
|
||||
define Package/wwand-qmi/install
|
||||
@@ -220,7 +221,8 @@ define Package/wwand-mbim/description
|
||||
MBIM control backend for wwand (cdc_mbim driver): native MS Basic Connect (+
|
||||
Extensions v2/v3) plus a QMI-over-MBIM passthrough that tunnels the whole QMI
|
||||
stack over the open MBIM channel for full CDC-level telemetry/config. The
|
||||
passthrough reuses qmi_backend, hence the dependency on wwand-qmi.
|
||||
passthrough reuses qmi_backend, hence the dependency on wwand-qmi. Pair
|
||||
with wwand-mhi for modems attached over PCIe/MHI.
|
||||
endef
|
||||
|
||||
define Package/wwand-mbim/install
|
||||
@@ -243,7 +245,9 @@ define Package/wwand-ncm
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=NCM/ECM backend for wwand
|
||||
DEPENDS:=+wwand +kmod-usb-net-cdc-ncm +kmod-usb-net-cdc-ether
|
||||
# +kmod-usb-net-rndis: the RNDIS datapath (rndis_host) used by e.g. the
|
||||
# Fibocom FM350-GL (MediaTek T700) — same AT-driven backend, RNDIS netdev.
|
||||
DEPENDS:=+wwand +kmod-usb-net-cdc-ncm +kmod-usb-net-cdc-ether +kmod-usb-net-rndis
|
||||
# coexists with the stock OpenWrt NCM stack (netifd `ncm` proto, package
|
||||
# comgt-ncm): wwand drives an AT/NCM modem only once its interface has been
|
||||
# migrated to `proto wwand` (LuCI modem list / migrate CLI), so both stacks can
|
||||
|
||||
Reference in new issue
Block a user