@phoenix-wing/code-core 0.6.4 → 0.6.5

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 CHANGED
@@ -13,6 +13,8 @@ Phoenix Code 可跨宿主复用的纯 TypeScript 算法与数据契约。包内
13
13
  - CAA 工程环境和对话框 handoff 数据契约;
14
14
  - 跨宿主文件、成员排序、UUID 与搜索替换结果 ViewModel;成员排序、UUID 与搜索替换另提供 Host-neutral Web Components;
15
15
  - 递归多层 `PnwNavigationTreeModel` 与 `<pnw-navigation-tree>`:受控选择/展开事件、ARIA 键盘、受控图标以及 VS Code 风格 light/dark 状态。
16
+ - Host-neutral `<pnw-combo>`:分组候选、逐行删除、全部清空和自动避让可见边界;
17
+ - Host-neutral `<pnw-cleanup-dialog>`:多方式/多目标、冻结预览 token 与默认启用的高风险二次确认。Host 可设置 `requireHighRiskConfirmation: false` 省略额外勾选;高风险标记、有效预览和显式执行按钮仍保留。
16
18
 
17
19
  通用浏览器 UI 从稳定门面导入:
18
20
 
@@ -22,7 +24,8 @@ import type { PnwNavigationTreeModel } from "@phoenix-wing/code-core/ui/model";
22
24
  ```
23
25
 
24
26
  物理目录 `src/ui/elements/` 与 `src/ui/model/` 不是公共 subpath。完整契约见仓库
25
- [`docs/Pnw通用NavigationTreeWebComponent.md`](../../docs/Pnw通用NavigationTreeWebComponent.md)
27
+ [`docs/Pnw通用NavigationTreeWebComponent.md`](../../docs/Pnw通用NavigationTreeWebComponent.md)
28
+ [`docs/Pnw可管理Combo与统一清理能力.md`](../../docs/Pnw可管理Combo与统一清理能力.md)。
26
29
 
27
30
  `kt-auto-code` 与 Desk Tools 应直接消费本包。旧 `phoenix-wing/code-core` subpath 仅保留兼容 re-export,不再持有算法真源。
28
31
 
@@ -0,0 +1,113 @@
1
+ export declare const PNW_CLEANUP_DIALOG_TAG = "pnw-cleanup-dialog";
2
+ export declare const PNW_CLEANUP_DIALOG_ACTION = "pnw-cleanup-dialog-action";
3
+ export type PnwCleanupDialogRisk = "normal" | "high";
4
+ export type PnwCleanupDialogPreviewState = "idle" | "loading" | "ready" | "executing" | "complete" | "error";
5
+ export interface PnwCleanupDialogMode {
6
+ readonly id: string;
7
+ readonly label: string;
8
+ readonly description?: string;
9
+ readonly risk: PnwCleanupDialogRisk;
10
+ /** Overrides the dialog-level fallback for this mode. */
11
+ readonly rulesVisible?: boolean;
12
+ }
13
+ export interface PnwCleanupDialogTarget {
14
+ readonly id: string;
15
+ readonly label: string;
16
+ readonly path: string;
17
+ readonly description?: string;
18
+ readonly selected: boolean;
19
+ readonly disabled?: boolean;
20
+ readonly disabledReason?: string;
21
+ readonly supportedModeIds?: readonly string[];
22
+ }
23
+ export interface PnwCleanupDialogPreview {
24
+ readonly state: PnwCleanupDialogPreviewState;
25
+ /** Opaque Host token binding Execute to the accepted preview. */
26
+ readonly token?: string;
27
+ readonly summary?: string;
28
+ readonly message?: string;
29
+ readonly items: readonly string[];
30
+ }
31
+ export interface PnwCleanupDialogModel {
32
+ readonly title: string;
33
+ readonly description?: string;
34
+ readonly modes: readonly PnwCleanupDialogMode[];
35
+ readonly selectedModeId?: string;
36
+ readonly targets: readonly PnwCleanupDialogTarget[];
37
+ readonly rulesVisible: boolean;
38
+ readonly rulesLabel: string;
39
+ readonly rulesYaml: string;
40
+ readonly preview: PnwCleanupDialogPreview;
41
+ readonly previewEnabled: boolean;
42
+ readonly previewDisabledReason?: string;
43
+ readonly executeEnabled: boolean;
44
+ readonly executeDisabledReason?: string;
45
+ readonly previewLabel: string;
46
+ readonly executeLabel: string;
47
+ readonly cancelLabel: string;
48
+ readonly highRiskConfirmationLabel: string;
49
+ /** Defaults to true. False omits only the extra checkbox, never the risk or frozen-preview gates. */
50
+ readonly requireHighRiskConfirmation?: boolean;
51
+ }
52
+ export interface PnwCleanupDialogRequest {
53
+ readonly modeId: string;
54
+ readonly targetIds: readonly string[];
55
+ readonly rulesYaml: string;
56
+ }
57
+ export type PnwCleanupDialogActionDetail = {
58
+ readonly kind: "change-mode";
59
+ readonly modeId: string;
60
+ } | {
61
+ readonly kind: "toggle-target";
62
+ readonly targetId: string;
63
+ readonly selected: boolean;
64
+ } | {
65
+ readonly kind: "change-rules";
66
+ readonly rulesYaml: string;
67
+ } | {
68
+ readonly kind: "preview";
69
+ readonly request: PnwCleanupDialogRequest;
70
+ } | {
71
+ readonly kind: "execute";
72
+ readonly request: PnwCleanupDialogRequest;
73
+ readonly previewToken: string;
74
+ } | {
75
+ readonly kind: "cancel";
76
+ };
77
+ export declare class PnwCleanupDialog extends HTMLElement {
78
+ private readonly pnwRoot;
79
+ private pnwActiveModel;
80
+ private pnwDialog?;
81
+ private pnwBody?;
82
+ private pnwTitle?;
83
+ private pnwDescription?;
84
+ private pnwCancelButton?;
85
+ private pnwPreviewButton?;
86
+ private pnwExecuteButton?;
87
+ private pnwHighRiskConfirmed;
88
+ connectedCallback(): void;
89
+ set model(value: PnwCleanupDialogModel | undefined);
90
+ get model(): PnwCleanupDialogModel;
91
+ showModal(modeId?: string): void;
92
+ close(): void;
93
+ private pnwUpgradePreDefinitionModel;
94
+ private pnwEnsureDom;
95
+ private pnwRender;
96
+ private pnwRenderMode;
97
+ private pnwRenderTargets;
98
+ private pnwActivateMode;
99
+ private pnwRenderRules;
100
+ private pnwRenderPreview;
101
+ private pnwRenderHighRisk;
102
+ private pnwField;
103
+ private pnwButton;
104
+ private pnwRequest;
105
+ private pnwRequestReady;
106
+ private pnwEmitPreview;
107
+ private pnwEmitExecute;
108
+ private pnwCancel;
109
+ private pnwEmit;
110
+ }
111
+ export declare function pnwNormalizeCleanupDialogModel(value: PnwCleanupDialogModel | undefined): PnwCleanupDialogModel;
112
+ export declare function pnwCodeDefineCleanupDialog(tagName?: string): void;
113
+ //# sourceMappingURL=PnwCleanupDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PnwCleanupDialog.d.ts","sourceRoot":"","sources":["../../../src/ui/elements/PnwCleanupDialog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,489 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ export const PNW_CLEANUP_DIALOG_TAG = "pnw-cleanup-dialog";
3
+ export const PNW_CLEANUP_DIALOG_ACTION = "pnw-cleanup-dialog-action";
4
+ const PNW_EMPTY_CLEANUP_DIALOG_MODEL = Object.freeze({
5
+ title: "清理",
6
+ modes: Object.freeze([]),
7
+ targets: Object.freeze([]),
8
+ rulesVisible: false,
9
+ rulesLabel: "清理规则",
10
+ rulesYaml: "",
11
+ preview: Object.freeze({ state: "idle", items: Object.freeze([]) }),
12
+ previewEnabled: false,
13
+ executeEnabled: false,
14
+ previewLabel: "预览",
15
+ executeLabel: "清理",
16
+ cancelLabel: "取消",
17
+ highRiskConfirmationLabel: "我已核对预览,确认执行不可撤销的清理。",
18
+ requireHighRiskConfirmation: true,
19
+ });
20
+ const PNW_CLEANUP_DIALOG_STYLE = `
21
+ :host { color: var(--vscode-foreground, #1f1f1f); font: var(--vscode-font-size, 13px)/1.4 var(--vscode-font-family, system-ui, sans-serif); }
22
+ * { box-sizing: border-box; }
23
+ button, select, textarea, input { font: inherit; }
24
+ .pnw-cleanup-dialog {
25
+ width: min(680px, calc(100vw - 32px)); max-width: 100%; max-height: min(760px, calc(100vh - 32px)); padding: 0;
26
+ overflow: hidden; border: 1px solid var(--vscode-widget-border, #8e8e8e); color: inherit;
27
+ background: var(--vscode-editorWidget-background, var(--vscode-editor-background, #fff)); box-shadow: 0 8px 30px var(--vscode-widget-shadow, rgba(0,0,0,.35));
28
+ }
29
+ .pnw-cleanup-dialog::backdrop { background: rgba(0,0,0,.38); }
30
+ .pnw-cleanup-shell { display: grid; max-height: inherit; grid-template-rows: auto minmax(0, 1fr) auto; }
31
+ .pnw-cleanup-header, .pnw-cleanup-footer { display: flex; align-items: center; gap: 8px; padding: 9px 12px; }
32
+ .pnw-cleanup-header { border-bottom: 1px solid var(--vscode-panel-border, #d4d4d4); }
33
+ .pnw-cleanup-title { flex: 1; min-width: 0; margin: 0; font-size: 14px; font-weight: 600; }
34
+ .pnw-cleanup-close { width: 26px; height: 26px; padding: 0; border: 0; color: inherit; background: transparent; cursor: pointer; }
35
+ .pnw-cleanup-close:hover { background: var(--vscode-toolbar-hoverBackground, #e8e8e8); }
36
+ .pnw-cleanup-content { min-height: 0; padding: 10px 12px; overflow: auto; }
37
+ .pnw-cleanup-description, .pnw-cleanup-mode-description, .pnw-cleanup-target-description, .pnw-cleanup-message { color: var(--vscode-descriptionForeground, #616161); }
38
+ .pnw-cleanup-description { margin: 0 0 9px; }
39
+ .pnw-cleanup-field { display: grid; gap: 5px; margin-top: 10px; }
40
+ .pnw-cleanup-field:first-child { margin-top: 0; }
41
+ .pnw-cleanup-label { font-weight: 600; }
42
+ .pnw-cleanup-mode { width: 100%; min-height: 28px; padding: 3px 6px; border: 1px solid var(--vscode-dropdown-border, #8e8e8e); color: var(--vscode-dropdown-foreground, inherit); background: var(--vscode-dropdown-background, #fff); }
43
+ .pnw-cleanup-mode-description { margin: 0; font-size: 12px; }
44
+ .pnw-cleanup-targets { display: grid; border: 1px solid var(--vscode-panel-border, #d4d4d4); }
45
+ .pnw-cleanup-target { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 7px; padding: 6px 7px; align-items: start; }
46
+ .pnw-cleanup-target + .pnw-cleanup-target { border-top: 1px solid var(--vscode-panel-border, #d4d4d4); }
47
+ .pnw-cleanup-target-main { min-width: 0; }
48
+ .pnw-cleanup-target-label { font-weight: 600; }
49
+ .pnw-cleanup-target-path { overflow: hidden; color: var(--vscode-descriptionForeground, #616161); font-family: var(--vscode-editor-font-family, monospace); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
50
+ .pnw-cleanup-target-description { font-size: 11px; }
51
+ .pnw-cleanup-rules { width: 100%; min-height: 132px; resize: vertical; padding: 6px 7px; border: 1px solid var(--vscode-input-border, #8e8e8e); color: var(--vscode-input-foreground, inherit); background: var(--vscode-input-background, #fff); font-family: var(--vscode-editor-font-family, monospace); }
52
+ .pnw-cleanup-preview { min-height: 74px; border: 1px solid var(--vscode-panel-border, #d4d4d4); }
53
+ .pnw-cleanup-preview-summary, .pnw-cleanup-message { margin: 0; padding: 6px 7px; }
54
+ .pnw-cleanup-preview-summary { font-weight: 600; background: var(--vscode-sideBarSectionHeader-background, rgba(128,128,128,.12)); }
55
+ .pnw-cleanup-message[data-error="true"] { color: var(--vscode-errorForeground, #a1260d); }
56
+ .pnw-cleanup-items { max-height: 164px; margin: 0; padding: 3px 7px 6px 25px; overflow: auto; font-family: var(--vscode-editor-font-family, monospace); font-size: 12px; }
57
+ .pnw-cleanup-confirm { display: flex; gap: 7px; margin-top: 10px; padding: 7px; border: 1px solid var(--vscode-inputValidation-warningBorder, #b89500); background: var(--vscode-inputValidation-warningBackground, rgba(184,149,0,.09)); }
58
+ .pnw-cleanup-footer { justify-content: flex-end; border-top: 1px solid var(--vscode-panel-border, #d4d4d4); }
59
+ .pnw-cleanup-button { min-height: 28px; padding: 3px 10px; border: 1px solid var(--vscode-button-border, transparent); color: var(--vscode-button-secondaryForeground, inherit); background: var(--vscode-button-secondaryBackground, #e5e5e5); cursor: pointer; }
60
+ .pnw-cleanup-button:hover:not(:disabled) { background: var(--vscode-button-secondaryHoverBackground, #d5d5d5); }
61
+ .pnw-cleanup-button-primary { color: var(--vscode-button-foreground, #fff); background: var(--vscode-button-background, #0078d4); }
62
+ .pnw-cleanup-button-primary:hover:not(:disabled) { background: var(--vscode-button-hoverBackground, #026ec1); }
63
+ .pnw-cleanup-button:disabled, input:disabled, select:disabled, textarea:disabled { cursor: default; opacity: .55; }
64
+ button:focus-visible, select:focus-visible, textarea:focus-visible, input:focus-visible { outline: 1px solid var(--vscode-focusBorder, #0078d4); outline-offset: 1px; }
65
+ @media (forced-colors: active) { .pnw-cleanup-dialog, .pnw-cleanup-targets, .pnw-cleanup-preview, .pnw-cleanup-rules, .pnw-cleanup-mode { border-color: CanvasText; } }
66
+ `;
67
+ export class PnwCleanupDialog extends HTMLElement {
68
+ pnwRoot = this.attachShadow({ mode: "open" });
69
+ pnwActiveModel = PNW_EMPTY_CLEANUP_DIALOG_MODEL;
70
+ pnwDialog;
71
+ pnwBody;
72
+ pnwTitle;
73
+ pnwDescription;
74
+ pnwCancelButton;
75
+ pnwPreviewButton;
76
+ pnwExecuteButton;
77
+ pnwHighRiskConfirmed = false;
78
+ connectedCallback() {
79
+ this.pnwUpgradePreDefinitionModel();
80
+ this.pnwEnsureDom();
81
+ this.pnwRender();
82
+ }
83
+ set model(value) {
84
+ const previousToken = this.pnwActiveModel.preview.token;
85
+ this.pnwActiveModel = pnwNormalizeCleanupDialogModel(value);
86
+ if (previousToken !== this.pnwActiveModel.preview.token)
87
+ this.pnwHighRiskConfirmed = false;
88
+ this.pnwEnsureDom();
89
+ this.pnwRender();
90
+ }
91
+ get model() { return this.pnwActiveModel; }
92
+ showModal(modeId) {
93
+ this.pnwEnsureDom();
94
+ if (modeId && this.pnwActiveModel.modes.some(({ id }) => id === modeId)) {
95
+ this.pnwActivateMode(modeId, false);
96
+ }
97
+ this.pnwRender();
98
+ if (!this.pnwDialog?.open) {
99
+ if (typeof this.pnwDialog?.showModal === "function")
100
+ this.pnwDialog.showModal();
101
+ else
102
+ this.pnwDialog?.setAttribute("open", "");
103
+ }
104
+ }
105
+ close() {
106
+ if (!this.pnwDialog?.open && !this.pnwDialog?.hasAttribute("open"))
107
+ return;
108
+ if (typeof this.pnwDialog.close === "function")
109
+ this.pnwDialog.close();
110
+ else
111
+ this.pnwDialog.removeAttribute("open");
112
+ }
113
+ pnwUpgradePreDefinitionModel() {
114
+ if (!Object.prototype.hasOwnProperty.call(this, "model"))
115
+ return;
116
+ const holder = this;
117
+ const model = holder.model;
118
+ delete holder.model;
119
+ this.model = model;
120
+ }
121
+ pnwEnsureDom() {
122
+ if (this.pnwDialog)
123
+ return;
124
+ const style = document.createElement("style");
125
+ style.textContent = PNW_CLEANUP_DIALOG_STYLE;
126
+ const dialog = document.createElement("dialog");
127
+ dialog.className = "pnw-cleanup-dialog";
128
+ dialog.setAttribute("part", "dialog");
129
+ const shell = document.createElement("section");
130
+ shell.className = "pnw-cleanup-shell";
131
+ const header = document.createElement("header");
132
+ header.className = "pnw-cleanup-header";
133
+ const title = document.createElement("h2");
134
+ title.className = "pnw-cleanup-title";
135
+ const close = document.createElement("button");
136
+ close.type = "button";
137
+ close.className = "pnw-cleanup-close";
138
+ close.setAttribute("aria-label", "关闭清理对话框");
139
+ close.textContent = "×";
140
+ close.onclick = () => this.pnwCancel();
141
+ const body = document.createElement("div");
142
+ body.className = "pnw-cleanup-content";
143
+ const description = document.createElement("p");
144
+ description.className = "pnw-cleanup-description";
145
+ const footer = document.createElement("footer");
146
+ footer.className = "pnw-cleanup-footer";
147
+ const cancel = this.pnwButton("pnw-cleanup-cancel", () => this.pnwCancel());
148
+ const preview = this.pnwButton("pnw-cleanup-preview-action", () => this.pnwEmitPreview());
149
+ const execute = this.pnwButton("pnw-cleanup-execute pnw-cleanup-button-primary", () => this.pnwEmitExecute());
150
+ header.append(title, close);
151
+ footer.append(cancel, preview, execute);
152
+ shell.append(header, description, body, footer);
153
+ dialog.append(shell);
154
+ dialog.addEventListener("cancel", (event) => {
155
+ event.preventDefault();
156
+ this.pnwCancel();
157
+ });
158
+ this.pnwRoot.append(style, dialog);
159
+ this.pnwDialog = dialog;
160
+ this.pnwBody = body;
161
+ this.pnwTitle = title;
162
+ this.pnwDescription = description;
163
+ this.pnwCancelButton = cancel;
164
+ this.pnwPreviewButton = preview;
165
+ this.pnwExecuteButton = execute;
166
+ }
167
+ pnwRender() {
168
+ if (!this.pnwBody || !this.pnwTitle || !this.pnwDescription)
169
+ return;
170
+ const model = this.pnwActiveModel;
171
+ const selectedMode = model.modes.find(({ id }) => id === model.selectedModeId);
172
+ const busy = model.preview.state === "loading" || model.preview.state === "executing";
173
+ this.pnwTitle.textContent = model.title;
174
+ this.pnwDescription.textContent = model.description ?? "";
175
+ this.pnwDescription.hidden = !model.description;
176
+ this.pnwBody.replaceChildren();
177
+ this.pnwBody.append(this.pnwRenderMode(model, selectedMode, busy), this.pnwRenderTargets(model, busy));
178
+ if (selectedMode?.rulesVisible ?? model.rulesVisible) {
179
+ this.pnwBody.append(this.pnwRenderRules(model, busy));
180
+ }
181
+ this.pnwBody.append(this.pnwRenderPreview(model));
182
+ if (selectedMode?.risk === "high" && model.requireHighRiskConfirmation !== false) {
183
+ this.pnwBody.append(this.pnwRenderHighRisk(model, busy));
184
+ }
185
+ if (this.pnwCancelButton)
186
+ this.pnwCancelButton.textContent = model.cancelLabel;
187
+ if (this.pnwPreviewButton) {
188
+ this.pnwPreviewButton.textContent = model.preview.state === "loading" ? "预览中…" : model.previewLabel;
189
+ this.pnwPreviewButton.disabled = busy || !model.previewEnabled || !this.pnwRequestReady();
190
+ this.pnwPreviewButton.title = this.pnwPreviewButton.disabled
191
+ ? model.previewDisabledReason ?? "请选择清理方式和目标"
192
+ : "";
193
+ }
194
+ if (this.pnwExecuteButton) {
195
+ this.pnwExecuteButton.textContent = model.preview.state === "executing" ? "清理中…" : model.executeLabel;
196
+ this.pnwExecuteButton.disabled = busy
197
+ || !model.executeEnabled
198
+ || model.preview.state !== "ready"
199
+ || !model.preview.token
200
+ || !this.pnwRequestReady()
201
+ || (selectedMode?.risk === "high" && model.requireHighRiskConfirmation !== false && !this.pnwHighRiskConfirmed);
202
+ this.pnwExecuteButton.title = this.pnwExecuteButton.disabled
203
+ ? model.executeDisabledReason ?? "请先预览并确认"
204
+ : "";
205
+ }
206
+ }
207
+ pnwRenderMode(model, selectedMode, busy) {
208
+ const field = this.pnwField("清理方式");
209
+ const select = document.createElement("select");
210
+ select.className = "pnw-cleanup-mode";
211
+ select.disabled = busy;
212
+ select.setAttribute("aria-label", "清理方式");
213
+ for (const mode of model.modes) {
214
+ const option = document.createElement("option");
215
+ option.value = mode.id;
216
+ option.textContent = `${mode.label}${mode.risk === "high" ? " · 高风险" : ""}`;
217
+ option.selected = mode.id === model.selectedModeId;
218
+ select.append(option);
219
+ }
220
+ select.onchange = () => {
221
+ this.pnwActivateMode(select.value, true);
222
+ };
223
+ field.append(select);
224
+ if (selectedMode?.description) {
225
+ const description = document.createElement("p");
226
+ description.className = "pnw-cleanup-mode-description";
227
+ description.textContent = selectedMode.description;
228
+ field.append(description);
229
+ }
230
+ return field;
231
+ }
232
+ pnwRenderTargets(model, busy) {
233
+ const field = this.pnwField("清理目标");
234
+ const targets = document.createElement("div");
235
+ targets.className = "pnw-cleanup-targets";
236
+ const visibleTargets = model.targets.filter((target) => (!target.supportedModeIds?.length || target.supportedModeIds.includes(model.selectedModeId ?? "")));
237
+ for (const target of visibleTargets) {
238
+ const label = document.createElement("label");
239
+ label.className = "pnw-cleanup-target";
240
+ const checkbox = document.createElement("input");
241
+ checkbox.type = "checkbox";
242
+ checkbox.checked = target.selected;
243
+ checkbox.disabled = busy || Boolean(target.disabled);
244
+ checkbox.title = target.disabledReason ?? "";
245
+ checkbox.onchange = () => {
246
+ const nextTargets = this.pnwActiveModel.targets.map((candidate) => (candidate.id === target.id ? { ...candidate, selected: checkbox.checked } : candidate));
247
+ this.pnwHighRiskConfirmed = false;
248
+ this.pnwActiveModel = pnwNormalizeCleanupDialogModel({
249
+ ...this.pnwActiveModel,
250
+ targets: nextTargets,
251
+ preview: { state: "idle", items: [] },
252
+ });
253
+ this.pnwEmit({ kind: "toggle-target", targetId: target.id, selected: checkbox.checked });
254
+ this.pnwRender();
255
+ };
256
+ const main = document.createElement("span");
257
+ main.className = "pnw-cleanup-target-main";
258
+ const name = document.createElement("span");
259
+ name.className = "pnw-cleanup-target-label";
260
+ name.textContent = target.label;
261
+ const path = document.createElement("span");
262
+ path.className = "pnw-cleanup-target-path";
263
+ path.textContent = target.path;
264
+ path.title = target.path;
265
+ main.append(name, document.createElement("br"), path);
266
+ if (target.description) {
267
+ const description = document.createElement("span");
268
+ description.className = "pnw-cleanup-target-description";
269
+ description.textContent = target.description;
270
+ main.append(document.createElement("br"), description);
271
+ }
272
+ label.append(checkbox, main);
273
+ targets.append(label);
274
+ }
275
+ if (!visibleTargets.length) {
276
+ const empty = document.createElement("p");
277
+ empty.className = "pnw-cleanup-message";
278
+ empty.textContent = "当前方式没有可用目标。";
279
+ targets.append(empty);
280
+ }
281
+ field.append(targets);
282
+ return field;
283
+ }
284
+ pnwActivateMode(modeId, emit) {
285
+ const nextTargets = this.pnwActiveModel.targets.map((target) => ({
286
+ ...target,
287
+ selected: !target.disabled
288
+ && (!target.supportedModeIds?.length || target.supportedModeIds.includes(modeId)),
289
+ }));
290
+ this.pnwHighRiskConfirmed = false;
291
+ this.pnwActiveModel = pnwNormalizeCleanupDialogModel({
292
+ ...this.pnwActiveModel,
293
+ selectedModeId: modeId,
294
+ targets: nextTargets,
295
+ preview: { state: "idle", items: [] },
296
+ });
297
+ if (emit)
298
+ this.pnwEmit({ kind: "change-mode", modeId });
299
+ this.pnwRender();
300
+ }
301
+ pnwRenderRules(model, busy) {
302
+ const field = this.pnwField(model.rulesLabel);
303
+ const rules = document.createElement("textarea");
304
+ rules.className = "pnw-cleanup-rules";
305
+ rules.value = model.rulesYaml;
306
+ rules.disabled = busy;
307
+ rules.spellcheck = false;
308
+ rules.setAttribute("aria-label", model.rulesLabel);
309
+ rules.oninput = () => {
310
+ this.pnwHighRiskConfirmed = false;
311
+ this.pnwActiveModel = pnwNormalizeCleanupDialogModel({
312
+ ...this.pnwActiveModel,
313
+ rulesYaml: rules.value,
314
+ preview: { state: "idle", items: [] },
315
+ });
316
+ this.pnwEmit({ kind: "change-rules", rulesYaml: rules.value });
317
+ };
318
+ field.append(rules);
319
+ return field;
320
+ }
321
+ pnwRenderPreview(model) {
322
+ const field = this.pnwField("预览结果");
323
+ const preview = document.createElement("section");
324
+ preview.className = "pnw-cleanup-preview";
325
+ preview.setAttribute("aria-live", "polite");
326
+ if (model.preview.summary) {
327
+ const summary = document.createElement("p");
328
+ summary.className = "pnw-cleanup-preview-summary";
329
+ summary.textContent = model.preview.summary;
330
+ preview.append(summary);
331
+ }
332
+ if (model.preview.message || !model.preview.items.length) {
333
+ const message = document.createElement("p");
334
+ message.className = "pnw-cleanup-message";
335
+ message.dataset.error = String(model.preview.state === "error");
336
+ message.textContent = model.preview.message ?? pnwCleanupPreviewFallback(model.preview.state);
337
+ preview.append(message);
338
+ }
339
+ if (model.preview.items.length) {
340
+ const items = document.createElement("ol");
341
+ items.className = "pnw-cleanup-items";
342
+ for (const value of model.preview.items) {
343
+ const item = document.createElement("li");
344
+ item.textContent = value;
345
+ item.title = value;
346
+ items.append(item);
347
+ }
348
+ preview.append(items);
349
+ }
350
+ field.append(preview);
351
+ return field;
352
+ }
353
+ pnwRenderHighRisk(model, busy) {
354
+ const label = document.createElement("label");
355
+ label.className = "pnw-cleanup-confirm";
356
+ const checkbox = document.createElement("input");
357
+ checkbox.type = "checkbox";
358
+ checkbox.checked = this.pnwHighRiskConfirmed;
359
+ checkbox.disabled = busy || model.preview.state !== "ready" || !model.preview.token;
360
+ checkbox.onchange = () => {
361
+ this.pnwHighRiskConfirmed = checkbox.checked;
362
+ this.pnwRender();
363
+ };
364
+ const text = document.createElement("span");
365
+ text.textContent = model.highRiskConfirmationLabel;
366
+ label.append(checkbox, text);
367
+ return label;
368
+ }
369
+ pnwField(labelText) {
370
+ const field = document.createElement("div");
371
+ field.className = "pnw-cleanup-field";
372
+ const label = document.createElement("div");
373
+ label.className = "pnw-cleanup-label";
374
+ label.textContent = labelText;
375
+ field.append(label);
376
+ return field;
377
+ }
378
+ pnwButton(className, action) {
379
+ const button = document.createElement("button");
380
+ button.type = "button";
381
+ button.className = `pnw-cleanup-button ${className}`;
382
+ button.onclick = action;
383
+ return button;
384
+ }
385
+ pnwRequest() {
386
+ const modeId = this.pnwActiveModel.selectedModeId;
387
+ if (!modeId)
388
+ return undefined;
389
+ const targetIds = this.pnwActiveModel.targets
390
+ .filter((target) => target.selected
391
+ && !target.disabled
392
+ && (!target.supportedModeIds?.length || target.supportedModeIds.includes(modeId)))
393
+ .map(({ id }) => id);
394
+ if (!targetIds.length)
395
+ return undefined;
396
+ return Object.freeze({ modeId, targetIds: Object.freeze(targetIds), rulesYaml: this.pnwActiveModel.rulesYaml });
397
+ }
398
+ pnwRequestReady() { return Boolean(this.pnwRequest()); }
399
+ pnwEmitPreview() {
400
+ const request = this.pnwRequest();
401
+ if (!request || !this.pnwActiveModel.previewEnabled)
402
+ return;
403
+ this.pnwHighRiskConfirmed = false;
404
+ this.pnwEmit({ kind: "preview", request });
405
+ }
406
+ pnwEmitExecute() {
407
+ const request = this.pnwRequest();
408
+ const token = this.pnwActiveModel.preview.token;
409
+ const selectedMode = this.pnwActiveModel.modes.find(({ id }) => id === request?.modeId);
410
+ if (!request || !token || !this.pnwActiveModel.executeEnabled
411
+ || this.pnwActiveModel.preview.state !== "ready"
412
+ || (selectedMode?.risk === "high" && this.pnwActiveModel.requireHighRiskConfirmation !== false && !this.pnwHighRiskConfirmed))
413
+ return;
414
+ this.pnwEmit({ kind: "execute", request, previewToken: token });
415
+ }
416
+ pnwCancel() {
417
+ this.close();
418
+ this.pnwEmit({ kind: "cancel" });
419
+ }
420
+ pnwEmit(detail) {
421
+ this.dispatchEvent(new CustomEvent(PNW_CLEANUP_DIALOG_ACTION, {
422
+ bubbles: true,
423
+ composed: true,
424
+ detail,
425
+ }));
426
+ }
427
+ }
428
+ export function pnwNormalizeCleanupDialogModel(value) {
429
+ if (!value)
430
+ return PNW_EMPTY_CLEANUP_DIALOG_MODEL;
431
+ const modeIds = new Set();
432
+ const modes = Object.freeze(value.modes.flatMap((mode) => {
433
+ const id = mode.id.trim();
434
+ if (!id || modeIds.has(id))
435
+ return [];
436
+ modeIds.add(id);
437
+ return [Object.freeze({ ...mode, id, label: mode.label.trim() || id })];
438
+ }));
439
+ const selectedModeId = value.selectedModeId && modeIds.has(value.selectedModeId)
440
+ ? value.selectedModeId
441
+ : modes[0]?.id;
442
+ const targetIds = new Set();
443
+ const targets = Object.freeze(value.targets.flatMap((target) => {
444
+ const id = target.id.trim();
445
+ if (!id || targetIds.has(id))
446
+ return [];
447
+ targetIds.add(id);
448
+ return [Object.freeze({
449
+ ...target,
450
+ id,
451
+ label: target.label.trim() || id,
452
+ path: target.path.trim(),
453
+ supportedModeIds: target.supportedModeIds
454
+ ? Object.freeze([...new Set(target.supportedModeIds.filter((modeId) => modeIds.has(modeId)))])
455
+ : undefined,
456
+ })];
457
+ }));
458
+ const preview = Object.freeze({
459
+ ...value.preview,
460
+ items: Object.freeze([...value.preview.items]),
461
+ });
462
+ return Object.freeze({
463
+ ...value,
464
+ title: value.title.trim() || "清理",
465
+ modes,
466
+ selectedModeId,
467
+ targets,
468
+ preview,
469
+ requireHighRiskConfirmation: value.requireHighRiskConfirmation !== false,
470
+ });
471
+ }
472
+ export function pnwCodeDefineCleanupDialog(tagName = PNW_CLEANUP_DIALOG_TAG) {
473
+ if (!customElements.get(tagName))
474
+ customElements.define(tagName, PnwCleanupDialog);
475
+ }
476
+ function pnwCleanupPreviewFallback(state) {
477
+ if (state === "loading")
478
+ return "正在生成预览…";
479
+ if (state === "executing")
480
+ return "正在执行清理…";
481
+ if (state === "complete")
482
+ return "清理已完成。";
483
+ if (state === "error")
484
+ return "清理预览失败。";
485
+ if (state === "ready")
486
+ return "未匹配到需要清理的项目。";
487
+ return "先选择清理方式和目标,再预览实际改动。";
488
+ }
489
+ //# sourceMappingURL=PnwCleanupDialog.js.map