dsh-quick-toc 0.10.0 → 0.10.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/CHANGELOG.en.md +23 -0
- package/CHANGELOG.md +23 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/lib/client.js +259 -52
- package/package.json +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-quick-toc** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.10.2] - 2026-10-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **A bookmarks panel on the curtain**: the curtain's toolbar has a bookmarks button on the left, beside the settings button; it slides in from the right in the same drawer the search uses, and the outline stays where it is instead of being replaced.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **The theme colour reaches more places**: hovering an entry paints its hierarchy guide in the theme colour, the search box takes it as a focus border, the hover preview card carries a theme-coloured bar on its reading side, and a pinned card's ring gets a filled core in the same colour.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **The theme colour came out black in a light theme**: with blue chosen, the plugin borrowed the host's accent colour, which is near-black ink in a light theme — so the guides, the search border, the card's bar and the pin's core all turned black. All six colours now use values of the plugin's own.
|
|
15
|
+
- **The curtain's landing bounce opened an unblurred seam along its top edge**: the settle now stretches downward from the top edge, so the top never leaves the line it hangs from.
|
|
16
|
+
|
|
17
|
+
## [0.10.1] - 2026-10-21
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Collapsing and expanding used to land on different spots**: the place the panel came to rest and the place it reappeared from differed slightly; both now sit on the same line.
|
|
21
|
+
- **A narrow window left a strip of the panel behind**: on a narrow window the collapsed panel kept a visible edge; it now clears away completely.
|
|
22
|
+
- **The collapsed handle did not line up with the panel's edge**: the handle sat a little to one side of the panel edge; the two now share a line.
|
|
23
|
+
- **On the right dock the outer shadow arrived before the content**: while expanding, the halo around the panel showed before the content underneath it; both are now clipped together, and the halo settles once the slide is done.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **A panel wider than the space available now narrows instead**: when the window cannot hold the width that was chosen, the panel narrows sideways and stays against the conversation area's edge rather than being pushed away from it; a wider window brings the chosen width back.
|
|
27
|
+
|
|
5
28
|
## [0.10.0] - 2026-10-19
|
|
6
29
|
|
|
7
30
|
### Added
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-quick-toc** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.10.2] - 2026-10-22
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
- **幕布的书签栏**:幕布的工具行左侧(设置按钮右边)新增一个书签按钮,点开后书签从右侧滑出,与搜索共用同一个抽屉;大纲原地保留,不再被整块替换。
|
|
9
|
+
|
|
10
|
+
### 变更
|
|
11
|
+
- **主题色覆盖更多位置**:悬停条目时它的层级引导线转为主题色,搜索框聚焦时边框转为主题色,悬停预览卡片的左侧有一条主题色竖条,钉住后的圆环核心也是主题色。
|
|
12
|
+
|
|
13
|
+
### 修复
|
|
14
|
+
- **浅色主题下主题色显示为黑色**:主题色选蓝色时,插件取用的是宿主界面的强调色,而浅色主题里那个值是近黑色,于是引导线、搜索框描边、卡片竖条与钉住圆环都变黑了;现在六种颜色都使用插件自己的色值。
|
|
15
|
+
- **幕布落下回弹时顶部露出一条没有模糊的缝隙**:回弹改为从顶边向下伸展,顶部不再离开它悬挂的那条线。
|
|
16
|
+
|
|
17
|
+
## [0.10.1] - 2026-10-21
|
|
18
|
+
|
|
19
|
+
### 修复
|
|
20
|
+
- **收起与展开的落点不一致**:收起后停下的位置与再次展开时出现的位置略有偏差,现在两者落在同一条线上。
|
|
21
|
+
- **窄窗口下收起后残留一条面板**:窗口较窄时,面板收起后仍会留下一条边,现在完全收起。
|
|
22
|
+
- **收起后的把手与面板边缘未对齐**:收起时把手比面板边缘偏一点,现在两者对齐。
|
|
23
|
+
- **右停靠展开时外框先于内容出现**:展开过程中,面板外圈的阴影会先于内容出现;现在阴影与内容一起裁切,展开完成后再自然铺开。
|
|
24
|
+
|
|
25
|
+
### 变更
|
|
26
|
+
- **面板宽度超出可用空间时改为收窄**:窗口放不下设定的宽度时,面板会横向收窄并继续贴着对话区边缘,不再被推离边缘;窗口变宽后恢复设定的宽度。
|
|
27
|
+
|
|
5
28
|
## [0.10.0] - 2026-10-19
|
|
6
29
|
|
|
7
30
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **English** | [中文](README.md)
|
|
4
4
|
|
|
5
|
-
A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): it turns the Markdown headings (H1–H6) of AI replies into a navigable outline — the same list as a side panel or as a full-width curtain — grouped by conversation turn and covering the whole session (including turns that are not loaded yet), with title/prompts/full-text/cross-session search, hover previews,
|
|
5
|
+
A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): it turns the Markdown headings (H1–H6) of AI replies into a navigable outline — the same list as a side panel or as a full-width curtain — grouped by conversation turn and covering the whole session (including turns that are not loaded yet), with title/prompts/full-text/cross-session search, hover previews, a right-click menu for jumping and copying, bookmarks with a bookmarks-only view, six theme colours and a translucent material, and a reading position that both follows and is remembered.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -44,7 +44,7 @@ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
44
44
|
|
|
45
45
|
| Plugin | Supported DSH |
|
|
46
46
|
| --- | --- |
|
|
47
|
-
| **0.10.x** (latest, 0.10.
|
|
47
|
+
| **0.10.x** (latest, 0.10.2) | 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, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2, 0.2.1-alpha.3, 0.2.1-alpha.4, 0.2.1-alpha.5, 0.2.1-rc.1, 0.2.1-rc.2 |
|
|
48
48
|
| **0.9.x** (0.9.5) | 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, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2, 0.2.1-alpha.3, 0.2.1-alpha.4, 0.2.1-alpha.5 |
|
|
49
49
|
| 0.8.x (0.8.2) | 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, 0.2.0-rc.2 |
|
|
50
50
|
| 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, 0.2.0-rc.2 |
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> [English](README.en.md) | **中文**
|
|
4
4
|
|
|
5
|
-
**DeepSeek Harness(DSH)对话大纲插件**:把 AI 回复中的 Markdown 标题(H1–H6
|
|
5
|
+
**DeepSeek Harness(DSH)对话大纲插件**:把 AI 回复中的 Markdown 标题(H1–H6)提取成可导航的大纲——侧边面板与全屏幕布两种视图,按对话回合分组、覆盖整段会话(含尚未加载进窗口的回合)。支持标题/提问/全文/跨会话四档检索、悬停预览、右键跳转与复制、书签与「只看书签」视图,六种主题色与半透明材质,阅读位置自动跟随并可记住。
|
|
6
6
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
| 插件版本 | 支持的 DSH 版本 |
|
|
46
46
|
| --- | --- |
|
|
47
|
-
| **0.10.x**(最新,0.10.
|
|
47
|
+
| **0.10.x**(最新,0.10.2) | 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、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5、0.2.1-rc.1、0.2.1-rc.2 |
|
|
48
48
|
| **0.9.x**(0.9.5) | 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、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5 |
|
|
49
49
|
| 0.8.x(0.8.2) | 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、0.2.0-rc.2 |
|
|
50
50
|
| 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、0.2.0-rc.2 |
|
package/lib/client.js
CHANGED
|
@@ -179,6 +179,8 @@ window.__ModuleLoader__.load({
|
|
|
179
179
|
"marks.tip.only": "只看书签({n})",
|
|
180
180
|
"marks.tip.all": "显示全部大纲(书签 {n})",
|
|
181
181
|
"marks.empty": "还没有书签。右键任意条目,选「加入书签」即可。",
|
|
182
|
+
"marks.title": "书签",
|
|
183
|
+
"marks.tip.col": "书签栏({n})",
|
|
182
184
|
"menu.goto": "跳到这一节",
|
|
183
185
|
"menu.mark": "加入书签",
|
|
184
186
|
"menu.unmark": "移出书签",
|
|
@@ -309,6 +311,8 @@ window.__ModuleLoader__.load({
|
|
|
309
311
|
"marks.tip.only": "Show bookmarks only ({n})",
|
|
310
312
|
"marks.tip.all": "Show the whole outline ({n} bookmarked)",
|
|
311
313
|
"marks.empty": "No bookmarks yet. Right-click any entry and choose \u201cAdd bookmark\u201d.",
|
|
314
|
+
"marks.title": "Bookmarks",
|
|
315
|
+
"marks.tip.col": "Bookmarks panel ({n})",
|
|
312
316
|
"menu.goto": "Jump to this section",
|
|
313
317
|
"menu.mark": "Add bookmark",
|
|
314
318
|
"menu.unmark": "Remove bookmark",
|
|
@@ -470,7 +474,12 @@ window.__ModuleLoader__.load({
|
|
|
470
474
|
// triplet with a solid that stays legible on both light and dark surfaces
|
|
471
475
|
// (the dot shows the vivid hue; the solid is the readable one).
|
|
472
476
|
var ACCENTS = {
|
|
473
|
-
|
|
477
|
+
// Every option is SELF-CONTAINED, blue included. Blue used to borrow the host's
|
|
478
|
+
// `--dsw-alias-brand-primary`; in a light theme that token is near-black ink, so
|
|
479
|
+
// picking "blue" painted the hierarchy guides, the search ring, the preview
|
|
480
|
+
// card's bar and the pin's core BLACK. The theme colour is the plugin's own
|
|
481
|
+
// decision, so it does not defer to the host's brand token at all.
|
|
482
|
+
blue: { rgb: "79, 140, 255", solid: "#4F8CFF", dot: "#4f8cff" },
|
|
474
483
|
red: { rgb: "245, 72, 77", solid: "#F5484D", dot: "#F5484D" },
|
|
475
484
|
yellow: { rgb: "232, 162, 12", solid: "#D99300", dot: "#FFB020" },
|
|
476
485
|
green: { rgb: "48, 164, 108", solid: "#2FA36B", dot: "#2FA36B" },
|
|
@@ -496,10 +505,13 @@ window.__ModuleLoader__.load({
|
|
|
496
505
|
// injected base, which keeps today's values as its fallback.
|
|
497
506
|
var accentCssText = function (id) {
|
|
498
507
|
var t = ACCENTS[normAccent(id)] || ACCENTS.blue;
|
|
499
|
-
var solid = t.solid || "
|
|
508
|
+
var solid = t.solid || "#4F8CFF";
|
|
500
509
|
return ".dqt-nav{box-shadow:inset 0 0 0 1px " + solid + ";background:rgba(" + t.rgb + ",0.10)}"
|
|
501
510
|
+ ".dqt-phead:focus-visible{outline-color:" + solid + "}"
|
|
502
|
-
+ ".dqt-slider{accent-color:" + solid + "}"
|
|
511
|
+
+ ".dqt-slider{accent-color:" + solid + "}"
|
|
512
|
+
// the search pill takes the theme colour the moment the caret is in it, so the
|
|
513
|
+
// box and the hit marks below it agree on which search is running
|
|
514
|
+
+ ".dqt-search-pill:focus-within{background:rgba(" + t.rgb + ",0.12);box-shadow:inset 0 0 0 1px " + solid + "}";
|
|
503
515
|
};
|
|
504
516
|
var applyAccentCss = function (id) {
|
|
505
517
|
if (typeof document === "undefined") return;
|
|
@@ -2165,7 +2177,10 @@ window.__ModuleLoader__.load({
|
|
|
2165
2177
|
panelBorder: "var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25))",
|
|
2166
2178
|
text: "var(--dsw-alias-label-primary, #e8eaee)",
|
|
2167
2179
|
muted: "var(--dsw-alias-label-secondary, #9aa0ab)",
|
|
2168
|
-
|
|
2180
|
+
// the plugin's own blue, NOT the host's brand token: that token is near-black
|
|
2181
|
+
// in light themes, and every accent piece (guides, search ring, preview bar,
|
|
2182
|
+
// pin core) turned black with it
|
|
2183
|
+
accent: "#4F8CFF",
|
|
2169
2184
|
// the selection triplet, for callers that paint their own alphas (search
|
|
2170
2185
|
// hit marks): same hue as every tint above, in one place
|
|
2171
2186
|
accentRgb: "79,140,255",
|
|
@@ -3160,6 +3175,21 @@ window.__ModuleLoader__.load({
|
|
|
3160
3175
|
var _sAside = react.useState(null);
|
|
3161
3176
|
var panelAside = _sAside[0];
|
|
3162
3177
|
var setPanelAside = _sAside[1];
|
|
3178
|
+
// The outer halo may only relax to its full bleed once a slide has LANDED. Riding
|
|
3179
|
+
// the same transition let the shadow reach past the panel's own (still clipped)
|
|
3180
|
+
// edge during an expansion, which reads as a frame appearing before the surface.
|
|
3181
|
+
// Declared up here with the other hooks so an early "nothing to show" return never
|
|
3182
|
+
// changes the hook count.
|
|
3183
|
+
var lastLineRef = react.useRef(null);
|
|
3184
|
+
var _sShadowSettled = react.useState(true);
|
|
3185
|
+
var shadowSettled = _sShadowSettled[0];
|
|
3186
|
+
var setShadowSettled = _sShadowSettled[1];
|
|
3187
|
+
var geoOpenNow = open && !panelAside;
|
|
3188
|
+
react.useEffect(function () {
|
|
3189
|
+
if (!geoOpenNow) { setShadowSettled(false); return undefined; }
|
|
3190
|
+
var t = setTimeout(function () { setShadowSettled(true); }, 640);
|
|
3191
|
+
return function () { clearTimeout(t); };
|
|
3192
|
+
}, [geoOpenNow]);
|
|
3163
3193
|
// The curtain hangs from just BELOW the centre column's view tabs (对话 / 轨迹 /
|
|
3164
3194
|
// 上下文), not from the top of the scroll container: when those tabs live inside
|
|
3165
3195
|
// that container, viewport.top sits above them and the curtain would cover them.
|
|
@@ -3220,6 +3250,8 @@ window.__ModuleLoader__.load({
|
|
|
3220
3250
|
sheetTimerRef.current = setTimeout(function () {
|
|
3221
3251
|
sheetTimerRef.current = null;
|
|
3222
3252
|
setSheetOpen(false);
|
|
3253
|
+
// the column only exists across the curtain: a later drop starts closed
|
|
3254
|
+
setMarksCol(false);
|
|
3223
3255
|
// the panel was open before the curtain: slide it back in instead of
|
|
3224
3256
|
// letting it pop into place (the reader saw that pop as a flash)
|
|
3225
3257
|
if (open) setPanelAside("return");
|
|
@@ -3643,9 +3675,24 @@ window.__ModuleLoader__.load({
|
|
|
3643
3675
|
return function () { clearTimeout(timer); };
|
|
3644
3676
|
}, [dock, panelY, panelW, panelH, levels, fuzzy]);
|
|
3645
3677
|
|
|
3678
|
+
// ---- the curtain's bookmarks column --------------------------------------
|
|
3679
|
+
// ONE drawer, two tenants. The search column is measured and slid from the
|
|
3680
|
+
// right edge (see the flip effect above); the bookmarks ride the very same box,
|
|
3681
|
+
// motion and width, so the curtain needs no second mechanism. Opening either
|
|
3682
|
+
// tenant takes the other's place, and the docked panel keeps its star button
|
|
3683
|
+
// and full-view overlay — there the outline has no column to share.
|
|
3684
|
+
var _sMarksCol = react.useState(false);
|
|
3685
|
+
var marksCol = _sMarksCol[0];
|
|
3686
|
+
var setMarksCol = _sMarksCol[1];
|
|
3687
|
+
var toggleMarksCol = function () {
|
|
3688
|
+
if (marksCol) { setMarksCol(false); return; }
|
|
3689
|
+
setMarksCol(true);
|
|
3690
|
+
if (searchOpen) closeSearch();
|
|
3691
|
+
};
|
|
3646
3692
|
var openSearch = function () {
|
|
3647
3693
|
setSearchOpen(true);
|
|
3648
3694
|
setSearchAnim("enter");
|
|
3695
|
+
if (marksCol) setMarksCol(false);
|
|
3649
3696
|
};
|
|
3650
3697
|
var closeSearch = function () {
|
|
3651
3698
|
setSearchAnim("out");
|
|
@@ -3677,7 +3724,7 @@ window.__ModuleLoader__.load({
|
|
|
3677
3724
|
// added after that point would change the hook count between renders (React #310).
|
|
3678
3725
|
var searchColRef = react.useRef(null);
|
|
3679
3726
|
var searchGeomRef = react.useRef(null);
|
|
3680
|
-
var sheetColW = (searchOpen || searchAnim === "out") ? SHEET_SEARCH_W : 0;
|
|
3727
|
+
var sheetColW = (searchOpen || marksCol || searchAnim === "out") ? SHEET_SEARCH_W : 0;
|
|
3681
3728
|
// The column used to animate its own WIDTH, and because the outline is the flex sibling
|
|
3682
3729
|
// that grows into the rest of the row, every frame of that re-laid out every rendered row
|
|
3683
3730
|
// (measured on a 34-row list: a width step per frame, ~18ms frames and a 76ms first hitch;
|
|
@@ -3701,7 +3748,7 @@ window.__ModuleLoader__.load({
|
|
|
3701
3748
|
// resting spot and eased back).
|
|
3702
3749
|
var layoutLeft = list.offsetLeft;
|
|
3703
3750
|
var colLayout = sheetColW > 0; // the layout currently on screen
|
|
3704
|
-
var wantOpen = !!searchOpen && searchAnim !== "out"; // "out" is the exit in flight
|
|
3751
|
+
var wantOpen = (!!searchOpen && searchAnim !== "out") || !!marksCol; // "out" is the exit in flight
|
|
3705
3752
|
var colWidth = col.getBoundingClientRect().width;
|
|
3706
3753
|
var prev = searchGeomRef.current;
|
|
3707
3754
|
if (prev && prev.colLayout !== colLayout) {
|
|
@@ -3738,7 +3785,7 @@ window.__ModuleLoader__.load({
|
|
|
3738
3785
|
}
|
|
3739
3786
|
searchGeomRef.current = { colLayout: colLayout, layoutLeft: layoutLeft, wantOpen: wantOpen };
|
|
3740
3787
|
return undefined;
|
|
3741
|
-
}, [sheetOpen, sheetColW, searchOpen, searchAnim]);
|
|
3788
|
+
}, [sheetOpen, sheetColW, searchOpen, searchAnim, marksCol]);
|
|
3742
3789
|
var onQueryChange = function (v) {
|
|
3743
3790
|
setQuery(v);
|
|
3744
3791
|
setMatchIdx(0);
|
|
@@ -5421,18 +5468,33 @@ window.__ModuleLoader__.load({
|
|
|
5421
5468
|
// (The collapsed panel is parked deliberately outside the area and is hidden
|
|
5422
5469
|
// by its own clip, so only the open position is clamped.)
|
|
5423
5470
|
var clampLeft = function (x) {
|
|
5424
|
-
|
|
5471
|
+
// clamp against the width the panel will actually be drawn at (geoW, below):
|
|
5472
|
+
// the stored preference can exceed the window, and clamping to THAT put the
|
|
5473
|
+
// open edge in the wrong place
|
|
5474
|
+
return Math.max(8, Math.min(Math.max(8, window.innerWidth - geoW - 8), x));
|
|
5425
5475
|
};
|
|
5426
5476
|
// While the panel is being handed over to (or back from) the curtain it is
|
|
5427
5477
|
// rendered at its COLLAPSED geometry, so the same transform/clip transition that
|
|
5428
5478
|
// serves open/collapse also serves the handoff: no teleport, no flash.
|
|
5429
5479
|
var geoOpen = open && !panelAside;
|
|
5430
|
-
|
|
5480
|
+
// The panel is docked AT the area's edge. When the stored width cannot fit beside
|
|
5481
|
+
// it, the WIDTH gives way for this render — never the position. Moving the open
|
|
5482
|
+
// panel inwards (the old fallback, via clampLeft) both covered the sidebar and
|
|
5483
|
+
// shortened the collapse below the panel's own width, so the tuck line stopped
|
|
5484
|
+
// agreeing with the line the panel came back out of: the panel appeared to close
|
|
5485
|
+
// to one place and reopen from another. The stored preference is untouched, so a
|
|
5486
|
+
// wider window brings the chosen width back.
|
|
5487
|
+
var geoW = Math.max(120, Math.min(panelW,
|
|
5488
|
+
dockRight
|
|
5489
|
+
? (window.innerWidth - (viewport ? viewport.right + 48 : 60)) - 8
|
|
5490
|
+
: window.innerWidth - (viewport ? viewport.left : 0) - 16));
|
|
5491
|
+
// Parked against the area, one panel width further out.
|
|
5492
|
+
var anchorLeft = viewport ? viewport.left - geoW - 8 : -(geoW + 48);
|
|
5431
5493
|
var openLeft;
|
|
5432
5494
|
if (dockRight) {
|
|
5433
5495
|
// RIGHT dock: keep the panel's VISIBLE right edge pinned at the SAME
|
|
5434
5496
|
// line when collapsed as when expanded (the expand edge is correct).
|
|
5435
|
-
var openRight = window.innerWidth - (viewport ? viewport.right + 48 : 60) -
|
|
5497
|
+
var openRight = window.innerWidth - (viewport ? viewport.right + 48 : 60) - geoW;
|
|
5436
5498
|
openLeft = clampLeft(openRight);
|
|
5437
5499
|
} else {
|
|
5438
5500
|
// LEFT dock open (left dock is the confirmed-correct reference — untouched)
|
|
@@ -5455,15 +5517,35 @@ window.__ModuleLoader__.load({
|
|
|
5455
5517
|
geoOpenRef.current = geoOpen;
|
|
5456
5518
|
geoHoldRef.current = geoOpen ? 0 : Date.now() + COLLAPSE_HOLD_MS;
|
|
5457
5519
|
geoSnapRef.current = geoOpen ? null : { anchorLeft: anchorLeft, openLeft: openLeft, dock: dock };
|
|
5520
|
+
// remember the line it is going into, so the next expansion can come back out
|
|
5521
|
+
// of exactly that line (see below)
|
|
5522
|
+
if (!geoOpen) lastLineRef.current = { openLeft: openLeft, dock: dock, w: window.innerWidth, h: window.innerHeight };
|
|
5458
5523
|
}
|
|
5459
5524
|
var geoFrozen = geoSnapRef.current !== null && geoSnapRef.current.dock === dock
|
|
5460
5525
|
&& Date.now() < geoHoldRef.current;
|
|
5461
5526
|
if (geoFrozen) {
|
|
5462
5527
|
anchorLeft = geoSnapRef.current.anchorLeft;
|
|
5463
5528
|
openLeft = geoSnapRef.current.openLeft;
|
|
5529
|
+
} else {
|
|
5530
|
+
// The host re-measures its area between the two legs (a slow beat), and a few
|
|
5531
|
+
// pixels of drift there is what made the panel reopen slightly to the RIGHT of
|
|
5532
|
+
// the place it had tucked into. While the drift is small the line it went into
|
|
5533
|
+
// wins — over BOTH legs, so the tuck line and the open line are the same
|
|
5534
|
+
// number. A larger difference means the reader really moved something (the
|
|
5535
|
+
// sidebar, the window), and the fresh measurement wins instead.
|
|
5536
|
+
var lastLine = lastLineRef.current;
|
|
5537
|
+
if (lastLine && lastLine.dock === dock
|
|
5538
|
+
&& lastLine.w === window.innerWidth && lastLine.h === window.innerHeight
|
|
5539
|
+
&& Math.abs(lastLine.openLeft - openLeft) <= 24
|
|
5540
|
+
&& lastLine.openLeft !== openLeft) {
|
|
5541
|
+
openLeft = lastLine.openLeft;
|
|
5542
|
+
// travel becomes geoW + 16 again, so `anchorLeft + clip` (the tuck line)
|
|
5543
|
+
// evaluates to this same line
|
|
5544
|
+
anchorLeft = openLeft - geoW - 16;
|
|
5545
|
+
}
|
|
5464
5546
|
}
|
|
5465
5547
|
var openOffset = openLeft - anchorLeft;
|
|
5466
|
-
var parkedOffset = dockRight ? (openLeft +
|
|
5548
|
+
var parkedOffset = dockRight ? (openLeft + geoW) - anchorLeft : 0;
|
|
5467
5549
|
var panelLeft = anchorLeft;
|
|
5468
5550
|
var panelOffset = geoOpen ? openOffset : parkedOffset;
|
|
5469
5551
|
var panelTransform = panelOffset === 0 ? "none" : "translateX(" + zLen(panelOffset) + ")";
|
|
@@ -6272,12 +6354,26 @@ window.__ModuleLoader__.load({
|
|
|
6272
6354
|
// the wipe edge drift 24px through the slide); the shadow lives on the panel's
|
|
6273
6355
|
// twin element instead.
|
|
6274
6356
|
var panelClip = "inset(0 0 0 0px)";
|
|
6357
|
+
// ONE line, and that is a geometric necessity rather than a taste: the visible
|
|
6358
|
+
// edge of a clipped box can never be left of the box's own left edge, so the
|
|
6359
|
+
// only way the tuck line and the open line can both sit somewhere is for the
|
|
6360
|
+
// wipe to equal the travel. Any other value makes the edge sweep between two
|
|
6361
|
+
// lines through the animation (pull it 8px back and the panel visibly slides
|
|
6362
|
+
// 8px further left as it vanishes, then reappears from the open line instead).
|
|
6363
|
+
//
|
|
6364
|
+
// It can never be SHORTER than the box it hides either: `clampLeft` pulls the
|
|
6365
|
+
// open position inwards when the window is narrow relative to the panel, which
|
|
6366
|
+
// shortened the travel below the panel width and left a strip of the parked
|
|
6367
|
+
// panel standing on the workspace (measured at 284px on a 13" screen). (The
|
|
6368
|
+
// right dock needs no guard — its travel is one panel width by construction.)
|
|
6369
|
+
// The halo twin reads the same number, so the two are clipped by one wipe.
|
|
6370
|
+
var clipWipe = Math.max(travelPx, geoW + 2);
|
|
6275
6371
|
if (!geoOpen) {
|
|
6276
6372
|
if (dockRight) {
|
|
6277
6373
|
// mirror of LEFT: clip the RIGHT side (panel flies right, shrinking from its right)
|
|
6278
6374
|
panelClip = "inset(0 " + zLen(travelPx) + " 0 0)";
|
|
6279
6375
|
} else {
|
|
6280
|
-
panelClip = "inset(0 0 0 " + zLen(
|
|
6376
|
+
panelClip = "inset(0 0 0 " + zLen(clipWipe) + ")";
|
|
6281
6377
|
}
|
|
6282
6378
|
}
|
|
6283
6379
|
// faded out entirely while another center-column view is active
|
|
@@ -6460,6 +6556,23 @@ window.__ModuleLoader__.load({
|
|
|
6460
6556
|
// twenty-pixel star, and a squeezed digit is worse than none). The count lives
|
|
6461
6557
|
// in the tooltip a moment later, where it has a line of its own.
|
|
6462
6558
|
var markCount = marks.length > 99 ? "99+" : String(marks.length);
|
|
6559
|
+
// the star, drawn once: the docked header's 15px face and the curtain's larger
|
|
6560
|
+
// one are the same glyph at two sizes
|
|
6561
|
+
var starIcon = function (size) {
|
|
6562
|
+
return react_jsx_runtime.jsx("svg", {
|
|
6563
|
+
width: size, height: size, viewBox: "0 0 24 24",
|
|
6564
|
+
style: { display: "block" },
|
|
6565
|
+
children: react_jsx_runtime.jsx("path", {
|
|
6566
|
+
// rounded joins (a stroke in the same colour as the fill) soften the
|
|
6567
|
+
// five points; without it the star reads as a spiky asterisk at this size
|
|
6568
|
+
d: "M12 3.1l2.72 5.52 6.09.89-4.4 4.29 1.04 6.06L12 16.9l-5.45 2.86 1.04-6.06-4.4-4.29 6.09-.89z",
|
|
6569
|
+
fill: "currentColor",
|
|
6570
|
+
stroke: "currentColor",
|
|
6571
|
+
strokeWidth: 2.4,
|
|
6572
|
+
strokeLinejoin: "round"
|
|
6573
|
+
})
|
|
6574
|
+
});
|
|
6575
|
+
};
|
|
6463
6576
|
var marksTip = onlyMarks
|
|
6464
6577
|
? Tp("marks.tip.all", { n: markCount })
|
|
6465
6578
|
: Tp("marks.tip.only", { n: markCount });
|
|
@@ -6484,21 +6597,14 @@ window.__ModuleLoader__.load({
|
|
|
6484
6597
|
padding: 0,
|
|
6485
6598
|
transition: "background 0.18s ease, color 0.18s ease"
|
|
6486
6599
|
},
|
|
6487
|
-
children:
|
|
6488
|
-
key: "star",
|
|
6489
|
-
width: 15, height: 15, viewBox: "0 0 24 24",
|
|
6490
|
-
style: { display: "block" },
|
|
6491
|
-
children: react_jsx_runtime.jsx("path", {
|
|
6492
|
-
// rounded joins (a stroke in the same colour as the fill) soften the
|
|
6493
|
-
// five points; without it the star reads as a spiky asterisk at this size
|
|
6494
|
-
d: "M12 3.1l2.72 5.52 6.09.89-4.4 4.29 1.04 6.06L12 16.9l-5.45 2.86 1.04-6.06-4.4-4.29 6.09-.89z",
|
|
6495
|
-
fill: "currentColor",
|
|
6496
|
-
stroke: "currentColor",
|
|
6497
|
-
strokeWidth: 2.4,
|
|
6498
|
-
strokeLinejoin: "round"
|
|
6499
|
-
})
|
|
6500
|
-
})
|
|
6600
|
+
children: starIcon(15)
|
|
6501
6601
|
}, "marks-btn");
|
|
6602
|
+
// the curtain's way into the bookmarks: the docked header's star, at the
|
|
6603
|
+
// curtain's own scale (toolBtn), sliding the drawer in from the right exactly
|
|
6604
|
+
// like the magnifier does. It sits in the LEFT pair, right of the settings
|
|
6605
|
+
// button (the docked header's arrangement), not next to the search
|
|
6606
|
+
var sheetMarksBtnEl = toolBtn("marks", "dqt-sheet-marks-btn", toggleMarksCol,
|
|
6607
|
+
Tp("marks.tip.col", { n: markCount }), marksCol, false, starIcon(tbIconLg));
|
|
6502
6608
|
// the curtain keeps its own entry point: the water-drop handle on the top
|
|
6503
6609
|
// edge (sheet.openHandle). The toolbar's curtain button is gone — the header
|
|
6504
6610
|
// is four controls now.
|
|
@@ -6520,8 +6626,10 @@ window.__ModuleLoader__.load({
|
|
|
6520
6626
|
cursor: "pointer"
|
|
6521
6627
|
},
|
|
6522
6628
|
onMouseEnter: function (e) {
|
|
6523
|
-
|
|
6524
|
-
|
|
6629
|
+
// C.chip / C.accent, not the host's brand variables: this button used to stay
|
|
6630
|
+
// blue whatever theme colour the reader picked
|
|
6631
|
+
e.currentTarget.style.background = C.chip;
|
|
6632
|
+
e.currentTarget.style.color = C.accent;
|
|
6525
6633
|
},
|
|
6526
6634
|
onMouseLeave: function (e) {
|
|
6527
6635
|
e.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.16))";
|
|
@@ -6634,7 +6742,10 @@ window.__ModuleLoader__.load({
|
|
|
6634
6742
|
children: [
|
|
6635
6743
|
react_jsx_runtime.jsx("div", {
|
|
6636
6744
|
style: { position: "relative", display: "flex", alignItems: "center", gap: 8, flex: "none" },
|
|
6637
|
-
|
|
6745
|
+
// settings first, then the bookmarks: the LEFT pair is "what the outline
|
|
6746
|
+
// holds", the right pair is "how you look through it" (search) and the way
|
|
6747
|
+
// out — the same split the docked header uses
|
|
6748
|
+
children: [settingsBtnEl, sheetMarksBtnEl]
|
|
6638
6749
|
}),
|
|
6639
6750
|
react_jsx_runtime.jsx("div", { style: { flex: "1 1 auto", minWidth: 0 } }),
|
|
6640
6751
|
searchBtnEl,
|
|
@@ -6742,6 +6853,7 @@ window.__ModuleLoader__.load({
|
|
|
6742
6853
|
// input with a custom animated placeholder: the box stays static,
|
|
6743
6854
|
// only the placeholder text fades when the scope switches
|
|
6744
6855
|
react_jsx_runtime.jsx("div", {
|
|
6856
|
+
className: "dqt-search-pill",
|
|
6745
6857
|
style: {
|
|
6746
6858
|
position: "relative",
|
|
6747
6859
|
flex: 1,
|
|
@@ -6953,7 +7065,7 @@ window.__ModuleLoader__.load({
|
|
|
6953
7065
|
// target lookup, the keyboard cursor, the follow). A saved place needs a label
|
|
6954
7066
|
// and a click — nothing else.
|
|
6955
7067
|
var markRowsEl = null;
|
|
6956
|
-
if (onlyMarks && !resultsEl) {
|
|
7068
|
+
if ((onlyMarks || marksCol) && !resultsEl) {
|
|
6957
7069
|
// `dense` is a local of the row renderers, not of this component: read the
|
|
6958
7070
|
// preference here instead (referencing the other one threw and took the
|
|
6959
7071
|
// whole panel down with it).
|
|
@@ -7047,7 +7159,7 @@ window.__ModuleLoader__.load({
|
|
|
7047
7159
|
// the moment the shorter list rendered, and putting the offset back afterwards
|
|
7048
7160
|
// was a race the browser usually won.
|
|
7049
7161
|
var marksOverlayEl = null;
|
|
7050
|
-
if (onlyMarks && !resultsEl) {
|
|
7162
|
+
if (onlyMarks && !resultsEl && !marksCol) {
|
|
7051
7163
|
marksOverlayEl = react_jsx_runtime.jsx("div", {
|
|
7052
7164
|
className: "dqt-marks-overlay",
|
|
7053
7165
|
style: {
|
|
@@ -7081,8 +7193,9 @@ window.__ModuleLoader__.load({
|
|
|
7081
7193
|
"aria-label": T("a11y.list"),
|
|
7082
7194
|
style: {
|
|
7083
7195
|
// hidden rather than unmounted while the bookmark view is up (see above):
|
|
7084
|
-
// it keeps its height and therefore its scroll offset
|
|
7085
|
-
|
|
7196
|
+
// it keeps its height and therefore its scroll offset. In the curtain the
|
|
7197
|
+
// bookmarks live in their own column, so the outline stays visible.
|
|
7198
|
+
visibility: onlyMarks && !resultsEl && !marksCol ? "hidden" : undefined,
|
|
7086
7199
|
overflowY: "auto",
|
|
7087
7200
|
// We are the only thing that holds the reader's place in this list (see the
|
|
7088
7201
|
// paging block): the browser's own scroll anchoring would also adjust
|
|
@@ -7139,6 +7252,7 @@ window.__ModuleLoader__.load({
|
|
|
7139
7252
|
ref: searchColRef,
|
|
7140
7253
|
className: "dqt-sheet-search",
|
|
7141
7254
|
"data-sheet-search": sheetColW > 0 ? "on" : undefined,
|
|
7255
|
+
"data-sheet-marks": marksCol ? "on" : undefined,
|
|
7142
7256
|
style: {
|
|
7143
7257
|
flex: "none",
|
|
7144
7258
|
width: sheetColW,
|
|
@@ -7148,14 +7262,45 @@ window.__ModuleLoader__.load({
|
|
|
7148
7262
|
},
|
|
7149
7263
|
children: react_jsx_runtime.jsx("div", {
|
|
7150
7264
|
style: { width: SHEET_SEARCH_W, height: "100%", display: "flex", flexDirection: "column", minHeight: 0 },
|
|
7151
|
-
children: [
|
|
7265
|
+
children: marksCol ? [
|
|
7266
|
+
// the same column, the other tenant: a quiet title line with the count,
|
|
7267
|
+
// then the saved rows. No controls — the star in the chrome closes it.
|
|
7268
|
+
react_jsx_runtime.jsx("div", {
|
|
7269
|
+
key: "col-marks-head",
|
|
7270
|
+
style: {
|
|
7271
|
+
flex: "none",
|
|
7272
|
+
display: "flex",
|
|
7273
|
+
alignItems: "center",
|
|
7274
|
+
gap: 8,
|
|
7275
|
+
padding: "10px 12px 9px",
|
|
7276
|
+
borderBottom: "1px solid " + C.panelBorder,
|
|
7277
|
+
fontSize: 12.5,
|
|
7278
|
+
fontWeight: 600,
|
|
7279
|
+
color: C.text
|
|
7280
|
+
},
|
|
7281
|
+
children: [
|
|
7282
|
+
react_jsx_runtime.jsx("span", { key: "t", children: T("marks.title") }, "mh-t"),
|
|
7283
|
+
react_jsx_runtime.jsx("span", {
|
|
7284
|
+
key: "n",
|
|
7285
|
+
style: { fontSize: 11, fontWeight: 400, color: C.muted },
|
|
7286
|
+
children: markCount
|
|
7287
|
+
}, "mh-n")
|
|
7288
|
+
]
|
|
7289
|
+
}),
|
|
7290
|
+
react_jsx_runtime.jsx("div", {
|
|
7291
|
+
key: "col-marks-body",
|
|
7292
|
+
className: "dqt-sheet-marks-body",
|
|
7293
|
+
style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", padding: "2px 6px 8px" },
|
|
7294
|
+
children: markRowsEl
|
|
7295
|
+
})
|
|
7296
|
+
] : [
|
|
7152
7297
|
searchRowEl,
|
|
7153
7298
|
// empty until something is typed — no placeholder note in the way
|
|
7154
7299
|
react_jsx_runtime.jsx("div", {
|
|
7155
7300
|
className: "dqt-sheet-results",
|
|
7156
7301
|
style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", padding: "0 8px 8px", direction: "ltr" },
|
|
7157
7302
|
children: resultsEl
|
|
7158
|
-
})
|
|
7303
|
+
}, "col-results")
|
|
7159
7304
|
]
|
|
7160
7305
|
})
|
|
7161
7306
|
}) : null;
|
|
@@ -7208,7 +7353,7 @@ window.__ModuleLoader__.load({
|
|
|
7208
7353
|
position: "fixed",
|
|
7209
7354
|
top: zLen(baseTopPx),
|
|
7210
7355
|
left: zLen(panelLeft),
|
|
7211
|
-
width: zLen(
|
|
7356
|
+
width: zLen(geoW),
|
|
7212
7357
|
maxHeight: zLen(vMaxH),
|
|
7213
7358
|
height: panelH > 0 ? zLen(panelH) : undefined,
|
|
7214
7359
|
zoom: zoom === 1 ? undefined : zoom,
|
|
@@ -7234,20 +7379,35 @@ window.__ModuleLoader__.load({
|
|
|
7234
7379
|
// One halo on ALL four sides — evenly, corners included (a card lifted off
|
|
7235
7380
|
// the conversation; no downward bias, or the top corners stay bare). Dark
|
|
7236
7381
|
// scheme gets a WHITE one — the theme's own raised language.
|
|
7237
|
-
//
|
|
7238
|
-
//
|
|
7239
|
-
//
|
|
7382
|
+
//
|
|
7383
|
+
// SHADOW_REACH: how far the edge OPPOSITE the wipe is pulled in while the panel
|
|
7384
|
+
// is parked, so the twin's halo cannot spill past the tuck line once the panel
|
|
7385
|
+
// body has been clipped away.
|
|
7386
|
+
// SHADOW_BLEED: how far all four sides bleed while the panel is at rest — the
|
|
7387
|
+
// deepest reach of the halo (15px below with the offset, more at the corners).
|
|
7240
7388
|
var SHADOW_REACH = 8;
|
|
7241
7389
|
var SHADOW_BLEED = 24;
|
|
7242
|
-
|
|
7390
|
+
// The WIPE side is the one the panel slides through. It is clipped by exactly the
|
|
7391
|
+
// same wipe as the panel while anything is moving — zero reach, so the halo is
|
|
7392
|
+
// never drawn where the panel itself is still hidden (any reach there reads as an
|
|
7393
|
+
// outer frame arriving before the surface). The halo the reader wants lives on the
|
|
7394
|
+
// OTHER three sides during the slide, and on this side only once the slide has
|
|
7395
|
+
// LANDED, when it relaxes to the full bleed.
|
|
7396
|
+
//
|
|
7397
|
+
// The relaxation waits for `shadowSettled` (set by the timer in the hook block
|
|
7398
|
+
// above) instead of riding the same transition: transitioning it together was
|
|
7399
|
+
// what let the halo lead the panel's clipped edge.
|
|
7400
|
+
var shadowDockSide = (geoOpen && shadowSettled)
|
|
7401
|
+
? -SHADOW_BLEED
|
|
7402
|
+
: (geoOpen ? 0 : (dockRight ? travelPx : clipWipe));
|
|
7243
7403
|
// Keep the four-sided halo while open. During a tuck, move the wipe edge
|
|
7244
7404
|
// with the panel AND bring the opposite horizontal clip edge inward to meet
|
|
7245
7405
|
// it at the parked position. Leaving that edge at -24px let the shadow twin
|
|
7246
7406
|
// paint a visible strip after the panel body had been clipped away (wider on
|
|
7247
7407
|
// the right dock, a thin sliver on the left dock).
|
|
7248
7408
|
var shadowClip = dockRight
|
|
7249
|
-
? "inset(" + zLen(-SHADOW_BLEED) + " " + zLen(
|
|
7250
|
-
: "inset(" + zLen(-SHADOW_BLEED) + " " + zLen(geoOpen ? -SHADOW_BLEED : -SHADOW_REACH) + " " + zLen(-SHADOW_BLEED) + " " + zLen(
|
|
7409
|
+
? "inset(" + zLen(-SHADOW_BLEED) + " " + zLen(shadowDockSide) + " " + zLen(-SHADOW_BLEED) + " " + zLen(geoOpen ? -SHADOW_BLEED : SHADOW_REACH) + ")"
|
|
7410
|
+
: "inset(" + zLen(-SHADOW_BLEED) + " " + zLen(geoOpen ? -SHADOW_BLEED : -SHADOW_REACH) + " " + zLen(-SHADOW_BLEED) + " " + zLen(shadowDockSide) + ")";
|
|
7251
7411
|
// The panel's blur lives OUTSIDE the clipping wrapper (see the portal list):
|
|
7252
7412
|
// inside it, any backdrop-filter promotes a layer in that subtree and the
|
|
7253
7413
|
// panel drops beneath the conversation. As a body-level twin with the same
|
|
@@ -7313,6 +7473,10 @@ window.__ModuleLoader__.load({
|
|
|
7313
7473
|
});
|
|
7314
7474
|
var panelEl = react_jsx_runtime.jsx("div", {
|
|
7315
7475
|
ref: panelRootRef,
|
|
7476
|
+
// a marker for the reader's own console checks (and for support): the panel
|
|
7477
|
+
// root used to have no attribute at all, so a lookup by class silently
|
|
7478
|
+
// returned null and read as "the panel is gone"
|
|
7479
|
+
"data-dqt-panel": "1",
|
|
7316
7480
|
// the panel takes focus on any click inside it (except into a text field), so
|
|
7317
7481
|
// ↑/↓/Enter/Esc keep working right after a mouse interaction instead of only
|
|
7318
7482
|
// when the user tabs into the widget first
|
|
@@ -7614,8 +7778,17 @@ window.__ModuleLoader__.load({
|
|
|
7614
7778
|
// below the threshold it slides back into the divider (its usual motion), while the
|
|
7615
7779
|
// droplet above the conversation stays put as the way in
|
|
7616
7780
|
var edgeShown = !open && handleShown && chatViewActive && !narrowWindow;
|
|
7617
|
-
|
|
7618
|
-
|
|
7781
|
+
// The pill is 12px centred in the 26px box, so its edge sits 7px inside the box
|
|
7782
|
+
// edge. Left dock: the panel opens at `area + 8`, so parking the box 1px out puts
|
|
7783
|
+
// the pill's left edge exactly on the panel's own left edge — one line.
|
|
7784
|
+
//
|
|
7785
|
+
// Right dock: deliberately NOT aligned. The panel is held 48px clear of the
|
|
7786
|
+
// area's right edge (the milestone rail lives there), so the pill — which hangs
|
|
7787
|
+
// on that edge — is 11px inside the panel's right edge by construction. Pulling
|
|
7788
|
+
// it out to meet the panel would push the pill into the rail, which is worse
|
|
7789
|
+
// than two lines nobody sees at the same time.
|
|
7790
|
+
var edgeLineLeft = (viewport ? viewport.left : 0) + 1;
|
|
7791
|
+
var edgeLineRight = (viewport ? viewport.right + 52 : 60); // clear of the milestone rail
|
|
7619
7792
|
var edgeEl = (!open || !edgeGone) ? react_jsx_runtime.jsx("div", {
|
|
7620
7793
|
style: {
|
|
7621
7794
|
position: "fixed",
|
|
@@ -7882,7 +8055,30 @@ window.__ModuleLoader__.load({
|
|
|
7882
8055
|
fontSize: 11,
|
|
7883
8056
|
lineHeight: "20px"
|
|
7884
8057
|
},
|
|
7885
|
-
|
|
8058
|
+
// A ring with a FILLED core, drawn rather than typed: the ◉/○ glyphs
|
|
8059
|
+
// could only be one colour, and the pin's filled state is exactly the
|
|
8060
|
+
// place the theme colour should show. Outline only when it is not pinned.
|
|
8061
|
+
children: [react_jsx_runtime.jsx("svg", {
|
|
8062
|
+
key: "pin-ico",
|
|
8063
|
+
width: 13,
|
|
8064
|
+
height: 13,
|
|
8065
|
+
viewBox: "0 0 16 16",
|
|
8066
|
+
style: { display: "block" },
|
|
8067
|
+
fill: "none",
|
|
8068
|
+
children: [
|
|
8069
|
+
react_jsx_runtime.jsx("circle", {
|
|
8070
|
+
key: "ring",
|
|
8071
|
+
cx: 8, cy: 8, r: 5.4,
|
|
8072
|
+
stroke: "currentColor",
|
|
8073
|
+
strokeWidth: 1.4
|
|
8074
|
+
}),
|
|
8075
|
+
opts.pinned ? react_jsx_runtime.jsx("circle", {
|
|
8076
|
+
key: "core",
|
|
8077
|
+
cx: 8, cy: 8, r: 2.9,
|
|
8078
|
+
fill: P.accent
|
|
8079
|
+
}) : null
|
|
8080
|
+
]
|
|
8081
|
+
})]
|
|
7886
8082
|
}, "hc-pin")
|
|
7887
8083
|
]
|
|
7888
8084
|
}, "hc-head")
|
|
@@ -7931,7 +8127,12 @@ window.__ModuleLoader__.load({
|
|
|
7931
8127
|
WebkitBackdropFilter: P.glassOn ? P.panelBlur : undefined,
|
|
7932
8128
|
color: P.text,
|
|
7933
8129
|
border: "1px solid " + P.panelBorder,
|
|
7934
|
-
|
|
8130
|
+
// An inset accent bar on the reading side says "this card belongs to that
|
|
8131
|
+
// row" without a second border: a shadow cannot move the box, and it
|
|
8132
|
+
// respects the corner radius. It stays while the card is pinned — that is
|
|
8133
|
+
// when the link matters most.
|
|
8134
|
+
boxShadow: "inset 2px 0 0 " + P.accent
|
|
8135
|
+
+ (P.glassOn ? ", " + rimInset() + ", 0 8px 24px rgba(0,0,0,0.28)" : ", 0 8px 24px rgba(0,0,0,0.28)"),
|
|
7935
8136
|
// above the panel and the curtain — it is a sibling of both in this
|
|
7936
8137
|
// portal, and both sit below it in the layer band we claim
|
|
7937
8138
|
zIndex: Z_HOVER + (opts.pinned ? 1 : 0),
|
|
@@ -8212,6 +8413,10 @@ window.__ModuleLoader__.load({
|
|
|
8212
8413
|
// old content-box the bottom pair sat below it and the clip cut them
|
|
8213
8414
|
// away, which is why the bottom edge had no visible line
|
|
8214
8415
|
boxSizing: "border-box",
|
|
8416
|
+
// the landing sag stretches the box DOWNWARD only, so its top edge never
|
|
8417
|
+
// leaves the clip it hangs from (see the dqt-sheet-in keyframes: a
|
|
8418
|
+
// translated top edge opened a slit that showed the transcript unblurred)
|
|
8419
|
+
transformOrigin: "top center",
|
|
8215
8420
|
display: "flex",
|
|
8216
8421
|
flexDirection: "column",
|
|
8217
8422
|
// The curtain is the transient surface (Windows' "Acrylic" case): the
|
|
@@ -8517,7 +8722,9 @@ window.__ModuleLoader__.load({
|
|
|
8517
8722
|
// that the text never looks like it jumped
|
|
8518
8723
|
e.currentTarget.style.transform = "translateX(1px)";
|
|
8519
8724
|
var gd = e.currentTarget.querySelector("[data-dqt-guide]");
|
|
8520
|
-
|
|
8725
|
+
// the guide column takes the theme colour while its row is the one being
|
|
8726
|
+
// read: a hairline's worth of colour is enough to say "these belong here"
|
|
8727
|
+
if (gd) { gd.style.transform = "translateX(-1px)"; gd.style.background = C.accent; }
|
|
8521
8728
|
if (extra && extra.hoverStart) extra.hoverStart(hoverInfo, e.currentTarget);
|
|
8522
8729
|
},
|
|
8523
8730
|
onMouseLeave: function (e) {
|
|
@@ -8527,7 +8734,7 @@ window.__ModuleLoader__.load({
|
|
|
8527
8734
|
if (b) b.style.opacity = "0";
|
|
8528
8735
|
e.currentTarget.style.transform = "translateX(0)";
|
|
8529
8736
|
var gd2 = e.currentTarget.querySelector("[data-dqt-guide]");
|
|
8530
|
-
if (gd2) gd2.style.transform = "translateX(0)";
|
|
8737
|
+
if (gd2) { gd2.style.transform = "translateX(0)"; gd2.style.background = C.panelBorder; }
|
|
8531
8738
|
if (extra && extra.hoverEnd) extra.hoverEnd();
|
|
8532
8739
|
},
|
|
8533
8740
|
onContextMenu: function (e) {
|
|
@@ -9881,7 +10088,7 @@ window.__ModuleLoader__.load({
|
|
|
9881
10088
|
// ease-out is fast at a segment's start, it read as a snap back into place.
|
|
9882
10089
|
// At the turning point the velocity is zero, so that leg eases IN as well as
|
|
9883
10090
|
// out — that is what makes it a settle instead of a bounce.
|
|
9884
|
-
"@keyframes dqt-sheet-in{from{transform:translateY(calc(-100% - 24px));animation-timing-function:cubic-bezier(0.22, 0.9, 0.3, 1)}62%{transform:translateY(
|
|
10091
|
+
"@keyframes dqt-sheet-in{from{transform:translateY(calc(-100% - 24px));animation-timing-function:cubic-bezier(0.22, 0.9, 0.3, 1)}62%{transform:translateY(0) scaleY(1.014);animation-timing-function:cubic-bezier(0.45, 0, 0.25, 1)}to{transform:translateY(0) scaleY(1)}}" +
|
|
9885
10092
|
// The retract has to clear the shadow too, not just the sheet: the sheet is
|
|
9886
10093
|
// 24px shorter than its shell (that strip is where the drop shadow lands),
|
|
9887
10094
|
// so `translateY(-100%)` parked its own shadow exactly at the shell's top
|
|
@@ -9921,7 +10128,7 @@ window.__ModuleLoader__.load({
|
|
|
9921
10128
|
// keyboard cursor: an INSET outline plus a whisper of fill, so it never
|
|
9922
10129
|
// reflows the row and stays distinct from the reading position's blue box
|
|
9923
10130
|
// (which keeps the 3px left accent bar and the pastel fill)
|
|
9924
|
-
".dqt-nav{box-shadow:inset 0 0 0 1px
|
|
10131
|
+
".dqt-nav{box-shadow:inset 0 0 0 1px #4F8CFF;background:rgba(79,140,255,0.10);border-radius:6px}" +
|
|
9925
10132
|
".dqt-nav:focus{outline:none}" +
|
|
9926
10133
|
// the list itself never draws a focus ring — the cursor row is the indicator
|
|
9927
10134
|
".dqt-list:focus-visible{outline:none}" +
|
|
@@ -9953,7 +10160,7 @@ window.__ModuleLoader__.load({
|
|
|
9953
10160
|
".dqt-pcard:hover{border-color:var(--dsw-alias-label-dimmed)}" +
|
|
9954
10161
|
".dqt-pcardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}" +
|
|
9955
10162
|
".dqt-phead{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}" +
|
|
9956
|
-
".dqt-phead:focus-visible{outline:2px solid
|
|
10163
|
+
".dqt-phead:focus-visible{outline:2px solid #4F8CFF;outline-offset:-2px}" +
|
|
9957
10164
|
".dqt-pheadText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}" +
|
|
9958
10165
|
".dqt-pname{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}" +
|
|
9959
10166
|
".dqt-pdesc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}" +
|
|
@@ -9987,7 +10194,7 @@ window.__ModuleLoader__.load({
|
|
|
9987
10194
|
".dqt-segOn{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-sidebar-nav-item-active)}" +
|
|
9988
10195
|
// scale slider — the fonttune card's slider shape (track, readout, end labels)
|
|
9989
10196
|
".dqt-sliderRow{align-items:center;gap:10px;margin-top:10px;display:flex}" +
|
|
9990
|
-
".dqt-slider{flex:1;min-width:0;height:20px;accent-color
|
|
10197
|
+
".dqt-slider{flex:1;min-width:0;height:20px;accent-color:#4F8CFF}" +
|
|
9991
10198
|
".dqt-slider:disabled{opacity:.4;cursor:default}" +
|
|
9992
10199
|
".dqt-sliderValue{flex:none;min-width:56px;text-align:right;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;font-variant-numeric:tabular-nums}" +
|
|
9993
10200
|
".dqt-sliderScale{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;display:flex;justify-content:space-between}";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quick-toc",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Conversation TOC for DeepSeek Harness (DSH): a whole-session, turn-grouped Markdown outline with hover previews, heading-level preferences, a full-width curtain, jump-to-end buttons, title/prompts/full-text/cross-session search and a Chinese/English interface.",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|