@yfwu2020/dsh-selection-explain 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/lib/client.js +31 -0
- package/package.json +1 -1
- package/scripts/smoke-panel.mjs +48 -0
- package/scripts/test-client.mjs +16 -0
- package/src/client/index.js +31 -0
package/README.md
CHANGED
|
@@ -113,6 +113,9 @@
|
|
|
113
113
|
- **只挂引用、一个字不写也能发**:这时用一句兜底提问("就上面引用的文字,说说它在这里是什么意思。"),不会空发。
|
|
114
114
|
- **只属于下一条提问**:发出即清空;换一段新的选中文字、或从「最近」回放另一段对话时也会清掉(那些引用属于上一次对话)。
|
|
115
115
|
- **小窗开着时浮标就是「引用」**:要解读新的一段,先按 `Esc` 收起小窗再划词——避免两个小窗抢同一处选区。
|
|
116
|
+
- **点完「✦ 解读」那行高亮会立刻收掉**(`Esc`、重开也一样):小窗顶部本来就写着选中文字,留着高亮反而碍事——
|
|
117
|
+
小窗亮着时它是「活的选区」,在输入框里按一下方向键就会又冒出一个「❝ 引用」,指的却是刚才解读的那段。
|
|
118
|
+
要引用它,重新划一次即可(那时浮标才是「❝ 引用」)。
|
|
116
119
|
- 引用会**进历史、也进升格**:回放这段对话时引用块跟着回来(以拼好的原文形式),升格出来的正式会话里同样带着。
|
|
117
120
|
|
|
118
121
|
### 输出偏好:Markdown ↔ 网页
|
|
@@ -479,9 +482,15 @@ git tag v0.2.0 && git push origin v0.2.0
|
|
|
479
482
|
|
|
480
483
|
npm 侧只需配一次:包设置 → **Trusted Publisher** → GitHub Actions,填 `yfwu2020` / `dsh-selection-explain` / `publish.yml`,并勾选允许 **`npm publish`**(默认只允许 `npm stage publish`)。
|
|
481
484
|
|
|
482
|
-
> 注意:`devDependencies` 里的 `@deepseek-ai/*` 钉在 **`0.1.5-rc.3
|
|
485
|
+
> 注意:`devDependencies` 里的 `@deepseek-ai/*` 钉在 **`0.1.5-rc.3`**(CI 构建校验用的就是这份类型)。
|
|
483
486
|
> npm 上这些包的 `latest` 还停在 `0.0.1-rc.x`,API 更旧(缺 `WebServer`、`createSystemMessage` 等),
|
|
484
487
|
> 用 `latest` 会编译失败——升级运行时时要同步改这里。
|
|
488
|
+
>
|
|
489
|
+
> **本机运行时通常比它新**(如 `0.1.7-rc.2`),两者 API 会漂移:例如 `createSystemMessage` 在
|
|
490
|
+
> `0.1.5-rc.3` 是 `(text, plugin)` 两个必填参数,在 `0.1.7-rc.x` 只剩 `(text)`。此时**别只按本机类型写**,
|
|
491
|
+
> 走一层宽松别名兼容两边(见 `src/index.ts` 的 `systemMessageOf`)——v0.4.0 就是漏了这一步,
|
|
492
|
+
> 本机构建全绿、CI 构建报 TS2554,整条发布卡在第一步(与 Trusted Publisher 无关)。
|
|
493
|
+
> 想提前发现:把仓库复制到临时目录、`npm install --no-save`(会装到 `0.1.5-rc.3`)再 `bash scripts/build.sh`。
|
|
485
494
|
|
|
486
495
|
## 目录
|
|
487
496
|
|
package/lib/client.js
CHANGED
|
@@ -3155,14 +3155,45 @@ window.__ModuleLoader__.load({
|
|
|
3155
3155
|
}
|
|
3156
3156
|
}
|
|
3157
3157
|
|
|
3158
|
+
/**
|
|
3159
|
+
* 点完「✦ 解读」就把**本文档**那次选区收掉。
|
|
3160
|
+
*
|
|
3161
|
+
* 为什么必须收:小窗一开,浮标就切成「❝ 引用」,而引用浮标是按"有没有活选区"判定的 ——
|
|
3162
|
+
* 留着解读用的那次选区,用户在小窗里敲字、按一下方向键(keyup 会触发一次检查),
|
|
3163
|
+
* 就会在小窗上方又冒出一个「❝ 引用」,指的是他刚才解读的那段文字,莫名其妙。
|
|
3164
|
+
*
|
|
3165
|
+
* 为什么能安全收掉:解读要的东西**在 mouseup 那一刻就都抓好了** ——
|
|
3166
|
+
* `state.selection` 里存的是文字副本 + **克隆的 Range**,上下文也是从克隆 Range 采的
|
|
3167
|
+
* (checkSelection → openForSelection),之后的请求、缓存 key、展开详解、历史回放、
|
|
3168
|
+
* 升格都不再读活选区。唯一的代价是那行字的高亮没了 —— 而小窗顶部本来就写着选中文字。
|
|
3169
|
+
*
|
|
3170
|
+
* 顺序要紧:必须在 openForSelection **采完上下文之后**调用 —— 万一环境没有 cloneRange,
|
|
3171
|
+
* 那边会退回读活选区(老浏览器),先清就采不到了。
|
|
3172
|
+
*/
|
|
3173
|
+
function clearDocSelection() {
|
|
3174
|
+
try {
|
|
3175
|
+
var selection = window.getSelection && window.getSelection()
|
|
3176
|
+
if (selection && typeof selection.removeAllRanges === 'function' && selection.rangeCount > 0) {
|
|
3177
|
+
selection.removeAllRanges()
|
|
3178
|
+
}
|
|
3179
|
+
} catch (error) {
|
|
3180
|
+
/* 桩环境/老浏览器没有 removeAllRanges:留着就留着,不影响主流程 */
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3158
3184
|
function openForSelection(selection) {
|
|
3159
3185
|
// 侧边栏网页里的选区:父页面读不到那个文档,上下文只能由帧内桥就地采好带过来
|
|
3160
3186
|
if (selection.source === 'iframe') {
|
|
3161
3187
|
openPanelWith(selection.text, selection.context || '', selection.label, selection.rect, selection.keyContext)
|
|
3188
|
+
// 帧内那条高亮清不掉(不透明源,父页面碰不到它的选区)——只能让浮标自己收着;
|
|
3189
|
+
// 好在帧内的桥按指纹去重,同一条选区不会反复上报。
|
|
3162
3190
|
return
|
|
3163
3191
|
}
|
|
3164
3192
|
var contextInfo = collectContext(selection.range ? rangeSelection(selection.range) : window.getSelection())
|
|
3165
3193
|
openPanelWith(selection.text, contextInfo.context, contextInfo.label, selection.rect, contextInfo.keyContext)
|
|
3194
|
+
clearDocSelection()
|
|
3195
|
+
// 选区连同它的记录一起作废:之后要引用就重新划一段(那会走 checkQuoteSelection)
|
|
3196
|
+
state.selection = null
|
|
3166
3197
|
}
|
|
3167
3198
|
|
|
3168
3199
|
/**
|
package/package.json
CHANGED
package/scripts/smoke-panel.mjs
CHANGED
|
@@ -161,6 +161,17 @@ function mouseUpOn(node) {
|
|
|
161
161
|
function clickOn(node) {
|
|
162
162
|
node.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
|
|
163
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* 像用户那样点一下:mousedown → mouseup → click。
|
|
166
|
+
* 只派发 click 是"程序化点击",浏览器不会走聚焦/选区那套默认行为 ——
|
|
167
|
+
* 而浮标靠 mousedown 的 preventDefault 保住选区,正是那条路要验。
|
|
168
|
+
*/
|
|
169
|
+
function realClick(node) {
|
|
170
|
+
var opts = { bubbles: true, cancelable: true, view: window, detail: 1 }
|
|
171
|
+
node.dispatchEvent(new MouseEvent("mousedown", opts))
|
|
172
|
+
node.dispatchEvent(new MouseEvent("mouseup", opts))
|
|
173
|
+
node.dispatchEvent(new MouseEvent("click", opts))
|
|
174
|
+
}
|
|
164
175
|
/** 浮标是不是**最上层**(z-index 低于面板时 elementFromPoint 会返回面板)。 */
|
|
165
176
|
function onTop(node) {
|
|
166
177
|
var rect = node.getBoundingClientRect()
|
|
@@ -345,6 +356,43 @@ async function main() {
|
|
|
345
356
|
check('主界面引用标成会话里的', !!picked8 && picked8.session === true, picked8 && String(picked8.session))
|
|
346
357
|
}
|
|
347
358
|
|
|
359
|
+
// ⑨ 点「✦ 解读」后立刻收掉本文档选区(用户报的:小窗亮起时旧选区又触发「❝ 引用」浮标)
|
|
360
|
+
{
|
|
361
|
+
hook.close()
|
|
362
|
+
await sleep(80)
|
|
363
|
+
var planBtn = document.querySelector('.dsh-sel-btn:not(.dsh-sel-quotebtn)')
|
|
364
|
+
var docp2 = document.getElementById('docp')
|
|
365
|
+
selectIn(docp2, 'the migration ran long')
|
|
366
|
+
mouseUpOn(docp2)
|
|
367
|
+
var planShown = await waitFor(function () {
|
|
368
|
+
return planBtn && planBtn.style.display !== 'none' && planBtn.getBoundingClientRect().width > 0
|
|
369
|
+
}, 1500)
|
|
370
|
+
check('主界面划词:浮出「✦ 解读」', planShown === true, planBtn ? planBtn.style.display : '未找到按钮')
|
|
371
|
+
if (planShown) {
|
|
372
|
+
realClick(planBtn)
|
|
373
|
+
// ⚠️ 这里**同步**检查,一下都不 await:开窗自动聚焦那一下是异步的(要等输入框可见),
|
|
374
|
+
// 所以"选区这会儿已经没了"只可能是我们自己在点的时候清的 —— 它骗不过这一条。
|
|
375
|
+
var live = window.getSelection()
|
|
376
|
+
check(
|
|
377
|
+
'点「解读」后选区立刻收掉(同步检查,不等聚焦那一下)',
|
|
378
|
+
!!live && (live.rangeCount === 0 || live.isCollapsed === true),
|
|
379
|
+
live ? 'rangeCount=' + live.rangeCount + ' collapsed=' + live.isCollapsed : 'null',
|
|
380
|
+
)
|
|
381
|
+
check('选区记录也一并作废(不再拿它当"当前选区")', hook.selection() === null, JSON.stringify(hook.selection()))
|
|
382
|
+
await sleep(120)
|
|
383
|
+
check('旧选区没有让「❝ 引用」浮标复活', !onTop(quoteBtn), String(quoteBtn.style.display))
|
|
384
|
+
// 用户报症状的那一步:小窗亮着时在输入框里按方向键(keyup 会触发一次选区检查)
|
|
385
|
+
var askBox = document.querySelector('.dsh-sel-askbox')
|
|
386
|
+
if (askBox) {
|
|
387
|
+
askBox.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowLeft', bubbles: true }))
|
|
388
|
+
await sleep(120)
|
|
389
|
+
check('开窗后按方向键也不再冒出引用浮标', !onTop(quoteBtn), String(quoteBtn.style.display))
|
|
390
|
+
}
|
|
391
|
+
hook.close()
|
|
392
|
+
await sleep(60)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
348
396
|
finish()
|
|
349
397
|
}
|
|
350
398
|
|
package/scripts/test-client.mjs
CHANGED
|
@@ -1044,11 +1044,16 @@ const textEl = new FakeEl('span')
|
|
|
1044
1044
|
textEl._text = 'Dev: the migration ran long, so we ship Wednesday. PM: fine, freeze features.'
|
|
1045
1045
|
container.appendChild(textEl)
|
|
1046
1046
|
body.appendChild(container)
|
|
1047
|
+
/** 选区桩:记下 removeAllRanges 有没有被调用(点「解读」后要把本文档选区收掉)。 */
|
|
1048
|
+
const clearedSelections = { count: 0 }
|
|
1047
1049
|
const selection = {
|
|
1048
1050
|
isCollapsed: false,
|
|
1049
1051
|
rangeCount: 1,
|
|
1050
1052
|
toString: () => 'the migration ran long',
|
|
1051
1053
|
getRangeAt: () => documentStub.createRange(),
|
|
1054
|
+
removeAllRanges() {
|
|
1055
|
+
clearedSelections.count += 1
|
|
1056
|
+
},
|
|
1052
1057
|
}
|
|
1053
1058
|
windowStub.getSelection = () => selection
|
|
1054
1059
|
documentStub.dispatch('mouseup', { target: body })
|
|
@@ -1092,6 +1097,17 @@ const chatLog = Array.from(walk(panel)).find((node) => node.className === 'dsh-s
|
|
|
1092
1097
|
const askRow = Array.from(walk(panel)).find((node) => node.className === 'dsh-sel-ask')
|
|
1093
1098
|
assert('弹窗打开', !!panel && panel.style.display === 'flex')
|
|
1094
1099
|
assert('弹窗显示选中文字', !!panel && textOf(panel).indexOf('the migration ran long') >= 0)
|
|
1100
|
+
// 点完「解读」立刻把本文档选区收掉:留着它,小窗开着时按个方向键又会冒出一个「❝ 引用」,
|
|
1101
|
+
// 指的是刚才解读的那段文字(解读要的文字与上下文在 mouseup 那刻就抓好了,见 openForSelection)
|
|
1102
|
+
assert('点「解读」后本文档选区被收掉', clearedSelections.count >= 1, `removeAllRanges 调用 ${clearedSelections.count} 次`)
|
|
1103
|
+
{
|
|
1104
|
+
const quoteBtnNode = Array.from(walk(mount)).find((n) => String(n.className).indexOf('dsh-sel-quotebtn') >= 0)
|
|
1105
|
+
assert(
|
|
1106
|
+
'点「解读」后引用浮标不出现(旧选区不该复活它)',
|
|
1107
|
+
!quoteBtnNode || quoteBtnNode.style.display === 'none',
|
|
1108
|
+
quoteBtnNode ? String(quoteBtnNode.style.display) : '浮标节点还没建出来(= 没显示)',
|
|
1109
|
+
)
|
|
1110
|
+
}
|
|
1095
1111
|
|
|
1096
1112
|
const hook = windowStub.__dshSelectionExplain
|
|
1097
1113
|
assert('自检钩子可用', !!hook && typeof hook.state === 'function')
|
package/src/client/index.js
CHANGED
|
@@ -3155,14 +3155,45 @@ window.__ModuleLoader__.load({
|
|
|
3155
3155
|
}
|
|
3156
3156
|
}
|
|
3157
3157
|
|
|
3158
|
+
/**
|
|
3159
|
+
* 点完「✦ 解读」就把**本文档**那次选区收掉。
|
|
3160
|
+
*
|
|
3161
|
+
* 为什么必须收:小窗一开,浮标就切成「❝ 引用」,而引用浮标是按"有没有活选区"判定的 ——
|
|
3162
|
+
* 留着解读用的那次选区,用户在小窗里敲字、按一下方向键(keyup 会触发一次检查),
|
|
3163
|
+
* 就会在小窗上方又冒出一个「❝ 引用」,指的是他刚才解读的那段文字,莫名其妙。
|
|
3164
|
+
*
|
|
3165
|
+
* 为什么能安全收掉:解读要的东西**在 mouseup 那一刻就都抓好了** ——
|
|
3166
|
+
* `state.selection` 里存的是文字副本 + **克隆的 Range**,上下文也是从克隆 Range 采的
|
|
3167
|
+
* (checkSelection → openForSelection),之后的请求、缓存 key、展开详解、历史回放、
|
|
3168
|
+
* 升格都不再读活选区。唯一的代价是那行字的高亮没了 —— 而小窗顶部本来就写着选中文字。
|
|
3169
|
+
*
|
|
3170
|
+
* 顺序要紧:必须在 openForSelection **采完上下文之后**调用 —— 万一环境没有 cloneRange,
|
|
3171
|
+
* 那边会退回读活选区(老浏览器),先清就采不到了。
|
|
3172
|
+
*/
|
|
3173
|
+
function clearDocSelection() {
|
|
3174
|
+
try {
|
|
3175
|
+
var selection = window.getSelection && window.getSelection()
|
|
3176
|
+
if (selection && typeof selection.removeAllRanges === 'function' && selection.rangeCount > 0) {
|
|
3177
|
+
selection.removeAllRanges()
|
|
3178
|
+
}
|
|
3179
|
+
} catch (error) {
|
|
3180
|
+
/* 桩环境/老浏览器没有 removeAllRanges:留着就留着,不影响主流程 */
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3158
3184
|
function openForSelection(selection) {
|
|
3159
3185
|
// 侧边栏网页里的选区:父页面读不到那个文档,上下文只能由帧内桥就地采好带过来
|
|
3160
3186
|
if (selection.source === 'iframe') {
|
|
3161
3187
|
openPanelWith(selection.text, selection.context || '', selection.label, selection.rect, selection.keyContext)
|
|
3188
|
+
// 帧内那条高亮清不掉(不透明源,父页面碰不到它的选区)——只能让浮标自己收着;
|
|
3189
|
+
// 好在帧内的桥按指纹去重,同一条选区不会反复上报。
|
|
3162
3190
|
return
|
|
3163
3191
|
}
|
|
3164
3192
|
var contextInfo = collectContext(selection.range ? rangeSelection(selection.range) : window.getSelection())
|
|
3165
3193
|
openPanelWith(selection.text, contextInfo.context, contextInfo.label, selection.rect, contextInfo.keyContext)
|
|
3194
|
+
clearDocSelection()
|
|
3195
|
+
// 选区连同它的记录一起作废:之后要引用就重新划一段(那会走 checkQuoteSelection)
|
|
3196
|
+
state.selection = null
|
|
3166
3197
|
}
|
|
3167
3198
|
|
|
3168
3199
|
/**
|