dsh-session-autarchive 0.1.5 → 0.1.6
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/README.en.md +3 -2
- package/README.md +3 -2
- package/lib/client.js +64 -0
- package/package.json +2 -2
- package/scripts/smoke.mjs +1 -0
- package/src/entry.jsx +72 -5
package/README.en.md
CHANGED
|
@@ -11,6 +11,7 @@ Archives sessions that have gone unused for longer than a configurable number of
|
|
|
11
11
|
| Where | What it looks like | What it does |
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| Session context menu | One row, "Do not auto-archive", next to Pin / Rename / Fork / Archive | Click once to mark, again to unmark |
|
|
14
|
+
| Session row trailing | On hover, a shield button next to the archive icon (hollow when unmarked, filled when marked) | Toggle "do not auto-archive" without opening the overflow menu |
|
|
14
15
|
| Session row start | A small shield on marked sessions | Shows at a glance that the session is protected |
|
|
15
16
|
| Hover card | One line, "Not auto-archived" | Same, with text |
|
|
16
17
|
| General settings | A row at the bottom, "Auto-archive after (days)" | Saved on blur or Enter, writes the same host field |
|
|
@@ -70,7 +71,7 @@ Writes are temp-file-then-rename, and reads retry — a half-written file is nev
|
|
|
70
71
|
## How it works
|
|
71
72
|
|
|
72
73
|
- The host half registers four local routes: `GET /dsh-session-autarchive/api/state`, `POST .../protect`, `POST .../config`, `POST .../run`. Loopback addresses only (`127.*`, `::1`, `::ffff:127.*`); anything else gets 403, a wrong method gets 405.
|
|
73
|
-
- The browser half mounts
|
|
74
|
+
- The browser half mounts five slots (menu row, row action, row start, hover card, settings row). Services are probed with `ctx.get`; when one is missing that entry is skipped and the others still mount.
|
|
74
75
|
- No network access, nothing uploaded. Decisions use local file times only.
|
|
75
76
|
|
|
76
77
|
## Known limits
|
|
@@ -85,7 +86,7 @@ Writes are temp-file-then-rename, and reads retry — a half-written file is nev
|
|
|
85
86
|
The source has two halves:
|
|
86
87
|
|
|
87
88
|
- `index.js` — host side (decision, archiving, four routes, the tool)
|
|
88
|
-
- `src/entry.jsx` — browser side (menu row, row shield, hover line, settings row, the 5-minute fast path)
|
|
89
|
+
- `src/entry.jsx` — browser side (menu row, row action shield, row shield, hover line, settings row, the 5-minute fast path)
|
|
89
90
|
|
|
90
91
|
Editing `src/entry.jsx` requires rebuilding. `lib/client.js` is a build artifact committed to the repository; without it the UI does not load:
|
|
91
92
|
|
package/README.md
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
把长期没动过的会话自动归档。按会话内最后一条对话结束的时间算天数,默认 5 天;已归档、置顶(可关)、标记「不自动归档」的都跳过。侧栏会话右键菜单给单个会话加标记,通用设置里改天数。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 界面上的四处
|
|
10
10
|
|
|
11
11
|
| 位置 | 样子 | 作用 |
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| 会话右键菜单 | 与「置顶会话/重命名/分叉会话/归档会话」并列的一行「不自动归档」 | 点一次标记,再点一次取消 |
|
|
14
|
+
| 会话行尾 | 悬停时归档图标旁边一个盾牌按钮(未标记空心盾、已标记实心盾) | 不用点开「更多」,直接开/关「不自动归档」 |
|
|
14
15
|
| 会话行首 | 被标记的会话多一个小盾牌 | 一眼看出这条不会被自动归档 |
|
|
15
16
|
| 悬停卡片 | 一行「不会被自动归档」 | 同上,带文字说明 |
|
|
16
17
|
| 通用设置 | 最底一行「自动归档天数」 | 失焦或回车即保存,写的是宿主同一个字段 |
|
|
@@ -85,7 +86,7 @@ dsh plugin --profile desktop add dsh-session-autarchive
|
|
|
85
86
|
源码分两半:
|
|
86
87
|
|
|
87
88
|
- `index.js` —— 宿主半边(判定、归档、四条路由、工具)
|
|
88
|
-
- `src/entry.jsx` ——
|
|
89
|
+
- `src/entry.jsx` —— 浏览器半边(菜单行、行尾盾牌按钮、行首盾牌、悬停说明、设置行、5 分钟快通道)
|
|
89
90
|
|
|
90
91
|
改过 `src/entry.jsx` 必须重新打包。`lib/client.js` 是提交进仓库的构建产物,缺了它界面不会加载:
|
|
91
92
|
|
package/lib/client.js
CHANGED
|
@@ -30,6 +30,7 @@ __export(entry_exports, {
|
|
|
30
30
|
ProtectHover: () => ProtectHover,
|
|
31
31
|
ProtectLeading: () => ProtectLeading,
|
|
32
32
|
ProtectMenuItem: () => ProtectMenuItem,
|
|
33
|
+
ProtectRowAction: () => ProtectRowAction,
|
|
33
34
|
apply: () => apply,
|
|
34
35
|
inject: () => inject
|
|
35
36
|
});
|
|
@@ -140,6 +141,35 @@ function ShieldIcon({ size = 14 }) {
|
|
|
140
141
|
}
|
|
141
142
|
);
|
|
142
143
|
}
|
|
144
|
+
function ShieldFilledIcon({ size = 14 }) {
|
|
145
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
146
|
+
"svg",
|
|
147
|
+
{
|
|
148
|
+
width: size,
|
|
149
|
+
height: size,
|
|
150
|
+
viewBox: "0 0 24 24",
|
|
151
|
+
"aria-hidden": "true",
|
|
152
|
+
focusable: "false",
|
|
153
|
+
fill: "currentColor",
|
|
154
|
+
stroke: "none",
|
|
155
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 3l7 2.6v5.2c0 4.3-3 8.2-7 9.4-4-1.2-7-5.1-7-9.4V5.6L12 3z" })
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
var ROW_ACTION = {
|
|
160
|
+
flex: "none",
|
|
161
|
+
display: "inline-flex",
|
|
162
|
+
alignItems: "center",
|
|
163
|
+
justifyContent: "center",
|
|
164
|
+
width: "16px",
|
|
165
|
+
height: "16px",
|
|
166
|
+
border: "none",
|
|
167
|
+
borderRadius: "var(--dsw-radius-xs)",
|
|
168
|
+
padding: 0,
|
|
169
|
+
background: "transparent",
|
|
170
|
+
cursor: "pointer",
|
|
171
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
172
|
+
};
|
|
143
173
|
var ITEM = {
|
|
144
174
|
display: "flex",
|
|
145
175
|
alignItems: "center",
|
|
@@ -203,6 +233,39 @@ function ProtectMenuItem(props) {
|
|
|
203
233
|
}
|
|
204
234
|
);
|
|
205
235
|
}
|
|
236
|
+
function ProtectRowAction(props) {
|
|
237
|
+
const { sessionId } = props;
|
|
238
|
+
const state = useStore();
|
|
239
|
+
const [hover, setHover] = (0, import_react.useState)(false);
|
|
240
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
241
|
+
const on = state.protected.has(sessionId);
|
|
242
|
+
const label = on ? "\u53D6\u6D88\u300C\u4E0D\u81EA\u52A8\u5F52\u6863\u300D" : "\u4E0D\u81EA\u52A8\u5F52\u6863";
|
|
243
|
+
const click = () => {
|
|
244
|
+
if (busy) return;
|
|
245
|
+
setBusy(true);
|
|
246
|
+
void toggleProtect(sessionId, !on).catch(() => {
|
|
247
|
+
}).finally(() => setBusy(false));
|
|
248
|
+
};
|
|
249
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
250
|
+
"button",
|
|
251
|
+
{
|
|
252
|
+
type: "button",
|
|
253
|
+
title: label,
|
|
254
|
+
"aria-label": label,
|
|
255
|
+
"aria-pressed": on,
|
|
256
|
+
disabled: busy,
|
|
257
|
+
onClick: click,
|
|
258
|
+
onMouseEnter: () => setHover(true),
|
|
259
|
+
onMouseLeave: () => setHover(false),
|
|
260
|
+
style: {
|
|
261
|
+
...ROW_ACTION,
|
|
262
|
+
color: hover && !busy ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-tertiary)",
|
|
263
|
+
opacity: busy ? 0.4 : 1
|
|
264
|
+
},
|
|
265
|
+
children: on ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShieldFilledIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShieldIcon, {})
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
206
269
|
function ProtectLeading(props) {
|
|
207
270
|
const state = useStore();
|
|
208
271
|
if (!state.protected.has(props.sessionId)) return null;
|
|
@@ -380,6 +443,7 @@ function apply(ctx) {
|
|
|
380
443
|
} catch {
|
|
381
444
|
}
|
|
382
445
|
place(ctx, "sidebar.workspaces.session.menu.item", 450, ProtectMenuItem);
|
|
446
|
+
place(ctx, "sidebar.workspaces.session.row.action", 150, ProtectRowAction);
|
|
383
447
|
place(ctx, "sidebar.session.row.leading", 100, ProtectLeading);
|
|
384
448
|
place(ctx, "sidebar.session.row.hover", 100, ProtectHover);
|
|
385
449
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-autarchive",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "把长期没动过的会话自动归档(默认 5 天),单个会话可以标成不归档。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": "^22.19.0 || >=24.0.0",
|
|
25
|
-
"dsh": ">=0.2.1-
|
|
25
|
+
"dsh": ">=0.2.1-alpha.3"
|
|
26
26
|
},
|
|
27
27
|
"dsh": {
|
|
28
28
|
"client": {
|
package/scripts/smoke.mjs
CHANGED
|
@@ -256,6 +256,7 @@ const client = readFileSync(join(root, 'lib', 'client.js'), 'utf8')
|
|
|
256
256
|
const idMatch = /^\s*id:\s*"([^"]+)"/m.exec(client)
|
|
257
257
|
const SLOTS = [
|
|
258
258
|
'sidebar.workspaces.session.menu.item',
|
|
259
|
+
'sidebar.workspaces.session.row.action',
|
|
259
260
|
'sidebar.session.row.leading',
|
|
260
261
|
'sidebar.session.row.hover',
|
|
261
262
|
'settings.general.item',
|
package/src/entry.jsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 超期会话自动归档(浏览器半边源码)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 会话列表上四处:
|
|
5
5
|
* 1. 会话菜单里平铺一行「不自动归档」——与官方「置顶会话/重命名/分叉会话/归档会话」同层并列,不另造「更多」;
|
|
6
|
-
* 2.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* 2. 行尾悬停图标组里、归档图标紧旁边一个盾牌按钮(order 150,落在归档 100 与置顶 200 之间),
|
|
7
|
+
* 未保护空心盾、已保护实心盾,点它开/关「不自动归档」——不用点开「更多」;
|
|
8
|
+
* 3. 被标记的会话行首一个小盾牌;
|
|
9
|
+
* 4. 悬停卡片里一行说明。
|
|
9
10
|
* 设置里一行:通用设置最底「自动归档天数」(`settings.general.item`),失焦或回车即落盘。
|
|
10
11
|
* 外加每 5 分钟拉一次宿主的候选清单:拿得到官方归档通道就顺手归档,拿不到就只刷新名单——
|
|
11
12
|
* 判定在宿主,宿主自己有兜底定时(窗口没开也照常归档)。
|
|
@@ -140,6 +141,28 @@ function ShieldIcon({ size = 14 }) {
|
|
|
140
141
|
)
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
/**
|
|
145
|
+
* 「已标记」实心盾:与描边版同一个轮廓,换成填充,被保护的会话一眼可辨。
|
|
146
|
+
* @param props.size - 边长,默认 14。
|
|
147
|
+
* @returns 一个实心盾牌。
|
|
148
|
+
*/
|
|
149
|
+
function ShieldFilledIcon({ size = 14 }) {
|
|
150
|
+
return (
|
|
151
|
+
<svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true" focusable="false"
|
|
152
|
+
fill="currentColor" stroke="none">
|
|
153
|
+
<path d="M12 3l7 2.6v5.2c0 4.3-3 8.2-7 9.4-4-1.2-7-5.1-7-9.4V5.6L12 3z" />
|
|
154
|
+
</svg>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 行尾图标按钮:数值照 Rows.module.css 的 `.iconButton`(16px 格、14px 字形、tertiary → hover primary)。 */
|
|
159
|
+
const ROW_ACTION = {
|
|
160
|
+
flex: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
161
|
+
width: '16px', height: '16px', border: 'none', borderRadius: 'var(--dsw-radius-xs)',
|
|
162
|
+
padding: 0, background: 'transparent', cursor: 'pointer',
|
|
163
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
164
|
+
}
|
|
165
|
+
|
|
143
166
|
/** 菜单行:照 ui-primitives Menu.module.css 的 `.item`。 */
|
|
144
167
|
const ITEM = {
|
|
145
168
|
display: 'flex', alignItems: 'center', gap: '6px', width: '100%', minHeight: '34px',
|
|
@@ -201,6 +224,48 @@ export function ProtectMenuItem(props) {
|
|
|
201
224
|
)
|
|
202
225
|
}
|
|
203
226
|
|
|
227
|
+
/**
|
|
228
|
+
* 行尾悬停按钮(order 150,紧挨归档图标):盾牌一档,点它开/关「不自动归档」。
|
|
229
|
+
* 未保护=空心盾,已保护=实心盾;标题与 aria-label 写明当前状态要发生什么。
|
|
230
|
+
* 与官方置顶/归档按钮同一排同一套样式;点击不打开会话行(宿主 rowActions 已截住冒泡)。
|
|
231
|
+
* @param props - 行身份。
|
|
232
|
+
* @returns 一个图标按钮。
|
|
233
|
+
*/
|
|
234
|
+
export function ProtectRowAction(props) {
|
|
235
|
+
const { sessionId } = props
|
|
236
|
+
const state = useStore()
|
|
237
|
+
const [hover, setHover] = useState(false)
|
|
238
|
+
const [busy, setBusy] = useState(false)
|
|
239
|
+
const on = state.protected.has(sessionId)
|
|
240
|
+
const label = on ? '取消「不自动归档」' : '不自动归档'
|
|
241
|
+
const click = () => {
|
|
242
|
+
if (busy) return
|
|
243
|
+
setBusy(true)
|
|
244
|
+
void toggleProtect(sessionId, !on)
|
|
245
|
+
.catch(() => { /* 路由没挂或写盘失败:不改本地状态,界面保持原样 */ })
|
|
246
|
+
.finally(() => setBusy(false))
|
|
247
|
+
}
|
|
248
|
+
return (
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
title={label}
|
|
252
|
+
aria-label={label}
|
|
253
|
+
aria-pressed={on}
|
|
254
|
+
disabled={busy}
|
|
255
|
+
onClick={click}
|
|
256
|
+
onMouseEnter={() => setHover(true)}
|
|
257
|
+
onMouseLeave={() => setHover(false)}
|
|
258
|
+
style={{
|
|
259
|
+
...ROW_ACTION,
|
|
260
|
+
color: hover && !busy ? 'var(--dsw-alias-label-primary)' : 'var(--dsw-alias-label-tertiary)',
|
|
261
|
+
opacity: busy ? 0.4 : 1,
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
{on ? <ShieldFilledIcon /> : <ShieldIcon />}
|
|
265
|
+
</button>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
204
269
|
/**
|
|
205
270
|
* 会话行首标记:被保护的会话显示盾牌(该格只在行处于空闲态时才渲染)。
|
|
206
271
|
* @param props - 行身份。
|
|
@@ -401,8 +466,10 @@ export function apply(ctx) {
|
|
|
401
466
|
} catch { /* 清理不了也只是多一个定时器 */ }
|
|
402
467
|
} catch { /* 见上 */ }
|
|
403
468
|
|
|
404
|
-
//
|
|
469
|
+
// 四处:菜单里并列一行 + 行尾悬停组里归档旁的盾牌按钮 + 行首标记 + 悬停说明。
|
|
470
|
+
// 行尾按钮 order 150:归档是 100、置顶是 200,插在两者之间,紧挨归档。
|
|
405
471
|
place(ctx, 'sidebar.workspaces.session.menu.item', 450, ProtectMenuItem)
|
|
472
|
+
place(ctx, 'sidebar.workspaces.session.row.action', 150, ProtectRowAction)
|
|
406
473
|
place(ctx, 'sidebar.session.row.leading', 100, ProtectLeading)
|
|
407
474
|
place(ctx, 'sidebar.session.row.hover', 100, ProtectHover)
|
|
408
475
|
|