@lkzc19/dsh-workspace-virtual-groups 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1051 @@
1
+ window.__ModuleLoader__.load({ id: "@lkzc19/dsh-workspace-virtual-groups", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ apply: () => apply,
24
+ inject: () => inject
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/core/types.ts
29
+ var DEFAULT_GROUP = "\u9ED8\u8BA4";
30
+
31
+ // src/core/grouping.ts
32
+ function effectiveCategories(manual) {
33
+ const entries = [{ key: DEFAULT_GROUP, source: "builtin" }];
34
+ const seen = /* @__PURE__ */ new Set([DEFAULT_GROUP]);
35
+ for (const name of manual?.categories ?? []) {
36
+ if (seen.has(name)) continue;
37
+ seen.add(name);
38
+ entries.push({ key: name, source: "manual" });
39
+ }
40
+ const order = manual?.categoryOrder;
41
+ if (order === void 0 || order.length === 0) return entries;
42
+ const position = new Map(order.map((key, index) => [key, index]));
43
+ const [defaultEntry, ...rest] = entries;
44
+ rest.sort((a, b) => {
45
+ const pa = position.get(a.key);
46
+ const pb = position.get(b.key);
47
+ if (pa !== void 0 && pb !== void 0) return pa - pb;
48
+ if (pa !== void 0) return -1;
49
+ if (pb !== void 0) return 1;
50
+ return 0;
51
+ });
52
+ return [defaultEntry, ...rest];
53
+ }
54
+ function displayCategoryKeys(manual) {
55
+ return effectiveCategories(manual).map((entry) => entry.key);
56
+ }
57
+ function resolveCategory(manual, workspaceId) {
58
+ const assignment = manual?.assignments[workspaceId];
59
+ if (assignment === void 0 || assignment === null) return DEFAULT_GROUP;
60
+ return manual?.categories.includes(assignment) ? assignment : DEFAULT_GROUP;
61
+ }
62
+ function takenCategoryNames(manual) {
63
+ return new Set(displayCategoryKeys(manual));
64
+ }
65
+ function moveBefore(list, id, beforeId) {
66
+ const rest = list.filter((x) => x !== id);
67
+ if (beforeId === void 0) return [...rest, id];
68
+ const index = rest.indexOf(beforeId);
69
+ if (index === -1) return [...rest, id];
70
+ rest.splice(index, 0, id);
71
+ return rest;
72
+ }
73
+ function moveAfter(list, id, afterId) {
74
+ const rest = list.filter((x) => x !== id);
75
+ if (afterId === void 0) return [...rest, id];
76
+ const index = rest.indexOf(afterId);
77
+ if (index === -1) return [...rest, id];
78
+ rest.splice(index + 1, 0, id);
79
+ return rest;
80
+ }
81
+
82
+ // inline-css:/Users/lkzc19/Projects/dsh-plugins/dsh-workspace-virtual-groups/src/client/styles.css
83
+ var styles_default = "/* Virtual group labels are the ONLY new visual element. Official project and\n session rows keep their own classes, fonts, spacing, icons and actions.\n Menus and dialogs mirror DSH's own section labels, context menu and modal\n metrics instead of inventing a separate visual language. */\n\n.wvg-list {\n display: flex !important;\n flex-direction: column;\n}\n\n/* Section label: same 36px row / 14px tertiary label as the official sidebar\n section header, plus a chevron and a group action button. */\n.wvg-group {\n position: relative;\n display: flex;\n align-items: center;\n gap: 4px;\n height: 36px;\n flex: none;\n padding: 0 8px 0 12px;\n border-radius: 12px;\n color: var(--dsw-alias-label-tertiary, #81858c);\n font: inherit;\n font-size: 14px;\n font-weight: 400;\n line-height: 20px;\n cursor: pointer;\n user-select: none;\n}\n\n.wvg-group[data-wvg-default='0'] {\n margin-top: 4px;\n}\n\n.wvg-group:hover {\n background: var(--dsw-bg-hover, rgba(127, 127, 127, 0.12));\n}\n\n.wvg-group.wvg-drop {\n background: var(--dsw-bg-selected, rgba(74, 144, 217, 0.18));\n}\n\n.wvg-chevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex: none;\n color: var(--dsw-alias-label-tertiary, #81858c);\n}\n\n.wvg-chevron svg {\n transition: transform 120ms ease;\n}\n\n.wvg-group[aria-expanded='true'] .wvg-chevron svg {\n transform: rotate(90deg);\n}\n\n.wvg-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Match the workspace row's hover-only 24px action button. */\n.wvg-more:not([data-wvg-native-icon]) {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n flex: none;\n margin: 0;\n padding: 0;\n border: none;\n border-radius: var(--dsw-radius-xs, 6px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #81858c);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .wvg-more:not([data-wvg-native-icon]):hover {\n color: var(--dsw-alias-label-primary, #0f1115);\n }\n}\n\n.wvg-group .wvg-more {\n display: none;\n margin: 0;\n}\n\n@media (hover: hover) {\n .wvg-group:hover .wvg-more,\n .wvg-group.wvg-menu-open .wvg-more,\n .wvg-group:focus-within .wvg-more {\n display: inline-flex;\n }\n}\n\n@media (hover: none) {\n .wvg-group .wvg-more {\n display: inline-flex;\n }\n}\n\n.wvg-group[draggable='true'] {\n cursor: grab;\n}\n\n.wvg-group[draggable='true']:active {\n cursor: grabbing;\n}\n\n.wvg-group.wvg-dragging {\n opacity: 0.45;\n}\n\n.wvg-group.wvg-insert-before::before,\n.wvg-group.wvg-insert-after::after {\n content: '';\n position: absolute;\n left: 2px;\n right: 2px;\n height: 2px;\n border-radius: 1px;\n background: var(--dsw-accent, #4a90d9);\n pointer-events: none;\n z-index: 2;\n}\n\n.wvg-group.wvg-insert-before::before {\n top: -2px;\n}\n\n.wvg-group.wvg-insert-after::after {\n bottom: -2px;\n}\n\n/* Context menu: 16px radius surface, 4px padding, 12px rows, 13px labels. */\n.wvg-menu {\n position: fixed;\n z-index: 1100;\n min-width: 144px;\n padding: 4px;\n border: none;\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-2, var(--dsw-bg-1, #fff));\n box-shadow:\n 0 0 0 0.5px rgba(0, 0, 0, 0.04),\n 0 3px 8px 0 rgba(0, 0, 0, 0.04),\n 0 0 20px 0 rgba(0, 0, 0, 0.05);\n}\n\n.wvg-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n min-height: 32px;\n box-sizing: border-box;\n padding: 6px 8px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary, var(--dsw-fg-1, inherit));\n font: inherit;\n font-size: 13px;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n}\n\n.wvg-menu-item:hover {\n background: var(--dsw-bg-hover, rgba(127, 127, 127, 0.12));\n}\n\n.wvg-menu-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n flex: none;\n color: var(--dsw-fg-2, inherit);\n}\n\n.wvg-menu-label {\n flex: 1;\n min-width: 0;\n}\n\n.wvg-menu-item.wvg-danger {\n color: var(--dsw-alias-label-danger, #d64545);\n}\n\n.wvg-menu-item.wvg-danger .wvg-menu-icon {\n color: currentColor;\n}\n\n\n.wvg-menu-separator {\n height: 1px;\n margin: 4px 8px;\n background: var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.15));\n}\n\n/* Modal: 380px / 28px radius surface and 36px buttons, matching DSH dialogs. */\n.wvg-overlay {\n position: fixed;\n inset: 0;\n z-index: 1400;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n padding: 24px;\n background: transparent;\n}\n\n.wvg-dialog {\n display: flex;\n flex-direction: column;\n gap: 20px;\n box-sizing: border-box;\n width: 380px;\n max-width: 100%;\n padding: 0 0 24px;\n overflow: hidden;\n border: none;\n border-radius: 28px;\n background: var(--dsw-alias-bg-layer-2, #2c2c2e);\n color: var(--dsw-alias-label-primary, #f9fafb);\n box-shadow:\n 0 0 0 0.5px var(--dsw-alias-border-l4, rgba(255, 255, 255, 0.2)),\n 0 3px 8px 0 rgba(0, 0, 0, 0.04),\n 0 0 20px 0 rgba(0, 0, 0, 0.05);\n font: inherit;\n}\n\n.wvg-dialog-content {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n.wvg-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n box-sizing: border-box;\n min-height: 62px;\n padding: 22px 14px 12px 24px;\n}\n\n.wvg-dialog-title {\n min-width: 0;\n margin: 0;\n color: var(--dsw-alias-label-primary, #f9fafb);\n font-size: 16px;\n font-weight: 500;\n line-height: 24px;\n}\n\n.wvg-dialog-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex: none;\n box-sizing: border-box;\n padding: 1px 6px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n cursor: pointer;\n}\n\n.wvg-dialog-close:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n}\n\n.wvg-dialog-body {\n display: flex;\n flex-direction: column;\n padding: 0 24px;\n}\n\n.wvg-dialog-message {\n margin: 0;\n padding: 0;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font-size: 14px;\n line-height: 22px;\n}\n\n.wvg-input {\n display: block;\n box-sizing: border-box;\n width: 100%;\n height: 44px;\n margin: 0;\n padding: 7px 14px;\n border: 1px solid var(--dsw-alias-border-l4, rgba(255, 255, 255, 0.2));\n border-radius: 16px;\n outline: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f9fafb);\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n}\n\n.wvg-error {\n min-height: 0;\n margin: 8px 0 0;\n color: var(--dsw-alias-state-error-primary, #d64545);\n font-size: 12px;\n line-height: 18px;\n}\n\n.wvg-error:empty {\n display: none;\n margin: 0;\n}\n\n.wvg-dialog-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 0 24px;\n}\n\n.wvg-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f9fafb);\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n}\n\n.wvg-button.wvg-outline {\n border: 1px solid transparent;\n box-shadow: inset 0 0 0 1px var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.16));\n}\n\n.wvg-button:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n}\n\n.wvg-button.wvg-primary {\n background: var(--dsw-alias-label-primary, #f9fafb);\n color: var(--dsw-alias-label-inverse, #0f1115);\n}\n\n.wvg-button.wvg-primary:hover {\n background: var(--dsw-alias-label-primary, #f9fafb);\n opacity: 0.9;\n}\n\n.wvg-button.wvg-danger {\n background: var(--dsw-alias-state-error-primary, #d64545);\n color: #fff;\n}\n\n.wvg-button:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.wvg-button:focus-visible,\n.wvg-dialog-close:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #7aaaff);\n outline-offset: 2px;\n}\n\n.wvg-toast {\n position: fixed;\n bottom: 24px;\n left: 50%;\n z-index: 1500;\n max-width: min(440px, calc(100vw - 32px));\n padding: 8px 12px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-inverse, #222);\n color: var(--dsw-alias-label-inverse, #fff);\n font-size: 13px;\n line-height: 20px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);\n transform: translateX(-50%);\n}\n\n";
84
+
85
+ // src/client/dom-groups.ts
86
+ var MIME = "application/x-dsh-workspace-virtual-groups";
87
+ var GROUP_MIME = "application/x-dsh-workspace-virtual-group";
88
+ var LAYER_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" data-wvg-layer="1" aria-hidden="true"><path d="M8 1.8 14 5 8 8.2 2 5 8 1.8Z"></path><path d="M2 8 8 11.2 14 8"></path><path d="M2 11 8 14.2 14 11"></path></svg>';
89
+ var GROUPING = {
90
+ workspace: {
91
+ icon: "dsw-icon-folder-close-regular",
92
+ labels: ["\u6309\u5DE5\u4F5C\u533A", "By workspace"]
93
+ },
94
+ tree: {
95
+ icon: "dsw-icon-workspace-tree-outline-regular",
96
+ labels: ["\u6309\u5DE5\u4F5C\u533A\u6811", "By workspace tree"]
97
+ },
98
+ flatList: {
99
+ icon: "dsw-icon-flat-list-outline-regular",
100
+ labels: ["\u5355\u5217\u8868", "Flat list"]
101
+ }
102
+ };
103
+ var OFFICIAL_GROUPING_ICONS = new Set(
104
+ Object.values(GROUPING).map((entry) => entry.icon)
105
+ );
106
+ var OFFICIAL_GROUPING_LABELS = new Set(
107
+ Object.values(GROUPING).flatMap((entry) => entry.labels)
108
+ );
109
+ var MANUAL_ROUTE = "/workspace-virtual-groups/manual";
110
+ var COLLAPSED_KEY = "dsh.workspace.virtual-groups.collapsed.v1";
111
+ var GROUP_INDENT = "20px";
112
+ var EMPTY_MANUAL = {
113
+ categories: [],
114
+ assignments: {},
115
+ categoryOrder: [],
116
+ mode: "virtual"
117
+ };
118
+ function normalizeManual(manual) {
119
+ if (manual === void 0) return EMPTY_MANUAL;
120
+ const categories = [...new Set(manual.categories)].filter((name) => name !== DEFAULT_GROUP);
121
+ const categorySet = new Set(categories);
122
+ const assignments = Object.fromEntries(
123
+ Object.entries(manual.assignments).map(([id, category]) => [
124
+ id,
125
+ category === null || !categorySet.has(category) ? null : category
126
+ ])
127
+ );
128
+ return {
129
+ categories,
130
+ assignments,
131
+ categoryOrder: (manual.categoryOrder ?? []).filter((key) => categorySet.has(key)),
132
+ mode: manual.mode === "official" ? "official" : "virtual"
133
+ };
134
+ }
135
+ async function loadState() {
136
+ const response = await fetch(MANUAL_ROUTE, { cache: "no-cache" });
137
+ if (!response.ok) throw new Error(`manual request failed: ${response.status}`);
138
+ return normalizeManual(await response.json());
139
+ }
140
+ async function putManual(manual) {
141
+ const response = await fetch(MANUAL_ROUTE, {
142
+ method: "PUT",
143
+ headers: { "Content-Type": "application/json" },
144
+ body: JSON.stringify(manual)
145
+ });
146
+ if (response.ok) return;
147
+ let message = `\u4FDD\u5B58\u5931\u8D25\uFF08${response.status}\uFF09`;
148
+ try {
149
+ const text = await response.text();
150
+ if (text !== "") message = text;
151
+ } catch {
152
+ }
153
+ throw new Error(message);
154
+ }
155
+ function readCollapsed() {
156
+ try {
157
+ const raw = window.localStorage.getItem(COLLAPSED_KEY);
158
+ if (raw === null) return /* @__PURE__ */ new Set();
159
+ const parsed = JSON.parse(raw);
160
+ return new Set(Array.isArray(parsed) ? parsed.filter((key) => typeof key === "string") : []);
161
+ } catch {
162
+ return /* @__PURE__ */ new Set();
163
+ }
164
+ }
165
+ function writeCollapsed(collapsed) {
166
+ try {
167
+ window.localStorage.setItem(COLLAPSED_KEY, JSON.stringify([...collapsed]));
168
+ } catch {
169
+ }
170
+ }
171
+ function svgIcon(id, size) {
172
+ return `<svg width="${size}" height="${size}" aria-hidden="true"><use href="#${id}"></use></svg>`;
173
+ }
174
+ function mountWorkspaceVirtualGroups(ctx) {
175
+ const style = document.createElement("style");
176
+ style.dataset.plugin = "dsh-workspace-virtual-groups";
177
+ style.textContent = styles_default;
178
+ document.head.append(style);
179
+ let disposed = false;
180
+ let manual = EMPTY_MANUAL;
181
+ let loaded = false;
182
+ let list = null;
183
+ let viewMode = "virtual";
184
+ let suppressViewModeClick = false;
185
+ let lastSelectedExtra = [];
186
+ let lastCheckHTML = null;
187
+ let addingWorkspace = false;
188
+ let frame = 0;
189
+ let menu = null;
190
+ let menuAnchor = null;
191
+ let menuPointerClose = null;
192
+ let menuKeyClose = null;
193
+ let modal = null;
194
+ let modalKeyHandler = null;
195
+ let toastTimer = 0;
196
+ let dragId = null;
197
+ let draggingGroup = null;
198
+ let saveQueue = Promise.resolve();
199
+ const groupedHeaders = /* @__PURE__ */ new Map();
200
+ const collapsed = readCollapsed();
201
+ const originalIndent = /* @__PURE__ */ new WeakMap();
202
+ const viewMenuMeta = /* @__PURE__ */ new WeakMap();
203
+ const headerActionsMeta = /* @__PURE__ */ new WeakMap();
204
+ const headerActionsTracked = /* @__PURE__ */ new Set();
205
+ function schedule() {
206
+ if (disposed || frame !== 0) return;
207
+ frame = window.requestAnimationFrame(() => {
208
+ frame = 0;
209
+ if (!disposed) reconcile();
210
+ });
211
+ }
212
+ function findList() {
213
+ const row = document.querySelector('[data-row-key^="workspace:"]');
214
+ return row?.closest('[role="tree"]') ?? null;
215
+ }
216
+ function sectionsOf(target) {
217
+ return Array.from(target.children).filter(
218
+ (element) => element instanceof HTMLElement && element.querySelector(':scope [data-row-key^="workspace:"]') !== null
219
+ );
220
+ }
221
+ function sectionWorkspaceId(section) {
222
+ const key = section.querySelector('[data-row-key^="workspace:"]')?.getAttribute("data-row-key") ?? "";
223
+ return key.startsWith("workspace:") ? key.slice("workspace:".length) : void 0;
224
+ }
225
+ function markList(target) {
226
+ target.classList.add("wvg-list");
227
+ target.dataset.wvgList = "1";
228
+ }
229
+ function clearSections(target) {
230
+ for (const section of sectionsOf(target)) {
231
+ section.style.order = "";
232
+ section.style.display = "";
233
+ const original = originalIndent.get(section);
234
+ if (original !== void 0 && section.style.getPropertyValue("--dsh-workspace-indent") === GROUP_INDENT) {
235
+ if (original === "") section.style.removeProperty("--dsh-workspace-indent");
236
+ else section.style.setProperty("--dsh-workspace-indent", original);
237
+ }
238
+ originalIndent.delete(section);
239
+ delete section.dataset.wvgIndent;
240
+ }
241
+ }
242
+ function clearHeaders() {
243
+ for (const header of groupedHeaders.values()) header.remove();
244
+ groupedHeaders.clear();
245
+ }
246
+ function cleanupList() {
247
+ if (list === null) return;
248
+ list.classList.remove("wvg-list");
249
+ delete list.dataset.wvgList;
250
+ clearSections(list);
251
+ clearHeaders();
252
+ }
253
+ function openCreateGroupDialog() {
254
+ openNameDialog("\u65B0\u5EFA\u5206\u7EC4", "", "\u521B\u5EFA", async (name) => {
255
+ const taken = takenCategoryNames(manual);
256
+ if (name === "" || name === DEFAULT_GROUP) return "\u8BE5\u540D\u79F0\u4E0D\u53EF\u7528";
257
+ if (taken.has(name)) return "\u5DF2\u5B58\u5728\u540C\u540D\u5206\u7EC4";
258
+ await persist({ ...manual, categories: [...manual.categories, name] });
259
+ return null;
260
+ });
261
+ }
262
+ function restoreLegacyAddButton() {
263
+ document.querySelectorAll("button[data-wvg-add-group]").forEach((button) => {
264
+ delete button.dataset.wvgAddGroup;
265
+ button.setAttribute("aria-label", "\u6DFB\u52A0\u5DE5\u4F5C\u533A");
266
+ button.setAttribute("aria-keyshortcuts", "Alt+Meta+O");
267
+ const svg = button.querySelector("svg");
268
+ const use = button.querySelector("use");
269
+ if (svg !== null) svg.setAttribute("data-icon", "dsw-icon-project-add-outline-regular");
270
+ if (use !== null) use.setAttribute("href", "#dsw-icon-project-add-outline-regular");
271
+ });
272
+ }
273
+ function officialAddButton() {
274
+ const use = document.querySelector('use[href="#dsw-icon-project-add-outline-regular"]');
275
+ const button = use?.closest("button");
276
+ return button instanceof HTMLButtonElement ? button : null;
277
+ }
278
+ function restoreHeaderActions(actions) {
279
+ if (actions === null) return;
280
+ const original = headerActionsMeta.get(actions);
281
+ if (original === void 0) return;
282
+ actions.style.width = original.width;
283
+ actions.style.minWidth = original.minWidth;
284
+ actions.style.overflow = original.overflow;
285
+ headerActionsMeta.delete(actions);
286
+ headerActionsTracked.delete(actions);
287
+ }
288
+ function removeNewGroupButtons() {
289
+ document.querySelectorAll("[data-wvg-new-group]").forEach((node) => {
290
+ const actions = node.parentElement;
291
+ node.remove();
292
+ restoreHeaderActions(actions);
293
+ });
294
+ for (const actions of [...headerActionsTracked]) restoreHeaderActions(actions);
295
+ }
296
+ function ensureNewGroupButton() {
297
+ restoreLegacyAddButton();
298
+ if (viewMode !== "virtual") {
299
+ removeNewGroupButtons();
300
+ return;
301
+ }
302
+ const add = officialAddButton();
303
+ if (add === null) {
304
+ removeNewGroupButtons();
305
+ return;
306
+ }
307
+ let button = document.querySelector("button[data-wvg-new-group]");
308
+ if (button !== null && button.dataset.wvgVersion !== "2") {
309
+ const oldActions = button.parentElement;
310
+ button.remove();
311
+ restoreHeaderActions(oldActions);
312
+ button = null;
313
+ }
314
+ if (button === null || !button.isConnected) {
315
+ button = add.cloneNode(true);
316
+ button.dataset.wvgNewGroup = "1";
317
+ button.dataset.wvgVersion = "2";
318
+ add.after(button);
319
+ }
320
+ if (button.previousElementSibling !== add) add.after(button);
321
+ button.setAttribute("aria-label", "\u65B0\u5EFA\u5206\u7EC4");
322
+ button.removeAttribute("aria-keyshortcuts");
323
+ button.removeAttribute("aria-expanded");
324
+ if (button.querySelector('svg[data-wvg-layer="1"]') === null) button.innerHTML = LAYER_ICON;
325
+ button.onclick = (event) => {
326
+ event.preventDefault();
327
+ event.stopPropagation();
328
+ openCreateGroupDialog();
329
+ };
330
+ const actions = button.parentElement;
331
+ if (actions !== null) {
332
+ if (!headerActionsMeta.has(actions)) {
333
+ headerActionsMeta.set(actions, {
334
+ width: actions.style.width,
335
+ minWidth: actions.style.minWidth,
336
+ overflow: actions.style.overflow
337
+ });
338
+ headerActionsTracked.add(actions);
339
+ }
340
+ actions.style.width = "max-content";
341
+ actions.style.minWidth = "max-content";
342
+ actions.style.overflow = "visible";
343
+ }
344
+ }
345
+ function groupingIcon(button) {
346
+ const svg = button.querySelector(":scope > span svg[data-icon]");
347
+ return svg?.getAttribute("data-icon") ?? null;
348
+ }
349
+ function groupingLabel(button) {
350
+ return (button.textContent ?? "").trim();
351
+ }
352
+ function isOfficialGroupingButton(button) {
353
+ const icon = groupingIcon(button);
354
+ if (icon !== null && OFFICIAL_GROUPING_ICONS.has(icon)) return true;
355
+ return OFFICIAL_GROUPING_LABELS.has(groupingLabel(button));
356
+ }
357
+ function findGroupingButton(menu2, group) {
358
+ const buttons = [...menu2.querySelectorAll('button[role="menuitem"]')];
359
+ const labels = group.labels;
360
+ return buttons.find((button) => groupingIcon(button) === group.icon) ?? buttons.find((button) => labels.includes(groupingLabel(button))) ?? null;
361
+ }
362
+ function virtualGroupingLabel(menu2) {
363
+ const workspace = findGroupingButton(menu2, GROUPING.workspace);
364
+ const label = workspace === null ? "" : groupingLabel(workspace);
365
+ return label.includes("\u6309") || label.includes("\u5DE5\u4F5C\u533A") ? "\u6309\u865A\u62DF\u5206\u7EC4" : "Virtual groups";
366
+ }
367
+ function findViewOptionsMenu() {
368
+ for (const menu2 of document.querySelectorAll('[role="menu"]')) {
369
+ if (menu2.className.includes("viewOptionsMenu")) return menu2;
370
+ if ([...menu2.querySelectorAll('button[role="menuitem"]')].some(isOfficialGroupingButton)) {
371
+ return menu2;
372
+ }
373
+ }
374
+ return null;
375
+ }
376
+ function closeViewOptions() {
377
+ if (findViewOptionsMenu() === null) return;
378
+ const use = document.querySelector('use[href="#dsw-icon-sliders-two-outline-regular"]');
379
+ const trigger = use?.closest("button");
380
+ if (trigger instanceof HTMLButtonElement) trigger.click();
381
+ else document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
382
+ }
383
+ function ensureViewOptions() {
384
+ const menu2 = findViewOptionsMenu();
385
+ if (menu2 === null) return;
386
+ const isOfficial = isOfficialGroupingButton;
387
+ let ours = menu2.querySelector("button[data-wvg-view-virtual]");
388
+ if (ours === null) {
389
+ const tree = findGroupingButton(menu2, GROUPING.tree);
390
+ const wrap = tree?.parentElement;
391
+ if (tree === null || wrap === void 0 || wrap === null) return;
392
+ const official = [...menu2.querySelectorAll('button[role="menuitem"]')].filter(isOfficial);
393
+ const unselected = official.find((button2) => button2.querySelector(":scope > svg") === null) ?? tree;
394
+ const selected = official.find((button2) => button2.querySelector(":scope > svg") !== null);
395
+ const baseClass = unselected.className;
396
+ const selectedExtra = selected === void 0 ? [] : [...selected.classList].filter((name) => !unselected.classList.contains(name));
397
+ const checkHTML = selected?.querySelector(":scope > svg")?.outerHTML ?? null;
398
+ if (selectedExtra.length > 0) lastSelectedExtra = selectedExtra;
399
+ if (checkHTML !== null) lastCheckHTML = checkHTML;
400
+ const clone = wrap.cloneNode(true);
401
+ clone.dataset.wvgViewWrap = "1";
402
+ const button = clone.querySelector('button[role="menuitem"]');
403
+ if (button === null) return;
404
+ button.dataset.wvgViewVirtual = "1";
405
+ button.className = baseClass;
406
+ const label = [...button.querySelectorAll(":scope > span")].find((span) => span.querySelector("svg") === null && (span.textContent ?? "").trim() !== "");
407
+ if (label !== void 0) label.textContent = virtualGroupingLabel(menu2);
408
+ const icon = button.querySelector('[class*="itemIcon"]') ?? [...button.querySelectorAll("span")].find((span) => span.querySelector("svg") !== null);
409
+ if (icon !== void 0) icon.innerHTML = LAYER_ICON;
410
+ button.querySelector(":scope > svg")?.remove();
411
+ wrap.after(clone);
412
+ viewMenuMeta.set(button, {
413
+ baseClass,
414
+ selectedExtra: selectedExtra.length > 0 ? selectedExtra : lastSelectedExtra,
415
+ check: checkHTML ?? lastCheckHTML
416
+ });
417
+ button.addEventListener("click", (event) => {
418
+ event.preventDefault();
419
+ event.stopPropagation();
420
+ activateVirtualMode(menu2);
421
+ });
422
+ ours = button;
423
+ }
424
+ if (menu2.dataset.wvgModeListener !== "1") {
425
+ menu2.dataset.wvgModeListener = "1";
426
+ menu2.addEventListener("click", (event) => {
427
+ if (suppressViewModeClick) return;
428
+ const button = event.target?.closest('button[role="menuitem"]');
429
+ if (button === null || button === void 0) return;
430
+ if (!isOfficial(button)) return;
431
+ setViewMode("official");
432
+ }, true);
433
+ }
434
+ const meta = viewMenuMeta.get(ours) ?? { baseClass: ours.className, selectedExtra: lastSelectedExtra, check: lastCheckHTML };
435
+ if (viewMode === "virtual") {
436
+ for (const button of menu2.querySelectorAll('button[role="menuitem"]')) {
437
+ if (button === ours || !isOfficial(button)) continue;
438
+ if (meta.selectedExtra.length > 0) button.classList.remove(...meta.selectedExtra);
439
+ button.querySelector(":scope > svg")?.remove();
440
+ }
441
+ ours.className = meta.selectedExtra.length > 0 ? `${meta.baseClass} ${meta.selectedExtra.join(" ")}` : meta.baseClass;
442
+ if (meta.check !== null && ours.querySelector(":scope > svg") === null) {
443
+ const holder = document.createElement("template");
444
+ holder.innerHTML = meta.check;
445
+ const check = holder.content.firstElementChild;
446
+ if (check !== null) ours.append(check);
447
+ }
448
+ } else {
449
+ ours.className = meta.baseClass;
450
+ ours.querySelector(":scope > svg")?.remove();
451
+ }
452
+ }
453
+ function setViewMode(mode) {
454
+ if (!loaded) return;
455
+ if (viewMode === mode) {
456
+ schedule();
457
+ return;
458
+ }
459
+ viewMode = mode;
460
+ manual = { ...manual, mode };
461
+ schedule();
462
+ void persist(manual).catch((reason) => {
463
+ showToast(reason instanceof Error ? reason.message : String(reason));
464
+ });
465
+ }
466
+ function activateVirtualMode(menu2) {
467
+ setViewMode("virtual");
468
+ const byWorkspace = findGroupingButton(menu2, GROUPING.workspace);
469
+ if (byWorkspace === null) {
470
+ closeViewOptions();
471
+ return;
472
+ }
473
+ suppressViewModeClick = true;
474
+ byWorkspace.click();
475
+ window.setTimeout(() => {
476
+ suppressViewModeClick = false;
477
+ if (menu2.isConnected) closeViewOptions();
478
+ }, 0);
479
+ }
480
+ function createGroupHeader(key) {
481
+ const header = document.createElement("div");
482
+ header.className = "wvg-group";
483
+ header.dataset.wvgGroup = key;
484
+ header.dataset.wvgDefault = key === DEFAULT_GROUP ? "1" : "0";
485
+ header.setAttribute("role", "treeitem");
486
+ header.setAttribute("aria-expanded", "true");
487
+ header.setAttribute("aria-label", key);
488
+ header.draggable = key !== DEFAULT_GROUP;
489
+ header.innerHTML = `<span class="wvg-chevron">${svgIcon("dsw-icon-triangle-right-fill-regular", 14)}</span><span class="wvg-label"></span><button type="button" class="wvg-more" aria-label="\u5206\u7EC4\u64CD\u4F5C" aria-haspopup="menu">${svgIcon("dsw-icon-ellipsis-outline-regular", 16)}</button>`;
490
+ header.querySelector(".wvg-more")?.setAttribute("aria-label", `\u5206\u7EC4\u201C${key}\u201D\u7684\u64CD\u4F5C`);
491
+ header.addEventListener("dragstart", (event) => {
492
+ if (key === DEFAULT_GROUP) {
493
+ event.preventDefault();
494
+ return;
495
+ }
496
+ if (event.target?.closest(".wvg-more") !== null) {
497
+ event.preventDefault();
498
+ return;
499
+ }
500
+ draggingGroup = key;
501
+ try {
502
+ event.dataTransfer?.setData(GROUP_MIME, key);
503
+ if (event.dataTransfer !== null) event.dataTransfer.effectAllowed = "move";
504
+ } catch {
505
+ }
506
+ header.classList.add("wvg-dragging");
507
+ });
508
+ header.addEventListener("dragend", () => {
509
+ clearDrag();
510
+ });
511
+ header.addEventListener("click", (event) => {
512
+ if (event.target?.closest(".wvg-more") !== null) return;
513
+ toggleGroup(key);
514
+ });
515
+ header.addEventListener("contextmenu", (event) => {
516
+ event.preventDefault();
517
+ openGroupMenu(key, header, event.clientX, event.clientY);
518
+ });
519
+ header.addEventListener("dragover", (event) => onGroupDragOver(event, header));
520
+ header.addEventListener("dragleave", (event) => onGroupDragLeave(event, header));
521
+ header.addEventListener("drop", (event) => onGroupDrop(event, key, header));
522
+ header.querySelector(".wvg-more")?.addEventListener("click", (event) => {
523
+ event.stopPropagation();
524
+ const rect = event.currentTarget.getBoundingClientRect();
525
+ openGroupMenu(key, event.currentTarget, rect.left, rect.bottom + 4);
526
+ });
527
+ return header;
528
+ }
529
+ function applyNativeIconClasses() {
530
+ const rowAction = document.querySelector('[data-row-key^="workspace:"] [data-context-menu-trigger]');
531
+ const nativeClass = rowAction?.className ?? officialAddButton()?.className ?? "";
532
+ if (nativeClass === "") return;
533
+ document.querySelectorAll(".wvg-more").forEach((button) => {
534
+ const previous = button.dataset.wvgNativeClass ?? "";
535
+ if (previous === nativeClass) return;
536
+ for (const name of previous.split(/\s+/).filter(Boolean)) button.classList.remove(name);
537
+ button.classList.add(...nativeClass.split(/\s+/).filter(Boolean));
538
+ button.dataset.wvgNativeClass = nativeClass;
539
+ button.dataset.wvgNativeIcon = "1";
540
+ });
541
+ }
542
+ function reconcile() {
543
+ if (disposed) return;
544
+ if (!loaded) {
545
+ cleanupList();
546
+ removeNewGroupButtons();
547
+ return;
548
+ }
549
+ ensureViewOptions();
550
+ ensureNewGroupButton();
551
+ if (viewMode !== "virtual") {
552
+ cleanupList();
553
+ return;
554
+ }
555
+ const nextList = findList();
556
+ if (nextList !== list) {
557
+ cleanupList();
558
+ list = nextList;
559
+ }
560
+ if (list === null) return;
561
+ const sections = sectionsOf(list);
562
+ if (sections.length === 0) {
563
+ cleanupList();
564
+ return;
565
+ }
566
+ for (const section of sections) {
567
+ if (!originalIndent.has(section)) originalIndent.set(section, section.style.getPropertyValue("--dsh-workspace-indent"));
568
+ }
569
+ const inOfficialTree = sections.some((section) => {
570
+ const original = originalIndent.get(section) ?? "";
571
+ if (Number.parseFloat(original) > 0) return true;
572
+ return section.dataset.wvgIndent === "1" && section.style.getPropertyValue("--dsh-workspace-indent") !== GROUP_INDENT;
573
+ });
574
+ if (inOfficialTree) {
575
+ cleanupList();
576
+ return;
577
+ }
578
+ markList(list);
579
+ const members = /* @__PURE__ */ new Map();
580
+ for (const section of sections) {
581
+ const workspaceId = sectionWorkspaceId(section);
582
+ const key = workspaceId === void 0 ? DEFAULT_GROUP : resolveCategory(manual, workspaceId);
583
+ const bucket = members.get(key);
584
+ if (bucket === void 0) members.set(key, [section]);
585
+ else bucket.push(section);
586
+ }
587
+ const groups = effectiveCategories(manual);
588
+ const visible = /* @__PURE__ */ new Set();
589
+ groups.forEach((entry, groupIndex) => {
590
+ visible.add(entry.key);
591
+ let header = groupedHeaders.get(entry.key);
592
+ if (header === void 0 || !header.isConnected) {
593
+ header = createGroupHeader(entry.key);
594
+ groupedHeaders.set(entry.key, header);
595
+ list.append(header);
596
+ }
597
+ header.style.order = String(groupIndex * 1e5 - 1e3);
598
+ header.setAttribute("aria-expanded", collapsed.has(entry.key) ? "false" : "true");
599
+ const label = header.querySelector(".wvg-label");
600
+ if (label !== null) label.textContent = entry.key;
601
+ const items = members.get(entry.key) ?? [];
602
+ items.forEach((section, index) => {
603
+ section.style.order = String(groupIndex * 1e5 + index);
604
+ section.style.display = collapsed.has(entry.key) ? "none" : "";
605
+ section.style.setProperty("--dsh-workspace-indent", GROUP_INDENT);
606
+ section.dataset.wvgIndent = "1";
607
+ });
608
+ });
609
+ for (const [key, header] of groupedHeaders) {
610
+ if (visible.has(key)) continue;
611
+ header.remove();
612
+ groupedHeaders.delete(key);
613
+ }
614
+ applyNativeIconClasses();
615
+ }
616
+ function toggleGroup(key) {
617
+ if (collapsed.has(key)) collapsed.delete(key);
618
+ else collapsed.add(key);
619
+ writeCollapsed(collapsed);
620
+ schedule();
621
+ }
622
+ function onDragStart(event) {
623
+ const row = event.target?.closest('[data-row-key^="workspace:"]');
624
+ if (row === null) return;
625
+ const key = row.getAttribute("data-row-key") ?? "";
626
+ if (!key.startsWith("workspace:")) return;
627
+ dragId = key.slice("workspace:".length);
628
+ try {
629
+ event.dataTransfer?.setData(MIME, dragId);
630
+ if (event.dataTransfer !== null) event.dataTransfer.effectAllowed = "move";
631
+ } catch {
632
+ }
633
+ }
634
+ function clearDrag() {
635
+ dragId = null;
636
+ draggingGroup = null;
637
+ document.querySelectorAll(".wvg-group.wvg-dragging, .wvg-group.wvg-drop, .wvg-group.wvg-insert-before, .wvg-group.wvg-insert-after").forEach((header) => header.classList.remove("wvg-dragging", "wvg-drop", "wvg-insert-before", "wvg-insert-after"));
638
+ }
639
+ function onGroupDragOver(event, header) {
640
+ if (draggingGroup !== null) {
641
+ const key = header.dataset.wvgGroup ?? "";
642
+ if (key === DEFAULT_GROUP || key === draggingGroup) return;
643
+ event.preventDefault();
644
+ event.stopPropagation();
645
+ if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
646
+ const rect = header.getBoundingClientRect();
647
+ const before = event.clientY < rect.top + rect.height / 2;
648
+ document.querySelectorAll(".wvg-group.wvg-insert-before, .wvg-group.wvg-insert-after").forEach((node) => node.classList.remove("wvg-insert-before", "wvg-insert-after"));
649
+ header.classList.add(before ? "wvg-insert-before" : "wvg-insert-after");
650
+ return;
651
+ }
652
+ if (dragId === null) return;
653
+ event.preventDefault();
654
+ event.stopPropagation();
655
+ if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
656
+ header.classList.add("wvg-drop");
657
+ }
658
+ function onGroupDragLeave(event, header) {
659
+ if (event.relatedTarget instanceof Node && header.contains(event.relatedTarget)) return;
660
+ header.classList.remove("wvg-drop", "wvg-insert-before", "wvg-insert-after");
661
+ }
662
+ function onGroupDrop(event, key, header) {
663
+ if (draggingGroup !== null) {
664
+ if (key === DEFAULT_GROUP || key === draggingGroup) return;
665
+ event.preventDefault();
666
+ event.stopPropagation();
667
+ const rect = header.getBoundingClientRect();
668
+ const before = event.clientY < rect.top + rect.height / 2;
669
+ const dragged = draggingGroup;
670
+ clearDrag();
671
+ void reorderGroup(dragged, key, before);
672
+ return;
673
+ }
674
+ if (dragId === null) return;
675
+ event.preventDefault();
676
+ event.stopPropagation();
677
+ const workspaceId = dragId;
678
+ clearDrag();
679
+ header.classList.remove("wvg-drop");
680
+ void moveToGroup(workspaceId, key);
681
+ }
682
+ function onDocumentDragStart(event) {
683
+ onDragStart(event);
684
+ }
685
+ function onDocumentDragEnd() {
686
+ clearDrag();
687
+ }
688
+ async function addWorkspaceToGroup(key) {
689
+ if (addingWorkspace) return;
690
+ addingWorkspace = true;
691
+ try {
692
+ const path = await ctx.uiWorkspace.pickDirectory();
693
+ if (path === null) return;
694
+ const workspace = await ctx.workspaces.create({ path });
695
+ collapsed.delete(key);
696
+ writeCollapsed(collapsed);
697
+ await persist({
698
+ ...manual,
699
+ assignments: { ...manual.assignments, [workspace.workspaceId]: key === DEFAULT_GROUP ? null : key }
700
+ });
701
+ try {
702
+ ctx.uiWorkspace.startSession(workspace.workspaceId);
703
+ } catch (reason) {
704
+ showToast(reason instanceof Error ? reason.message : String(reason));
705
+ }
706
+ } catch (reason) {
707
+ showToast(reason instanceof Error ? reason.message : String(reason));
708
+ } finally {
709
+ addingWorkspace = false;
710
+ }
711
+ }
712
+ async function reorderGroup(draggedKey, targetKey, before) {
713
+ if (draggedKey === targetKey || targetKey === DEFAULT_GROUP) return;
714
+ const order = effectiveCategories(manual).map((entry) => entry.key).filter((key) => key !== DEFAULT_GROUP);
715
+ const categoryOrder = before ? moveBefore(order, draggedKey, targetKey) : moveAfter(order, draggedKey, targetKey);
716
+ try {
717
+ await persist({ ...manual, categoryOrder });
718
+ } catch (reason) {
719
+ showToast(reason instanceof Error ? reason.message : String(reason));
720
+ }
721
+ }
722
+ async function moveToGroup(workspaceId, key) {
723
+ const current = resolveCategory(manual, workspaceId);
724
+ if (current === key) return;
725
+ collapsed.delete(key);
726
+ writeCollapsed(collapsed);
727
+ try {
728
+ await persist({
729
+ ...manual,
730
+ assignments: { ...manual.assignments, [workspaceId]: key === DEFAULT_GROUP ? null : key }
731
+ });
732
+ } catch (reason) {
733
+ showToast(reason instanceof Error ? reason.message : String(reason));
734
+ }
735
+ }
736
+ function persist(next) {
737
+ const run = saveQueue.then(async () => {
738
+ await putManual(next);
739
+ manual = next;
740
+ schedule();
741
+ });
742
+ saveQueue = run.catch(() => {
743
+ });
744
+ return run;
745
+ }
746
+ function requestDeleteGroup(key) {
747
+ openConfirmDialog("\u5220\u9664\u5206\u7EC4", `\u5220\u9664\u540E\uFF0C\u8BE5\u5206\u7EC4\u5185\u7684\u9879\u76EE\u4F1A\u79FB\u5230\u300C${DEFAULT_GROUP}\u300D\u3002`, async () => {
748
+ await deleteGroup(key);
749
+ });
750
+ }
751
+ function menuItem(label, icon, danger = false) {
752
+ const button = document.createElement("button");
753
+ button.type = "button";
754
+ button.className = danger ? "wvg-menu-item wvg-danger" : "wvg-menu-item";
755
+ button.setAttribute("role", "menuitem");
756
+ button.innerHTML = `<span class="wvg-menu-icon">${svgIcon(icon, 16)}</span><span class="wvg-menu-label"></span>`;
757
+ const text = button.querySelector(".wvg-menu-label");
758
+ if (text !== null) text.textContent = label;
759
+ return button;
760
+ }
761
+ function openGroupMenu(key, anchor, x, y) {
762
+ closeMenu();
763
+ menuAnchor = anchor;
764
+ anchor.closest(".wvg-group")?.classList.add("wvg-menu-open");
765
+ const panel = document.createElement("div");
766
+ panel.className = "wvg-menu";
767
+ panel.setAttribute("role", "menu");
768
+ panel.setAttribute("aria-label", `\u5206\u7EC4\u201C${key}\u201D\u7684\u64CD\u4F5C`);
769
+ const addWorkspace = menuItem("\u6DFB\u52A0\u5DE5\u4F5C\u533A", "dsw-icon-project-add-outline-regular");
770
+ addWorkspace.addEventListener("click", () => {
771
+ closeMenu();
772
+ void addWorkspaceToGroup(key);
773
+ });
774
+ panel.append(addWorkspace);
775
+ if (key !== DEFAULT_GROUP) {
776
+ const rename = menuItem("\u91CD\u547D\u540D\u5206\u7EC4", "dsw-icon-edit-outline-regular");
777
+ rename.addEventListener("click", () => {
778
+ closeMenu();
779
+ openNameDialog("\u91CD\u547D\u540D\u5206\u7EC4", key, "\u91CD\u547D\u540D", async (name) => {
780
+ if (name === "" || name === DEFAULT_GROUP) return "\u8BE5\u540D\u79F0\u4E0D\u53EF\u7528";
781
+ if (name === key) return null;
782
+ if (takenCategoryNames(manual).has(name)) return "\u5DF2\u5B58\u5728\u540C\u540D\u5206\u7EC4";
783
+ await renameGroup(key, name);
784
+ return null;
785
+ });
786
+ });
787
+ const separator = document.createElement("div");
788
+ separator.className = "wvg-menu-separator";
789
+ separator.setAttribute("role", "separator");
790
+ const remove = menuItem("\u5220\u9664\u5206\u7EC4", "dsw-icon-trash-outline-regular", true);
791
+ remove.addEventListener("click", () => {
792
+ closeMenu();
793
+ requestDeleteGroup(key);
794
+ });
795
+ panel.append(rename, separator, remove);
796
+ }
797
+ document.body.append(panel);
798
+ const rect = panel.getBoundingClientRect();
799
+ panel.style.left = `${Math.max(8, Math.min(x, window.innerWidth - rect.width - 8))}px`;
800
+ panel.style.top = `${Math.max(8, Math.min(y, window.innerHeight - rect.height - 8))}px`;
801
+ menu = panel;
802
+ const close = (event) => {
803
+ if (panel.contains(event.target)) return;
804
+ closeMenu();
805
+ };
806
+ menuPointerClose = close;
807
+ window.setTimeout(() => {
808
+ if (menuPointerClose === close) document.addEventListener("pointerdown", close, true);
809
+ }, 0);
810
+ menuKeyClose = (event) => {
811
+ if (event.key === "Escape") closeMenu();
812
+ };
813
+ document.addEventListener("keydown", menuKeyClose, true);
814
+ panel.querySelector(".wvg-menu-item")?.focus();
815
+ }
816
+ function closeMenu() {
817
+ const openHeaders = [...document.querySelectorAll(".wvg-group.wvg-menu-open")];
818
+ if (menuPointerClose !== null) document.removeEventListener("pointerdown", menuPointerClose, true);
819
+ if (menuKeyClose !== null) document.removeEventListener("keydown", menuKeyClose, true);
820
+ menuPointerClose = null;
821
+ menuKeyClose = null;
822
+ const anchor = menuAnchor;
823
+ menuAnchor = null;
824
+ menu?.remove();
825
+ menu = null;
826
+ if (anchor?.isConnected) anchor.focus();
827
+ openHeaders.forEach((header) => header.classList.remove("wvg-menu-open"));
828
+ }
829
+ async function renameGroup(from, name) {
830
+ const assignments = Object.fromEntries(
831
+ Object.entries(manual.assignments).map(([id, category]) => [
832
+ id,
833
+ category === from ? name : category
834
+ ])
835
+ );
836
+ const categoryOrder = manual.categoryOrder.map((key) => key === from ? name : key);
837
+ await persist({
838
+ ...manual,
839
+ categories: manual.categories.map((key) => key === from ? name : key),
840
+ assignments,
841
+ categoryOrder
842
+ });
843
+ }
844
+ async function deleteGroup(key) {
845
+ if (key === DEFAULT_GROUP) return;
846
+ const assignments = { ...manual.assignments };
847
+ for (const [workspaceId, category] of Object.entries(assignments)) {
848
+ if (category === key) assignments[workspaceId] = null;
849
+ }
850
+ await persist({
851
+ ...manual,
852
+ categories: manual.categories.filter((entry) => entry !== key),
853
+ assignments,
854
+ categoryOrder: manual.categoryOrder.filter((entry) => entry !== key)
855
+ });
856
+ }
857
+ function openNameDialog(title, initial, confirmLabel, submit) {
858
+ closeModal();
859
+ const overlay = document.createElement("div");
860
+ overlay.className = "wvg-overlay";
861
+ overlay.innerHTML = `<div class="wvg-dialog" role="dialog" aria-modal="true" tabindex="-1"><div class="wvg-dialog-content"><div class="wvg-dialog-header"><h2 class="wvg-dialog-title"></h2><button type="button" class="wvg-dialog-close" aria-label="\u5173\u95ED">${svgIcon("dsw-icon-close-outline-regular", 14)}</button></div><div class="wvg-dialog-body"><input class="wvg-input" type="text" maxlength="80" aria-label="\u5206\u7EC4\u540D\u79F0" /><div class="wvg-error" role="alert"></div></div></div><div class="wvg-dialog-actions"><button type="button" class="wvg-button wvg-outline" data-wvg-cancel>\u53D6\u6D88</button><button type="button" class="wvg-button wvg-primary" data-wvg-ok></button></div></div>`;
862
+ const dialog = overlay.querySelector(".wvg-dialog");
863
+ dialog.setAttribute("aria-label", title);
864
+ dialog.querySelector(".wvg-dialog-title").textContent = title;
865
+ const input = dialog.querySelector(".wvg-input");
866
+ input.value = initial;
867
+ const error = dialog.querySelector(".wvg-error");
868
+ const ok = dialog.querySelector("[data-wvg-ok]");
869
+ ok.textContent = confirmLabel;
870
+ let busy = false;
871
+ const initialTrimmed = initial.trim();
872
+ const syncOk = () => {
873
+ if (busy) return;
874
+ const value = input.value.trim();
875
+ ok.disabled = value === "" || initialTrimmed !== "" && value === initialTrimmed;
876
+ };
877
+ syncOk();
878
+ const close = () => closeModal();
879
+ const confirm = async () => {
880
+ if (busy || ok.disabled) return;
881
+ busy = true;
882
+ ok.disabled = true;
883
+ error.textContent = "";
884
+ try {
885
+ const message = await submit(input.value.trim());
886
+ if (message !== null) {
887
+ error.textContent = message;
888
+ return;
889
+ }
890
+ close();
891
+ } catch (reason) {
892
+ error.textContent = reason instanceof Error ? reason.message : String(reason);
893
+ } finally {
894
+ busy = false;
895
+ syncOk();
896
+ }
897
+ };
898
+ dialog.querySelector("[data-wvg-cancel]").addEventListener("click", close);
899
+ dialog.querySelector(".wvg-dialog-close").addEventListener("click", close);
900
+ input.addEventListener("input", syncOk);
901
+ ok.addEventListener("click", () => {
902
+ void confirm();
903
+ });
904
+ input.addEventListener("keydown", (event) => {
905
+ if (event.key === "Enter" && !event.isComposing) {
906
+ event.preventDefault();
907
+ void confirm();
908
+ }
909
+ });
910
+ overlay.addEventListener("pointerdown", (event) => {
911
+ if (event.target === overlay) close();
912
+ });
913
+ document.body.append(overlay);
914
+ modal = overlay;
915
+ modalKeyHandler = (event) => {
916
+ if (event.key === "Escape") close();
917
+ };
918
+ document.addEventListener("keydown", modalKeyHandler);
919
+ window.requestAnimationFrame(() => {
920
+ input.focus();
921
+ input.select();
922
+ });
923
+ }
924
+ function openConfirmDialog(title, message, confirm) {
925
+ closeModal();
926
+ const overlay = document.createElement("div");
927
+ overlay.className = "wvg-overlay";
928
+ overlay.innerHTML = `<div class="wvg-dialog" role="dialog" aria-modal="true" tabindex="-1"><div class="wvg-dialog-content"><div class="wvg-dialog-header"><h2 class="wvg-dialog-title"></h2><button type="button" class="wvg-dialog-close" aria-label="\u5173\u95ED">${svgIcon("dsw-icon-close-outline-regular", 14)}</button></div><div class="wvg-dialog-body"><div class="wvg-dialog-message"></div><div class="wvg-error" role="alert"></div></div></div><div class="wvg-dialog-actions"><button type="button" class="wvg-button wvg-outline" data-wvg-cancel>\u53D6\u6D88</button><button type="button" class="wvg-button wvg-danger" data-wvg-ok>\u5220\u9664</button></div></div>`;
929
+ const dialog = overlay.querySelector(".wvg-dialog");
930
+ dialog.setAttribute("aria-label", title);
931
+ dialog.querySelector(".wvg-dialog-title").textContent = title;
932
+ dialog.querySelector(".wvg-dialog-message").textContent = message;
933
+ const error = dialog.querySelector(".wvg-error");
934
+ const ok = dialog.querySelector("[data-wvg-ok]");
935
+ let busy = false;
936
+ const close = () => closeModal();
937
+ const run = async () => {
938
+ if (busy) return;
939
+ busy = true;
940
+ ok.disabled = true;
941
+ error.textContent = "";
942
+ try {
943
+ await confirm();
944
+ close();
945
+ } catch (reason) {
946
+ error.textContent = reason instanceof Error ? reason.message : String(reason);
947
+ } finally {
948
+ busy = false;
949
+ ok.disabled = false;
950
+ }
951
+ };
952
+ dialog.querySelector("[data-wvg-cancel]").addEventListener("click", close);
953
+ dialog.querySelector(".wvg-dialog-close").addEventListener("click", close);
954
+ ok.addEventListener("click", () => {
955
+ void run();
956
+ });
957
+ overlay.addEventListener("pointerdown", (event) => {
958
+ if (event.target === overlay) close();
959
+ });
960
+ document.body.append(overlay);
961
+ modal = overlay;
962
+ modalKeyHandler = (event) => {
963
+ if (event.key === "Escape") close();
964
+ };
965
+ document.addEventListener("keydown", modalKeyHandler);
966
+ window.requestAnimationFrame(() => {
967
+ dialog.querySelector("[data-wvg-cancel]")?.focus();
968
+ });
969
+ }
970
+ function closeModal() {
971
+ if (modalKeyHandler !== null) document.removeEventListener("keydown", modalKeyHandler);
972
+ modalKeyHandler = null;
973
+ modal?.remove();
974
+ modal = null;
975
+ }
976
+ function showToast(message) {
977
+ document.querySelectorAll(".wvg-toast").forEach((node) => node.remove());
978
+ const toast = document.createElement("div");
979
+ toast.className = "wvg-toast";
980
+ toast.textContent = message;
981
+ document.body.append(toast);
982
+ window.clearTimeout(toastTimer);
983
+ toastTimer = window.setTimeout(() => toast.remove(), 3200);
984
+ }
985
+ void loadState().then((state) => {
986
+ if (disposed) return;
987
+ manual = state;
988
+ viewMode = manual.mode;
989
+ loaded = true;
990
+ schedule();
991
+ }).catch((reason) => {
992
+ if (disposed) return;
993
+ loaded = false;
994
+ console.warn("workspace virtual groups: manual overlay unavailable:", reason);
995
+ showToast("\u5206\u7EC4\u6570\u636E\u52A0\u8F7D\u5931\u8D25\uFF0C\u865A\u62DF\u5206\u7EC4\u5DF2\u6682\u65F6\u505C\u7528\u3002\u8BF7\u5237\u65B0\u91CD\u8BD5\u3002");
996
+ schedule();
997
+ });
998
+ const OBSERVED_SELECTOR = [
999
+ '[role="tree"]',
1000
+ '[role="menu"]',
1001
+ '[class*="viewOptionsMenu"]',
1002
+ '[class*="sectionHeader"]',
1003
+ '[class*="headerActions"]',
1004
+ "[data-wvg-new-group]",
1005
+ "[data-wvg-view-wrap]"
1006
+ ].join(",");
1007
+ const mutationRelevant = (record) => {
1008
+ if (record.target instanceof Element && record.target.closest(OBSERVED_SELECTOR) !== null) return true;
1009
+ for (const node of record.addedNodes) {
1010
+ if (node instanceof Element && (node.matches(OBSERVED_SELECTOR) || node.querySelector(OBSERVED_SELECTOR) !== null)) return true;
1011
+ }
1012
+ for (const node of record.removedNodes) {
1013
+ if (node instanceof Element && (node.matches(OBSERVED_SELECTOR) || node.querySelector(OBSERVED_SELECTOR) !== null)) return true;
1014
+ }
1015
+ return !(record.target instanceof Element);
1016
+ };
1017
+ const observer = new MutationObserver((records) => {
1018
+ if (records.some(mutationRelevant)) schedule();
1019
+ });
1020
+ observer.observe(document.body, { childList: true, subtree: true });
1021
+ document.addEventListener("dragstart", onDocumentDragStart, true);
1022
+ document.addEventListener("dragend", onDocumentDragEnd, true);
1023
+ schedule();
1024
+ return () => {
1025
+ disposed = true;
1026
+ observer.disconnect();
1027
+ document.removeEventListener("dragstart", onDocumentDragStart, true);
1028
+ document.removeEventListener("dragend", onDocumentDragEnd, true);
1029
+ if (frame !== 0) window.cancelAnimationFrame(frame);
1030
+ window.clearTimeout(toastTimer);
1031
+ closeMenu();
1032
+ closeViewOptions();
1033
+ closeModal();
1034
+ cleanupList();
1035
+ restoreLegacyAddButton();
1036
+ removeNewGroupButtons();
1037
+ document.querySelectorAll("[data-wvg-view-wrap]").forEach((node) => node.remove());
1038
+ style.remove();
1039
+ };
1040
+ }
1041
+
1042
+ // src/client/index.ts
1043
+ var inject = ["workspaces", "uiWorkspace"];
1044
+ function apply(ctx) {
1045
+ ctx.effect(
1046
+ () => mountWorkspaceVirtualGroups(ctx),
1047
+ "dsh-workspace-virtual-groups: sidebar grouping layer"
1048
+ );
1049
+ }
1050
+ return module.exports; } });
1051
+ //# sourceMappingURL=client.js.map