mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 02:11:19 +08:00
⛅ Sync 2026-07-05 04:44:40
This commit is contained in:
parent
1bca68cd89
commit
5378a4f7c1
@ -108,11 +108,13 @@ Some third-party LuCI apps ship markup that doesn't adapt to the theme and needs
|
||||
1. **One file per page, named by `data-page`.** Each patch lives at `media/patches/<page>.css`, where `<page>` is the value of `<body data-page="…">` for the target page — i.e. the request path segments joined by `-` (e.g. `admin-services-openclash-config`). `header.ut` computes the same string at render time from `ctx.request_path`, falling back to `ctx.path` when `request_path` is empty (`join('-', length(ctx.request_path) ? ctx.request_path : ctx.path)`) so default landings reached without an explicit path still resolve their patch.
|
||||
2. **Build splits, not bundles.** `vite.config.ts` adds every `media/patches/*.css` as its own Rollup entry, so each compiles to `htdocs/luci-static/aurora/patches/<page>.css`. They are no longer part of `main.css`.
|
||||
3. **`@reference`, not `@import`.** Every patch file starts with `@reference "../main.css";`. This loads the theme context (tokens, custom utilities like `bg-surface`, the `dark:` variant) so `@apply` resolves — **without** re-emitting `main.css` into the patch output. Using `@import` here would inline all of `main.css` into every patch (hundreds of KB); `@reference` keeps each patch to just its own rules.
|
||||
4. **`header.ut` links exactly one patch.** A static allow-list `PATCH_PAGES` in `header.ut` lists which pages have a patch file. When the current page is in the list, header emits a single `<link>` to its patch file, right after `main.css` (so patches can override base styles). Pages not in the list get nothing — no extra request, no 404. A static list is used (rather than probing the filesystem) to avoid a hard `fs` dependency in the template and to avoid 404s on the ~95% of pages with no patch.
|
||||
4. **`header.ut` discovers patches at render time.** On each (non-login) page render, `header.ut` lists `/www/luci-static/aurora/patches/` with ucode's `fs.lsdir()` (a readdir of a dozen entries — microseconds, dwarfed by the template's existing `ubus` call) and matches the installed `*.css` filenames against the **cumulative path-segment prefixes** of the request: a patch matches its exact page and any subpage, but only on real segment boundaries — `admin-services-wol.css` covers `admin/services/wol/plus`, yet never a sibling app whose own segment merely starts the same way (`admin/services/wol-plus`). Every matching patch is linked right after `main.css`, in lexical order — so a general patch loads before a more specific one and the specific one cascades on top. Pages with no match get nothing — no extra request, no 404. If the directory is missing or unreadable, the list is empty and the page renders unpatched.
|
||||
5. **The patches directory is a drop-in extension point.** Because discovery is at render time, patches don't have to ship with the theme: **any package may install a `<page-prefix>.css` into `/www/luci-static/aurora/patches/`** and the theme will load it on matching pages. Install/uninstall lifecycle is automatic — the file appears and disappears with the package, no registration or allow-list rebuild. (Patches shipped this way are plain CSS served as-is; the theme's own patches additionally go through the Tailwind build below.)
|
||||
6. **Dynamically generated pages are covered by their fixed prefix.** Some apps mint a page per entity — e.g. QModem's SMS conversations render as `admin-modem-qmodem-sms-conversation-<contact>`. Name the patch after the fixed prefix (`admin-modem-qmodem-sms-conversation.css`) and the prefix match loads it for every conversation page, regardless of the contact name. No wildcard syntax is needed (and `*` in a filename is not supported).
|
||||
|
||||
**Adding a patch:**
|
||||
|
||||
1. Open the target page in the browser and read `document.body.dataset.page` — that exact string is your filename.
|
||||
1. Open the target page in the browser and read `document.body.dataset.page` — that exact string is your filename (for a family of dynamic per-entity pages, use their fixed prefix instead — see point 5 above).
|
||||
2. Create `media/patches/<that-string>.css`:
|
||||
```css
|
||||
/* PATCH: <page> (luci-app-foo) */
|
||||
@ -122,12 +124,38 @@ Some third-party LuCI apps ship markup that doesn't adapt to the theme and needs
|
||||
/* narrow, selector-scoped overrides using @apply + CSS Nesting */
|
||||
}
|
||||
```
|
||||
3. Run `pnpm build` (or just `pnpm gen:patch-pages`). The `PATCH_PAGES` allow-list in `header.ut` is **auto-generated** from the `patches/` directory by `scripts/gen-patch-pages.js` — no manual editing. It rewrites the region between the `//#patch-pages-start` / `//#patch-pages-end` markers; don't hand-edit inside them.
|
||||
3. Run `pnpm build`. There is no allow-list to regenerate — the loader discovers whatever `.css` files are installed under `patches/` at render time.
|
||||
4. Verify `htdocs/luci-static/aurora/patches/<page>.css` is small (just your rules, not a copy of `main.css`).
|
||||
|
||||
> Removing a patch is symmetric: delete the file and rebuild — it drops out of `PATCH_PAGES` automatically.
|
||||
> Removing a patch is symmetric: delete the file and rebuild — the loader stops linking it because it no longer exists.
|
||||
|
||||
> **Naming notes:** match the page exactly — granularity is per page, not per app. An app with several pages (e.g. openclash `…-config` and `…-settings`) gets one file per page. The filename has no `_` prefix (unlike the `_`-prefixed partials, which are `@import`-only fragments); patch files are real build entries that ship to `htdocs/`.
|
||||
**Shipping a patch with a third-party app** (no theme release needed): build or hand-write a plain CSS file named after your page's `data-page` prefix and install it from your package's Makefile:
|
||||
|
||||
```makefile
|
||||
define Package/luci-app-foo/install
|
||||
...
|
||||
$(INSTALL_DIR) $(1)/www/luci-static/aurora/patches
|
||||
$(INSTALL_DATA) ./htdocs/aurora-patch.css \
|
||||
$(1)/www/luci-static/aurora/patches/admin-services-foo.css
|
||||
endef
|
||||
```
|
||||
|
||||
The theme loads it automatically on `admin-services-foo` and all its subpages whenever both packages are installed. Note app-shipped patches bypass the theme's Tailwind build — write plain CSS (you can still target the theme's CSS custom properties, e.g. `var(--surface)`), and scope every rule under your own `[data-page^="…"]` selector.
|
||||
|
||||
**Naming.** The filename is the page's `data-page` string; matching by prefix means broader targets also just work:
|
||||
|
||||
| You want to patch… | File to create | Then it loads on |
|
||||
| --- | --- | --- |
|
||||
| One specific page, `admin/services/foo/general` | `admin-services-foo-general.css` | that page (and any subpage under it) |
|
||||
| A whole app, all pages under `admin/services/foo/…` | `admin-services-foo.css` | `foo`, `foo/general`, `foo/rules`, … |
|
||||
| Dynamic per-entity pages, e.g. QModem SMS `…/sms/conversation/<contact>` | `admin-modem-qmodem-sms-conversation.css` (the fixed prefix — no wildcard needed) | every conversation page, whatever the contact |
|
||||
|
||||
Two rules of thumb that follow from prefix matching:
|
||||
|
||||
- **A patch applies to its page and all subpages by default.** `admin-services-foo.css` loads on every page under `admin/services/foo/…`. When you need finer targeting, narrow it in either of two ways: scope individual rules inside the file (`[data-page="admin-services-foo-general"] { … }` only affects that one page), or ship an additional, longer-named file (`admin-services-foo-rules.css`) for page-specific rules — on a page matching both, **both load**, shorter name first, so the more specific file wins the cascade.
|
||||
- **Matching respects path-segment boundaries**, so a prefix never leaks onto a lookalike sibling: `admin-services-wol.css` covers `admin/services/wol/plus` but not a different app at `admin/services/wol-plus`. The one unavoidable collision is two paths joining to the same `data-page` string (`wol/plus` vs `wol-plus`) — such a patch loads on both pages. If that matters, key rules to your app's own class names/ids so an accidental load matches nothing.
|
||||
|
||||
> Unlike the `_`-prefixed partials (which are `@import`-only fragments), patch filenames have no `_` prefix — each is a real build entry that ships to `htdocs/`.
|
||||
|
||||
### Design Tokens
|
||||
|
||||
@ -144,6 +172,8 @@ Some third-party LuCI apps ship markup that doesn't adapt to the theme and needs
|
||||
2. Run `pnpm gen:tokens` (also runs automatically as part of `pnpm build`) to rewrite `src/media/_tokens.css` — it emits `:root` (light) and `[data-darkmode="true"]` (dark) blocks plus the `@theme inline` mapping, in that order.
|
||||
3. Run `pnpm test` to check the color-math operators and derived-token invariants (`tests/engine.test.js`, `tests/resolve.test.js`, `tests/surfaces.test.js`) — e.g. hue families, lightness ordering between `bg`/`surface_sunken`/`surface`, and translucency of menu backgrounds.
|
||||
|
||||
**Runtime overrides from UCI:** `header.ut` reads `uci get_all aurora.theme` on each render and re-emits stored tokens as CSS custom-property overrides in an inline `<style>` after `main.css`. Keys are namespaced by prefix — `light_*` and `struct_*` land in `:root`, `dark_*` in `[data-darkmode="true"]` — with the prefix stripped and `_` mapped to `-` (e.g. `light_surface_sunken` → `--surface-sunken`). The template flattens all keys in a single pass into two pre-joined declaration strings (rather than per-key template loops), which halves the iteration work and keeps the emitted `<style>` compact. This is the hook `luci-app-aurora-config` writes through.
|
||||
|
||||
### LuCI JavaScript API
|
||||
|
||||
For LuCI-specific JavaScript development, refer to the official API documentation:
|
||||
|
||||
@ -4,10 +4,9 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "npm run clean && npm run gen:tokens && npm run gen:patch-pages && vite build",
|
||||
"build": "npm run clean && npm run gen:tokens && vite build",
|
||||
"clean": "node scripts/clean.js",
|
||||
"gen:tokens": "node scripts/gen-tokens.js",
|
||||
"gen:patch-pages": "node scripts/gen-patch-pages.js",
|
||||
"test": "node --test tests/*.test.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
import { readdirSync } from "node:fs";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
// Regenerates the PATCH_PAGES allow-list inside header.ut from the set of
|
||||
// per-page patch files in src/media/patches/. Keeps the on-demand patch loader
|
||||
// in sync with the patches that actually exist, with no manual bookkeeping.
|
||||
// Source: src/media/patches/<page>.css -> header.ut //#patch-pages markers
|
||||
|
||||
const PATCHES_DIR = resolve(import.meta.dirname, "../src/media/patches");
|
||||
const HEADER_UT = resolve(
|
||||
import.meta.dirname,
|
||||
"../../ucode/template/themes/aurora/header.ut",
|
||||
);
|
||||
|
||||
const MARKER = /\t\/\/#patch-pages-start\n[\s\S]*?\t\/\/#patch-pages-end/;
|
||||
|
||||
const pages = readdirSync(PATCHES_DIR)
|
||||
.filter((f) => f.endsWith(".css"))
|
||||
.map((f) => f.slice(0, -4))
|
||||
.sort();
|
||||
|
||||
const block =
|
||||
"\t//#patch-pages-start\n" +
|
||||
"\tconst PATCH_PAGES = [\n" +
|
||||
pages.map((p) => `\t\t'${p}',`).join("\n") +
|
||||
(pages.length ? "\n" : "") +
|
||||
"\t];\n" +
|
||||
"\t//#patch-pages-end";
|
||||
|
||||
const src = await readFile(HEADER_UT, "utf-8");
|
||||
|
||||
if (!MARKER.test(src)) {
|
||||
console.error(
|
||||
"gen-patch-pages: //#patch-pages-start.../#patch-pages-end markers not found in header.ut",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const next = src.replace(MARKER, block);
|
||||
|
||||
if (next !== src) {
|
||||
await writeFile(HEADER_UT, next, "utf-8");
|
||||
console.log(`gen-patch-pages: wrote ${pages.length} page(s) to header.ut`);
|
||||
} else {
|
||||
console.log("gen-patch-pages: header.ut already up to date");
|
||||
}
|
||||
@ -26,7 +26,7 @@ No linter CLI. Formatting uses Prettier with format-on-save (`.vscode/settings.j
|
||||
- `.dev/public/aurora/` → `htdocs/luci-static/aurora/` (copied as-is)
|
||||
- `ucode/template/themes/aurora/*.ut` — server-side templates, not processed by Vite
|
||||
|
||||
**CSS**: two Tailwind CSS v4 entry points in `.dev/src/media/` — `main.css` (admin UI; a pure import manifest over `_tokens.css`, `_base.css`, `_elements.css`, `_layout.css`, `components/*.css`, `_utilities.css`) and `login.css` (standalone login page; imports `_base.css`). Third-party compatibility patches are not bundled into `main.css`; each lives in `media/patches/<data-page>.css`, builds to its own `htdocs/luci-static/aurora/patches/<data-page>.css`, and is linked on demand by `header.ut` (gated on a `PATCH_PAGES` allow-list) — see "On-Demand Third-Party Patches" in `.dev/docs/DEVELOPMENT.md`. All styling MUST use TailwindCSS v4 utility classes via `@apply` — no raw CSS properties (e.g. write `@apply text-sm font-semibold;` not `font-size: 14px; font-weight: 600;`). Use [CSS Nesting syntax](https://drafts.csswg.org/css-nesting/) for selectors. Theme colors defined as OKLCH custom properties in `_tokens.css` and mapped via `@theme inline`. `@layer` at-rules stripped by PostCSS plugin for OpenWrt compatibility. See `.dev/docs/DEVELOPMENT.md` for the full CSS file layout.
|
||||
**CSS**: two Tailwind CSS v4 entry points in `.dev/src/media/` — `main.css` (admin UI; a pure import manifest over `_tokens.css`, `_base.css`, `_elements.css`, `_layout.css`, `components/*.css`, `_utilities.css`) and `login.css` (standalone login page; imports `_base.css`). Third-party compatibility patches are not bundled into `main.css`; each lives in `media/patches/<data-page>.css`, builds to its own `htdocs/luci-static/aurora/patches/<data-page>.css`, and is linked on demand by `header.ut`, which discovers installed patches at render time via `fs.lsdir()` and matches them on path-segment-boundary prefixes (dynamic subpages inherit their prefix's patch; third-party packages may drop their own patch into the directory) — see "On-Demand Third-Party Patches" in `.dev/docs/DEVELOPMENT.md`. All styling MUST use TailwindCSS v4 utility classes via `@apply` — no raw CSS properties (e.g. write `@apply text-sm font-semibold;` not `font-size: 14px; font-weight: 600;`). Use [CSS Nesting syntax](https://drafts.csswg.org/css-nesting/) for selectors. Theme colors defined as OKLCH custom properties in `_tokens.css` and mapped via `@theme inline`. `@layer` at-rules stripped by PostCSS plugin for OpenWrt compatibility. See `.dev/docs/DEVELOPMENT.md` for the full CSS file layout.
|
||||
|
||||
**Design tokens**: `_tokens.css` is GENERATED — do not hand-edit (see its header comment). Source of truth is `.dev/tokens/`: `defaults.js` holds the 10 editable input colors per mode (`bg`, `surface`, `text`, `brand`, `on_brand`, `link`, `info`, `warning`, `success`, `danger`); `spec.js` declares how every other token derives from those via `mix`/`shade`/`set`/`alpha`/`const` operators plus `FIXED` literals (shadows); `engine.js`/`resolve.js` do the OKLCH math and flatten everything to plain `oklch(...)` values. After changing inputs or derivations, run `pnpm gen:tokens` to rewrite `_tokens.css`, and `pnpm test` to check the color-math and token invariants.
|
||||
|
||||
|
||||
@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=1.0.5
|
||||
PKG_RELEASE:=39
|
||||
PKG_VERSION:=1.0.6
|
||||
PKG_RELEASE:=40
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_MINIFY_CSS:=
|
||||
|
||||
@ -12,26 +12,36 @@
|
||||
{%
|
||||
import { getuid, getspnam } from 'luci.core';
|
||||
import { cursor } from 'uci';
|
||||
import { lsdir } from 'fs';
|
||||
|
||||
const boardinfo = ubus.call('system', 'board') ?? {};
|
||||
const hostname = striptags(boardinfo.hostname ?? '?');
|
||||
const uci = cursor();
|
||||
const tokens = uci.get_all('aurora', 'theme') || {};
|
||||
|
||||
// On-demand third-party patches — see DEVELOPMENT.md. AUTO-GENERATED by
|
||||
// `pnpm gen:patch-pages`; do not edit between the markers.
|
||||
//#patch-pages-start
|
||||
const PATCH_PAGES = [
|
||||
'admin-dashboard',
|
||||
'admin-modem-modemdata-modempreview',
|
||||
'admin-network-network',
|
||||
'admin-services-openclash-config',
|
||||
'admin-services-openclash-settings',
|
||||
'admin-statistics-graphs',
|
||||
'admin-system-filemanager',
|
||||
];
|
||||
//#patch-pages-end
|
||||
const page = join('-', length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []));
|
||||
const has_patch = page in PATCH_PAGES;
|
||||
const page_segs = length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []);
|
||||
const page = join('-', page_segs);
|
||||
// On-demand patches — see "On-Demand Third-Party Patches" in DEVELOPMENT.md.
|
||||
let patch_files = [];
|
||||
if (!blank_page) {
|
||||
let seg_prefixes = {}, acc = null;
|
||||
for (let seg in page_segs)
|
||||
seg_prefixes[acc = (acc == null ? seg : `${acc}-${seg}`)] = true;
|
||||
for (let f in lsdir(`/www${media}/patches`) ?? [])
|
||||
if (substr(f, -4) == '.css' && exists(seg_prefixes, substr(f, 0, -4)))
|
||||
push(patch_files, substr(f, 0, -4));
|
||||
sort(patch_files);
|
||||
}
|
||||
let token_css_light = '', token_css_dark = '';
|
||||
for (let key, val in tokens) {
|
||||
if (index(key, 'light_') == 0)
|
||||
token_css_light += `--${replace(substr(key, 6), '_', '-')}:${val};`;
|
||||
else if (index(key, 'struct_') == 0)
|
||||
token_css_light += `--${replace(substr(key, 7), '_', '-')}:${val};`;
|
||||
else if (index(key, 'dark_') == 0)
|
||||
token_css_dark += `--${replace(substr(key, 5), '_', '-')}:${val};`;
|
||||
}
|
||||
|
||||
const nav_type = tokens.nav_type || 'mega-menu';
|
||||
const toolbar_enabled = tokens.toolbar_enabled ? tokens.toolbar_enabled == '1' : true;
|
||||
const icon_cache_version = tokens.icon_cache_version || '0';
|
||||
@ -73,7 +83,7 @@
|
||||
<html lang="{{ dispatcher.lang }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ striptags(`${dispatched?.title ? `${_(dispatched.title)} - ` : ''}${boardinfo.hostname ?? '?'}`) }}</title>
|
||||
<title>{{ striptags(dispatched?.title ? `${_(dispatched.title)} - ` : '') }}{{ hostname }}</title>
|
||||
{% if (blank_page): %}
|
||||
<script>
|
||||
(function () {
|
||||
@ -154,9 +164,9 @@
|
||||
<link rel="stylesheet" href="{{ media }}/login.css">
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ media }}/main.css">
|
||||
{% if (has_patch): %}
|
||||
<link rel="stylesheet" href="{{ media }}/patches/{{ page }}.css">
|
||||
{% endif %}
|
||||
{% for (let patch in patch_files): %}
|
||||
<link rel="stylesheet" href="{{ media }}/patches/{{ patch }}.css">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ media }}/fonts/aurora-font.css?v={{ icon_cache_version }}">
|
||||
<link rel="icon" href="{{ media }}/images/{{ logo_svg }}?v={{ icon_cache_version }}" sizes="any" type="image/svg+xml">
|
||||
@ -177,22 +187,10 @@
|
||||
{% if (css): %}
|
||||
<style title="text/css">{{ css }}</style>
|
||||
{% endif %}
|
||||
{% if (length(tokens)): %}
|
||||
{% if (token_css_light || token_css_dark): %}
|
||||
<style>
|
||||
:root {
|
||||
{% for (let key in tokens): %}
|
||||
{% if (index(key, 'light_') == 0 || index(key, 'struct_') == 0): %}
|
||||
--{{ replace(substr(key, index(key, 'light_') == 0 ? 6 : 7), '_', '-') }}: {{ tokens[key] }};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
[data-darkmode="true"] {
|
||||
{% for (let key in tokens): %}
|
||||
{% if (index(key, 'dark_') == 0): %}
|
||||
--{{ replace(substr(key, 5), '_', '-') }}: {{ tokens[key] }};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
:root { {{ token_css_light }} }
|
||||
[data-darkmode="true"] { {{ token_css_dark }} }
|
||||
</style>
|
||||
{% endif %}
|
||||
{% if (!blank_page): %}
|
||||
@ -214,7 +212,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<a class="brand" href="/">{{ striptags(boardinfo.hostname ?? '?') }}</a>
|
||||
<a class="brand" href="/">{{ hostname }}</a>
|
||||
<ul class="nav" id="topmenu" style="display:none"></ul>
|
||||
{% if (nav_type == 'sidebar'): %}
|
||||
<ol class="header-crumb" id="header-crumb"></ol>
|
||||
@ -241,7 +239,7 @@
|
||||
#}
|
||||
<div class="desktop-menu-sheet"><div class="desktop-menu-canvas"></div></div>
|
||||
<div class="desktop-menu-board" aria-hidden="true"><span class="board-label">{{ _('Device') }}</span>
|
||||
<span class="board-line board-line-host" title="{{ entityencode(striptags(boardinfo.hostname ?? '?'), true) }}"><span class="board-text">{{ striptags(boardinfo.hostname ?? '?') }}</span></span>
|
||||
<span class="board-line board-line-host" title="{{ entityencode(hostname, true) }}"><span class="board-text">{{ hostname }}</span></span>
|
||||
{% if (board_model): %}
|
||||
<span class="board-line board-line-model" title="{{ entityencode(striptags(board_model), true) }}"><span class="board-text">{{ striptags(board_model) }}</span></span>
|
||||
{% endif %}
|
||||
|
||||
2
quectel_MHI/Makefile
Executable file → Normal file
2
quectel_MHI/Makefile
Executable file → Normal file
@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=pcie_mhi
|
||||
PKG_VERSION:=1.3.8
|
||||
PKG_RELEASE:=32
|
||||
PKG_RELEASE:=33
|
||||
|
||||
include $(INCLUDE_DIR)/kernel.mk
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
Loading…
Reference in New Issue
Block a user