@nsnanocat/preference-panes 1.1.2 → 1.1.4
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 +7 -4
- package/dist/api.js +6 -530
- package/dist/module/index.html +1 -1
- package/dist/module/index.mjs +49 -38
- package/dist/module/navigation.mjs +3 -3
- package/dist/preference-panes.mjs +49 -38
- package/dist/web.js +1 -1
- package/package.json +1 -1
- package/src/api.mjs +6 -38
- package/src/browser/ActionMenu.mjs +2 -2
- package/src/browser/ModuleStatus.mjs +1 -1
- package/src/browser/boxjs.mjs +1 -0
- package/src/browser/panel.css +0 -3
- package/src/browser/panel.mjs +45 -35
package/dist/module/index.mjs
CHANGED
|
@@ -35,6 +35,7 @@ function normalizeBoxJs(config, module) {
|
|
|
35
35
|
if (!storageKey || storageKey.startsWith("@") || parts.length < 2) throw new TypeError("A BoxJS setting must be below a literal storage root and module");
|
|
36
36
|
validatePathParts(parts);
|
|
37
37
|
const name = parts[0];
|
|
38
|
+
if (["get", "set", "delete"].includes(name)) throw new TypeError(`Reserved API module name: ${name}`);
|
|
38
39
|
let target = modules.get(name);
|
|
39
40
|
if (!target) {
|
|
40
41
|
target = { module: name, storageKey, entries: [], owners: new Set() };
|
|
@@ -275,8 +276,8 @@ function requestConfirmation(host, message) {
|
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
279
|
+
* 独立页面的三点按钮和底部操作菜单;嵌入页面由宿主提供对应界面。
|
|
280
|
+
* Overflow trigger and bottom action sheet for standalone pages; embedded pages use host-provided chrome.
|
|
280
281
|
*/
|
|
281
282
|
class ActionMenu {
|
|
282
283
|
#button;
|
|
@@ -816,13 +817,9 @@ class PreferencesPanel {
|
|
|
816
817
|
const title = definition.metadata?.name ?? definition.module;
|
|
817
818
|
const document = root.ownerDocument;
|
|
818
819
|
const window = document.defaultView;
|
|
820
|
+
const frame = window.frameElement?.dataset.preferencePanes ? window.frameElement : undefined;
|
|
819
821
|
const shell = element("div", "pp-panel");
|
|
820
822
|
shell.dataset.module = definition.module;
|
|
821
|
-
const header = element("header", "pp-header");
|
|
822
|
-
const back = element("button", "pp-back", "‹");
|
|
823
|
-
back.setAttribute("aria-label", "返回");
|
|
824
|
-
back.type = "button";
|
|
825
|
-
const heading = element("h1", "pp-title", title);
|
|
826
823
|
const handlers = new Map();
|
|
827
824
|
const menuItems = [
|
|
828
825
|
{ id: "viewSettings", label: "查看设置" },
|
|
@@ -830,37 +827,51 @@ class PreferencesPanel {
|
|
|
830
827
|
{ id: "clearCaches", label: "清空缓存", destructive: true },
|
|
831
828
|
{ id: "reset", label: "重置设置", destructive: true },
|
|
832
829
|
];
|
|
833
|
-
const menu = new ActionMenu(id => runAction(id));
|
|
834
|
-
const trailing = element("span", "pp-nav-spacer");
|
|
835
|
-
trailing.append(menu.element);
|
|
836
830
|
const viewport = element("div", "pp-viewport");
|
|
831
|
+
let back, heading, menu;
|
|
832
|
+
if (frame) shell.append(viewport);
|
|
833
|
+
else {
|
|
834
|
+
const header = element("header", "pp-header");
|
|
835
|
+
back = element("button", "pp-back", "‹");
|
|
836
|
+
back.setAttribute("aria-label", "返回");
|
|
837
|
+
back.type = "button";
|
|
838
|
+
heading = element("h1", "pp-title", title);
|
|
839
|
+
menu = new ActionMenu(id => runAction(id));
|
|
840
|
+
const trailing = element("span", "pp-nav-spacer");
|
|
841
|
+
trailing.append(menu.element);
|
|
842
|
+
header.append(back, heading, trailing);
|
|
843
|
+
shell.append(header, viewport);
|
|
844
|
+
}
|
|
837
845
|
let toast;
|
|
838
|
-
header.append(back, heading, trailing);
|
|
839
|
-
shell.append(header, viewport);
|
|
840
846
|
root.append(shell);
|
|
841
847
|
// 嵌入模式向宿主发布导航状态,宿主不读取或修改模块内部 DOM。
|
|
842
848
|
// Embedded mode publishes navigation state without host reads or mutations of the module DOM.
|
|
843
849
|
const publishNavigation = () => {
|
|
844
850
|
const actions = handlers.size ? menuItems : [];
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
851
|
+
if (frame)
|
|
852
|
+
frame.dispatchEvent(
|
|
853
|
+
new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
|
|
854
|
+
detail: { title: currentTitle, module: definition.module, busy: saving, canGoBack, actions },
|
|
855
|
+
}),
|
|
856
|
+
);
|
|
857
|
+
else {
|
|
858
|
+
heading.textContent = currentTitle;
|
|
859
|
+
back.disabled = !canGoBack;
|
|
860
|
+
menu.update(actions, saving);
|
|
861
|
+
}
|
|
853
862
|
};
|
|
854
863
|
const onAction = event => {
|
|
855
864
|
if (!saving && handlers.has(event.detail)) runAction(event.detail);
|
|
856
865
|
};
|
|
857
|
-
|
|
866
|
+
frame?.addEventListener("preferencepanes:action", onAction);
|
|
858
867
|
let timer,
|
|
859
868
|
navigation,
|
|
860
869
|
generation = 0,
|
|
861
870
|
active = null,
|
|
862
871
|
saving = false,
|
|
863
|
-
destroyed = false
|
|
872
|
+
destroyed = false,
|
|
873
|
+
currentTitle = title,
|
|
874
|
+
canGoBack = window.history.length > 1;
|
|
864
875
|
/**
|
|
865
876
|
* 展示短暂通知,不刷新设置数据。
|
|
866
877
|
* Display a transient notification without refreshing settings.
|
|
@@ -889,7 +900,6 @@ class PreferencesPanel {
|
|
|
889
900
|
}
|
|
890
901
|
// 宿主接管时不创建网页 Toast,也不运行其计时器。
|
|
891
902
|
// A host-owned notice creates no web Toast and starts no local timer.
|
|
892
|
-
const frame = window.frameElement;
|
|
893
903
|
if (frame && !frame.dispatchEvent(new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:notice", { cancelable: true, detail: { kind: event.kind, message } }))) return;
|
|
894
904
|
if (!toast) {
|
|
895
905
|
toast = element("div", "pp-toast");
|
|
@@ -927,8 +937,8 @@ class PreferencesPanel {
|
|
|
927
937
|
async function open(module) {
|
|
928
938
|
const version = ++generation;
|
|
929
939
|
active = module;
|
|
930
|
-
|
|
931
|
-
|
|
940
|
+
currentTitle = module;
|
|
941
|
+
canGoBack = window.history.length > 1;
|
|
932
942
|
publishNavigation();
|
|
933
943
|
viewport.replaceChildren(statusView("读取设置…"));
|
|
934
944
|
try {
|
|
@@ -947,7 +957,7 @@ class PreferencesPanel {
|
|
|
947
957
|
*/
|
|
948
958
|
function controls() {
|
|
949
959
|
const { definition, values } = client.snapshot();
|
|
950
|
-
|
|
960
|
+
currentTitle = definition.metadata?.name || active;
|
|
951
961
|
const view = element("section", "pp-fields");
|
|
952
962
|
/**
|
|
953
963
|
* 挂载后执行的多行高度更新
|
|
@@ -967,8 +977,8 @@ class PreferencesPanel {
|
|
|
967
977
|
*/
|
|
968
978
|
const updateNavigation = () => {
|
|
969
979
|
const editor = editors.get(navigation.current);
|
|
970
|
-
|
|
971
|
-
|
|
980
|
+
currentTitle = editor?.title ?? definition.metadata?.name ?? active;
|
|
981
|
+
canGoBack = !saving && navigation.canGoBack;
|
|
972
982
|
publishNavigation();
|
|
973
983
|
};
|
|
974
984
|
/**
|
|
@@ -982,7 +992,7 @@ class PreferencesPanel {
|
|
|
982
992
|
function perform(action, success, failure = () => {}) {
|
|
983
993
|
pendingWrites++;
|
|
984
994
|
saving = true;
|
|
985
|
-
|
|
995
|
+
canGoBack = false;
|
|
986
996
|
publishNavigation();
|
|
987
997
|
queue = queue
|
|
988
998
|
.then(action)
|
|
@@ -998,7 +1008,7 @@ class PreferencesPanel {
|
|
|
998
1008
|
pendingWrites--;
|
|
999
1009
|
saving = pendingWrites > 0;
|
|
1000
1010
|
if (destroyed && !saving) client.leave();
|
|
1001
|
-
|
|
1011
|
+
canGoBack = !saving && navigation.canGoBack;
|
|
1002
1012
|
publishNavigation();
|
|
1003
1013
|
});
|
|
1004
1014
|
return queue;
|
|
@@ -1272,16 +1282,17 @@ class PreferencesPanel {
|
|
|
1272
1282
|
* Loaded forms delegate back to navigation; loading views can return to the previous document.
|
|
1273
1283
|
* @returns {void} 无返回值 / No return value.
|
|
1274
1284
|
*/
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1285
|
+
if (back)
|
|
1286
|
+
back.onclick = () => {
|
|
1287
|
+
if (saving) return;
|
|
1288
|
+
if (navigation) navigation.back();
|
|
1289
|
+
else window.history.back();
|
|
1290
|
+
};
|
|
1280
1291
|
open(definition.module);
|
|
1281
1292
|
return () => {
|
|
1282
1293
|
destroyed = true;
|
|
1283
|
-
menu
|
|
1284
|
-
|
|
1294
|
+
menu?.destroy();
|
|
1295
|
+
frame?.removeEventListener("preferencepanes:action", onAction);
|
|
1285
1296
|
navigation?.destroy();
|
|
1286
1297
|
generation++;
|
|
1287
1298
|
if (active && !saving) client.leave();
|
|
@@ -1300,7 +1311,7 @@ class PreferencesPanel {
|
|
|
1300
1311
|
}
|
|
1301
1312
|
}
|
|
1302
1313
|
|
|
1303
|
-
var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n
|
|
1314
|
+
var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n@supports (height: 100dvh) {\n .pp-panel {\n height: 100dvh;\n }\n}\n.pp-fields,\n.pp-choice-page,\n.pp-settings-page,\n.pp-cache-page {\n position: absolute;\n inset: 0;\n min-width: 0;\n overflow-x: hidden;\n overflow-y: auto;\n padding: 12px max(16px, calc((100% - 688px) / 2)) calc(28px + env(safe-area-inset-bottom) + var(--pp-keyboard-height, 0px));\n scroll-padding-bottom: var(--pp-keyboard-height, 0px);\n background: var(--pp-background);\n}\n.pp-choice-link {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n max-width: 45%;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n text-align: right;\n flex: 1;\n}\n.pp-summary {\n color: var(--pp-muted);\n font-size: 13px;\n line-height: 18px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.pp-chevron {\n color: var(--pp-muted);\n font-size: 22px;\n flex: none;\n}\n.pp-editor {\n flex: none;\n width: 45%;\n min-width: 0;\n min-height: 36px;\n padding: 8px 10px;\n font: inherit;\n color: var(--pp-text);\n background: var(--pp-field);\n border: 0;\n border-radius: 6px;\n}\n.pp-panel .pp-multiline {\n display: block;\n}\n.pp-multiline .pp-editor {\n width: 100%;\n margin-top: 10px;\n}\n.pp-panel [hidden] {\n display: none !important;\n}\n.pp-label {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n margin-right: 16px;\n}\n.pp-field-name {\n color: var(--pp-text);\n font-size: 15px;\n}\n.pp-field-description {\n margin-top: 2px;\n color: var(--pp-muted);\n font-size: 12px;\n}\n.pp-group {\n margin-top: 16px;\n}\n.pp-group-title {\n margin: 0 0 8px;\n color: var(--pp-muted);\n font-size: 15px;\n font-weight: 400;\n}\n.pp-row {\n min-width: 0;\n min-height: 48px;\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n}\n.pp-rows > :last-child {\n border-bottom: 0 !important;\n}\n.pp-switch {\n flex: none;\n accent-color: var(--pp-accent);\n}\n.pp-choice {\n justify-content: space-between;\n cursor: pointer;\n}\n.pp-choice input {\n width: 20px;\n height: 20px;\n flex: none;\n accent-color: var(--pp-accent);\n margin: 0;\n}\n.pp-description {\n font-size: 12px;\n line-height: 1.6;\n color: var(--pp-muted);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-module-info {\n display: flex;\n gap: 12px;\n margin: 12px 0;\n}\n.pp-module-details {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-module-source {\n color: inherit;\n text-decoration: underline;\n}\n.pp-status {\n position: fixed;\n inset: 0;\n display: grid;\n place-content: center;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n margin: 0;\n padding: 24px;\n color: var(--pp-muted, GrayText);\n text-align: center;\n background: var(--pp-background, Canvas);\n}\n.pp-viewport > .pp-status {\n position: absolute;\n}\n.pp-status-spinner {\n box-sizing: border-box;\n width: 28px;\n height: 28px;\n border: 3px solid color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: var(--pp-accent, AccentColor);\n border-radius: 50%;\n animation: pp-status-spin 0.8s linear infinite;\n}\n.pp-status-message {\n max-width: 100%;\n margin: 0;\n overflow-wrap: anywhere;\n}\n.pp-status-action {\n min-width: 96px;\n min-height: 44px;\n padding: 8px 16px;\n border: 0;\n border-radius: 6px;\n color: var(--pp-text, ButtonText);\n font: inherit;\n cursor: pointer;\n background: var(--pp-surface, ButtonFace);\n}\n@keyframes pp-status-spin {\n to {\n transform: rotate(1turn);\n }\n}\n.pp-cache {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-toast {\n pointer-events: none;\n position: fixed;\n bottom: calc(30px + env(safe-area-inset-bottom));\n left: 50%;\n transform: translateX(-50%);\n max-width: 90vw;\n padding: 10px 16px;\n border-radius: 8px;\n background: #333e;\n color: white;\n font-size: 13px;\n z-index: 20;\n}\n.pp-toast[data-kind=\"error\"] {\n background: #8d2424;\n}\n.pp-panel :focus-visible {\n outline: 2px solid var(--pp-accent);\n outline-offset: -2px;\n}\n";
|
|
1304
1315
|
|
|
1305
1316
|
const selector = "style[data-preference-panes-defaults]";
|
|
1306
1317
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 独立页面的三点按钮和底部操作菜单;嵌入页面由宿主提供对应界面。
|
|
3
|
+
* Overflow trigger and bottom action sheet for standalone pages; embedded pages use host-provided chrome.
|
|
4
4
|
*/
|
|
5
5
|
class ActionMenu {
|
|
6
6
|
#button;
|
|
@@ -331,7 +331,7 @@ class ModuleStatus extends EventTarget {
|
|
|
331
331
|
const response = await probeModule(url, { ...options, signal: controller.signal });
|
|
332
332
|
if (controller !== this.#controller) return response;
|
|
333
333
|
const version = response.status === 200 ? response.headers.get("X-PreferencePanes-Version")?.trim() || null : null;
|
|
334
|
-
this.#render(
|
|
334
|
+
this.#render(version ? "installed" : "missing", version);
|
|
335
335
|
return response;
|
|
336
336
|
} catch (error) {
|
|
337
337
|
if (controller !== this.#controller) return;
|
|
@@ -35,6 +35,7 @@ function normalizeBoxJs(config, module) {
|
|
|
35
35
|
if (!storageKey || storageKey.startsWith("@") || parts.length < 2) throw new TypeError("A BoxJS setting must be below a literal storage root and module");
|
|
36
36
|
validatePathParts(parts);
|
|
37
37
|
const name = parts[0];
|
|
38
|
+
if (["get", "set", "delete"].includes(name)) throw new TypeError(`Reserved API module name: ${name}`);
|
|
38
39
|
let target = modules.get(name);
|
|
39
40
|
if (!target) {
|
|
40
41
|
target = { module: name, storageKey, entries: [], owners: new Set() };
|
|
@@ -275,8 +276,8 @@ function requestConfirmation(host, message) {
|
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
279
|
+
* 独立页面的三点按钮和底部操作菜单;嵌入页面由宿主提供对应界面。
|
|
280
|
+
* Overflow trigger and bottom action sheet for standalone pages; embedded pages use host-provided chrome.
|
|
280
281
|
*/
|
|
281
282
|
class ActionMenu {
|
|
282
283
|
#button;
|
|
@@ -816,13 +817,9 @@ class PreferencesPanel {
|
|
|
816
817
|
const title = definition.metadata?.name ?? definition.module;
|
|
817
818
|
const document = root.ownerDocument;
|
|
818
819
|
const window = document.defaultView;
|
|
820
|
+
const frame = window.frameElement?.dataset.preferencePanes ? window.frameElement : undefined;
|
|
819
821
|
const shell = element("div", "pp-panel");
|
|
820
822
|
shell.dataset.module = definition.module;
|
|
821
|
-
const header = element("header", "pp-header");
|
|
822
|
-
const back = element("button", "pp-back", "‹");
|
|
823
|
-
back.setAttribute("aria-label", "返回");
|
|
824
|
-
back.type = "button";
|
|
825
|
-
const heading = element("h1", "pp-title", title);
|
|
826
823
|
const handlers = new Map();
|
|
827
824
|
const menuItems = [
|
|
828
825
|
{ id: "viewSettings", label: "查看设置" },
|
|
@@ -830,37 +827,51 @@ class PreferencesPanel {
|
|
|
830
827
|
{ id: "clearCaches", label: "清空缓存", destructive: true },
|
|
831
828
|
{ id: "reset", label: "重置设置", destructive: true },
|
|
832
829
|
];
|
|
833
|
-
const menu = new ActionMenu(id => runAction(id));
|
|
834
|
-
const trailing = element("span", "pp-nav-spacer");
|
|
835
|
-
trailing.append(menu.element);
|
|
836
830
|
const viewport = element("div", "pp-viewport");
|
|
831
|
+
let back, heading, menu;
|
|
832
|
+
if (frame) shell.append(viewport);
|
|
833
|
+
else {
|
|
834
|
+
const header = element("header", "pp-header");
|
|
835
|
+
back = element("button", "pp-back", "‹");
|
|
836
|
+
back.setAttribute("aria-label", "返回");
|
|
837
|
+
back.type = "button";
|
|
838
|
+
heading = element("h1", "pp-title", title);
|
|
839
|
+
menu = new ActionMenu(id => runAction(id));
|
|
840
|
+
const trailing = element("span", "pp-nav-spacer");
|
|
841
|
+
trailing.append(menu.element);
|
|
842
|
+
header.append(back, heading, trailing);
|
|
843
|
+
shell.append(header, viewport);
|
|
844
|
+
}
|
|
837
845
|
let toast;
|
|
838
|
-
header.append(back, heading, trailing);
|
|
839
|
-
shell.append(header, viewport);
|
|
840
846
|
root.append(shell);
|
|
841
847
|
// 嵌入模式向宿主发布导航状态,宿主不读取或修改模块内部 DOM。
|
|
842
848
|
// Embedded mode publishes navigation state without host reads or mutations of the module DOM.
|
|
843
849
|
const publishNavigation = () => {
|
|
844
850
|
const actions = handlers.size ? menuItems : [];
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
851
|
+
if (frame)
|
|
852
|
+
frame.dispatchEvent(
|
|
853
|
+
new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
|
|
854
|
+
detail: { title: currentTitle, module: definition.module, busy: saving, canGoBack, actions },
|
|
855
|
+
}),
|
|
856
|
+
);
|
|
857
|
+
else {
|
|
858
|
+
heading.textContent = currentTitle;
|
|
859
|
+
back.disabled = !canGoBack;
|
|
860
|
+
menu.update(actions, saving);
|
|
861
|
+
}
|
|
853
862
|
};
|
|
854
863
|
const onAction = event => {
|
|
855
864
|
if (!saving && handlers.has(event.detail)) runAction(event.detail);
|
|
856
865
|
};
|
|
857
|
-
|
|
866
|
+
frame?.addEventListener("preferencepanes:action", onAction);
|
|
858
867
|
let timer,
|
|
859
868
|
navigation,
|
|
860
869
|
generation = 0,
|
|
861
870
|
active = null,
|
|
862
871
|
saving = false,
|
|
863
|
-
destroyed = false
|
|
872
|
+
destroyed = false,
|
|
873
|
+
currentTitle = title,
|
|
874
|
+
canGoBack = window.history.length > 1;
|
|
864
875
|
/**
|
|
865
876
|
* 展示短暂通知,不刷新设置数据。
|
|
866
877
|
* Display a transient notification without refreshing settings.
|
|
@@ -889,7 +900,6 @@ class PreferencesPanel {
|
|
|
889
900
|
}
|
|
890
901
|
// 宿主接管时不创建网页 Toast,也不运行其计时器。
|
|
891
902
|
// A host-owned notice creates no web Toast and starts no local timer.
|
|
892
|
-
const frame = window.frameElement;
|
|
893
903
|
if (frame && !frame.dispatchEvent(new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:notice", { cancelable: true, detail: { kind: event.kind, message } }))) return;
|
|
894
904
|
if (!toast) {
|
|
895
905
|
toast = element("div", "pp-toast");
|
|
@@ -927,8 +937,8 @@ class PreferencesPanel {
|
|
|
927
937
|
async function open(module) {
|
|
928
938
|
const version = ++generation;
|
|
929
939
|
active = module;
|
|
930
|
-
|
|
931
|
-
|
|
940
|
+
currentTitle = module;
|
|
941
|
+
canGoBack = window.history.length > 1;
|
|
932
942
|
publishNavigation();
|
|
933
943
|
viewport.replaceChildren(statusView("读取设置…"));
|
|
934
944
|
try {
|
|
@@ -947,7 +957,7 @@ class PreferencesPanel {
|
|
|
947
957
|
*/
|
|
948
958
|
function controls() {
|
|
949
959
|
const { definition, values } = client.snapshot();
|
|
950
|
-
|
|
960
|
+
currentTitle = definition.metadata?.name || active;
|
|
951
961
|
const view = element("section", "pp-fields");
|
|
952
962
|
/**
|
|
953
963
|
* 挂载后执行的多行高度更新
|
|
@@ -967,8 +977,8 @@ class PreferencesPanel {
|
|
|
967
977
|
*/
|
|
968
978
|
const updateNavigation = () => {
|
|
969
979
|
const editor = editors.get(navigation.current);
|
|
970
|
-
|
|
971
|
-
|
|
980
|
+
currentTitle = editor?.title ?? definition.metadata?.name ?? active;
|
|
981
|
+
canGoBack = !saving && navigation.canGoBack;
|
|
972
982
|
publishNavigation();
|
|
973
983
|
};
|
|
974
984
|
/**
|
|
@@ -982,7 +992,7 @@ class PreferencesPanel {
|
|
|
982
992
|
function perform(action, success, failure = () => {}) {
|
|
983
993
|
pendingWrites++;
|
|
984
994
|
saving = true;
|
|
985
|
-
|
|
995
|
+
canGoBack = false;
|
|
986
996
|
publishNavigation();
|
|
987
997
|
queue = queue
|
|
988
998
|
.then(action)
|
|
@@ -998,7 +1008,7 @@ class PreferencesPanel {
|
|
|
998
1008
|
pendingWrites--;
|
|
999
1009
|
saving = pendingWrites > 0;
|
|
1000
1010
|
if (destroyed && !saving) client.leave();
|
|
1001
|
-
|
|
1011
|
+
canGoBack = !saving && navigation.canGoBack;
|
|
1002
1012
|
publishNavigation();
|
|
1003
1013
|
});
|
|
1004
1014
|
return queue;
|
|
@@ -1272,16 +1282,17 @@ class PreferencesPanel {
|
|
|
1272
1282
|
* Loaded forms delegate back to navigation; loading views can return to the previous document.
|
|
1273
1283
|
* @returns {void} 无返回值 / No return value.
|
|
1274
1284
|
*/
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1285
|
+
if (back)
|
|
1286
|
+
back.onclick = () => {
|
|
1287
|
+
if (saving) return;
|
|
1288
|
+
if (navigation) navigation.back();
|
|
1289
|
+
else window.history.back();
|
|
1290
|
+
};
|
|
1280
1291
|
open(definition.module);
|
|
1281
1292
|
return () => {
|
|
1282
1293
|
destroyed = true;
|
|
1283
|
-
menu
|
|
1284
|
-
|
|
1294
|
+
menu?.destroy();
|
|
1295
|
+
frame?.removeEventListener("preferencepanes:action", onAction);
|
|
1285
1296
|
navigation?.destroy();
|
|
1286
1297
|
generation++;
|
|
1287
1298
|
if (active && !saving) client.leave();
|
|
@@ -1300,7 +1311,7 @@ class PreferencesPanel {
|
|
|
1300
1311
|
}
|
|
1301
1312
|
}
|
|
1302
1313
|
|
|
1303
|
-
var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n
|
|
1314
|
+
var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n@supports (height: 100dvh) {\n .pp-panel {\n height: 100dvh;\n }\n}\n.pp-fields,\n.pp-choice-page,\n.pp-settings-page,\n.pp-cache-page {\n position: absolute;\n inset: 0;\n min-width: 0;\n overflow-x: hidden;\n overflow-y: auto;\n padding: 12px max(16px, calc((100% - 688px) / 2)) calc(28px + env(safe-area-inset-bottom) + var(--pp-keyboard-height, 0px));\n scroll-padding-bottom: var(--pp-keyboard-height, 0px);\n background: var(--pp-background);\n}\n.pp-choice-link {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n max-width: 45%;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n text-align: right;\n flex: 1;\n}\n.pp-summary {\n color: var(--pp-muted);\n font-size: 13px;\n line-height: 18px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.pp-chevron {\n color: var(--pp-muted);\n font-size: 22px;\n flex: none;\n}\n.pp-editor {\n flex: none;\n width: 45%;\n min-width: 0;\n min-height: 36px;\n padding: 8px 10px;\n font: inherit;\n color: var(--pp-text);\n background: var(--pp-field);\n border: 0;\n border-radius: 6px;\n}\n.pp-panel .pp-multiline {\n display: block;\n}\n.pp-multiline .pp-editor {\n width: 100%;\n margin-top: 10px;\n}\n.pp-panel [hidden] {\n display: none !important;\n}\n.pp-label {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n margin-right: 16px;\n}\n.pp-field-name {\n color: var(--pp-text);\n font-size: 15px;\n}\n.pp-field-description {\n margin-top: 2px;\n color: var(--pp-muted);\n font-size: 12px;\n}\n.pp-group {\n margin-top: 16px;\n}\n.pp-group-title {\n margin: 0 0 8px;\n color: var(--pp-muted);\n font-size: 15px;\n font-weight: 400;\n}\n.pp-row {\n min-width: 0;\n min-height: 48px;\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n}\n.pp-rows > :last-child {\n border-bottom: 0 !important;\n}\n.pp-switch {\n flex: none;\n accent-color: var(--pp-accent);\n}\n.pp-choice {\n justify-content: space-between;\n cursor: pointer;\n}\n.pp-choice input {\n width: 20px;\n height: 20px;\n flex: none;\n accent-color: var(--pp-accent);\n margin: 0;\n}\n.pp-description {\n font-size: 12px;\n line-height: 1.6;\n color: var(--pp-muted);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-module-info {\n display: flex;\n gap: 12px;\n margin: 12px 0;\n}\n.pp-module-details {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-module-source {\n color: inherit;\n text-decoration: underline;\n}\n.pp-status {\n position: fixed;\n inset: 0;\n display: grid;\n place-content: center;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n margin: 0;\n padding: 24px;\n color: var(--pp-muted, GrayText);\n text-align: center;\n background: var(--pp-background, Canvas);\n}\n.pp-viewport > .pp-status {\n position: absolute;\n}\n.pp-status-spinner {\n box-sizing: border-box;\n width: 28px;\n height: 28px;\n border: 3px solid color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: var(--pp-accent, AccentColor);\n border-radius: 50%;\n animation: pp-status-spin 0.8s linear infinite;\n}\n.pp-status-message {\n max-width: 100%;\n margin: 0;\n overflow-wrap: anywhere;\n}\n.pp-status-action {\n min-width: 96px;\n min-height: 44px;\n padding: 8px 16px;\n border: 0;\n border-radius: 6px;\n color: var(--pp-text, ButtonText);\n font: inherit;\n cursor: pointer;\n background: var(--pp-surface, ButtonFace);\n}\n@keyframes pp-status-spin {\n to {\n transform: rotate(1turn);\n }\n}\n.pp-cache {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-toast {\n pointer-events: none;\n position: fixed;\n bottom: calc(30px + env(safe-area-inset-bottom));\n left: 50%;\n transform: translateX(-50%);\n max-width: 90vw;\n padding: 10px 16px;\n border-radius: 8px;\n background: #333e;\n color: white;\n font-size: 13px;\n z-index: 20;\n}\n.pp-toast[data-kind=\"error\"] {\n background: #8d2424;\n}\n.pp-panel :focus-visible {\n outline: 2px solid var(--pp-accent);\n outline-offset: -2px;\n}\n";
|
|
1304
1315
|
|
|
1305
1316
|
const selector = "style[data-preference-panes-defaults]";
|
|
1306
1317
|
|