@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/CHANGELOG.md +7 -0
- package/CHANGELOG.zh-CN.md +7 -0
- package/README.md +33 -0
- package/README.zh-CN.md +33 -0
- package/lib/api-base.js +32 -0
- package/lib/client.js +11 -4
- package/lib/index.js +116 -3
- package/lib/plugin-settings.js +123 -0
- package/lib/plugin-updater.js +9 -0
- package/lib/sidebar-entry.js +393 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,14 +1,125 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
+
};
|
package/lib/plugin-updater.js
CHANGED
|
@@ -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.
|
|
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",
|