@michengai/dsh-archive-manager 1.1.22 → 1.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getOnlineUpdate, getSidebarEntry, getSidebarFavoriteStar, setOnlineUpdate, setSidebarEntry, setSidebarFavoriteStar } from "./plugin-settings.js";
1
+ import { getEntryLocation, getOnlineUpdate, getSidebarFavoriteStar, setEntryLocation, setOnlineUpdate, setSidebarFavoriteStar } from "./plugin-settings.js";
2
2
  import { isTrustedUpdateRequest, rawService, registerPluginUpdater, releaseUpdateRoute } from "./plugin-updater.js";
3
3
  import { apply as installHost, inject as hostInject } from "./host-install.js";
4
4
  import { authorizeExplorerPath } from "./explorer-path-policy.js";
@@ -45,8 +45,8 @@ function registerSettingsRoutes(ctx, hostUpdateDisabled) {
45
45
  path: SIDEBAR_ENTRY_ENDPOINT,
46
46
  handler: async (request, response) => {
47
47
  if (request.method === "GET" || request.method === "HEAD") {
48
- const enabled = await getSidebarEntry();
49
- sendJson(response, 200, { ok: true, enabled });
48
+ const location = await getEntryLocation();
49
+ sendJson(response, 200, { ok: true, location, enabled: location !== "settings" });
50
50
  return;
51
51
  }
52
52
  if (request.method === "POST") {
@@ -55,12 +55,25 @@ function registerSettingsRoutes(ctx, hostUpdateDisabled) {
55
55
  return;
56
56
  }
57
57
  const body = await readJsonBody(request);
58
- if (!body || typeof body.enabled !== "boolean") {
59
- sendJson(response, 400, { ok: false, error: "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" });
58
+ if (!body) {
59
+ sendJson(response, 400, { ok: false, error: "\u8BF7\u6C42\u4F53\u4E0D\u80FD\u4E3A\u7A7A" });
60
60
  return;
61
61
  }
62
- const enabled = await setSidebarEntry(body.enabled);
63
- sendJson(response, 200, { ok: true, enabled });
62
+ if (typeof body.location === "string") {
63
+ if (body.location !== "both" && body.location !== "sidebar" && body.location !== "settings") {
64
+ sendJson(response, 400, { ok: false, error: "location \u5FC5\u987B\u662F both\u3001sidebar \u6216 settings" });
65
+ return;
66
+ }
67
+ const location = await setEntryLocation(body.location);
68
+ sendJson(response, 200, { ok: true, location, enabled: location !== "settings" });
69
+ return;
70
+ }
71
+ if (typeof body.enabled === "boolean") {
72
+ const location = await setEntryLocation(body.enabled ? "both" : "settings");
73
+ sendJson(response, 200, { ok: true, location, enabled: location !== "settings" });
74
+ return;
75
+ }
76
+ sendJson(response, 400, { ok: false, error: "\u7F3A\u5C11\u5408\u6CD5\u7684 location \u6216 enabled \u53C2\u6570" });
64
77
  return;
65
78
  }
66
79
  response.writeHead(405, { allow: "GET, HEAD, POST" });
@@ -58,41 +58,81 @@ async function replaceTextFile(file, next) {
58
58
  }
59
59
  async function readSettings() {
60
60
  const path = managerSettingsPath();
61
+ const defaultSettings = {
62
+ entryLocation: "settings",
63
+ sidebarEntry: false,
64
+ onlineUpdate: true,
65
+ sidebarFavoriteStar: true
66
+ };
61
67
  if (!existsSync(path)) {
62
- return { sidebarEntry: false, onlineUpdate: true, sidebarFavoriteStar: true };
68
+ return defaultSettings;
63
69
  }
64
70
  try {
65
71
  const raw = await readFile(path, "utf8");
66
72
  const parsed = JSON.parse(raw);
67
73
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
68
- return { sidebarEntry: false, onlineUpdate: true, sidebarFavoriteStar: true };
74
+ return defaultSettings;
69
75
  }
70
76
  const item = parsed;
77
+ let entryLocation;
78
+ if (item.entryLocation === "both" || item.entryLocation === "sidebar" || item.entryLocation === "settings") {
79
+ entryLocation = item.entryLocation;
80
+ } else if (item.sidebarEntry === true) {
81
+ entryLocation = "both";
82
+ } else {
83
+ entryLocation = "settings";
84
+ }
71
85
  return {
72
- sidebarEntry: typeof item.sidebarEntry === "boolean" ? item.sidebarEntry : false,
86
+ entryLocation,
87
+ sidebarEntry: entryLocation !== "settings",
73
88
  onlineUpdate: typeof item.onlineUpdate === "boolean" ? item.onlineUpdate : true,
74
89
  sidebarFavoriteStar: typeof item.sidebarFavoriteStar === "boolean" ? item.sidebarFavoriteStar : true
75
90
  };
76
91
  } catch {
77
- return { sidebarEntry: false, onlineUpdate: true, sidebarFavoriteStar: true };
92
+ return defaultSettings;
78
93
  }
79
94
  }
80
95
  async function writeSettings(next) {
81
96
  const path = managerSettingsPath();
82
- const serialized = `${JSON.stringify(next, null, 2)}
97
+ const location = next.entryLocation ?? (next.sidebarEntry ? "both" : "settings");
98
+ const normalized = {
99
+ entryLocation: location,
100
+ sidebarEntry: location !== "settings",
101
+ onlineUpdate: next.onlineUpdate !== false,
102
+ sidebarFavoriteStar: next.sidebarFavoriteStar !== false
103
+ };
104
+ const serialized = `${JSON.stringify(normalized, null, 2)}
83
105
  `;
84
106
  await replaceTextFile(path, serialized);
85
107
  }
108
+ async function getEntryLocation() {
109
+ const current = await readSettings();
110
+ return current.entryLocation ?? "settings";
111
+ }
112
+ async function setEntryLocation(location) {
113
+ if (location !== "both" && location !== "sidebar" && location !== "settings") {
114
+ throw new TypeError("entryLocation must be one of 'both', 'sidebar', or 'settings'");
115
+ }
116
+ return enqueueWrite(async () => {
117
+ const current = await readSettings();
118
+ current.entryLocation = location;
119
+ current.sidebarEntry = location !== "settings";
120
+ await writeSettings(current);
121
+ return location;
122
+ });
123
+ }
86
124
  async function getSidebarEntry() {
87
125
  const current = await readSettings();
88
- return current.sidebarEntry === true;
126
+ return current.entryLocation !== "settings";
89
127
  }
90
128
  async function setSidebarEntry(enabled) {
91
129
  if (typeof enabled !== "boolean") {
92
130
  throw new TypeError("sidebarEntry must be a boolean");
93
131
  }
94
132
  return enqueueWrite(async () => {
133
+ const location = enabled ? "both" : "settings";
95
134
  const current = await readSettings();
135
+ current.entryLocation = location;
96
136
  current.sidebarEntry = enabled;
97
137
  await writeSettings(current);
98
138
  return enabled;
@@ -129,11 +169,13 @@ async function setSidebarFavoriteStar(enabled) {
129
169
  });
130
170
  }
131
171
  export {
172
+ getEntryLocation,
132
173
  getOnlineUpdate,
133
174
  getSidebarEntry,
134
175
  getSidebarFavoriteStar,
135
176
  managerSettingsPath,
136
177
  readSettings,
178
+ setEntryLocation,
137
179
  setOnlineUpdate,
138
180
  setSidebarEntry,
139
181
  setSidebarFavoriteStar,
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { IconArchiveOutlineMedium, Switch as PrimitiveSwitch } from "@deepseek-ai/dsh-client-ui-primitives";
2
+ import { IconArchiveOutlineMedium, SegmentedControl, Switch as PrimitiveSwitch } from "@deepseek-ai/dsh-client-ui-primitives";
3
3
  const h = React.createElement;
4
4
  const BUNDLE_KEY = "@michengai/dsh-archive-manager";
5
5
  const SIDEBAR_PANEL_ID = "archived-sessions";
@@ -12,6 +12,7 @@ const sidebarEntryCss = `
12
12
  .dsham_bundle_label_box{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1;text-align:left}
13
13
  .dsham_bundle_title{font-size:13px;font-weight:500;line-height:20px;color:var(--dsw-alias-label-primary)}
14
14
  .dsham_bundle_desc{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
15
+ .dsham_bundle_error{font-size:12px;line-height:16px;color:var(--dsw-alias-state-error-primary,#ef4444);margin-top:2px}
15
16
  `;
16
17
  function ensureSidebarEntryStyle() {
17
18
  if (typeof document === "undefined" || typeof document.getElementById !== "function") return;
@@ -30,7 +31,10 @@ function ArchiveBundleConfigCard(props) {
30
31
  ensureSidebarEntryStyle();
31
32
  const {
32
33
  t,
33
- enabled,
34
+ location: propLocation,
35
+ onLocationChange,
36
+ errorMessage,
37
+ enabled = false,
34
38
  onToggle,
35
39
  disabled,
36
40
  onlineUpdateEnabled = true,
@@ -41,21 +45,38 @@ function ArchiveBundleConfigCard(props) {
41
45
  onToggleSidebarFavoriteStar,
42
46
  sidebarFavoriteStarDisabled
43
47
  } = props;
48
+ const effectiveLocation = propLocation ?? (enabled ? "both" : "settings");
49
+ const handleLocationChange = (next) => {
50
+ if (next === "both" || next === "sidebar" || next === "settings") {
51
+ if (typeof onLocationChange === "function") {
52
+ onLocationChange(next);
53
+ } else if (typeof onToggle === "function") {
54
+ onToggle(next !== "settings");
55
+ }
56
+ }
57
+ };
44
58
  const rows = [
45
59
  h(
46
60
  "div",
47
- { key: "sidebar-entry", className: "dsham_bundle_row" },
61
+ { key: "entry-location", className: "dsham_bundle_row" },
48
62
  h(
49
63
  "div",
50
64
  { className: "dsham_bundle_label_box" },
51
- h("span", { className: "dsham_bundle_title" }, t("sidebar.entry.title")),
52
- h("span", { className: "dsham_bundle_desc" }, t("sidebar.entry.desc"))
65
+ h("span", { className: "dsham_bundle_title" }, t("entry.location.title")),
66
+ h("span", { className: "dsham_bundle_desc" }, t("entry.location.desc")),
67
+ errorMessage ? h("span", { className: "dsham_bundle_error", role: "alert" }, errorMessage) : null
53
68
  ),
54
- h(PrimitiveSwitch, {
55
- checked: enabled,
56
- onChange: onToggle,
57
- label: t("sidebar.entry.toggle"),
58
- disabled
69
+ h(SegmentedControl, {
70
+ id: "dsham-bundle-entry-location",
71
+ value: effectiveLocation,
72
+ onChange: handleLocationChange,
73
+ label: t("entry.location.title"),
74
+ disabled,
75
+ options: [
76
+ { value: "both", label: t("entry.location.both") },
77
+ { value: "sidebar", label: t("entry.location.sidebar") },
78
+ { value: "settings", label: t("entry.location.settings") }
79
+ ]
59
80
  })
60
81
  )
61
82
  ];
@@ -104,16 +125,18 @@ function ArchiveBundleConfigCard(props) {
104
125
  return h(React.Fragment, null, rows);
105
126
  }
106
127
  function ArchiveBundleConfigCardWrapper(props) {
107
- const [enabled, setEnabled] = React.useState(props.getEnabled);
128
+ const [location, setLocation] = React.useState(props.getLocation);
108
129
  const [onlineUpdateEnabled, setOnlineUpdateEnabled] = React.useState(props.getOnlineUpdateEnabled ? props.getOnlineUpdateEnabled : () => true);
109
130
  const [onlineUpdateLocked, setOnlineUpdateLocked] = React.useState(props.getOnlineUpdateLocked ? props.getOnlineUpdateLocked : () => false);
110
131
  const [sidebarFavoriteStarEnabled, setSidebarFavoriteStarEnabled] = React.useState(props.getSidebarFavoriteStarEnabled ? props.getSidebarFavoriteStarEnabled : () => true);
111
- const [savingSidebar, setSavingSidebar] = React.useState(false);
132
+ const [saveError, setSaveError] = React.useState(null);
133
+ const [savingLocation, setSavingLocation] = React.useState(false);
112
134
  const [savingUpdate, setSavingUpdate] = React.useState(false);
113
135
  const [savingFavoriteStar, setSavingFavoriteStar] = React.useState(false);
114
136
  React.useEffect(() => {
115
137
  const syncState = () => {
116
- setEnabled(props.getEnabled());
138
+ setLocation(props.getLocation());
139
+ setSaveError(null);
117
140
  if (props.getOnlineUpdateEnabled) {
118
141
  setOnlineUpdateEnabled(props.getOnlineUpdateEnabled());
119
142
  }
@@ -127,13 +150,16 @@ function ArchiveBundleConfigCardWrapper(props) {
127
150
  syncState();
128
151
  return props.subscribe(syncState);
129
152
  }, [props]);
130
- const onToggleSidebar = (next) => {
131
- setSavingSidebar(true);
132
- setEnabled(next);
133
- props.setSidebar(next).catch(() => {
134
- setEnabled(props.getEnabled());
153
+ const onLocationChange = (next) => {
154
+ setSavingLocation(true);
155
+ setSaveError(null);
156
+ setLocation(next);
157
+ props.setLocation(next).catch((err) => {
158
+ console.error("archive-manager: setLocation failed:", err);
159
+ setLocation(props.getLocation());
160
+ setSaveError(props.t("entry.location.saveFailed") || "\u4FDD\u5B58\u5165\u53E3\u8BBE\u7F6E\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u91CD\u542F\u5BBF\u4E3B\u8FDB\u7A0B");
135
161
  }).finally(() => {
136
- setSavingSidebar(false);
162
+ setSavingLocation(false);
137
163
  });
138
164
  };
139
165
  const onToggleOnlineUpdate = props.setOnlineUpdate ? (next) => {
@@ -161,9 +187,10 @@ function ArchiveBundleConfigCardWrapper(props) {
161
187
  } : void 0;
162
188
  return h(ArchiveBundleConfigCard, {
163
189
  t: props.t,
164
- enabled,
165
- disabled: savingSidebar,
166
- onToggle: onToggleSidebar,
190
+ location,
191
+ errorMessage: saveError,
192
+ disabled: savingLocation,
193
+ onLocationChange,
167
194
  onlineUpdateEnabled,
168
195
  onlineUpdateDisabled: savingUpdate || onlineUpdateLocked,
169
196
  onlineUpdateLocked,
@@ -177,6 +204,9 @@ function setupSidebarEntry(ctx, options) {
177
204
  ensureSidebarEntryStyle();
178
205
  if (!ctx.locale) {
179
206
  return {
207
+ getLocation: () => "settings",
208
+ setLocation: async () => "settings",
209
+ isSettingsSectionEnabled: () => true,
180
210
  isEnabled: () => false,
181
211
  isSupported: () => false,
182
212
  setEnabled: async () => false,
@@ -190,7 +220,7 @@ function setupSidebarEntry(ctx, options) {
190
220
  };
191
221
  }
192
222
  const { callApi, renderPanel, ns } = options;
193
- let sidebarEntryEnabled = false;
223
+ let entryLocation = "settings";
194
224
  let sidebarSupported = false;
195
225
  let mainSupported = false;
196
226
  let offPanel = null;
@@ -199,6 +229,8 @@ function setupSidebarEntry(ctx, options) {
199
229
  let destroyed = false;
200
230
  let layoutService;
201
231
  const listeners = /* @__PURE__ */ new Set();
232
+ const isSidebarEnabled = () => entryLocation === "both" || entryLocation === "sidebar";
233
+ const isSettingsSectionEnabled = () => entryLocation === "both" || entryLocation === "settings";
202
234
  const notify = () => {
203
235
  if (destroyed) return;
204
236
  for (const listener of listeners) {
@@ -209,7 +241,7 @@ function setupSidebarEntry(ctx, options) {
209
241
  }
210
242
  };
211
243
  const mountMain = () => {
212
- if (destroyed || !mainSupported || !sidebarEntryEnabled || offPanel) return;
244
+ if (destroyed || !mainSupported || !isSidebarEnabled() || offPanel) return;
213
245
  const res = ctx.slots.register({
214
246
  name: "main",
215
247
  key: SIDEBAR_PANEL_ID,
@@ -232,7 +264,7 @@ function setupSidebarEntry(ctx, options) {
232
264
  }
233
265
  };
234
266
  const mountRow = () => {
235
- if (destroyed || !sidebarSupported || !sidebarEntryEnabled || offRow) return;
267
+ if (destroyed || !sidebarSupported || !isSidebarEnabled() || offRow) return;
236
268
  const res = ctx.slots.register({
237
269
  name: "sidebar.panellist",
238
270
  id: SIDEBAR_PANEL_ID,
@@ -254,7 +286,7 @@ function setupSidebarEntry(ctx, options) {
254
286
  };
255
287
  const sync = () => {
256
288
  if (destroyed) return;
257
- if (sidebarEntryEnabled) {
289
+ if (isSidebarEnabled()) {
258
290
  mountMain();
259
291
  mountRow();
260
292
  } else {
@@ -270,28 +302,45 @@ function setupSidebarEntry(ctx, options) {
270
302
  unmountMain();
271
303
  }
272
304
  };
273
- const setEnabled = async (next) => {
274
- if (destroyed) return sidebarEntryEnabled;
305
+ const setLocation = async (next) => {
306
+ if (destroyed) return entryLocation;
275
307
  const currentEpoch = ++epoch;
276
- sidebarEntryEnabled = next;
308
+ const previous = entryLocation;
309
+ entryLocation = next;
277
310
  sync();
278
311
  notify();
312
+ if (typeof options.onLocationChange === "function") {
313
+ try {
314
+ options.onLocationChange(next);
315
+ } catch {
316
+ }
317
+ }
279
318
  try {
280
319
  await callApi("/sidebar-entry", {
281
320
  method: "POST",
282
321
  headers: { "content-type": "application/json" },
283
- body: JSON.stringify({ enabled: next })
322
+ body: JSON.stringify({ location: next })
284
323
  });
285
324
  return next;
286
325
  } catch (err) {
287
326
  if (currentEpoch === epoch) {
288
- sidebarEntryEnabled = !next;
327
+ entryLocation = previous;
289
328
  sync();
290
329
  notify();
330
+ if (typeof options.onLocationChange === "function") {
331
+ try {
332
+ options.onLocationChange(previous);
333
+ } catch {
334
+ }
335
+ }
291
336
  }
292
337
  throw err;
293
338
  }
294
339
  };
340
+ const setEnabled = async (next) => {
341
+ await setLocation(next ? "both" : "settings");
342
+ return isSidebarEnabled();
343
+ };
295
344
  let onlineUpdateEnabled = true;
296
345
  let onlineUpdateLocked = false;
297
346
  let updateEpoch = 0;
@@ -369,10 +418,20 @@ function setupSidebarEntry(ctx, options) {
369
418
  };
370
419
  const loadEpoch = epoch;
371
420
  callApi("/sidebar-entry").then((res) => {
372
- if (!destroyed && epoch === loadEpoch && res && typeof res.enabled === "boolean") {
373
- sidebarEntryEnabled = res.enabled;
421
+ if (!destroyed && epoch === loadEpoch && res) {
422
+ if (res.location === "both" || res.location === "sidebar" || res.location === "settings") {
423
+ entryLocation = res.location;
424
+ } else if (typeof res.enabled === "boolean") {
425
+ entryLocation = res.enabled ? "both" : "settings";
426
+ }
374
427
  sync();
375
428
  notify();
429
+ if (typeof options.onLocationChange === "function") {
430
+ try {
431
+ options.onLocationChange(entryLocation);
432
+ } catch {
433
+ }
434
+ }
376
435
  }
377
436
  }).catch(() => {
378
437
  });
@@ -439,7 +498,9 @@ function setupSidebarEntry(ctx, options) {
439
498
  if (ownerProps && ownerProps.view === "summary") return null;
440
499
  return h(ArchiveBundleConfigCardWrapper, {
441
500
  t: ctx.locale.bind(ns),
442
- getEnabled: () => sidebarEntryEnabled,
501
+ getLocation: () => entryLocation,
502
+ setLocation,
503
+ getEnabled: () => isSidebarEnabled(),
443
504
  setSidebar: setEnabled,
444
505
  getOnlineUpdateEnabled: () => onlineUpdateEnabled,
445
506
  setOnlineUpdate,
@@ -456,7 +517,10 @@ function setupSidebarEntry(ctx, options) {
456
517
  });
457
518
  });
458
519
  return {
459
- isEnabled: () => sidebarEntryEnabled,
520
+ getLocation: () => entryLocation,
521
+ setLocation,
522
+ isSettingsSectionEnabled,
523
+ isEnabled: () => isSidebarEnabled(),
460
524
  isSupported: () => sidebarSupported,
461
525
  setEnabled,
462
526
  isOnlineUpdateEnabled: () => onlineUpdateEnabled,
@@ -479,6 +543,7 @@ function setupSidebarEntry(ctx, options) {
479
543
  }
480
544
  export {
481
545
  ArchiveBundleConfigCard,
546
+ ArchiveBundleConfigCardWrapper,
482
547
  ArchiveSidebarIcon,
483
548
  BUNDLE_KEY,
484
549
  SIDEBAR_ENTRY_STYLE_ID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "1.1.22",
3
+ "version": "1.2.0",
4
4
  "description": "归档、恢复、收藏和永久删除会话,并诊断损坏的会话日志。 · Archive, restore, favorite, and permanently delete sessions, and diagnose damaged session logs.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",