dsh-ask-from-text 0.1.12 → 0.1.19

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.md CHANGED
@@ -4,6 +4,109 @@ All notable changes to `dsh-ask-from-text` are documented here. The format is
4
4
  based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
5
5
  project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.1.19] - 2026-11-13
8
+
9
+ ### Fixed
10
+
11
+ - Do not pin a selected branch row above the group. Only the root/main Session
12
+ is allowed above `追问分支`; clicking a branch keeps it in the group.
13
+
14
+ ## [0.1.18] - 2026-11-13
15
+
16
+ ### Fixed
17
+
18
+ - Put the shared `追问分支` header before the topmost branch row in visual
19
+ document order, so collapsing/expanding no longer moves it below the group.
20
+
21
+ ## [0.1.17] - 2026-11-13
22
+
23
+ ### Fixed
24
+
25
+ - Keep the shared branch-group header when the group is collapsed by ignoring
26
+ rows hidden by the plugin itself, while still ignoring rows hidden inside a
27
+ folded Workspace or a stale search tree.
28
+
29
+ ## [0.1.16] - 2026-11-13
30
+
31
+ ### Fixed
32
+
33
+ - Restore the shared `追问分支` header by inserting it inside the first branch
34
+ row wrapper, where the sidebar animation layer participates in layout, and
35
+ keep only one header for all branch rows.
36
+
37
+ ## [0.1.15] - 2026-11-13
38
+
39
+ ### Fixed
40
+
41
+ - Group all branch Session rows under one shared `追问分支` header even when
42
+ the sidebar gives each row its own animated wrapper, and keep the selected
43
+ main row above that shared header.
44
+
45
+ ## [0.1.14] - 2026-11-13
46
+
47
+ ### Fixed
48
+
49
+ - Handle main and branch Sessions that the sidebar places in different
50
+ Workspace sections by moving the main section above the first branch section.
51
+
52
+ ## [0.1.13] - 2026-11-13
53
+
54
+ ### Fixed
55
+
56
+ - README screenshots now use absolute CDN URLs so they render on the npm package page.
57
+
58
+ ## [0.1.19] - 2026-11-13
59
+
60
+ ### Fixed
61
+
62
+ - Do not pin a selected branch row above the group. Only the root/main Session
63
+ is allowed above `追问分支`; clicking a branch keeps it in the group.
64
+
65
+ ## [0.1.18] - 2026-11-13
66
+
67
+ ### Fixed
68
+
69
+ - Put the shared `追问分支` header before the topmost branch row in visual
70
+ document order, so collapsing/expanding no longer moves it below the group.
71
+
72
+ ## [0.1.17] - 2026-11-13
73
+
74
+ ### Fixed
75
+
76
+ - Keep the shared branch-group header when the group is collapsed by ignoring
77
+ rows hidden by the plugin itself, while still ignoring rows hidden inside a
78
+ folded Workspace or a stale search tree.
79
+
80
+ ## [0.1.16] - 2026-11-13
81
+
82
+ ### Fixed
83
+
84
+ - Restore the shared `追问分支` header by inserting it inside the first branch
85
+ row wrapper, where the sidebar animation layer participates in layout, and
86
+ keep only one header for all branch rows.
87
+
88
+ ## [0.1.15] - 2026-11-13
89
+
90
+ ### Fixed
91
+
92
+ - Group all branch Session rows under one shared `追问分支` header even when
93
+ the sidebar gives each row its own animated wrapper, and keep the selected
94
+ main row above that shared header.
95
+
96
+ ## [0.1.14] - 2026-11-13
97
+
98
+ ### Fixed
99
+
100
+ - Handle main and branch Sessions that the sidebar places in different
101
+ Workspace sections by moving the main section above the first branch section.
102
+
103
+ ## [0.1.13] - 2026-11-13
104
+
105
+ ### Fixed
106
+
107
+ - Prefer the selected sidebar row when pinning the main conversation above its
108
+ branch group, and re-evaluate when the selected Session changes.
109
+
7
110
  ## [0.1.12] - 2026-11-13
8
111
 
9
112
  ### Changed
