Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [1.4.1] - 2026-08-18
### 修复
- **恢复 Cmd/Ctrl+Enter 纯批注直接发送(issue #17)**:v1.3.18 为修 issue #10 把修饰键 Enter 全部排除,导致空草稿时 Cmd/Ctrl+Enter 无法直接发送纯批注。现仅在草稿为空时接管 Cmd/Ctrl+Enter,并在 capture 阶段主动 `submit('queue')`,避免 composer 的 accelerated 路径在「运行中 + 有排队消息」时误走 steerQueue 而把批注块留在输入框;草稿已有文字时仍交由 composer 处理,保留宿主的 Queue / Steer 策略。

## [1.4.0] - 2026-08-18
### 新功能
- **语言跟随 DSH(issue #11)**:接入 `@deepseek-ai/dsh-client-locale` 的 `locale` 服务(`ctx.locale.getSnapshot().active` + `subscribe()`),UI 文案与批注协议块按 `zh`/`en` 双语生成与实时切换;气泡隐藏手术与反解析同时兼容 `提问:`/`Ask:`、`批注:`/`Note: ` 及「问题:」老格式,历史消息跨语言切换可解析;回复芯片保持语言中立的 `Annotation N`;locale 服务缺失时回退 zh,行为与旧版一致。
Expand Down
46 changes: 37 additions & 9 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -994,13 +994,33 @@ window.__ModuleLoader__.load({
// 批注清单 + 用户输入的问题。用户始终看不到文本被塞进去。
// IME 铁律(v1.3.10 修了 nativeEvent.keyCode;v1.3.11 补 compositionend
// 后 Enter keyCode=13 的时序洞):合成期 / 上屏确认 Enter 绝不能 setDraft。
// 修饰键守卫(v1.3.18):只拦裸 Enter——Shift+Enter 换行、Ctrl/Meta+Enter
// 官方 accelerated 提交、以及浏览器默认换行路径都不应触发拼稿。
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
// 修饰键守卫(v1.3.18 修 issue #10):Shift+Enter 换行、Alt+Enter 默认路径
// 不触发拼稿;裸 Enter 继续处理带文字草稿,Cmd/Ctrl+Enter 只接管空草稿
// 的纯批注。已有文字时交回 composer,保留宿主的 Queue / Steer 策略。
// 纯批注的 Cmd/Ctrl+Enter 需要在这里直接提交:composer 的 accelerated 路径在
// 「运行中 + 有排队消息」时会走 steerQueue,而不是发送当前草稿;我们在
// capture 阶段 setDraft 后 stopPropagation,主动 submit('queue'),保证
// 纯批注能直接发出,同时不会把批注块明文留在输入框。
if (e.key === 'Enter' && !e.shiftKey && !e.altKey
&& ui.quotes.length > 0 && !isImeKeyBlocked(e)) {
var ta = e.target
if (ta instanceof HTMLTextAreaElement && ta.closest && ta.closest('[data-composer-card]') !== null) {
attachAndSend()
var attached = attachAndSend(e)
if (attached && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
e.stopPropagation()
var current = sessions.list.getSnapshot().current
if (current !== undefined) {
var scoped = sessions.scope(current)
if (scoped !== undefined) {
try {
ctx.conversation.input.for(scoped).submit('queue')
} catch (err) {
console.warn('[annotation] Cmd/Ctrl+Enter 直接提交失败:', err)
}
}
}
}
}
}
}
Expand Down Expand Up @@ -1295,25 +1315,33 @@ window.__ModuleLoader__.load({
+ '\n\n' + t('block.format', { n: n }) + '\n\n' + t('block.marker')
}

/** 提交前把批注块拼进 composer 草稿(随回车一起发送)。 */
function attachAndSend() {
function shouldAttachForEnter(e, draft) {
return !(e.ctrlKey || e.metaKey) || draft.trim() === ''
}

/** 提交前把批注块拼进 composer 草稿(随回车一起发送)。
* 返回 true 表示批注块已在草稿中(本次刚拼入,或之前已拼入未发送)。 */
function attachAndSend(e) {
var current = sessions.list.getSnapshot().current
if (current === undefined) return
if (current === undefined) return false
try {
var scoped = sessions.scope(current)
if (scoped === undefined) return
if (scoped === undefined) return false
var shell = ctx.conversation.input.for(scoped)
var st = shell.state.getSnapshot()
var draft = st.draft || ''
if (!shouldAttachForEnter(e, draft)) return false
// 草稿已含批注块(上次追加未发送)→ 不重复追加(zh/en 双哨兵)。
if (hasAnnotationBlock(draft)) return
if (hasAnnotationBlock(draft)) return true
var block = buildBlock()
shell.setDraft(block + '\n' + draft)
annotationAttached = true
console.log('[annotation] 批注块已拼入草稿,回车将随消息发送(' + ui.quotes.length + ' 条)')
return true
} catch (err) {
console.warn('[annotation] 批注拼稿失败:', err)
showToast(t('toast.attachFail') + (err && err.message ? err.message : err))
return false
}
}

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@omdsh-dev/dsh-annotation",
"version": "1.4.0",
"version": "1.4.1",
"type": "module",
"description": "DSH Web selection-annotation plugin: select assistant text, annotate (optional), and press Enter to send the annotation block with your message — the model replies to each annotation by number, with hoverable chips in the reply.",
"main": "lib/index.js",
Expand All @@ -26,7 +26,8 @@
],
"scripts": {
"build": "node -e \"require('node:fs').rmSync('lib', { recursive: true, force: true })\" && tsc -p tsconfig.json",
"check": "npm run build && node --check lib/index.js && node --check client.js",
"test": "node --test test/*.test.mjs",
"check": "npm run build && node --check lib/index.js && node --check client.js && npm test",
"prepack": "npm run check"
},
"publishConfig": {
Expand Down
15 changes: 15 additions & 0 deletions test/enter-policy.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'

const source = readFileSync(new URL('../client.js', import.meta.url), 'utf8')
const match = source.match(/function shouldAttachForEnter\(e, draft\) \{[\s\S]*?\n \}/)
assert.ok(match, 'client.js should define shouldAttachForEnter')
const shouldAttachForEnter = Function(`return (${match[0]})`)()

test('快捷回车只接管空草稿的纯批注', () => {
assert.equal(shouldAttachForEnter({ ctrlKey: true, metaKey: false }, ''), true)
assert.equal(shouldAttachForEnter({ ctrlKey: false, metaKey: true }, ' '), true)
assert.equal(shouldAttachForEnter({ ctrlKey: true, metaKey: false }, '用户问题'), false)
assert.equal(shouldAttachForEnter({ ctrlKey: false, metaKey: false }, '用户问题'), true)
})