@tobycaimf/dsh-archived-sessions 1.0.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/.github/workflows/compat.yml +28 -0
- package/.github/workflows/publish.yml +29 -0
- package/LICENSE +21 -0
- package/README.en.md +75 -0
- package/README.md +75 -0
- package/assets/screenshot-session-details.png +0 -0
- package/assets/screenshot-session-settingsmenu.png +0 -0
- package/assets/screenshot-session-submenu.png +0 -0
- package/assets/screenshot-session-trash.png +0 -0
- package/build.mjs +52 -0
- package/cordis.patch.yml +7 -0
- package/lib/client.js +207 -0
- package/lib/client.js.map +7 -0
- package/lib/index.js +280 -0
- package/lib/index.js.map +7 -0
- package/package.json +44 -0
- package/scripts/check-session-health.mjs +66 -0
- package/src/client/index.jsx +204 -0
- package/src/index.js +277 -0
- package/src/zstd-frame.js +137 -0
- package/tests/host.test.js +145 -0
- package/tests/zstd-frame.test.js +148 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: compat
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: '0 4 * * *' # daily compat check
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: dsh-plugin build check
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: '22'
|
|
19
|
+
- name: Install deps
|
|
20
|
+
run: npm install
|
|
21
|
+
- name: Build plugin (host + client)
|
|
22
|
+
run: npm run build
|
|
23
|
+
- name: Run host behavior tests
|
|
24
|
+
run: npm test
|
|
25
|
+
- name: Verify committed bundles match source
|
|
26
|
+
run: git diff --exit-code -- lib
|
|
27
|
+
- name: Verify host bundle loads
|
|
28
|
+
run: node -e "import('./lib/index.js').then(m => { if (m.name !== 'dsh-sessions-manager') process.exit(1); }).catch(e => { console.error(e); process.exit(1); })"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # Required for OIDC Trusted Publishing
|
|
13
|
+
contents: read
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: '20'
|
|
20
|
+
registry-url: 'https://registry.npmjs.org'
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Build
|
|
26
|
+
run: npm run build
|
|
27
|
+
|
|
28
|
+
- name: Publish to npm
|
|
29
|
+
run: npm publish --access public
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TOBYCAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# dsh-archived-sessions
|
|
2
|
+
|
|
3
|
+
> English | [中文](./README.md)
|
|
4
|
+
|
|
5
|
+
> Manage your **archived sessions** in DSH — view, restore, and permanently delete them.
|
|
6
|
+
> Archive logo, session dates, workspace tags, and **batch multi-select** (restore / delete selected).
|
|
7
|
+
|
|
8
|
+
A persistent DSH plugin (host + browser halves). Manage every session that has been archived (hidden from the sidebar) under **Settings → 归档会话 (Archived Sessions)**.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **View**: list all archived sessions — each row shows the title (falls back to the first user message), the **session date** (`createdAt`, localized), and a **workspace tag** (title or directory name; a dashed "原工作区已删除 / workspace deleted" tag when the original workspace is gone).
|
|
13
|
+
- **Restore**: unarchive and put the session back into its group.
|
|
14
|
+
- **Permanently delete**: physically removes the session log file + detaches workspace accounting — **irreversible** (asks for confirmation first).
|
|
15
|
+
- **Batch multi-select**: one-click batch mode — select-all, restore selected, delete selected (single confirmation for batch delete).
|
|
16
|
+
- **Adaptive UI**: follows the theme tokens (light/dark), stacks vertically on narrow screens; loading / empty / error / busy states, keyboard focus and `prefers-reduced-motion` support.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
# Option 1: install as a Git dependency (recommended — no local clone; restart DSH to apply)
|
|
22
|
+
dsh plugin --profile desktop add "github:TOBYCAI/dsh-archived-sessions"
|
|
23
|
+
|
|
24
|
+
# For the web UI (if you also use dsh web):
|
|
25
|
+
dsh plugin --profile web add "github:TOBYCAI/dsh-archived-sessions"
|
|
26
|
+
|
|
27
|
+
# Option 2: local link (for development)
|
|
28
|
+
git clone https://github.com/TOBYCAI/dsh-archived-sessions.git
|
|
29
|
+
dsh plugin --profile desktop add link:/path/to/dsh-archived-sessions
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> After installing, **restart DSH** (or refresh the page to reload the bundle). Then Settings → Archived Sessions becomes available.
|
|
33
|
+
|
|
34
|
+
## Uninstall
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
dsh plugin --profile desktop remove dsh-archived-sessions
|
|
38
|
+
dsh plugin --profile web remove dsh-archived-sessions
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Structure
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
package.json npm metadata + dsh.bundle.patch + dsh.client (browser-half registration)
|
|
45
|
+
cordis.patch.yml inserts this plugin's row into the profile bundle
|
|
46
|
+
src/index.js host source (/archived-sessions/* JSON routes)
|
|
47
|
+
src/client/index.jsx client source (React, settings.section)
|
|
48
|
+
build.mjs esbuild build script (regenerates lib/)
|
|
49
|
+
lib/index.js pre-built host (ESM)
|
|
50
|
+
lib/client.js pre-built client (ModuleLoader CJS handshake)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`lib/` is pre-built, so cloning and using it needs no esbuild. To modify the source, run `npm i -D esbuild && npm run build` to regenerate `lib/`.
|
|
54
|
+
|
|
55
|
+
## Host routes
|
|
56
|
+
|
|
57
|
+
| Method | Path | Description |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| POST | `/archived-sessions/list` | List archived sessions (skips deleted / non-existent ones) |
|
|
60
|
+
| POST | `/archived-sessions/restore` | Unarchive a single session |
|
|
61
|
+
| POST | `/archived-sessions/restore-many` | Unarchive many |
|
|
62
|
+
| POST | `/archived-sessions/delete` | Permanently delete a single session (physically removes log, keeps hidden) |
|
|
63
|
+
| POST | `/archived-sessions/delete-many` | Permanently delete many |
|
|
64
|
+
|
|
65
|
+
> On delete the session **stays archived** (is not unarchived), so it won't pop back into the sidebar / "ungrouped"; entries whose log is already gone are skipped.
|
|
66
|
+
> DSH has no official session-delete API; this plugin implements "physically remove log + detach ownership + keep hidden".
|
|
67
|
+
|
|
68
|
+
## Compatibility
|
|
69
|
+
|
|
70
|
+
- Works in both DSH Desktop and DSH web (same host + client halves).
|
|
71
|
+
- Peer dependencies are listed in `package.json`; `react` and `@deepseek-ai/*` are provided by the DSH runtime.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
[MIT](./LICENSE) © TOBYCAI
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# dsh-archived-sessions
|
|
2
|
+
|
|
3
|
+
> [English](./README.en.md) | 中文
|
|
4
|
+
|
|
5
|
+
> DSH 设置面板「归档会话」管理器:查看、恢复、彻底删除已归档的 session。
|
|
6
|
+
> 带归档 logo、会话日期、所属工作区标签,以及**批量多选**(恢复所选 / 删除所选)。
|
|
7
|
+
|
|
8
|
+
一个 DSH 持久化插件(host + browser 双半)。在 **设置 → 归档会话** 里管理所有被归档(从侧栏隐藏)的会话。
|
|
9
|
+
|
|
10
|
+
## 功能
|
|
11
|
+
|
|
12
|
+
- **查看**:列出全部已归档会话,每行显示 —— 标题(缺失时回退到首条用户消息)、**会话日期**(createdAt,中文格式)、**所属工作区标签**(标题或目录名;原工作区已删除时显示虚线「工作区已删 · 目录名」)。
|
|
13
|
+
- **恢复**:取消归档,放回原分组。
|
|
14
|
+
- **彻底删除**:物理删除会话日志文件 + 解除工作区归属,**不可恢复**(删除前有二次确认)。
|
|
15
|
+
- **批量多选**:一键进入批量模式,全选 / 恢复所选 / 删除所选(批量删除一次二次确认)。
|
|
16
|
+
- **自适应 UI**:深浅色跟随主题 token,窄屏自动纵向堆叠;加载/空/错误/操作中状态齐全;支持键盘焦点、`prefers-reduced-motion`。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
# 方式一:Git 依赖直装(推荐,无需本地 clone,重启 DSH 生效)
|
|
22
|
+
dsh plugin --profile desktop add "github:TOBYCAI/dsh-archived-sessions"
|
|
23
|
+
|
|
24
|
+
# web 端(若你也用 dsh web):
|
|
25
|
+
dsh plugin --profile web add "github:TOBYCAI/dsh-archived-sessions"
|
|
26
|
+
|
|
27
|
+
# 方式二:本地 link(开发调试)
|
|
28
|
+
git clone https://github.com/TOBYCAI/dsh-archived-sessions.git
|
|
29
|
+
dsh plugin --profile desktop add link:/path/to/dsh-archived-sessions
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> 装完**重启 DSH**(或刷新页面重新加载 bundle)后,设置 → 归档会话 即可用。
|
|
33
|
+
|
|
34
|
+
## 卸载
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
dsh plugin --profile desktop remove dsh-archived-sessions
|
|
38
|
+
dsh plugin --profile web remove dsh-archived-sessions
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 结构
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
package.json npm 元数据 + dsh.bundle.patch + dsh.client(浏览器半注册)
|
|
45
|
+
cordis.patch.yml 向 profile bundle 插入本插件行
|
|
46
|
+
src/index.js host 源码(/archived-sessions/* JSON 路由)
|
|
47
|
+
src/client/index.jsx client 源码(React,settings.section)
|
|
48
|
+
build.mjs esbuild 构建脚本(本地开发时生成 lib/)
|
|
49
|
+
lib/index.js 预构建 host(ESM)
|
|
50
|
+
lib/client.js 预构建 client(ModuleLoader CJS handshake)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`lib/` 已预构建,clone 下来即可直接用、无需 esbuild。若要改源码,运行 `npm i -D esbuild && npm run build` 重新生成 `lib/`。
|
|
54
|
+
|
|
55
|
+
## Host 路由
|
|
56
|
+
|
|
57
|
+
| 方法 | 路径 | 说明 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| POST | `/archived-sessions/list` | 列出归档会话(跳过已删除/不存在者) |
|
|
60
|
+
| POST | `/archived-sessions/restore` | 取消归档单个 |
|
|
61
|
+
| POST | `/archived-sessions/restore-many` | 批量取消归档 |
|
|
62
|
+
| POST | `/archived-sessions/delete` | 彻底删除单个(物理删日志,保持隐藏) |
|
|
63
|
+
| POST | `/archived-sessions/delete-many` | 批量彻底删除 |
|
|
64
|
+
|
|
65
|
+
> 删除时会**保留归档位**(不取消归档),从而不会把会话放回侧栏/未分组;并跳过日志已不存在的条目。
|
|
66
|
+
> DSH 本身没有官方 session 删除接口,本插件是「物理删日志 + 解除归属 + 保持隐藏」的实现。
|
|
67
|
+
|
|
68
|
+
## 兼容性
|
|
69
|
+
|
|
70
|
+
- DSH Desktop / web 均可(同一套 host + client)。
|
|
71
|
+
- peerDependencies 见 `package.json`;`react`、`@deepseek-ai/*` 由 DSH 运行时提供。
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
[MIT](./LICENSE) © TOBYCAI
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/build.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-archived-sessions — build script (mirrors dsh-skill-picker).
|
|
3
|
+
*
|
|
4
|
+
* Host half (lib/index.js): plain Node ESM, externalizing @deepseek-ai/dsh-*
|
|
5
|
+
* plus cordis (the profile's node_modules provide them).
|
|
6
|
+
*
|
|
7
|
+
* Client half (lib/client.js): a single CJS bundle wrapped in the ModuleLoader
|
|
8
|
+
* handshake — the web shell serves exactly one file per plugin and REQUIRES
|
|
9
|
+
* the bundle to register itself via `window.__ModuleLoader__.load({ id, factory })`.
|
|
10
|
+
* react / @deepseek-ai/dsh-* stay external and are provided by the app.
|
|
11
|
+
*/
|
|
12
|
+
import { build } from 'esbuild'
|
|
13
|
+
import { mkdirSync } from 'node:fs'
|
|
14
|
+
|
|
15
|
+
mkdirSync('lib', { recursive: true })
|
|
16
|
+
|
|
17
|
+
const dshExternal = ['@deepseek-ai/cordis', '@deepseek-ai/dsh-*']
|
|
18
|
+
|
|
19
|
+
// ---- Host half: plain Node ESM -------------------------------------------
|
|
20
|
+
await build({
|
|
21
|
+
entryPoints: ['src/index.js'],
|
|
22
|
+
outfile: 'lib/index.js',
|
|
23
|
+
bundle: true,
|
|
24
|
+
format: 'esm',
|
|
25
|
+
platform: 'node',
|
|
26
|
+
target: ['node22'],
|
|
27
|
+
sourcemap: true,
|
|
28
|
+
external: dshExternal,
|
|
29
|
+
logLevel: 'info',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// ---- Client half: CJS bundle wrapped in the ModuleLoader handshake --------
|
|
33
|
+
await build({
|
|
34
|
+
entryPoints: ['src/client/index.jsx'],
|
|
35
|
+
outfile: 'lib/client.js',
|
|
36
|
+
bundle: true,
|
|
37
|
+
format: 'cjs',
|
|
38
|
+
platform: 'browser',
|
|
39
|
+
target: ['es2022'],
|
|
40
|
+
sourcemap: true,
|
|
41
|
+
jsx: 'automatic',
|
|
42
|
+
external: [...dshExternal, 'react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'scheduler'],
|
|
43
|
+
banner: {
|
|
44
|
+
js: "window.__ModuleLoader__.load({ id: 'dsh-archived-sessions', factory: (require) => { var module = { exports: {} }; var exports = module.exports;",
|
|
45
|
+
},
|
|
46
|
+
footer: {
|
|
47
|
+
js: 'return module.exports; } });',
|
|
48
|
+
},
|
|
49
|
+
logLevel: 'info',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
console.log('build complete: lib/index.js (ESM host), lib/client.js (CJS + __ModuleLoader__ handshake)')
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# dsh-archived-sessions bundle patch: inserts the plugin row into the profile
|
|
2
|
+
# roster. The node half (exports ".") serves /archived-sessions/* JSON routes;
|
|
3
|
+
# the browser half (exports "./client", declared in package.json dsh.client)
|
|
4
|
+
# renders the "归档会话" Settings section that talks to those routes.
|
|
5
|
+
- insert:
|
|
6
|
+
- id: dsh-archived-sessions
|
|
7
|
+
name: 'dsh-archived-sessions'
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: 'dsh-archived-sessions', factory: (require) => { var module = { exports: {} }; var exports = module.exports;
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/client/index.jsx
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
apply: () => apply,
|
|
34
|
+
inject: () => inject
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_react = __toESM(require("react"), 1);
|
|
38
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
+
var inject = ["slots"];
|
|
40
|
+
var CSS = `
|
|
41
|
+
.archv{display:flex;flex-direction:column;gap:4px;max-width:800px;padding:8px 2px 28px}
|
|
42
|
+
.archv-head{display:flex;align-items:center;gap:10px;margin:0 0 2px}
|
|
43
|
+
.archv-title{font-size:16px;font-weight:650;color:var(--dsw-alias-label-primary);letter-spacing:-0.01em;margin:0}
|
|
44
|
+
.archv-count{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-subtle);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:1px 8px;flex:none}
|
|
45
|
+
.archv-sub{font-size:12px;line-height:1.55;color:var(--dsw-alias-label-tertiary);margin:0 0 12px;max-width:64ch}
|
|
46
|
+
.archv-err{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);border-radius:10px;color:var(--dsw-alias-state-error-primary);font-size:12px;margin-bottom:10px}
|
|
47
|
+
.archv-errretry{appearance:none;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent);background:transparent;color:inherit;border-radius:8px;padding:4px 10px;font-size:11px;cursor:pointer;flex:none}
|
|
48
|
+
.archv-errretry:focus-visible,.archv-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
49
|
+
.archv-list{display:flex;flex-direction:column;gap:8px}
|
|
50
|
+
.archv-card{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-fill-elevated);transition:border-color .15s ease,background-color .15s ease}
|
|
51
|
+
.archv-card:hover{border-color:var(--dsw-alias-border-l4)}
|
|
52
|
+
.archv-main{min-width:0;display:flex;flex-direction:column;gap:4px}
|
|
53
|
+
.archv-name{font-size:13px;font-weight:550;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
54
|
+
.archv-meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap;min-width:0}
|
|
55
|
+
.archv-wtag{display:inline-flex;align-items:center;gap:4px;max-width:100%;font-size:11px;font-weight:500;line-height:1;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-subtle);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
56
|
+
.archv-wgone{border-style:dashed;color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}
|
|
57
|
+
.archv-date{font-size:11px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none}
|
|
58
|
+
.archv-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10.5px;color:var(--dsw-alias-label-tertiary);flex:none}
|
|
59
|
+
.archv-dot{color:var(--dsw-alias-border-l3);flex:none}
|
|
60
|
+
.archv-actions{display:flex;gap:8px;flex:none}
|
|
61
|
+
.archv-btn{appearance:none;min-height:32px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-secondary);border-radius:9px;font-size:12px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:background-color .15s ease,border-color .15s ease,color .15s ease}
|
|
62
|
+
.archv-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
63
|
+
.archv-btn:disabled{opacity:.5;cursor:default}
|
|
64
|
+
.archv-restore{min-width:62px;justify-content:center}
|
|
65
|
+
.archv-del{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent)}
|
|
66
|
+
.archv-del:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
67
|
+
.archv-empty{display:flex;align-items:center;gap:10px;padding:20px 14px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
68
|
+
.archv-skel{display:flex;flex-direction:column;gap:8px}
|
|
69
|
+
.archv-skel-card{height:58px;border-radius:12px;background:var(--dsw-alias-fill-subtle);position:relative;overflow:hidden}
|
|
70
|
+
.archv-skel-card::after{content:'';position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--dsw-alias-fill-elevated) 75%,transparent),transparent);animation:archv-shimmer 1.4s infinite}
|
|
71
|
+
.archv-status{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:60;background:var(--dsw-alias-fill-elevated);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);padding:9px 16px;border-radius:999px;font-size:12px;box-shadow:0 8px 24px rgb(0 0 0/.18);display:flex;align-items:center;gap:8px;animation:archv-pop .18s ease-out;max-width:min(90vw,420px)}
|
|
72
|
+
.archv-spin{width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dsw-alias-label-secondary) 35%,transparent);border-top-color:var(--dsw-alias-label-secondary);border-radius:50%;animation:archv-rot .8s linear infinite;flex:none}
|
|
73
|
+
@keyframes archv-shimmer{100%{transform:translateX(100%)}}
|
|
74
|
+
@keyframes archv-rot{to{transform:rotate(360deg)}}
|
|
75
|
+
@keyframes archv-pop{from{opacity:0;transform:translateX(-50%) translateY(10px)}}
|
|
76
|
+
@media (prefers-reduced-motion:reduce){.archv-skel-card::after{animation:none}.archv-card,.archv-btn{transition:none}.archv-status,.archv-spin{animation:none}}
|
|
77
|
+
@media (max-width:560px){.archv-card{flex-direction:column;align-items:stretch;gap:10px}.archv-actions{justify-content:flex-end}}
|
|
78
|
+
`;
|
|
79
|
+
function fmtDate(iso) {
|
|
80
|
+
if (!iso) return null;
|
|
81
|
+
const d = new Date(iso);
|
|
82
|
+
if (isNaN(d.getTime())) return null;
|
|
83
|
+
try {
|
|
84
|
+
return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }).format(d);
|
|
85
|
+
} catch {
|
|
86
|
+
return String(iso);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function pathName(p) {
|
|
90
|
+
if (!p) return null;
|
|
91
|
+
const parts = String(p).replace(/\\+$/, "").split(/[/\\]/);
|
|
92
|
+
return parts[parts.length - 1] || p;
|
|
93
|
+
}
|
|
94
|
+
async function postJSON(path, body) {
|
|
95
|
+
const res = await fetch(path, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
98
|
+
body: JSON.stringify(body || {})
|
|
99
|
+
});
|
|
100
|
+
const data = await res.json().catch(() => null);
|
|
101
|
+
if (!res.ok || !data) throw new Error(data && (data.error || data.message) || `request failed (${res.status})`);
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
function ArchivedPanel() {
|
|
105
|
+
const [items, setItems] = (0, import_react.useState)(null);
|
|
106
|
+
const [busy, setBusy] = (0, import_react.useState)(null);
|
|
107
|
+
const [confirmId, setConfirmId] = (0, import_react.useState)(null);
|
|
108
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
109
|
+
const [toast, setToast] = (0, import_react.useState)(null);
|
|
110
|
+
const timer = (0, import_react.useRef)(null);
|
|
111
|
+
const showToast = (msg) => {
|
|
112
|
+
if (timer.current) clearTimeout(timer.current);
|
|
113
|
+
setToast(msg);
|
|
114
|
+
timer.current = setTimeout(() => setToast(null), 2400);
|
|
115
|
+
};
|
|
116
|
+
const refresh = () => {
|
|
117
|
+
setError(null);
|
|
118
|
+
postJSON("/archived-sessions/list", {}).then((r) => setItems(r.items || [])).catch((e) => setError(String(e && e.message || e)));
|
|
119
|
+
};
|
|
120
|
+
(0, import_react.useEffect)(() => {
|
|
121
|
+
refresh();
|
|
122
|
+
return () => {
|
|
123
|
+
if (timer.current) clearTimeout(timer.current);
|
|
124
|
+
};
|
|
125
|
+
}, []);
|
|
126
|
+
const act = (action, it) => {
|
|
127
|
+
if (busy) return;
|
|
128
|
+
if (action === "delete") {
|
|
129
|
+
if (confirmId !== it.sessionId) {
|
|
130
|
+
setConfirmId(it.sessionId);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
setConfirmId(null);
|
|
134
|
+
} else {
|
|
135
|
+
setConfirmId(null);
|
|
136
|
+
setError(null);
|
|
137
|
+
}
|
|
138
|
+
setBusy(it.sessionId);
|
|
139
|
+
postJSON("/archived-sessions/" + action, { sessionId: it.sessionId }).then(() => {
|
|
140
|
+
setBusy(null);
|
|
141
|
+
showToast(action === "restore" ? `\u5DF2\u6062\u590D\u300C${it.title || it.sessionId}\u300D` : `\u5DF2\u5220\u9664 ${it.title || it.sessionId}`);
|
|
142
|
+
refresh();
|
|
143
|
+
}).catch((e) => {
|
|
144
|
+
setBusy(null);
|
|
145
|
+
setConfirmId(null);
|
|
146
|
+
setError(String(e && e.message || e));
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const workspaceTag = (it) => {
|
|
150
|
+
if (it.hasWorkspace && it.workspaceGone) {
|
|
151
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "archv-wtag archv-wgone", title: (it.workspacePath || "") + "\uFF08\u539F\u5DE5\u4F5C\u533A\u5DF2\u5220\u9664\uFF09", children: [
|
|
152
|
+
"\u5DE5\u4F5C\u533A\u5DF2\u5220 \xB7 ",
|
|
153
|
+
pathName(it.workspacePath) || "?"
|
|
154
|
+
] });
|
|
155
|
+
}
|
|
156
|
+
const wName = it.workspaceTitle || pathName(it.workspacePath);
|
|
157
|
+
if (wName) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-wtag", title: it.workspacePath || "", children: wName });
|
|
158
|
+
return null;
|
|
159
|
+
};
|
|
160
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv", role: "region", "aria-label": "\u5F52\u6863\u4F1A\u8BDD", children: [
|
|
161
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: CSS }),
|
|
162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-head", children: [
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "archv-title", children: "\u5F52\u6863\u4F1A\u8BDD" }),
|
|
164
|
+
items !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-count", "aria-label": `${items.length} \u4E2A\u5F52\u6863\u4F1A\u8BDD`, children: items.length })
|
|
165
|
+
] }),
|
|
166
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "archv-sub", children: "\u88AB\u5F52\u6863\uFF08\u4ECE\u4FA7\u680F\u9690\u85CF\uFF09\u7684\u4F1A\u8BDD\u90FD\u5728\u8FD9\u91CC\u3002\u6062\u590D\u4F1A\u628A\u4F1A\u8BDD\u653E\u56DE\u539F\u5206\u7EC4\uFF1B\u5220\u9664\u4F1A\u5F7B\u5E95\u79FB\u9664\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u53EF\u6062\u590D\u3002" }),
|
|
167
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-err", role: "alert", children: [
|
|
168
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: error }),
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-errretry", onClick: refresh, children: "\u91CD\u8BD5" })
|
|
170
|
+
] }),
|
|
171
|
+
items === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-skel", "aria-label": "\u52A0\u8F7D\u4E2D", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-skel-card" }, i)) }) : items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-empty", children: "\u76EE\u524D\u6CA1\u6709\u5F52\u6863\u4F1A\u8BDD\u3002\u5728\u4FA7\u680F\u4EFB\u4E00\u4F1A\u8BDD\u7684\u83DC\u5355\u91CC\u9009\u62E9\u300C\u5F52\u6863\u300D\u5373\u53EF\u6536\u7EB3\u8FDB\u6765\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-list", role: "list", children: items.map((it) => {
|
|
172
|
+
const isConfirm = confirmId === it.sessionId;
|
|
173
|
+
const date = fmtDate(it.createdAt);
|
|
174
|
+
const wtag = workspaceTag(it);
|
|
175
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-card", role: "listitem", children: [
|
|
176
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-main", children: [
|
|
177
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-name", title: it.title || "", children: it.title || "(\u65E0\u6807\u9898)" }),
|
|
178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-meta", children: [
|
|
179
|
+
date && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-date", children: date }),
|
|
180
|
+
date && wtag && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-dot", children: "\xB7" }),
|
|
181
|
+
wtag,
|
|
182
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-id", children: it.sessionId })
|
|
183
|
+
] })
|
|
184
|
+
] }),
|
|
185
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-actions", children: [
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "archv-btn archv-restore", disabled: busy !== null, "aria-label": "\u6062\u590D " + (it.title || it.sessionId), onClick: () => act("restore", it), children: [
|
|
187
|
+
busy === it.sessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-spin", "aria-hidden": "true" }),
|
|
188
|
+
"\u6062\u590D"
|
|
189
|
+
] }),
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: busy !== null, "aria-label": (isConfirm ? "\u786E\u8BA4\u5220\u9664 " : "\u5220\u9664 ") + (it.title || it.sessionId), onClick: () => act("delete", it), children: isConfirm ? "\u786E\u8BA4\u5220\u9664?" : "\u5220\u9664" })
|
|
191
|
+
] })
|
|
192
|
+
] }, it.sessionId);
|
|
193
|
+
}) }),
|
|
194
|
+
toast && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-status", role: "status", children: toast })
|
|
195
|
+
] });
|
|
196
|
+
}
|
|
197
|
+
function apply(ctx) {
|
|
198
|
+
ctx.slots.inject(
|
|
199
|
+
"settings.section",
|
|
200
|
+
() => ctx.slots.register(
|
|
201
|
+
{ name: "settings.section", id: "archived-sessions", order: 90, label: "\u5F52\u6863\u4F1A\u8BDD" },
|
|
202
|
+
(props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArchivedPanel, { ...props })
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return module.exports; } });
|
|
207
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/client/index.jsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * dsh-archived-sessions \u2014 browser half: renders the \"\u5F52\u6863\u4F1A\u8BDD\" Settings\n * section (settings.section list slot). It talks to the host half's\n * /archived-sessions/* JSON routes (list / restore / delete) by fetch,\n * showing title, session date, and a workspace tag (handles deleted\n * workspaces). All DOM/runtime wiring failures are logged, never thrown \u2014 a\n * thrown plugin apply takes down the whole web-shell boot.\n *\n * @module dsh-archived-sessions/client\n */\n\nimport React, { useEffect, useRef, useState } from 'react'\n\nexport const inject = ['slots']\n\nconst CSS = `\n.archv{display:flex;flex-direction:column;gap:4px;max-width:800px;padding:8px 2px 28px}\n.archv-head{display:flex;align-items:center;gap:10px;margin:0 0 2px}\n.archv-title{font-size:16px;font-weight:650;color:var(--dsw-alias-label-primary);letter-spacing:-0.01em;margin:0}\n.archv-count{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-subtle);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:1px 8px;flex:none}\n.archv-sub{font-size:12px;line-height:1.55;color:var(--dsw-alias-label-tertiary);margin:0 0 12px;max-width:64ch}\n.archv-err{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);border-radius:10px;color:var(--dsw-alias-state-error-primary);font-size:12px;margin-bottom:10px}\n.archv-errretry{appearance:none;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent);background:transparent;color:inherit;border-radius:8px;padding:4px 10px;font-size:11px;cursor:pointer;flex:none}\n.archv-errretry:focus-visible,.archv-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.archv-list{display:flex;flex-direction:column;gap:8px}\n.archv-card{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-fill-elevated);transition:border-color .15s ease,background-color .15s ease}\n.archv-card:hover{border-color:var(--dsw-alias-border-l4)}\n.archv-main{min-width:0;display:flex;flex-direction:column;gap:4px}\n.archv-name{font-size:13px;font-weight:550;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.archv-meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap;min-width:0}\n.archv-wtag{display:inline-flex;align-items:center;gap:4px;max-width:100%;font-size:11px;font-weight:500;line-height:1;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-subtle);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.archv-wgone{border-style:dashed;color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}\n.archv-date{font-size:11px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none}\n.archv-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10.5px;color:var(--dsw-alias-label-tertiary);flex:none}\n.archv-dot{color:var(--dsw-alias-border-l3);flex:none}\n.archv-actions{display:flex;gap:8px;flex:none}\n.archv-btn{appearance:none;min-height:32px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-secondary);border-radius:9px;font-size:12px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:background-color .15s ease,border-color .15s ease,color .15s ease}\n.archv-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}\n.archv-btn:disabled{opacity:.5;cursor:default}\n.archv-restore{min-width:62px;justify-content:center}\n.archv-del{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent)}\n.archv-del:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}\n.archv-empty{display:flex;align-items:center;gap:10px;padding:20px 14px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}\n.archv-skel{display:flex;flex-direction:column;gap:8px}\n.archv-skel-card{height:58px;border-radius:12px;background:var(--dsw-alias-fill-subtle);position:relative;overflow:hidden}\n.archv-skel-card::after{content:'';position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--dsw-alias-fill-elevated) 75%,transparent),transparent);animation:archv-shimmer 1.4s infinite}\n.archv-status{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:60;background:var(--dsw-alias-fill-elevated);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);padding:9px 16px;border-radius:999px;font-size:12px;box-shadow:0 8px 24px rgb(0 0 0/.18);display:flex;align-items:center;gap:8px;animation:archv-pop .18s ease-out;max-width:min(90vw,420px)}\n.archv-spin{width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dsw-alias-label-secondary) 35%,transparent);border-top-color:var(--dsw-alias-label-secondary);border-radius:50%;animation:archv-rot .8s linear infinite;flex:none}\n@keyframes archv-shimmer{100%{transform:translateX(100%)}}\n@keyframes archv-rot{to{transform:rotate(360deg)}}\n@keyframes archv-pop{from{opacity:0;transform:translateX(-50%) translateY(10px)}}\n@media (prefers-reduced-motion:reduce){.archv-skel-card::after{animation:none}.archv-card,.archv-btn{transition:none}.archv-status,.archv-spin{animation:none}}\n@media (max-width:560px){.archv-card{flex-direction:column;align-items:stretch;gap:10px}.archv-actions{justify-content:flex-end}}\n`\n\nfunction fmtDate(iso) {\n if (!iso) return null\n const d = new Date(iso)\n if (isNaN(d.getTime())) return null\n try {\n return new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(d)\n } catch {\n return String(iso)\n }\n}\n\nfunction pathName(p) {\n if (!p) return null\n const parts = String(p).replace(/\\\\+$/, '').split(/[/\\\\]/)\n return parts[parts.length - 1] || p\n}\n\nasync function postJSON(path, body) {\n const res = await fetch(path, {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'application/json' },\n body: JSON.stringify(body || {}),\n })\n const data = await res.json().catch(() => null)\n if (!res.ok || !data) throw new Error((data && (data.error || data.message)) || `request failed (${res.status})`)\n return data\n}\n\nfunction ArchivedPanel() {\n const [items, setItems] = useState(null)\n const [busy, setBusy] = useState(null)\n const [confirmId, setConfirmId] = useState(null)\n const [error, setError] = useState(null)\n const [toast, setToast] = useState(null)\n const timer = useRef(null)\n\n const showToast = (msg) => {\n if (timer.current) clearTimeout(timer.current)\n setToast(msg)\n timer.current = setTimeout(() => setToast(null), 2400)\n }\n\n const refresh = () => {\n setError(null)\n postJSON('/archived-sessions/list', {})\n .then((r) => setItems(r.items || []))\n .catch((e) => setError(String((e && e.message) || e)))\n }\n\n useEffect(() => {\n refresh()\n return () => { if (timer.current) clearTimeout(timer.current) }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const act = (action, it) => {\n if (busy) return\n if (action === 'delete') {\n if (confirmId !== it.sessionId) { setConfirmId(it.sessionId); return }\n setConfirmId(null)\n } else { setConfirmId(null); setError(null) }\n setBusy(it.sessionId)\n postJSON('/archived-sessions/' + action, { sessionId: it.sessionId })\n .then(() => {\n setBusy(null)\n showToast(action === 'restore' ? `\u5DF2\u6062\u590D\u300C${it.title || it.sessionId}\u300D` : `\u5DF2\u5220\u9664 ${it.title || it.sessionId}`)\n refresh()\n })\n .catch((e) => { setBusy(null); setConfirmId(null); setError(String((e && e.message) || e)) })\n }\n\n const workspaceTag = (it) => {\n if (it.hasWorkspace && it.workspaceGone) {\n return (\n <span className=\"archv-wtag archv-wgone\" title={(it.workspacePath || '') + '\uFF08\u539F\u5DE5\u4F5C\u533A\u5DF2\u5220\u9664\uFF09'}>\n \u5DE5\u4F5C\u533A\u5DF2\u5220 \u00B7 {pathName(it.workspacePath) || '?'}\n </span>\n )\n }\n const wName = it.workspaceTitle || pathName(it.workspacePath)\n if (wName) return <span className=\"archv-wtag\" title={it.workspacePath || ''}>{wName}</span>\n return null\n }\n\n return (\n <div className=\"archv\" role=\"region\" aria-label=\"\u5F52\u6863\u4F1A\u8BDD\">\n <style>{CSS}</style>\n <div className=\"archv-head\">\n <h2 className=\"archv-title\">\u5F52\u6863\u4F1A\u8BDD</h2>\n {items !== null && <span className=\"archv-count\" aria-label={`${items.length} \u4E2A\u5F52\u6863\u4F1A\u8BDD`}>{items.length}</span>}\n </div>\n <p className=\"archv-sub\">\n \u88AB\u5F52\u6863\uFF08\u4ECE\u4FA7\u680F\u9690\u85CF\uFF09\u7684\u4F1A\u8BDD\u90FD\u5728\u8FD9\u91CC\u3002\u6062\u590D\u4F1A\u628A\u4F1A\u8BDD\u653E\u56DE\u539F\u5206\u7EC4\uFF1B\u5220\u9664\u4F1A\u5F7B\u5E95\u79FB\u9664\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u53EF\u6062\u590D\u3002\n </p>\n {error && (\n <div className=\"archv-err\" role=\"alert\">\n <span>{error}</span>\n <button type=\"button\" className=\"archv-errretry\" onClick={refresh}>\u91CD\u8BD5</button>\n </div>\n )}\n {items === null ? (\n <div className=\"archv-skel\" aria-label=\"\u52A0\u8F7D\u4E2D\">\n {[0, 1, 2].map((i) => <div key={i} className=\"archv-skel-card\" />)}\n </div>\n ) : items.length === 0 ? (\n <div className=\"archv-empty\">\u76EE\u524D\u6CA1\u6709\u5F52\u6863\u4F1A\u8BDD\u3002\u5728\u4FA7\u680F\u4EFB\u4E00\u4F1A\u8BDD\u7684\u83DC\u5355\u91CC\u9009\u62E9\u300C\u5F52\u6863\u300D\u5373\u53EF\u6536\u7EB3\u8FDB\u6765\u3002</div>\n ) : (\n <div className=\"archv-list\" role=\"list\">\n {items.map((it) => {\n const isConfirm = confirmId === it.sessionId\n const date = fmtDate(it.createdAt)\n const wtag = workspaceTag(it)\n return (\n <div key={it.sessionId} className=\"archv-card\" role=\"listitem\">\n <div className=\"archv-main\">\n <div className=\"archv-name\" title={it.title || ''}>{it.title || '(\u65E0\u6807\u9898)'}</div>\n <div className=\"archv-meta\">\n {date && <span className=\"archv-date\">{date}</span>}\n {date && wtag && <span className=\"archv-dot\">\u00B7</span>}\n {wtag}\n <span className=\"archv-id\">{it.sessionId}</span>\n </div>\n </div>\n <div className=\"archv-actions\">\n <button type=\"button\" className=\"archv-btn archv-restore\" disabled={busy !== null} aria-label={'\u6062\u590D ' + (it.title || it.sessionId)} onClick={() => act('restore', it)}>\n {busy === it.sessionId && <span className=\"archv-spin\" aria-hidden=\"true\" />}\u6062\u590D\n </button>\n <button type=\"button\" className=\"archv-btn archv-del\" disabled={busy !== null} aria-label={(isConfirm ? '\u786E\u8BA4\u5220\u9664 ' : '\u5220\u9664 ') + (it.title || it.sessionId)} onClick={() => act('delete', it)}>\n {isConfirm ? '\u786E\u8BA4\u5220\u9664?' : '\u5220\u9664'}\n </button>\n </div>\n </div>\n )\n })}\n </div>\n )}\n {toast && <div className=\"archv-status\" role=\"status\">{toast}</div>}\n </div>\n )\n}\n\nexport function apply(ctx) {\n ctx.slots.inject('settings.section', () =>\n ctx.slots.register(\n { name: 'settings.section', id: 'archived-sessions', order: 90, label: '\u5F52\u6863\u4F1A\u8BDD' },\n (props) => <ArchivedPanel {...props} />,\n ),\n )\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,mBAAmD;AAsH3C;AApHD,IAAM,SAAS,CAAC,OAAO;AAE9B,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwCZ,SAAS,QAAQ,KAAK;AACpB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,IAAI,KAAK,GAAG;AACtB,MAAI,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AAC/B,MAAI;AACF,WAAO,IAAI,KAAK,eAAe,SAAS,EAAE,MAAM,WAAW,OAAO,SAAS,KAAK,WAAW,MAAM,WAAW,QAAQ,UAAU,CAAC,EAAE,OAAO,CAAC;AAAA,EAC3I,QAAQ;AACN,WAAO,OAAO,GAAG;AAAA,EACnB;AACF;AAEA,SAAS,SAAS,GAAG;AACnB,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,QAAQ,OAAO,CAAC,EAAE,QAAQ,QAAQ,EAAE,EAAE,MAAM,OAAO;AACzD,SAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AACpC;AAEA,eAAe,SAAS,MAAM,MAAM;AAClC,QAAM,MAAM,MAAM,MAAM,MAAM;AAAA,IAC5B,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oBAAoB,QAAQ,mBAAmB;AAAA,IAC1E,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC;AAAA,EACjC,CAAC;AACD,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC9C,MAAI,CAAC,IAAI,MAAM,CAAC,KAAM,OAAM,IAAI,MAAO,SAAS,KAAK,SAAS,KAAK,YAAa,mBAAmB,IAAI,MAAM,GAAG;AAChH,SAAO;AACT;AAEA,SAAS,gBAAgB;AACvB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,IAAI;AACvC,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAS,IAAI;AACrC,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,IAAI;AACvC,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,IAAI;AACvC,QAAM,YAAQ,qBAAO,IAAI;AAEzB,QAAM,YAAY,CAAC,QAAQ;AACzB,QAAI,MAAM,QAAS,cAAa,MAAM,OAAO;AAC7C,aAAS,GAAG;AACZ,UAAM,UAAU,WAAW,MAAM,SAAS,IAAI,GAAG,IAAI;AAAA,EACvD;AAEA,QAAM,UAAU,MAAM;AACpB,aAAS,IAAI;AACb,aAAS,2BAA2B,CAAC,CAAC,EACnC,KAAK,CAAC,MAAM,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EACnC,MAAM,CAAC,MAAM,SAAS,OAAQ,KAAK,EAAE,WAAY,CAAC,CAAC,CAAC;AAAA,EACzD;AAEA,8BAAU,MAAM;AACd,YAAQ;AACR,WAAO,MAAM;AAAE,UAAI,MAAM,QAAS,cAAa,MAAM,OAAO;AAAA,IAAE;AAAA,EAEhE,GAAG,CAAC,CAAC;AAEL,QAAM,MAAM,CAAC,QAAQ,OAAO;AAC1B,QAAI,KAAM;AACV,QAAI,WAAW,UAAU;AACvB,UAAI,cAAc,GAAG,WAAW;AAAE,qBAAa,GAAG,SAAS;AAAG;AAAA,MAAO;AACrE,mBAAa,IAAI;AAAA,IACnB,OAAO;AAAE,mBAAa,IAAI;AAAG,eAAS,IAAI;AAAA,IAAE;AAC5C,YAAQ,GAAG,SAAS;AACpB,aAAS,wBAAwB,QAAQ,EAAE,WAAW,GAAG,UAAU,CAAC,EACjE,KAAK,MAAM;AACV,cAAQ,IAAI;AACZ,gBAAU,WAAW,YAAY,2BAAO,GAAG,SAAS,GAAG,SAAS,WAAM,sBAAO,GAAG,SAAS,GAAG,SAAS,EAAE;AACvG,cAAQ;AAAA,IACV,CAAC,EACA,MAAM,CAAC,MAAM;AAAE,cAAQ,IAAI;AAAG,mBAAa,IAAI;AAAG,eAAS,OAAQ,KAAK,EAAE,WAAY,CAAC,CAAC;AAAA,IAAE,CAAC;AAAA,EAChG;AAEA,QAAM,eAAe,CAAC,OAAO;AAC3B,QAAI,GAAG,gBAAgB,GAAG,eAAe;AACvC,aACE,6CAAC,UAAK,WAAU,0BAAyB,QAAQ,GAAG,iBAAiB,MAAM,0DAAa;AAAA;AAAA,QAC7E,SAAS,GAAG,aAAa,KAAK;AAAA,SACzC;AAAA,IAEJ;AACA,UAAM,QAAQ,GAAG,kBAAkB,SAAS,GAAG,aAAa;AAC5D,QAAI,MAAO,QAAO,4CAAC,UAAK,WAAU,cAAa,OAAO,GAAG,iBAAiB,IAAK,iBAAM;AACrF,WAAO;AAAA,EACT;AAEA,SACE,6CAAC,SAAI,WAAU,SAAQ,MAAK,UAAS,cAAW,4BAC9C;AAAA,gDAAC,WAAO,eAAI;AAAA,IACZ,6CAAC,SAAI,WAAU,cACb;AAAA,kDAAC,QAAG,WAAU,eAAc,sCAAI;AAAA,MAC/B,UAAU,QAAQ,4CAAC,UAAK,WAAU,eAAc,cAAY,GAAG,MAAM,MAAM,mCAAW,gBAAM,QAAO;AAAA,OACtG;AAAA,IACA,4CAAC,OAAE,WAAU,aAAY,wSAEzB;AAAA,IACC,SACC,6CAAC,SAAI,WAAU,aAAY,MAAK,SAC9B;AAAA,kDAAC,UAAM,iBAAM;AAAA,MACb,4CAAC,YAAO,MAAK,UAAS,WAAU,kBAAiB,SAAS,SAAS,0BAAE;AAAA,OACvE;AAAA,IAED,UAAU,OACT,4CAAC,SAAI,WAAU,cAAa,cAAW,sBACpC,WAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,4CAAC,SAAY,WAAU,qBAAb,CAA+B,CAAE,GACnE,IACE,MAAM,WAAW,IACnB,4CAAC,SAAI,WAAU,eAAc,oNAAiC,IAE9D,4CAAC,SAAI,WAAU,cAAa,MAAK,QAC9B,gBAAM,IAAI,CAAC,OAAO;AACjB,YAAM,YAAY,cAAc,GAAG;AACnC,YAAM,OAAO,QAAQ,GAAG,SAAS;AACjC,YAAM,OAAO,aAAa,EAAE;AAC5B,aACE,6CAAC,SAAuB,WAAU,cAAa,MAAK,YAClD;AAAA,qDAAC,SAAI,WAAU,cACb;AAAA,sDAAC,SAAI,WAAU,cAAa,OAAO,GAAG,SAAS,IAAK,aAAG,SAAS,wBAAQ;AAAA,UACxE,6CAAC,SAAI,WAAU,cACZ;AAAA,oBAAQ,4CAAC,UAAK,WAAU,cAAc,gBAAK;AAAA,YAC3C,QAAQ,QAAQ,4CAAC,UAAK,WAAU,aAAY,kBAAC;AAAA,YAC7C;AAAA,YACD,4CAAC,UAAK,WAAU,YAAY,aAAG,WAAU;AAAA,aAC3C;AAAA,WACF;AAAA,QACA,6CAAC,SAAI,WAAU,iBACb;AAAA,uDAAC,YAAO,MAAK,UAAS,WAAU,2BAA0B,UAAU,SAAS,MAAM,cAAY,mBAAS,GAAG,SAAS,GAAG,YAAY,SAAS,MAAM,IAAI,WAAW,EAAE,GAChK;AAAA,qBAAS,GAAG,aAAa,4CAAC,UAAK,WAAU,cAAa,eAAY,QAAO;AAAA,YAAG;AAAA,aAC/E;AAAA,UACA,4CAAC,YAAO,MAAK,UAAS,WAAU,uBAAsB,UAAU,SAAS,MAAM,eAAa,YAAY,8BAAU,oBAAU,GAAG,SAAS,GAAG,YAAY,SAAS,MAAM,IAAI,UAAU,EAAE,GACnL,sBAAY,8BAAU,gBACzB;AAAA,WACF;AAAA,WAjBQ,GAAG,SAkBb;AAAA,IAEJ,CAAC,GACH;AAAA,IAED,SAAS,4CAAC,SAAI,WAAU,gBAAe,MAAK,UAAU,iBAAM;AAAA,KAC/D;AAEJ;AAEO,SAAS,MAAM,KAAK;AACzB,MAAI,MAAM;AAAA,IAAO;AAAA,IAAoB,MACnC,IAAI,MAAM;AAAA,MACR,EAAE,MAAM,oBAAoB,IAAI,qBAAqB,OAAO,IAAI,OAAO,2BAAO;AAAA,MAC9E,CAAC,UAAU,4CAAC,iBAAe,GAAG,OAAO;AAAA,IACvC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|