diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_dynamiclist_com.htm b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_dynamiclist_com.htm index 3a74ce20..2e0a2980 100644 --- a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_dynamiclist_com.htm +++ b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_dynamiclist_com.htm @@ -79,6 +79,10 @@ justify-content: space-between; box-sizing: border-box; } +/* 使用控件自带箭头,清除主题背景箭头 */ +.dl-dropdown-display.cbi-input-select { + background-image: none !important; +} .dl-dropdown-label, .dl-selected-label { display: inline-block; @@ -100,7 +104,7 @@ overscroll-behavior: contain; } .dl-dropdown-search { - width: 100%; + width: 100% !important; max-width: 100% !important; min-width: 0 !important; box-sizing: border-box; @@ -136,6 +140,10 @@ flex-direction: column; gap: 5px; } +/* 选择框与已选列表之间保持相同的行间距 */ +.dl-selected-list:not(:empty) { + margin-bottom: 5px; +} .dl-remove { margin-left: 8px; cursor: pointer; @@ -260,7 +268,8 @@ const panelRadius = styleSelect.borderRadius; const optionColor = !dl_isTransparent(styleOption.backgroundColor) ? styleOption.backgroundColor : !dl_isTransparent(styleSelect.backgroundColor) ? styleSelect.backgroundColor : styleBody.backgroundColor const titleColor = dl_getColorSchema(optionColor) === "light" ? dl_darker(optionColor, 30) : dl_lighter(optionColor, 30) - const selectStyleCSS = [`#${CSS.escape(cbid + ".display")}, .dl-selected-item {`, dl_style2Css(styleSelect, styleNames), dl_style2Css(styleSelect, ["backgroundColor"]), "}"] + // 自带箭头已占据布局空间,右内边距使用主题的左内边距 + const selectStyleCSS = [`#${CSS.escape(cbid + ".display")}, .dl-selected-item {`, dl_style2Css(styleSelect, styleNames), dl_style2Css(styleSelect, ["backgroundColor"]), `padding-right: ${styleSelect.paddingLeft} !important;`, "}"] const optionStyleCSS = [`#${CSS.escape(cbid + ".panel")} {`, dl_style2Css(styleOption, styleNames), `background-color: ${optionColor};`, `border-radius: ${panelRadius};`, "}"] const titleStyleCSS = [`#${CSS.escape(cbid + ".panel")} .dl-group-title {`, `background-color: ${titleColor} !important;`, "}"] styleNode.textContent = [].concat(selectStyleCSS, optionStyleCSS, titleStyleCSS).join("\n") diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm index 30de14bb..ad8ce390 100644 --- a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm +++ b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm @@ -88,6 +88,10 @@ justify-content: space-between; box-sizing: border-box; } +/* 使用控件自带箭头,清除主题背景箭头 */ +.lv-dropdown-display.cbi-input-select { + background-image: none !important; +} .lv-dropdown-label { display: inline-block; overflow: hidden; @@ -108,7 +112,7 @@ overscroll-behavior: contain; } .lv-dropdown-search { - width: 100%; + width: 100% !important; max-width: 100% !important; min-width: 0 !important; box-sizing: border-box; @@ -254,7 +258,8 @@ const panelRadius = styleSelect.borderRadius; const optionColor = !lv_isTransparent(styleOption.backgroundColor) ? styleOption.backgroundColor : !lv_isTransparent(styleSelect.backgroundColor) ? styleSelect.backgroundColor : styleBody.backgroundColor const titleColor = lv_getColorSchema(optionColor) === "light" ? lv_darker(optionColor, 30) : lv_lighter(optionColor, 30) - const selectStyleCSS = [`#${CSS.escape(cbid + ".display")} {`, lv_style2Css(styleSelect, styleNames), lv_style2Css(styleSelect, ["backgroundColor"]), "}"] + // 自带箭头已占据布局空间,右内边距使用主题的左内边距 + const selectStyleCSS = [`#${CSS.escape(cbid + ".display")} {`, lv_style2Css(styleSelect, styleNames), lv_style2Css(styleSelect, ["backgroundColor"]), `padding-right: ${styleSelect.paddingLeft} !important;`, "}"] const optionStyleCSS = [`#${CSS.escape(cbid + ".panel")} {`, lv_style2Css(styleOption, styleNames), `background-color: ${optionColor};`, `border-radius: ${panelRadius};`, "}"] const titleStyleCSS = [`#${CSS.escape(cbid + ".panel")} .lv-group-title {`, `background-color: ${titleColor} !important;`, "}"] styleNode.textContent = [].concat(selectStyleCSS, optionStyleCSS, titleStyleCSS).join("\n") diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm index f5c6ea39..e61cb420 100644 --- a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm +++ b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm @@ -63,7 +63,7 @@ local function idSafe(id) end %> -
+
+
+
<%:You choose node is:%>
+
-
-
- -
- +
+ <% if #shunt_rule_list > 0 then %> -
- -
+
+ +
<% for i, v in ipairs(shunt_rule_list) do %> - ')" value="<%=v.remarks%>" /> + ')" value="<%=v.remarks%>" /> <% end %>
diff --git a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_dynamiclist_com.htm b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_dynamiclist_com.htm index 38594857..f32f6b79 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_dynamiclist_com.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_dynamiclist_com.htm @@ -79,6 +79,10 @@ justify-content: space-between; box-sizing: border-box; } +/* 使用控件自带箭头,清除主题背景箭头 */ +.dl-dropdown-display.cbi-input-select { + background-image: none !important; +} .dl-dropdown-label, .dl-selected-label { display: inline-block; @@ -100,7 +104,7 @@ overscroll-behavior: contain; } .dl-dropdown-search { - width: 100%; + width: 100% !important; max-width: 100% !important; min-width: 0 !important; box-sizing: border-box; @@ -136,6 +140,10 @@ flex-direction: column; gap: 5px; } +/* 选择框与已选列表之间保持相同的行间距 */ +.dl-selected-list:not(:empty) { + margin-bottom: 5px; +} .dl-remove { margin-left: 8px; cursor: pointer; @@ -260,7 +268,8 @@ const panelRadius = styleSelect.borderRadius; const optionColor = !dl_isTransparent(styleOption.backgroundColor) ? styleOption.backgroundColor : !dl_isTransparent(styleSelect.backgroundColor) ? styleSelect.backgroundColor : styleBody.backgroundColor const titleColor = dl_getColorSchema(optionColor) === "light" ? dl_darker(optionColor, 30) : dl_lighter(optionColor, 30) - const selectStyleCSS = [`#${CSS.escape(cbid + ".display")}, .dl-selected-item {`, dl_style2Css(styleSelect, styleNames), dl_style2Css(styleSelect, ["backgroundColor"]), "}"] + // 自带箭头已占据布局空间,右内边距使用主题的左内边距 + const selectStyleCSS = [`#${CSS.escape(cbid + ".display")}, .dl-selected-item {`, dl_style2Css(styleSelect, styleNames), dl_style2Css(styleSelect, ["backgroundColor"]), `padding-right: ${styleSelect.paddingLeft} !important;`, "}"] const optionStyleCSS = [`#${CSS.escape(cbid + ".panel")} {`, dl_style2Css(styleOption, styleNames), `background-color: ${optionColor};`, `border-radius: ${panelRadius};`, "}"] const titleStyleCSS = [`#${CSS.escape(cbid + ".panel")} .dl-group-title {`, `background-color: ${titleColor} !important;`, "}"] styleNode.textContent = [].concat(selectStyleCSS, optionStyleCSS, titleStyleCSS).join("\n") diff --git a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm index 30de14bb..ad8ce390 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm @@ -88,6 +88,10 @@ justify-content: space-between; box-sizing: border-box; } +/* 使用控件自带箭头,清除主题背景箭头 */ +.lv-dropdown-display.cbi-input-select { + background-image: none !important; +} .lv-dropdown-label { display: inline-block; overflow: hidden; @@ -108,7 +112,7 @@ overscroll-behavior: contain; } .lv-dropdown-search { - width: 100%; + width: 100% !important; max-width: 100% !important; min-width: 0 !important; box-sizing: border-box; @@ -254,7 +258,8 @@ const panelRadius = styleSelect.borderRadius; const optionColor = !lv_isTransparent(styleOption.backgroundColor) ? styleOption.backgroundColor : !lv_isTransparent(styleSelect.backgroundColor) ? styleSelect.backgroundColor : styleBody.backgroundColor const titleColor = lv_getColorSchema(optionColor) === "light" ? lv_darker(optionColor, 30) : lv_lighter(optionColor, 30) - const selectStyleCSS = [`#${CSS.escape(cbid + ".display")} {`, lv_style2Css(styleSelect, styleNames), lv_style2Css(styleSelect, ["backgroundColor"]), "}"] + // 自带箭头已占据布局空间,右内边距使用主题的左内边距 + const selectStyleCSS = [`#${CSS.escape(cbid + ".display")} {`, lv_style2Css(styleSelect, styleNames), lv_style2Css(styleSelect, ["backgroundColor"]), `padding-right: ${styleSelect.paddingLeft} !important;`, "}"] const optionStyleCSS = [`#${CSS.escape(cbid + ".panel")} {`, lv_style2Css(styleOption, styleNames), `background-color: ${optionColor};`, `border-radius: ${panelRadius};`, "}"] const titleStyleCSS = [`#${CSS.escape(cbid + ".panel")} .lv-group-title {`, `background-color: ${titleColor} !important;`, "}"] styleNode.textContent = [].concat(selectStyleCSS, optionStyleCSS, titleStyleCSS).join("\n") diff --git a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue.htm b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue.htm index 8e52206c..e61cb420 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue.htm @@ -63,7 +63,7 @@ local function idSafe(id) end %> -
+
+
+
<%:You choose node is:%>
+
-
+
+
+
<%- if #shunt_rule_list > 0 then for i, v in ipairs(shunt_rule_list) do -%> - ')" value="<%=v.remarks%>" /> + ')" value="<%=v.remarks%>" /> <%- end -%> <% else %> - + <% end %> +
+
diff --git a/luci-theme-footstrap/LICENSE b/luci-theme-footstrap/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/luci-theme-footstrap/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/luci-theme-footstrap/Makefile b/luci-theme-footstrap/Makefile deleted file mode 100644 index a49fa2d0..00000000 --- a/luci-theme-footstrap/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# -# Copyright (C) 2026 -# -# This is free software, licensed under the Apache License, Version 2.0 . -# - -include $(TOPDIR)/rules.mk - -PKG_NAME:=luci-theme-footstrap -# luci.mk keys the Build/Prepare hook name on LUCI_NAME, which defaults to the checkout's -# directory name — a differently-named checkout symlinked into the feed would silently skip -# the CSS build. Pin it. -LUCI_NAME:=luci-theme-footstrap - -# Version is git-derived; never hardcode PKG_VERSION. luci.mk falls back to PKG_SRC_VERSION, -# which it always defines, when nothing here sets it — see docs/package.md for why no override -# lives here. -LUCI_TITLE:=Footstrap Theme -# +luci-base is the WHOLE list. Nothing shipped fetches at run time; `curl` is not in OpenWrt's -# default set (8 MB devices), so fall back to uclient-fetch, jsonfilter, sha256sum or do without. -LUCI_DEPENDS:=+luci-base -LUCI_PKGARCH:=all -# Else luci.mk claims MAINTAINER "OpenWrt LuCI community" and URL openwrt/luci. -LUCI_MAINTAINER:=Ivan Kvashonkin -LUCI_URL:=https://github.com/VizzleTF/luci-theme-footstrap - -# CSS ships verbatim: luci.mk's csstidy mangles :has(), color-mix() and nested calc(). build-css.sh -# minifies. -LUCI_MINIFY_CSS:=0 -# -# JS: the released package is built by owfeed (tools/stage.sh), not by this Makefile through the -# SDK, so LUCI_MINIFY_JS keeps luci.mk's jsmin. jsmin's one-character lookback does not know `n` or -# `>`, so `return /re/` makes it eat the rest of the file AND EXIT 0 (openwrt/luci#8299): wrap a -# regex literal there in parentheses. - -# Apache-2.0, not a free choice: styles/base began as a fork of luci-theme-bootstrap's cascade.css, -# the ucode templates derive from LuCI's and a few JS helpers are verbatim copies; the notices -# travel with it. No OFL-1.1: the theme ships no Font Software. -PKG_LICENSE:=Apache-2.0 -PKG_LICENSE_FILES:=LICENSE - -# /etc/config/footstrap is SHIPPED as an empty stub and WRITTEN AT RUNTIME: Appearance -> -# "Save to router" has rpcd uci-set the router-wide axes into that very file. Without this define -# the package manager owns it as an ordinary file and REPLACES it on upgrade, so the admin's saved -# defaults are wiped by the theme's own one-click Update, silently and reported as success. -# OpenWrt honours the define for BOTH formats (include/package-pack.mk), so one covers 24.10's opkg -# and 25.12's apk alike. Any future root/etc/config/* must be listed here too. -define Package/luci-theme-footstrap/conffiles -/etc/config/footstrap -endef - -# No `rpcd reload`: it re-execs rpcd, which drops a plugin that is absent mid-write until the next -# reload, and this package registers no rpcd object (rpcd re-reads acl.d at every login). The postinst -# ends a session only when a fresh login would grant it more. Comments inside a define ship into the -# router's script store (1984 of 3239 B), so the bodies are code only (`set -f`: the unquoted -# `$$(uci -q get …)` lists must split, not glob a `*`). docs/package.md, "Why postinst/postrm never -# call `rpcd reload`". -define Package/luci-theme-footstrap/postinst -#!/bin/sh -[ -n "$${IPKG_INSTROOT}" ] || { - rm -f /tmp/luci-indexcache* /tmp/luci-modulecache/* >/dev/null 2>&1 || true - set -f - ubus list 2>/dev/null | grep -qx session && { - for s in $$(ubus call session list 2>/dev/null | grep -o '"ubus_rpc_session": "[a-f0-9]*"' | cut -d'"' -f4); do - case "$$s" in *[!0]*) ;; *) continue ;; esac - ubus call session access "{\"ubus_rpc_session\":\"$$s\",\"scope\":\"uci\",\"object\":\"footstrap\",\"function\":\"write\"}" 2>/dev/null | grep -q true && continue - u=$$(ubus call session get "{\"ubus_rpc_session\":\"$$s\"}" 2>/dev/null | grep -o '"username": *"[^"]*"' | cut -d'"' -f4) - [ -n "$$u" ] || continue - perm=$$(uci show rpcd 2>/dev/null | grep -F .username= | while read -r kv; do - sect=$${kv%%.username=*} - un=$${kv#*.username=} - un=$${un#\'} - un=$${un%\'} - [ "$$un" = "$$u" ] || continue - for grp in $$(uci -q get "$$sect.write"); do - [ "$$grp" = luci-theme-footstrap ] && { echo write; break 2; } - done - for grp in $$(uci -q get "$$sect.read"); do - [ "$$grp" = luci-theme-footstrap ] && { echo read; break 2; } - done - done) - [ -n "$$perm" ] || continue - [ "$$perm" = read ] && ubus call session access "{\"ubus_rpc_session\":\"$$s\",\"scope\":\"uci\",\"object\":\"footstrap\",\"function\":\"read\"}" 2>/dev/null | grep -q true && continue - ubus call session destroy "{\"ubus_rpc_session\":\"$$s\"}" >/dev/null 2>&1 || true - done - } -} -exit 0 -endef - -# opkg runs the OLD package's postrm with "upgrade" on a version upgrade; it must change nothing -# there, since reverting mediaurlbase flipped upgrading 24.10 users back to bootstrap (apk never -# runs it on upgrade). Removal restores bootstrap only if its media dir and header.ut exist, drops -# /etc/footstrap (the uploaded login background) with the two links uci-defaults points at it, which -# the package manager does not own, and, as in the postinst, no rpcd reload. -define Package/luci-theme-footstrap/postrm -#!/bin/sh -case "$$1" in *upgrade*) exit 0 ;; esac -[ -n "$${IPKG_INSTROOT}" ] || { - uci -q delete luci.themes.Footstrap - case "$$(uci -q get luci.main.mediaurlbase)" in - /luci-static/footstrap*) - [ -d /www/luci-static/bootstrap ] && \ - [ -f /usr/share/ucode/luci/template/themes/bootstrap/header.ut ] && \ - uci set luci.main.mediaurlbase=/luci-static/bootstrap ;; - esac - uci commit luci - rm -rf /etc/footstrap >/dev/null 2>&1 || true - rm -f /www/luci-static/footstrap/bg /www/luci-static/footstrap/fonts - rmdir /www/luci-static/footstrap >/dev/null 2>&1 || true - rm -f /tmp/luci-indexcache* /tmp/luci-modulecache/* >/dev/null 2>&1 || true -} -endef - -# Runs right after luci.mk copies the sources into PKG_BUILD_DIR, so it rewrites the build copy -# only. cascade.css is generated, not committed, and styles/ is not in luci.mk's copy list: -# build-css.sh reads it from $(CURDIR) (cat and awk only, so it runs on a buildbot). The git-derived -# version is stamped into fs-version.js unconditionally; only an un-built checkout keeps -# '0.0.0-dev'. The catalogue is luci.mk's business: `po/` is what LUCI_LANGUAGES globs. -define Build/Prepare/luci-theme-footstrap - # PKG_LICENSE_FILES resolves against $(PKG_BUILD_DIR), which luci.mk fills with only src/ luasrc/ - # htdocs/ root/ ucode/; the copy is metadata only, luci.mk never installs it. - $(CP) $(CURDIR)/LICENSE $(PKG_BUILD_DIR)/ - $(SHELL) $(CURDIR)/build-css.sh $(PKG_BUILD_DIR)/htdocs/luci-static/footstrap/cascade.css - # Private `--fs-*` names are 14% of the sheet; runs BEFORE the templates strip, which takes the - # comments it reads. - $(SHELL) $(CURDIR)/mangle-tokens.sh \ - $(PKG_BUILD_DIR)/htdocs/luci-static/footstrap/cascade.css \ - $(PKG_BUILD_DIR)/htdocs/luci-static/resources $(PKG_BUILD_DIR)/ucode - # The theme's own `.fs-*` class names, whole tokens, the same trees. - $(SHELL) $(CURDIR)/mangle-classes.sh \ - $(PKG_BUILD_DIR)/htdocs/luci-static/footstrap/cascade.css \ - $(PKG_BUILD_DIR)/htdocs/luci-static/resources $(PKG_BUILD_DIR)/ucode - # Strips `{# … #}`, whole-line `/* … */` and the indentation from the templates (-27.6 KB of 48.3). - # dev-sync.sh does not run this, so a router being debugged keeps its comments and indentation. - $(SHELL) $(CURDIR)/strip-build.sh templates $(PKG_BUILD_DIR)/ucode - # The same for the shell under root/ (about half of uci-defaults and the CGI are comments); - # whole-line `#` only. - $(SHELL) $(CURDIR)/strip-build.sh shell $(PKG_BUILD_DIR)/root - # The SVG favicon's comment and the manifest's indentation; awk only, so no node on a buildbot. - $(SHELL) $(CURDIR)/strip-build.sh assets $(PKG_BUILD_DIR)/htdocs/luci-static/footstrap - $(SED) "s#const FS_VERSION *= *'[^']*'#const FS_VERSION = '$(if $(PKG_VERSION),$(PKG_VERSION),$(PKG_SRC_VERSION))'#" \ - $(PKG_BUILD_DIR)/htdocs/luci-static/resources/fs-version.js -endef - -# Absolute, not a relative path: the usual ../../luci.mk resolves only inside the luci feed. -include $(TOPDIR)/feeds/luci/luci.mk - -# Load-bearing, not boilerplate: include/scan.mk finds packages by GREPPING Makefiles for -# `call (Build/DefaultTargets|BuildPackage|KernelPackage)`. luci.mk calls BuildPackage, which the -# grep cannot see, so without the literal line below the package gets no CONFIG_PACKAGE_* symbol and -# `make package//compile` fails with "No rule to make target". -# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-theme-footstrap/README.md b/luci-theme-footstrap/README.md deleted file mode 100644 index b0825c04..00000000 --- a/luci-theme-footstrap/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# luci-theme-footstrap (the package) - -A LuCI theme for OpenWrt **24.10.3 and newer** (ucode templates). Installing and using it: -[the repository README](../README.md). Developer documentation: [`../docs/`](../docs/README.md). - -Internal name: `footstrap`. Media path: `/luci-static/footstrap`. - -## One theme, one entry - -`luci.themes` carries **exactly one** entry, `Footstrap` → `/luci-static/footstrap`. Everything -else is a **client** axis on the System → Footstrap page: layout (top bar by default, or the side -menu), mode, palette, density, wallpaper, rounding and every colour. The colours are the accent, the -three status colours and the four surfaces. The axes live in `localStorage` plus attributes and -inline properties on `:root`. Nothing is written to the router until "Save to router" is pressed. There are no per-layout or per-mode themes. - -There is one menu renderer too, `renderMainMenu` in `fs-chrome.js`. The top bar is its own markup, -morphed by CSS through `:root[data-layout]`. No second template, no second renderer, no `footstrap-top` symlink. - -**The theme's boundary:** it supplies the chrome and the design language; page content is drawn by -the view JS of `luci-mod-*`. The one exception is `fs-overview.js`, which draws no content of its own -and only re-arranges the stock overview sections. - -## Layout - -The main parts; the full tree is in [`../docs/package.md`](../docs/package.md) "Source tree". - -``` -Makefile luci.mk; Build/Prepare (CSS, mangle, strip, version) -styles/ CSS source: layers tokens / base / theme / pages -build-css.sh styles/ → htdocs/luci-static/footstrap/cascade.css -po/ translation catalogue (Weblate translates this) -ucode/template/themes/footstrap/ header.ut, footer.ut, sysauth.ut, partials/ -htdocs/luci-static/footstrap/ cascade.css (generated), logo.svg; no webfonts, no wallpaper -htdocs/luci-static/resources/ menu-footstrap.js, fs-*.js, view/footstrap/appearance.js -root/ uci-defaults, the conffile, menu.d, the ACL, keep.d, the pattern CGI -``` - -There is no update checker. The theme is installed from the package feed (`install.sh` adds it), so -`apk upgrade` / `opkg upgrade` carries it forward like everything else on the router. The -System → Footstrap page shows the version it is running and makes no network call to do it. - -**Do not edit `cascade.css`.** `build-css.sh` generates it from `styles/`, and git ignores it. -Colours go in `styles/03-palettes.css`, scales and tokens in `styles/02-tokens.css`. - -## Working on a router - -The normal dev stand is the disposable routers `../owlab.yaml` defines, driven by `owlab`. See -[`../docs/development.md`](../docs/development.md). `dev-sync.sh` targets a **hardware** router over -ssh: - -```sh -./dev-sync.sh [host] # push over ssh, default host router2512 (registers the theme but does NOT activate it) -ssh router2512 'uci set luci.main.mediaurlbase=/luci-static/footstrap; uci commit luci; rm -f /tmp/luci-indexcache*' -ssh router2512 'uci set luci.main.mediaurlbase=/luci-static/bootstrap; uci commit luci' # roll back -``` - -Before pushing, see [`../docs/verifying.md`](../docs/verifying.md). The rules and the traps are in -[`../docs/conventions.md`](../docs/conventions.md); packaging is in -[`../docs/package.md`](../docs/package.md). diff --git a/luci-theme-footstrap/build-apk.sh b/luci-theme-footstrap/build-apk.sh deleted file mode 100755 index c69c664a..00000000 --- a/luci-theme-footstrap/build-apk.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/sh -# Build luci-theme-footstrap as an OpenWrt .apk via the SDK. The theme is noarch, so the package -# installs on any router of that release whatever its CPU architecture. -# -# ./build-apk.sh # download SDK if needed, then build -# BUILD_DIR=~/x ./build-apk.sh -# -# NOT how the release is built (`./tools/stage.sh && owfeed build` is). This exercises the other -# path: the Makefile, luci.mk, jsmin and the SDK's packaging, for anyone building without owfeed. -set -eu - -# Release and gcc string are baked into the SDK filename and downloads.openwrt.org keeps only the -# current point release, so this goes stale: keep it at owfeed.lock's 25.12 `point:`. Override with -# OPENWRT_RELEASE and OPENWRT_SDK_FILE. -REL="${OPENWRT_RELEASE:-25.12.6}" -SDK_BASE="https://downloads.openwrt.org/releases/${REL}/targets/mediatek/filogic" -SDK_FILE="${OPENWRT_SDK_FILE:-openwrt-sdk-${REL}-mediatek-filogic_gcc-14.3.0_musl.Linux-x86_64.tar.zst}" -SDK_URL="$SDK_BASE/$SDK_FILE" -# MUST be a case-sensitive fs (ext4/…), NOT an NTFS/9p Windows mount. -BUILD_DIR="${BUILD_DIR:-/tmp/ow-footstrap-build}" -# FORCE=1 overrides buildroot's host-prereq bail-outs (see step 4). -export FORCE=1 -THEME_DIR="$(cd "$(dirname "$0")" && pwd)" # this package -REPO="$(cd "$THEME_DIR/.." && pwd)" # repo root (holds tools/, .github/) -SDK_DIR="$BUILD_DIR/sdk" -# the SDK signing channel, derived from REL (25.12.2 -> 25.12) -CHANNEL="${REL%.*}" - -mkdir -p "$BUILD_DIR" -cd "$BUILD_DIR" - -# 1. SDK -if [ ! -d "$SDK_DIR" ]; then - echo ">> downloading SDK $REL ..." - # --https-only: GNU wget follows https -> http redirects - wget -q --https-only -O sdk.tar.zst "$SDK_URL" - - # Signature first, checksum under it: the toolchain lands inside the built package. `sha256sums` - # alone proves nothing, it is served unsigned beside the tarball (docs/conventions.md). The - # ed25519 signature, checked with a key pinned from another host (github.com/openwrt/keyring), - # is what holds. Fails CLOSED. Every pin comes from luci-upstream.pin. - . "$THEME_DIR/luci-upstream.pin" - [ -n "${OPENWRT_KEYRING_PIN:-}" ] || { echo "build-apk: OPENWRT_KEYRING_PIN missing from luci-upstream.pin" >&2; exit 1; } - - # OPENWRT_SDK_KEY_, through eval: /bin/sh has no associative arrays - CH_VAR="$(printf '%s' "$CHANNEL" | tr -d .)" - eval "SDK_KEY=\${OPENWRT_SDK_KEY_$CH_VAR:-}" - eval "SDK_KEY_SHA256=\${OPENWRT_SDK_KEY_${CH_VAR}_SHA256:-}" - [ -n "$SDK_KEY" ] && [ -n "$SDK_KEY_SHA256" ] \ - || { echo "build-apk: no SDK signing key for channel $CHANNEL in luci-upstream.pin" >&2; exit 1; } - - echo ">> verifying SDK signature (key $SDK_KEY, channel $CHANNEL) ..." - rm -rf "$BUILD_DIR/usign" - U="$("$REPO/tools/build-usign.sh" "$BUILD_DIR/usign")" - wget -q --https-only -O sha256sums "$SDK_BASE/sha256sums" - wget -q --https-only -O sha256sums.sig "$SDK_BASE/sha256sums.sig" - wget -q --https-only -O openwrt.pub \ - "https://raw.githubusercontent.com/openwrt/keyring/$OPENWRT_KEYRING_PIN/usign/$SDK_KEY" - echo "$SDK_KEY_SHA256 openwrt.pub" | sha256sum -c - >/dev/null \ - || { echo "build-apk: OpenWrt's $CHANNEL key does not match its pin — refusing to verify with it" >&2; exit 1; } - "$U" -V -m sha256sums -p openwrt.pub -x sha256sums.sig \ - || { echo "build-apk: BAD SIGNATURE on OpenWrt's sha256sums — refusing to build with this SDK." >&2; exit 1; } - echo ">> signature verified; now the checksum means something." - - WANT="$(grep -F " *$SDK_FILE" sha256sums | cut -d' ' -f1)" - [ -n "$WANT" ] || { echo "build-apk: no sha256 published for $SDK_FILE" >&2; exit 1; } - GOT="$(sha256sum sdk.tar.zst | cut -d' ' -f1)" - [ "$WANT" = "$GOT" ] || { echo "build-apk: SDK checksum mismatch: want $WANT, got $GOT" >&2; exit 1; } - rm -f sha256sums sha256sums.sig openwrt.pub - echo ">> SDK verified." - - echo ">> extracting ..." - mkdir -p "$SDK_DIR" - tar --zstd -xf sdk.tar.zst -C "$SDK_DIR" --strip-components=1 - rm -f sdk.tar.zst -fi -cd "$SDK_DIR" - -# 2. feeds (need luci for luci.mk + BuildPackage macros) -if [ ! -f feeds/luci.index ] && [ ! -d feeds/luci ]; then - ./scripts/feeds update base luci -fi -./scripts/feeds install -a -p luci >/dev/null 2>&1 || true - -# 3. drop our theme into the luci themes feed (fresh copy) -DEST="feeds/luci/themes/luci-theme-footstrap" -rm -rf "$DEST" -cp -a "$THEME_DIR" "$DEST" -# a worktree can hold 0600/0700 (git stores 100644); uhttpd answers 403 on a file that is not world-readable -chmod -R u=rwX,go=rX "$DEST" -rm -rf "$DEST/build-apk.sh" "$DEST/dev-sync.sh" 2>/dev/null || true - -./scripts/feeds update -i luci -./scripts/feeds install luci-theme-footstrap - -# 4. build. ncurses is only needed for interactive menuconfig, not for a noarch -# theme, so satisfy the host prereq stamp to skip that check. -mkdir -p staging_dir/host -touch staging_dir/host/.prereq-build -make defconfig FORCE=1 -make package/luci-theme-footstrap/clean FORCE=1 V=s >/dev/null 2>&1 || true -make package/luci-theme-footstrap/compile FORCE=1 V=s - -# 5. locate the artifact -echo -echo ">> built packages:" -find bin -name 'luci-theme-footstrap*' \( -name '*.apk' -o -name '*.ipk' \) -print diff --git a/luci-theme-footstrap/build-css.sh b/luci-theme-footstrap/build-css.sh deleted file mode 100755 index c789c19c..00000000 --- a/luci-theme-footstrap/build-css.sh +++ /dev/null @@ -1,337 +0,0 @@ -#!/bin/sh -# Concatenate the styles/ tree into a single cascade.css. -# -# ./build-css.sh [outfile] [--dev] -# -# Directory order is layer order (styles/, base/, theme/, pages/); filename order is source order -# within a directory. A later layer beats an earlier one whatever the specificity, so nothing needs -# !important to override base. -# -# Minified unless --dev: uhttpd serves CSS with no gzip, so every byte is a wire byte. Comments and -# whitespace go, and five spellings shrink (docs/css.md, "The build"): [a="b"] -> [a=b], the space -# before !important, ::before -> :before, the spaces beside * and / in a declaration, and a flex -# triple with a keyword form (1 1 auto -> auto). Nothing else in a selector or declaration is -# rewritten, which is why csstidy stays off (it mangles :has() and color-mix()). Needs only cat and -# awk, so an OpenWrt buildbot can run it. -set -eu - -D="$(cd "$(dirname "$0")" && pwd)" -OUT="" -DEV=0 -for a in "$@"; do - case "$a" in - --dev) DEV=1 ;; - # Else a typo like `--devv` becomes the output file name. - -*) echo "build-css: unknown option: $a" >&2; exit 1 ;; - *) OUT="$a" ;; - esac -done -[ -n "$OUT" ] || OUT="$D/htdocs/luci-static/footstrap/cascade.css" - -for d in styles styles/base styles/theme styles/pages; do - [ -d "$D/$d" ] || { echo "build-css: $D/$d missing" >&2; exit 1; } -done - -TMP="$OUT.tmp.$$" -# $TMP.min too: an awk failure leaves it behind. -trap 'rm -f "$TMP" "$TMP.min" "$TMP.layer"' EXIT -trap 'exit 130' INT TERM -mkdir -p "$(dirname "$OUT")" - -# One `@layer X{` per LAYER, not per FILE: 37 file wrappers are 542 bytes. The wrapper comes from -# the DIRECTORY, so each file must open with the layer its directory means or it is silently -# re-layered. A file with no wrapper (00-header.css: banner and layer-order statement) is copied -# through and must come before the wrapped ones, or it lands inside the block. -emit_layer() { - layer="$1"; shift - body="$TMP.layer" - : > "$body" - for f in "$@"; do - if head -1 "$f" | grep -q '^@layer '; then - head -1 "$f" | grep -q "^@layer $layer {\$" || { - echo "build-css: $f is in a $layer directory but does not open with '@layer $layer {'" >&2 - exit 1; } - # the file's own wrapper: its first line, and the bare `}` where brace depth returns to 0. - # Anything but blank or comment after that `}` is refused: the source leaves it unlayered - # and the build would move it into the block, a silent cascade change. - awk -v F="$f" 'NR == FNR { - if (end) { - l = $0 - if (inc) { if (sub(/^.*\*\//, "", l)) inc = 0; else next } - gsub(/\/\*.*\*\//, "", l) - if (match(l, /\/\*/)) { inc = 1; l = substr(l, 1, RSTART - 1) } - if (!bad && l ~ /[^ \t\r]/) bad = NR - next - } - # depth, string- and comment-aware: a `{` in a multi-line comment or in content:"{" - # is not a block (the same walk as brace_count below) - l = $0; n = length(l) - for (i = 1; i <= n; i++) { - ch = substr(l, i, 1) - if (inc) { if (ch == "*" && substr(l, i + 1, 1) == "/") { inc = 0; i++ } continue } - if (q != "") { if (ch == "\\") i++; else if (ch == q) q = ""; continue } - if (ch == "\"" || ch == "'"'"'") { q = ch; continue } - if (ch == "/" && substr(l, i + 1, 1) == "*") { inc = 1; i++; continue } - if (ch == "{") d++; else if (ch == "}") d-- - } - if (NR > 1 && d == 0) end = NR; next } - END { if (bad) { printf "build-css: %s:%d: a rule after the @layer wrapper would be moved into the layer; put it inside\n", F, bad > "/dev/stderr"; exit 1 } } - FNR > 1 && FNR != end' "$f" "$f" >> "$body" - elif [ -s "$body" ]; then - echo "build-css: $f has no @layer wrapper but follows one that does — it would be" >&2 - echo "build-css: swallowed into the $layer block instead of staying above it." >&2 - exit 1 - else - cat "$f" - fi - done - if [ -s "$body" ]; then - printf '@layer %s {\n' "$layer" - cat "$body" - printf '}\n' - fi - # not covered by the EXIT trap: the success path runs `trap - EXIT` first - rm -f "$body" -} - -# glob expands in filename order -{ - emit_layer tokens "$D"/styles/*.css - emit_layer base "$D"/styles/base/*.css - emit_layer theme "$D"/styles/theme/*.css - emit_layer page "$D"/styles/pages/*.css -} > "$TMP" - -# Comments, and — unless told not to — the whitespace CSS ignores anyway, in one string-aware -# pass rather than two chained ones. squeeze=0 (the pre-compression rule count below; a -# `--dev` build skips the pass and keeps the raw sheet) strips comments and trims each line; -# squeeze=1 also does the ~9.5 KB whitespace pass. Why a scan has to be string-aware, what the -# whitespace pass leaves alone and why, and the sed bug this replaced: docs/css.md, "The build". -squeeze() { - awk -v SQUEEZE="$2" ' - # The exact flex triples that have a shorter keyword form (shorthand initial values: grow 1 - # shrink 1 basis auto). Bare 0 is left alone, since `flex:0` means grow 0. - function flexv(v, imp) { - if (v ~ /!important$/) { imp = "!important"; v = substr(v, 1, length(v) - 10) } - if (v == "0 0 auto") v = "none" - else if (v == "1 1 auto") v = "auto" - else if (v == "0 1 auto") v = "0 auto" - else if (v ~ /^1 1 ([0-9]+\.?[0-9]*|\.[0-9]+)(%|[A-Za-z]+)$/) v = substr(v, 5) - return v imp - } - BEGIN { q = ""; inc = 0; banner = 0; lastc = ""; buf = ""; lastreal = ""; d = 0; st = 0; fl = 0 } - { - line = $0 - # a banner already open on an earlier line: nothing on a pure continuation line can - # change q or inc, so the whole line is banner content until its own closing "*/". - if (banner) { - trimmed = line - sub(/^[ \t]+/, "", trimmed); sub(/[ \t]+$/, "", trimmed) - print trimmed - lastc = ""; lastreal = "" - if (index(line, "*/")) banner = 0 - next - } - # The line BREAK a squeeze pass is about to swallow is whitespace, and a declaration - # may be wrapped across it. Feed it to the whitespace-run logic below as a leading - # space: it survives only where a space would and is dropped next to { } ; , : — - # lastc == "" means output is already at the start of a line, with nothing to glue to. - if (SQUEEZE && lastc != "" && q == "" && !inc) line = " " line - out = ""; i = 1; n = length(line); opened = 0 - while (i <= n) { - c = substr(line, i, 1) - if (inc) { # inside a stripped /* ... */ - if (c == "*" && substr(line, i + 1, 1) == "/") { inc = 0; i += 2; continue } - i++; continue - } - if (q != "") { # inside a "..." or '"'"'...'"'"' string - out = out c - if (c == "\\") { out = out substr(line, i + 1, 1); i += 2; continue } - if (c == q) q = "" - lastreal = "" - i++; continue - } - if (c == "\"" || c == "'"'"'") { - # [attr="ident"] -> [attr=ident]: the same selector, 2 bytes less, 394 sites. Only a - # whole ident closed on this line right before `]`; "--" stays quoted, since - # mangle-tokens.sh reads an unquoted --fs- as a name to rename. - if (SQUEEZE) { - j = index(substr(line, i + 1), c) - v = substr(line, i + 1, j - 1) - if (j && substr(line, i + j + 1, 1) == "]" && out ~ /\[[-A-Za-z0-9_]+[~|^$*]?=$/ && - v ~ /^-?[A-Za-z_][-A-Za-z0-9_]*$/ && !index(v, "--")) { - out = out v; lastreal = ""; i += j + 1; continue - } - } - q = c; out = out c; lastreal = ""; i++; continue - } - if (c == "/" && substr(line, i + 1, 1) == "*") { - if (substr(line, i + 2, 1) == "!") { - # a genuine banner open: reached only with q=="" and inc==0 AT THIS - # CHARACTER, the same guard strip_comments used before this pass absorbed - # it — so a "/*!" that is really bytes inside a string or an already-open - # ordinary comment never lands here. Copied raw from the marker on, byte - # for byte: it is an Apache-2.0 attribution, not formatting. - banner = 1 - rest = substr(line, i) - out = out rest - if (index(rest, "*/")) banner = 0 - opened = 1 - break - } - inc = 1; i += 2; continue - } - if (SQUEEZE) { - if (c == " " || c == "\t") { # collapse a run of whitespace to one space - while (i <= n && (substr(line, i, 1) == " " || substr(line, i, 1) == "\t")) i++ - # the last char EMITTED, which on a continuation line lives on the - # previous output line — hence lastc, not just `out`. - prev = (length(out) ? substr(out, length(out), 1) : lastc) - nxt = (i <= n ? substr(line, i, 1) : "") - # Drop the space next to a delimiter; otherwise it may be a combinator. `>` joins: - # outside a string it is the child combinator or a range `>` - # (`@container (width > 560px)`), both valid unspaced. `~` and `+` do not: - # `[attr~=v]` and `calc(100% - 10px)` are ambiguous without bracket depth. - # `!` joins too: `red!important` is the same declaration (35 sites). - if (prev == "" || prev == "{" || prev == "}" || prev == ";" || prev == "," || prev == ":" || prev == ">") - continue - if (nxt == "{" || nxt == "}" || nxt == ";" || nxt == "," || nxt == "" || nxt == ">" || nxt == "!") - continue - # Inside a declaration only + and - need their spaces: calc(1px * 2) -> calc(1px*2), - # 16 / 9 -> 16/9. Not before a comment opener, and `/*` or `*/` is never joined. - if (bk[d] == "D" && (prev == "*" || prev == "/" || nxt == "*" || (nxt == "/" && substr(line, i + 1, 1) != "*")) && - (prev nxt) != "/*" && (prev nxt) != "*/") - continue - out = out " "; lastreal = " " - continue - } - # ::before/::after -> :before/:after, the CSS2 form every engine still parses and - # serialises as `::`; the first colon of 93 pseudo-elements. Not `::before-x`. - if (c == ":" && substr(line, i + 1, 1) == ":") { - w = (substr(line, i + 2, 6) == "before") ? 6 : (substr(line, i + 2, 5) == "after") ? 5 : 0 - if (w && substr(line, i + 2 + w, 1) !~ /[-A-Za-z0-9_]/) { i++; continue } - } - # transparent -> #0000: the same computed rgba(0,0,0,0) in 6 bytes fewer (4-digit hex: Chrome - # 62, Safari 10), 97 sites. A whole word inside a declaration only, never part of an ident. - if (c == "t" && bk[d] == "D" && substr(line, i, 11) == "transparent" && - substr(line, i + 11, 1) !~ /[-A-Za-z0-9_(]/ && - (length(out) ? substr(out, length(out), 1) : lastc) !~ /[-A-Za-z0-9_]/) { - out = out "#0000"; lastreal = "0"; i += 11; continue - } - # bk[d] is the kind of the open block: G holds rules (@layer @media @supports @container - # @keyframes), D holds declarations (a style rule, @font-face, a keyframe step). st: a - # statement has started, pk: its kind. The value rewrites cannot tell a nested rule from a - # declaration, so a rule inside a D block fails the build. - if (c == "{") { - if (bk[d] == "D") { printf "build-css: a rule nested in a declaration block (docs/css.md, \"The build\")\n" > "/dev/stderr"; exit 1 } - bk[++d] = (st ? pk : "D"); st = 0 - } else if (c == "}") { d--; st = 0 } - else if (c == ";") st = 0 - else if (!st) { - st = 1 - pk = (substr(line, i, 24) ~ /^@(layer|media|supports|container|scope|starting-style|[-a-z]*keyframes)/) ? "G" : "D" - } - # flex: vs is the offset of the value in buf out; it is rewritten when the declaration ends - if (c == ":" && bk[d] == "D" && out ~ /flex$/ && (buf out) ~ /(^|[{;])flex$/) { fl = 1; vs = length(buf) + length(out) + 1 } - if (fl && (c == ";" || c == "}")) { - fl = 0; ft = buf out; fv = substr(ft, vs + 1); fw = flexv(fv) - if (fw != fv) { buf = ""; out = substr(ft, 1, vs) fw } - } - # The last `;` of a block is dropped here, inside the string-aware scanner: a sed - # `s/;}/}/` cannot see strings and turns `content: ";}"` into `content: "}"`. - # The `;` may already sit in the previous output line, so text is held in `buf` - # until the rule closes. - if (c == "}") { - if (length(out) && substr(out, length(out), 1) == ";") - out = substr(out, 1, length(out) - 1) - else if (!length(out) && length(buf) && substr(buf, length(buf), 1) == ";") - buf = substr(buf, 1, length(buf) - 1) - } - out = out c; lastreal = c; i++ - } else { - out = out c; i++ - } - } - if (opened) { - trimmed = out - sub(/^[ \t]+/, "", trimmed); sub(/[ \t]+$/, "", trimmed) - print trimmed - lastc = ""; lastreal = "" - next - } - if (SQUEEZE) { - buf = buf out - if (length(out)) lastc = substr(out, length(out), 1) - # newline only after a closing brace — one rule per line. lastreal, not lastc: a - # line ending in a QUOTED `}` (content: "}") is not the end of a rule, and - # flushing there would split the rule and lose the space before its next token. - if (lastreal == "}") { print buf; buf = ""; lastc = ""; lastreal = "" } - } else { - sub(/^[ \t]+/, "", out) - sub(/[ \t]+$/, "", out) - if (length(out)) print out - } - } - END { if (SQUEEZE) { if (length(buf)) print buf; else printf "\n" } } - ' "$1" -} - -# Fail loudly rather than let an unbalanced block ship. -# -# String-aware, for the same reason the comment stripper is: a brace inside a CSS STRING is not a -# block, so a counter that gsub()s over the raw line makes a perfectly valid rule fail the build. -# Measured, all three shapes: `content: ";}"`, `content: "{"`, and a data-URI carrying `;}`. It -# fails closed, so nothing is ever corrupted; what it costs is a hunt for an imbalance that is not -# there. -brace_count() { - awk ' - BEGIN { q = "" } - { - line = $0; n = length(line); i = 1 - while (i <= n) { - ch = substr(line, i, 1) - if (q != "") { # inside a string - if (ch == "\\") { i += 2; continue } # escape: skip the pair - if (ch == q) q = "" - i++; continue - } - if (ch == "\"" || ch == "'"'"'") { q = ch; i++; continue } - if (ch == "{") o++ - else if (ch == "}") c++ - i++ - } - } - END { - if (o != c) { printf "build-css: %s: unbalanced braces (%d { vs %d })\n", FILENAME, o, c > "/dev/stderr"; exit 1 } - if (o < 100) { printf "build-css: %s: suspiciously few rules (%d)\n", FILENAME, o > "/dev/stderr"; exit 1 } - print o - }' "$1" -} - -# Always brace-check a COMMENT-STRIPPED, UNSQUEEZED copy, --dev included: counting braces in the -# raw file made a stray "{" in prose fail the build on perfectly valid CSS. -squeeze "$TMP" 0 > "$TMP.min" -RULES_BEFORE=$(brace_count "$TMP.min") || exit 1 -rm -f "$TMP.min" - -if [ "$DEV" -eq 0 ]; then - squeeze "$TMP" 1 > "$TMP.min" - mv "$TMP.min" "$TMP" - - # Recount on what ships: the squeeze is the pass most able to corrupt the sheet. The rule COUNT, - # not brace balance, catches a truncated write: a cut right after the base layer closes is still - # balanced against the true rule count, and RULES_BEFORE comes from the untruncated sheet. - RULES_AFTER=$(brace_count "$TMP") || exit 1 - if [ "$RULES_BEFORE" != "$RULES_AFTER" ]; then - echo "build-css: the squeeze changed the rule count ($RULES_BEFORE -> $RULES_AFTER)." >&2 - exit 1 - fi -fi -# --dev: $TMP keeps comments and formatting (only the @layer wrappers are merged), for reading on a -# router rather than shipping. - -SIZE=$(wc -c < "$TMP" | tr -d ' ') -mv "$TMP" "$OUT" -trap - EXIT - -echo "build-css: $SIZE bytes -> $OUT" diff --git a/luci-theme-footstrap/dev-sync.sh b/luci-theme-footstrap/dev-sync.sh deleted file mode 100755 index e579c733..00000000 --- a/luci-theme-footstrap/dev-sync.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/bin/sh -# Push the footstrap theme to a HARDWARE router over ssh (containers use owlab). -# ONE theme, one luci.themes entry; layout (top/sidebar) is a client axis on the -# System -> Footstrap page, not a theme entry. This registers the theme; it does NOT activate it. -set -eu - -R="${1:-router2512}" -N=footstrap -D="$(cd "$(dirname "$0")" && pwd)" - -# cascade.css is generated from styles/ and is not in git. --dev keeps the comments so the -# file on the router still reads like the source. -"$D"/build-css.sh "$D/htdocs/luci-static/$N/cascade.css" --dev - -ssh "$R" "mkdir -p /usr/share/ucode/luci/template/themes/$N \ - /www/luci-static/$N \ -" - -# the ONE template (+ partials/). Sidebar and top bar are the same markup, morphed by -# :root[data-layout] — there is no second theme dir to copy. -scp -q "$D"/ucode/template/themes/$N/*.ut "$R":/usr/share/ucode/luci/template/themes/$N/ -ssh "$R" "mkdir -p /usr/share/ucode/luci/template/themes/$N/partials" -scp -q "$D"/ucode/template/themes/$N/partials/*.ut \ - "$R":/usr/share/ucode/luci/template/themes/$N/partials/ - -# shared static: cascade.css, the logo (SVG + PNGs), the app icon and the manifest; no wallpapers. -scp -qr "$D"/htdocs/luci-static/$N/* "$R":/www/luci-static/$N/ -# scp carries a worktree's 0600 across, and uhttpd answers 403 on a file that is not world-readable -ssh "$R" "chmod -R u=rwX,go=rX /www/luci-static/$N /usr/share/ucode/luci/template/themes/$N" - -# Every resource JS goes as a TAR OF THE TREE, never by name or `scp *.js`: luci.mk copies htdocs/ -# wholesale, so a file listed by hand, or one in a subdirectory, ships in the package and never -# reaches the dev router. Every path under resources/ is ours to place. --mode is GNU tar (a -# checkout's umask would otherwise set the router's modes); bsdtar on macOS refuses it, so use gtar. -tar -C "$D/htdocs/luci-static/resources" --mode=u=rwX,go=rX -cf - . | - ssh "$R" "mkdir -p /www/luci-static/resources && tar -C /www/luci-static/resources -xf -" - -# stamp the git-derived version into the deployed fs-version.js (the package does the same in -# Build/Prepare) so the System -> Footstrap page shows a real version instead of "(dev)". The FILE -# NAME is part of the contract: FS_VERSION lives in fs-version.js, so moving it changes every sed. -FS_V="$(git -C "$D" describe --tags --always 2>/dev/null | sed 's/^v//')" -# if-form, not `[ -n ] && ssh`: under `set -e` a bare `&&`-list whose LAST command fails is what -# aborts, so `[ -n "$FS_V" ] && ssh …` would take the sync down whenever git describe yielded -# nothing (a copied tree without .git). An `&&` guarding an ASSIGNMENT is a different shape and is -# safe. expr refuses a tag with sed- or shell-special characters rather than interpolating it. -if [ -n "$FS_V" ] && expr "$FS_V" : '[0-9A-Za-z._-]*$' >/dev/null; then - ssh "$R" "sed -i \"s#const FS_VERSION *= *'[^']*'#const FS_VERSION = '$FS_V'#\" \ - /www/luci-static/resources/fs-version.js" -fi - -# The catalogue: the release ships it as luci-i18n-footstrap-, so a dev router gets it only from this sync. -# po2lmo is a luci-base host tool, so this is best-effort: without it strings stay English. Build it -# with make -C modules/luci-base/src po2lmo (plural_formula.c is generated by contrib/lemon). -# The basename is `footstrap`, as luci.mk and owfeed.yml ship it (luci-i18n-footstrap-); the -# old `footstrap-theme.*.lmo` of an earlier dev sync is removed, or a stale catalogue could win. -if command -v po2lmo >/dev/null 2>&1; then - ssh "$R" "mkdir -p /usr/lib/lua/luci/i18n && rm -f /usr/lib/lua/luci/i18n/footstrap-theme.*.lmo" - for po in "$D"/po/*/*.po; do - [ -e "$po" ] || continue - lang="$(basename "$(dirname "$po")")" - po2lmo "$po" "/tmp/footstrap.$lang.lmo" - scp -q "/tmp/footstrap.$lang.lmo" "$R":/usr/lib/lua/luci/i18n/ - rm -f "/tmp/footstrap.$lang.lmo" - done -else - echo " (po2lmo not found — skipping the translation catalogue; strings stay English)" -fi - -# root/ -> / as a TREE with a GLOB of top-level dirs, never a list of names (luci.mk installs root/ -# wholesale; a hand-written `usr` list missed root/etc/config/footstrap). tar, not `scp -r`: its -# merge onto an existing /usr is ambiguous. Two subtrees are excluded by behaviour, handled below: -# - etc/uci-defaults runs from /tmp; in /etc/uci-defaults the next boot would run and DELETE it. -# - etc/config holds conffiles, which the package manager keeps on upgrade and so must we: the -# live file holds the admin's "Save to router". -set -- -for _d in "$D"/root/*/; do set -- "$@" "$(basename "$_d")"; done -tar -C "$D/root" --mode=u=rwX,go=rX --exclude=etc/uci-defaults --exclude=etc/config -cf - "$@" | - ssh "$R" "tar -C / -xf -" - -# conffiles: install only when ABSENT, which is exactly what the package manager does with them. -for _f in "$D"/root/etc/config/*; do - [ -f "$_f" ] || continue - _b=$(basename "$_f") - scp -q "$_f" "$R":"/tmp/.fs-conf-$_b" - ssh "$R" "[ -f /etc/config/$_b ] || { mv /tmp/.fs-conf-$_b /etc/config/$_b; \ - echo ' installed /etc/config/$_b (was absent)'; }; rm -f /tmp/.fs-conf-$_b" -done -# No rpcd reload here — same race as the Makefile's postinst note (Package/luci-theme- -# footstrap/postinst): this copies files onto a router where the theme registers no rpcd object, -# rpcd re-reads acl.d/*.json at LOGIN (not only on reload), and a developer's root session already -# carries read='*' write='*', so the theme's ACL group is irrelevant to it regardless. - -ssh "$R" " -# Sweep every pre-consolidation name, $N-top included: a stale mediaurlbase on its media dir renders -# a theme LuCI no longer lists. rm -rf, not rm -f (it refuses directories and this block has no -# set -e); every path is a literal built from \$N. -cd /www/luci-static -rm -rf $N/$N $N-top $N-dark $N-light $N-top-dark $N-top-light -cd /usr/share/ucode/luci/template/themes -rm -rf $N/$N $N-top $N-dark $N-light $N-top-dark $N-top-light -# The overview layout must not be left in luci-mod-status's global include dir, where LuCI loads -# every *.js under any theme: it is a chrome module now. A package upgrade drops the old file, but -# dev-sync copies rather than installs, so it has to be swept by name — otherwise the dev router -# runs both copies and the grid is arranged twice. -rm -f /www/luci-static/resources/view/status/include/05_footstrap_overview_layout.js -" - -# Register by running the package's own uci-defaults script — the single source of truth -# (ONE entry, luci.themes.Footstrap; layout, mode and palette are client-side toggles). -# PKG_UPGRADE=1 forces its upgrade branch, so the dev router's active theme is left alone. -scp -q "$D"/root/etc/uci-defaults/30_luci-theme-footstrap "$R":/tmp/30_luci-theme-footstrap -ssh "$R" "PKG_UPGRADE=1 sh /tmp/30_luci-theme-footstrap; rm -f /tmp/30_luci-theme-footstrap" - - -# ---- last: drop LuCI's menu cache and bust the browser cache ---- -# -# LuCI's index cache is rebuilt from the files and the registration above. head.ut versions assets -# with `?v={{ pkgs_update_time }}`, the mtime of the PACKAGE DATABASE. An install moves it, scp does -# not, so a dev deploy keeps the URL while the bytes change and a browser may serve a stylesheet -# several deploys old. Touching the file is what an install would do. -ssh "$R" 'rm -f /tmp/luci-indexcache*; - touch /lib/apk/db/installed 2>/dev/null || touch /usr/lib/opkg/status 2>/dev/null || true' - -echo "synced to $R (single Footstrap theme registered, active theme unchanged)" diff --git a/luci-theme-footstrap/htdocs/luci-static/footstrap/app-icon-192.png b/luci-theme-footstrap/htdocs/luci-static/footstrap/app-icon-192.png deleted file mode 100644 index fbcbf068..00000000 Binary files a/luci-theme-footstrap/htdocs/luci-static/footstrap/app-icon-192.png and /dev/null differ diff --git a/luci-theme-footstrap/htdocs/luci-static/footstrap/logo.svg b/luci-theme-footstrap/htdocs/luci-static/footstrap/logo.svg deleted file mode 100644 index 93302ff6..00000000 --- a/luci-theme-footstrap/htdocs/luci-static/footstrap/logo.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - diff --git a/luci-theme-footstrap/htdocs/luci-static/footstrap/logo_48.png b/luci-theme-footstrap/htdocs/luci-static/footstrap/logo_48.png deleted file mode 100644 index ae7a6461..00000000 Binary files a/luci-theme-footstrap/htdocs/luci-static/footstrap/logo_48.png and /dev/null differ diff --git a/luci-theme-footstrap/htdocs/luci-static/footstrap/manifest.json b/luci-theme-footstrap/htdocs/luci-static/footstrap/manifest.json deleted file mode 100644 index 974777ad..00000000 --- a/luci-theme-footstrap/htdocs/luci-static/footstrap/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "LuCI", - "start_url": "/cgi-bin/luci/", - "display": "standalone", - "background_color": "#f6f8fa", - "theme_color": "#f6f8fa", - "icons": [ - { - "src": "app-icon-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any maskable" - } - ] -} diff --git a/luci-theme-footstrap/htdocs/luci-static/resources/fs-assets.js b/luci-theme-footstrap/htdocs/luci-static/resources/fs-assets.js deleted file mode 100644 index 988c07e6..00000000 --- a/luci-theme-footstrap/htdocs/luci-static/resources/fs-assets.js +++ /dev/null @@ -1,291 +0,0 @@ -'use strict'; -'require baseclass'; -'require rpc'; -'require ui'; -'require fs-axes as axes'; - -/* fs-assets — putting a file on the router and taking it off again: the pattern and login-photo uploads, - * the SVG sanitizer, the rollback. A module of its own: only the Appearance page needs these ~4 KB. */ - -/* Hoisted: a string literal is not mangled, so each repeat is paid on flash; msgid stays literal - * for update-po.sh. */ -const MSG_UPLOAD_FAILED = _('Upload failed.', 'footstrap'); -const MSG_NOT_SVG = _('That file is not an SVG image.', 'footstrap'); -const MSG_BAD_IMAGE = _('Could not process the image.', 'footstrap'); -const MSG_PICK_SVG = _('Please choose an SVG file.', 'footstrap'); -/* One template, two numbers: a translator places both %d in one sentence instead of agreeing - * plurals. */ -const MSG_SANITIZED = _('Cleaned before upload: removed %d unsafe element(s) and %d external reference(s).', 'footstrap'); -const MSG_NOTHING_LEFT = _('That SVG had nothing left once the unsafe parts were removed.', 'footstrap'); - -/* ---- the pattern: an SVG the admin uploads, tiled and recoloured ---- - * Fixed path matched by the rpcd ACL, under /etc so an upgrade keeps it. Not the security boundary: a - * `curl` to cgi-upload skips every check below; a direct open is stopped by the CGI's - * `Content-Security-Policy: default-src 'none'; sandbox`. */ -const PAT_PATH = '/etc/footstrap/pattern.svg'; /* cgi-upload target; the ACL grants exactly this */ -const PAT_MAX = 512 * 1024; /* a tile that has to reach a router's flash and then every page load */ -/* What `_sanitizeSvg` removes, judged on the parsed document, not the text. Painted as an image nothing - * loads; these rules cover a direct open of the file: - * - elements: only the SVG namespace stays (`` ran same-origin), minus - * script, foreignObject, a ` - {% endif %} - - - {# - Lets an admin install this page as an app (icon, own window, no address bar), worth more - than a tab on the phone LuCI is opened from when the network is broken. Both files are - static, a theme having no dispatcher node to render them per request: start_url is - /cgi-bin/luci/, uhttpd's default (a moved LuCI opens the standard path), and - background/theme_color is the default palette's page colour, read once at install time. - .json, not .webmanifest: uhttpd's compiled-in MIME map has no entry for that extension, - so it answers application/octet-stream. - #} - - {# - iOS reads this link and never the manifest's icons array, and it scales the 192 square the - manifest also uses down to its own 180. One raster serves both. - #} - - {% if (dispatched?.css): %} - {# - The `css` property of the dispatched menu.d node: a stylesheet the server links for the - page instead of the view injecting one at module eval. `dispatched`, not `node`: the - dispatcher exports the resolved node as `dispatched` and never binds `node`, so no ucode - theme emitted this link until openwrt/luci#8920. That commit also adds `css: 'string'` to - the node schema; without it the property is filtered out and this stays false, as on 24.10 - and 25.12. Versioned, as uhttpd sends no Cache-Control. Not `data-fs-shell`: the sheet - belongs to one page, so the SPA router must judge and contain it like any app sheet. - #} - - {% endif %} - {% if (css): %} - {# - no `title` attribute: on a - {% endif %} - {# - The catalogue is a CGI spawn on the critical path (30 ms on the router) that delivers 13 - bytes at lang=en, where no en catalogue exists because the msgids already are English. - The process is the cost, so emit those bytes inline when the language has no catalogue - and keep the tag when it has. The probe mirrors the server: load_catalog() reads every - `*..lmo` in /usr/lib/lua/luci/i18n, so a router shipping an en catalogue still gets - the tag; deciding by language NAME would drop it. Fails open: a throwing probe keeps the - tag, as a missing catalogue renders English and reports nothing. - #} - {%- let has_catalog = true; - try { has_catalog = length(require('fs').glob(`/usr/lib/lua/luci/i18n/*.${dispatcher.lang}.lmo`)) > 0; } catch (e) {} %} - {% if (has_catalog): %} - - {% else %} - - {% endif %} - {# - the one asset stock LuCI leaves without a cache key. Must stay synchronous and un-deferred: - luci.js captures `window.cbi_init` in its constructor, and `defer` is not an option for the - catalogue above either — footer.ut runs `L.require('menu-footstrap')` inline while the - parser is still running, so a module's `_()` would race a deferred window.TR. - #} - - diff --git a/luci-theme-footstrap/ucode/template/themes/footstrap/partials/notice.ut b/luci-theme-footstrap/ucode/template/themes/footstrap/partials/notice.ut deleted file mode 100644 index c6227eab..00000000 --- a/luci-theme-footstrap/ucode/template/themes/footstrap/partials/notice.ut +++ /dev/null @@ -1,16 +0,0 @@ -{# - One top-of-content warning notice: heading, sentence and an optional "go fix it" button. The - `dispatcher.lookup` guard must not drift: `path` is looked up before it is linked, so a build - without that page, or a user without rights to it, gets the warning and no dead link. - include(…, { title, text }) for a plain notice; add { path, label } for the button. - title/text are emitted unescaped: they are `_()` results over strings this theme ships, not - request data. - Licensed to the public under the Apache License 2.0. --#} -
-

{{ title }}

-

{{ text }}

- {% if (path && dispatcher.lookup(path)): %} - - {% endif %} -
diff --git a/luci-theme-footstrap/ucode/template/themes/footstrap/sysauth.ut b/luci-theme-footstrap/ucode/template/themes/footstrap/sysauth.ut deleted file mode 100644 index 231cebe5..00000000 --- a/luci-theme-footstrap/ucode/template/themes/footstrap/sysauth.ut +++ /dev/null @@ -1,185 +0,0 @@ -{# - The LOGIN page. - - Copyright 2008 Steven Barth - Copyright 2008-2012 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. - - It exists because the generic template includes the header WITHOUT `blank_page: true`: with no - theme-local sysauth the whole chrome renders around the login form with every control dead. - No departure here may drop a field the backend posts. - Do not re-introduce a `