dsh-quick-toc 0.7.14 → 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/CHANGELOG.en.md +8 -0
- package/CHANGELOG.md +8 -0
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/lib/client.js +370 -31
- package/package.json +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-quick-toc** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.8.0] - 2026-09-29
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **The hover preview can be pinned**: a pin button on the card keeps it on screen after the pointer moves away, and its text can be selected; click the pin again, press Escape or click elsewhere to drop it. One pinned card at a time, and it stays where it was put.
|
|
9
|
+
- **Two previews side by side**: while a card is pinned, hovering another row opens a second card next to it — on whichever side has more room — so two places can be compared.
|
|
10
|
+
- **A context menu on outline rows**: right-click a heading row for "Pin this preview / Unpin" and "Jump to the end of this section".
|
|
11
|
+
- **Cards and menus animate in and out**: the pinned card, the hover preview and the row menu all fade in and out instead of appearing or vanishing instantly.
|
|
12
|
+
|
|
5
13
|
## [0.7.14] - 2026-09-28
|
|
6
14
|
|
|
7
15
|
### Changed
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-quick-toc** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.8.0] - 2026-09-29
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
- **悬停预览可以钉住**:预览卡右上角多了一个图钉,点一下把它钉在屏幕上——鼠标移开也不消失,文字可以选中;再点一次图钉、按 Esc 或点别处取消。同时只保留一张钉住的卡,钉住的位置不会再动。
|
|
9
|
+
- **钉住后可以两处对照**:钉住状态下悬停别的行会同时显示第二张卡,两张不重叠——第二张放在空得多的一侧。
|
|
10
|
+
- **大纲行右键菜单**:在标题行上点右键可以「钉住预览/取消钉住」或「跳到本节末尾」。
|
|
11
|
+
- **卡片与菜单的收放都有动画**:钉住的卡、悬停预览和右键菜单都以淡入淡出出现和消失,不再瞬间弹出或消失。
|
|
12
|
+
|
|
5
13
|
## [0.7.14] - 2026-09-28
|
|
6
14
|
|
|
7
15
|
### 变更
|
package/README.en.md
CHANGED
|
@@ -42,7 +42,8 @@ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
42
42
|
|
|
43
43
|
| Plugin | Supported DSH |
|
|
44
44
|
| --- | --- |
|
|
45
|
-
| **0.
|
|
45
|
+
| **0.8.x** (latest, 0.8.0) | 0.1.5-rc.3, 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1 |
|
|
46
|
+
| 0.7.x (0.7.14) | 0.1.5-rc.3, 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1 |
|
|
46
47
|
| 0.6.x (0.6.3) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
|
47
48
|
| 0.5.x (0.5.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
|
48
49
|
| 0.4.x (0.4.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
package/README.md
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
|
|
43
43
|
| 插件版本 | 支持的 DSH 版本 |
|
|
44
44
|
| --- | --- |
|
|
45
|
-
| **0.
|
|
45
|
+
| **0.8.x**(最新,0.8.0) | 0.1.5-rc.3、0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1 |
|
|
46
|
+
| 0.7.x(0.7.14) | 0.1.5-rc.3、0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1 |
|
|
46
47
|
| 0.6.x(0.6.3) | 0.1.5-rc.1、0.1.5-rc.2 |
|
|
47
48
|
| 0.5.x(0.5.1) | 0.1.5-rc.1、0.1.5-rc.2 |
|
|
48
49
|
| 0.4.x(0.4.1) | 0.1.5-rc.1、0.1.5-rc.2 |
|
package/lib/client.js
CHANGED
|
@@ -148,6 +148,11 @@ window.__ModuleLoader__.load({
|
|
|
148
148
|
"time.yesterday": "昨天",
|
|
149
149
|
"time.beforeYesterday": "前天",
|
|
150
150
|
"preview.truncated": "预览由宿主提供,可能被截断",
|
|
151
|
+
"preview.pin": "钉住预览",
|
|
152
|
+
"preview.unpin": "取消钉住",
|
|
153
|
+
"menu.pin": "钉住预览",
|
|
154
|
+
"menu.unpin": "取消钉住",
|
|
155
|
+
"menu.end": "跳到本节末尾",
|
|
151
156
|
"resize.w": "拖拽调整宽度",
|
|
152
157
|
"resize.h": "拖拽调整高度",
|
|
153
158
|
"resize.wh": "拖拽同时调整宽高",
|
|
@@ -246,6 +251,11 @@ window.__ModuleLoader__.load({
|
|
|
246
251
|
// carries the numeric date. Yesterday itself reads as the word in both.
|
|
247
252
|
"time.yesterday": "yesterday",
|
|
248
253
|
"preview.truncated": "Preview comes from the host and may be truncated",
|
|
254
|
+
"preview.pin": "Pin this preview",
|
|
255
|
+
"preview.unpin": "Unpin",
|
|
256
|
+
"menu.pin": "Pin this preview",
|
|
257
|
+
"menu.unpin": "Unpin this preview",
|
|
258
|
+
"menu.end": "Jump to the end of this section",
|
|
249
259
|
"resize.w": "Drag to resize the width",
|
|
250
260
|
"resize.h": "Drag to resize the height",
|
|
251
261
|
"resize.wh": "Drag to resize width and height",
|
|
@@ -1981,7 +1991,10 @@ window.__ModuleLoader__.load({
|
|
|
1981
1991
|
ghost: info.ghost,
|
|
1982
1992
|
top: r.top,
|
|
1983
1993
|
left: r.left,
|
|
1984
|
-
right: r.right
|
|
1994
|
+
right: r.right,
|
|
1995
|
+
// the row this card describes: pinning keeps it, and the menu labels itself from it
|
|
1996
|
+
key: info.key,
|
|
1997
|
+
idx: info.idx
|
|
1985
1998
|
});
|
|
1986
1999
|
}, 260);
|
|
1987
2000
|
};
|
|
@@ -1997,6 +2010,152 @@ window.__ModuleLoader__.load({
|
|
|
1997
2010
|
}, 240);
|
|
1998
2011
|
};
|
|
1999
2012
|
|
|
2013
|
+
// ---- pinned preview + the row menu (0.8.0) --------------------------------
|
|
2014
|
+
// Pinning freezes one card where it is, so the pointer can move on and a second
|
|
2015
|
+
// row can be hovered next to it: two previews read side by side. One pin at a
|
|
2016
|
+
// time — pinning another row replaces it (the newest pin is the one that stays).
|
|
2017
|
+
var _sPin = react.useState(null);
|
|
2018
|
+
var pinnedCard = _sPin[0];
|
|
2019
|
+
var setPinnedCard = _sPin[1];
|
|
2020
|
+
var pinFromCard = function (card, opts) {
|
|
2021
|
+
if (!card) return;
|
|
2022
|
+
if (pinCloseTimerRef.current) { clearTimeout(pinCloseTimerRef.current); pinCloseTimerRef.current = null; }
|
|
2023
|
+
setPinnedClosing(false);
|
|
2024
|
+
setPinnedCard({
|
|
2025
|
+
title: card.title,
|
|
2026
|
+
sub: card.sub,
|
|
2027
|
+
preview: card.preview,
|
|
2028
|
+
meta: card.meta,
|
|
2029
|
+
ghost: card.ghost,
|
|
2030
|
+
key: card.key,
|
|
2031
|
+
top: card.top,
|
|
2032
|
+
left: card.left,
|
|
2033
|
+
right: card.right,
|
|
2034
|
+
// adopting the card that is already on screen must not re-play its entrance
|
|
2035
|
+
// animation, or pinning would blink the very card the reader is looking at
|
|
2036
|
+
fresh: !!(opts && opts.fresh)
|
|
2037
|
+
});
|
|
2038
|
+
// the card the reader is looking at BECOMES the pinned one: the hover card is
|
|
2039
|
+
// dropped in the same frame, so pinning never moves what is on screen
|
|
2040
|
+
if (hoverCloseTimerRef.current) { clearTimeout(hoverCloseTimerRef.current); hoverCloseTimerRef.current = null; }
|
|
2041
|
+
setHoverClosing(false);
|
|
2042
|
+
setHoverCard(null);
|
|
2043
|
+
};
|
|
2044
|
+
// The pinned card STAYS where it was put: scrolling the outline leaves it alone.
|
|
2045
|
+
// Dropping it runs the same fade the hover card uses, so nothing ever pops out.
|
|
2046
|
+
var _sPinClose = react.useState(false);
|
|
2047
|
+
var pinnedClosing = _sPinClose[0];
|
|
2048
|
+
var setPinnedClosing = _sPinClose[1];
|
|
2049
|
+
var pinCloseTimerRef = react.useRef(null);
|
|
2050
|
+
var unpinCard = function () {
|
|
2051
|
+
if (!pinnedCard || pinCloseTimerRef.current) return;
|
|
2052
|
+
setPinnedClosing(true);
|
|
2053
|
+
pinCloseTimerRef.current = setTimeout(function () {
|
|
2054
|
+
pinCloseTimerRef.current = null;
|
|
2055
|
+
setPinnedCard(null);
|
|
2056
|
+
setPinnedClosing(false);
|
|
2057
|
+
}, 180);
|
|
2058
|
+
};
|
|
2059
|
+
// the pinned card measures itself so the second card can be placed against it
|
|
2060
|
+
var pinnedRef = react.useRef(null);
|
|
2061
|
+
// the card keeps the row it describes, so the menu can say pin/unpin for it
|
|
2062
|
+
var isPinned = function (key) { return !!pinnedCard && !!key && pinnedCard.key === key; };
|
|
2063
|
+
|
|
2064
|
+
// A right-click on an outline row opens a small menu instead of the browser's:
|
|
2065
|
+
// pin/unpin this row's preview and jump to the end of its section.
|
|
2066
|
+
var _sMenu = react.useState(null);
|
|
2067
|
+
var rowMenu = _sMenu[0];
|
|
2068
|
+
var setRowMenu = _sMenu[1];
|
|
2069
|
+
// the menu closes through a short fade like every other card
|
|
2070
|
+
var _sMenuClose = react.useState(false);
|
|
2071
|
+
var menuClosing = _sMenuClose[0];
|
|
2072
|
+
var setMenuClosing = _sMenuClose[1];
|
|
2073
|
+
var menuCloseTimerRef = react.useRef(null);
|
|
2074
|
+
var openRowMenu = function (e, info, key, idx) {
|
|
2075
|
+
if (e && e.preventDefault) e.preventDefault();
|
|
2076
|
+
if (e && e.stopPropagation) e.stopPropagation();
|
|
2077
|
+
var x = e && typeof e.clientX === "number" ? e.clientX : 0;
|
|
2078
|
+
var y = e && typeof e.clientY === "number" ? e.clientY : 0;
|
|
2079
|
+
if (menuCloseTimerRef.current) { clearTimeout(menuCloseTimerRef.current); menuCloseTimerRef.current = null; }
|
|
2080
|
+
setMenuClosing(false);
|
|
2081
|
+
// pinning from the menu puts the card where the row is, not under the pointer
|
|
2082
|
+
var rect = e && e.currentTarget && e.currentTarget.getBoundingClientRect ? e.currentTarget.getBoundingClientRect() : null;
|
|
2083
|
+
setRowMenu({
|
|
2084
|
+
x: x,
|
|
2085
|
+
y: y,
|
|
2086
|
+
info: info,
|
|
2087
|
+
key: key,
|
|
2088
|
+
idx: idx,
|
|
2089
|
+
rowTop: rect ? rect.top : undefined,
|
|
2090
|
+
rowLeft: rect ? rect.left : undefined,
|
|
2091
|
+
rowRight: rect ? rect.right : undefined
|
|
2092
|
+
});
|
|
2093
|
+
};
|
|
2094
|
+
var closeRowMenu = function () {
|
|
2095
|
+
if (!rowMenu || menuCloseTimerRef.current) return;
|
|
2096
|
+
setMenuClosing(true);
|
|
2097
|
+
menuCloseTimerRef.current = setTimeout(function () {
|
|
2098
|
+
menuCloseTimerRef.current = null;
|
|
2099
|
+
setRowMenu(null);
|
|
2100
|
+
setMenuClosing(false);
|
|
2101
|
+
}, 150);
|
|
2102
|
+
};
|
|
2103
|
+
// Esc / a click anywhere else closes the menu and drops the pin; a pinned card
|
|
2104
|
+
// also goes away when the panel closes or the surface switches (below).
|
|
2105
|
+
// The listeners are bound ONCE and read the live state through a ref: when they were
|
|
2106
|
+
// re-bound on every state change the registered closure ran one step behind (Esc
|
|
2107
|
+
// closed the menu a second time and never reached the pin).
|
|
2108
|
+
var latestPin = react.useRef({});
|
|
2109
|
+
latestPin.current = {
|
|
2110
|
+
pinned: !!pinnedCard,
|
|
2111
|
+
menu: !!rowMenu,
|
|
2112
|
+
unpin: unpinCard,
|
|
2113
|
+
closeMenu: closeRowMenu
|
|
2114
|
+
};
|
|
2115
|
+
react.useEffect(function () {
|
|
2116
|
+
var onDown = function (ev) {
|
|
2117
|
+
var L = latestPin.current;
|
|
2118
|
+
if (!L.pinned && !L.menu) return;
|
|
2119
|
+
// a right-click is how the row menu opens: it must not drop the pin the reader
|
|
2120
|
+
// is comparing against
|
|
2121
|
+
if (ev.button === 2) return;
|
|
2122
|
+
var t = ev.target;
|
|
2123
|
+
if (t && t.closest && (t.closest("[data-dqt-pin]") || t.closest("[data-dqt-menu]"))) return;
|
|
2124
|
+
L.closeMenu();
|
|
2125
|
+
if (L.pinned && t && t.closest && t.closest("[data-dqt-pinned]")) return;
|
|
2126
|
+
L.unpin();
|
|
2127
|
+
};
|
|
2128
|
+
var onKey = function (ev) {
|
|
2129
|
+
var L = latestPin.current;
|
|
2130
|
+
if (ev.key !== "Escape") return;
|
|
2131
|
+
// Esc takes the menu first, then the pin; only when neither is up does it
|
|
2132
|
+
// reach the panel's own handler and close the panel.
|
|
2133
|
+
if (L.menu) { L.closeMenu(); ev.stopPropagation(); ev.preventDefault(); return; }
|
|
2134
|
+
if (L.pinned) { L.unpin(); ev.stopPropagation(); ev.preventDefault(); }
|
|
2135
|
+
};
|
|
2136
|
+
document.addEventListener("mousedown", onDown, true);
|
|
2137
|
+
document.addEventListener("keydown", onKey, true);
|
|
2138
|
+
return function () {
|
|
2139
|
+
document.removeEventListener("mousedown", onDown, true);
|
|
2140
|
+
document.removeEventListener("keydown", onKey, true);
|
|
2141
|
+
};
|
|
2142
|
+
}, []);
|
|
2143
|
+
// a pinned card belongs to the surface it was pinned on: closing the panel or
|
|
2144
|
+
// switching between the panel and the curtain drops it (through the same fade).
|
|
2145
|
+
// These act on the TRANSITION only — a plain re-run must never touch a live pin.
|
|
2146
|
+
var wasOpenRef = react.useRef(open);
|
|
2147
|
+
react.useEffect(function () {
|
|
2148
|
+
if (wasOpenRef.current === open) return;
|
|
2149
|
+
wasOpenRef.current = open;
|
|
2150
|
+
if (!open) unpinCard();
|
|
2151
|
+
}, [open]);
|
|
2152
|
+
var wasSheetRef = react.useRef(sheetOpen);
|
|
2153
|
+
react.useEffect(function () {
|
|
2154
|
+
if (wasSheetRef.current === sheetOpen) return;
|
|
2155
|
+
wasSheetRef.current = sheetOpen;
|
|
2156
|
+
unpinCard();
|
|
2157
|
+
}, [sheetOpen]);
|
|
2158
|
+
|
|
2000
2159
|
// ---- the outline belongs to the CHAT view only: when the center column
|
|
2001
2160
|
// switches to another view (trajectory / context / plugin views), fade the
|
|
2002
2161
|
// panel and its collapsed handle out. Polled lightly because view switches
|
|
@@ -4747,6 +4906,8 @@ window.__ModuleLoader__.load({
|
|
|
4747
4906
|
onOpenTurn: openTurn,
|
|
4748
4907
|
hoverStart: hoverStart,
|
|
4749
4908
|
hoverEnd: hoverEnd,
|
|
4909
|
+
// right-click on a row: pin its preview / jump to the end of its section
|
|
4910
|
+
rowMenu: openRowMenu,
|
|
4750
4911
|
// the curtain is wide: rows switch to a one-line, multi-part layout that
|
|
4751
4912
|
// puts the section's opening text out to the right instead of under the
|
|
4752
4913
|
// title (see renderItem / renderResultRow)
|
|
@@ -6197,42 +6358,111 @@ window.__ModuleLoader__.load({
|
|
|
6197
6358
|
// Rendered INSIDE the body portal (position: fixed) so the outline's own
|
|
6198
6359
|
// scroll container can never clip it, and pointer-events: none so it can
|
|
6199
6360
|
// never steal the hover that opened it.
|
|
6200
|
-
|
|
6201
|
-
|
|
6361
|
+
// One card body, used for both the hover card and the pinned one: same width,
|
|
6362
|
+
// same fields, same layering. `opts.pinned` turns it into the frozen copy (it
|
|
6363
|
+
// takes pointer events, so its text can be selected and its pin can be clicked);
|
|
6364
|
+
// the hover card stays click-through except for its pin button.
|
|
6365
|
+
var renderPreviewCard = function (card, opts) {
|
|
6202
6366
|
var cardW = 264;
|
|
6203
|
-
var
|
|
6204
|
-
var
|
|
6205
|
-
? Math.max(8,
|
|
6206
|
-
: Math.min(window.innerWidth - cardW - 8,
|
|
6207
|
-
|
|
6208
|
-
|
|
6367
|
+
var top = Math.max(8, Math.min(card.top - 4, window.innerHeight - 210));
|
|
6368
|
+
var left = dockRight
|
|
6369
|
+
? Math.max(8, card.left - cardW - 10)
|
|
6370
|
+
: Math.min(window.innerWidth - cardW - 8, card.right + 10);
|
|
6371
|
+
// With a card already pinned, a second one on the same band would cover it: put
|
|
6372
|
+
// the hover card on whichever side of the pinned card has more room (measured,
|
|
6373
|
+
// so a short pinned card does not push the second one further than needed).
|
|
6374
|
+
if (!opts.pinned && pinnedCard && pinnedVisible && !pinnedClosing) {
|
|
6375
|
+
var pinRect = pinnedRef.current && pinnedRef.current.getBoundingClientRect ? pinnedRef.current.getBoundingClientRect() : null;
|
|
6376
|
+
var pinTop = pinRect && pinRect.height > 0 ? pinRect.top : Math.max(8, Math.min(pinnedCard.top - 4, window.innerHeight - 210));
|
|
6377
|
+
var pinH = pinRect && pinRect.height > 0 ? pinRect.height : 210;
|
|
6378
|
+
var pinLeft = pinRect && pinRect.width > 0
|
|
6379
|
+
? pinRect.left
|
|
6380
|
+
: (dockRight ? Math.max(8, pinnedCard.left - cardW - 10) : Math.min(window.innerWidth - cardW - 8, pinnedCard.right + 10));
|
|
6381
|
+
var overlaps = Math.abs(pinLeft - left) < cardW && top < pinTop + pinH + 6 && pinTop < top + 210 + 6;
|
|
6382
|
+
if (overlaps) {
|
|
6383
|
+
var belowRoom = window.innerHeight - 8 - (pinTop + pinH + 8);
|
|
6384
|
+
var aboveRoom = pinTop - 8 - 8;
|
|
6385
|
+
top = belowRoom >= aboveRoom
|
|
6386
|
+
? Math.max(8, Math.min(pinTop + pinH + 8, window.innerHeight - 210 - 8))
|
|
6387
|
+
: Math.max(8, pinTop - 210 - 8);
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
var body = card.preview || card.sub || "";
|
|
6391
|
+
var kids = [
|
|
6209
6392
|
react_jsx_runtime.jsx("div", {
|
|
6210
|
-
style: {
|
|
6211
|
-
children:
|
|
6212
|
-
|
|
6393
|
+
style: { display: "flex", alignItems: "flex-start", gap: 6 },
|
|
6394
|
+
children: [
|
|
6395
|
+
react_jsx_runtime.jsx("div", {
|
|
6396
|
+
style: { flex: "1 1 auto", minWidth: 0, fontSize: 12, fontWeight: 600, color: C.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
6397
|
+
children: card.title
|
|
6398
|
+
}, "hc-t"),
|
|
6399
|
+
react_jsx_runtime.jsx("button", {
|
|
6400
|
+
"data-dqt-pin": opts.pinned ? "on" : "off",
|
|
6401
|
+
title: opts.pinned ? T("preview.unpin") : T("preview.pin"),
|
|
6402
|
+
onClick: function (e) {
|
|
6403
|
+
e.preventDefault();
|
|
6404
|
+
e.stopPropagation();
|
|
6405
|
+
if (opts.pinned) unpinCard();
|
|
6406
|
+
else pinFromCard(card);
|
|
6407
|
+
},
|
|
6408
|
+
style: {
|
|
6409
|
+
flex: "none",
|
|
6410
|
+
width: 20,
|
|
6411
|
+
height: 20,
|
|
6412
|
+
marginTop: -2,
|
|
6413
|
+
marginRight: -4,
|
|
6414
|
+
display: "flex",
|
|
6415
|
+
alignItems: "center",
|
|
6416
|
+
justifyContent: "center",
|
|
6417
|
+
border: "none",
|
|
6418
|
+
borderRadius: 6,
|
|
6419
|
+
background: opts.pinned ? C.hover : "transparent",
|
|
6420
|
+
color: opts.pinned ? C.text : C.muted,
|
|
6421
|
+
cursor: "pointer",
|
|
6422
|
+
// the hover card is click-through except for this button, so reaching
|
|
6423
|
+
// for it does not depend on the row staying hovered
|
|
6424
|
+
pointerEvents: "auto",
|
|
6425
|
+
padding: 0,
|
|
6426
|
+
fontSize: 11,
|
|
6427
|
+
lineHeight: "20px"
|
|
6428
|
+
},
|
|
6429
|
+
children: opts.pinned ? "\u25c9" : "\u25cb"
|
|
6430
|
+
}, "hc-pin")
|
|
6431
|
+
]
|
|
6432
|
+
}, "hc-head")
|
|
6213
6433
|
];
|
|
6214
|
-
if (
|
|
6215
|
-
|
|
6434
|
+
if (card.meta) {
|
|
6435
|
+
kids.push(react_jsx_runtime.jsx("div", {
|
|
6216
6436
|
style: { fontSize: 10, color: C.muted, opacity: 0.8, marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
6217
|
-
children:
|
|
6437
|
+
children: card.meta
|
|
6218
6438
|
}, "hc-m"));
|
|
6219
6439
|
}
|
|
6220
|
-
|
|
6440
|
+
kids.push(react_jsx_runtime.jsx("div", {
|
|
6221
6441
|
style: { fontSize: 11, lineHeight: "16px", color: C.text, opacity: 0.9, marginTop: 4, maxHeight: 116, overflow: "hidden" },
|
|
6222
|
-
children:
|
|
6442
|
+
children: body || (card.ghost ? T("turn.loadTip") : "")
|
|
6223
6443
|
}, "hc-b"));
|
|
6224
|
-
if (
|
|
6225
|
-
|
|
6444
|
+
if (card.ghost) {
|
|
6445
|
+
kids.push(react_jsx_runtime.jsx("div", {
|
|
6226
6446
|
style: { fontSize: 10, color: C.muted, opacity: 0.7, marginTop: 4 },
|
|
6227
6447
|
children: T("preview.truncated")
|
|
6228
6448
|
}, "hc-n"));
|
|
6229
6449
|
}
|
|
6230
|
-
|
|
6231
|
-
className: "dqt-hover" + (
|
|
6450
|
+
return react_jsx_runtime.jsx("div", {
|
|
6451
|
+
className: "dqt-hover" + (opts.closing ? " dqt-hover-closing" : "") + (opts.pinned ? " dqt-hover-pinned" : "")
|
|
6452
|
+
// a card adopted from the hover card is already on screen: skip its entrance
|
|
6453
|
+
+ (opts.pinned && !card.fresh ? " dqt-hover-static" : ""),
|
|
6454
|
+
"data-dqt-pinned": opts.pinned ? "on" : undefined,
|
|
6455
|
+
ref: opts.pinned ? pinnedRef : undefined,
|
|
6456
|
+
onMouseEnter: opts.pinned ? undefined : function () {
|
|
6457
|
+
// the pointer reached the card: keep it open so its pin can be clicked
|
|
6458
|
+
if (hoverCloseTimerRef.current) { clearTimeout(hoverCloseTimerRef.current); hoverCloseTimerRef.current = null; }
|
|
6459
|
+
setHoverClosing(false);
|
|
6460
|
+
},
|
|
6461
|
+
onMouseLeave: opts.pinned ? undefined : function () { hoverEnd(); },
|
|
6232
6462
|
style: {
|
|
6233
6463
|
position: "fixed",
|
|
6234
|
-
top:
|
|
6235
|
-
left:
|
|
6464
|
+
top: top,
|
|
6465
|
+
left: left,
|
|
6236
6466
|
width: cardW,
|
|
6237
6467
|
padding: "8px 10px",
|
|
6238
6468
|
borderRadius: 8,
|
|
@@ -6243,11 +6473,106 @@ window.__ModuleLoader__.load({
|
|
|
6243
6473
|
boxShadow: "0 8px 24px rgba(0,0,0,0.28)",
|
|
6244
6474
|
// above the panel and the curtain — it is a sibling of both in this
|
|
6245
6475
|
// portal, and both sit below it in the layer band we claim
|
|
6246
|
-
zIndex: Z_HOVER,
|
|
6247
|
-
|
|
6476
|
+
zIndex: Z_HOVER + (opts.pinned ? 1 : 0),
|
|
6477
|
+
// a fading card is inert: it must not swallow the click that dismissed it
|
|
6478
|
+
// (and the pinned card is the only clickable one while it is up)
|
|
6479
|
+
pointerEvents: opts.closing ? "none" : (opts.pinned ? "auto" : "none"),
|
|
6480
|
+
userSelect: opts.pinned ? "text" : "none"
|
|
6481
|
+
},
|
|
6482
|
+
children: kids
|
|
6483
|
+
}, opts.pinned ? "pinned-card" : "hover-card");
|
|
6484
|
+
};
|
|
6485
|
+
|
|
6486
|
+
// The pinned card is FROZEN where it was put: scrolling the outline leaves it alone,
|
|
6487
|
+
// and it only leaves the screen through the same fade the hover card uses.
|
|
6488
|
+
var pinnedVisible = !!(pinnedCard && open);
|
|
6489
|
+
var pinnedEl = pinnedVisible
|
|
6490
|
+
? renderPreviewCard(pinnedCard, { pinned: true, closing: pinnedClosing })
|
|
6491
|
+
: null;
|
|
6492
|
+
var hoverEl = null;
|
|
6493
|
+
if (hoverCard && open) hoverEl = renderPreviewCard(hoverCard, { pinned: false, closing: hoverClosing });
|
|
6494
|
+
|
|
6495
|
+
// ---- the row menu ---------------------------------------------------------
|
|
6496
|
+
// Small, fixed-position, keyboard-reachable: ↑/↓ move, Enter runs, Esc closes.
|
|
6497
|
+
var rowMenuEl = null;
|
|
6498
|
+
if (rowMenu && open) {
|
|
6499
|
+
var menuW = 208;
|
|
6500
|
+
var menuH = 76;
|
|
6501
|
+
var menuLeft = Math.max(8, Math.min(rowMenu.x, window.innerWidth - menuW - 8));
|
|
6502
|
+
var menuTop = Math.max(8, Math.min(rowMenu.y, window.innerHeight - menuH - 8));
|
|
6503
|
+
var menuItems = [
|
|
6504
|
+
{
|
|
6505
|
+
id: "pin",
|
|
6506
|
+
label: isPinned(rowMenu.key) ? T("menu.unpin") : T("menu.pin"),
|
|
6507
|
+
run: function () {
|
|
6508
|
+
if (isPinned(rowMenu.key)) unpinCard();
|
|
6509
|
+
else pinFromCard({
|
|
6510
|
+
title: rowMenu.info.title,
|
|
6511
|
+
sub: rowMenu.info.sub,
|
|
6512
|
+
preview: rowMenu.info.preview,
|
|
6513
|
+
meta: rowMenu.info.meta,
|
|
6514
|
+
ghost: rowMenu.info.ghost,
|
|
6515
|
+
key: rowMenu.key,
|
|
6516
|
+
// pin it where its ROW is, not where the right-click landed
|
|
6517
|
+
top: rowMenu.rowTop !== undefined ? rowMenu.rowTop : rowMenu.y,
|
|
6518
|
+
left: rowMenu.rowLeft !== undefined ? rowMenu.rowLeft : rowMenu.x,
|
|
6519
|
+
right: rowMenu.rowRight !== undefined ? rowMenu.rowRight : rowMenu.x
|
|
6520
|
+
}, { fresh: true });
|
|
6521
|
+
closeRowMenu();
|
|
6522
|
+
}
|
|
6248
6523
|
},
|
|
6249
|
-
|
|
6250
|
-
|
|
6524
|
+
{
|
|
6525
|
+
id: "end",
|
|
6526
|
+
label: T("menu.end"),
|
|
6527
|
+
run: function () {
|
|
6528
|
+
var key = rowMenu.key;
|
|
6529
|
+
var idx = rowMenu.idx;
|
|
6530
|
+
closeRowMenu();
|
|
6531
|
+
if (typeof endJumpRow === "function") endJumpRow(key, idx === undefined ? 0 : idx);
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
];
|
|
6535
|
+
rowMenuEl = react_jsx_runtime.jsx("div", {
|
|
6536
|
+
"data-dqt-menu": "on",
|
|
6537
|
+
className: "dqt-rowmenu" + (menuClosing ? " dqt-rowmenu-closing" : ""),
|
|
6538
|
+
role: "menu",
|
|
6539
|
+
style: {
|
|
6540
|
+
position: "fixed",
|
|
6541
|
+
top: menuTop,
|
|
6542
|
+
left: menuLeft,
|
|
6543
|
+
width: menuW,
|
|
6544
|
+
padding: 4,
|
|
6545
|
+
borderRadius: 8,
|
|
6546
|
+
background: C.panelBg,
|
|
6547
|
+
color: C.text,
|
|
6548
|
+
border: "1px solid " + C.panelBorder,
|
|
6549
|
+
boxShadow: "0 10px 28px rgba(0,0,0,0.32)",
|
|
6550
|
+
zIndex: Z_HOVER + 2,
|
|
6551
|
+
// a fading menu must not swallow the click that dismissed it
|
|
6552
|
+
pointerEvents: menuClosing ? "none" : "auto"
|
|
6553
|
+
},
|
|
6554
|
+
children: menuItems.map(function (item) {
|
|
6555
|
+
return react_jsx_runtime.jsx("button", {
|
|
6556
|
+
role: "menuitem",
|
|
6557
|
+
"data-dqt-menu-item": item.id,
|
|
6558
|
+
onClick: function (e) { e.preventDefault(); e.stopPropagation(); item.run(); },
|
|
6559
|
+
style: {
|
|
6560
|
+
display: "block",
|
|
6561
|
+
width: "100%",
|
|
6562
|
+
textAlign: "left",
|
|
6563
|
+
padding: "6px 8px",
|
|
6564
|
+
border: "none",
|
|
6565
|
+
borderRadius: 6,
|
|
6566
|
+
background: "transparent",
|
|
6567
|
+
color: C.text,
|
|
6568
|
+
cursor: "pointer",
|
|
6569
|
+
fontSize: 12,
|
|
6570
|
+
lineHeight: "18px"
|
|
6571
|
+
},
|
|
6572
|
+
children: item.label
|
|
6573
|
+
}, "menu-" + item.id);
|
|
6574
|
+
})
|
|
6575
|
+
}, "row-menu");
|
|
6251
6576
|
}
|
|
6252
6577
|
|
|
6253
6578
|
// ---- the curtain shell ---------------------------------------------------
|
|
@@ -6333,7 +6658,7 @@ window.__ModuleLoader__.load({
|
|
|
6333
6658
|
|
|
6334
6659
|
return react_dom.createPortal(
|
|
6335
6660
|
react_jsx_runtime.jsx(ErrorBoundary, {
|
|
6336
|
-
children: [sheetShellEl, edgeEl, topHandleEl, hoverEl]
|
|
6661
|
+
children: [sheetShellEl, edgeEl, topHandleEl, pinnedEl, hoverEl, rowMenuEl]
|
|
6337
6662
|
}),
|
|
6338
6663
|
document.body
|
|
6339
6664
|
);
|
|
@@ -6419,7 +6744,10 @@ window.__ModuleLoader__.load({
|
|
|
6419
6744
|
sub: n.sub || "",
|
|
6420
6745
|
preview: n.preview || "",
|
|
6421
6746
|
meta: [n.time, path ? path.path : ""].filter(Boolean).join(" · "),
|
|
6422
|
-
ghost: false
|
|
6747
|
+
ghost: false,
|
|
6748
|
+
// what pinning and the row menu need to act on this very row
|
|
6749
|
+
key: n.key,
|
|
6750
|
+
idx: n.idx
|
|
6423
6751
|
};
|
|
6424
6752
|
var rows;
|
|
6425
6753
|
if (wide) {
|
|
@@ -6520,6 +6848,9 @@ window.__ModuleLoader__.load({
|
|
|
6520
6848
|
if (b) b.style.opacity = "0";
|
|
6521
6849
|
if (extra && extra.hoverEnd) extra.hoverEnd();
|
|
6522
6850
|
},
|
|
6851
|
+
onContextMenu: function (e) {
|
|
6852
|
+
if (extra && extra.rowMenu) extra.rowMenu(e, hoverInfo, n.key, n.idx);
|
|
6853
|
+
},
|
|
6523
6854
|
"data-jump-key": n.key,
|
|
6524
6855
|
"data-jump-idx": n.idx !== undefined ? String(n.idx) : "0",
|
|
6525
6856
|
// keyboard navigation walks the ORDER of these rows inside the list
|
|
@@ -7646,9 +7977,17 @@ window.__ModuleLoader__.load({
|
|
|
7646
7977
|
// re-renders (which rewrite inline styles) cannot restart or cancel them
|
|
7647
7978
|
".dqt-levels-pop{animation:dqt-pop-in 0.22s cubic-bezier(0.22, 0.9, 0.3, 1) both;will-change:transform,opacity}" +
|
|
7648
7979
|
// hover preview card: fades in place, and fades OUT when the pointer
|
|
7649
|
-
// leaves (both as stylesheet classes so re-renders cannot cancel them)
|
|
7980
|
+
// leaves (both as stylesheet classes so re-renders cannot cancel them).
|
|
7981
|
+
// The pinned card wears the same two classes, so pinning, unpinning, Esc, a
|
|
7982
|
+
// click outside and a panel close all animate; `.dqt-hover-static` is for the
|
|
7983
|
+
// card that is ADOPTED from the hover card (it is already on screen, so it must
|
|
7984
|
+
// not re-play the entrance and blink).
|
|
7650
7985
|
".dqt-hover{animation:dqt-fade-in 0.12s linear both}" +
|
|
7651
|
-
".dqt-hover-
|
|
7986
|
+
".dqt-hover-static{animation:none}" +
|
|
7987
|
+
".dqt-hover-closing{animation:dqt-fade-out 0.16s cubic-bezier(0.2, 0.9, 0.3, 1) both;pointer-events:none}" +
|
|
7988
|
+
// the outline row menu: same vocabulary as the level picker (a small pop)
|
|
7989
|
+
".dqt-rowmenu{animation:dqt-pop-in 0.14s cubic-bezier(0.22, 0.9, 0.3, 1) both;will-change:transform,opacity}" +
|
|
7990
|
+
".dqt-rowmenu-closing{animation:dqt-pop-out 0.14s cubic-bezier(0.2, 0.9, 0.3, 1) both;pointer-events:none;will-change:transform,opacity}" +
|
|
7652
7991
|
// transient edge banner over the bottom of the outline list
|
|
7653
7992
|
"@keyframes dqt-toast-in{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}" +
|
|
7654
7993
|
"@keyframes dqt-toast-out{from{opacity:1;transform:none}to{opacity:0;transform:none}}" +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quick-toc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Conversation TOC for DeepSeek Harness (DSH): a whole-session, turn-grouped Markdown outline with hover previews, a level filter, a full-width curtain, jump-to-end buttons, title/full-text/cross-session search and a Chinese/English interface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|