@nsnanocat/preference-panes 1.2.5 → 1.3.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/CHANGELOG.md +31 -0
- package/README.md +66 -25
- package/dist/home.html +2208 -0
- package/dist/{module/navigation.mjs → navigation.mjs} +51 -143
- package/dist/preference-panes.mjs +913 -614
- package/package.json +4 -3
- package/src/browser/ActionMenu.mjs +7 -6
- package/src/browser/Navigation.d.mts +4 -95
- package/src/browser/Navigation.mjs +43 -14
- package/src/browser/boxjs.mjs +38 -8
- package/src/browser/client.d.mts +3 -3
- package/src/browser/client.mjs +57 -25
- package/src/browser/components.mjs +0 -44
- package/src/browser/home.d.mts +66 -0
- package/src/browser/home.html +15 -0
- package/src/browser/home.mjs +287 -0
- package/src/browser/index.d.ts +2 -0
- package/src/browser/mount.mjs +50 -91
- package/src/browser/page.mjs +325 -0
- package/src/browser/panel.mjs +118 -149
- package/src/browser/styles.css +565 -0
- package/src/index.d.ts +8 -0
- package/dist/module/index.html +0 -16
- package/dist/module/index.mjs +0 -1625
- package/src/browser/ModuleFrame.mjs +0 -122
- package/src/browser/index.mjs +0 -89
- package/src/browser/module.html +0 -16
- package/src/browser/panel.css +0 -315
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 模块文档容器:获取静态 HTML,并通过 iframe 数据属性传递页面资源输入。
|
|
3
|
-
* Module document container: fetch static HTML and pass page resource inputs through iframe data attributes.
|
|
4
|
-
*/
|
|
5
|
-
export class ModuleFrame extends EventTarget {
|
|
6
|
-
#url;
|
|
7
|
-
#options;
|
|
8
|
-
#controller = new AbortController();
|
|
9
|
-
#abort = () => this.destroy();
|
|
10
|
-
#state;
|
|
11
|
-
#change = event => {
|
|
12
|
-
this.#state = { ...event.detail, actions: event.detail.actions ?? [] };
|
|
13
|
-
this.dispatchEvent(new Event("change"));
|
|
14
|
-
};
|
|
15
|
-
#forward(type, event) {
|
|
16
|
-
if (!this.dispatchEvent(new CustomEvent(type, { cancelable: true, detail: event.detail }))) event.preventDefault();
|
|
17
|
-
}
|
|
18
|
-
#confirmation = event => this.#forward("confirm", event);
|
|
19
|
-
#notice = event => this.#forward("notice", event);
|
|
20
|
-
#openURL = event => this.#forward("open-url", event);
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 建立 iframe;调用方挂载 element 后调用 load。
|
|
24
|
-
* Create the iframe; callers mount element and then call load.
|
|
25
|
-
* @param {string | URL} url 模块请求地址 / Module request URL.
|
|
26
|
-
* @param {{signal?: AbortSignal, headers?: HeadersInit}} [options] 请求选项 / Request options.
|
|
27
|
-
*/
|
|
28
|
-
constructor(url, options = {}) {
|
|
29
|
-
super();
|
|
30
|
-
this.#url = new URL(url, document.baseURI);
|
|
31
|
-
const match = /^\/settings\/([a-zA-Z0-9_-]+)\/?$/.exec(this.#url.pathname);
|
|
32
|
-
if (!match) throw new TypeError("Open a concrete module URL");
|
|
33
|
-
this.#options = { signal: options.signal };
|
|
34
|
-
this.element = document.createElement("iframe");
|
|
35
|
-
this.element.title = `${match[1]} 设置`;
|
|
36
|
-
this.element.dataset.preferencePanes = "true";
|
|
37
|
-
this.element.dataset.preferencePanesModule = match[1];
|
|
38
|
-
this.element.dataset.preferencePanesBase = this.#url.href;
|
|
39
|
-
const headers = new Headers(options.headers);
|
|
40
|
-
for (const [header, property, kind] of [
|
|
41
|
-
["X-PreferencePanes-JSON", "preferencePanesJson", "BoxJS"],
|
|
42
|
-
["X-PreferencePanes-CSS", "preferencePanesStylesheet", "CSS"],
|
|
43
|
-
]) {
|
|
44
|
-
if (!headers.has(header)) continue;
|
|
45
|
-
const source = headers.get(header)?.trim();
|
|
46
|
-
if (!source) {
|
|
47
|
-
if (kind === "BoxJS") throw new TypeError("BoxJS resource URL is required");
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const resource = new URL(source, this.#url);
|
|
51
|
-
if (!["http:", "https:"].includes(resource.protocol)) throw new TypeError(`${kind} resource must use HTTP(S)`);
|
|
52
|
-
this.element.dataset[property] = resource.href;
|
|
53
|
-
}
|
|
54
|
-
this.element.addEventListener("preferencepanes:change", this.#change);
|
|
55
|
-
this.element.addEventListener("preferencepanes:confirm", this.#confirmation);
|
|
56
|
-
this.element.addEventListener("preferencepanes:notice", this.#notice);
|
|
57
|
-
this.element.addEventListener("preferencepanes:open-url", this.#openURL);
|
|
58
|
-
this.#state = { title: match[1], module: match[1], busy: false, canGoBack: true, actions: [] };
|
|
59
|
-
options.signal?.addEventListener("abort", this.#abort, { once: true });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 当前模块导航状态。
|
|
64
|
-
* Current module navigation state.
|
|
65
|
-
*/
|
|
66
|
-
get state() {
|
|
67
|
-
return { ...this.#state };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 获取原始 HTML;晚到响应在退出后不得重新挂载。
|
|
72
|
-
* Fetch unmodified HTML; a late response must not remount after departure.
|
|
73
|
-
* @returns {Promise<void>} HTML 已交给 iframe;表单状态通过 change 事件提供 / HTML assigned; form state is reported through change.
|
|
74
|
-
*/
|
|
75
|
-
async load() {
|
|
76
|
-
if (this.#options.signal?.aborted) this.destroy();
|
|
77
|
-
const timer = setTimeout(() => this.#controller.abort(), 10000);
|
|
78
|
-
try {
|
|
79
|
-
const response = await fetch(this.#url, { method: "GET", cache: "no-store", credentials: "omit", signal: this.#controller.signal });
|
|
80
|
-
if (response.status !== 200) throw new Error(`HTTP ${response.status}`);
|
|
81
|
-
const html = await response.text();
|
|
82
|
-
this.#controller.signal.throwIfAborted();
|
|
83
|
-
this.element.srcdoc = html;
|
|
84
|
-
} finally {
|
|
85
|
-
clearTimeout(timer);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 使用 iframe 的联合历史返回;写入期间不导航。
|
|
91
|
-
* Navigate joint iframe history back, except while a write is pending.
|
|
92
|
-
* @returns {void} 无返回值 / No return value.
|
|
93
|
-
*/
|
|
94
|
-
back() {
|
|
95
|
-
if (!this.#state.busy && this.#state.canGoBack) this.element.contentWindow.history.back();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 向模块发送菜单操作,不让宿主访问内部 DOM 或存储客户端。
|
|
100
|
-
* Dispatch a menu action without host access to internal DOM or the storage client.
|
|
101
|
-
* @param {string} id 当前可用操作 / Available action identifier.
|
|
102
|
-
* @returns {void} 无返回值 / No return value.
|
|
103
|
-
*/
|
|
104
|
-
perform(id) {
|
|
105
|
-
if (this.#state.busy || !this.#state.actions.some(action => action.id === id)) throw new Error("Action is not available");
|
|
106
|
-
this.element.dispatchEvent(new CustomEvent("preferencepanes:action", { detail: id }));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 取消加载与事件订阅;节点保留到 Navigation 的退出动画结束。
|
|
111
|
-
* Cancel loading and subscriptions; Navigation retains the node until its exit animation ends.
|
|
112
|
-
* @returns {void} 无返回值 / No return value.
|
|
113
|
-
*/
|
|
114
|
-
destroy() {
|
|
115
|
-
this.#controller.abort();
|
|
116
|
-
this.#options.signal?.removeEventListener("abort", this.#abort);
|
|
117
|
-
this.element.removeEventListener("preferencepanes:change", this.#change);
|
|
118
|
-
this.element.removeEventListener("preferencepanes:confirm", this.#confirmation);
|
|
119
|
-
this.element.removeEventListener("preferencepanes:notice", this.#notice);
|
|
120
|
-
this.element.removeEventListener("preferencepanes:open-url", this.#openURL);
|
|
121
|
-
}
|
|
122
|
-
}
|
package/src/browser/index.mjs
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { normalizeBoxJs } from "./boxjs.mjs";
|
|
2
|
-
import { statusView } from "./components.mjs";
|
|
3
|
-
import { mount } from "./mount.mjs";
|
|
4
|
-
import { installDefaultStyles } from "./styles.mjs";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 管理模块文档的配置请求、重载和错误状态。
|
|
8
|
-
* Manage configuration requests, reloads, and error states for a module document.
|
|
9
|
-
*/
|
|
10
|
-
class ModulePage {
|
|
11
|
-
#window;
|
|
12
|
-
#root;
|
|
13
|
-
#view;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 创建模块页面控制器并安装基础样式。
|
|
17
|
-
* Create the module page controller and install base styles.
|
|
18
|
-
* @param {Document} document 模块文档 / Module document.
|
|
19
|
-
*/
|
|
20
|
-
constructor(document) {
|
|
21
|
-
this.#window = document.defaultView;
|
|
22
|
-
this.#root = document.querySelector("#preferences");
|
|
23
|
-
installDefaultStyles(document);
|
|
24
|
-
this.#window.addEventListener("pageshow", this.#show);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 读取页面声明的 BoxJS JSON 并挂载通用前端。
|
|
29
|
-
* Read the BoxJS JSON declared by the page and mount the generic frontend.
|
|
30
|
-
* @returns {Promise<void>} 启动完成 / Startup completion.
|
|
31
|
-
*/
|
|
32
|
-
async start() {
|
|
33
|
-
try {
|
|
34
|
-
this.#view?.destroy();
|
|
35
|
-
this.#view = undefined;
|
|
36
|
-
this.#root.replaceChildren(statusView("读取设置…"));
|
|
37
|
-
const embedded = this.#window.frameElement?.dataset.preferencePanesModule;
|
|
38
|
-
const match = /^\/settings\/([a-zA-Z0-9_-]+)\/?$/.exec(this.#window.location.pathname);
|
|
39
|
-
const module = embedded ?? match?.[1];
|
|
40
|
-
if (!module) throw new TypeError("Open a concrete module URL");
|
|
41
|
-
const frame = this.#window.frameElement?.dataset;
|
|
42
|
-
const base = frame?.preferencePanesBase ?? this.#window.location.href;
|
|
43
|
-
this.#window.document.querySelector("link[data-preference-panes-stylesheet]")?.remove();
|
|
44
|
-
const stylesheetSource = frame?.preferencePanesStylesheet ?? new URLSearchParams(this.#window.location.search).get("css")?.trim();
|
|
45
|
-
if (stylesheetSource) {
|
|
46
|
-
const stylesheet = new URL(stylesheetSource, base);
|
|
47
|
-
if (!["http:", "https:"].includes(stylesheet.protocol)) throw new TypeError("CSS resource must use HTTP(S)");
|
|
48
|
-
const link = this.#window.document.createElement("link");
|
|
49
|
-
link.dataset.preferencePanesStylesheet = "true";
|
|
50
|
-
link.rel = "stylesheet";
|
|
51
|
-
link.href = stylesheet.href;
|
|
52
|
-
this.#window.document.head.append(link);
|
|
53
|
-
}
|
|
54
|
-
const jsonSource = frame?.preferencePanesJson ?? new URLSearchParams(this.#window.location.search).get("json")?.trim();
|
|
55
|
-
const source = new URL(jsonSource || `/api/${encodeURIComponent(module)}`, base);
|
|
56
|
-
if (!["http:", "https:"].includes(source.protocol)) throw new TypeError("BoxJS resource must use HTTP(S)");
|
|
57
|
-
const response = await fetch(source, { cache: "no-store", credentials: "omit", headers: { Accept: "application/json" } });
|
|
58
|
-
if (response.status !== 200) throw new Error(`HTTP ${response.status}`);
|
|
59
|
-
const boxjs = await response.json();
|
|
60
|
-
normalizeBoxJs(boxjs, module);
|
|
61
|
-
this.#view = mount(boxjs);
|
|
62
|
-
} catch (error) {
|
|
63
|
-
this.#root.replaceChildren(statusView(`加载失败:${error.message}`, () => this.start()));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 释放页面视图和页面级监听器。
|
|
69
|
-
* Release the page view and page-level listener.
|
|
70
|
-
* @returns {void} 无返回值 / No return value.
|
|
71
|
-
*/
|
|
72
|
-
destroy() {
|
|
73
|
-
this.#window.removeEventListener("pageshow", this.#show);
|
|
74
|
-
this.#view?.destroy();
|
|
75
|
-
this.#view = undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 从前进后退缓存恢复时重新加载模块。
|
|
80
|
-
* Reload the module when restored from the back-forward cache.
|
|
81
|
-
* @param {PageTransitionEvent} event 页面显示事件 / Page show event.
|
|
82
|
-
* @returns {void} 无返回值 / No return value.
|
|
83
|
-
*/
|
|
84
|
-
#show = event => {
|
|
85
|
-
if (event.persisted) this.start();
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
new ModulePage(document).start();
|
package/src/browser/module.html
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
<!--__PREFERENCE_PANES_JSON__-->
|
|
8
|
-
<title>Module Preferences</title>
|
|
9
|
-
<style>body { margin: 0; }</style>
|
|
10
|
-
<!--__PREFERENCE_PANES_STYLESHEET__-->
|
|
11
|
-
</head>
|
|
12
|
-
<body>
|
|
13
|
-
<main id="preferences"></main>
|
|
14
|
-
<script type="module" src="/settings/assets/index.mjs?v=__VERSION__"></script>
|
|
15
|
-
</body>
|
|
16
|
-
</html>
|
package/src/browser/panel.css
DELETED
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。
|
|
2
|
-
* Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */
|
|
3
|
-
.pp-panel {
|
|
4
|
-
--pp-text: #18191c;
|
|
5
|
-
--pp-background: #f6f7f8;
|
|
6
|
-
--pp-surface: #fff;
|
|
7
|
-
--pp-field: #f1f2f3;
|
|
8
|
-
--pp-border: #e3e5e7;
|
|
9
|
-
--pp-muted: #797f87;
|
|
10
|
-
--pp-accent: #1677ff;
|
|
11
|
-
--pp-warning: #9a6700;
|
|
12
|
-
font:
|
|
13
|
-
15px / 1.5 -apple-system,
|
|
14
|
-
BlinkMacSystemFont,
|
|
15
|
-
"Segoe UI",
|
|
16
|
-
sans-serif;
|
|
17
|
-
color: var(--pp-text);
|
|
18
|
-
background: var(--pp-background);
|
|
19
|
-
position: relative;
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
width: 100%;
|
|
23
|
-
max-width: 100vw;
|
|
24
|
-
min-width: 0;
|
|
25
|
-
height: 100vh;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
:root[data-theme="dark"] .pp-panel {
|
|
30
|
-
--pp-text: #f1f2f3;
|
|
31
|
-
--pp-background: #0d0e0f;
|
|
32
|
-
--pp-surface: #18191c;
|
|
33
|
-
--pp-field: #2f3238;
|
|
34
|
-
--pp-border: #2f3238;
|
|
35
|
-
--pp-muted: #9499a0;
|
|
36
|
-
--pp-warning: #f0b849;
|
|
37
|
-
}
|
|
38
|
-
.pp-panel * {
|
|
39
|
-
box-sizing: border-box;
|
|
40
|
-
letter-spacing: 0;
|
|
41
|
-
}
|
|
42
|
-
.pp-header {
|
|
43
|
-
flex: none;
|
|
44
|
-
height: calc(52px + env(safe-area-inset-top));
|
|
45
|
-
padding: env(safe-area-inset-top) 12px 0;
|
|
46
|
-
display: flex;
|
|
47
|
-
align-items: center;
|
|
48
|
-
background: var(--pp-surface);
|
|
49
|
-
border-bottom: 1px solid var(--pp-border);
|
|
50
|
-
position: relative;
|
|
51
|
-
z-index: 2;
|
|
52
|
-
}
|
|
53
|
-
.pp-title {
|
|
54
|
-
flex: 1;
|
|
55
|
-
text-align: center;
|
|
56
|
-
font-size: 17px;
|
|
57
|
-
font-weight: 500;
|
|
58
|
-
margin: 0;
|
|
59
|
-
min-width: 0;
|
|
60
|
-
overflow-wrap: anywhere;
|
|
61
|
-
}
|
|
62
|
-
.pp-nav-spacer {
|
|
63
|
-
width: 44px;
|
|
64
|
-
flex: none;
|
|
65
|
-
}
|
|
66
|
-
.pp-panel button {
|
|
67
|
-
font: inherit;
|
|
68
|
-
cursor: pointer;
|
|
69
|
-
border: 0;
|
|
70
|
-
background: none;
|
|
71
|
-
color: inherit;
|
|
72
|
-
}
|
|
73
|
-
.pp-panel .pp-back {
|
|
74
|
-
width: 44px;
|
|
75
|
-
height: 44px;
|
|
76
|
-
flex: none;
|
|
77
|
-
font-size: 34px;
|
|
78
|
-
line-height: 32px;
|
|
79
|
-
padding: 0;
|
|
80
|
-
}
|
|
81
|
-
.pp-panel button:disabled {
|
|
82
|
-
opacity: 0.5;
|
|
83
|
-
cursor: wait;
|
|
84
|
-
}
|
|
85
|
-
.pp-viewport {
|
|
86
|
-
position: relative;
|
|
87
|
-
flex: 1;
|
|
88
|
-
min-width: 0;
|
|
89
|
-
min-height: 0;
|
|
90
|
-
overflow: hidden;
|
|
91
|
-
}
|
|
92
|
-
@supports (height: 100dvh) {
|
|
93
|
-
.pp-panel {
|
|
94
|
-
height: 100dvh;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
.pp-fields,
|
|
98
|
-
.pp-choice-page,
|
|
99
|
-
.pp-settings-page,
|
|
100
|
-
.pp-cache-page {
|
|
101
|
-
position: absolute;
|
|
102
|
-
inset: 0;
|
|
103
|
-
min-width: 0;
|
|
104
|
-
overflow-x: hidden;
|
|
105
|
-
overflow-y: auto;
|
|
106
|
-
padding: 12px max(16px, calc((100% - 688px) / 2)) calc(28px + env(safe-area-inset-bottom) + var(--pp-keyboard-height, 0px));
|
|
107
|
-
scroll-padding-bottom: var(--pp-keyboard-height, 0px);
|
|
108
|
-
background: var(--pp-background);
|
|
109
|
-
}
|
|
110
|
-
.pp-choice-link {
|
|
111
|
-
display: flex;
|
|
112
|
-
align-items: center;
|
|
113
|
-
justify-content: flex-end;
|
|
114
|
-
gap: 8px;
|
|
115
|
-
max-width: 45%;
|
|
116
|
-
min-width: 44px;
|
|
117
|
-
min-height: 44px;
|
|
118
|
-
padding: 0;
|
|
119
|
-
text-align: right;
|
|
120
|
-
flex: 1;
|
|
121
|
-
}
|
|
122
|
-
.pp-panel a.pp-choice-link {
|
|
123
|
-
color: var(--pp-accent);
|
|
124
|
-
text-decoration: none;
|
|
125
|
-
}
|
|
126
|
-
.pp-summary {
|
|
127
|
-
color: var(--pp-muted);
|
|
128
|
-
font-size: 13px;
|
|
129
|
-
line-height: 18px;
|
|
130
|
-
display: -webkit-box;
|
|
131
|
-
-webkit-line-clamp: 2;
|
|
132
|
-
-webkit-box-orient: vertical;
|
|
133
|
-
overflow: hidden;
|
|
134
|
-
overflow-wrap: anywhere;
|
|
135
|
-
}
|
|
136
|
-
.pp-chevron {
|
|
137
|
-
color: var(--pp-muted);
|
|
138
|
-
font-size: 22px;
|
|
139
|
-
flex: none;
|
|
140
|
-
}
|
|
141
|
-
.pp-editor {
|
|
142
|
-
flex: none;
|
|
143
|
-
width: 45%;
|
|
144
|
-
min-width: 0;
|
|
145
|
-
min-height: 36px;
|
|
146
|
-
padding: 8px 10px;
|
|
147
|
-
font: inherit;
|
|
148
|
-
color: var(--pp-text);
|
|
149
|
-
background: var(--pp-field);
|
|
150
|
-
border: 0;
|
|
151
|
-
border-radius: 6px;
|
|
152
|
-
}
|
|
153
|
-
.pp-panel .pp-multiline {
|
|
154
|
-
display: block;
|
|
155
|
-
}
|
|
156
|
-
.pp-multiline .pp-editor {
|
|
157
|
-
width: 100%;
|
|
158
|
-
margin-top: 10px;
|
|
159
|
-
}
|
|
160
|
-
.pp-panel [hidden] {
|
|
161
|
-
display: none !important;
|
|
162
|
-
}
|
|
163
|
-
.pp-label {
|
|
164
|
-
flex: 1;
|
|
165
|
-
min-width: 0;
|
|
166
|
-
display: flex;
|
|
167
|
-
flex-direction: column;
|
|
168
|
-
align-items: flex-start;
|
|
169
|
-
margin-right: 16px;
|
|
170
|
-
}
|
|
171
|
-
.pp-field-name {
|
|
172
|
-
color: var(--pp-text);
|
|
173
|
-
font-size: 15px;
|
|
174
|
-
}
|
|
175
|
-
.pp-field-description {
|
|
176
|
-
margin-top: 2px;
|
|
177
|
-
color: var(--pp-muted);
|
|
178
|
-
font-size: 12px;
|
|
179
|
-
}
|
|
180
|
-
.pp-field-warning {
|
|
181
|
-
margin-top: 4px;
|
|
182
|
-
color: var(--pp-warning);
|
|
183
|
-
font-size: 12px;
|
|
184
|
-
overflow-wrap: anywhere;
|
|
185
|
-
}
|
|
186
|
-
.pp-group {
|
|
187
|
-
margin-top: 16px;
|
|
188
|
-
}
|
|
189
|
-
.pp-group-title {
|
|
190
|
-
margin: 0 0 8px;
|
|
191
|
-
color: var(--pp-muted);
|
|
192
|
-
font-size: 15px;
|
|
193
|
-
font-weight: 400;
|
|
194
|
-
}
|
|
195
|
-
.pp-row {
|
|
196
|
-
min-width: 0;
|
|
197
|
-
min-height: 48px;
|
|
198
|
-
padding: 16px;
|
|
199
|
-
display: flex;
|
|
200
|
-
align-items: center;
|
|
201
|
-
justify-content: space-between;
|
|
202
|
-
background: var(--pp-surface);
|
|
203
|
-
border-bottom: 1px solid var(--pp-border);
|
|
204
|
-
}
|
|
205
|
-
.pp-rows > :last-child {
|
|
206
|
-
border-bottom: 0 !important;
|
|
207
|
-
}
|
|
208
|
-
.pp-switch {
|
|
209
|
-
flex: none;
|
|
210
|
-
accent-color: var(--pp-accent);
|
|
211
|
-
}
|
|
212
|
-
.pp-choice {
|
|
213
|
-
justify-content: space-between;
|
|
214
|
-
cursor: pointer;
|
|
215
|
-
}
|
|
216
|
-
.pp-choice input {
|
|
217
|
-
width: 20px;
|
|
218
|
-
height: 20px;
|
|
219
|
-
flex: none;
|
|
220
|
-
accent-color: var(--pp-accent);
|
|
221
|
-
margin: 0;
|
|
222
|
-
}
|
|
223
|
-
.pp-description {
|
|
224
|
-
font-size: 12px;
|
|
225
|
-
line-height: 1.6;
|
|
226
|
-
color: var(--pp-muted);
|
|
227
|
-
white-space: pre-wrap;
|
|
228
|
-
overflow-wrap: anywhere;
|
|
229
|
-
}
|
|
230
|
-
.pp-module-info {
|
|
231
|
-
display: flex;
|
|
232
|
-
gap: 12px;
|
|
233
|
-
margin: 12px 0;
|
|
234
|
-
}
|
|
235
|
-
.pp-module-details {
|
|
236
|
-
min-width: 0;
|
|
237
|
-
overflow-wrap: anywhere;
|
|
238
|
-
}
|
|
239
|
-
.pp-module-source {
|
|
240
|
-
color: inherit;
|
|
241
|
-
text-decoration: underline;
|
|
242
|
-
}
|
|
243
|
-
.pp-status {
|
|
244
|
-
position: fixed;
|
|
245
|
-
inset: 0;
|
|
246
|
-
display: grid;
|
|
247
|
-
place-content: center;
|
|
248
|
-
justify-items: center;
|
|
249
|
-
gap: 12px;
|
|
250
|
-
min-width: 0;
|
|
251
|
-
min-height: 0;
|
|
252
|
-
margin: 0;
|
|
253
|
-
padding: 24px;
|
|
254
|
-
color: var(--pp-muted, GrayText);
|
|
255
|
-
text-align: center;
|
|
256
|
-
background: var(--pp-background, Canvas);
|
|
257
|
-
}
|
|
258
|
-
.pp-viewport > .pp-status {
|
|
259
|
-
position: absolute;
|
|
260
|
-
}
|
|
261
|
-
.pp-status-spinner {
|
|
262
|
-
box-sizing: border-box;
|
|
263
|
-
width: 28px;
|
|
264
|
-
height: 28px;
|
|
265
|
-
border: 3px solid color-mix(in srgb, currentColor 25%, transparent);
|
|
266
|
-
border-top-color: var(--pp-accent, AccentColor);
|
|
267
|
-
border-radius: 50%;
|
|
268
|
-
animation: pp-status-spin 0.8s linear infinite;
|
|
269
|
-
}
|
|
270
|
-
.pp-status-message {
|
|
271
|
-
max-width: 100%;
|
|
272
|
-
margin: 0;
|
|
273
|
-
overflow-wrap: anywhere;
|
|
274
|
-
}
|
|
275
|
-
.pp-status-action {
|
|
276
|
-
min-width: 96px;
|
|
277
|
-
min-height: 44px;
|
|
278
|
-
padding: 8px 16px;
|
|
279
|
-
border: 0;
|
|
280
|
-
border-radius: 6px;
|
|
281
|
-
color: var(--pp-text, ButtonText);
|
|
282
|
-
font: inherit;
|
|
283
|
-
cursor: pointer;
|
|
284
|
-
background: var(--pp-surface, ButtonFace);
|
|
285
|
-
}
|
|
286
|
-
@keyframes pp-status-spin {
|
|
287
|
-
to {
|
|
288
|
-
transform: rotate(1turn);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
.pp-cache {
|
|
292
|
-
white-space: pre-wrap;
|
|
293
|
-
overflow-wrap: anywhere;
|
|
294
|
-
}
|
|
295
|
-
.pp-toast {
|
|
296
|
-
pointer-events: none;
|
|
297
|
-
position: fixed;
|
|
298
|
-
bottom: calc(30px + env(safe-area-inset-bottom));
|
|
299
|
-
left: 50%;
|
|
300
|
-
transform: translateX(-50%);
|
|
301
|
-
max-width: 90vw;
|
|
302
|
-
padding: 10px 16px;
|
|
303
|
-
border-radius: 8px;
|
|
304
|
-
background: #333e;
|
|
305
|
-
color: white;
|
|
306
|
-
font-size: 13px;
|
|
307
|
-
z-index: 20;
|
|
308
|
-
}
|
|
309
|
-
.pp-toast[data-kind="error"] {
|
|
310
|
-
background: #8d2424;
|
|
311
|
-
}
|
|
312
|
-
.pp-panel :focus-visible {
|
|
313
|
-
outline: 2px solid var(--pp-accent);
|
|
314
|
-
outline-offset: -2px;
|
|
315
|
-
}
|