@nsnanocat/preference-panes 0.8.1 → 0.9.1
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 +10 -0
- package/dist/api.js +1 -1
- package/dist/module/app.mjs +144 -42
- package/dist/module/index.html +1 -1
- package/dist/module/navigation.mjs +6 -0
- package/dist/preference-panes.mjs +144 -42
- package/package.json +1 -1
- package/src/browser/ModuleFrame.mjs +6 -0
- package/src/browser/Navigation.d.mts +28 -0
- package/src/browser/components.mjs +38 -0
- package/src/browser/index.mjs +28 -4
- package/src/browser/official-styles.json +6 -0
- package/src/browser/panel.css +21 -128
- package/src/browser/panel.mjs +78 -38
package/dist/module/app.mjs
CHANGED
|
@@ -125,10 +125,30 @@ function pageInputs(url, headers = {}) {
|
|
|
125
125
|
function element(tag, className, text) {
|
|
126
126
|
const node = document.createElement(tag);
|
|
127
127
|
node.className = className;
|
|
128
|
+
// 官方 AppSettings 1.1.2 的作用域标记与原版 CSS 一起固定版本。
|
|
129
|
+
// Pin official AppSettings 1.1.2 scope attributes together with its unmodified CSS.
|
|
130
|
+
if (/\bform-row(?:\b|__)/.test(className)) node.setAttribute("data-v-b69aa1ea", "");
|
|
131
|
+
if (/\bform-group(?:\b|__)/.test(className)) node.setAttribute("data-v-e590be47", "");
|
|
128
132
|
if (text !== undefined) node.textContent = text;
|
|
129
133
|
return node;
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
/**
|
|
137
|
+
* 搜索、选择和文本控件共用官方 VField 的 DOM 结构。
|
|
138
|
+
* Share the official VField DOM structure across search, select and text controls.
|
|
139
|
+
* @param {HTMLElement} control 已创建的原生控件 / Existing native control.
|
|
140
|
+
* @param {boolean} [multiline] 是否为多行输入 / Whether the control is multiline.
|
|
141
|
+
* @returns {HTMLDivElement} 字段容器 / Field container.
|
|
142
|
+
*/
|
|
143
|
+
function fieldControl(control, multiline = false) {
|
|
144
|
+
const field = element("div", `v-field pp-editor${multiline ? " v-field--textarea" : ""}`);
|
|
145
|
+
const body = element("div", "v-field__body");
|
|
146
|
+
control.classList.add("v-field__control");
|
|
147
|
+
body.append(control);
|
|
148
|
+
field.append(body);
|
|
149
|
+
return field;
|
|
150
|
+
}
|
|
151
|
+
|
|
132
152
|
/**
|
|
133
153
|
* 元数据地址只允许 HTTP(S) 和相对地址。
|
|
134
154
|
* Allow only HTTP(S) and relative metadata addresses.
|
|
@@ -173,7 +193,25 @@ function errorView(error, retry) {
|
|
|
173
193
|
return view;
|
|
174
194
|
}
|
|
175
195
|
|
|
176
|
-
|
|
196
|
+
/**
|
|
197
|
+
* 请求宿主确认;独立网页使用浏览器对话框。
|
|
198
|
+
* Request confirmation from the host, using the browser dialog for standalone pages.
|
|
199
|
+
* @param {Window} host 模块窗口 / Module window.
|
|
200
|
+
* @param {string} message 确认内容 / Confirmation message.
|
|
201
|
+
* @returns {Promise<boolean>} 用户是否确认 / Whether the user confirmed.
|
|
202
|
+
*/
|
|
203
|
+
function requestConfirmation(host, message) {
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const frame = host.frameElement;
|
|
206
|
+
if (frame) {
|
|
207
|
+
const event = new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:confirm", { cancelable: true, detail: { message, resolve, reject } });
|
|
208
|
+
if (!frame.dispatchEvent(event)) return;
|
|
209
|
+
}
|
|
210
|
+
resolve(host.confirm(message));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
var defaults = "@import url(\"https://hilo.bilibili.com/h5_common/theme.min.css\") layer(preference-panes);\n@import url(\"https://hilo.bilibili.com/h5_common/b-style.min.css\") layer(preference-panes);\n@import url(\"https://hilo.bilibili.com/app_settings/assets/messageSettingsLayout-ltzQ1gMi.css\") layer(preference-panes);\n@import url(\"https://hilo.bilibili.com/app_settings/assets/message-settings-BD3N1lqQ.css\") layer(preference-panes);\n@layer preference-panes {\n/* 分组列表沿用 Bilibili 设置页的行结构,样式限定在面板内。\n * Grouped rows follow the Bilibili settings layout, scoped to the panel. */\n.pp-panel {\n --pp-text: var(--text1);\n --pp-background: var(--bg2);\n --pp-surface: var(--bg1);\n --pp-border: var(--line_regular);\n --pp-muted: var(--text3);\n --pp-accent: var(--brand_pink);\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 min-height: 100vh;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\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: sticky;\n top: 0;\n z-index: 1;\n}\n.pp-title {\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-brand {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n text-align: center;\n}\n.pp-brand-icon {\n display: none;\n flex: none;\n width: 28px;\n height: 28px;\n}\n.pp-brand-icon:not(:empty) {\n display: block;\n}\n.pp-brand-icon img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button:not(.v-toggle) {\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 height: calc(100vh - 52px - env(safe-area-inset-top));\n overflow: hidden;\n}\n:root[data-preference-panes-embedded] .pp-header {\n display: none;\n}\n:root[data-preference-panes-embedded] .pp-viewport {\n height: 100vh;\n}\n@supports (height: 100dvh) {\n .pp-viewport {\n height: calc(100dvh - 52px - env(safe-area-inset-top));\n }\n :root[data-preference-panes-embedded] .pp-viewport {\n height: 100dvh;\n }\n}\n.pp-fields,\n.pp-choice-page,\n.pp-cache-page {\n position: absolute;\n inset: 0;\n overflow: 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}\n.pp-search {\n width: 100%;\n margin-bottom: 12px;\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-panel .v-toggle {\n border: 0;\n padding: 0;\n flex: none;\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-icon {\n width: 48px;\n height: 48px;\n object-fit: contain;\n flex: none;\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-error button {\n min-height: 44px;\n padding: 8px 12px;\n border-radius: 6px;\n background: var(--pp-surface);\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\n}";
|
|
177
215
|
|
|
178
216
|
/**
|
|
179
217
|
* 标题栏共用三点菜单;Shadow DOM 隔离项目样式,保留继承的主题色。
|
|
@@ -795,7 +833,7 @@ function mountPanel(root, catalog) {
|
|
|
795
833
|
{ id: "clearCaches", label: "清空缓存", destructive: true },
|
|
796
834
|
{ id: "reset", label: "重置模块", destructive: true },
|
|
797
835
|
];
|
|
798
|
-
const menu = new ActionMenu(id =>
|
|
836
|
+
const menu = new ActionMenu(id => runAction(id));
|
|
799
837
|
const trailing = element("span", "pp-nav-spacer");
|
|
800
838
|
trailing.append(menu.element);
|
|
801
839
|
const brand = element("div", "pp-brand");
|
|
@@ -825,7 +863,7 @@ function mountPanel(root, catalog) {
|
|
|
825
863
|
);
|
|
826
864
|
};
|
|
827
865
|
const onAction = event => {
|
|
828
|
-
if (!saving && handlers.has(event.detail))
|
|
866
|
+
if (!saving && handlers.has(event.detail)) runAction(event.detail);
|
|
829
867
|
};
|
|
830
868
|
window.frameElement?.addEventListener("preferencepanes:action", onAction);
|
|
831
869
|
let timer,
|
|
@@ -867,6 +905,19 @@ function mountPanel(root, catalog) {
|
|
|
867
905
|
}, 2400);
|
|
868
906
|
};
|
|
869
907
|
const client = createPreferencesClient({ catalog, notify });
|
|
908
|
+
/**
|
|
909
|
+
* 两种菜单入口共用异步错误处理,包含宿主确认框错误。
|
|
910
|
+
* Share async error handling between both menus, including host-dialog errors.
|
|
911
|
+
* @param {string} id 操作标识 / Action identifier.
|
|
912
|
+
* @returns {Promise<void>} 操作已处理 / Action handled.
|
|
913
|
+
*/
|
|
914
|
+
async function runAction(id) {
|
|
915
|
+
try {
|
|
916
|
+
await handlers.get(id)();
|
|
917
|
+
} catch (error) {
|
|
918
|
+
notify({ kind: "error", message: error.message });
|
|
919
|
+
}
|
|
920
|
+
}
|
|
870
921
|
/**
|
|
871
922
|
* 打开模块并忽略已过期的异步结果。
|
|
872
923
|
* Open a module and ignore stale asynchronous results.
|
|
@@ -898,6 +949,14 @@ function mountPanel(root, catalog) {
|
|
|
898
949
|
const { definition, values } = client.snapshot(active);
|
|
899
950
|
heading.textContent = definition.metadata?.name || active;
|
|
900
951
|
const view = element("section", "pp-fields");
|
|
952
|
+
const search = element("input", "");
|
|
953
|
+
search.type = "search";
|
|
954
|
+
search.placeholder = "搜索设置项";
|
|
955
|
+
search.setAttribute("aria-label", "搜索设置");
|
|
956
|
+
const searchField = fieldControl(search);
|
|
957
|
+
searchField.classList.add("pp-search");
|
|
958
|
+
view.append(searchField);
|
|
959
|
+
const searchRows = [];
|
|
901
960
|
/**
|
|
902
961
|
* 挂载后执行的多行高度更新
|
|
903
962
|
* Textarea sizing callbacks run after mounting.
|
|
@@ -972,7 +1031,7 @@ function mountPanel(root, catalog) {
|
|
|
972
1031
|
const match = /^\[([^\]]+)\]\s*(.*)$/.exec(field.name);
|
|
973
1032
|
const group = match?.[1] ?? "通用";
|
|
974
1033
|
if (!groups.has(group)) {
|
|
975
|
-
const section = element("section", "form-group");
|
|
1034
|
+
const section = element("section", "form-group form-group--has-title");
|
|
976
1035
|
const rows = element("div", "form-group__row");
|
|
977
1036
|
section.append(element("h2", "form-group__title", group), rows);
|
|
978
1037
|
groups.set(group, rows);
|
|
@@ -1000,7 +1059,7 @@ function mountPanel(root, catalog) {
|
|
|
1000
1059
|
let eventName = "change";
|
|
1001
1060
|
switch (true) {
|
|
1002
1061
|
case Boolean(field.options) && field.type !== "array": {
|
|
1003
|
-
const select = element("select", "
|
|
1062
|
+
const select = element("select", "");
|
|
1004
1063
|
select.setAttribute("aria-label", field.name);
|
|
1005
1064
|
field.options.forEach((option, index) => {
|
|
1006
1065
|
const item = element("option", "", option.label);
|
|
@@ -1010,7 +1069,7 @@ function mountPanel(root, catalog) {
|
|
|
1010
1069
|
write = value => {
|
|
1011
1070
|
select.selectedIndex = field.options.findIndex(option => option.key === value);
|
|
1012
1071
|
};
|
|
1013
|
-
row.append(select);
|
|
1072
|
+
row.append(fieldControl(select));
|
|
1014
1073
|
read = () => field.options[select.selectedIndex]?.key;
|
|
1015
1074
|
break;
|
|
1016
1075
|
}
|
|
@@ -1056,9 +1115,27 @@ function mountPanel(root, catalog) {
|
|
|
1056
1115
|
};
|
|
1057
1116
|
break;
|
|
1058
1117
|
}
|
|
1118
|
+
case field.type === "boolean": {
|
|
1119
|
+
const toggle = element("button", "v-toggle v-toggle--small form-row__toggle");
|
|
1120
|
+
toggle.type = "button";
|
|
1121
|
+
toggle.setAttribute("role", "switch");
|
|
1122
|
+
toggle.setAttribute("aria-label", field.name);
|
|
1123
|
+
toggle.append(element("span", "v-toggle__circle"));
|
|
1124
|
+
write = value => {
|
|
1125
|
+
toggle.setAttribute("aria-checked", String(value === true));
|
|
1126
|
+
toggle.classList.toggle("v-toggle--closed", value !== true);
|
|
1127
|
+
};
|
|
1128
|
+
read = () => toggle.getAttribute("aria-checked") === "true";
|
|
1129
|
+
toggle.onclick = () => {
|
|
1130
|
+
write(!read());
|
|
1131
|
+
toggle.dispatchEvent(new window.Event("change", { bubbles: true }));
|
|
1132
|
+
};
|
|
1133
|
+
row.append(toggle);
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1059
1136
|
default: {
|
|
1060
1137
|
const multiline = field.control === "textarea" || field.type === "array";
|
|
1061
|
-
const input = element(multiline ? "textarea" : "input", "
|
|
1138
|
+
const input = element(multiline ? "textarea" : "input", "");
|
|
1062
1139
|
if (multiline) row.classList.add("pp-multiline");
|
|
1063
1140
|
input.setAttribute("aria-label", field.name);
|
|
1064
1141
|
if (field.placeholder) input.placeholder = field.placeholder;
|
|
@@ -1080,33 +1157,23 @@ function mountPanel(root, catalog) {
|
|
|
1080
1157
|
input.addEventListener("input", grow);
|
|
1081
1158
|
growingInputs.push(grow);
|
|
1082
1159
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
input.
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
case "array":
|
|
1101
|
-
return JSON.parse(input.value);
|
|
1102
|
-
case "number":
|
|
1103
|
-
return input.value === "" ? Number.NaN : Number(input.value);
|
|
1104
|
-
default:
|
|
1105
|
-
return input.value;
|
|
1106
|
-
}
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
row.append(input);
|
|
1160
|
+
eventName = "input";
|
|
1161
|
+
if (!multiline) input.type = field.type === "number" ? "number" : "text";
|
|
1162
|
+
write = value => {
|
|
1163
|
+
input.value = field.type === "array" ? JSON.stringify(value ?? []) : (value ?? "");
|
|
1164
|
+
grow();
|
|
1165
|
+
};
|
|
1166
|
+
read = () => {
|
|
1167
|
+
switch (field.type) {
|
|
1168
|
+
case "array":
|
|
1169
|
+
return JSON.parse(input.value);
|
|
1170
|
+
case "number":
|
|
1171
|
+
return input.value === "" ? Number.NaN : Number(input.value);
|
|
1172
|
+
default:
|
|
1173
|
+
return input.value;
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
row.append(fieldControl(input, multiline));
|
|
1110
1177
|
break;
|
|
1111
1178
|
}
|
|
1112
1179
|
}
|
|
@@ -1136,7 +1203,18 @@ function mountPanel(root, catalog) {
|
|
|
1136
1203
|
});
|
|
1137
1204
|
if (eventName === "input") inputContainer.addEventListener("compositionend", event => event.target.dispatchEvent(new window.Event("input", { bubbles: true })));
|
|
1138
1205
|
groups.get(group).append(row);
|
|
1206
|
+
searchRows.push({ row, text: [field.name, field.key, field.description, ...(field.options ?? []).map(option => option.label)].join(" ").toLocaleLowerCase() });
|
|
1139
1207
|
}
|
|
1208
|
+
const empty = element("p", "pp-description", "没有匹配的设置项");
|
|
1209
|
+
empty.hidden = true;
|
|
1210
|
+
empty.setAttribute("role", "status");
|
|
1211
|
+
view.append(empty);
|
|
1212
|
+
search.oninput = () => {
|
|
1213
|
+
const words = search.value.trim().toLocaleLowerCase().split(/\s+/);
|
|
1214
|
+
for (const { row, text } of searchRows) row.hidden = !words.every(word => text.includes(word));
|
|
1215
|
+
for (const rows of groups.values()) rows.parentElement.hidden = [...rows.children].every(row => row.hidden);
|
|
1216
|
+
empty.hidden = searchRows.some(({ row }) => !row.hidden);
|
|
1217
|
+
};
|
|
1140
1218
|
const cachePage = element("section", "pp-cache-page");
|
|
1141
1219
|
const output = element("pre", "pp-cache");
|
|
1142
1220
|
output.textContent = "暂无缓存";
|
|
@@ -1161,9 +1239,9 @@ function mountPanel(root, catalog) {
|
|
|
1161
1239
|
},
|
|
1162
1240
|
);
|
|
1163
1241
|
});
|
|
1164
|
-
handlers.set("clearCaches", () => {
|
|
1242
|
+
handlers.set("clearCaches", async () => {
|
|
1165
1243
|
if (saving) return;
|
|
1166
|
-
if (!window
|
|
1244
|
+
if (!(await requestConfirmation(window, `清空 ${active} 的全部 Caches?`)) || destroyed || saving) return;
|
|
1167
1245
|
return perform(
|
|
1168
1246
|
() => client.clearCaches(active),
|
|
1169
1247
|
() => {
|
|
@@ -1171,9 +1249,9 @@ function mountPanel(root, catalog) {
|
|
|
1171
1249
|
},
|
|
1172
1250
|
);
|
|
1173
1251
|
});
|
|
1174
|
-
handlers.set("reset", () => {
|
|
1252
|
+
handlers.set("reset", async () => {
|
|
1175
1253
|
if (saving) return;
|
|
1176
|
-
if (!window
|
|
1254
|
+
if (!(await requestConfirmation(window, `重置 ${active}?这将删除该模块的 Settings、Caches 和其它持久化数据。`)) || destroyed || saving) return;
|
|
1177
1255
|
return perform(() => client.reset(active), controls);
|
|
1178
1256
|
});
|
|
1179
1257
|
navigation?.destroy();
|
|
@@ -1213,8 +1291,8 @@ function mountPanel(root, catalog) {
|
|
|
1213
1291
|
}
|
|
1214
1292
|
|
|
1215
1293
|
/**
|
|
1216
|
-
*
|
|
1217
|
-
* Mount
|
|
1294
|
+
* 挂载模块设置页;内置资源只引用官方地址,CSS 输入仅用于该页。
|
|
1295
|
+
* Mount the module page with official resource URLs and optional page-specific CSS.
|
|
1218
1296
|
* @param {import("../index.js").BoxJSInput} boxjs 单个模块的 BoxJS JSON / BoxJS JSON for one module.
|
|
1219
1297
|
* @param {string} [css] 可选 CSS 正文 / Optional CSS text.
|
|
1220
1298
|
* @returns {import("./index.js").MountedPreferences} 模块生命周期句柄 / Module lifecycle handle.
|
|
@@ -1239,8 +1317,28 @@ function mount(boxjs, css = "") {
|
|
|
1239
1317
|
document.head.append(base, custom);
|
|
1240
1318
|
const previousTitle = document.title;
|
|
1241
1319
|
const previousTheme = document.documentElement.dataset.theme;
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1320
|
+
const previousDark = document.documentElement.classList.contains("bili_dark");
|
|
1321
|
+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
|
|
1322
|
+
const previousKeyboard = document.documentElement.style.getPropertyValue("--pp-keyboard-height");
|
|
1323
|
+
const host = window.frameElement?.ownerDocument.documentElement;
|
|
1324
|
+
/**
|
|
1325
|
+
* 跟随嵌入宿主的通用环境状态,不识别业务 App 或解析其 UA。
|
|
1326
|
+
* Follow generic host appearance without detecting a business app or parsing its user agent.
|
|
1327
|
+
* @returns {void} 已同步主题与键盘避让 / Theme and keyboard clearance synchronized.
|
|
1328
|
+
*/
|
|
1329
|
+
const syncAppearance = () => {
|
|
1330
|
+
const theme = host?.dataset.theme ?? previousTheme ?? (systemTheme.matches ? "dark" : "light");
|
|
1331
|
+
document.documentElement.dataset.theme = theme;
|
|
1332
|
+
document.documentElement.classList.toggle("bili_dark", theme === "dark");
|
|
1333
|
+
if (host) document.documentElement.style.setProperty("--pp-keyboard-height", host.style.getPropertyValue("--pp-keyboard-height"));
|
|
1334
|
+
};
|
|
1335
|
+
let observer;
|
|
1336
|
+
syncAppearance();
|
|
1337
|
+
systemTheme.addEventListener("change", syncAppearance);
|
|
1338
|
+
if (host) {
|
|
1339
|
+
observer = new MutationObserver(syncAppearance);
|
|
1340
|
+
observer.observe(host, { attributes: true, attributeFilter: ["data-theme", "style"] });
|
|
1341
|
+
}
|
|
1244
1342
|
document.title = metadata.name ?? catalog.module.module;
|
|
1245
1343
|
let panel;
|
|
1246
1344
|
const view = {
|
|
@@ -1250,6 +1348,9 @@ function mount(boxjs, css = "") {
|
|
|
1250
1348
|
* @returns {void} 无返回值 / No return value.
|
|
1251
1349
|
*/
|
|
1252
1350
|
destroy() {
|
|
1351
|
+
observer?.disconnect();
|
|
1352
|
+
systemTheme.removeEventListener("change", syncAppearance);
|
|
1353
|
+
document.documentElement.classList.toggle("bili_dark", previousDark);
|
|
1253
1354
|
panel?.destroy();
|
|
1254
1355
|
base.remove();
|
|
1255
1356
|
custom.remove();
|
|
@@ -1258,6 +1359,7 @@ function mount(boxjs, css = "") {
|
|
|
1258
1359
|
document.title = previousTitle;
|
|
1259
1360
|
if (previousTheme === undefined) delete document.documentElement.dataset.theme;
|
|
1260
1361
|
else document.documentElement.dataset.theme = previousTheme;
|
|
1362
|
+
document.documentElement.style.setProperty("--pp-keyboard-height", previousKeyboard);
|
|
1261
1363
|
},
|
|
1262
1364
|
};
|
|
1263
1365
|
try {
|
package/dist/module/index.html
CHANGED
|
@@ -146,6 +146,10 @@ class ModuleFrame extends EventTarget {
|
|
|
146
146
|
this.#state = { ...event.detail, actions: event.detail.actions ?? [] };
|
|
147
147
|
this.dispatchEvent(new Event("change"));
|
|
148
148
|
};
|
|
149
|
+
#confirmation = event => {
|
|
150
|
+
const request = new CustomEvent("confirm", { cancelable: true, detail: event.detail });
|
|
151
|
+
if (!this.dispatchEvent(request)) event.preventDefault();
|
|
152
|
+
};
|
|
149
153
|
|
|
150
154
|
/**
|
|
151
155
|
* 建立 iframe 与请求输入;调用方挂载 element 后调用 load。
|
|
@@ -162,6 +166,7 @@ class ModuleFrame extends EventTarget {
|
|
|
162
166
|
this.element.title = `${inputs.module} 设置`;
|
|
163
167
|
this.element.dataset.preferencePanes = JSON.stringify(inputs);
|
|
164
168
|
this.element.addEventListener("preferencepanes:change", this.#change);
|
|
169
|
+
this.element.addEventListener("preferencepanes:confirm", this.#confirmation);
|
|
165
170
|
this.#state = { title: inputs.module, module: inputs.module, busy: false, canGoBack: true, actions: [] };
|
|
166
171
|
options.signal?.addEventListener("abort", this.#abort, { once: true });
|
|
167
172
|
}
|
|
@@ -222,6 +227,7 @@ class ModuleFrame extends EventTarget {
|
|
|
222
227
|
this.#controller.abort();
|
|
223
228
|
this.#options.signal?.removeEventListener("abort", this.#abort);
|
|
224
229
|
this.element.removeEventListener("preferencepanes:change", this.#change);
|
|
230
|
+
this.element.removeEventListener("preferencepanes:confirm", this.#confirmation);
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
233
|
|