dsh-vscode-mode 0.1.17 → 0.1.20
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 +17 -7
- package/lib/client.js +1261 -280
- package/lib/client.js.map +1 -1
- package/lib/index.js +169 -23
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/addToConversation.ts +211 -0
- package/src/client/diffDock.ts +52 -0
- package/src/client/diffDockStore.ts +87 -0
- package/src/client/editorLayout.ts +19 -0
- package/src/client/events.ts +40 -11
- package/src/client/index.ts +48 -5
- package/src/client/sidebar/SidebarView.ts +73 -0
- package/src/client/sidebar/panels/FileExplorer.ts +161 -0
- package/src/client/sidebar/panels/index.ts +24 -0
- package/src/client/sidebar/registry.ts +52 -0
- package/src/client/sidebar/types.ts +31 -0
- package/src/client/styles/editor.css +127 -5
- package/src/client/ui/ConversationDiffDock.ts +84 -0
- package/src/client/ui/DiffBox.ts +78 -29
- package/src/client/ui/EditorView.ts +375 -68
- package/src/rpc.ts +84 -18
- package/src/shared/rpc.ts +29 -1
- package/src/store.ts +6 -2
- package/src/tree.ts +62 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树)。
|
|
4
|
+
* 点目录展开/收起(首次经 edrv.listDir 拉取并缓存),点文件经 ctx.openFile 打开;
|
|
5
|
+
* 差异角标取 pendingByPath,活动文件高亮;edrv:refresh 事件与手动刷新重载树。
|
|
6
|
+
* 作者 ddj 2026-08-26
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import { rpc } from '../../rpc.js'
|
|
10
|
+
import type { SidebarCtx } from '../types.js'
|
|
11
|
+
|
|
12
|
+
const DIR_CAP = 4000
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 目录树面板主体。
|
|
16
|
+
* @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath)
|
|
17
|
+
*/
|
|
18
|
+
export function FileExplorer(props) {
|
|
19
|
+
const ctx = props?.ctx
|
|
20
|
+
const sessionId = ctx?.sessionId
|
|
21
|
+
const openFile = ctx?.openFile
|
|
22
|
+
const activePath = ctx?.activePath ?? null
|
|
23
|
+
const pendingByPath = ctx?.pendingByPath ?? {}
|
|
24
|
+
const [root, setRoot] = React.useState(null)
|
|
25
|
+
const [dirs, setDirs] = React.useState({})
|
|
26
|
+
const [expanded, setExpanded] = React.useState({})
|
|
27
|
+
const [loading, setLoading] = React.useState({})
|
|
28
|
+
const [error, setError] = React.useState(null)
|
|
29
|
+
const tokensRef = React.useRef({})
|
|
30
|
+
// expanded 的 ref 镜像:edrv:refresh 监听用首次渲染闭包,但需读到最新展开态
|
|
31
|
+
const expandedRef = React.useRef({})
|
|
32
|
+
expandedRef.current = expanded
|
|
33
|
+
|
|
34
|
+
const loadDir = (rel) => {
|
|
35
|
+
const token = (tokensRef.current[rel] || 0) + 1
|
|
36
|
+
tokensRef.current[rel] = token
|
|
37
|
+
setLoading((prev) => Object.assign({}, prev, { [rel]: true }))
|
|
38
|
+
setError(null)
|
|
39
|
+
rpc('edrv.listDir', { sessionId, path: rel }).then((res) => {
|
|
40
|
+
if (tokensRef.current[rel] !== token) return
|
|
41
|
+
if (res && res.ok && Array.isArray(res.entries)) {
|
|
42
|
+
setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }))
|
|
43
|
+
if (res.root && !root) setRoot(res.root)
|
|
44
|
+
} else {
|
|
45
|
+
setError(res?.error ? String(res.error) : '读取目录失败')
|
|
46
|
+
}
|
|
47
|
+
}).catch((e) => {
|
|
48
|
+
if (tokensRef.current[rel] !== token) return
|
|
49
|
+
setError('读取目录异常:' + String(e))
|
|
50
|
+
}).finally(() => {
|
|
51
|
+
if (tokensRef.current[rel] !== token) return
|
|
52
|
+
setLoading((prev) => {
|
|
53
|
+
const next = Object.assign({}, prev)
|
|
54
|
+
delete next[rel]
|
|
55
|
+
return next
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const toggle = (rel) => {
|
|
61
|
+
if (expanded[rel] === true) {
|
|
62
|
+
setExpanded((prev) => Object.assign({}, prev, { [rel]: false }))
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if (!dirs[rel]) void loadDir(rel)
|
|
66
|
+
setExpanded((prev) => Object.assign({}, prev, { [rel]: true }))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const refresh = () => {
|
|
70
|
+
const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true)
|
|
71
|
+
tokensRef.current = {}
|
|
72
|
+
setDirs({})
|
|
73
|
+
setLoading({})
|
|
74
|
+
setError(null)
|
|
75
|
+
if (keep.length) {
|
|
76
|
+
const next = {}
|
|
77
|
+
for (const rel of keep) next[rel] = true
|
|
78
|
+
setExpanded(next)
|
|
79
|
+
for (const rel of keep) void loadDir(rel)
|
|
80
|
+
} else {
|
|
81
|
+
setExpanded({})
|
|
82
|
+
}
|
|
83
|
+
void loadDir('')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
tokensRef.current = {}
|
|
88
|
+
setDirs({})
|
|
89
|
+
setExpanded({})
|
|
90
|
+
setLoading({})
|
|
91
|
+
setError(null)
|
|
92
|
+
setRoot(null)
|
|
93
|
+
void loadDir('')
|
|
94
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
95
|
+
}, [sessionId])
|
|
96
|
+
|
|
97
|
+
React.useEffect(() => {
|
|
98
|
+
const onRefresh = () => refresh()
|
|
99
|
+
window.addEventListener('edrv:refresh', onRefresh)
|
|
100
|
+
return () => window.removeEventListener('edrv:refresh', onRefresh)
|
|
101
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
102
|
+
}, [])
|
|
103
|
+
|
|
104
|
+
const rootName = root ? String(root).split(/[\\/]/).pop() || root : ''
|
|
105
|
+
|
|
106
|
+
const rowEl = (e, depth, isDir, isOpen) => {
|
|
107
|
+
const pending = isDir ? 0 : (pendingByPath[e.path] ?? 0)
|
|
108
|
+
const active = !isDir && e.path === activePath
|
|
109
|
+
const dim = e.type === 'other'
|
|
110
|
+
return React.createElement('div', {
|
|
111
|
+
key: e.path,
|
|
112
|
+
className: 'edrv-tree-row' + (active ? ' edrv-tree-active' : ''),
|
|
113
|
+
title: e.path,
|
|
114
|
+
style: { paddingLeft: 6 + depth * 14 },
|
|
115
|
+
onClick: () => { if (isDir) toggle(e.path); else openFile(e.path) },
|
|
116
|
+
},
|
|
117
|
+
React.createElement('span', { className: 'edrv-tree-chev' },
|
|
118
|
+
isDir ? (isOpen ? '▾' : '▸') : ''),
|
|
119
|
+
React.createElement('span', { className: 'edrv-tree-name' + (dim ? ' edrv-tree-dim' : '') },
|
|
120
|
+
isDir ? (isOpen ? '📂' : '📁') : '📄', ' ', e.name),
|
|
121
|
+
(pending > 0
|
|
122
|
+
? React.createElement('span', { className: 'edrv-tree-badge' }, String(pending))
|
|
123
|
+
: null))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const rowsOf = (rel, depth) => {
|
|
127
|
+
const entries = dirs[rel]
|
|
128
|
+
if (!entries) {
|
|
129
|
+
return [React.createElement('div', { key: rel + ':loading', className: 'edrv-tree-loading', style: { paddingLeft: 6 + depth * 14 } },
|
|
130
|
+
loading[rel] ? '加载中…' : '无法加载')]
|
|
131
|
+
}
|
|
132
|
+
const rows = []
|
|
133
|
+
const cap = entries.length
|
|
134
|
+
for (let i = 0; i < cap; i++) {
|
|
135
|
+
const e = entries[i]
|
|
136
|
+
const isDir = e.type === 'directory'
|
|
137
|
+
const isOpen = expanded[e.path] === true
|
|
138
|
+
rows.push(rowEl(e, depth, isDir, isOpen))
|
|
139
|
+
if (isDir && isOpen) rows.push(...rowsOf(e.path, depth + 1))
|
|
140
|
+
}
|
|
141
|
+
if (cap >= DIR_CAP) {
|
|
142
|
+
rows.push(React.createElement('div', { key: rel + ':cap', className: 'edrv-tree-loading', style: { paddingLeft: 6 + depth * 14 } },
|
|
143
|
+
'目录条目过多,仅显示前 ' + DIR_CAP + ' 项'))
|
|
144
|
+
}
|
|
145
|
+
return rows
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return React.createElement('div', { className: 'edrv-side-panel' },
|
|
149
|
+
React.createElement('div', { className: 'edrv-side-head' },
|
|
150
|
+
React.createElement('span', { className: 'edrv-side-title' }, '资源管理器'),
|
|
151
|
+
React.createElement('span', { className: 'edrv-side-root', title: root || '' }, rootName),
|
|
152
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
153
|
+
React.createElement('button', { className: 'edrv-side-btn', title: '刷新目录树', onClick: refresh }, '⟳')),
|
|
154
|
+
React.createElement('div', { className: 'edrv-tree' },
|
|
155
|
+
(error
|
|
156
|
+
? React.createElement('div', { className: 'edrv-tree-error' },
|
|
157
|
+
React.createElement('span', null, String(error)),
|
|
158
|
+
React.createElement('button', { className: 'edrv-side-btn', onClick: () => refresh() }, '重试'))
|
|
159
|
+
: null),
|
|
160
|
+
rowsOf('', 0)))
|
|
161
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 「文件管理」面板定义(注册表一项)。
|
|
4
|
+
* 作者 ddj 2026-08-26
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react'
|
|
7
|
+
import { FileExplorer } from './FileExplorer.js'
|
|
8
|
+
import type { SidebarPanelDef, SidebarCtx } from '../types.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 构造文件管理面板定义。
|
|
12
|
+
* @author ddj 2026年08月26号
|
|
13
|
+
* @returns 面板定义(含活动栏徽标 = 差异文件总数)
|
|
14
|
+
*/
|
|
15
|
+
export function createFilePanel(): SidebarPanelDef {
|
|
16
|
+
return {
|
|
17
|
+
id: 'explorer',
|
|
18
|
+
title: '文件管理',
|
|
19
|
+
icon: '📁',
|
|
20
|
+
order: 10,
|
|
21
|
+
badge: (ctx: SidebarCtx) => (ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null),
|
|
22
|
+
render: (ctx: SidebarCtx) => React.createElement(FileExplorer, { ctx }),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 侧边栏面板注册表。
|
|
3
|
+
* 镜像 fileOpeners.ts 的注册表模式:create + register(返回注销) + list + subscribe,
|
|
4
|
+
* 由 client/index.ts `ctx.provide('edrvSidebarPanels', registry)` 对外暴露(第三方可注册)。
|
|
5
|
+
* 作者 ddj 2026-08-26
|
|
6
|
+
*/
|
|
7
|
+
import type { SidebarPanelDef } from './types.js'
|
|
8
|
+
|
|
9
|
+
export interface SidebarPanelRegistry {
|
|
10
|
+
register(panel: SidebarPanelDef): () => void
|
|
11
|
+
list(): readonly SidebarPanelDef[]
|
|
12
|
+
subscribe(listener: () => void): () => void
|
|
13
|
+
get(id: string): SidebarPanelDef | undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
|
|
17
|
+
function panelRegister(entries: Map<string, SidebarPanelDef>, notify: () => void, panel: SidebarPanelDef): void {
|
|
18
|
+
if (!panel.id || typeof panel.render !== 'function') throw new TypeError('侧边栏面板必须提供 id 和 render')
|
|
19
|
+
entries.set(panel.id, panel)
|
|
20
|
+
notify()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 创建生命周期独立的面板注册表。
|
|
25
|
+
* @author ddj 2026年08月26号
|
|
26
|
+
* @returns 面板注册表
|
|
27
|
+
*/
|
|
28
|
+
export function createSidebarPanelRegistry(): SidebarPanelRegistry {
|
|
29
|
+
const entries = new Map<string, SidebarPanelDef>()
|
|
30
|
+
const listeners = new Set<() => void>()
|
|
31
|
+
const notify = (): void => {
|
|
32
|
+
for (const listener of listeners) listener()
|
|
33
|
+
}
|
|
34
|
+
const list = (): readonly SidebarPanelDef[] =>
|
|
35
|
+
[...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100))
|
|
36
|
+
return {
|
|
37
|
+
register(panel: SidebarPanelDef): () => void {
|
|
38
|
+
panelRegister(entries, notify, panel)
|
|
39
|
+
return () => {
|
|
40
|
+
if (entries.get(panel.id) !== panel) return
|
|
41
|
+
entries.delete(panel.id)
|
|
42
|
+
notify()
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
list,
|
|
46
|
+
subscribe(listener: () => void): () => void {
|
|
47
|
+
listeners.add(listener)
|
|
48
|
+
return () => listeners.delete(listener)
|
|
49
|
+
},
|
|
50
|
+
get: (id: string) => entries.get(id),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 侧边栏面板契约(类型 + 上下文)。
|
|
3
|
+
* 面板 = 活动栏一项 + 面板区一处渲染;新增面板只需注册一条 SidebarPanelDef。
|
|
4
|
+
* 作者 ddj 2026-08-26
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** 面板可用的共享上下文(由 SidebarView 从 EditorView 注入,面板不直碰其内部)。 */
|
|
8
|
+
export interface SidebarCtx {
|
|
9
|
+
sessionId?: string
|
|
10
|
+
openFile: (path: string) => void
|
|
11
|
+
activePath: string | null
|
|
12
|
+
/** 文件路径 → 待处理差异数(>0 才收录)。 */
|
|
13
|
+
pendingByPath: Record<string, number>
|
|
14
|
+
/** 按文件分组的差异摘要(records 状态派生)。 */
|
|
15
|
+
sum: { totalFiles: number; files: Array<{ path: string; pending: number }> }
|
|
16
|
+
/** 触发差异记录刷新(保存/决策后由 EditorView 统一 emitRefresh)。 */
|
|
17
|
+
refreshRecords: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 单个侧边栏面板定义。 */
|
|
21
|
+
export interface SidebarPanelDef {
|
|
22
|
+
id: string
|
|
23
|
+
title: string
|
|
24
|
+
/** 活动栏图标(文本/SVG 均可,横向居中显示)。 */
|
|
25
|
+
icon: string
|
|
26
|
+
order?: number
|
|
27
|
+
/** 活动栏徽标:返回数字/null;缺省不显示。 */
|
|
28
|
+
badge?: (ctx: SidebarCtx) => number | null
|
|
29
|
+
/** 面板区渲染(ctx 每次渲染注入最新快照)。 */
|
|
30
|
+
render: (ctx: SidebarCtx) => unknown
|
|
31
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */
|
|
2
2
|
/* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):
|
|
3
3
|
在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */
|
|
4
|
-
[data-edrv-view] { --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }
|
|
4
|
+
[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }
|
|
5
5
|
|
|
6
6
|
[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }
|
|
7
7
|
[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }
|
|
16
16
|
[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }
|
|
17
17
|
[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }
|
|
18
|
+
[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }
|
|
19
|
+
[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }
|
|
18
20
|
[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }
|
|
19
21
|
[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }
|
|
20
22
|
[data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }
|
|
@@ -43,11 +45,12 @@
|
|
|
43
45
|
[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }
|
|
44
46
|
[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }
|
|
45
47
|
[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }
|
|
46
|
-
[data-edrv-view] .edrv-monaco-host { flex: 1; min-height: 0; position: relative; }
|
|
47
|
-
[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; }
|
|
48
|
+
[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
|
|
49
|
+
[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }
|
|
48
50
|
[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }
|
|
49
|
-
[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius:
|
|
50
|
-
[data-edrv-view] .edrv-
|
|
51
|
+
[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }
|
|
52
|
+
[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }
|
|
53
|
+
[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }
|
|
51
54
|
[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }
|
|
52
55
|
[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }
|
|
53
56
|
[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -61,6 +64,12 @@
|
|
|
61
64
|
[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
62
65
|
[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }
|
|
63
66
|
[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }
|
|
67
|
+
/* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */
|
|
68
|
+
[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }
|
|
69
|
+
[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
70
|
+
[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
71
|
+
[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }
|
|
72
|
+
[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }
|
|
64
73
|
[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }
|
|
65
74
|
[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }
|
|
66
75
|
[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
@@ -99,3 +108,116 @@
|
|
|
99
108
|
[data-edrv-view] .edrv-progress-value { transition: none; }
|
|
100
109
|
}
|
|
101
110
|
|
|
111
|
+
/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */
|
|
112
|
+
[data-edrv-diff-dock-shell] {
|
|
113
|
+
box-sizing: border-box;
|
|
114
|
+
width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));
|
|
115
|
+
max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));
|
|
116
|
+
margin: 0 auto;
|
|
117
|
+
padding: 0;
|
|
118
|
+
flex: none;
|
|
119
|
+
border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);
|
|
120
|
+
border-radius: 12px;
|
|
121
|
+
background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));
|
|
122
|
+
box-shadow: none;
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
}
|
|
125
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-dock {
|
|
126
|
+
box-sizing: border-box;
|
|
127
|
+
position: relative;
|
|
128
|
+
left: auto;
|
|
129
|
+
right: auto;
|
|
130
|
+
bottom: auto;
|
|
131
|
+
z-index: auto;
|
|
132
|
+
width: 100%;
|
|
133
|
+
max-width: none;
|
|
134
|
+
margin: 0;
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-direction: column;
|
|
137
|
+
border: none;
|
|
138
|
+
border-radius: 0;
|
|
139
|
+
background: transparent;
|
|
140
|
+
box-shadow: none;
|
|
141
|
+
overflow: visible;
|
|
142
|
+
}
|
|
143
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }
|
|
144
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }
|
|
145
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
146
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
147
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
148
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }
|
|
149
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }
|
|
150
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }
|
|
151
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }
|
|
152
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-nav,
|
|
153
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-files,
|
|
154
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }
|
|
155
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }
|
|
156
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }
|
|
157
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }
|
|
158
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }
|
|
159
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }
|
|
160
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }
|
|
161
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }
|
|
162
|
+
[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }
|
|
163
|
+
[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }
|
|
164
|
+
[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }
|
|
165
|
+
[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }
|
|
166
|
+
[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }
|
|
167
|
+
[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }
|
|
168
|
+
[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }
|
|
169
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }
|
|
170
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
171
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }
|
|
172
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }
|
|
173
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }
|
|
174
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }
|
|
175
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }
|
|
176
|
+
[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
177
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }
|
|
178
|
+
[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
179
|
+
[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
180
|
+
[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }
|
|
181
|
+
[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
182
|
+
[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }
|
|
183
|
+
[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }
|
|
184
|
+
[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }
|
|
185
|
+
@media (max-width: 620px) {
|
|
186
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }
|
|
187
|
+
[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */
|
|
191
|
+
[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }
|
|
192
|
+
[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }
|
|
193
|
+
[data-edrv-view] .edrv-main-col > .edrv-pathbar,
|
|
194
|
+
[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }
|
|
195
|
+
[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
|
|
196
|
+
[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 0; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }
|
|
197
|
+
[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }
|
|
198
|
+
[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }
|
|
199
|
+
[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
200
|
+
[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }
|
|
201
|
+
[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }
|
|
202
|
+
[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }
|
|
203
|
+
[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }
|
|
204
|
+
[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
|
|
205
|
+
[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }
|
|
206
|
+
[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }
|
|
207
|
+
[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
208
|
+
[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }
|
|
209
|
+
[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
210
|
+
[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }
|
|
211
|
+
[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }
|
|
212
|
+
[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
213
|
+
[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }
|
|
214
|
+
[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }
|
|
215
|
+
[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
216
|
+
[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
|
|
217
|
+
[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }
|
|
218
|
+
[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }
|
|
219
|
+
[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }
|
|
220
|
+
[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }
|
|
221
|
+
[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }
|
|
222
|
+
[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }
|
|
223
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 会话级差异 dock。
|
|
4
|
+
* conversation.input.dock 是唯一实例:普通对话显示摘要,文件编辑页显示完整操作条。
|
|
5
|
+
* 作者 ddj 2026-08-26
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import { rpc } from '../rpc.js'
|
|
9
|
+
import { openDiffView } from '../events.js'
|
|
10
|
+
import { summarize } from '../state/records.js'
|
|
11
|
+
import { nextDiffPath } from '../diffDock.js'
|
|
12
|
+
import { readDiffDock, subscribeDiffDock } from '../diffDockStore.js'
|
|
13
|
+
import { DiffBox } from './DiffBox.js'
|
|
14
|
+
|
|
15
|
+
const nextIndexBySession = new Map()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 对话输入框上方的唯一差异 dock。
|
|
19
|
+
* @author ddj 2026年08月26号
|
|
20
|
+
* @param props DSH 注入的会话属性
|
|
21
|
+
* @returns 差异 dock React 元素或 null
|
|
22
|
+
*/
|
|
23
|
+
export function ConversationDiffDock(props) {
|
|
24
|
+
const sessionId = props?.sessionId
|
|
25
|
+
const [summary, setSummary] = React.useState(null)
|
|
26
|
+
const [, setStoreRevision] = React.useState(0)
|
|
27
|
+
const seq = React.useRef(0)
|
|
28
|
+
|
|
29
|
+
const load = React.useCallback(() => {
|
|
30
|
+
if (!sessionId) return
|
|
31
|
+
const current = ++seq.current
|
|
32
|
+
rpc('edrv.list', { sessionId }).then((res) => {
|
|
33
|
+
if (current !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return
|
|
34
|
+
setSummary(summarize(res.records))
|
|
35
|
+
}).catch(() => {})
|
|
36
|
+
}, [sessionId])
|
|
37
|
+
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
load()
|
|
40
|
+
const timer = setInterval(load, 5000)
|
|
41
|
+
const onRefresh = () => load()
|
|
42
|
+
window.addEventListener('edrv:refresh', onRefresh)
|
|
43
|
+
return () => {
|
|
44
|
+
clearInterval(timer)
|
|
45
|
+
window.removeEventListener('edrv:refresh', onRefresh)
|
|
46
|
+
}
|
|
47
|
+
}, [load])
|
|
48
|
+
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
if (!sessionId) return undefined
|
|
51
|
+
const sync = () => setStoreRevision((value) => value + 1)
|
|
52
|
+
sync()
|
|
53
|
+
return subscribeDiffDock(sessionId, sync)
|
|
54
|
+
}, [sessionId])
|
|
55
|
+
|
|
56
|
+
const renderDock = (content, key) => React.createElement('div', {
|
|
57
|
+
key,
|
|
58
|
+
className: 'edrv-diff-dock-shell',
|
|
59
|
+
'data-edrv-diff-dock-shell': '1',
|
|
60
|
+
}, content)
|
|
61
|
+
|
|
62
|
+
const editorSnapshot = readDiffDock(sessionId)
|
|
63
|
+
if (editorSnapshot) {
|
|
64
|
+
if (editorSnapshot.mode === 'editor-empty' && !editorSnapshot.fileTotal) return null
|
|
65
|
+
return renderDock(React.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!sessionId || !summary?.pendingFiles?.length) return null
|
|
69
|
+
|
|
70
|
+
const paths = summary.pendingFiles.map((file) => file.path)
|
|
71
|
+
const click = () => {
|
|
72
|
+
const current = nextIndexBySession.get(sessionId) ?? 0
|
|
73
|
+
const next = nextDiffPath(paths, current)
|
|
74
|
+
nextIndexBySession.set(sessionId, next.index)
|
|
75
|
+
if (next.path) openDiffView(next.path)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return renderDock(React.createElement(DiffBox, {
|
|
79
|
+
mode: 'chat',
|
|
80
|
+
dock: true,
|
|
81
|
+
fileTotal: paths.length,
|
|
82
|
+
onOpenNextFile: click,
|
|
83
|
+
}), sessionId || 'chat')
|
|
84
|
+
}
|