@nsnanocat/preference-panes 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -127
- package/dist/module/app.mjs +1030 -0
- package/dist/module/index.html +14 -0
- package/dist/preference-panes.config.js +839 -813
- package/dist/preference-panes.mjs +935 -825
- package/dist/preference-panes.proxy.js +1726 -2123
- package/package.json +62 -67
- package/src/BoxJS.mjs +86 -0
- package/src/Store.mjs +127 -0
- package/src/browser/app.mjs +24 -148
- package/src/browser/client.d.mts +150 -0
- package/src/browser/client.mjs +191 -212
- package/src/browser/components.mjs +59 -0
- package/src/browser/index.d.ts +19 -152
- package/src/browser/index.mjs +60 -5
- package/src/browser/module.html +14 -0
- package/src/browser/panel.css +221 -221
- package/src/browser/panel.mjs +455 -514
- package/src/build.mjs +31 -0
- package/src/index.d.ts +227 -133
- package/src/index.mjs +3 -6
- package/src/lib/boxjs.mjs +77 -99
- package/src/lib/response.mjs +16 -0
- package/src/lib/settings-path.mjs +10 -23
- package/src/proxy/config.mjs +6 -11
- package/src/proxy/handler.mjs +40 -27
- package/src/proxy/response.mjs +16 -0
- package/dist/preference-panes.request.js +0 -2126
- package/dist/settings/app.mjs +0 -1044
- package/dist/settings/home.css +0 -134
- package/dist/settings/index.html +0 -15
- package/dist/settings/panel.css +0 -325
- package/src/PreferencesHandler.mjs +0 -50
- package/src/SettingsHandler.mjs +0 -144
- package/src/browser/home.css +0 -134
- package/src/browser/site.html +0 -15
- package/src/proxy/request.mjs +0 -5
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 创建元素,所有展示文本通过 textContent 写入。
|
|
3
|
+
* Create elements and assign display text through textContent only.
|
|
4
|
+
* @template {keyof HTMLElementTagNameMap} T
|
|
5
|
+
* @param {T} tag 元素标签 / Element tag.
|
|
6
|
+
* @param {string} className 样式类名 / CSS class.
|
|
7
|
+
* @param {string} [text] 纯文本 / Plain text.
|
|
8
|
+
* @returns {HTMLElementTagNameMap[T]} 创建的元素 / Created element.
|
|
9
|
+
*/
|
|
10
|
+
export function element(tag, className, text) {
|
|
11
|
+
const node = document.createElement(tag);
|
|
12
|
+
node.className = className;
|
|
13
|
+
if (text !== undefined) node.textContent = text;
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 元数据地址只允许 HTTP(S) 和相对地址。
|
|
19
|
+
* Allow only HTTP(S) and relative metadata addresses.
|
|
20
|
+
* @param {string} value 元数据地址 / Metadata address.
|
|
21
|
+
* @returns {string} 完整地址 / Absolute address.
|
|
22
|
+
*/
|
|
23
|
+
export function resourceURL(value) {
|
|
24
|
+
const url = new URL(value, location.href);
|
|
25
|
+
if (!["http:", "https:"].includes(url.protocol)) throw new TypeError("Metadata URLs must use HTTP(S)");
|
|
26
|
+
return url.href;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 展示标准 BoxJS 图标;icons 保持透明/彩色语义,不解释为亮暗版本。
|
|
31
|
+
* Display standard BoxJS icons, preserving transparent/color rather than light/dark semantics.
|
|
32
|
+
* @param {import("../index.js").BoxJSMetadata} metadata 展示信息 / Presentation metadata.
|
|
33
|
+
* @param {string} className 样式 / CSS class.
|
|
34
|
+
* @returns {HTMLImageElement | null} 图标或无图标 / Icon or no icon.
|
|
35
|
+
*/
|
|
36
|
+
export function icon(metadata, className) {
|
|
37
|
+
const source = metadata.icon || metadata.icons?.[1] || metadata.icons?.[0];
|
|
38
|
+
if (!source) return null;
|
|
39
|
+
const image = element("img", className);
|
|
40
|
+
image.src = resourceURL(source);
|
|
41
|
+
image.alt = "";
|
|
42
|
+
return image;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 共享加载失败视图,不创建配置表单或数据读取。
|
|
47
|
+
* Share a load-error view without creating controls or reading settings.
|
|
48
|
+
* @param {Error} error 失败原因 / Failure reason.
|
|
49
|
+
* @param {() => unknown} retry 重试动作 / Retry action.
|
|
50
|
+
* @returns {HTMLElement} 错误视图 / Error view.
|
|
51
|
+
*/
|
|
52
|
+
export function errorView(error, retry) {
|
|
53
|
+
const view = element("section", "pp-error");
|
|
54
|
+
const button = element("button", "", "重新读取");
|
|
55
|
+
button.type = "button";
|
|
56
|
+
button.onclick = retry;
|
|
57
|
+
view.append(element("p", "", `加载失败:${error.message}`), button);
|
|
58
|
+
return view;
|
|
59
|
+
}
|
package/src/browser/index.d.ts
CHANGED
|
@@ -1,155 +1,22 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
key: string;
|
|
15
|
-
/** 失败原因,仅错误事件提供 / Failure reason, provided for errors only. */
|
|
16
|
-
message?: string;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* 浏览器会话客户端选项。
|
|
20
|
-
* Options for the browser session client.
|
|
21
|
-
*/
|
|
22
|
-
export interface PreferencesClientOptions {
|
|
23
|
-
/** 默认使用浏览器 fetch,可注入同签名传输 / Defaults to browser fetch; an equivalent transport may be supplied. */
|
|
24
|
-
fetch?: typeof globalThis.fetch;
|
|
25
|
-
/** 成功写入或失败时调用,不用于读取事件 / Called for successful mutations or failures, not reads. */
|
|
26
|
-
notify?: (notification: Notification) => void;
|
|
27
|
-
/** 单次请求超时,单位毫秒,默认 10000 / Per-request timeout in milliseconds; defaults to 10000. */
|
|
28
|
-
timeout?: number;
|
|
1
|
+
import type { BoxJSInput } from "../index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 具体模块设置页的生命周期句柄。
|
|
5
|
+
* Lifecycle handle for a concrete module settings page.
|
|
6
|
+
*/
|
|
7
|
+
export interface MountedPreferences {
|
|
8
|
+
/**
|
|
9
|
+
* 移除页面、样式、监听器和临时会话。
|
|
10
|
+
* Remove the page, styles, listeners and transient sessions.
|
|
11
|
+
* @returns 无返回值 / No return value.
|
|
12
|
+
*/
|
|
13
|
+
destroy(): void;
|
|
29
14
|
}
|
|
30
15
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
definition: ModuleDefinition;
|
|
37
|
-
/** 点分键到显示值的映射,已包含适用的默认值 / Dotted keys mapped to display values including applicable defaults. */
|
|
38
|
-
values: Record<string, SettingsScalar | SettingsScalar[]>;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* 只在页面存活期间维护模块缓存的通用客户端。
|
|
42
|
-
* Generic client maintaining module caches only during the page lifetime.
|
|
43
|
-
*/
|
|
44
|
-
export interface PreferencesClient {
|
|
45
|
-
/**
|
|
46
|
-
* HEAD 探测配置 Mock,不读取持久化数据。
|
|
47
|
-
* Probe the config Mock with HEAD without reading persistence.
|
|
48
|
-
* @param module 模块标识 / Module identifier.
|
|
49
|
-
* @returns 仅 HTTP 200 为 true;无效模块或请求失败为 false / True only for HTTP 200; false for invalid modules or failed requests.
|
|
50
|
-
*/
|
|
51
|
-
probe(module: string): Promise<boolean>;
|
|
52
|
-
/**
|
|
53
|
-
* 替换会话,各读取一次配置与设置子树。
|
|
54
|
-
* Replace the session and fetch config and settings subtree once each.
|
|
55
|
-
* @param module 模块标识 / Module identifier.
|
|
56
|
-
* @returns 新会话的独立快照 / Independent snapshot of the new session.
|
|
57
|
-
* @throws {Error} 写入进行中、请求或配置无效、会话被替换 / Active write, invalid request or config, or replaced session.
|
|
58
|
-
*/
|
|
59
|
-
open(module: string): Promise<ModuleSnapshot>;
|
|
60
|
-
/**
|
|
61
|
-
* 获取已打开模块的快照,不发请求。
|
|
62
|
-
* Get a snapshot of an open module without network requests.
|
|
63
|
-
* @param module 模块标识 / Module identifier.
|
|
64
|
-
* @returns 深拷贝快照 / Deep-cloned snapshot.
|
|
65
|
-
* @throws {Error} 模块尚未打开 / Module has not been opened.
|
|
66
|
-
*/
|
|
67
|
-
snapshot(module: string): ModuleSnapshot;
|
|
68
|
-
/**
|
|
69
|
-
* 取消未完成的读取并移除缓存,不撤销已发送的写入。
|
|
70
|
-
* Abort pending reads and discard the cache without undoing dispatched writes.
|
|
71
|
-
* @param module 模块标识 / Module identifier.
|
|
72
|
-
* @returns 无返回值 / No return value.
|
|
73
|
-
*/
|
|
74
|
-
leave(module: string): void;
|
|
75
|
-
/**
|
|
76
|
-
* POST 单个字段,HTTP 200 后更新缓存,不追加 GET。
|
|
77
|
-
* POST one field and update its cache only on HTTP 200, without a follow-up GET.
|
|
78
|
-
* @param module 已打开的模块 / Open module.
|
|
79
|
-
* @param key 完整点分字段路径 / Complete dotted field path.
|
|
80
|
-
* @param value 符合字段类型和选项的值 / Value matching the field type and choices.
|
|
81
|
-
* @returns 操作完成 / Completion of the operation.
|
|
82
|
-
* @throws {Error} 会话、值、并发写入或网络错误 / Session, value, concurrent-write or network error.
|
|
83
|
-
*/
|
|
84
|
-
set(module: string, key: string, value: SettingsScalar | SettingsScalar[]): Promise<void>;
|
|
85
|
-
/**
|
|
86
|
-
* DELETE 单个覆盖值,HTTP 200 后显示默认值,不追加 GET。
|
|
87
|
-
* DELETE an override and display its default after HTTP 200, without a follow-up GET.
|
|
88
|
-
* @param module 已打开的模块 / Open module.
|
|
89
|
-
* @param key 完整点分字段路径 / Complete dotted field path.
|
|
90
|
-
* @returns 操作完成 / Completion of the operation.
|
|
91
|
-
* @throws {Error} 会话、路径、并发写入或网络错误 / Session, path, concurrent-write or network error.
|
|
92
|
-
*/
|
|
93
|
-
remove(module: string, key: string): Promise<void>;
|
|
94
|
-
/**
|
|
95
|
-
* 按需读取整个模块 Caches,不刷新设置。
|
|
96
|
-
* Read all module Caches on demand without refreshing settings.
|
|
97
|
-
* @param module 已打开模块 / Open module.
|
|
98
|
-
* @returns 缓存 JSON 值,缺失时为 undefined / Cache JSON value, or undefined when absent.
|
|
99
|
-
*/
|
|
100
|
-
readCaches(module: string): Promise<unknown>;
|
|
101
|
-
/**
|
|
102
|
-
* 删除模块 Caches 并更新相关页面状态,不追加 GET。
|
|
103
|
-
* Delete module Caches and update related page state without a follow-up GET.
|
|
104
|
-
* @param module 已打开模块 / Open module.
|
|
105
|
-
* @returns 清理完成 / Cleanup completion.
|
|
106
|
-
*/
|
|
107
|
-
clearCaches(module: string): Promise<void>;
|
|
108
|
-
/**
|
|
109
|
-
* 删除整个模块持久化数据,页面使用当前 BoxJS 默认值。
|
|
110
|
-
* Delete all module persistence and use current BoxJS defaults on the page.
|
|
111
|
-
* @param module 已打开模块 / Open module.
|
|
112
|
-
* @returns 重置完成 / Reset completion.
|
|
113
|
-
*/
|
|
114
|
-
reset(module: string): Promise<void>;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* WebView 面板的挂载选项,模块标识从页面路径读取。
|
|
118
|
-
* Mount options for the WebView panel; the module is read from the page path.
|
|
119
|
-
*/
|
|
120
|
-
export interface PreferencesPanelOptions {
|
|
121
|
-
/** 具有浏览器 window 的挂载元素 / Mount element owned by a document with a browser window. */
|
|
122
|
-
element: HTMLElement;
|
|
123
|
-
/** 无有效模块时的标题,默认 Preferences / Title without a valid module; defaults to Preferences. */
|
|
124
|
-
title?: string;
|
|
125
|
-
/** 可选浏览器请求传输 / Optional browser request transport. */
|
|
126
|
-
fetch?: typeof globalThis.fetch;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* 面板生命周期控制句柄。
|
|
130
|
-
* Handle controlling the panel lifecycle.
|
|
131
|
-
*/
|
|
132
|
-
export interface PreferencesPanel {
|
|
133
|
-
/**
|
|
134
|
-
* 移除事件、面板与定时器,并取消当前读取。
|
|
135
|
-
* Remove listeners, panel and timers, and abort the current read.
|
|
136
|
-
* @returns 无返回值 / No return value.
|
|
137
|
-
*/
|
|
138
|
-
destroy(): void;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 创建模块会话客户端,不立即发出请求。
|
|
142
|
-
* Create a module session client without immediately making requests.
|
|
143
|
-
* @param options 请求、通知和超时选项 / Request, notification and timeout options.
|
|
144
|
-
* @returns 通用客户端 / Generic client.
|
|
145
|
-
*/
|
|
146
|
-
export function createPreferencesClient(options?: PreferencesClientOptions): PreferencesClient;
|
|
147
|
-
/**
|
|
148
|
-
* 按 /settings/{module} 挂载动态面板,监听导航和页面恢复事件。
|
|
149
|
-
* Mount a dynamic panel at /settings/{module} and observe navigation and page restoration.
|
|
150
|
-
* 控件变化即时串行写入;多选用二级页,返回不重新读取设置。
|
|
151
|
-
* Control changes save immediately in sequence; multi-select uses a secondary page without refetching on return.
|
|
152
|
-
* @param options 挂载选项 / Mount options.
|
|
153
|
-
* @returns 面板生命周期句柄 / Panel lifecycle handle.
|
|
16
|
+
* 仅以 BoxJS 和可选 CSS 挂载一个模块页,不生成项目主页。
|
|
17
|
+
* Mount one module page using BoxJS and optional CSS, without a project landing page.
|
|
18
|
+
* @param boxjs 恰好包含一个模块的 BoxJS JSON / BoxJS JSON describing exactly one module.
|
|
19
|
+
* @param css 可选 CSS 正文;默认样式始终内置 / Optional CSS text; default styles are built in.
|
|
20
|
+
* @returns 生命周期句柄 / Lifecycle handle.
|
|
154
21
|
*/
|
|
155
|
-
export function
|
|
22
|
+
export function mount(boxjs: BoxJSInput, css?: string): MountedPreferences;
|
package/src/browser/index.mjs
CHANGED
|
@@ -1,7 +1,62 @@
|
|
|
1
|
+
import defaults from "#styles";
|
|
2
|
+
import { BoxJS } from "../BoxJS.mjs";
|
|
3
|
+
import { element, resourceURL } from "./components.mjs";
|
|
4
|
+
import { mountPanel } from "./panel.mjs";
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @
|
|
7
|
+
* 只挂载导入 JSON 对应的模块设置页,默认样式内置,CSS 仅用于该页。
|
|
8
|
+
* Mount only the imported module's settings page with built-in defaults and optional page CSS.
|
|
9
|
+
* @param {import("../index.js").BoxJSInput} boxjs 单个模块的 BoxJS JSON / BoxJS JSON for one module.
|
|
10
|
+
* @param {string} [css] 可选 CSS 正文 / Optional CSS text.
|
|
11
|
+
* @returns {import("./index.js").MountedPreferences} 模块生命周期句柄 / Module lifecycle handle.
|
|
5
12
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
13
|
+
export function mount(boxjs, css = "") {
|
|
14
|
+
if (typeof css !== "string") throw new TypeError("CSS must be a string");
|
|
15
|
+
const catalog = new BoxJS(boxjs);
|
|
16
|
+
const metadata = catalog.module.metadata;
|
|
17
|
+
const image = metadata.icon || metadata.icons?.[1] || metadata.icons?.[0];
|
|
18
|
+
if (image) resourceURL(image);
|
|
19
|
+
if (metadata.repo) resourceURL(metadata.repo);
|
|
20
|
+
const existing = document.querySelector("#preferences");
|
|
21
|
+
const root = existing ?? element("main", "");
|
|
22
|
+
if (!existing) {
|
|
23
|
+
root.id = "preferences";
|
|
24
|
+
document.body.append(root);
|
|
25
|
+
}
|
|
26
|
+
const base = element("style", ""),
|
|
27
|
+
custom = element("style", "");
|
|
28
|
+
base.textContent = defaults;
|
|
29
|
+
custom.textContent = css;
|
|
30
|
+
document.head.append(base, custom);
|
|
31
|
+
const previousTitle = document.title;
|
|
32
|
+
const previousTheme = document.documentElement.dataset.theme;
|
|
33
|
+
const theme = navigator.userAgent.match(/themeId\/(\d+)/)?.[1];
|
|
34
|
+
if (theme) document.documentElement.dataset.theme = theme === "2" ? "dark" : "light";
|
|
35
|
+
document.title = metadata.name ?? catalog.module.module;
|
|
36
|
+
let panel;
|
|
37
|
+
const view = {
|
|
38
|
+
/**
|
|
39
|
+
* 释放模块视图、样式与会话,不操作项目入口页。
|
|
40
|
+
* Release the module view, styles and session without operating a project landing page.
|
|
41
|
+
* @returns {void} 无返回值 / No return value.
|
|
42
|
+
*/
|
|
43
|
+
destroy() {
|
|
44
|
+
panel?.destroy();
|
|
45
|
+
base.remove();
|
|
46
|
+
custom.remove();
|
|
47
|
+
if (existing) root.replaceChildren();
|
|
48
|
+
else root.remove();
|
|
49
|
+
document.title = previousTitle;
|
|
50
|
+
if (previousTheme === undefined) delete document.documentElement.dataset.theme;
|
|
51
|
+
else document.documentElement.dataset.theme = previousTheme;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
root.replaceChildren();
|
|
56
|
+
panel = mountPanel(root, catalog);
|
|
57
|
+
return view;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
view.destroy();
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
6
|
+
<meta name="color-scheme" content="light dark">
|
|
7
|
+
<title>Module Preferences</title>
|
|
8
|
+
<style>body { margin: 0; }</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main id="preferences"></main>
|
|
12
|
+
<script type="module" src="/settings/assets/app.mjs?v=__VERSION__"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|