dsh-quick-toc 0.6.1 → 0.6.3
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 +18 -0
- package/CHANGELOG.md +18 -0
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/lib/client.js +178 -84
- package/lib/index.js +6 -0
- package/package.json +2 -2
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-quick-toc** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.6.3] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **The configuration card's expand/collapse chevron did not match the host's arrows**: it was a text character (⌄), thinner than the host's 14×14 SVG chevron icon, and the glyph's ink is not centred inside its 20px line box — the rotation turned around the box centre, so the glyph swung aside instead of flipping in place. It is now the host's own 14×14 SVG chevron (an inline SVG whose path is byte-identical to the icon the host uses, with the class on the `<svg>` itself — the same structure as the host's plugin card), so expanding and collapsing flip it around the icon's own centre.
|
|
9
|
+
|
|
10
|
+
## [0.6.2] - 2026-09-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **A vertical position for the collapsed handle**: the card gains a "Handle position" row — a **0%–100% slider in 1% steps** that decides where the edge handle sits vertically once the panel is collapsed: `0%` at the bottom, `100%` at the top, and `50%` (the previously fixed centred spot) by default. The handle follows that height on whichever edge the panel is docked to.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Writing a default value back means "not customized"**: as soon as a card field is back at its default (a slider returned to the default step, a switch returned to its default state, the level set back to the default selection), the entry leaves the document's user layer — the "customized" badge and that field's "Reset" go with it, exactly as if it had never been changed. 0.6.1 and earlier pinned the default into the settings document and kept the badge.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **A panel size could fail to reach storage**: the size write is debounced by 0.4s, and the "already published" record was advanced the moment a write was scheduled — so a second adjustment inside that window (dragging the height, dragging the top bar, toggling a level or the fuzzy switch) cancelled the pending write while the record already counted it as published, and that size was never written again. Measured: drag the width 200 → 150 and then drag the height, and the stored width stayed 200 — the next load brought the panel back 200px wide. The record now advances only when the write really happens, and a cancelled write is issued again on the next pass.
|
|
20
|
+
- **A narrow or short panel's size was stored in vain**: the limits used when reading a size back disagreed with the drag limits — dragging allows 120px wide and 60px high, but anything under 180px wide / 160px high was discarded as invalid on the next load, dropping the panel back to the default width. Both sides now share the same floors (120px / 60px).
|
|
21
|
+
- **The first height drag jumped while the height was "auto"**: the drag started from a fixed 400px guess, so a panel that was in fact taller or shorter jumped to a wrong height on the very first move. It now starts from the panel's measured height (the visual height after the panel scale — the unit that gets stored), falling back to 400px only when nothing can be measured.
|
|
22
|
+
|
|
5
23
|
## [0.6.1] - 2026-09-15
|
|
6
24
|
|
|
7
25
|
### Added
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-quick-toc** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.6.3] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
- **配置卡片的展开/收起箭头与宿主界面的箭头不一致**:箭头原先是一个文本字符(⌄),比宿主 14×14 的 SVG 箭头图标更细;而且字符的墨迹在 20px 行盒里并不居中,旋转绕的是盒子中心,转起来字符就被甩向一旁,看起来不像绕自身翻转。现在换成与宿主一致的 14×14 SVG 箭头图标(内联 SVG,路径与宿主所用图标逐字节一致,类名直接挂在 `<svg>` 上——与宿主插件卡片相同的做法),展开/收起时绕图标自身中心原地翻转。
|
|
9
|
+
|
|
10
|
+
## [0.6.2] - 2026-09-15
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
13
|
+
- **收起把手的垂直位置**:设置卡片里新增「把手位置」——**0%–100%、每档 1%** 的滑块,决定面板收起后那枚边缘把手停在对话区的什么高度:`0%` 在最下、`100%` 在最上,默认 `50%`(就是原先固定的居中位置)。无论面板停在哪一侧,把手都按这个高度贴边。
|
|
14
|
+
|
|
15
|
+
### 变更
|
|
16
|
+
- **写回默认值等于「未自定义」**:卡片里的字段一旦回到它的默认值(滑块拖回默认档、开关拨回默认状态、层级选回默认集合),该项就从设置的用户层移除——「已自定义」标记与「重置」随之消失,等同于从未改过。0.6.1 及更早版本会把默认值也写进设置文档,并且一直标着「已自定义」。
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
- **面板尺寸可能不落盘**:尺寸写入有 0.4 秒去抖,而"已发布"的台账在发起写入的那一刻就被推进——拖动后在 0.4 秒内再动一次(拖高度、拖顶栏、切层级或模糊开关等),前一次的待写会被取消,台账却已经记成发布过,于是那个尺寸再也不会写入。实测:宽度 200 → 150 之后再拖高度,存下来的宽度仍是 200,刷新后面板回到 200。现在台账只在真正写入时推进,被取消的那次会在下一轮重新发出。
|
|
20
|
+
- **宽度或高度偏小的面板存了也白存**:读回尺寸的下限与拖动下限不一致——拖动允许宽度到 120px、高度到 60px,但下次打开时低于 180px 宽 / 160px 高的值会被当作无效丢弃,面板回到默认宽度。现在两侧下限一致(120px / 60px)。
|
|
21
|
+
- **高度为「自动」时第一次拖高度会跳**:拖拽起点原先按固定的 400px 估算,面板实际更高或更矮时,第一次移动就跳到错误的高度。现在起点取面板的实测高度(缩放后的视觉高度,与存储同一个单位),量不到才退回 400px。
|
|
22
|
+
|
|
5
23
|
## [0.6.1] - 2026-09-15
|
|
6
24
|
|
|
7
25
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -10,7 +10,7 @@ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
10
10
|
- **Whole-session coverage** — turns the conversation window has not loaded are listed too (tagged `未加载` / "Not loaded", with previews); clicking one loads that turn and jumps to it
|
|
11
11
|
- **Failures are reported** — a turn with no reply at all (request timeout, upstream error) shows `请求失败` / "Request failed" plus the host's error text, and clicking it jumps to that error in the conversation
|
|
12
12
|
- **Bilingual** — the interface language can follow the host, or be forced to Chinese or English; Chinese stamps use `昨天` / `前天`, English reads yesterday as the word (`yesterday`) and carries the `YY-MM-DD HH:MM` date for anything older
|
|
13
|
-
- **A plugin-configuration card** — a "Conversation Outline" card under Settings → Plugins → Plugin configuration: the language, the default docked edge, the
|
|
13
|
+
- **A plugin-configuration card** — a "Conversation Outline" card under Settings → Plugins → Plugin configuration: the language, the default docked edge, the heading levels shown, where the collapsed handle sits, fuzzy search, hover previews and the diagnostic switch; changed fields are marked "customized" and can be reset individually (returning a field to its default drops the mark), and the card and the panel stay in sync
|
|
14
14
|
- **Turn stamps carry the day** — yesterday reads `昨天 15:04`, the day before `前天 15:04`, anything older `25-09-11 15:04`, so times never blur together in a session spanning days (with the interface in English every stamp older than today carries a `YY-MM-DD` date instead)
|
|
15
15
|
- **Row subtitles** — under each heading, the first sentence of that section, so identically-titled headings can be told apart
|
|
16
16
|
- **Hover previews** — hovering a heading row shows the section's opening, the turn time and the heading path
|
|
@@ -22,7 +22,7 @@ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
22
22
|
- **Auto-follow** — the turn being read lights up in a closed blue box while you scroll the conversation; the outline follows on its own
|
|
23
23
|
- **Jumping** — click a heading to jump to its position in the conversation, with the scroll animation handed to the browser's own smooth scrolling (advanced on the wall clock, so it takes the same time on a 60 Hz and a 240 Hz panel); when the host re-pages and moves the target mid-flight the jump re-aims, and when the host's own scrolling cancels the animation it is issued again, so a cross-page jump never stops halfway; a browser that carries the call out at once gets a frame-by-frame glide drawn by the plugin instead
|
|
24
24
|
- **Back to the newest row** — after paging far up, a floating button in the list's lower-right corner returns to the newest entry in one click (it appears when the list is not at its bottom and disappears once it is)
|
|
25
|
-
- **Dockable and resizable** — drag the top bar to move the panel, ◀ / ▶ to dock left or right, drag an edge to resize (no upper bound, down to 120px wide), and collapse it into an edge handle; position and size are remembered per browser (top offset, width and height are adjusted by dragging only)
|
|
25
|
+
- **Dockable and resizable** — drag the top bar to move the panel, ◀ / ▶ to dock left or right, drag an edge to resize (no upper bound, down to 120px wide), and collapse it into an edge handle (its height is set by the card's "Handle position"); position and size are remembered per browser (top offset, width and height are adjusted by dragging only)
|
|
26
26
|
- **Panel scale** — a slider in the settings card scales the **content** (text, icons, buttons and their spacing) from 50% to 200% in 5% steps while the **panel's own size stays exactly as dragged**; dragging only moves the readout, and the settings document is written once you let go. A narrow panel keeps its four header buttons on one line: the empty middle gives way first, then the whole row scrolls sideways (wheel over the header), and the grey grab bar fades out
|
|
27
27
|
- **Paging** — the most recent groups show first; scrolling up both expands the index and loads older conversation
|
|
28
28
|
- **Edge hints** — a brief hint at the bottom of the panel when you keep scrolling past the first or the last entry
|
|
@@ -34,7 +34,7 @@ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
34
34
|
|
|
35
35
|
| Plugin | Supported DSH |
|
|
36
36
|
| --- | --- |
|
|
37
|
-
| **0.6.x** (latest, 0.6.
|
|
37
|
+
| **0.6.x** (latest, 0.6.3) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
|
38
38
|
| 0.5.x (0.5.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
|
39
39
|
| 0.4.x (0.4.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
|
40
40
|
| 0.3.x (0.3.3) | 0.1.5-rc.1 |
|
|
@@ -42,7 +42,7 @@ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
42
42
|
|
|
43
43
|
Each major line lists only its newest patch (the defects a new feature introduces are fixed in the patches that follow, so within one major line the newest patch is the one to use; older patches keep working — the plugin does not break existing interfaces).
|
|
44
44
|
|
|
45
|
-
`engines.dsh` has a floor of **0.1.5-rc.1** (`>=`): from that release on, the plugin uses the host's session-scoped slot injection. 0.6.
|
|
45
|
+
`engines.dsh` has a floor of **0.1.5-rc.1** (`>=`): from that release on, the plugin uses the host's session-scoped slot injection. 0.6.3 was tested on **0.1.5-rc.2** (this release's only change is the configuration card's expand/collapse chevron, which was compared byte-for-byte against the host's own chevron icon and verified to flip around the icon's own centre in a real browser; 0.6.2 already measured a dragged size reaching storage and coming back after a reload, and the collapsed handle's position; 0.6.1 already measured the host half's settings-namespace registration, the configuration card, the jump animation and the panel scale); on **0.1.5-rc.1** the plugin's loading and client-module delivery were tested for real (the host boots, and the `dsh-quick-toc/client.js` rev in its boot graph is byte-identical to this repository's `lib/client.js`). The host interfaces it relies on — the `turnOutline` projection, the session-scoped `useProjection`, the client `sessions` service and its `loadThrough` jump loader (since 0.5.0), the `turn-error` node used for failed turns (since 0.5.1), and the host `settings` service (`installSection`), the client `settingsScope` and the `settings.plugin.item` card slot added in 0.6.0 — were checked one by one against the installed 0.1.5-rc.1 and 0.1.5-rc.2 bundles, whose code is byte-identical between the two releases (`dsh-client-ui-chat` differs by a single CSS declaration unrelated to this plugin); nothing in 0.6.3 relies on a new host interface (the chevron is an inline SVG inside the plugin's own card), so both releases are claimed. Older or newer DSH versions are unverified and therefore not claimed; on an older DSH the newest usable plugin version is **0.3.2**. On install or update, the DSH market pre-flights host compatibility from `engines.dsh`, `dsh.compatibility.dshReleases` and `peerDependencies` in `package.json`.
|
|
46
46
|
|
|
47
47
|
## Install
|
|
48
48
|
|
|
@@ -77,7 +77,7 @@ After installing, restart DSH and open the Web UI. The panel starts collapsed; c
|
|
|
77
77
|
- **Loading older turns**: scroll up inside the outline (it both expands the index and loads older conversation)
|
|
78
78
|
- **Back to the newest row**: after paging far up, click the floating button in the list's lower-right corner
|
|
79
79
|
- **Reading position**: scroll the conversation and the turn you are reading is boxed in blue; the outline follows
|
|
80
|
-
- **Settings**: expand the "Conversation Outline" card under **Settings → Plugins → Plugin configuration** for the language, the default docked edge, the panel scale (a 50%–200% slider in 5% steps), the heading levels shown and the fuzzy / hover / diagnostic switches; changed fields can be reset individually. The card and the panel are live in both directions, with no reload needed, and the preferences follow the DSH settings document
|
|
80
|
+
- **Settings**: expand the "Conversation Outline" card under **Settings → Plugins → Plugin configuration** for the language, the default docked edge, the panel scale (a 50%–200% slider in 5% steps), where the collapsed handle sits (a 0%–100% slider in 1% steps: 0% at the bottom, 100% at the top, 50% centred by default), the heading levels shown and the fuzzy / hover / diagnostic switches; changed fields can be reset individually (returning one to its default drops the mark). The card and the panel are live in both directions, with no reload needed, and the preferences follow the DSH settings document
|
|
81
81
|
|
|
82
82
|
## Diagnostics
|
|
83
83
|
|
|
@@ -98,7 +98,7 @@ A missing `turnOutline` means the host has no such projection, and the staged `j
|
|
|
98
98
|
- The "unloaded turn" capability needs two host facilities: the `turnOutline` projection (the whole-session turn index, each entry carrying its `turn/start` seq) and the turn-jump loader (the client `sessions` service's `binding(sessionId).session.loadThrough(seq)`, reached through the client ctx's declaration-free `ctx.get("sessions")` lookup). Each degrades on its own: without the projection the outline lists loaded turns only, without the loader unloaded entries are shown but not jumped to, and nothing else in the panel is affected.
|
|
99
99
|
- The failure row reads the host's `turn-error` conversation node (published when `turn/end` carries an `error` reason, with `message` and an optional `code`); on a host without that node the row is simply absent and nothing else changes.
|
|
100
100
|
- Interface text lives in one string table at the top of `lib/client.js` (`DICTS`, one Chinese and one English copy) and the language setting picks between them: on `follow the host` the host's translate function (`ctx.locale.bind("dsh-quick-toc")`, fed by the same `DICTS` tables) is asked first and the built-in table is only the fallback. The two tables must stay key-aligned; the only Chinese-only entry is `time.beforeYesterday` (English carries the date for anything older than yesterday), while `time.yesterday` exists in both (`yesterday`).
|
|
101
|
-
- Preferences sit in two layers behind one module-level store: the Host settings document (`ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })` — language, docked edge, levels, fuzzy, hover and diagnostic; authoritative on a loopback page) and `localStorage` (the mirror, plus the real storage for top offset / width / height, which describe this screen and are saved per browser). Writes are routed per field: host fields go through `scope.mutate` (folded in locally, reconciled when the host answers), everything else goes to `localStorage
|
|
101
|
+
- Preferences sit in two layers behind one module-level store: the Host settings document (`ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })` — language, docked edge, levels, scale, handle position, fuzzy, hover and diagnostic; authoritative on a loopback page) and `localStorage` (the mirror, plus the real storage for top offset / width / height, which describe this screen and are saved per browser). Writes are routed per field: host fields go through `scope.mutate` (folded in locally, reconciled when the host answers), everything else goes to `localStorage`; a value that IS the field's default is written by removing that entry from the user layer instead (the same thing as "not customized"). On a non-loopback page DSH marks settings read-only, so those eight preferences fall back to the mirror and behave exactly as in 0.5.x; values left by a 0.5.x install are imported into the host layer once, the first time it answers (and only while the user layer is empty).
|
|
102
102
|
- The configuration card registers into the `settings.plugin.item` slot (keyed on the plugin's namespace; the slot is dispatched only for namespaces the Host actually serves, so a deployment without the host half simply shows no card and the panel is unaffected). The card and the panel share the store above, which is what makes the two live in both directions.
|
|
103
103
|
- Changes to `lib/client.js` show up after a page refresh (client modules are served under a content hash and DSH's client HMR pushes reloads); changes to `lib/index.js` (the host half) need a DSH restart
|
|
104
104
|
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- **覆盖整段会话** —— 未加载进对话窗口的回合也在列表里(带「未加载」标记与预览);点击即加载该回合并跳过去
|
|
11
11
|
- **失败回合的报错** —— 请求超时 / 上游报错这类没有回复的回合显示 `请求失败` 与报错原文,点击跳到对话中的报错位置
|
|
12
12
|
- **中英双语** —— 界面语言可设为跟随宿主、中文或 English;中文时间戳用 `昨天` / `前天`,英文昨天用 `yesterday`、更早用 `YY-MM-DD HH:MM` 日期
|
|
13
|
-
- **插件配置卡片** —— 设置 → 插件 →
|
|
13
|
+
- **插件配置卡片** —— 设置 → 插件 → 插件配置里的「对话大纲」卡片:语言、默认停靠边缘、显示的标题层级、收起把手的位置、模糊搜索、悬停预览与诊断开关;改过的字段标「已自定义」并可单独重置(改回默认值时标记自动消失),卡片与面板实时同步
|
|
14
14
|
- **回合时间带日期** —— 昨天 `昨天 15:04`、前天 `前天 15:04`、更早 `25-09-11 15:04`,跨天的会话里时间不再重影
|
|
15
15
|
- **标题行副标题** —— 每行标题下方显示该节正文的第一句,同名标题一眼可辨
|
|
16
16
|
- **悬停预览** —— 悬停标题行显示该节开头、回合时间与层级路径
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **自动跟随** —— 滚动对话时正在阅读的回合自动点亮(封闭蓝框),大纲自动跟随
|
|
23
23
|
- **跳转** —— 点击标题跳到对话中该标题的位置,滚动动画交给浏览器自己的平滑滚动(按真实时间推进,60Hz 与 240Hz 屏幕上时长一致);飞行中目标被宿主翻页挪动时会重新瞄准、动画被宿主滚动打断时会重新发起,跨页远跳不会半途停下;若某个浏览器把平滑滚动直接执行成瞬移,插件改为自己逐帧绘制。面板与对话双向定位
|
|
24
24
|
- **回到底部** —— 往上翻远之后,列表右下角的浮动按钮一步回到最新一条(不在底部时出现、到底后消失)
|
|
25
|
-
- **可停靠、可缩放** —— 拖顶部横条移动,◀ / ▶ 切换左右停靠,拖边缘调宽高(尺寸没有上限,最窄可到 120px
|
|
25
|
+
- **可停靠、可缩放** —— 拖顶部横条移动,◀ / ▶ 切换左右停靠,拖边缘调宽高(尺寸没有上限,最窄可到 120px),收起后成为边缘把手(停在什么高度由设置里的「把手位置」决定);位置与尺寸按浏览器记住(顶边距、宽度、高度只通过拖拽调整)
|
|
26
26
|
- **面板缩放** —— 设置里的滑块把**内容**(文字、图标、按钮及其间距)在 50%–200% 之间按 5% 一档缩放,**面板本身的尺寸不变**;拖动滑块时只移动读数,松手才写入设置。面板变窄时顶栏四颗按钮保持一行:先挤掉中间的空白,挤满后整行可横向滚动(在顶栏上滚滚轮即可),此时顶部的灰色拖拽横条渐隐消失
|
|
27
27
|
- **分页** —— 默认显示最近的若干组,向上滚动既展开索引也加载更早的对话
|
|
28
28
|
- **边界提示** —— 滚到最早或最新再继续滚动时,面板底部短暂提示
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
| 插件版本 | 支持的 DSH 版本 |
|
|
36
36
|
| --- | --- |
|
|
37
|
-
| **0.6.x**(最新,0.6.
|
|
37
|
+
| **0.6.x**(最新,0.6.3) | 0.1.5-rc.1、0.1.5-rc.2 |
|
|
38
38
|
| 0.5.x(0.5.1) | 0.1.5-rc.1、0.1.5-rc.2 |
|
|
39
39
|
| 0.4.x(0.4.1) | 0.1.5-rc.1、0.1.5-rc.2 |
|
|
40
40
|
| 0.3.x(0.3.3) | 0.1.5-rc.1 |
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
每个大版本只列该系列最新的一个补丁版本(新功能引入的缺陷都在其后的补丁里修掉了,所以同一个大版本内直接用最新补丁即可;旧补丁仍可继续用,插件不破坏既有接口)。
|
|
44
44
|
|
|
45
|
-
`engines.dsh` 下限为 **0.1.5-rc.1**(`>=`,该版本起插件改用会话级槽位注入的宿主接口)。0.6.
|
|
45
|
+
`engines.dsh` 下限为 **0.1.5-rc.1**(`>=`,该版本起插件改用会话级槽位注入的宿主接口)。0.6.3 在 **0.1.5-rc.2** 上实测通过(本轮改动只有配置卡片的展开/收起箭头,已在真实浏览器里与宿主的同款图标逐字节比对、并验证展开/收起时绕图标自身中心翻转;0.6.2 已实测面板尺寸的落盘与刷新后的读回、收起把手的位置;0.6.1 已实测宿主半的设置命名空间注册、配置卡片、跳转动画与面板缩放);**0.1.5-rc.1** 上实测了插件加载与客户端模块下发(宿主启动正常、boot graph 中 `dsh-quick-toc/client.js` 的 rev 与本仓库 `lib/client.js` 逐字节一致),它依赖的宿主接口——`turnOutline` 投影、会话级槽位的 `useProjection`、客户端 `sessions` 服务与 `loadThrough` 跳转加载器(0.5.0 起)、失败回合所用的 `turn-error` 节点(0.5.1 起)、以及 0.6.0 新增的宿主 `settings` 服务(`installSection`)、客户端 `settingsScope` 与 `settings.plugin.item` 配置卡片槽位——在 0.1.5-rc.1 与 0.1.5-rc.2 的安装包中逐个核对存在(这些宿主包的代码在两版之间逐字节相同,`dsh-client-ui-chat` 仅差一条与本插件无关的 CSS 声明);0.6.3 的改动都不依赖新的宿主接口(箭头是插件自己卡片里的内联 SVG),因此两版都声明兼容。更早或更新的 DSH 版本未经验证,不作声明;在更早的 DSH 上可安装的最新插件版本是 **0.3.2**。安装或更新时,DSH 市场会依据 `package.json` 中的 `engines.dsh`、`dsh.compatibility.dshReleases` 与 `peerDependencies` 做宿主兼容预检。
|
|
46
46
|
|
|
47
47
|
## 安装
|
|
48
48
|
|
|
@@ -77,7 +77,7 @@ dsh plugin --profile web add <插件目录路径>
|
|
|
77
77
|
- **加载更早**:在大纲中向上滚动(既展开索引,也加载更早的对话)
|
|
78
78
|
- **回到底部**:翻远了之后点列表右下角的浮动按钮回到最新一条
|
|
79
79
|
- **阅读位置**:滚动对话,正在阅读的回合会以蓝框标出;大纲会自动跟随
|
|
80
|
-
- **设置**:在 **设置 → 插件 → 插件配置** 里展开「对话大纲」卡片,可改语言、默认停靠边缘、面板缩放(50%–200% 滑块,5%
|
|
80
|
+
- **设置**:在 **设置 → 插件 → 插件配置** 里展开「对话大纲」卡片,可改语言、默认停靠边缘、面板缩放(50%–200% 滑块,5% 一档)、收起把手的位置(0%–100% 滑块,1% 一档;0% 在最下、100% 在最上,默认 50% 居中)、显示的标题层级与模糊/悬停/诊断开关;改过的字段可单独重置(改回默认值时标记自动消失)。卡片与面板实时同步,不需要刷新;偏好保存在 DSH 的设置里,跟随配置走
|
|
81
81
|
|
|
82
82
|
## 诊断
|
|
83
83
|
|
|
@@ -98,7 +98,7 @@ dsh plugin --profile web add <插件目录路径>
|
|
|
98
98
|
- 「未加载回合」能力依赖宿主的两样东西:`turnOutline` 投影(整段会话的回合索引,每条含 `turn/start` 的 seq)与会话跳转加载器(客户端 `sessions` 服务的 `binding(sessionId).session.loadThrough(seq)`,通过客户端 ctx 的 `ctx.get("sessions")` 免声明查找取得)。两者各自独立降级:没有投影时只列已加载回合,没有加载器时未加载条目只展示、不跳转,面板其余功能不受影响。
|
|
99
99
|
- 失败回合的报错行读的是宿主的 `turn-error` 会话节点(宿主在 `turn/end` 的原因为 `error` 时发布,含 `message` 与可选的 `code`);宿主不提供该节点时只是不显示这一行,其余功能不受影响。
|
|
100
100
|
- 界面文字来自 `lib/client.js` 顶部的一张字符串表(`DICTS`,中英各一份),语言设置决定用哪一份:`跟随宿主` 时优先问宿主的翻译函数(`ctx.locale.bind("dsh-quick-toc")`,注册的表就来自 `DICTS`),拿不到才回退到内置表。中英两份表的键必须对齐(仅 `time.beforeYesterday`(前天)是中文独有——英文对更早的时间直接用日期;`time.yesterday` 两语言都有,英文作 `yesterday`)。
|
|
101
|
-
- 偏好存储分两层,由一个模块级 store 统一读出:宿主设置文档(`ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })
|
|
101
|
+
- 偏好存储分两层,由一个模块级 store 统一读出:宿主设置文档(`ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })`,语言/停靠边/层级/缩放/把手位置/模糊/悬停/诊断八项,回环页面上是权威层)与 `localStorage`(镜像 + 顶边距/宽度/高度三项的正式存储——它们描述"这一块屏幕",按浏览器保存)。写入按字段路由:宿主字段走 `scope.mutate`(本地同步折叠、宿主应答后对账),其余写 `localStorage`;写入的值若正好是该字段的默认值,则改为把用户层里的这一项删掉(等同于"未自定义")。非回环页面 DSH 将设置标记为只读,此时这八项偏好退回本地镜像,行为与 0.5.x 一致;0.5.x 留下的本地值会在宿主层首次应答时导入一次(仅当用户层为空)。
|
|
102
102
|
- 插件配置卡片注册进 `settings.plugin.item` 槽(`key` 为插件命名空间;该槽按宿主实际提供的命名空间派发,宿主半未加载时卡片自然不出现,面板不受影响);卡片与面板共享上面那个 store,因此两边实时互通。
|
|
103
103
|
- 修改 `lib/client.js` 后刷新页面即可看到变化(客户端模块按内容哈希发版,DSH 的客户端 HMR 也会推送重载);改 `lib/index.js`(宿主半)需重启 DSH
|
|
104
104
|
|
package/lib/client.js
CHANGED
|
@@ -77,6 +77,8 @@ window.__ModuleLoader__.load({
|
|
|
77
77
|
"settings.levels": "显示的标题层级",
|
|
78
78
|
"settings.zoom": "面板缩放",
|
|
79
79
|
"settings.zoom.tip": "文字、图标与按钮按比例缩放,面板本身的尺寸不变",
|
|
80
|
+
"settings.handle": "把手位置",
|
|
81
|
+
"settings.handle.tip": "收起面板后,边缘把手在对话区里的上下位置(0% 最下、100% 最上)",
|
|
80
82
|
"settings.on": "开启",
|
|
81
83
|
"settings.off": "关闭",
|
|
82
84
|
"settings.fuzzy": "模糊搜索",
|
|
@@ -139,6 +141,8 @@ window.__ModuleLoader__.load({
|
|
|
139
141
|
"settings.levels": "Heading levels shown",
|
|
140
142
|
"settings.zoom": "Panel scale",
|
|
141
143
|
"settings.zoom.tip": "Scales the text, icons and buttons; the panel's own size is unchanged",
|
|
144
|
+
"settings.handle": "Handle position",
|
|
145
|
+
"settings.handle.tip": "Where the collapsed-panel handle sits vertically (0% bottom, 100% top of the conversation area)",
|
|
142
146
|
"settings.on": "On",
|
|
143
147
|
"settings.off": "Off",
|
|
144
148
|
"settings.fuzzy": "Fuzzy search",
|
|
@@ -205,13 +209,13 @@ window.__ModuleLoader__.load({
|
|
|
205
209
|
// purpose — they describe THIS screen, not the account.
|
|
206
210
|
var NAMESPACE = "dsh-quick-toc";
|
|
207
211
|
// Fields backed by the Host settings document; everything else is localStorage.
|
|
208
|
-
var HOST_FIELDS = { dock: 1, lang: 1, levels: 1, zoom: 1, fuzzy: 1, hover: 1, debug: 1 };
|
|
212
|
+
var HOST_FIELDS = { dock: 1, lang: 1, levels: 1, zoom: 1, handle: 1, fuzzy: 1, hover: 1, debug: 1 };
|
|
209
213
|
// The schema defaults (mirrored here for the card's per-field reset and for the
|
|
210
214
|
// one-time import of a 0.5.x install's stored values).
|
|
211
215
|
var DEFAULTS = {
|
|
212
216
|
dock: "left", y: 0, w: 0, h: 0,
|
|
213
217
|
levels: [1, 2, 3, 4, 5, 6],
|
|
214
|
-
zoom: 1,
|
|
218
|
+
zoom: 1, handle: 0.5,
|
|
215
219
|
fuzzy: false, hover: true, lang: "auto", debug: false
|
|
216
220
|
};
|
|
217
221
|
var PREF_KEYS = {
|
|
@@ -221,11 +225,21 @@ window.__ModuleLoader__.load({
|
|
|
221
225
|
h: "dsh-quick-toc.panelH.v1",
|
|
222
226
|
levels: "dsh-quick-toc.levels.v1",
|
|
223
227
|
zoom: "dsh-quick-toc.zoom.v1",
|
|
228
|
+
handle: "dsh-quick-toc.handle.v1",
|
|
224
229
|
fuzzy: "dsh-quick-toc.fuzzy.v1",
|
|
225
230
|
hover: "dsh-quick-toc.hover.v1",
|
|
226
231
|
lang: "dsh-quick-toc.lang.v1",
|
|
227
232
|
debug: "dsh-quick-toc.debug"
|
|
228
233
|
};
|
|
234
|
+
// Writing a field's schema default through the card IS its reset — a value that
|
|
235
|
+
// equals the default carries no customization, so it must not pin an entry (and
|
|
236
|
+
// the "customized" badge with it) into the user layer.
|
|
237
|
+
var isDefaultValue = function (key, value) {
|
|
238
|
+
var d = DEFAULTS[key];
|
|
239
|
+
if (typeof value === "number" && typeof d === "number") return Math.round(value * 100) === Math.round(d * 100);
|
|
240
|
+
if (Array.isArray(value) || Array.isArray(d)) return JSON.stringify(value) === JSON.stringify(d);
|
|
241
|
+
return value === d;
|
|
242
|
+
};
|
|
229
243
|
// Legacy keys from the renamed plugin (dsh-dagang -> dsh-contents -> dsh-quick-toc).
|
|
230
244
|
var PREF_LEGACY = {
|
|
231
245
|
dock: ["dsh-contents.dock.v2", "dsh-dagang.dock.v2"],
|
|
@@ -280,14 +294,19 @@ window.__ModuleLoader__.load({
|
|
|
280
294
|
var hRaw = read(PREF_KEYS.h);
|
|
281
295
|
var langRaw = read(PREF_KEYS.lang);
|
|
282
296
|
var zoomRaw = read(PREF_KEYS.zoom); // NB: absent (null) means 1 — Number(null) is 0!
|
|
297
|
+
var handleRaw = read(PREF_KEYS.handle); // absent (null) means 0.5 — same trap
|
|
298
|
+
var clampRatio = function (raw, fallback) {
|
|
299
|
+
return raw !== null && isFinite(Number(raw)) ? Math.min(1, Math.max(0, Math.round(Number(raw) * 100) / 100)) : fallback;
|
|
300
|
+
};
|
|
283
301
|
return {
|
|
284
302
|
dock: dockRaw === "right" ? "right" : "left",
|
|
285
303
|
// 0 means "the panel's own default" (see PANEL_WIDTH / auto height)
|
|
286
304
|
y: yRaw !== null && isFinite(Number(yRaw)) ? Number(yRaw) : 0,
|
|
287
|
-
w: wRaw !== null && isFinite(Number(wRaw)) && Number(wRaw) >=
|
|
288
|
-
h: hRaw !== null && isFinite(Number(hRaw)) && Number(hRaw) >=
|
|
305
|
+
w: wRaw !== null && isFinite(Number(wRaw)) && Number(wRaw) >= 120 ? Number(wRaw) : 0,
|
|
306
|
+
h: hRaw !== null && isFinite(Number(hRaw)) && Number(hRaw) >= 60 ? Number(hRaw) : 0,
|
|
289
307
|
levels: parseLevels(rawLevels) || LEVELS_ALL.slice(),
|
|
290
|
-
zoom:
|
|
308
|
+
zoom: clampRatio(zoomRaw, 1),
|
|
309
|
+
handle: clampRatio(handleRaw, 0.5),
|
|
291
310
|
fuzzy: read(PREF_KEYS.fuzzy) === "1",
|
|
292
311
|
// absent means "on": the hover card predates this switch
|
|
293
312
|
hover: read(PREF_KEYS.hover) !== "0",
|
|
@@ -303,6 +322,7 @@ window.__ModuleLoader__.load({
|
|
|
303
322
|
h: function (v) { return String(v); },
|
|
304
323
|
levels: function (v) { return JSON.stringify(v); },
|
|
305
324
|
zoom: function (v) { return String(v); },
|
|
325
|
+
handle: function (v) { return String(v); },
|
|
306
326
|
fuzzy: function (v) { return v ? "1" : "0"; },
|
|
307
327
|
hover: function (v) { return v ? "1" : "0"; },
|
|
308
328
|
lang: function (v) { return v; },
|
|
@@ -326,11 +346,13 @@ window.__ModuleLoader__.load({
|
|
|
326
346
|
var v = snap.value;
|
|
327
347
|
var levels = parseLevelList(v.levels);
|
|
328
348
|
var zoomNum = Number(v.zoom);
|
|
349
|
+
var handleNum = Number(v.handle);
|
|
329
350
|
return {
|
|
330
351
|
dock: v.dock === "right" ? "right" : "left",
|
|
331
352
|
lang: v.lang === "zh" || v.lang === "en" ? v.lang : "auto",
|
|
332
353
|
levels: levels || LEVELS_ALL.slice(),
|
|
333
354
|
zoom: isFinite(zoomNum) ? Math.min(2, Math.max(0.5, Math.round(zoomNum * 100) / 100)) : 1,
|
|
355
|
+
handle: isFinite(handleNum) ? Math.min(1, Math.max(0, Math.round(handleNum * 100) / 100)) : 0.5,
|
|
334
356
|
fuzzy: v.fuzzy === true,
|
|
335
357
|
hover: v.hover !== false,
|
|
336
358
|
debug: v.debug === true
|
|
@@ -342,7 +364,7 @@ window.__ModuleLoader__.load({
|
|
|
342
364
|
if (!over) return local;
|
|
343
365
|
return {
|
|
344
366
|
dock: over.dock, y: local.y, w: local.w, h: local.h,
|
|
345
|
-
levels: over.levels, zoom: over.zoom, fuzzy: over.fuzzy, hover: over.hover,
|
|
367
|
+
levels: over.levels, zoom: over.zoom, handle: over.handle, fuzzy: over.fuzzy, hover: over.hover,
|
|
346
368
|
lang: over.lang, debug: over.debug
|
|
347
369
|
};
|
|
348
370
|
};
|
|
@@ -370,6 +392,23 @@ window.__ModuleLoader__.load({
|
|
|
370
392
|
var user = snap.user && typeof snap.user === "object" ? Object.assign({}, snap.user) : {}; delete user[field];
|
|
371
393
|
return Object.assign({}, snap, { value: value, user: user });
|
|
372
394
|
};
|
|
395
|
+
// Remove one field's entry from the host document's user layer (the mirror is
|
|
396
|
+
// the caller's business). Used by the card's per-field reset — and by `set`,
|
|
397
|
+
// for a write whose value IS the schema default.
|
|
398
|
+
var unsetHostEntry = function (field) {
|
|
399
|
+
if (!(HOST_FIELDS[field] && hostScope && hostSnap && hostSnap.writable === true)) return;
|
|
400
|
+
hostSnap = foldUnset(hostSnap, field);
|
|
401
|
+
var settle = function () {
|
|
402
|
+
if (!hostScope) return;
|
|
403
|
+
hostSnap = hostScope.getSnapshot();
|
|
404
|
+
store.version++;
|
|
405
|
+
notify();
|
|
406
|
+
};
|
|
407
|
+
try {
|
|
408
|
+
var p = hostScope.unset(field);
|
|
409
|
+
if (p && typeof p.then === "function") p.then(settle, settle);
|
|
410
|
+
} catch (e) { settle(); }
|
|
411
|
+
};
|
|
373
412
|
var sendHost = function (ops) {
|
|
374
413
|
if (!ops.length || !hostScope || !hostSnap || hostSnap.writable !== true) return;
|
|
375
414
|
hostSnap = foldOps(hostSnap, ops);
|
|
@@ -400,6 +439,7 @@ window.__ModuleLoader__.load({
|
|
|
400
439
|
if (local.lang !== DEFAULTS.lang) ops.push({ op: "set", path: ["lang"], value: local.lang });
|
|
401
440
|
if (JSON.stringify(local.levels) !== JSON.stringify(DEFAULTS.levels)) ops.push({ op: "set", path: ["levels"], value: local.levels.slice() });
|
|
402
441
|
if (local.zoom !== DEFAULTS.zoom) ops.push({ op: "set", path: ["zoom"], value: local.zoom });
|
|
442
|
+
if (local.handle !== DEFAULTS.handle) ops.push({ op: "set", path: ["handle"], value: local.handle });
|
|
403
443
|
if (local.fuzzy !== DEFAULTS.fuzzy) ops.push({ op: "set", path: ["fuzzy"], value: local.fuzzy });
|
|
404
444
|
if (local.hover !== DEFAULTS.hover) ops.push({ op: "set", path: ["hover"], value: local.hover });
|
|
405
445
|
if (local.debug !== DEFAULTS.debug) ops.push({ op: "set", path: ["debug"], value: local.debug });
|
|
@@ -427,13 +467,20 @@ window.__ModuleLoader__.load({
|
|
|
427
467
|
} else if (next[key] !== current[key]) changed = true;
|
|
428
468
|
}
|
|
429
469
|
if (!changed) return current;
|
|
470
|
+
// A host-field write whose value IS the schema default is a reset, not a
|
|
471
|
+
// customization: the entry leaves the user layer (the "customized" badge
|
|
472
|
+
// goes with it) instead of pinning the default into the document.
|
|
473
|
+
var resets = [];
|
|
430
474
|
var ops = [];
|
|
431
475
|
for (var k in patch) {
|
|
432
476
|
if (!hasOwn(patch, k)) continue;
|
|
433
|
-
if (HOST_FIELDS[k])
|
|
477
|
+
if (!HOST_FIELDS[k]) continue;
|
|
478
|
+
if (isDefaultValue(k, patch[k])) { resets.push(k); continue; }
|
|
479
|
+
ops.push({ op: "set", path: [k], value: patch[k] });
|
|
434
480
|
}
|
|
435
481
|
sendHost(ops);
|
|
436
482
|
write(patch); // the localStorage mirror (and the local-only y/w/h)
|
|
483
|
+
for (var r = 0; r < resets.length; r++) unsetHostEntry(resets[r]);
|
|
437
484
|
store.version++;
|
|
438
485
|
notify();
|
|
439
486
|
return store.get();
|
|
@@ -449,19 +496,7 @@ window.__ModuleLoader__.load({
|
|
|
449
496
|
var mirror = {};
|
|
450
497
|
mirror[field] = field === "levels" ? DEFAULTS.levels.slice() : DEFAULTS[field];
|
|
451
498
|
write(mirror);
|
|
452
|
-
|
|
453
|
-
hostSnap = foldUnset(hostSnap, field);
|
|
454
|
-
var settle = function () {
|
|
455
|
-
if (!hostScope) return;
|
|
456
|
-
hostSnap = hostScope.getSnapshot();
|
|
457
|
-
store.version++;
|
|
458
|
-
notify();
|
|
459
|
-
};
|
|
460
|
-
try {
|
|
461
|
-
var p = hostScope.unset(field);
|
|
462
|
-
if (p && typeof p.then === "function") p.then(settle, settle);
|
|
463
|
-
} catch (e) { settle(); }
|
|
464
|
-
}
|
|
499
|
+
unsetHostEntry(field);
|
|
465
500
|
store.version++;
|
|
466
501
|
notify();
|
|
467
502
|
return store.get();
|
|
@@ -1395,6 +1430,7 @@ window.__ModuleLoader__.load({
|
|
|
1395
1430
|
var visibleCount = _s9[0];
|
|
1396
1431
|
var setVisibleCount = _s9[1];
|
|
1397
1432
|
var listRef = react.useRef(null);
|
|
1433
|
+
var panelRootRef = react.useRef(null); // the panel box itself (for its live height)
|
|
1398
1434
|
var didInitScroll = react.useRef(false);
|
|
1399
1435
|
var outlineTouchRef = react.useRef(0); // last time the user touched the outline
|
|
1400
1436
|
var lastScrollTopRef = react.useRef(0);
|
|
@@ -1630,6 +1666,15 @@ window.__ModuleLoader__.load({
|
|
|
1630
1666
|
}, [prefs]);
|
|
1631
1667
|
// ...and publish the panel's own changes back, debounced: a drag fires on every
|
|
1632
1668
|
// pointer move and the Settings section does not need that resolution.
|
|
1669
|
+
//
|
|
1670
|
+
// The record of what has been published advances only when the write actually
|
|
1671
|
+
// happens. This effect re-runs on EVERY change and its cleanup cancels the
|
|
1672
|
+
// pending timer, so a second adjustment inside the 400ms window replaces the
|
|
1673
|
+
// first one's patch; an eagerly advanced record would leave the cancelled field
|
|
1674
|
+
// looking published, the next pass would find nothing to publish, and the value
|
|
1675
|
+
// just dragged would never reach storage (measured in Edge: drag the width
|
|
1676
|
+
// 200 -> 150, drag the height 250ms later — the stored width stayed 200 and the
|
|
1677
|
+
// next load brought the panel back 50px wider than it was left).
|
|
1633
1678
|
react.useEffect(function () {
|
|
1634
1679
|
var seen = sent.current;
|
|
1635
1680
|
var nextLevels = levels.join(",");
|
|
@@ -1645,13 +1690,15 @@ window.__ModuleLoader__.load({
|
|
|
1645
1690
|
// next pass — including the one the store's own notification triggers — finds
|
|
1646
1691
|
// nothing left to publish and the debounced write cannot bounce back
|
|
1647
1692
|
var has = function (key) { return Object.prototype.hasOwnProperty.call(patch, key); };
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1693
|
+
var timer = setTimeout(function () {
|
|
1694
|
+
if (has("dock")) seen.dock = patch.dock;
|
|
1695
|
+
if (has("y")) seen.y = patch.y;
|
|
1696
|
+
if (has("w")) seen.w = patch.w;
|
|
1697
|
+
if (has("h")) seen.h = patch.h;
|
|
1698
|
+
if (has("levels")) seen.levels = nextLevels;
|
|
1699
|
+
if (has("fuzzy")) seen.fuzzy = patch.fuzzy;
|
|
1700
|
+
prefsStore.set(patch);
|
|
1701
|
+
}, 400);
|
|
1655
1702
|
return function () { clearTimeout(timer); };
|
|
1656
1703
|
}, [dock, panelY, panelW, panelH, levels, fuzzy]);
|
|
1657
1704
|
|
|
@@ -1847,6 +1894,19 @@ window.__ModuleLoader__.load({
|
|
|
1847
1894
|
// No upper bound and only a keep-it-usable floor: the reader decides how small
|
|
1848
1895
|
// the panel gets. The drags only move the live state; the preference effect
|
|
1849
1896
|
// persists the settled value (debounced), so there is exactly one writer.
|
|
1897
|
+
// A panel whose height is still "auto" (never dragged) starts from its REAL
|
|
1898
|
+
// height: the old fixed 400px guess made the panel jump on the first pointer
|
|
1899
|
+
// move. getBoundingClientRect reports the visual (post-zoom) height, which is
|
|
1900
|
+
// the same unit panelH is stored in.
|
|
1901
|
+
var dragStartH = function () {
|
|
1902
|
+
if (panelH > 0) return panelH;
|
|
1903
|
+
var el = panelRootRef.current;
|
|
1904
|
+
if (el && el.getBoundingClientRect) {
|
|
1905
|
+
var h = el.getBoundingClientRect().height;
|
|
1906
|
+
if (h > 0) return h;
|
|
1907
|
+
}
|
|
1908
|
+
return 400; // nothing measurable yet: a sane starting point
|
|
1909
|
+
};
|
|
1850
1910
|
var onResizeWDown = function (e) {
|
|
1851
1911
|
e.preventDefault();
|
|
1852
1912
|
e.stopPropagation();
|
|
@@ -1868,7 +1928,7 @@ window.__ModuleLoader__.load({
|
|
|
1868
1928
|
e.stopPropagation();
|
|
1869
1929
|
if (e.button !== 0) return;
|
|
1870
1930
|
var startY = e.clientY;
|
|
1871
|
-
var startH =
|
|
1931
|
+
var startH = dragStartH();
|
|
1872
1932
|
var move = function (ev) {
|
|
1873
1933
|
setPanelH(Math.max(60, startH + (ev.clientY - startY)));
|
|
1874
1934
|
};
|
|
@@ -1886,7 +1946,7 @@ window.__ModuleLoader__.load({
|
|
|
1886
1946
|
var startX = e.clientX;
|
|
1887
1947
|
var startY = e.clientY;
|
|
1888
1948
|
var startW = panelW;
|
|
1889
|
-
var startH =
|
|
1949
|
+
var startH = dragStartH();
|
|
1890
1950
|
var move = function (ev) {
|
|
1891
1951
|
setPanelW(Math.max(120, startW + (ev.clientX - startX)));
|
|
1892
1952
|
setPanelH(Math.max(60, startH + (ev.clientY - startY)));
|
|
@@ -2480,6 +2540,11 @@ window.__ModuleLoader__.load({
|
|
|
2480
2540
|
}
|
|
2481
2541
|
var vMaxH = viewport ? Math.max(200, viewport.height - 28) : "72vh";
|
|
2482
2542
|
var baseTopPx = (viewport ? viewport.top + 14 : window.innerHeight * 0.12) + panelY;
|
|
2543
|
+
// the collapsed handle's vertical position (a 0..1 ratio of the conversation
|
|
2544
|
+
// area — 0 pins the handle to the BOTTOM, 1 to the top; 0.5 = centred, which is
|
|
2545
|
+
// exactly the previous fixed position)
|
|
2546
|
+
var handlePos = isFinite(Number(prefs.handle)) ? Math.min(1, Math.max(0, Number(prefs.handle))) : 0.5;
|
|
2547
|
+
var handleFromTop = 1 - handlePos; // the setting counts from the bottom up
|
|
2483
2548
|
|
|
2484
2549
|
// ---- panel top drag bar ----
|
|
2485
2550
|
var onHandleDown = function (e) {
|
|
@@ -2891,6 +2956,7 @@ window.__ModuleLoader__.load({
|
|
|
2891
2956
|
var panelOpacity = open && chatViewActive ? (hovered ? 1 : 0.72) : 0;
|
|
2892
2957
|
|
|
2893
2958
|
var panelEl = react_jsx_runtime.jsx("div", {
|
|
2959
|
+
ref: panelRootRef,
|
|
2894
2960
|
style: {
|
|
2895
2961
|
position: "fixed",
|
|
2896
2962
|
top: zLen(baseTopPx),
|
|
@@ -3537,7 +3603,11 @@ window.__ModuleLoader__.load({
|
|
|
3537
3603
|
justifyContent: "center",
|
|
3538
3604
|
cursor: "pointer",
|
|
3539
3605
|
zIndex: Z_BASE,
|
|
3540
|
-
|
|
3606
|
+
// the ratio counts from the BOTTOM (0% = bottom, 100% = top) — at 0.5 this
|
|
3607
|
+
// is the same centred spot the handle always had
|
|
3608
|
+
top: (viewport
|
|
3609
|
+
? viewport.top + (viewport.height - 92) * handleFromTop
|
|
3610
|
+
: (window.innerHeight - 92) * handleFromTop) + "px",
|
|
3541
3611
|
transform: handleShown ? "translateX(0)" : (dockRight ? "translateX(16px)" : "translateX(-16px)"),
|
|
3542
3612
|
opacity: chatViewActive ? 1 : 0,
|
|
3543
3613
|
pointerEvents: chatViewActive ? "auto" : "none",
|
|
@@ -4154,32 +4224,34 @@ window.__ModuleLoader__.load({
|
|
|
4154
4224
|
return seg([["on", T("settings.on")], ["off", T("settings.off")]], on ? "on" : "off",
|
|
4155
4225
|
function (v) { onToggle(v === "on"); });
|
|
4156
4226
|
};
|
|
4157
|
-
//
|
|
4158
|
-
// card's slider. Dragging only moves a LOCAL
|
|
4159
|
-
// written once the pointer is released (a
|
|
4160
|
-
// panel mid-drag), and the local value
|
|
4161
|
-
//
|
|
4162
|
-
|
|
4163
|
-
var
|
|
4164
|
-
var
|
|
4165
|
-
var
|
|
4166
|
-
var
|
|
4167
|
-
|
|
4168
|
-
|
|
4227
|
+
// Percentage sliders (the panel scale and the handle position) — the same
|
|
4228
|
+
// control shape as the fonttune card's slider. Dragging only moves a LOCAL
|
|
4229
|
+
// value: the settings document is written once the pointer is released (a
|
|
4230
|
+
// per-notch rewrite would recompute the panel mid-drag), and the local value
|
|
4231
|
+
// stays on screen until the host confirms it, so the readout never bounces back
|
|
4232
|
+
// to the old number for a frame. One drag/confirm ledger serves both sliders.
|
|
4233
|
+
var _sSliderDrag = react.useState(null); // { field, value } or null
|
|
4234
|
+
var sliderDrag = _sSliderDrag[0];
|
|
4235
|
+
var setSliderDrag = _sSliderDrag[1];
|
|
4236
|
+
var sliderAwaiting = react.useRef({});
|
|
4237
|
+
var commitSlider = function (field, pct) {
|
|
4238
|
+
sliderAwaiting.current[field] = pct;
|
|
4239
|
+
commit(field === "zoom" ? { zoom: pct / 100 } : { handle: pct / 100 });
|
|
4169
4240
|
};
|
|
4170
4241
|
react.useEffect(function () {
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
}, [prefs.zoom]);
|
|
4242
|
+
var a = sliderAwaiting.current;
|
|
4243
|
+
var settled = false;
|
|
4244
|
+
if (a.zoom !== undefined && Math.round(prefs.zoom * 100) === a.zoom) { delete a.zoom; settled = true; }
|
|
4245
|
+
if (a.handle !== undefined && Math.round(prefs.handle * 100) === a.handle) { delete a.handle; settled = true; }
|
|
4246
|
+
if (settled) setSliderDrag(null);
|
|
4247
|
+
}, [prefs.zoom, prefs.handle]);
|
|
4177
4248
|
react.useEffect(function () {
|
|
4178
|
-
if (
|
|
4249
|
+
if (sliderDrag === null) return undefined;
|
|
4179
4250
|
var release = function () {
|
|
4180
4251
|
// one release per drag: a late pointerup must not rewrite the same value
|
|
4181
|
-
|
|
4182
|
-
|
|
4252
|
+
var f = sliderDrag.field;
|
|
4253
|
+
if (sliderAwaiting.current[f] !== undefined) return;
|
|
4254
|
+
commitSlider(f, sliderDrag.value);
|
|
4183
4255
|
};
|
|
4184
4256
|
window.addEventListener("pointerup", release, true);
|
|
4185
4257
|
window.addEventListener("touchend", release, true);
|
|
@@ -4187,38 +4259,42 @@ window.__ModuleLoader__.load({
|
|
|
4187
4259
|
window.removeEventListener("pointerup", release, true);
|
|
4188
4260
|
window.removeEventListener("touchend", release, true);
|
|
4189
4261
|
};
|
|
4190
|
-
}, [
|
|
4191
|
-
var
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4262
|
+
}, [sliderDrag]);
|
|
4263
|
+
var sliderCtl = function (field, label, min, max, step, pct) {
|
|
4264
|
+
var value = (sliderDrag && sliderDrag.field === field) ? sliderDrag.value : pct;
|
|
4265
|
+
return react_jsx_runtime.jsxs("div", {
|
|
4266
|
+
children: [
|
|
4267
|
+
react_jsx_runtime.jsxs("div", {
|
|
4268
|
+
className: "dqt-sliderRow",
|
|
4269
|
+
children: [
|
|
4270
|
+
react_jsx_runtime.jsx("input", {
|
|
4271
|
+
type: "range",
|
|
4272
|
+
className: "dqt-slider",
|
|
4273
|
+
min: min,
|
|
4274
|
+
max: max,
|
|
4275
|
+
step: step,
|
|
4276
|
+
value: value,
|
|
4277
|
+
disabled: !writable,
|
|
4278
|
+
"aria-label": T(label),
|
|
4279
|
+
onChange: function (e) { setSliderDrag({ field: field, value: Number(e.target.value) }); },
|
|
4280
|
+
onKeyUp: function () { if (sliderDrag && sliderDrag.field === field) commitSlider(field, sliderDrag.value); },
|
|
4281
|
+
onBlur: function () { if (sliderDrag && sliderDrag.field === field) commitSlider(field, sliderDrag.value); }
|
|
4282
|
+
}),
|
|
4283
|
+
react_jsx_runtime.jsx("span", { className: "dqt-sliderValue", children: value + "%" })
|
|
4284
|
+
]
|
|
4285
|
+
}),
|
|
4286
|
+
react_jsx_runtime.jsxs("div", {
|
|
4287
|
+
className: "dqt-sliderScale",
|
|
4288
|
+
children: [
|
|
4289
|
+
react_jsx_runtime.jsx("span", { children: min + "%" }),
|
|
4290
|
+
react_jsx_runtime.jsx("span", { children: max + "%" })
|
|
4291
|
+
]
|
|
4292
|
+
})
|
|
4293
|
+
]
|
|
4294
|
+
});
|
|
4295
|
+
};
|
|
4296
|
+
var zoomControl = sliderCtl("zoom", "settings.zoom", 50, 200, 5, Math.round(prefs.zoom * 100));
|
|
4297
|
+
var handleControl = sliderCtl("handle", "settings.handle", 0, 100, 1, Math.round(prefs.handle * 100));
|
|
4222
4298
|
var field = function (key, label, hint, control, inline, last) {
|
|
4223
4299
|
var overridden = Object.prototype.hasOwnProperty.call(user, key);
|
|
4224
4300
|
var reset = overridden ? react_jsx_runtime.jsx("button", {
|
|
@@ -4279,6 +4355,7 @@ window.__ModuleLoader__.load({
|
|
|
4279
4355
|
function (v) { commit({ dock: v }); }), true),
|
|
4280
4356
|
field("levels", T("settings.levels"), "", levelsControl(true), true),
|
|
4281
4357
|
field("zoom", T("settings.zoom"), T("settings.zoom.tip"), zoomControl, false),
|
|
4358
|
+
field("handle", T("settings.handle"), T("settings.handle.tip"), handleControl, false),
|
|
4282
4359
|
field("fuzzy", T("settings.fuzzy"), "", onoff(prefs.fuzzy, function (v) { commit({ fuzzy: v }); }), true),
|
|
4283
4360
|
field("hover", T("settings.hover"), "", onoff(prefs.hover, function (v) { commit({ hover: v }); }), true),
|
|
4284
4361
|
field("debug", T("settings.debug"), T("settings.debug.tip"), onoff(prefs.debug, function (v) { commit({ debug: v }); }), true, true)
|
|
@@ -4296,7 +4373,24 @@ window.__ModuleLoader__.load({
|
|
|
4296
4373
|
react_jsx_runtime.jsx("span", { className: "dqt-pname", children: T("panel.title") }),
|
|
4297
4374
|
react_jsx_runtime.jsx("span", { className: "dqt-pdesc", children: T("settings.desc") })
|
|
4298
4375
|
] }),
|
|
4299
|
-
|
|
4376
|
+
// The host's own chevron (IconChevronDownOutline14): a filled 14x14 glyph.
|
|
4377
|
+
// A text arrowhead ("⌄") sat in a 20px line box, so its ink hung off the
|
|
4378
|
+
// box centre — rotating it swung the glyph sideways instead of turning it
|
|
4379
|
+
// in place — and at text weight it never matched the host's other icons.
|
|
4380
|
+
// The class goes on the <svg> itself, exactly as the host's PluginCard does,
|
|
4381
|
+
// so `transform:rotate(180deg)` turns around the icon's own centre.
|
|
4382
|
+
react_jsx_runtime.jsx("svg", {
|
|
4383
|
+
className: "dqt-pchev" + (open ? " dqt-pchevOpen" : ""),
|
|
4384
|
+
width: 14,
|
|
4385
|
+
height: 14,
|
|
4386
|
+
viewBox: "0 0 14 14",
|
|
4387
|
+
fill: "none",
|
|
4388
|
+
"aria-hidden": "true",
|
|
4389
|
+
children: react_jsx_runtime.jsx("path", {
|
|
4390
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
4391
|
+
fill: "currentColor"
|
|
4392
|
+
})
|
|
4393
|
+
})
|
|
4300
4394
|
]
|
|
4301
4395
|
}),
|
|
4302
4396
|
open ? react_jsx_runtime.jsx("div", {
|
package/lib/index.js
CHANGED
|
@@ -41,6 +41,12 @@ export const Config = z.object({
|
|
|
41
41
|
.max(2)
|
|
42
42
|
.default(1)
|
|
43
43
|
.description("Scale factor for the panel's content (text, icons, buttons) from 50% to 200%; the panel's own size is unchanged"),
|
|
44
|
+
handle: z
|
|
45
|
+
.number()
|
|
46
|
+
.min(0)
|
|
47
|
+
.max(1)
|
|
48
|
+
.default(0.5)
|
|
49
|
+
.description("Vertical position of the collapsed-panel handle: 0 = bottom, 1 = top of the conversation area"),
|
|
44
50
|
fuzzy: z
|
|
45
51
|
.boolean()
|
|
46
52
|
.default(false)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quick-toc",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Conversation TOC for DeepSeek Harness (DSH): turn-grouped Markdown outline
|
|
3
|
+
"version": "0.6.3",
|
|
4
|
+
"description": "Conversation TOC for DeepSeek Harness (DSH): a turn-grouped Markdown outline of the whole session, title/full-text search, hover previews, in-chat highlighting, a level filter, reading-position follow, a Chinese/English interface and a settings card.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"files": [
|