@@ -84,3 +187,17 @@ project follows [Semantic Versioning](https://semver.org/).
84
187
  [0.1.10]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.10
85
188
  [0.1.11]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.11
86
189
  [0.1.12]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.12
190
+ [0.1.13]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.13
191
+ [0.1.14]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.14
192
+ [0.1.15]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.15
193
+ [0.1.16]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.16
194
+ [0.1.17]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.17
195
+ [0.1.18]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.18
196
+ [0.1.19]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.19
197
+ [0.1.13]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.13
198
+ [0.1.14]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.14
199
+ [0.1.15]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.15
200
+ [0.1.16]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.16
201
+ [0.1.17]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.17
202
+ [0.1.18]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.18
203
+ [0.1.19]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.19
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  在 DeepSeek Harness 的回答里划选一句话,输入追问;回答会在独立的浮窗中继续,并保留原文标记。
4
4
 
5
- ![多个追问与收起列表](docs/images/multi-branch.jpg)
5
+ ![多个追问与收起列表](https://cdn.jsdelivr.net/gh/gbn0219/dsh-ask-from-text@main/docs/images/multi-branch.jpg)
6
6
 
7
7
  > 界面示意图。当前版本新建追问时只弹出一个输入浮窗;发送后才会显示完整对话浮窗。
8
8
 
@@ -10,13 +10,13 @@
10
10
 
11
11
  需要 DeepSeek Harness 0.2.2-rc.x。
12
12
 
13
- ### 从 GitHub 安装(当前可用)
13
+ ### 从 GitHub 安装
14
14
 
15
15
  ```sh
16
16
  dsh plugin --profile web add github:gbn0219/dsh-ask-from-text
17
17
  ```
18
18
 
19
- ### 从 npm 安装(发布后可用)
19
+ ### 从 npm 安装
20
20
 
21
21
  ```sh
22
22
  dsh plugin --profile web add dsh-ask-from-text
@@ -80,7 +80,7 @@ dsh plugin --profile web add /absolute/path/to/dsh-ask-from-text
80
80
  - `↘ 还原为浮窗`:回到小浮窗;
81
81
  - `✕ 关闭`:回到主对话。
82
82
 
83
- ![舞台与分支树](docs/images/stage-and-tree.jpg)
83
+ ![舞台与分支树](https://cdn.jsdelivr.net/gh/gbn0219/dsh-ask-from-text@main/docs/images/stage-and-tree.jpg)
84
84
 
85
85
  ### 6. 删除草稿
86
86
 
@@ -124,7 +124,7 @@ dsh plugin --profile web add /absolute/path/to/dsh-ask-from-text
124
124
 
125
125
  **Ask from Text** lets you select a sentence in a completed assistant answer and ask a question in a separate floating branch, while keeping the original text marked in place.
126
126
 
127
- ![Multiple branches and the collapsed-branch list](docs/images/multi-branch.jpg)
127
+ ![Multiple branches and the collapsed-branch list](https://cdn.jsdelivr.net/gh/gbn0219/dsh-ask-from-text@main/docs/images/multi-branch.jpg)
128
128
 
129
129
  > Interface illustration. In the current version a new follow-up opens only an input window; the full conversation window appears after you send the first question.
130
130
 
@@ -132,13 +132,13 @@ dsh plugin --profile web add /absolute/path/to/dsh-ask-from-text
132
132
 
133
133
  Requires DeepSeek Harness 0.2.2-rc.x.
134
134
 
135
- #### From GitHub (available now)
135
+ #### From GitHub
136
136
 
137
137
  ```sh
138
138
  dsh plugin --profile web add github:gbn0219/dsh-ask-from-text
139
139
  ```
140
140
 
141
- #### From npm (after the npm release)
141
+ #### From npm
142
142
 
143
143
  ```sh
144
144
  dsh plugin --profile web add dsh-ask-from-text
@@ -202,7 +202,7 @@ The stage provides:
202
202
  - `↘ Restore to window` to return to the floating window.
203
203
  - `✕ Close` to return to the main chat.
204
204
 
205
- ![Stage and branch tree](docs/images/stage-and-tree.jpg)
205
+ ![Stage and branch tree](https://cdn.jsdelivr.net/gh/gbn0219/dsh-ask-from-text@main/docs/images/stage-and-tree.jpg)
206
206
 
207
207
  #### 6. Delete a draft
208
208
 
package/client.js CHANGED
@@ -1013,7 +1013,13 @@ window.__ModuleLoader__.load({
1013
1013
  })
1014
1014
  if (!own) scheduleProject()
1015
1015
  })
1016
- mutationObserver.observe(document.body, { childList: true, subtree: true, characterData: true })
1016
+ mutationObserver.observe(document.body, {
1017
+ childList: true,
1018
+ subtree: true,
1019
+ characterData: true,
1020
+ attributes: true,
1021
+ attributeFilter: ['aria-selected'],
1022
+ })
1017
1023
  }
