feat(annotate): ①折叠/标注按钮左移10px避让滚动条 ②标注图标去加号(纯对话框) ③编辑器弹出即恢复系统光标(打字不再无光标) ④标题改“点击或拖动”+首次使用右侧气泡提示(仅一次) ⑤连续标注改为追加模式(不顶掉上一处)

This commit is contained in:
Aisland
2026-09-02 02:03:47 +08:00
parent c113584fc1
commit d6d5d3b0c0
4 changed files with 61 additions and 17 deletions
+4 -2
View File
@@ -182,7 +182,8 @@ export const zh = {
'pane.termFail': '终端不可用(宿主缺少 node-pty/ws 或连接失败)',
'pane.closeTab': '关闭标签页',
'pane.collapse': '折叠窗格',
'annot.label': '标注:点击窗口中的位置提要求',
'annot.label': '标注:点击或拖动窗口中的位置提要求',
'annot.hint': '点击或拖动框选窗口中的任意位置,再输入你的要求;连续标注会依次追加到输入框',
'annot.placeholder': '对这里提要求…(Enter 确认,Shift+Enter 换行,Esc 取消)',
'annot.ok': '确认:注入对话框输入框(不发送)',
'annot.cancel': '取消',
@@ -405,7 +406,8 @@ export const en = {
'pane.termFail': 'Terminal unavailable (host missing node-pty/ws, or connection failed)',
'pane.closeTab': 'Close tab',
'pane.collapse': 'Collapse pane',
'annot.label': 'Annotate: click a spot in the window',
'annot.label': 'Annotate: click or drag a spot in the window',
'annot.hint': 'Click or drag to select a spot in any window, then type your request; consecutive annotations append to the input.',
'annot.placeholder': 'Describe what to change here… (Enter to confirm, Shift+Enter newline, Esc cancel)',
'annot.ok': 'Confirm: put into chat input (not sent)',
'annot.cancel': 'Cancel',
+19 -3
View File
@@ -505,8 +505,10 @@ function fillHostInput(text: string): boolean {
?? Array.from(document.querySelectorAll<HTMLTextAreaElement>('textarea')).pop()
if (!ta) return false
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set
if (setter) setter.call(ta, text)
else ta.value = text
// 连续标注:已有内容不顶掉,换行追加(用户可以同一对话连续标注多处)
const next = ta.value && ta.value.trim() ? ta.value + '\n\n' + text : text
if (setter) setter.call(ta, next)
else ta.value = next
ta.dispatchEvent(new Event('input', { bubbles: true }))
try {
ta.focus()
@@ -600,6 +602,7 @@ function AnnotationOverlay() {
const ctx = ctxOf(e.target)
if (ctx) stat += ',元素 ' + ctx
}
document.body.classList.remove('dsh-wt-annotating')
const ex = Math.max(8, Math.min(e.clientX + 10, window.innerWidth - 288))
const ey = Math.max(8, Math.min(e.clientY + 14, window.innerHeight - 148))
setAnnot({ started: true, drawing: false, ex, ey, stat })
@@ -617,7 +620,20 @@ function AnnotationOverlay() {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [s.on, s.started])
// 首次进入标注:右侧气泡提示「拖动也可框选」,仅显示一次
const [hintOnce] = useState<boolean>(() => { try { return localStorage.getItem('dsh.worktable.annotHint.v1') !== '1' } catch { return false } })
const [hintVisible, setHintVisible] = useState(false)
useEffect(() => {
if (!s.on || !hintOnce) return
setHintVisible(true)
try { localStorage.setItem('dsh.worktable.annotHint.v1', '1') } catch {}
const t = window.setTimeout(() => setHintVisible(false), 4200)
return () => window.clearTimeout(t)
}, [s.on, hintOnce])
if (!s.on) return null
{hintVisible && (
<div className="dsh-wt_annotUi dsh-wt_annotHint">{T('annot.hint')}</div>
)}
if (s.drawing) {
const L = Math.min(s.bx0, s.bx1)
const T = Math.min(s.by0, s.by1)
@@ -2942,7 +2958,7 @@ function WorkspaceLayer(props: { spec: LayoutSpec; geom: Geom | null; chatW: num
aria-label={T('annot.label')}
onClick={() => startAnnot(windowLabelOf(row, index))}
>
<svg viewBox="0 0 16 16" aria-hidden><path d="M2 5.6c0-1.2 1-2.2 2.2-2.2h7.6c1.2 0 2.2 1 2.2 2.2v3.6c0 1.2-1 2.2-2.2 2.2H7.5L5 13.6l.3-2.4H4.2c-1.2 0-2.2-1-2.2-2.2z" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round" /><path d="M8 5.3v3.4M6.3 7h3.4" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" /></svg>
<svg viewBox="0 0 16 16" aria-hidden><path d="M2 5.6c0-1.2 1-2.2 2.2-2.2h7.6c1.2 0 2.2 1 2.2 2.2v3.6c0 1.2-1 2.2-2.2 2.2H7.5L5 13.6l.3-2.4H4.2c-1.2 0-2.2-1-2.2-2.2z" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round" /></svg>
</button>
<button
type="button"
+4 -2
View File
@@ -145,10 +145,10 @@ export const css = xtermCss + '\n' + [
'.dsh-wt_paneBar{padding-right:26px}',
'.dsh-wt_paneTitle{flex:1;min-width:0;font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary,#9aa4b2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
// 窗格折叠按钮:常态只是一个折角小标签(无底色无描边),hover 才显示按钮范围(底+描边)
'.dsh-wt_collapseBtn{position:absolute;top:2px;right:4px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}',
'.dsh-wt_collapseBtn{position:absolute;top:2px;right:14px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}',
'.dsh-wt_collapseBtn:hover{color:var(--dsw-alias-label-primary,#e6e8eb);background:var(--dsw-alias-fill-l2,rgba(255,255,255,.09));border-color:var(--dsw-alias-border-l1,#262b36)}',
'.dsh-wt_collapseBtn svg{width:12px;height:12px;display:block}',
'.dsh-wt_annotBtn{position:absolute;top:2px;right:26px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}',
'.dsh-wt_annotBtn{position:absolute;top:2px;right:36px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}',
'.dsh-wt_annotBtn:hover{color:var(--dsw-alias-label-primary,#e6e8eb);background:var(--dsw-alias-fill-l2,rgba(255,255,255,.09));border-color:var(--dsw-alias-border-l1,#262b36)}',
'.dsh-wt_annotBtn svg{width:12px;height:12px;display:block}',
'.dsh-wt-annotating,.dsh-wt-annotating *{cursor:none!important}',
@@ -156,6 +156,8 @@ export const css = xtermCss + '\n' + [
'.dsh-wt_annotBubble{position:fixed;z-index:5000;left:0;top:0;transform:translate(-50%,-100%) translateY(-4px);pointer-events:none;width:30px;height:22px;border-radius:9px;background:#4f8ef7;box-shadow:0 2px 10px rgba(79,142,247,.5);display:flex;align-items:center;justify-content:center}',
'.dsh-wt_annotBubble::after{content:"";position:absolute;left:50%;bottom:-5px;transform:translateX(-50%);border:6px solid #4f8ef7;border-top-width:5px;border-bottom-width:0;border-left-color:transparent;border-right-color:transparent}',
'.dsh-wt_annotSel{position:fixed;z-index:4999;border:1.5px solid #4f8ef7;background:rgba(79,142,247,.16);box-shadow:0 0 0 1px rgba(79,142,247,.25);pointer-events:none}',
'.dsh-wt_annotHint{position:fixed;right:18px;bottom:56px;z-index:5002;max-width:320px;padding:9px 12px;border-radius:10px;background:rgba(16,20,28,.96);border:1px solid rgba(122,92,255,.45);box-shadow:0 8px 24px rgba(0,0,0,.5);color:#e6e8eb;font-size:12px;line-height:18px;pointer-events:none;animation:annotHintIn .25s ease-out}',
'@keyframes annotHintIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}',
'.dsh-wt_annotEditor{position:fixed;z-index:5001;width:280px;box-sizing:border-box;display:flex;flex-direction:column;gap:6px;padding:8px;border-radius:10px;background:rgba(16,20,28,.96);border:1px solid rgba(255,255,255,.2);box-shadow:0 10px 30px rgba(0,0,0,.5)}',
'.dsh-wt_annotInput{width:100%;box-sizing:border-box;height:56px;padding:6px 8px;border:1px solid rgba(255,255,255,.18);border-radius:7px;background:rgba(255,255,255,.05);color:#e6e8eb;font:inherit;font-size:12px;line-height:17px;resize:none;outline:none;cursor:text}',
'.dsh-wt_annotInput:focus{border-color:#4f8ef7}',