dsh-explorer 0.0.0-stage → 0.1.0
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/LICENSE +22 -0
- package/README.md +106 -2
- package/cordis.patch.yml +5 -0
- package/icon.svg +7 -0
- package/lib/client.js +2814 -0
- package/lib/index.js +747 -0
- package/package.json +92 -4
- package/src/client/ExplorerContextMenu.tsx +159 -0
- package/src/client/ExplorerHeader.tsx +75 -0
- package/src/client/ExplorerOptionsModal.tsx +243 -0
- package/src/client/ExplorerRow.tsx +240 -0
- package/src/client/ExplorerSearch.tsx +42 -0
- package/src/client/ExplorerView.tsx +1139 -0
- package/src/client/api.ts +121 -0
- package/src/client/conversation-draft.ts +635 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/drag-drop.ts +209 -0
- package/src/client/explorer.module.css +628 -0
- package/src/client/index.tsx +160 -0
- package/src/client/options.ts +59 -0
- package/src/client/vscode-theme.tsx +250 -0
- package/src/config.ts +11 -0
- package/src/fs-operations.ts +116 -0
- package/src/fs-tree.ts +155 -0
- package/src/fs-watch.ts +179 -0
- package/src/git-status.ts +161 -0
- package/src/index.ts +165 -0
- package/src/path-security.ts +22 -0
- package/src/session-path.ts +35 -0
- package/src/wire.ts +63 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { fetchFileContent } from './api.ts'
|
|
2
|
+
import {
|
|
3
|
+
insertMentionToComposer,
|
|
4
|
+
insertSnippetToComposer,
|
|
5
|
+
resolveActiveSessionId,
|
|
6
|
+
} from './conversation-draft.ts'
|
|
7
|
+
|
|
8
|
+
export interface DragExplorerItem {
|
|
9
|
+
path: string
|
|
10
|
+
relativePath: string
|
|
11
|
+
isDir: boolean
|
|
12
|
+
name: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DSH_EXPLORER_MIME = 'application/vnd.dsh.explorer-items'
|
|
16
|
+
|
|
17
|
+
let currentDragItems: DragExplorerItem[] | null = null
|
|
18
|
+
|
|
19
|
+
export function setupDragStart(
|
|
20
|
+
e: React.DragEvent,
|
|
21
|
+
items: DragExplorerItem[],
|
|
22
|
+
): void {
|
|
23
|
+
currentDragItems = items
|
|
24
|
+
const payload = JSON.stringify(items)
|
|
25
|
+
try {
|
|
26
|
+
e.dataTransfer.setData(DSH_EXPLORER_MIME, payload)
|
|
27
|
+
} catch {}
|
|
28
|
+
const plainText = items
|
|
29
|
+
.map(item => (item.isDir ? `@${item.relativePath}/` : `@${item.relativePath}`))
|
|
30
|
+
.join(' ')
|
|
31
|
+
try {
|
|
32
|
+
e.dataTransfer.setData('text/plain', plainText)
|
|
33
|
+
} catch {}
|
|
34
|
+
e.dataTransfer.effectAllowed = 'copy'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function setupDragEnd(): void {
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
currentDragItems = null
|
|
40
|
+
}, 200)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let activeOverlay: HTMLDivElement | null = null
|
|
44
|
+
|
|
45
|
+
function getOrCreateOverlay(targetEl: HTMLElement): HTMLDivElement {
|
|
46
|
+
if (activeOverlay && document.body.contains(activeOverlay)) {
|
|
47
|
+
return activeOverlay
|
|
48
|
+
}
|
|
49
|
+
const div = document.createElement('div')
|
|
50
|
+
div.style.position = 'absolute'
|
|
51
|
+
div.style.inset = '0'
|
|
52
|
+
div.style.backgroundColor = 'rgba(33, 150, 243, 0.08)'
|
|
53
|
+
div.style.border = '2px dashed #007fd4'
|
|
54
|
+
div.style.borderRadius = '8px'
|
|
55
|
+
div.style.zIndex = '9999'
|
|
56
|
+
div.style.pointerEvents = 'none'
|
|
57
|
+
div.style.display = 'flex'
|
|
58
|
+
div.style.flexDirection = 'column'
|
|
59
|
+
div.style.alignItems = 'center'
|
|
60
|
+
div.style.justifyContent = 'center'
|
|
61
|
+
div.style.gap = '8px'
|
|
62
|
+
div.style.backdropFilter = 'blur(2px)'
|
|
63
|
+
div.style.transition = 'opacity 0.15s cubic-bezier(0.2, 0, 0, 1)'
|
|
64
|
+
|
|
65
|
+
div.innerHTML = `
|
|
66
|
+
<div style="font-weight: 600; font-size: 13px; color: #58a6ff; background: rgba(22, 27, 34, 0.85); border: 1px solid rgba(88, 166, 255, 0.3); padding: 8px 16px; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.4); display: flex; align-items: center; gap: 8px;">
|
|
67
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
68
|
+
<path d="M8 2a.75.75 0 0 1 .75.75v8.69l3.22-3.22a.75.75 0 1 1 1.06 1.06l-4.5 4.5a.75.75 0 0 1-1.06 0l-4.5-4.5a.75.75 0 1 1 1.06-1.06l3.22 3.22V2.75A.75.75 0 0 1 8 2z"/>
|
|
69
|
+
</svg>
|
|
70
|
+
Drop at cursor to mention
|
|
71
|
+
</div>
|
|
72
|
+
<div style="font-size: 11px; opacity: 0.9; color: #8b949e; background: rgba(22, 27, 34, 0.7); padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.06);">
|
|
73
|
+
Hold <b>Alt</b> or <b>Shift</b> to embed code snippet
|
|
74
|
+
</div>
|
|
75
|
+
`
|
|
76
|
+
activeOverlay = div
|
|
77
|
+
return div
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function attachGlobalConversationDropListener(ctx: any): () => void {
|
|
81
|
+
let dragDepth = 0
|
|
82
|
+
|
|
83
|
+
const hasExplorerData = (e: DragEvent): boolean => {
|
|
84
|
+
if (currentDragItems && currentDragItems.length > 0) return true
|
|
85
|
+
try {
|
|
86
|
+
const types = Array.from(e.dataTransfer?.types ?? [])
|
|
87
|
+
return types.includes(DSH_EXPLORER_MIME) || types.includes('text/plain')
|
|
88
|
+
} catch {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const getConversationContainer = (): HTMLElement | null => {
|
|
94
|
+
return (
|
|
95
|
+
document.querySelector<HTMLElement>('[data-slot="conversation.session"]') ??
|
|
96
|
+
document.querySelector<HTMLElement>('[data-slot="conversation.shell"]') ??
|
|
97
|
+
document.querySelector<HTMLElement>('[data-slot*="conversation"]') ??
|
|
98
|
+
document.querySelector<HTMLElement>('.an3WzG_root') ??
|
|
99
|
+
document.querySelector<HTMLElement>('.conversation-main') ??
|
|
100
|
+
document.querySelector<HTMLElement>('main') ??
|
|
101
|
+
document.querySelector<HTMLElement>('#root > div') ??
|
|
102
|
+
null
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const handleDragEnter = (e: DragEvent) => {
|
|
107
|
+
if (!hasExplorerData(e)) return
|
|
108
|
+
const container = getConversationContainer()
|
|
109
|
+
if (!container) return
|
|
110
|
+
|
|
111
|
+
dragDepth += 1
|
|
112
|
+
const overlay = getOrCreateOverlay(container)
|
|
113
|
+
if (!container.contains(overlay)) {
|
|
114
|
+
if (getComputedStyle(container).position === 'static') {
|
|
115
|
+
container.style.position = 'relative'
|
|
116
|
+
}
|
|
117
|
+
container.appendChild(overlay)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const handleDragOver = (e: DragEvent) => {
|
|
122
|
+
if (!hasExplorerData(e)) return
|
|
123
|
+
e.preventDefault()
|
|
124
|
+
if (e.dataTransfer) {
|
|
125
|
+
e.dataTransfer.dropEffect = 'copy'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const handleDragLeave = (e: DragEvent) => {
|
|
130
|
+
if (!hasExplorerData(e)) return
|
|
131
|
+
dragDepth -= 1
|
|
132
|
+
if (dragDepth <= 0) {
|
|
133
|
+
dragDepth = 0
|
|
134
|
+
activeOverlay?.remove()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const handleDrop = async (e: DragEvent) => {
|
|
139
|
+
if (!hasExplorerData(e)) return
|
|
140
|
+
e.preventDefault()
|
|
141
|
+
e.stopPropagation()
|
|
142
|
+
|
|
143
|
+
dragDepth = 0
|
|
144
|
+
activeOverlay?.remove()
|
|
145
|
+
|
|
146
|
+
let items = currentDragItems
|
|
147
|
+
if (!items || items.length === 0) {
|
|
148
|
+
try {
|
|
149
|
+
const raw = e.dataTransfer?.getData(DSH_EXPLORER_MIME)
|
|
150
|
+
if (raw) items = JSON.parse(raw) as DragExplorerItem[]
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!items || items.length === 0) {
|
|
155
|
+
// Fallback: try reading plain text
|
|
156
|
+
const plain = e.dataTransfer?.getData('text/plain')
|
|
157
|
+
if (plain && plain.startsWith('@')) {
|
|
158
|
+
const relativePath = plain.replace(/^@"?/, '').replace(/"?$/, '')
|
|
159
|
+
items = [{
|
|
160
|
+
path: relativePath,
|
|
161
|
+
relativePath,
|
|
162
|
+
isDir: relativePath.endsWith('/'),
|
|
163
|
+
name: relativePath,
|
|
164
|
+
}]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!items || items.length === 0) return
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const activeSessionId = resolveActiveSessionId(ctx)
|
|
172
|
+
const isAlt = e.altKey || e.shiftKey
|
|
173
|
+
let currentPoint: { x: number; y: number } | undefined = { x: e.clientX, y: e.clientY }
|
|
174
|
+
|
|
175
|
+
for (const item of items) {
|
|
176
|
+
if (isAlt && !item.isDir) {
|
|
177
|
+
try {
|
|
178
|
+
const content = await fetchFileContent(item.path)
|
|
179
|
+
insertSnippetToComposer(ctx, activeSessionId, item.relativePath, content, currentPoint)
|
|
180
|
+
} catch {
|
|
181
|
+
insertMentionToComposer(ctx, activeSessionId, item.relativePath, item.isDir, currentPoint)
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
insertMentionToComposer(ctx, activeSessionId, item.relativePath, item.isDir, currentPoint)
|
|
185
|
+
}
|
|
186
|
+
currentPoint = undefined
|
|
187
|
+
if (items.length > 1) {
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, 40))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.warn('[dsh-explorer] drop failed:', err)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Use capture phase so we intercept before child components swallow
|
|
197
|
+
document.addEventListener('dragenter', handleDragEnter, true)
|
|
198
|
+
document.addEventListener('dragover', handleDragOver, true)
|
|
199
|
+
document.addEventListener('dragleave', handleDragLeave, true)
|
|
200
|
+
document.addEventListener('drop', handleDrop, true)
|
|
201
|
+
|
|
202
|
+
return () => {
|
|
203
|
+
document.removeEventListener('dragenter', handleDragEnter, true)
|
|
204
|
+
document.removeEventListener('dragover', handleDragOver, true)
|
|
205
|
+
document.removeEventListener('dragleave', handleDragLeave, true)
|
|
206
|
+
document.removeEventListener('drop', handleDrop, true)
|
|
207
|
+
activeOverlay?.remove()
|
|
208
|
+
}
|
|
209
|
+
}
|