dsh-ws-recency 0.9.0 → 0.9.2
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.md +4 -3
- package/client.js +58 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,8 +12,9 @@ DSH 已经把**组内**会话按 `updatedAt` 倒序排好了(排序方式 =
|
|
|
12
12
|
|
|
13
13
|
会话超过 `foldDays`(默认 **3 天**)没有更新就折叠起来;整个工作区都过期时,它只剩一行工作区标题——需要翻历史时点开就行。
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
15
|
+
- **每组一个控件**:宿主的「展开其余 N 条会话」被隐藏,由插件的 `展开历史 N` / `收起历史` 接管。N = 宿主还没渲染的行 + 已折叠的过期行;
|
|
16
|
+
- 点「展开历史」→ 插件逐轮驱动宿主那个隐藏按钮(每轮它会多渲染 5 条,剩下 ≤5 条时一次全出),直到整组都渲染出来,同时展开过期行——**一次点到全展开**;
|
|
17
|
+
- 点「收起历史」→ 重新折叠过期行(宿主已渲染的部分仍由插件按时间折叠,不会丢失);
|
|
17
18
|
- 选中 / 运行中 / 等待处理的会话**永不折叠**(可在设置里关掉这个豁免);
|
|
18
19
|
- 空白草稿行(新会话)不参与折叠判断;
|
|
19
20
|
- 每 5 分钟自动重算一次,所以跨过 3 天边界的会话会自己折叠,不用刷新。
|
|
@@ -97,7 +98,7 @@ dsh plugin --profile <profile> add github:chyxhtc/dsh-ws-recency
|
|
|
97
98
|
或固定版本的预构建包:
|
|
98
99
|
|
|
99
100
|
```powershell
|
|
100
|
-
dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.9.
|
|
101
|
+
dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.9.2/dsh-ws-recency-0.9.2.tgz
|
|
101
102
|
```
|
|
102
103
|
|
|
103
104
|
装完把 `dsh-ws-recency` 加进 `~/.hypers-work/profiles/<profile>/package.json` 的 `dsh.profile.bundles`,重启应用。
|
package/client.js
CHANGED
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
var MIN_DAYS = 1;
|
|
70
70
|
var MAX_DAYS = 90;
|
|
71
71
|
var DEFAULT_DAYS = 3;
|
|
72
|
+
/** Safety stop for driving the host's own "show more" (5 rows per click). */
|
|
73
|
+
var MAX_EXPAND_CLICKS = 40;
|
|
72
74
|
var TICK_MS = 5 * 60 * 1000;
|
|
73
75
|
|
|
74
76
|
var data = { sessions: null, workspaces: null, settings: null, statuses: null, actions: null, service: null, t: null };
|
|
@@ -78,13 +80,12 @@
|
|
|
78
80
|
var written = new Set();
|
|
79
81
|
var roots = new Set();
|
|
80
82
|
var hidden = new Set();
|
|
81
|
-
var labelled = new Set();
|
|
82
83
|
var toggles = new Set();
|
|
83
84
|
var dots = new Set();
|
|
84
85
|
var pinHidden = new Set();
|
|
85
86
|
var pinHost = null;
|
|
87
|
+
var expandClicks = new Map();
|
|
86
88
|
var expandedSections = new Set();
|
|
87
|
-
var clickMarked = new WeakSet();
|
|
88
89
|
var headerMarked = new WeakSet();
|
|
89
90
|
var disposed = false;
|
|
90
91
|
|
|
@@ -93,6 +94,7 @@
|
|
|
93
94
|
"[" + ROOT_ATTR + '="true"]>*{flex:0 0 auto}',
|
|
94
95
|
"[" + HIDDEN_ATTR + '="true"]{display:none !important}',
|
|
95
96
|
"[" + PIN_HIDDEN_ATTR + '="true"]{display:none !important}',
|
|
97
|
+
'[class*="' + OVERFLOW_TOKEN + '"]{display:none !important}',
|
|
96
98
|
".wsr-pin{display:flex;flex-direction:column;flex:0 0 auto;padding:2px 0 6px;margin-bottom:4px;border-bottom:.5px solid var(--dsw-alias-border-l2)}",
|
|
97
99
|
".wsr-pin-head{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary);padding:4px 12px 2px}",
|
|
98
100
|
".wsr-pin-row{display:flex;align-items:center;gap:8px;height:30px;padding:0 12px;border-radius:6px;cursor:pointer}",
|
|
@@ -325,11 +327,6 @@
|
|
|
325
327
|
return out;
|
|
326
328
|
}
|
|
327
329
|
|
|
328
|
-
function overflowButtonOf(section) {
|
|
329
|
-
if (typeof section.querySelector !== "function") return null;
|
|
330
|
-
return section.querySelector('button[class*="' + OVERFLOW_TOKEN + '"]');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
330
|
function isActiveRow(row) {
|
|
334
331
|
if (typeof row.querySelector !== "function") return false;
|
|
335
332
|
return row.querySelector('[data-state="ongoing"], [data-state="warning"]') !== null;
|
|
@@ -371,55 +368,23 @@
|
|
|
371
368
|
return session !== undefined && session !== null && session.blank === true;
|
|
372
369
|
}
|
|
373
370
|
|
|
374
|
-
/**
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
function rewriteLabel(button, extraHidden) {
|
|
379
|
-
if (button === null || button === undefined) return;
|
|
380
|
-
if (typeof button.textContent !== "string" || button.dataset === undefined) return;
|
|
381
|
-
var base = button.dataset.wsrBase;
|
|
382
|
-
if (typeof base !== "string") {
|
|
383
|
-
base = button.textContent;
|
|
384
|
-
button.dataset.wsrBase = base;
|
|
385
|
-
labelled.add(button);
|
|
386
|
-
}
|
|
387
|
-
var match = /\d+/.exec(base);
|
|
388
|
-
if (match === null) return;
|
|
389
|
-
var hostCount = Number(match[0]);
|
|
390
|
-
if (!Number.isFinite(hostCount)) return;
|
|
391
|
-
var total = hostCount + extraHidden;
|
|
392
|
-
var text = total <= 0 ? base : base.slice(0, match.index) + String(total) + base.slice(match.index + match[0].length);
|
|
393
|
-
if (button.textContent !== text) button.textContent = text;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function releaseLabel(button) {
|
|
397
|
-
if (button === null || button === undefined || button.dataset === undefined) return;
|
|
398
|
-
var base = button.dataset.wsrBase;
|
|
399
|
-
if (typeof base === "string") {
|
|
400
|
-
if (button.textContent !== base) button.textContent = base;
|
|
401
|
-
delete button.dataset.wsrBase;
|
|
402
|
-
}
|
|
403
|
-
labelled.delete(button);
|
|
371
|
+
/** The host's own "show more" button, hidden by CSS but still clickable. */
|
|
372
|
+
function overflowButtonOf(section) {
|
|
373
|
+
if (typeof section.querySelector !== "function") return null;
|
|
374
|
+
return section.querySelector('button[class*="' + OVERFLOW_TOKEN + '"]');
|
|
404
375
|
}
|
|
405
376
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
button.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
exactly what decides whether the click reveals or hides. */
|
|
415
|
-
var expanded = typeof button.getAttribute === "function"
|
|
416
|
-
&& button.getAttribute("aria-expanded") === "true";
|
|
417
|
-
if (expanded) expandedSections.delete(key);
|
|
418
|
-
else expandedSections.add(key);
|
|
419
|
-
schedule();
|
|
420
|
-
});
|
|
377
|
+
/** The count the host reports on its own button, 0 when there is none. */
|
|
378
|
+
function labelNumber(button) {
|
|
379
|
+
if (button === null || button === undefined) return 0;
|
|
380
|
+
var text = typeof button.textContent === "string" ? button.textContent : "";
|
|
381
|
+
var match = /\d+/.exec(text);
|
|
382
|
+
if (match === null) return 0;
|
|
383
|
+
var value = Number(match[0]);
|
|
384
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
421
385
|
}
|
|
422
386
|
|
|
387
|
+
/** Collapsing a group resets its fold state, so the click is observed. */
|
|
423
388
|
function markHeader(header, key) {
|
|
424
389
|
if (typeof header.addEventListener !== "function" || headerMarked.has(header)) return;
|
|
425
390
|
headerMarked.add(header);
|
|
@@ -441,11 +406,15 @@
|
|
|
441
406
|
button.addEventListener("click", function (event) {
|
|
442
407
|
if (disposed) return;
|
|
443
408
|
if (event !== undefined && typeof event.stopPropagation === "function") event.stopPropagation();
|
|
444
|
-
|
|
409
|
+
var current = button.wsrOnClick;
|
|
410
|
+
if (typeof current === "function") current();
|
|
445
411
|
});
|
|
446
412
|
section.appendChild(button);
|
|
447
413
|
toggles.add(button);
|
|
448
414
|
}
|
|
415
|
+
/* The handler is refreshed every pass: the button toggles between "show"
|
|
416
|
+
and "hide", and a captured first callback would only ever show. */
|
|
417
|
+
button.wsrOnClick = onClick;
|
|
449
418
|
if (button.textContent !== label) button.textContent = label;
|
|
450
419
|
return button;
|
|
451
420
|
}
|
|
@@ -462,6 +431,13 @@
|
|
|
462
431
|
* Fold the rows the host rendered that have not changed for `foldDays`, and
|
|
463
432
|
* offer the group's own control to bring them back. A workspace whose every
|
|
464
433
|
* session is that old therefore renders as a bare header.
|
|
434
|
+
*
|
|
435
|
+
* The host's own "show more" button is hidden (CSS) and driven from here: it
|
|
436
|
+
* only renders five rows at a time and cannot collapse once everything is
|
|
437
|
+
* rendered, which made "show more" look broken next to a fold. Expanding the
|
|
438
|
+
* group now clicks that hidden button once per pass until the host has
|
|
439
|
+
* rendered every session, so one click reveals everything; collapsing just
|
|
440
|
+
* folds the stale rows again.
|
|
465
441
|
*/
|
|
466
442
|
function foldRows(entries, settings, sessions, now) {
|
|
467
443
|
var cutoff = now - settings.foldDays * DAY;
|
|
@@ -471,10 +447,27 @@
|
|
|
471
447
|
var rows = rowElements(section);
|
|
472
448
|
var hostButton = overflowButtonOf(section);
|
|
473
449
|
markHeader(entry.header, entry.key);
|
|
474
|
-
if (hostButton !== null) markButton(hostButton, entry.key);
|
|
475
|
-
releaseLabel(hostButton);
|
|
476
450
|
|
|
477
451
|
var expanded = expandedSections.has(entry.key);
|
|
452
|
+
var hostHidden = hostButton === null ? 0 : labelNumber(hostButton);
|
|
453
|
+
var hostExpanded = hostButton !== null
|
|
454
|
+
&& typeof hostButton.getAttribute === "function"
|
|
455
|
+
&& hostButton.getAttribute("aria-expanded") === "true";
|
|
456
|
+
if (expanded && hostButton !== null && !hostExpanded) {
|
|
457
|
+
/* The host renders five more per click (or everything when five or
|
|
458
|
+
fewer remain) and reports `aria-expanded` once nothing is hidden.
|
|
459
|
+
Stopping on that flag is what keeps the loop from collapsing the
|
|
460
|
+
very thing it just expanded - a click on an expanded host button
|
|
461
|
+
resets the watermark. */
|
|
462
|
+
var used = expandClicks.get(entry.key) || 0;
|
|
463
|
+
if (used < MAX_EXPAND_CLICKS) {
|
|
464
|
+
expandClicks.set(entry.key, used + 1);
|
|
465
|
+
hostButton.click();
|
|
466
|
+
}
|
|
467
|
+
} else if (!expanded) {
|
|
468
|
+
expandClicks.delete(entry.key);
|
|
469
|
+
}
|
|
470
|
+
|
|
478
471
|
var folded = 0;
|
|
479
472
|
for (var j = 0; j < rows.length; j += 1) {
|
|
480
473
|
var row = rows[j];
|
|
@@ -493,23 +486,22 @@
|
|
|
493
486
|
}
|
|
494
487
|
}
|
|
495
488
|
|
|
496
|
-
/* ONE control per group:
|
|
497
|
-
|
|
498
|
-
folded
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
rewriteLabel(hostButton, expanded ? 0 : folded);
|
|
503
|
-
} else if (expanded) {
|
|
489
|
+
/* ONE control per group: its count is everything the group still hides,
|
|
490
|
+
both the rows the host did not render and the stale rows this plugin
|
|
491
|
+
folded. Expanding drives the hidden host button until nothing is left
|
|
492
|
+
unrendered, so one click reveals the whole group. */
|
|
493
|
+
var remaining = folded + hostHidden;
|
|
494
|
+
if (expanded) {
|
|
504
495
|
(function (entryKey) {
|
|
505
496
|
ensureToggle(section, entryKey, t("fold.hide"), function () {
|
|
506
497
|
expandedSections.delete(entryKey);
|
|
498
|
+
expandClicks.delete(entryKey);
|
|
507
499
|
schedule();
|
|
508
500
|
});
|
|
509
501
|
})(entry.key);
|
|
510
|
-
} else if (
|
|
502
|
+
} else if (remaining > 0) {
|
|
511
503
|
(function (entryKey) {
|
|
512
|
-
ensureToggle(section, entryKey, t("fold.show", { n:
|
|
504
|
+
ensureToggle(section, entryKey, t("fold.show", { n: remaining }), function () {
|
|
513
505
|
expandedSections.add(entryKey);
|
|
514
506
|
schedule();
|
|
515
507
|
});
|
|
@@ -958,8 +950,6 @@
|
|
|
958
950
|
if (element.removeAttribute !== undefined) element.removeAttribute(HIDDEN_ATTR);
|
|
959
951
|
});
|
|
960
952
|
hidden.clear();
|
|
961
|
-
labelled.forEach(function (button) { releaseLabel(button); });
|
|
962
|
-
labelled.clear();
|
|
963
953
|
toggles.forEach(function (button) { button.remove(); });
|
|
964
954
|
toggles.clear();
|
|
965
955
|
dots.forEach(function (dot) { dot.remove(); });
|
|
@@ -970,6 +960,7 @@
|
|
|
970
960
|
pinHidden.clear();
|
|
971
961
|
if (pinHost !== null && pinHost.parentElement !== null) pinHost.remove();
|
|
972
962
|
pinHost = null;
|
|
963
|
+
expandClicks.clear();
|
|
973
964
|
roots.forEach(function (root) { root.removeAttribute(ROOT_ATTR); });
|
|
974
965
|
roots.clear();
|
|
975
966
|
var marked = document.querySelectorAll("[" + STATE_ATTR + "]");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ws-recency",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Sidebar session management for DeepSeek Harness: workspace groups sorted by latest activity, stale history folded, a global pinned section, follow-up marks, and session delete into a recoverable trash.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "chyxhtc",
|