@michengai/dsh-archive-manager 1.1.7 → 1.1.8

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,14 +1,125 @@
1
- import { registerPluginUpdater } from "./plugin-updater.js";
1
+ import { getOnlineUpdate, getSidebarEntry, setOnlineUpdate, setSidebarEntry } from "./plugin-settings.js";
2
+ import { isTrustedUpdateRequest, rawService, registerPluginUpdater, releaseUpdateRoute } from "./plugin-updater.js";
2
3
  import { apply as installHost, inject as hostInject } from "./host-install.js";
3
4
  const inject = ["webServer", ...hostInject];
4
- async function apply(ctx) {
5
+ const SIDEBAR_ENTRY_ENDPOINT = "/api/michengai/dsh-archive-manager/sidebar-entry";
6
+ const ONLINE_UPDATE_ENDPOINT = "/api/michengai/dsh-archive-manager/online-update";
7
+ function sendJson(response, statusCode, value) {
8
+ response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
9
+ response.end(JSON.stringify(value));
10
+ }
11
+ function readJsonBody(request) {
12
+ return new Promise((resolve) => {
13
+ const chunks = [];
14
+ let bytes = 0;
15
+ request.on("data", (chunk) => {
16
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
17
+ bytes += buffer.length;
18
+ if (bytes > 64 * 1024) return;
19
+ chunks.push(buffer);
20
+ });
21
+ request.on("end", () => {
22
+ try {
23
+ const raw = Buffer.concat(chunks).toString("utf8");
24
+ const parsed = JSON.parse(raw);
25
+ resolve(parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null);
26
+ } catch {
27
+ resolve(null);
28
+ }
29
+ });
30
+ request.on("error", () => resolve(null));
31
+ });
32
+ }
33
+ function registerSettingsRoutes(ctx, hostUpdateDisabled) {
34
+ const server = rawService(ctx.webServer);
35
+ releaseUpdateRoute(server, SIDEBAR_ENTRY_ENDPOINT);
36
+ releaseUpdateRoute(server, ONLINE_UPDATE_ENDPOINT);
37
+ const disposeSidebar = ctx.webServer.register({
38
+ kind: "exact",
39
+ path: SIDEBAR_ENTRY_ENDPOINT,
40
+ handler: async (request, response) => {
41
+ if (request.method === "GET" || request.method === "HEAD") {
42
+ const enabled = await getSidebarEntry();
43
+ sendJson(response, 200, { ok: true, enabled });
44
+ return;
45
+ }
46
+ if (request.method === "POST") {
47
+ if (!isTrustedUpdateRequest(request)) {
48
+ sendJson(response, 403, { ok: false, error: "\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u540C\u6E90\u8BF7\u6C42\u3002" });
49
+ return;
50
+ }
51
+ const body = await readJsonBody(request);
52
+ if (!body || typeof body.enabled !== "boolean") {
53
+ sendJson(response, 400, { ok: false, error: "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" });
54
+ return;
55
+ }
56
+ const enabled = await setSidebarEntry(body.enabled);
57
+ sendJson(response, 200, { ok: true, enabled });
58
+ return;
59
+ }
60
+ response.writeHead(405, { allow: "GET, HEAD, POST" });
61
+ response.end();
62
+ }
63
+ });
64
+ const disposeUpdate = ctx.webServer.register({
65
+ kind: "exact",
66
+ path: ONLINE_UPDATE_ENDPOINT,
67
+ handler: async (request, response) => {
68
+ if (request.method === "GET" || request.method === "HEAD") {
69
+ const enabled = !hostUpdateDisabled && await getOnlineUpdate();
70
+ sendJson(response, 200, { ok: true, enabled, locked: hostUpdateDisabled });
71
+ return;
72
+ }
73
+ if (request.method === "POST") {
74
+ if (!isTrustedUpdateRequest(request)) {
75
+ sendJson(response, 403, { ok: false, error: "\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u540C\u6E90\u8BF7\u6C42\u3002" });
76
+ return;
77
+ }
78
+ if (hostUpdateDisabled) {
79
+ sendJson(response, 403, { ok: false, error: "\u5728\u7EBF\u66F4\u65B0\u5DF2\u88AB\u5BBF\u4E3B\u914D\u7F6E\u9501\u5B9A\u7981\u7528" });
80
+ return;
81
+ }
82
+ const body = await readJsonBody(request);
83
+ if (!body || typeof body.enabled !== "boolean") {
84
+ sendJson(response, 400, { ok: false, error: "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" });
85
+ return;
86
+ }
87
+ const enabled = await setOnlineUpdate(body.enabled);
88
+ sendJson(response, 200, { ok: true, enabled });
89
+ return;
90
+ }
91
+ response.writeHead(405, { allow: "GET, HEAD, POST" });
92
+ response.end();
93
+ }
94
+ });
95
+ return () => {
96
+ try {
97
+ disposeSidebar();
98
+ } catch {
99
+ }
100
+ try {
101
+ disposeUpdate();
102
+ } catch {
103
+ }
104
+ };
105
+ }
106
+ async function apply(ctx, config) {
107
+ const hostUpdateDisabled = config?.updater === false || config?.onlineUpdate === false;
5
108
  let disposeUpdater = () => {
6
109
  };
110
+ let disposeSettingsRoutes = () => {
111
+ };
112
+ try {
113
+ disposeSettingsRoutes = registerSettingsRoutes(ctx, hostUpdateDisabled);
114
+ } catch (error) {
115
+ ctx.logger?.warn?.(`archive-manager: settings routes skipped: ${String(error)}`);
116
+ }
7
117
  try {
8
118
  disposeUpdater = registerPluginUpdater(ctx, {
9
119
  endpoint: "/api/michengai/dsh-archive-manager/update",
10
120
  packageName: "@michengai/dsh-archive-manager",
11
- manifestUrl: new URL("../package.json", import.meta.url)
121
+ manifestUrl: new URL("../package.json", import.meta.url),
122
+ isEnabled: async () => !hostUpdateDisabled && await getOnlineUpdate()
12
123
  });
13
124
  } catch (error) {
14
125
  ctx.logger?.warn?.(`archive-manager: update route skipped: ${String(error)}`);
@@ -18,11 +129,13 @@ async function apply(ctx) {
18
129
  disposeHost = await installHost(ctx);
19
130
  } catch (error) {
20
131
  disposeUpdater();
132
+ disposeSettingsRoutes();
21
133
  throw error;
22
134
  }
23
135
  return async () => {
24
136
  try {
25
137
  disposeUpdater();
138
+ disposeSettingsRoutes();
26
139
  } finally {
27
140
  await disposeHost();
28
141
  }
@@ -0,0 +1,123 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { readFile, rename, unlink, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ function resolveDshHome() {
7
+ const custom = process.env.DSH_HOME;
8
+ if (typeof custom === "string" && custom.trim() !== "") {
9
+ return custom.trim();
10
+ }
11
+ return join(homedir(), ".dsh");
12
+ }
13
+ function managerSettingsPath() {
14
+ return join(resolveDshHome(), "archive-manager", "settings.json");
15
+ }
16
+ let writeQueue = Promise.resolve();
17
+ function enqueueWrite(task) {
18
+ const next = writeQueue.then(task, task);
19
+ writeQueue = next.then(() => {
20
+ }, () => {
21
+ });
22
+ return next;
23
+ }
24
+ async function replaceTextFile(file, next) {
25
+ const dir = dirname(file);
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+ const nonce = randomUUID();
30
+ const tmp = `${file}.${process.pid}.${nonce}.tmp`;
31
+ const backup = `${file}.${process.pid}.${nonce}.bak`;
32
+ await writeFile(tmp, next, "utf8");
33
+ try {
34
+ await rename(tmp, file);
35
+ return;
36
+ } catch (error) {
37
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
38
+ if (code !== "EEXIST" && code !== "EPERM" && code !== "EACCES") {
39
+ await unlink(tmp).catch(() => {
40
+ });
41
+ throw error;
42
+ }
43
+ }
44
+ await unlink(backup).catch(() => {
45
+ });
46
+ await rename(file, backup);
47
+ try {
48
+ await rename(tmp, file);
49
+ } catch (error) {
50
+ await rename(backup, file).catch(() => {
51
+ });
52
+ await unlink(tmp).catch(() => {
53
+ });
54
+ throw error;
55
+ }
56
+ await unlink(backup).catch(() => {
57
+ });
58
+ }
59
+ async function readSettings() {
60
+ const path = managerSettingsPath();
61
+ if (!existsSync(path)) {
62
+ return { sidebarEntry: false, onlineUpdate: true };
63
+ }
64
+ try {
65
+ const raw = await readFile(path, "utf8");
66
+ const parsed = JSON.parse(raw);
67
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
68
+ return { sidebarEntry: false, onlineUpdate: true };
69
+ }
70
+ const item = parsed;
71
+ return {
72
+ sidebarEntry: typeof item.sidebarEntry === "boolean" ? item.sidebarEntry : false,
73
+ onlineUpdate: typeof item.onlineUpdate === "boolean" ? item.onlineUpdate : true
74
+ };
75
+ } catch {
76
+ return { sidebarEntry: false, onlineUpdate: true };
77
+ }
78
+ }
79
+ async function writeSettings(next) {
80
+ const path = managerSettingsPath();
81
+ const serialized = `${JSON.stringify(next, null, 2)}
82
+ `;
83
+ await replaceTextFile(path, serialized);
84
+ }
85
+ async function getSidebarEntry() {
86
+ const current = await readSettings();
87
+ return current.sidebarEntry === true;
88
+ }
89
+ async function setSidebarEntry(enabled) {
90
+ if (typeof enabled !== "boolean") {
91
+ throw new TypeError("sidebarEntry must be a boolean");
92
+ }
93
+ return enqueueWrite(async () => {
94
+ const current = await readSettings();
95
+ current.sidebarEntry = enabled;
96
+ await writeSettings(current);
97
+ return enabled;
98
+ });
99
+ }
100
+ async function getOnlineUpdate() {
101
+ const current = await readSettings();
102
+ return current.onlineUpdate !== false;
103
+ }
104
+ async function setOnlineUpdate(enabled) {
105
+ if (typeof enabled !== "boolean") {
106
+ throw new TypeError("onlineUpdate must be a boolean");
107
+ }
108
+ return enqueueWrite(async () => {
109
+ const current = await readSettings();
110
+ current.onlineUpdate = enabled;
111
+ await writeSettings(current);
112
+ return enabled;
113
+ });
114
+ }
115
+ export {
116
+ getOnlineUpdate,
117
+ getSidebarEntry,
118
+ managerSettingsPath,
119
+ readSettings,
120
+ setOnlineUpdate,
121
+ setSidebarEntry,
122
+ writeSettings
123
+ };
@@ -367,6 +367,13 @@ function registerPluginUpdater(ctx, options) {
367
367
  path: options.endpoint,
368
368
  handler: async (request, response) => {
369
369
  try {
370
+ if (options.isEnabled) {
371
+ const allowed = await options.isEnabled();
372
+ if (!allowed) {
373
+ json(response, 403, { ok: false, error: "\u5728\u7EBF\u66F4\u65B0\u5DF2\u88AB\u7981\u7528\u3002" });
374
+ return;
375
+ }
376
+ }
370
377
  const target = runtime(host);
371
378
  if (request.method === "GET" || request.method === "HEAD") {
372
379
  const payload = await status(options, target);
@@ -430,8 +437,10 @@ export {
430
437
  isNewerVersion,
431
438
  isTrustedUpdateRequest,
432
439
  mergeMinimumReleaseAgeExclude,
440
+ rawService,
433
441
  recordReleaseAgeExclude,
434
442
  registerPluginUpdater,
443
+ releaseUpdateRoute,
435
444
  resolveUpdateRuntime,
436
445
  shouldNotifyParent
437
446
  };
@@ -0,0 +1,393 @@
1
+ import * as React from "react";
2
+ import { IconArchiveOutlineMedium, Switch as PrimitiveSwitch } from "@deepseek-ai/dsh-client-ui-primitives";
3
+ const h = React.createElement;
4
+ const BUNDLE_KEY = "@michengai/dsh-archive-manager";
5
+ const SIDEBAR_PANEL_ID = "archived-sessions";
6
+ const SIDEBAR_PANEL_ORDER = 4;
7
+ const SIDEBAR_ENTRY_STYLE_ID = "dsham-sidebar-entry-style";
8
+ const sidebarEntryCss = `
9
+ .dsham_mainPanel{box-sizing:border-box;height:100%;min-height:0;overflow-y:auto;display:flex;flex-direction:column;align-items:center;padding:28px clamp(24px,4vw,48px) 48px}
10
+ .dsham_mainPanel>*{width:100%;max-width:960px}
11
+ .dsham_bundle_row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 0;border-bottom:0.5px solid var(--dsw-alias-border-l2)}
12
+ .dsham_bundle_label_box{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1;text-align:left}
13
+ .dsham_bundle_title{font-size:13px;font-weight:500;line-height:20px;color:var(--dsw-alias-label-primary)}
14
+ .dsham_bundle_desc{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
15
+ `;
16
+ function ensureSidebarEntryStyle() {
17
+ if (typeof document === "undefined" || typeof document.getElementById !== "function") return;
18
+ if (document.getElementById(SIDEBAR_ENTRY_STYLE_ID) !== null) return;
19
+ if (typeof document.createElement !== "function") return;
20
+ const style = document.createElement("style");
21
+ style.id = SIDEBAR_ENTRY_STYLE_ID;
22
+ style.textContent = sidebarEntryCss;
23
+ (document.head ?? document.documentElement)?.appendChild?.(style);
24
+ }
25
+ function ArchiveSidebarIcon(props) {
26
+ const size = props && typeof props.size === "number" ? props.size : 16;
27
+ return h(IconArchiveOutlineMedium, { size });
28
+ }
29
+ function ArchiveBundleConfigCard(props) {
30
+ ensureSidebarEntryStyle();
31
+ const {
32
+ t,
33
+ enabled,
34
+ onToggle,
35
+ disabled,
36
+ onlineUpdateEnabled = true,
37
+ onToggleOnlineUpdate,
38
+ onlineUpdateDisabled,
39
+ onlineUpdateLocked = false
40
+ } = props;
41
+ const rows = [
42
+ h(
43
+ "div",
44
+ { key: "sidebar-entry", className: "dsham_bundle_row" },
45
+ h(
46
+ "div",
47
+ { className: "dsham_bundle_label_box" },
48
+ h("span", { className: "dsham_bundle_title" }, t("sidebar.entry.title")),
49
+ h("span", { className: "dsham_bundle_desc" }, t("sidebar.entry.desc"))
50
+ ),
51
+ h(PrimitiveSwitch, {
52
+ checked: enabled,
53
+ onChange: onToggle,
54
+ label: t("sidebar.entry.toggle"),
55
+ disabled
56
+ })
57
+ )
58
+ ];
59
+ if (typeof onToggleOnlineUpdate === "function") {
60
+ const updateDesc = onlineUpdateLocked ? `${t("update.entry.desc")} (${t("update.entry.locked")})` : t("update.entry.desc");
61
+ rows.push(
62
+ h(
63
+ "div",
64
+ { key: "online-update", className: "dsham_bundle_row" },
65
+ h(
66
+ "div",
67
+ { className: "dsham_bundle_label_box" },
68
+ h("span", { className: "dsham_bundle_title" }, t("update.entry.title")),
69
+ h("span", { className: "dsham_bundle_desc" }, updateDesc)
70
+ ),
71
+ h(PrimitiveSwitch, {
72
+ checked: onlineUpdateEnabled,
73
+ onChange: onToggleOnlineUpdate,
74
+ label: t("update.entry.toggle"),
75
+ disabled: onlineUpdateDisabled || onlineUpdateLocked,
76
+ title: onlineUpdateLocked ? t("update.entry.locked") : void 0
77
+ })
78
+ )
79
+ );
80
+ }
81
+ return h(React.Fragment, null, rows);
82
+ }
83
+ function ArchiveBundleConfigCardWrapper(props) {
84
+ const [enabled, setEnabled] = React.useState(props.getEnabled);
85
+ const [onlineUpdateEnabled, setOnlineUpdateEnabled] = React.useState(props.getOnlineUpdateEnabled ? props.getOnlineUpdateEnabled : () => true);
86
+ const [onlineUpdateLocked, setOnlineUpdateLocked] = React.useState(props.getOnlineUpdateLocked ? props.getOnlineUpdateLocked : () => false);
87
+ const [savingSidebar, setSavingSidebar] = React.useState(false);
88
+ const [savingUpdate, setSavingUpdate] = React.useState(false);
89
+ React.useEffect(() => {
90
+ const syncState = () => {
91
+ setEnabled(props.getEnabled());
92
+ if (props.getOnlineUpdateEnabled) {
93
+ setOnlineUpdateEnabled(props.getOnlineUpdateEnabled());
94
+ }
95
+ if (props.getOnlineUpdateLocked) {
96
+ setOnlineUpdateLocked(props.getOnlineUpdateLocked());
97
+ }
98
+ };
99
+ syncState();
100
+ return props.subscribe(syncState);
101
+ }, [props]);
102
+ const onToggleSidebar = (next) => {
103
+ setSavingSidebar(true);
104
+ setEnabled(next);
105
+ props.setSidebar(next).catch(() => {
106
+ setEnabled(props.getEnabled());
107
+ }).finally(() => {
108
+ setSavingSidebar(false);
109
+ });
110
+ };
111
+ const onToggleOnlineUpdate = props.setOnlineUpdate ? (next) => {
112
+ if (props.getOnlineUpdateLocked?.()) return;
113
+ setSavingUpdate(true);
114
+ setOnlineUpdateEnabled(next);
115
+ props.setOnlineUpdate(next).catch(() => {
116
+ if (props.getOnlineUpdateEnabled) {
117
+ setOnlineUpdateEnabled(props.getOnlineUpdateEnabled());
118
+ }
119
+ }).finally(() => {
120
+ setSavingUpdate(false);
121
+ });
122
+ } : void 0;
123
+ return h(ArchiveBundleConfigCard, {
124
+ t: props.t,
125
+ enabled,
126
+ disabled: savingSidebar,
127
+ onToggle: onToggleSidebar,
128
+ onlineUpdateEnabled,
129
+ onlineUpdateDisabled: savingUpdate || onlineUpdateLocked,
130
+ onlineUpdateLocked,
131
+ onToggleOnlineUpdate
132
+ });
133
+ }
134
+ function setupSidebarEntry(ctx, options) {
135
+ ensureSidebarEntryStyle();
136
+ if (!ctx.locale) {
137
+ return {
138
+ isEnabled: () => false,
139
+ isSupported: () => false,
140
+ setEnabled: async () => false,
141
+ isOnlineUpdateEnabled: () => true,
142
+ isOnlineUpdateLocked: () => false,
143
+ setOnlineUpdate: async () => false,
144
+ destroy: () => {
145
+ }
146
+ };
147
+ }
148
+ const { callApi, renderPanel, ns } = options;
149
+ let sidebarEntryEnabled = false;
150
+ let sidebarSupported = false;
151
+ let mainSupported = false;
152
+ let offPanel = null;
153
+ let offRow = null;
154
+ let epoch = 0;
155
+ let destroyed = false;
156
+ let layoutService;
157
+ const listeners = /* @__PURE__ */ new Set();
158
+ const notify = () => {
159
+ if (destroyed) return;
160
+ for (const listener of listeners) {
161
+ try {
162
+ listener();
163
+ } catch {
164
+ }
165
+ }
166
+ };
167
+ const mountMain = () => {
168
+ if (destroyed || !mainSupported || !sidebarEntryEnabled || offPanel) return;
169
+ const res = ctx.slots.register({
170
+ name: "main",
171
+ key: SIDEBAR_PANEL_ID,
172
+ locale: ns,
173
+ inject: () => {
174
+ const extra = typeof options.injectProps === "function" ? options.injectProps() : {};
175
+ return Object.assign({ t: ctx.locale?.bind ? ctx.locale.bind(ns) : void 0 }, extra);
176
+ }
177
+ }, (ownerProps) => h("div", { className: "dsham_mainPanel" }, renderPanel(ownerProps)));
178
+ offPanel = typeof res === "function" ? res : () => {
179
+ };
180
+ };
181
+ const unmountMain = () => {
182
+ if (offPanel) {
183
+ try {
184
+ offPanel();
185
+ } catch {
186
+ }
187
+ offPanel = null;
188
+ }
189
+ };
190
+ const mountRow = () => {
191
+ if (destroyed || !sidebarSupported || !sidebarEntryEnabled || offRow) return;
192
+ const res = ctx.slots.register({
193
+ name: "sidebar.panellist",
194
+ id: SIDEBAR_PANEL_ID,
195
+ order: SIDEBAR_PANEL_ORDER,
196
+ label: () => ctx.locale.bind(ns)("archives.title"),
197
+ locale: ns
198
+ }, (ownerProps) => ArchiveSidebarIcon(ownerProps && typeof ownerProps === "object" && "size" in ownerProps ? ownerProps : void 0));
199
+ offRow = typeof res === "function" ? res : () => {
200
+ };
201
+ };
202
+ const unmountRow = () => {
203
+ if (offRow) {
204
+ try {
205
+ offRow();
206
+ } catch {
207
+ }
208
+ offRow = null;
209
+ }
210
+ };
211
+ const sync = () => {
212
+ if (destroyed) return;
213
+ if (sidebarEntryEnabled) {
214
+ mountMain();
215
+ mountRow();
216
+ } else {
217
+ if (layoutService?.panelInfo && typeof layoutService.selectPanel === "function") {
218
+ try {
219
+ if (layoutService.panelInfo.getSnapshot().activePanelId === SIDEBAR_PANEL_ID) {
220
+ layoutService.selectPanel(null);
221
+ }
222
+ } catch {
223
+ }
224
+ }
225
+ unmountRow();
226
+ unmountMain();
227
+ }
228
+ };
229
+ const setEnabled = async (next) => {
230
+ if (destroyed) return sidebarEntryEnabled;
231
+ const currentEpoch = ++epoch;
232
+ sidebarEntryEnabled = next;
233
+ sync();
234
+ notify();
235
+ try {
236
+ await callApi("/sidebar-entry", {
237
+ method: "POST",
238
+ headers: { "content-type": "application/json" },
239
+ body: JSON.stringify({ enabled: next })
240
+ });
241
+ return next;
242
+ } catch (err) {
243
+ if (currentEpoch === epoch) {
244
+ sidebarEntryEnabled = !next;
245
+ sync();
246
+ notify();
247
+ }
248
+ throw err;
249
+ }
250
+ };
251
+ let onlineUpdateEnabled = true;
252
+ let onlineUpdateLocked = false;
253
+ let updateEpoch = 0;
254
+ const setOnlineUpdate = async (next) => {
255
+ if (destroyed || onlineUpdateLocked) return onlineUpdateEnabled;
256
+ const currentEpoch = ++updateEpoch;
257
+ onlineUpdateEnabled = next;
258
+ notify();
259
+ if (typeof options.onOnlineUpdateChange === "function") {
260
+ try {
261
+ options.onOnlineUpdateChange(next);
262
+ } catch {
263
+ }
264
+ }
265
+ try {
266
+ await callApi("/online-update", {
267
+ method: "POST",
268
+ headers: { "content-type": "application/json" },
269
+ body: JSON.stringify({ enabled: next })
270
+ });
271
+ if (currentEpoch === updateEpoch && typeof options.onOnlineUpdateChange === "function") {
272
+ try {
273
+ options.onOnlineUpdateChange(next);
274
+ } catch {
275
+ }
276
+ }
277
+ return next;
278
+ } catch (err) {
279
+ if (currentEpoch === updateEpoch) {
280
+ onlineUpdateEnabled = !next;
281
+ notify();
282
+ if (typeof options.onOnlineUpdateChange === "function") {
283
+ try {
284
+ options.onOnlineUpdateChange(!next);
285
+ } catch {
286
+ }
287
+ }
288
+ }
289
+ throw err;
290
+ }
291
+ };
292
+ const loadEpoch = epoch;
293
+ callApi("/sidebar-entry").then((res) => {
294
+ if (!destroyed && epoch === loadEpoch && res && typeof res.enabled === "boolean") {
295
+ sidebarEntryEnabled = res.enabled;
296
+ sync();
297
+ notify();
298
+ }
299
+ }).catch(() => {
300
+ });
301
+ const loadUpdateEpoch = updateEpoch;
302
+ callApi("/online-update").then((res) => {
303
+ if (!destroyed && updateEpoch === loadUpdateEpoch && res && typeof res.enabled === "boolean") {
304
+ onlineUpdateEnabled = res.enabled;
305
+ if (res.locked === true) onlineUpdateLocked = true;
306
+ notify();
307
+ if (typeof options.onOnlineUpdateChange === "function") {
308
+ try {
309
+ options.onOnlineUpdateChange(onlineUpdateEnabled);
310
+ } catch {
311
+ }
312
+ }
313
+ }
314
+ }).catch(() => {
315
+ });
316
+ const hostCtx = ctx;
317
+ if (typeof hostCtx.inject === "function") {
318
+ hostCtx.inject(["layout"], (scoped) => {
319
+ if (!destroyed && scoped.layout) layoutService = scoped.layout;
320
+ });
321
+ }
322
+ ctx.slots.inject("main", () => {
323
+ if (destroyed) return;
324
+ mainSupported = true;
325
+ mountMain();
326
+ return () => {
327
+ mainSupported = false;
328
+ unmountMain();
329
+ };
330
+ });
331
+ ctx.slots.inject("sidebar.panellist", () => {
332
+ if (destroyed) return;
333
+ sidebarSupported = true;
334
+ mountRow();
335
+ return () => {
336
+ sidebarSupported = false;
337
+ unmountRow();
338
+ };
339
+ });
340
+ ctx.slots.inject("plugins.bundle.config", () => {
341
+ if (destroyed) return;
342
+ return ctx.slots.register({
343
+ name: "plugins.bundle.config",
344
+ key: BUNDLE_KEY,
345
+ locale: ns
346
+ }, (ownerProps) => {
347
+ if (ownerProps && ownerProps.view === "summary") return null;
348
+ return h(ArchiveBundleConfigCardWrapper, {
349
+ t: ctx.locale.bind(ns),
350
+ getEnabled: () => sidebarEntryEnabled,
351
+ setSidebar: setEnabled,
352
+ getOnlineUpdateEnabled: () => onlineUpdateEnabled,
353
+ setOnlineUpdate,
354
+ getOnlineUpdateLocked: () => onlineUpdateLocked,
355
+ subscribe: (fn) => {
356
+ listeners.add(fn);
357
+ return () => {
358
+ listeners.delete(fn);
359
+ };
360
+ }
361
+ });
362
+ });
363
+ });
364
+ return {
365
+ isEnabled: () => sidebarEntryEnabled,
366
+ isSupported: () => sidebarSupported,
367
+ setEnabled,
368
+ isOnlineUpdateEnabled: () => onlineUpdateEnabled,
369
+ isOnlineUpdateLocked: () => onlineUpdateLocked,
370
+ setOnlineUpdate,
371
+ destroy: () => {
372
+ if (destroyed) return;
373
+ destroyed = true;
374
+ epoch++;
375
+ updateEpoch++;
376
+ layoutService = void 0;
377
+ listeners.clear();
378
+ unmountRow();
379
+ unmountMain();
380
+ }
381
+ };
382
+ }
383
+ export {
384
+ ArchiveBundleConfigCard,
385
+ ArchiveSidebarIcon,
386
+ BUNDLE_KEY,
387
+ SIDEBAR_ENTRY_STYLE_ID,
388
+ SIDEBAR_PANEL_ID,
389
+ SIDEBAR_PANEL_ORDER,
390
+ ensureSidebarEntryStyle,
391
+ setupSidebarEntry,
392
+ sidebarEntryCss
393
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
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",