mirror of
https://github.com/openwrt/luci.git
synced 2026-10-12 05:07:26 +04:00
luci-theme-footstrap: remove unused plugin and search api
Removes the companion-package extension API: the footstrap.settings.plugin loader (window.__fsPlugins), fs-search addSource/refresh and window.__fsSearchSources, and the remember and nodeForSegs exports. Nothing in the tree used them. Signed-off-by: Ivan Kvashonkin <vizzlef@gmail.com>
This commit is contained in:
1 parent
a7dc58d459
commit
d3de328368
6 files changed
+19
-132
No files matched your search
@@ -159,10 +159,6 @@ function currentNode() {
|
||||
return baseclass.extend({
|
||||
setTree,
|
||||
tree: () => _tree,
|
||||
/* raw presence, no alias or firstchild resolution: fs-commands gates each command on the menu
|
||||
* node whose `depends.acl` names the group that command needs, and a node that resolves
|
||||
* elsewhere would answer for a permission the session may not hold */
|
||||
nodeForSegs,
|
||||
segsFromPath,
|
||||
currentNode,
|
||||
resolveSegs,
|
||||
|
||||
@@ -952,8 +952,8 @@ function navigate(pathname, push, kbd) {
|
||||
/* the outgoing page's links are about to become a detached tree — do not hold one of them */
|
||||
_lastHovered = null;
|
||||
/* Run every registered navigation callback. The seam is inverted on purpose: a registrant calls
|
||||
* onNavigate() and the router names nobody, so an optional module that is not installed cannot
|
||||
* be a DependencyError taking out the whole chrome.
|
||||
* onNavigate() and the router names nobody, so the router keeps no static dependency
|
||||
* (a DependencyError would take out the whole chrome).
|
||||
*
|
||||
* The RESOLVED segments are passed in, because this runs before L.env is re-pointed below and a
|
||||
* callback reading L.env.dispatchpath would record the page being left.
|
||||
@@ -1452,8 +1452,7 @@ function wireVisibility() {
|
||||
|
||||
/* Callbacks to run on every SPA navigation, each handed the resolved segments of the INCOMING page
|
||||
* (they run before L.env is re-pointed). The registry is inverted on purpose: a registrant calls in
|
||||
* and the router names nobody, so it cannot grow a static dependency on a module that may not be
|
||||
* installed. */
|
||||
* and the router names nobody, so it cannot grow a static dependency on one. */
|
||||
const _navCbs = [];
|
||||
function onNavigate(fn) { if (typeof fn === 'function') _navCbs.push(fn); }
|
||||
|
||||
|
||||
@@ -99,48 +99,6 @@ function index() {
|
||||
return _index;
|
||||
}
|
||||
|
||||
/* ---- extra sources -------------------------------------------------------
|
||||
*
|
||||
* An optional package can add rows to the same list — one indexes the SECTION titles inside each
|
||||
* page, so "footstrap" finds System -> Appearance. A source hands over entries in the shape
|
||||
* buildIndex() produces and nothing else: the matching, the ranking and the rendering stay here,
|
||||
* or two lists would disagree about what a hit is. Two fields are the source's alone: `onTake`,
|
||||
* called when the row is chosen, and `key`, what the recents list stores it under when its `path`
|
||||
* is not its own (see keyOf).
|
||||
*
|
||||
* Registration is a GLOBAL ARRAY, not an export a package requires. This module is fetched on the
|
||||
* first gesture and most sessions never make it; a package that had to `require` it to register
|
||||
* would pull it onto every page and pay its 4.5 KB for a palette nobody opened. Pushing a function
|
||||
* onto `window.__fsSearchSources` costs the package nothing and names no one in either direction.
|
||||
*
|
||||
* `window.__fsSearchGen` is how a source says its data grew — a harvester fills in over a session
|
||||
* — and the stamp below is what rebuilds the pool when it does. */
|
||||
const _sources = [];
|
||||
let _pool = null, _stamp = -1;
|
||||
|
||||
function globalSources() {
|
||||
return Array.isArray(window.__fsSearchSources) ? window.__fsSearchSources : [];
|
||||
}
|
||||
|
||||
function addSource(fn) {
|
||||
_sources.push(fn);
|
||||
_pool = null;
|
||||
}
|
||||
function refresh() {
|
||||
_pool = null;
|
||||
}
|
||||
function pool() {
|
||||
const all = _sources.concat(globalSources());
|
||||
const stamp = all.length + (window.__fsSearchGen || 0);
|
||||
if (_pool && stamp === _stamp) return _pool;
|
||||
_stamp = stamp;
|
||||
_pool = all.reduce((rows, fn) => {
|
||||
try { return rows.concat(fn() || []); }
|
||||
catch (e) { console.error('footstrap: a search source threw', e); return rows; }
|
||||
}, index().slice());
|
||||
return _pool;
|
||||
}
|
||||
|
||||
/* ---- matching ----------------------------------------------------------- */
|
||||
|
||||
/* Every whitespace-separated token must hit something: a second word means AND. Deliberately not
|
||||
@@ -160,7 +118,7 @@ function search(q, limit) {
|
||||
if (!toks.length) return [];
|
||||
|
||||
const hits = [];
|
||||
for (const e of pool()) {
|
||||
for (const e of index()) {
|
||||
let sum = 0;
|
||||
for (const tok of toks) {
|
||||
const s = tokenScore(e, tok);
|
||||
@@ -179,31 +137,21 @@ function search(q, limit) {
|
||||
/* What the palette shows before anything is typed: an admin lives in three or four pages, so the
|
||||
* empty state is its most-used view.
|
||||
*
|
||||
* Only the KEY is stored, never the title — the title is resolved through the pool on every
|
||||
* render, so it follows the UI language and a row whose package went away drops out instead of
|
||||
* lingering as a dead row. A page's key is its menu path; a row from a source carries its own
|
||||
* `key`, because a section has no dispatcher node and therefore no path that is only its own —
|
||||
* the sections source keys one `admin/system/system#Footstrap`, the page it is on plus its own
|
||||
* heading. */
|
||||
* Only the PATH is stored, never the title — the title is resolved through the index on every
|
||||
* render, so it follows the UI language and a page an ACL change hides drops out instead of
|
||||
* lingering as a dead row. RECENT_KEY is the one name menu-footstrap-common.js's remember() also
|
||||
* writes; the two modules stay decoupled (see there), so the string is the only thing shared. */
|
||||
const RECENT_KEY = 'fs-recent';
|
||||
const RECENT_MAX = 8;
|
||||
|
||||
/* the string a row is remembered under, and the one menu-footstrap-common's remember() writes */
|
||||
function keyOf(e) {
|
||||
return e.key || e.path;
|
||||
}
|
||||
|
||||
/* The list is WRITTEN by menu-footstrap-common.js, which is on every page — this module is not any
|
||||
* more, and a palette that only loads when it is opened cannot be what records where the admin has
|
||||
* been. Read here, at open time, so it is always current. `prefs.lsGetArr` owns the parse, the
|
||||
* corruption guard and the Array check; only the "these are keys" filter belongs here. */
|
||||
function recentEntries() {
|
||||
const recent = prefs.lsGetArr(RECENT_KEY).filter((x) => typeof x === 'string');
|
||||
/* pool(), not index(): a section is recalled exactly as a page is. Against the index alone a
|
||||
* section key resolved to nothing and the row silently vanished, so taking "Footstrap" left
|
||||
* only "System" in the list — the page path is all either row carries. */
|
||||
const byKey = new Map(pool().map((e) => [ keyOf(e), e ]));
|
||||
return recent.map((k) => byKey.get(k)).filter(Boolean).slice(0, RECENT_MAX);
|
||||
const byPath = new Map(index().map((e) => [ e.path, e ]));
|
||||
return recent.map((k) => byPath.get(k)).filter(Boolean).slice(0, RECENT_MAX);
|
||||
}
|
||||
|
||||
/* ---- the palette -------------------------------------------------------- */
|
||||
@@ -311,16 +259,8 @@ function build() {
|
||||
e.trail.length ? E('span', { 'class': 'fs-search-opt-path' }, [ e.trail.join(' › ') ]) : ''
|
||||
]);
|
||||
/* close before the click reaches the router, which re-renders the chrome underneath;
|
||||
* no focus return, the user is going elsewhere.
|
||||
*
|
||||
* `onTake` is how a row from an extra source finishes the job the href cannot: a
|
||||
* section row's href can only reach the PAGE, so the source that produced it opens the
|
||||
* tab and scrolls to the section itself. It fires for a click and for Enter alike —
|
||||
* Enter synthesises this very click. */
|
||||
a.addEventListener('click', () => {
|
||||
close(false);
|
||||
if (typeof e.onTake === 'function') e.onTake();
|
||||
});
|
||||
* no focus return, the user is going elsewhere */
|
||||
a.addEventListener('click', () => close(false));
|
||||
a.addEventListener('pointermove', () => { if (at !== i) setActive(i); });
|
||||
list.appendChild(a);
|
||||
return a;
|
||||
@@ -421,7 +361,5 @@ function openPalette() {
|
||||
}
|
||||
|
||||
return baseclass.extend({
|
||||
open: openPalette,
|
||||
/* the seam an optional package registers through; see addSource() */
|
||||
addSource, refresh
|
||||
open: openPalette
|
||||
});
|
||||
@@ -52,27 +52,19 @@ function wirePageModules() {
|
||||
* and the warm pass that uses it, so both live here, in the file every page already loads.
|
||||
*
|
||||
* The palette reads the list back from localStorage when it opens, so the two halves share the key
|
||||
* and nothing else. */
|
||||
* and nothing else — fs-search.js owns the "recently visited" feature, so RECENT_KEY and RECENT_MAX
|
||||
* below have to match its constants of the same name exactly. */
|
||||
const RECENT_KEY = 'fs-recent';
|
||||
const RECENT_MAX = 8;
|
||||
const RECENT_WARM = 5;
|
||||
|
||||
/* A key is a menu path, or a page path plus the heading of a section inside it
|
||||
* (`admin/system/system#Footstrap`) — a section has no dispatcher node to name it, and only the
|
||||
* source that produced the row can build that half. Exported for exactly that: the writer stays
|
||||
* one function, or the two halves would drift on the cap and the de-duplication. */
|
||||
/* A key is a menu path — what the router can navigate to and what warmRecent() prefetches. */
|
||||
function remember(key) {
|
||||
if (typeof key !== 'string' || !key) return;
|
||||
const recent = prefs.lsGetArr(RECENT_KEY).filter((x) => typeof x === 'string');
|
||||
prefs.lsSet(RECENT_KEY, JSON.stringify([ key ].concat(recent.filter((p) => p !== key)).slice(0, RECENT_MAX)));
|
||||
}
|
||||
|
||||
/* the page half of a key: what the router can navigate to and what warmRecent() prefetches */
|
||||
function pageOf(key) {
|
||||
const h = key.indexOf('#');
|
||||
return h < 0 ? key : key.slice(0, h);
|
||||
}
|
||||
|
||||
/* ---- warm the pages this admin actually uses ----
|
||||
*
|
||||
* The router's per-link prefetch needs a hover, tap or focus first, so a session's first visit to a
|
||||
@@ -87,10 +79,9 @@ function pageOf(key) {
|
||||
function warmRecent() {
|
||||
try { if (navigator.connection && navigator.connection.saveData) return; } catch (e) {}
|
||||
const here = (L.env.dispatchpath || []).join('/');
|
||||
/* Keys, not paths: a section key names the page it sits on, and two sections of one page must
|
||||
* warm it once — the module chain is the page's. */
|
||||
/* already de-duplicated: remember() drops the key from its old slot before re-adding it */
|
||||
const keys = prefs.lsGetArr(RECENT_KEY).filter((p) => typeof p === 'string');
|
||||
const paths = [ ...new Set(keys.map(pageOf)) ].filter((p) => p !== here).slice(0, RECENT_WARM);
|
||||
const paths = keys.filter((p) => p !== here).slice(0, RECENT_WARM);
|
||||
if (!paths.length) return;
|
||||
const go = () => paths.forEach((p) => router.prefetchSegs(p.split('/')));
|
||||
if (typeof window.requestIdleCallback === 'function')
|
||||
@@ -139,23 +130,6 @@ function wireSearch() {
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- optional companion packages ----
|
||||
*
|
||||
* header.ut prints `window.__fsPlugins` from `footstrap.settings.plugin`, a list a package writes
|
||||
* from its own uci-defaults; each entry is a LuCI module name, already whitelisted there. The
|
||||
* chrome requires each one after everything below is wired — a plugin registers itself through the
|
||||
* seams the theme exports (`fs-router.onNavigate`, `fs-search.addSource`) and the theme names
|
||||
* nobody. A plugin that throws costs only itself.
|
||||
*
|
||||
* No plugin, no cost: an empty list is the shipped state and this loop does nothing. */
|
||||
function loadPlugins() {
|
||||
const RT = window.L;
|
||||
const names = Array.isArray(window.__fsPlugins) ? window.__fsPlugins : [];
|
||||
names.forEach((name) => {
|
||||
RT.require(name).catch((e) => console.error('footstrap: plugin ' + name + ' did not load', e));
|
||||
});
|
||||
}
|
||||
|
||||
/* warn/danger split for the meter fill (theme/25-progressbar.css): a DECISION, not a measurement —
|
||||
* 20% of an 84 MiB overlay and 20% of an 8 GiB disk are different news at the same reading, and a
|
||||
* plain percentage is kept anyway so every meter on the page reads the same way. */
|
||||
@@ -382,10 +356,6 @@ ensureOverviewHelpers();
|
||||
* halves (fs-menutree, fs-prefs) are separate modules. */
|
||||
|
||||
return baseclass.extend({
|
||||
/* the seam a companion package writes its own rows into the recents list through; see
|
||||
* remember() for what a key is */
|
||||
remember,
|
||||
|
||||
/* the seam fs-overview.js calls, on its own already-coalesced poll-tick observer, to annotate
|
||||
* the meters a stock Status -> Overview include draws with its own local progressbar() */
|
||||
annotateMeters,
|
||||
@@ -422,9 +392,6 @@ return baseclass.extend({
|
||||
wirePageModules();
|
||||
router.wire();
|
||||
router.wireVisibility();
|
||||
/* last: a plugin registers against the parts above, and a broken one must not be able
|
||||
* to take the chrome with it */
|
||||
loadPlugins();
|
||||
/* no sane partial recovery — a throw above loses the menu, the router and the Appearance
|
||||
* tab together — so this fails loudly rather than silently */
|
||||
}).catch((e) => console.error('footstrap: chrome init failed', e));
|
||||
|
||||
@@ -69,22 +69,10 @@
|
||||
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, fs_plugins }) %}
|
||||
{% include('themes/footstrap/partials/head', { boardinfo, fs_defaults }) %}
|
||||
|
||||
{#
|
||||
data-page carries the DISPATCH path (ctx.path), not request_path: on a firstchild route
|
||||
|
||||
@@ -150,7 +150,6 @@
|
||||
{# 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
|
||||
|
||||
Reference in new issue
Block a user