1018
1024
  scheduleProject()
1019
1025
  return () => {
@@ -1263,57 +1269,96 @@ window.__ModuleLoader__.load({
1263
1269
  const ids = new Set(state.order
1264
1270
  .map(id => state.branches[id]?.sessionId)
1265
1271
  .filter(Boolean))
1266
- const parents = new Map()
1272
+ const rows = []
1267
1273
  for (const id of ids) {
1268
1274
  for (const row of document.querySelectorAll(`[data-row-key="session:${cssEscape(id)}"]`)) {
1269
1275
  if (row.closest('[data-ifu-root]')) continue
1270
- const parent = row.parentElement
1271
- if (!parent) continue
1272
- const list = parents.get(parent) || []
1273
- list.push(row)
1274
- parents.set(parent, list)
1276
+ // Keep rows hidden by our own collapsed state in the group so the
1277
+ // header stays clickable; skip rows hidden inside a folded
1278
+ // Workspace or a stale search tree.
1279
+ if (row.getClientRects().length === 0 && !row.classList.contains('ifu-sidebar-hidden')) continue
1280
+ rows.push(row)
1275
1281
  }
1276
1282
  }
1277
- const activeParents = new Set(parents.keys())
1278
- for (const header of document.querySelectorAll('[data-ifu-sidebar-group]')) {
1279
- if (!activeParents.has(header.parentElement)) header.remove()
1280
- }
1281
- for (const [parent, rows] of parents) {
1282
- let header = parent.querySelector(':scope > [data-ifu-sidebar-group]')
1283
- if (!header) {
1284
- header = document.createElement('button')
1285
- header.type = 'button'
1286
- header.className = 'ifu-sidebar-group'
1287
- header.setAttribute('data-ifu-sidebar-group', '')
1288
- header.addEventListener('click', (event) => {
1289
- event.preventDefault()
1290
- event.stopPropagation()
1291
- toggleSidebarBranches()
1292
- })
1293
- }
1294
- const first = rows[0]
1295
- if (header.nextSibling !== first) parent.insertBefore(header, first)
1296
- const label = `${state.sidebarCollapsed ? '▸' : '▾'} 追问分支 · ${rows.length}`
1297
- if (header.textContent !== label) header.textContent = label
1298
- for (const row of rows) row.classList.toggle('ifu-sidebar-hidden', state.sidebarCollapsed)
1299
- // Keep the main conversation row above its branch rows even when the
1300
- // host sort places a recently used branch first. Moving the row at
1301
- // the React-owned DOM level is deliberate: the Session list is not a
1302
- // flex/order container, and the collapsible group header sits here.
1303
- const mainId = state.mainSessionId
1304
- const mainRow = mainId
1305
- ? parent.querySelector(`:scope > [data-row-key="session:${cssEscape(mainId)}"]`)
1306
- : null
1307
- if (mainRow && mainRow !== header
1283
+ rows.sort((a, b) => a === b ? 0
1284
+ : (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) ? -1 : 1)
1285
+ if (rows.length === 0) {
1286
+ for (const header of document.querySelectorAll('[data-ifu-sidebar-group]')) header.remove()
1287
+ for (const row of document.querySelectorAll('.ifu-sidebar-hidden')) row.classList.remove('ifu-sidebar-hidden')
1288
+ return
1289
+ }
1290
+ const first = rows[0]
1291
+ const firstParent = first.parentElement
1292
+ if (!firstParent) return
1293
+ let header = firstParent.querySelector(':scope > [data-ifu-sidebar-group]')
1294
+ if (!header) {
1295
+ header = document.createElement('button')
1296
+ header.type = 'button'
1297
+ header.className = 'ifu-sidebar-group'
1298
+ header.setAttribute('data-ifu-sidebar-group', '')
1299
+ header.addEventListener('click', (event) => {
1300
+ event.preventDefault()
1301
+ event.stopPropagation()
1302
+ toggleSidebarBranches()
1303
+ })
1304
+ }
1305
+ // Keep one shared header, inside the first branch wrapper. Putting it
1306
+ // at the animated tree root would be clipped or ignored by the row
1307
+ // animation layer; a wrapper child participates in the same layout as
1308
+ // the original per-row header did.
1309
+ for (const other of document.querySelectorAll('[data-ifu-sidebar-group]')) {
1310
+ if (other !== header) other.remove()
1311
+ }
1312
+ if (header.nextSibling !== first) firstParent.insertBefore(header, first)
1313
+ const label = `${state.sidebarCollapsed ? '▸' : '▾'} 追问分支 · ${rows.length}`
1314
+ if (header.textContent !== label) header.textContent = label
1315
+ for (const row of rows) row.classList.toggle('ifu-sidebar-hidden', state.sidebarCollapsed)
1316
+
1317
+ const branchSessionIds = ids
1318
+ const rootSessionIds = new Set(state.order
1319
+ .map(id => state.branches[id]?.rootSessionId)
1320
+ .filter(Boolean))
1321
+ const sessionIdOf = (row) => {
1322
+ const key = row.getAttribute('data-row-key') || ''
1323
+ return key.startsWith('session:') ? key.slice('session:'.length) : null
1324
+ }
1325
+ const selectedRow = Array.from(document.querySelectorAll('[data-row-key^="session:"][aria-selected="true"]'))
1326
+ .find(row => row.getClientRects().length > 0) || null
1327
+ // A branch row can be selected too (opening it as the main view). Only a
1328
+ // root/main Session may be pinned above the branch group; otherwise
1329
+ // clicking a branch would make that branch jump above its siblings.
1330
+ const selectedMain = selectedRow && rootSessionIds.has(sessionIdOf(selectedRow))
1331
+ ? selectedRow
1332
+ : null
1333
+ let mainId = state.mainSessionId
1334
+ if (mainId && branchSessionIds.has(mainId)) {
1335
+ mainId = branchBySession(mainId)?.rootSessionId || null
1336
+ }
1337
+ const mainRow = selectedMain || (mainId
1338
+ ? Array.from(document.querySelectorAll(`[data-row-key="session:${cssEscape(mainId)}"]`))
1339
+ .find(row => row.getClientRects().length > 0) || null
1340
+ : null)
1341
+ if (!mainRow) return
1342
+ if (mainRow.parentElement === firstParent) {
1343
+ if (mainRow !== header
1308
1344
  && (mainRow.compareDocumentPosition(header) & Node.DOCUMENT_POSITION_FOLLOWING) === 0) {
1309
- parent.insertBefore(mainRow, header)
1345
+ firstParent.insertBefore(mainRow, header)
1310
1346
  }
1347
+ return
1311
1348
  }
1312
- // Keep row classes correct even when no parent could be found this pass.
1313
- for (const id of ids) {
1314
- for (const row of document.querySelectorAll(`[data-row-key="session:${cssEscape(id)}"]`)) {
1315
- if (!row.closest('[data-ifu-root]')) row.classList.toggle('ifu-sidebar-hidden', state.sidebarCollapsed)
1316
- }
1349
+ let common = mainRow.parentElement
1350
+ while (common && !common.contains(firstParent)) common = common.parentElement
1351
+ if (!common || common === document.body || common === document.documentElement) return
1352
+ const directChild = (node) => {
1353
+ let element = node
1354
+ while (element && element.parentElement !== common) element = element.parentElement
1355
+ return element && element.parentElement === common ? element : null
1356
+ }
1357
+ const mainChild = directChild(mainRow)
1358
+ const branchChild = directChild(firstParent)
1359
+ if (mainChild && branchChild && mainChild !== branchChild
1360
+ && (mainChild.compareDocumentPosition(branchChild) & Node.DOCUMENT_POSITION_FOLLOWING) === 0) {
1361
+ common.insertBefore(mainChild, branchChild)
1317
1362
  }
1318
1363
  }
1319
1364
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ask-from-text",
3
- "version": "0.1.12",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "description": "Ask a follow-up from selected text in a DeepSeek Harness answer, in a real Session branch with a tree of context.",
6
6
  "exports": {