dsh-ws-recency 0.7.2 → 0.8.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/README.md +18 -5
- package/client.js +228 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-ws-recency
|
|
2
2
|
|
|
3
|
-
DSH
|
|
3
|
+
DSH 侧栏的四件事:**工作区分组按最近活跃排序**、**过期历史自动折叠**、**运行中/未读/需跟进三色标记**、**所有置顶会话汇总到顶部一个区**。单独装上就有效,不依赖其它插件。
|
|
4
4
|
|
|
5
5
|
## 一、分组排序
|
|
6
6
|
|
|
@@ -32,15 +32,28 @@ DSH 已经把**组内**会话按 `updatedAt` 倒序排好了(排序方式 =
|
|
|
32
32
|
|
|
33
33
|
- 标记存在 Client store 的 `followUps`(随 `dsh.ws-recency.prefs.v1` 持久化),重启后还在;
|
|
34
34
|
- 琥珀圆点渲染在宿主自己的 `sidebar.session.row.leading` 槽里,**不往行里塞子元素**——塞进去会让后面的行整体下移一格;点圆点即取消标记;
|
|
35
|
-
-
|
|
36
|
-
-
|
|
35
|
+
- **标记只有你手动取消才会消失**:运行中/完成未读期间它只是被更强的状态盖住显示,跑完、读完(没有新未读)之后琥珀自己回来;
|
|
36
|
+
- 不受打开时长、会话是否继续对话影响——继续对话恰恰常常是还欠一次跟进的原因。
|
|
37
|
+
|
|
38
|
+
## 四、全局置顶区
|
|
39
|
+
|
|
40
|
+
DSH 原生的置顶只把会话顶到**它所属工作区分组的第一行**,分组本身仍在原位;一旦分组排在列表下面,置顶了也看不见。
|
|
41
|
+
|
|
42
|
+
本插件在**所有工作区分组之上**加一个「置顶」区,把宿主自己的置顶会话(`workspaces.pinnedSessionIds`)集中列出来:
|
|
43
|
+
|
|
44
|
+
- 用的是**宿主原生置顶集合**,不是第二套置顶:会话「…」菜单里的置顶/取消置顶照旧;
|
|
45
|
+
- 置顶区每行显示标题 + 所属工作区名,点行直接打开;hover 出现「取消置顶」;
|
|
46
|
+
- 置顶期间它在原工作区分组里的那一行**隐藏**(不重复出现),取消置顶后原行回到原位;
|
|
47
|
+
- 工作区分组、组内顺序、折叠、颜色全部照旧;
|
|
48
|
+
- 只在按工作区分组时出现;搜索、扁平视图、侧栏收起时自动隐藏。
|
|
37
49
|
|
|
38
50
|
## 设置
|
|
39
51
|
|
|
40
52
|
**设置 → 通用**:
|
|
41
53
|
|
|
42
54
|
- **历史折叠天数**:`−` / 数字输入 / `+`(1–90,默认 3),以及“选中 / 运行中 / 等待处理的会话不折叠”;
|
|
43
|
-
-
|
|
55
|
+
- **颜色与跟进标记**:颜色标记总开关;
|
|
56
|
+
- **全局置顶区**:显示开关。
|
|
44
57
|
|
|
45
58
|
偏好持久化在 Client store 的 `dsh.ws-recency.prefs.v1`。
|
|
46
59
|
|
|
@@ -69,7 +82,7 @@ dsh plugin --profile <profile> add github:chyxhtc/dsh-ws-recency
|
|
|
69
82
|
或固定版本的预构建包:
|
|
70
83
|
|
|
71
84
|
```powershell
|
|
72
|
-
dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.
|
|
85
|
+
dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.8.0/dsh-ws-recency-0.8.0.tgz
|
|
73
86
|
```
|
|
74
87
|
|
|
75
88
|
装完把 `dsh-ws-recency` 加进 `~/.hypers-work/profiles/<profile>/package.json` 的 `dsh.profile.bundles`,重启应用。
|
package/client.js
CHANGED
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
var FOLD_ATTR = "data-wsr-fold";
|
|
60
60
|
var STATE_ATTR = "data-wsr-state";
|
|
61
61
|
var DOT_TOKEN = "wsr-dot";
|
|
62
|
+
var PIN_ATTR = "data-wsr-pin";
|
|
63
|
+
var PIN_HIDDEN_ATTR = "data-wsr-pin-hidden";
|
|
62
64
|
var BUSY_RANK_ATTR = "data-busy-rank";
|
|
63
65
|
var CSS_ID = "dsh-ws-recency/css";
|
|
64
66
|
var MENU_ORDER = 460;
|
|
@@ -69,7 +71,7 @@
|
|
|
69
71
|
var DEFAULT_DAYS = 3;
|
|
70
72
|
var TICK_MS = 5 * 60 * 1000;
|
|
71
73
|
|
|
72
|
-
var data = { sessions: null, workspaces: null, settings: null, statuses: null, actions: null, t: null };
|
|
74
|
+
var data = { sessions: null, workspaces: null, settings: null, statuses: null, actions: null, service: null, t: null };
|
|
73
75
|
var timer = null;
|
|
74
76
|
var ticker = null;
|
|
75
77
|
var observer = null;
|
|
@@ -79,6 +81,8 @@
|
|
|
79
81
|
var labelled = new Set();
|
|
80
82
|
var toggles = new Set();
|
|
81
83
|
var dots = new Set();
|
|
84
|
+
var pinHidden = new Set();
|
|
85
|
+
var pinHost = null;
|
|
82
86
|
var expandedSections = new Set();
|
|
83
87
|
var clickMarked = new WeakSet();
|
|
84
88
|
var headerMarked = new WeakSet();
|
|
@@ -88,6 +92,16 @@
|
|
|
88
92
|
"[" + ROOT_ATTR + '="true"]{display:flex;flex-direction:column}',
|
|
89
93
|
"[" + ROOT_ATTR + '="true"]>*{flex:0 0 auto}',
|
|
90
94
|
"[" + HIDDEN_ATTR + '="true"]{display:none !important}',
|
|
95
|
+
"[" + PIN_HIDDEN_ATTR + '="true"]{display:none !important}',
|
|
96
|
+
".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
|
+
".wsr-pin-head{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary);padding:4px 12px 2px}",
|
|
98
|
+
".wsr-pin-row{display:flex;align-items:center;gap:8px;height:30px;padding:0 12px;border-radius:6px;cursor:pointer}",
|
|
99
|
+
".wsr-pin-row:hover{background:var(--dsw-alias-bg-layer-2)}",
|
|
100
|
+
".wsr-pin-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--dsw-alias-label-primary)}",
|
|
101
|
+
".wsr-pin-ws{flex:none;max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:var(--dsw-alias-label-tertiary)}",
|
|
102
|
+
".wsr-pin-unpin{flex:none;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:11px;padding:2px 4px;border-radius:4px;visibility:hidden}",
|
|
103
|
+
".wsr-pin-row:hover .wsr-pin-unpin{visibility:visible}",
|
|
104
|
+
".wsr-pin-unpin:hover{color:var(--dsw-alias-label-primary)}",
|
|
91
105
|
".wsr-toggle{width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;font-size:12px}",
|
|
92
106
|
".wsr-toggle:hover{color:var(--dsw-alias-label-secondary)}",
|
|
93
107
|
'[' + STATE_ATTR + '="running"] [class*="_title"]{color:#4f8ef7}',
|
|
@@ -114,6 +128,11 @@
|
|
|
114
128
|
"color.title": "\u989c\u8272\u4e0e\u8ddf\u8fdb\u6807\u8bb0",
|
|
115
129
|
"color.description": "\u8fd0\u884c\u4e2d=\u84dd\uff0c\u5b8c\u6210\u672a\u8bfb=\u7eff\uff0c\u624b\u52a8\u6807\u8bb0\u9700\u8981\u8ddf\u8fdb=\u7425\u73c0\u3002\u5728\u4f1a\u8bdd\u300c\u2026\u300d\u83dc\u5355\u91cc\u6807\u8bb0 / \u53d6\u6d88\u3002",
|
|
116
130
|
"color.enabled": "\u542f\u7528\u989c\u8272\u6807\u8bb0",
|
|
131
|
+
"pin.title": "\u5168\u5c40\u7f6e\u9876\u533a",
|
|
132
|
+
"pin.description": "\u5728\u6240\u6709\u5de5\u4f5c\u533a\u4e4b\u4e0a\u5355\u72ec\u5217\u51fa\u7f6e\u9876\u4f1a\u8bdd\uff1b\u53d6\u6d88\u7f6e\u9876\u540e\u56de\u5230\u5b83\u539f\u6765\u7684\u5de5\u4f5c\u533a\u4f4d\u7f6e\u3002",
|
|
133
|
+
"pin.enabled": "\u663e\u793a\u7f6e\u9876\u533a",
|
|
134
|
+
"pin.head": "\u7f6e\u9876",
|
|
135
|
+
"pin.unpin": "\u53d6\u6d88\u7f6e\u9876",
|
|
117
136
|
"menu.flag": "\u6807\u8bb0\u9700\u8981\u8ddf\u8fdb",
|
|
118
137
|
"menu.unflag": "\u53d6\u6d88\u8ddf\u8fdb\u6807\u8bb0",
|
|
119
138
|
};
|
|
@@ -126,6 +145,11 @@
|
|
|
126
145
|
"color.title": "Colors and follow-up marks",
|
|
127
146
|
"color.description": "Running = blue, finished-unopened = green, a session you marked as needing follow-up = amber. Mark it from the session's \"...\" menu.",
|
|
128
147
|
"color.enabled": "Enable color marks",
|
|
148
|
+
"pin.title": "Global pinned section",
|
|
149
|
+
"pin.description": "Lists pinned sessions in one section above every workspace; unpinning sends a session back to its own group.",
|
|
150
|
+
"pin.enabled": "Show the pinned section",
|
|
151
|
+
"pin.head": "Pinned",
|
|
152
|
+
"pin.unpin": "Unpin",
|
|
129
153
|
"menu.flag": "Mark as needs follow-up",
|
|
130
154
|
"menu.unflag": "Clear follow-up mark",
|
|
131
155
|
};
|
|
@@ -168,6 +192,7 @@
|
|
|
168
192
|
foldDays: Math.min(MAX_DAYS, Math.max(MIN_DAYS, Math.round(days))),
|
|
169
193
|
keepActive: settings.keepActive !== false,
|
|
170
194
|
colors: settings.colors !== false,
|
|
195
|
+
pinnedSection: settings.pinnedSection !== false,
|
|
171
196
|
followUps: settings.followUps !== null && typeof settings.followUps === "object" ? settings.followUps : {},
|
|
172
197
|
};
|
|
173
198
|
}
|
|
@@ -489,6 +514,157 @@
|
|
|
489
514
|
}
|
|
490
515
|
}
|
|
491
516
|
|
|
517
|
+
function baseName(path) {
|
|
518
|
+
if (typeof path !== "string" || path === "") return "";
|
|
519
|
+
var parts = path.replace(/[\\/]+$/, "").split(/[\\/]/);
|
|
520
|
+
return parts.length === 0 ? path : parts[parts.length - 1];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** sessionId -> owning workspace label, for the trailing tag in the pinned rows. */
|
|
524
|
+
function workspaceLabels(workspaces) {
|
|
525
|
+
var map = {};
|
|
526
|
+
var items = (workspaces !== null && workspaces !== undefined && workspaces.items) || [];
|
|
527
|
+
for (var i = 0; i < items.length; i += 1) {
|
|
528
|
+
var item = items[i];
|
|
529
|
+
var label = (item.title && String(item.title)) || baseName(item.path);
|
|
530
|
+
var ids = item.sessionIds || [];
|
|
531
|
+
for (var j = 0; j < ids.length; j += 1) if (map[ids[j]] === undefined) map[ids[j]] = label;
|
|
532
|
+
}
|
|
533
|
+
return map;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* The sessions the host itself has pinned, newest first.
|
|
538
|
+
*
|
|
539
|
+
* The pins are the host's own set (`workspaces.pinnedSessionIds`), so this is
|
|
540
|
+
* not a second pinning system: pin / unpin from the normal "..." menu and the
|
|
541
|
+
* section follows.
|
|
542
|
+
*/
|
|
543
|
+
function pinnedItemsOf(sessions, workspaces, settings) {
|
|
544
|
+
if (!settings.pinnedSection) return [];
|
|
545
|
+
var ids = (workspaces !== null && workspaces !== undefined && workspaces.pinnedSessionIds) || [];
|
|
546
|
+
if (ids.length === 0) return [];
|
|
547
|
+
var byId = sessions.byId || {};
|
|
548
|
+
var archived = new Set((workspaces && workspaces.archivedSessionIds) || []);
|
|
549
|
+
var labels = workspaceLabels(workspaces);
|
|
550
|
+
var out = [];
|
|
551
|
+
for (var i = 0; i < ids.length; i += 1) {
|
|
552
|
+
var session = byId[ids[i]];
|
|
553
|
+
if (session === undefined || session === null) continue;
|
|
554
|
+
if (session.origin === "subagent" || session.blank === true) continue;
|
|
555
|
+
if (archived.has(ids[i])) continue;
|
|
556
|
+
var title = (session.title || "").trim();
|
|
557
|
+
out.push({
|
|
558
|
+
id: ids[i],
|
|
559
|
+
title: title === "" ? ids[i].slice(0, 8) : title,
|
|
560
|
+
where: labels[ids[i]] || "",
|
|
561
|
+
updatedAt: typeof session.updatedAt === "number" ? session.updatedAt : 0,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
out.sort(function (a, b) { return b.updatedAt - a.updatedAt; });
|
|
565
|
+
return out;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function pinnedRowElement(item) {
|
|
569
|
+
var row = document.createElement("div");
|
|
570
|
+
row.className = "wsr-pin-row";
|
|
571
|
+
row.setAttribute("data-row-key", "pin:" + item.id);
|
|
572
|
+
var title = document.createElement("span");
|
|
573
|
+
title.className = "wsr-pin-title";
|
|
574
|
+
title.textContent = item.title;
|
|
575
|
+
title.setAttribute("title", item.title);
|
|
576
|
+
row.appendChild(title);
|
|
577
|
+
if (item.where !== "") {
|
|
578
|
+
var where = document.createElement("span");
|
|
579
|
+
where.className = "wsr-pin-ws";
|
|
580
|
+
where.textContent = item.where;
|
|
581
|
+
row.appendChild(where);
|
|
582
|
+
}
|
|
583
|
+
var unpin = document.createElement("button");
|
|
584
|
+
unpin.type = "button";
|
|
585
|
+
unpin.className = "wsr-pin-unpin";
|
|
586
|
+
unpin.textContent = t("pin.unpin");
|
|
587
|
+
unpin.addEventListener("click", function (event) {
|
|
588
|
+
if (event !== undefined && typeof event.stopPropagation === "function") event.stopPropagation();
|
|
589
|
+
if (data.service !== null && typeof data.service.unpinSession === "function") data.service.unpinSession(item.id);
|
|
590
|
+
schedule();
|
|
591
|
+
});
|
|
592
|
+
row.appendChild(unpin);
|
|
593
|
+
row.addEventListener("click", function () {
|
|
594
|
+
if (data.service !== null && typeof data.service.openSession === "function") data.service.openSession(item.id);
|
|
595
|
+
});
|
|
596
|
+
return row;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Hide the copies inside their own groups so nothing is listed twice. */
|
|
600
|
+
function hideOriginalRows(entries, pinnedIds) {
|
|
601
|
+
for (var i = 0; i < entries.length; i += 1) {
|
|
602
|
+
var rows = rowElements(entries[i].section);
|
|
603
|
+
for (var j = 0; j < rows.length; j += 1) {
|
|
604
|
+
var id = sessionIdOf(rows[j]);
|
|
605
|
+
if (id !== null && pinnedIds.has(id)) {
|
|
606
|
+
if (rows[j].getAttribute(PIN_HIDDEN_ATTR) !== "true") rows[j].setAttribute(PIN_HIDDEN_ATTR, "true");
|
|
607
|
+
pinHidden.add(rows[j]);
|
|
608
|
+
} else if (rows[j].getAttribute(PIN_HIDDEN_ATTR) !== null) {
|
|
609
|
+
rows[j].removeAttribute(PIN_HIDDEN_ATTR);
|
|
610
|
+
pinHidden.delete(rows[j]);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Render the "pinned" section above every workspace group.
|
|
618
|
+
*
|
|
619
|
+
* It is a plain element inserted as the first child of the list container
|
|
620
|
+
* (flex order -1), rebuilt only when the pinned set or its labels change. The
|
|
621
|
+
* host keeps owning the list; the workspace grouping below is untouched.
|
|
622
|
+
*/
|
|
623
|
+
function renderPinned(entries, sessions, workspaces, settings) {
|
|
624
|
+
if (typeof document === "undefined") return;
|
|
625
|
+
var items = pinnedItemsOf(sessions, workspaces, settings);
|
|
626
|
+
|
|
627
|
+
if (items.length === 0) {
|
|
628
|
+
if (pinHost !== null) {
|
|
629
|
+
pinHost.remove();
|
|
630
|
+
pinHost = null;
|
|
631
|
+
}
|
|
632
|
+
hideOriginalRows(entries, new Set());
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (entries.length === 0) return;
|
|
636
|
+
var container = entries[0].section.parentElement;
|
|
637
|
+
if (container === null) return;
|
|
638
|
+
|
|
639
|
+
if (pinHost === null || pinHost.parentElement !== container) {
|
|
640
|
+
var existing = container.querySelector("[" + PIN_ATTR + '="true"]');
|
|
641
|
+
if (existing !== null) existing.remove();
|
|
642
|
+
pinHost = document.createElement("div");
|
|
643
|
+
pinHost.className = "wsr-pin";
|
|
644
|
+
pinHost.setAttribute(PIN_ATTR, "true");
|
|
645
|
+
pinHost.style.order = "-1";
|
|
646
|
+
if (typeof container.insertBefore === "function" && container.firstChild !== undefined && container.firstChild !== null) {
|
|
647
|
+
container.insertBefore(pinHost, container.firstChild);
|
|
648
|
+
} else {
|
|
649
|
+
container.appendChild(pinHost);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
pinHost.style.order = "-1";
|
|
653
|
+
|
|
654
|
+
var signature = items.map(function (item) { return item.id + "\u0000" + item.title + "\u0000" + item.where; }).join("\u0001");
|
|
655
|
+
if (pinHost.dataset.wsrSignature !== signature) {
|
|
656
|
+
pinHost.dataset.wsrSignature = signature;
|
|
657
|
+
while (pinHost.firstChild !== undefined && pinHost.firstChild !== null) pinHost.removeChild(pinHost.firstChild);
|
|
658
|
+
var head = document.createElement("div");
|
|
659
|
+
head.className = "wsr-pin-head";
|
|
660
|
+
head.textContent = t("pin.head");
|
|
661
|
+
pinHost.appendChild(head);
|
|
662
|
+
for (var i = 0; i < items.length; i += 1) pinHost.appendChild(pinnedRowElement(items[i]));
|
|
663
|
+
}
|
|
664
|
+
var pinnedIds = new Set(items.map(function (item) { return item.id; }));
|
|
665
|
+
hideOriginalRows(entries, pinnedIds);
|
|
666
|
+
}
|
|
667
|
+
|
|
492
668
|
function statusOf(statuses, id) {
|
|
493
669
|
if (statuses === null || statuses === undefined) return null;
|
|
494
670
|
if (typeof statuses.get === "function") return statuses.get(id) || null;
|
|
@@ -513,8 +689,8 @@
|
|
|
513
689
|
/**
|
|
514
690
|
* Color the rows: running (blue), finished-unopened (green), and the manual
|
|
515
691
|
* "needs follow-up" flag (amber). The flag is the user's own judgement - it
|
|
516
|
-
* is never inferred, and
|
|
517
|
-
*
|
|
692
|
+
* is never inferred, and only an explicit clear removes it. Running and
|
|
693
|
+
* unread outrank it on screen; once they pass, amber comes back.
|
|
518
694
|
*/
|
|
519
695
|
function decorateRows(entries, sessions, statuses, settings) {
|
|
520
696
|
var flags = settings.followUps;
|
|
@@ -529,13 +705,13 @@
|
|
|
529
705
|
var status = statusOf(statuses, id);
|
|
530
706
|
var running = status !== null ? status.running === true : session.running === true;
|
|
531
707
|
var unread = status !== null ? status.completionUnread === true : session.completed === true;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
states[id] = running ? "running" : unread ? "unread" :
|
|
708
|
+
/* The mark is the user's own judgement and survives everything except
|
|
709
|
+
an explicit clear: running / unread only outrank it on screen, and
|
|
710
|
+
once those pass it comes back. It is never dropped just because the
|
|
711
|
+
conversation moved on - continuing a conversation is often the very
|
|
712
|
+
reason a follow-up is still owed. */
|
|
713
|
+
var flagged = flags[id] !== undefined;
|
|
714
|
+
states[id] = running ? "running" : unread ? "unread" : flagged ? "followup" : null;
|
|
539
715
|
}
|
|
540
716
|
|
|
541
717
|
for (var i = 0; i < entries.length; i += 1) {
|
|
@@ -560,6 +736,7 @@
|
|
|
560
736
|
|
|
561
737
|
orderGroups(entries, latestByWorkspace(sessions, workspaces));
|
|
562
738
|
foldRows(entries, settings, sessions, Date.now());
|
|
739
|
+
renderPinned(entries, sessions, workspaces, settings);
|
|
563
740
|
if (settings.colors) decorateRows(entries, sessions, data.statuses, settings);
|
|
564
741
|
}
|
|
565
742
|
|
|
@@ -700,6 +877,28 @@
|
|
|
700
877
|
);
|
|
701
878
|
}
|
|
702
879
|
|
|
880
|
+
/** Settings -> General row: the global pinned section on/off. */
|
|
881
|
+
function PinSectionRow(props) {
|
|
882
|
+
var settings = props.useStore(function (state) { return state; });
|
|
883
|
+
var enabled = settings.pinnedSection !== false;
|
|
884
|
+
var text = props.t("pin.title");
|
|
885
|
+
return h("div", { className: "wsr-setRow" },
|
|
886
|
+
h("div", { className: "wsr-setText" },
|
|
887
|
+
h("div", { className: "wsr-setTitle" }, text),
|
|
888
|
+
h("div", { className: "wsr-setDesc" }, props.t("pin.description")),
|
|
889
|
+
),
|
|
890
|
+
h("div", { className: "wsr-controls" },
|
|
891
|
+
h("label", { className: "wsr-exempt" },
|
|
892
|
+
h("input", {
|
|
893
|
+
type: "checkbox",
|
|
894
|
+
checked: enabled,
|
|
895
|
+
onChange: function (event) { props.actions.setPinnedSection(event.target.checked); },
|
|
896
|
+
}),
|
|
897
|
+
props.t("pin.enabled")),
|
|
898
|
+
),
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
703
902
|
function shutdown() {
|
|
704
903
|
disposed = true;
|
|
705
904
|
if (timer !== null) window.clearTimeout(timer);
|
|
@@ -723,6 +922,12 @@
|
|
|
723
922
|
toggles.clear();
|
|
724
923
|
dots.forEach(function (dot) { dot.remove(); });
|
|
725
924
|
dots.clear();
|
|
925
|
+
pinHidden.forEach(function (element) {
|
|
926
|
+
if (element.removeAttribute !== undefined) element.removeAttribute(PIN_HIDDEN_ATTR);
|
|
927
|
+
});
|
|
928
|
+
pinHidden.clear();
|
|
929
|
+
if (pinHost !== null && pinHost.parentElement !== null) pinHost.remove();
|
|
930
|
+
pinHost = null;
|
|
726
931
|
roots.forEach(function (root) { root.removeAttribute(ROOT_ATTR); });
|
|
727
932
|
roots.clear();
|
|
728
933
|
var marked = document.querySelectorAll("[" + STATE_ATTR + "]");
|
|
@@ -736,15 +941,17 @@
|
|
|
736
941
|
|
|
737
942
|
function apply_(ctx) {
|
|
738
943
|
installCss();
|
|
944
|
+
try { data.service = ctx.get("uiWorkspace") || null; } catch (error) { data.service = null; }
|
|
739
945
|
var prefs = clientStore.defineStore({
|
|
740
946
|
init: function () {
|
|
741
|
-
return { enabled: true, foldDays: DEFAULT_DAYS, keepActive: true, colors: true, followUps: {} };
|
|
947
|
+
return { enabled: true, foldDays: DEFAULT_DAYS, keepActive: true, colors: true, pinnedSection: true, followUps: {} };
|
|
742
948
|
},
|
|
743
949
|
persist: "dsh.ws-recency.prefs.v1",
|
|
744
950
|
actions: {
|
|
745
951
|
setEnabled: function (draft, value) { draft.enabled = value === true; },
|
|
746
952
|
setKeepActive: function (draft, value) { draft.keepActive = value === true; },
|
|
747
953
|
setColors: function (draft, value) { draft.colors = value === true; },
|
|
954
|
+
setPinnedSection: function (draft, value) { draft.pinnedSection = value === true; },
|
|
748
955
|
setFoldDays: function (draft, value) {
|
|
749
956
|
var next = Number(value);
|
|
750
957
|
if (!Number.isFinite(next)) return;
|
|
@@ -807,6 +1014,16 @@
|
|
|
807
1014
|
}, ColorsRow);
|
|
808
1015
|
});
|
|
809
1016
|
|
|
1017
|
+
ctx.slots.inject("settings.general.item", function () {
|
|
1018
|
+
return ctx.slots.register({
|
|
1019
|
+
name: "settings.general.item",
|
|
1020
|
+
id: "ws-recency-pins",
|
|
1021
|
+
order: 47,
|
|
1022
|
+
store: prefs,
|
|
1023
|
+
locale: NS,
|
|
1024
|
+
}, PinSectionRow);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
810
1027
|
ctx.slots.inject("sidebar.workspaces.session.menu.item", function () {
|
|
811
1028
|
return ctx.slots.register({
|
|
812
1029
|
name: "sidebar.workspaces.session.menu.item",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ws-recency",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sidebar session management for DeepSeek Harness: workspace groups sorted by latest activity, stale history folded, manual follow-up marks.",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Sidebar session management for DeepSeek Harness: workspace groups sorted by latest activity, stale history folded, a global pinned section, and manual follow-up marks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "chyxhtc",
|
|
7
7
|
"repository": {
|