mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 09:54:06 +08:00
🔥 Sync 2026-09-28 00:13:15
This commit is contained in:
1 parent
c540ea3fd1
commit
1854a1c960
151 files changed
+25199
-9649
No files matched your search
@@ -1,35 +1 @@
|
||||
{#
|
||||
Footer body. One layout template and one client menu renderer: sidebar and top bar are the same
|
||||
markup, morphed by :root[data-layout].
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
{% if (!blank_page): %}
|
||||
</div>{# /.fs-content #}
|
||||
|
||||
{#
|
||||
role="contentinfo" is explicit because a <footer> gets it implicitly only when
|
||||
its nearest ancestor is <body>, and this one must stay inside <main>: both
|
||||
layouts lay .fs-main out as a flex column and order the footer within it.
|
||||
The version strings are entityencode()d — /etc/openwrt_release is root-owned,
|
||||
but an unescaped `&` in a distname would still break the markup.
|
||||
#}
|
||||
<footer class="fs-footer" role="contentinfo">
|
||||
<span>
|
||||
Powered by
|
||||
<a href="https://github.com/openwrt/luci" target="_blank" rel="noreferrer">
|
||||
{{ entityencode(version.luciname, true) }} ({{ entityencode(version.luciversion, true) }})</a>
|
||||
/
|
||||
<a href="{{ entityencode(version.disturl ?? '#', true) }}" target="_blank" rel="noreferrer">
|
||||
{{ entityencode(version.distname, true) }} {{ entityencode(version.distversion, true) }} ({{ entityencode(version.distrevision, true) }})</a>
|
||||
{% if (lua_active): %}
|
||||
/ {{ _('Lua compatibility mode active') }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</footer>
|
||||
</main>{# /.fs-main #}
|
||||
</div>{# /.fs-shell #}
|
||||
<script>L.require('menu-footstrap')</script>
|
||||
<script>L.require('fs-select')</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
{% include('themes/footstrap/partials/footer') %}
|
||||
@@ -69,10 +69,22 @@
|
||||
it, so the CI gate cannot see the difference. */
|
||||
fs_defaults.layout = fsd.layout || config.main?.footstrap_layout || '';
|
||||
|
||||
/* Optional companion packages announce themselves in the theme's own config —
|
||||
`uci add_list footstrap.settings.plugin=<module>` from the package's uci-defaults — rather
|
||||
than being named in this tree. The chrome requires each name and knows nothing else about it,
|
||||
so the edge points inwards exactly as fs-router.onNavigate does.
|
||||
|
||||
Whitelisted to the shape of a LuCI module name before it is printed into a <script>: writing
|
||||
that file needs root, but a value that reaches an inline script is checked anyway. */
|
||||
let fs_plugins = fsd.plugin ?? [];
|
||||
if (type(fs_plugins) != 'array')
|
||||
fs_plugins = [ fs_plugins ];
|
||||
fs_plugins = filter(fs_plugins, (m) => type(m) == 'string' && match(m, /^[a-zA-Z0-9._-]{1,48}$/));
|
||||
|
||||
http.prepare_content('text/html; charset=UTF-8');
|
||||
-%}
|
||||
|
||||
{% include('themes/footstrap/partials/head', { boardinfo, fs_defaults }) %}
|
||||
{% include('themes/footstrap/partials/head', { boardinfo, fs_defaults, fs_plugins }) %}
|
||||
|
||||
{#
|
||||
data-page carries the DISPATCH path (ctx.path), not request_path: on a firstchild route
|
||||
@@ -85,46 +97,6 @@
|
||||
#}
|
||||
{% const fs_page = entityencode(join('-', length(ctx.path) ? ctx.path : ctx.request_path), true) %}
|
||||
<body class="lang_{{ dispatcher.lang }}" data-page="{{ fs_page }}">
|
||||
<script>
|
||||
/* Records every node a script parks directly on <body> (issue #56): luci-app-bandix's
|
||||
* render() appends 7 tooltip/modal nodes unconditionally on every visit, never
|
||||
* checking whether they already exist. fs-router.js's strayBodyNode()/bodyLittered()
|
||||
* read window.__fsBodyAdds to decide whether the document is still fit to navigate
|
||||
* away from — Rule 2 (docs/third-party-apps.md), extended from CSS to the DOM. First
|
||||
* byte of <body>, before any chrome element or the dispatched page's own <div id="view">.
|
||||
* Own property of this <body> instance, never Node.prototype, and its gap:
|
||||
* docs/spa-router.md, "Foreign view DOM". */
|
||||
(() => {
|
||||
const body = document.body;
|
||||
if (!body || body.__fsBodyHooked) return;
|
||||
body.__fsBodyHooked = true;
|
||||
const adds = window.__fsBodyAdds || (window.__fsBodyAdds = []);
|
||||
const names = [ 'appendChild', 'insertBefore', 'replaceChild', 'append', 'prepend',
|
||||
'insertAdjacentElement', 'insertAdjacentHTML' ];
|
||||
names.forEach((name) => {
|
||||
const orig = body[name];
|
||||
if (typeof orig !== 'function') return;
|
||||
try {
|
||||
Object.defineProperty(body, name, {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value() {
|
||||
const before = Array.prototype.slice.call(this.children);
|
||||
const ret = orig.apply(this, arguments);
|
||||
const after = this.children;
|
||||
for (let i = 0; i < after.length; i++) {
|
||||
if (before.indexOf(after[i]) === -1) adds.push(after[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* a host that refuses to redefine this one name leaves it unwrapped; the rest
|
||||
* still are */
|
||||
catch (e) {}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{#
|
||||
The wallpaper pattern's paint surface: 15-wallpaper.css masks this element so an
|
||||
uploaded SVG takes the theme's ink. A mask applies to an element's whole rendering,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{#
|
||||
Footer body. One layout template and one client menu renderer: sidebar and top bar are the same
|
||||
markup, morphed by :root[data-layout].
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
{% if (!blank_page): %}
|
||||
</div>{# /.fs-content #}
|
||||
|
||||
{#
|
||||
role="contentinfo" is explicit because a <footer> gets it implicitly only when
|
||||
its nearest ancestor is <body>, and this one must stay inside <main>: both
|
||||
layouts lay .fs-main out as a flex column and order the footer within it.
|
||||
The version strings are entityencode()d — /etc/openwrt_release is root-owned,
|
||||
but an unescaped `&` in a distname would still break the markup.
|
||||
#}
|
||||
<footer class="fs-footer" role="contentinfo">
|
||||
<span>
|
||||
Powered by
|
||||
<a href="https://github.com/openwrt/luci" target="_blank" rel="noreferrer">
|
||||
{{ entityencode(version.luciname, true) }} ({{ entityencode(version.luciversion, true) }})</a>
|
||||
/
|
||||
<a href="{{ entityencode(version.disturl ?? '#', true) }}" target="_blank" rel="noreferrer">
|
||||
{{ entityencode(version.distname, true) }} {{ entityencode(version.distversion, true) }} ({{ entityencode(version.distrevision, true) }})</a>
|
||||
{% if (lua_active): %}
|
||||
/ {{ _('Lua compatibility mode active') }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</footer>
|
||||
</main>{# /.fs-main #}
|
||||
</div>{# /.fs-shell #}
|
||||
<script>L.require('menu-footstrap')</script>
|
||||
<script>L.require('fs-select')</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -21,21 +21,10 @@
|
||||
let _sd_pink = index([ 'theme', 'original' ], fs_defaults.pattern_ink) >= 0 ? fs_defaults.pattern_ink : '';
|
||||
let _sd_ac = index([ 'on', 'off' ], fs_defaults.autocollapse) >= 0 ? fs_defaults.autocollapse : '';
|
||||
let _sd_dens = index([ 'compact', 'normal', 'large' ], fs_defaults.density) >= 0 ? fs_defaults.density : '';
|
||||
/* Six numeric axes share this shape: '' (unset) must map to -1, never to 0, because 0 is a real
|
||||
value for every one of them (sharp corners, a transparent scrim, no tile). */
|
||||
function _sd_range(raw, min, max) {
|
||||
let n = int(raw);
|
||||
return (raw != '' && n >= min && n <= max) ? n : -1;
|
||||
}
|
||||
/* a cache-bust TOKEN (the cgi-upload md5 for an uploaded background/pattern/font sheet), not a
|
||||
path — the served path is fixed. Whitelisted to hex so a hand-edited config cannot inject
|
||||
markup into an inline script that interpolates it. */
|
||||
function _sd_token(v) {
|
||||
return (type(v) == 'string' && match(v, /^[a-f0-9]{6,64}$/)) ? v : '';
|
||||
}
|
||||
/* issue #44: how far the content column may grow past --fs-content-max's 1280px default.
|
||||
header.ut's FS_AXES carries 'content_width', so a saved router default reaches this. */
|
||||
let _sd_cwidth = _sd_range(fs_defaults.content_width, 1280, 3840);
|
||||
let _cw = int(fs_defaults.content_width);
|
||||
let _sd_cwidth = (fs_defaults.content_width != '' && _cw >= 1280 && _cw <= 3840) ? _cw : -1;
|
||||
/* The five colour axes each hold one of three things: '' or '0' for off, '1'..'360' for a hue,
|
||||
or '#rrggbb'. Both live forms reach the client as a string, and the pre-paint re-reads which
|
||||
it is the way fs-prefs.js's normColor() does, so this passes through exactly those two shapes
|
||||
@@ -64,14 +53,32 @@
|
||||
let _sd_ctrl = _sd_hex(fs_defaults.control);
|
||||
let _sd_bar = _sd_hex(fs_defaults.bar);
|
||||
let _sd_line = _sd_hex(fs_defaults.line);
|
||||
let _sd_round = _sd_range(fs_defaults.rounding, 0, 20);
|
||||
let _sd_lbg = _sd_token(fs_defaults.login_bg);
|
||||
let _sd_tstr = _sd_range(fs_defaults.tint_strength, 0, 200);
|
||||
let _sd_pdim = _sd_range(fs_defaults.photo_dim, 0, 100);
|
||||
let _sd_pat = _sd_token(fs_defaults.pattern);
|
||||
/* the pattern's tile edge in px and the layer's opacity 0-100 */
|
||||
let _sd_psize = _sd_range(fs_defaults.pattern_size, 40, 1600);
|
||||
let _sd_pstr = _sd_range(fs_defaults.pattern_strength, 0, 100);
|
||||
let _rr = int(fs_defaults.rounding);
|
||||
/* rounding 0 is SHARP corners, a real choice, so '' (unset) must map to -1, not 0 */
|
||||
let _sd_round = (fs_defaults.rounding != '' && _rr >= 0 && _rr <= 20) ? _rr : -1;
|
||||
/* login_bg is the cache-bust TOKEN for the uploaded background (the cgi-upload md5), not the
|
||||
image and not a path — the served path is fixed. Whitelisted to hex so a hand-edited config
|
||||
cannot inject markup into the inline script that builds the url(). */
|
||||
let _lb = fs_defaults.login_bg;
|
||||
let _sd_lbg = (type(_lb) == 'string' && match(_lb, /^[a-f0-9]{6,64}$/)) ? _lb : '';
|
||||
/* the Tint's strength 0-200 (% of the designed chroma). Unset -> -1, like rounding, because 0
|
||||
is a real value. */
|
||||
let _ts = int(fs_defaults.tint_strength);
|
||||
let _sd_tstr = (fs_defaults.tint_strength != '' && _ts >= 0 && _ts <= 200) ? _ts : -1;
|
||||
/* the File photo's scrim opacity 0-100; unset -> -1, 0 being a real value (a transparent
|
||||
scrim) */
|
||||
let _pd = int(fs_defaults.photo_dim);
|
||||
let _sd_pdim = (fs_defaults.photo_dim != '' && _pd >= 0 && _pd <= 100) ? _pd : -1;
|
||||
/* the uploaded pattern's cache-bust token, whitelisted like login_bg and for the same reason;
|
||||
the served path is fixed */
|
||||
let _pt = fs_defaults.pattern;
|
||||
let _sd_pat = (type(_pt) == 'string' && match(_pt, /^[a-f0-9]{6,64}$/)) ? _pt : '';
|
||||
/* the pattern's tile edge in px and the layer's opacity 0-100; unset -> -1, since 0 is a real
|
||||
strength and 0 px is not a tile */
|
||||
let _ps = int(fs_defaults.pattern_size);
|
||||
let _sd_psize = (fs_defaults.pattern_size != '' && _ps >= 40 && _ps <= 1600) ? _ps : -1;
|
||||
let _pst = int(fs_defaults.pattern_strength);
|
||||
let _sd_pstr = (fs_defaults.pattern_strength != '' && _pst >= 0 && _pst <= 100) ? _pst : -1;
|
||||
/* ---- the fonts ----
|
||||
The theme carries no faces, so the two stacks in styles/02-tokens.css are what everyone gets
|
||||
unless the router names something else here. Two independent options, either usable alone:
|
||||
@@ -114,7 +121,8 @@
|
||||
}
|
||||
let _sd_fsans = _sd_family(fs_defaults.font_sans);
|
||||
let _sd_fmono = _sd_family(fs_defaults.font_mono);
|
||||
let _sd_ftok = _sd_token(fs_defaults.fonts);
|
||||
let _ft = fs_defaults.fonts;
|
||||
let _sd_ftok = (type(_ft) == 'string' && match(_ft, /^[a-f0-9]{6,64}$/)) ? _ft : '';
|
||||
/* ---- writing direction ----
|
||||
Nothing in LuCI emits `dir`: the dispatcher stamps `lang` and stops, so a router in Arabic,
|
||||
Persian or Hebrew renders right-to-left text inside a left-to-right document — paragraphs
|
||||
@@ -142,6 +150,7 @@
|
||||
{# The router-wide Appearance defaults, read by both the pre-paint below and fs-prefs.js's
|
||||
current*(), so the Appearance controls show the effective default when this browser has no
|
||||
localStorage. See the sanitiser above for the unset encodings. #}
|
||||
<script>window.__fsPlugins={{ sprintf('%J', fs_plugins) }};</script>
|
||||
<script>window.__fsSD={layout:"{{ _sd_lay }}",darkmode:"{{ _sd_dark }}",palette:"{{ _sd_pal }}",wallpaper:"{{ _sd_wall }}",tint:"{{ _sd_tint }}",accent:"{{ _sd_acc }}",good:"{{ _sd_good }}",warn:"{{ _sd_warn }}",danger:"{{ _sd_dang }}",card:"{{ _sd_card }}",control:"{{ _sd_ctrl }}",bar:"{{ _sd_bar }}",line:"{{ _sd_line }}",rounding:{{ _sd_round }},autocollapse:"{{ _sd_ac }}",login_bg:"{{ _sd_lbg }}",tint_strength:{{ _sd_tstr }},photo_dim:{{ _sd_pdim }},density:"{{ _sd_dens }}",pattern:"{{ _sd_pat }}",pattern_size:{{ _sd_psize }},pattern_strength:{{ _sd_pstr }},pattern_ink:"{{ _sd_pink }}",content_width:{{ _sd_cwidth }}};</script>
|
||||
<script>
|
||||
/* Each IIFE below pre-paints one Appearance axis before the first frame, duplicating a
|
||||
@@ -157,7 +166,7 @@
|
||||
Precedence for every axis: localStorage ?? router default (window.__fsSD) ?? built-in.
|
||||
Dark mode: a saved preference, else the router default, else the OS. */
|
||||
(() => {
|
||||
const root = document.documentElement,
|
||||
const root = document.querySelector(':root'),
|
||||
sd = (window.__fsSD || {}).darkmode;
|
||||
let saved = null;
|
||||
try { saved = localStorage.getItem('fs-darkmode'); } catch (e) {}
|
||||
@@ -201,7 +210,7 @@
|
||||
applier's lsGet means. The outer try is the last resort for everything else. */
|
||||
const lsGet = (k) => { try { return localStorage.getItem(k); } catch (e) { return null; } };
|
||||
try {
|
||||
const root = document.documentElement,
|
||||
const root = document.querySelector(':root'),
|
||||
sd = window.__fsSD || {};
|
||||
let p = lsGet('fs-palette');
|
||||
/* A stray or retired palette name needs no migration here: it fails the list
|
||||
@@ -342,7 +351,7 @@
|
||||
(() => {
|
||||
try {
|
||||
if (localStorage.getItem('fs-rail') === 'true')
|
||||
document.documentElement.setAttribute('data-rail', 'true');
|
||||
document.querySelector(':root').setAttribute('data-rail', 'true');
|
||||
} catch (e) {}
|
||||
})();
|
||||
|
||||
@@ -353,7 +362,7 @@
|
||||
try {
|
||||
const saved = localStorage.getItem('fs-layout');
|
||||
if (saved === 'top' || saved === 'sidebar')
|
||||
document.documentElement.setAttribute('data-layout', saved);
|
||||
document.querySelector(':root').setAttribute('data-layout', saved);
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<div class="cbi-value cbi-value-last">
|
||||
<label class="cbi-value-title" for="luci_password">{{ _('Password') }}</label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="cbi-input-text" type="password" name="luci_password" id="luci_password" autocomplete="current-password" autofocus />
|
||||
<input class="cbi-input-text" type="password" name="luci_password" id="luci_password" autocomplete="current-password" />
|
||||
</div>
|
||||
</div>
|
||||
{#
|
||||
@@ -181,6 +181,11 @@
|
||||
<script>window.__fsHttps={ports:{{ https_ports }},resource:"{{ resource }}"};</script>
|
||||
<script>
|
||||
(() => {
|
||||
const input = document.getElementsByName('luci_password')[0];
|
||||
|
||||
if (input)
|
||||
input.focus();
|
||||
|
||||
/* if the router also serves HTTPS, hop over to it before the password goes over this
|
||||
* connection; the <img> probe is upstream's trick for proving the TLS port answers first
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user