@civilization/dsh-workspace-files 0.3.2 → 0.3.3
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 +8 -0
- package/lib/client.js +612 -541
- package/lib/client.js.map +1 -1
- package/lib/index.js +15 -1
- package/package.json +1 -1
- package/src/client/icons.tsx +39 -0
- package/src/client/index.tsx +549 -554
- package/src/files.ts +14 -0
package/src/client/index.tsx
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser entry for the enhanced workspace files explorer plugin.
|
|
3
|
+
* High-performance virtualized flat-row rendering with DSH Design Tokens and UI Motion.
|
|
3
4
|
* Overrides DSH official 'files' tab using 'priority: extension'.
|
|
4
5
|
*/
|
|
5
6
|
import type { Context } from '@deepseek-ai/cordis'
|
|
6
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
7
|
+
import React, { memo, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
|
|
8
|
+
import type { JSX } from 'react'
|
|
7
9
|
import {
|
|
8
10
|
FileGlyphIcon,
|
|
9
11
|
IconAt,
|
|
10
12
|
IconCheck,
|
|
11
|
-
|
|
13
|
+
IconChevronRight,
|
|
12
14
|
IconClose,
|
|
13
15
|
IconCollapseAll,
|
|
14
16
|
IconCopy,
|
|
@@ -93,7 +95,6 @@ export function apply(ctx: Context): void {
|
|
|
93
95
|
const slots = (ctx as any).slots
|
|
94
96
|
|
|
95
97
|
if (sidebarRightTabs && slots) {
|
|
96
|
-
// 关键:kind: 'files', priority: 'extension'(Rank 3 > builtin 2),完美接管官方文件标签页
|
|
97
98
|
ctx.effect(() => sidebarRightTabs.register({
|
|
98
99
|
id: OFFICIAL_FILES_TAB_ID,
|
|
99
100
|
kind: 'files',
|
|
@@ -177,16 +178,16 @@ function ensureSettingsStyle() {
|
|
|
177
178
|
style.id = id
|
|
178
179
|
style.textContent = `
|
|
179
180
|
.dsh-wf-card {
|
|
180
|
-
border:
|
|
181
|
-
background: var(--dsw-alias-
|
|
182
|
-
border-radius:
|
|
181
|
+
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
182
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
183
|
+
border-radius: var(--dsw-radius-md, 8px);
|
|
183
184
|
list-style: none;
|
|
184
|
-
transition: border-color .16s, background .16s;
|
|
185
|
+
transition: border-color 0.16s, background 0.16s;
|
|
185
186
|
box-sizing: border-box;
|
|
186
187
|
}
|
|
187
188
|
.dsh-wf-card-open {
|
|
188
|
-
background: var(--dsw-alias-
|
|
189
|
-
border-color: var(--dsw-alias-
|
|
189
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
190
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
190
191
|
}
|
|
191
192
|
.dsh-wf-header {
|
|
192
193
|
-webkit-appearance: none;
|
|
@@ -196,24 +197,24 @@ function ensureSettingsStyle() {
|
|
|
196
197
|
color: inherit;
|
|
197
198
|
text-align: left;
|
|
198
199
|
cursor: pointer;
|
|
199
|
-
background:
|
|
200
|
+
background: transparent;
|
|
200
201
|
border: 0;
|
|
201
|
-
border-radius:
|
|
202
|
+
border-radius: var(--dsw-radius-md, 8px);
|
|
202
203
|
align-items: center;
|
|
203
204
|
gap: 12px;
|
|
204
|
-
padding: 14px
|
|
205
|
+
padding: 12px 14px;
|
|
205
206
|
display: flex;
|
|
206
207
|
}
|
|
207
208
|
.dsh-wf-head-text {
|
|
208
209
|
flex-direction: column;
|
|
209
210
|
flex: 1;
|
|
210
|
-
gap:
|
|
211
|
+
gap: 3px;
|
|
211
212
|
min-width: 0;
|
|
212
213
|
display: flex;
|
|
213
214
|
}
|
|
214
215
|
.dsh-wf-name {
|
|
215
|
-
color: var(--dsw-alias-label-primary
|
|
216
|
-
font-size:
|
|
216
|
+
color: var(--dsw-alias-label-primary);
|
|
217
|
+
font-size: 14px;
|
|
217
218
|
font-weight: 600;
|
|
218
219
|
line-height: 1.4;
|
|
219
220
|
display: flex;
|
|
@@ -221,38 +222,38 @@ function ensureSettingsStyle() {
|
|
|
221
222
|
gap: 8px;
|
|
222
223
|
}
|
|
223
224
|
.dsh-wf-version {
|
|
224
|
-
font-size:
|
|
225
|
+
font-size: 11px;
|
|
225
226
|
font-weight: 400;
|
|
226
|
-
color: var(--dsw-alias-label-tertiary
|
|
227
|
+
color: var(--dsw-alias-label-tertiary);
|
|
227
228
|
}
|
|
228
229
|
.dsh-wf-desc {
|
|
229
|
-
color: var(--dsw-alias-label-
|
|
230
|
-
font-size:
|
|
230
|
+
color: var(--dsw-alias-label-secondary);
|
|
231
|
+
font-size: 12px;
|
|
231
232
|
line-height: 1.5;
|
|
232
233
|
}
|
|
233
234
|
.dsh-wf-chevron {
|
|
234
|
-
color: var(--dsw-alias-label-tertiary
|
|
235
|
+
color: var(--dsw-alias-label-tertiary);
|
|
235
236
|
flex: none;
|
|
236
|
-
transition: transform .
|
|
237
|
+
transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
237
238
|
display: inline-flex;
|
|
238
239
|
}
|
|
239
240
|
.dsh-wf-chevron-open {
|
|
240
241
|
transform: rotate(180deg);
|
|
241
242
|
}
|
|
242
243
|
.dsh-wf-body {
|
|
243
|
-
border-top:
|
|
244
|
-
margin: 0
|
|
244
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
245
|
+
margin: 0 14px;
|
|
245
246
|
padding: 8px 0 12px;
|
|
246
247
|
}
|
|
247
248
|
.dsh-wf-row {
|
|
248
249
|
align-items: center;
|
|
249
250
|
gap: 16px;
|
|
250
|
-
padding:
|
|
251
|
+
padding: 10px 0;
|
|
251
252
|
display: flex;
|
|
252
253
|
justify-content: space-between;
|
|
253
254
|
}
|
|
254
255
|
.dsh-wf-row + .dsh-wf-row {
|
|
255
|
-
border-top:
|
|
256
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
256
257
|
}
|
|
257
258
|
.dsh-wf-label-box {
|
|
258
259
|
flex-direction: column;
|
|
@@ -262,14 +263,14 @@ function ensureSettingsStyle() {
|
|
|
262
263
|
display: flex;
|
|
263
264
|
}
|
|
264
265
|
.dsh-wf-label {
|
|
265
|
-
font-size:
|
|
266
|
+
font-size: 13px;
|
|
266
267
|
font-weight: 550;
|
|
267
|
-
color: var(--dsw-alias-label-primary
|
|
268
|
+
color: var(--dsw-alias-label-primary);
|
|
268
269
|
}
|
|
269
270
|
.dsh-wf-hint {
|
|
270
|
-
color: var(--dsw-alias-label-tertiary
|
|
271
|
-
font-size:
|
|
272
|
-
line-height:
|
|
271
|
+
color: var(--dsw-alias-label-tertiary);
|
|
272
|
+
font-size: 11.5px;
|
|
273
|
+
line-height: 16px;
|
|
273
274
|
}
|
|
274
275
|
.dsh-wf-ctrl {
|
|
275
276
|
flex-shrink: 0;
|
|
@@ -278,56 +279,56 @@ function ensureSettingsStyle() {
|
|
|
278
279
|
gap: 8px;
|
|
279
280
|
}
|
|
280
281
|
.dsh-wf-seg {
|
|
281
|
-
border:
|
|
282
|
-
border-radius:
|
|
282
|
+
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
283
|
+
border-radius: var(--dsw-radius-sm, 6px);
|
|
283
284
|
gap: 2px;
|
|
284
285
|
padding: 2px;
|
|
285
286
|
display: inline-flex;
|
|
286
|
-
background: var(--dsw-alias-
|
|
287
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
287
288
|
}
|
|
288
289
|
.dsh-wf-seg-btn {
|
|
289
290
|
font: inherit;
|
|
290
|
-
color: var(--dsw-alias-label-secondary
|
|
291
|
+
color: var(--dsw-alias-label-secondary);
|
|
291
292
|
cursor: pointer;
|
|
292
|
-
background:
|
|
293
|
+
background: transparent;
|
|
293
294
|
border: none;
|
|
294
|
-
border-radius:
|
|
295
|
-
padding:
|
|
296
|
-
font-size:
|
|
297
|
-
|
|
298
|
-
transition: all 0.15s;
|
|
295
|
+
border-radius: 4px;
|
|
296
|
+
padding: 3px 8px;
|
|
297
|
+
font-size: 11.5px;
|
|
298
|
+
transition: all 0.15s cubic-bezier(0.16, 1, 0.3, 1);
|
|
299
299
|
}
|
|
300
300
|
.dsh-wf-seg-btn:hover {
|
|
301
|
-
color: var(--dsw-alias-label-primary
|
|
301
|
+
color: var(--dsw-alias-label-primary);
|
|
302
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
302
303
|
}
|
|
303
304
|
.dsh-wf-seg-active {
|
|
304
|
-
background: var(--dsw-alias-
|
|
305
|
-
color: var(--dsw-alias-
|
|
305
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent);
|
|
306
|
+
color: var(--dsw-alias-state-business-primary);
|
|
306
307
|
font-weight: 600;
|
|
307
|
-
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
|
308
308
|
}
|
|
309
309
|
.dsh-wf-input {
|
|
310
310
|
box-sizing: border-box;
|
|
311
|
-
border:
|
|
312
|
-
background: var(--dsw-alias-
|
|
313
|
-
width:
|
|
311
|
+
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
312
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
313
|
+
width: 58px;
|
|
314
314
|
text-align: center;
|
|
315
|
-
color: var(--dsw-alias-label-primary
|
|
315
|
+
color: var(--dsw-alias-label-primary);
|
|
316
316
|
font: inherit;
|
|
317
|
-
border-radius:
|
|
318
|
-
padding:
|
|
319
|
-
font-size:
|
|
320
|
-
|
|
317
|
+
border-radius: 6px;
|
|
318
|
+
padding: 3px 6px;
|
|
319
|
+
font-size: 12px;
|
|
320
|
+
outline: none;
|
|
321
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
321
322
|
}
|
|
322
323
|
.dsh-wf-input:focus {
|
|
323
|
-
border-color: var(--dsw-alias-
|
|
324
|
-
|
|
324
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
325
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent);
|
|
325
326
|
}
|
|
326
327
|
.dsh-wf-toggle {
|
|
327
328
|
position: relative;
|
|
328
329
|
display: inline-block;
|
|
329
|
-
width:
|
|
330
|
-
height:
|
|
330
|
+
width: 36px;
|
|
331
|
+
height: 20px;
|
|
331
332
|
cursor: pointer;
|
|
332
333
|
}
|
|
333
334
|
.dsh-wf-toggle input {
|
|
@@ -339,55 +340,55 @@ function ensureSettingsStyle() {
|
|
|
339
340
|
.dsh-wf-toggle-track {
|
|
340
341
|
position: absolute;
|
|
341
342
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
342
|
-
background-color: var(--dsw-alias-border-
|
|
343
|
-
transition: .
|
|
344
|
-
border-radius:
|
|
343
|
+
background-color: var(--dsw-alias-border-l1);
|
|
344
|
+
transition: 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
345
|
+
border-radius: 20px;
|
|
345
346
|
}
|
|
346
347
|
.dsh-wf-toggle input:checked + .dsh-wf-toggle-track {
|
|
347
|
-
background-color: var(--dsw-alias-
|
|
348
|
+
background-color: var(--dsw-alias-state-business-primary);
|
|
348
349
|
}
|
|
349
350
|
.dsh-wf-toggle-thumb {
|
|
350
351
|
position: absolute;
|
|
351
|
-
height:
|
|
352
|
-
width:
|
|
352
|
+
height: 14px;
|
|
353
|
+
width: 14px;
|
|
353
354
|
left: 3px;
|
|
354
355
|
bottom: 3px;
|
|
355
|
-
background-color:
|
|
356
|
-
transition: .
|
|
356
|
+
background-color: #ffffff;
|
|
357
|
+
transition: 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
357
358
|
border-radius: 50%;
|
|
358
|
-
box-shadow: 0 1px
|
|
359
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.18);
|
|
359
360
|
}
|
|
360
361
|
.dsh-wf-toggle input:checked + .dsh-wf-toggle-track .dsh-wf-toggle-thumb {
|
|
361
|
-
transform: translateX(
|
|
362
|
+
transform: translateX(16px);
|
|
362
363
|
}
|
|
363
364
|
.dsh-wf-footer {
|
|
364
|
-
margin-top:
|
|
365
|
-
padding-top:
|
|
366
|
-
border-top:
|
|
365
|
+
margin-top: 6px;
|
|
366
|
+
padding-top: 8px;
|
|
367
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
367
368
|
display: flex;
|
|
368
369
|
align-items: center;
|
|
369
370
|
justify-content: space-between;
|
|
370
371
|
}
|
|
371
372
|
.dsh-wf-saved-hint {
|
|
372
|
-
font-size:
|
|
373
|
-
color: var(--dsw-alias-state-
|
|
373
|
+
font-size: 11px;
|
|
374
|
+
color: var(--dsw-alias-state-business-primary);
|
|
374
375
|
display: flex;
|
|
375
376
|
align-items: center;
|
|
376
377
|
gap: 4px;
|
|
377
378
|
}
|
|
378
379
|
.dsh-wf-btn-reset {
|
|
379
380
|
background: transparent;
|
|
380
|
-
border:
|
|
381
|
-
border-radius:
|
|
382
|
-
padding: 3px
|
|
383
|
-
font-size:
|
|
384
|
-
color: var(--dsw-alias-label-secondary
|
|
381
|
+
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
382
|
+
border-radius: 4px;
|
|
383
|
+
padding: 3px 8px;
|
|
384
|
+
font-size: 11px;
|
|
385
|
+
color: var(--dsw-alias-label-secondary);
|
|
385
386
|
cursor: pointer;
|
|
386
|
-
transition: all 0.
|
|
387
|
+
transition: all 0.12s;
|
|
387
388
|
}
|
|
388
389
|
.dsh-wf-btn-reset:hover {
|
|
389
|
-
background: var(--dsw-alias-bg-
|
|
390
|
-
color: var(--dsw-alias-label-primary
|
|
390
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
391
|
+
color: var(--dsw-alias-label-primary);
|
|
391
392
|
}
|
|
392
393
|
`
|
|
393
394
|
document.head.appendChild(style)
|
|
@@ -429,29 +430,27 @@ export function WorkspaceFilesSettingsCard(): JSX.Element {
|
|
|
429
430
|
const def = { maxDepth: 16, showHidden: false }
|
|
430
431
|
setConfig(def)
|
|
431
432
|
saveWorkspaceFilesConfig(def)
|
|
432
|
-
setSavedTick(true)
|
|
433
|
-
setTimeout(() => setSavedTick(false), 2000)
|
|
434
433
|
}
|
|
435
434
|
|
|
436
435
|
return (
|
|
437
|
-
<div className={
|
|
436
|
+
<div className={`dsh-wf-card ${open ? 'dsh-wf-card-open' : ''}`}>
|
|
438
437
|
<button
|
|
439
438
|
type="button"
|
|
440
439
|
className="dsh-wf-header"
|
|
441
|
-
aria-expanded={open}
|
|
442
440
|
onClick={() => setOpen(!open)}
|
|
443
441
|
>
|
|
442
|
+
<FolderGuideGlyph size={22} />
|
|
444
443
|
<div className="dsh-wf-head-text">
|
|
445
444
|
<div className="dsh-wf-name">
|
|
446
445
|
<span>工作区文件</span>
|
|
447
446
|
<span className="dsh-wf-version">v{__PLUGIN_VERSION__}</span>
|
|
448
447
|
</div>
|
|
449
448
|
<div className="dsh-wf-desc">
|
|
450
|
-
|
|
449
|
+
全量项目文件树浏览、即时检索与 @-mention 会话一键引用。
|
|
451
450
|
</div>
|
|
452
451
|
</div>
|
|
453
|
-
<span className={
|
|
454
|
-
<
|
|
452
|
+
<span className={`dsh-wf-chevron ${open ? 'dsh-wf-chevron-open' : ''}`}>
|
|
453
|
+
<IconChevronRight size={14} style={{ transform: open ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.16s' }} />
|
|
455
454
|
</span>
|
|
456
455
|
</button>
|
|
457
456
|
|
|
@@ -459,42 +458,42 @@ export function WorkspaceFilesSettingsCard(): JSX.Element {
|
|
|
459
458
|
<div className="dsh-wf-body">
|
|
460
459
|
<div className="dsh-wf-row">
|
|
461
460
|
<div className="dsh-wf-label-box">
|
|
462
|
-
<
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
</
|
|
461
|
+
<span className="dsh-wf-label">扫描最大深度 (层级)</span>
|
|
462
|
+
<span className="dsh-wf-hint">
|
|
463
|
+
控制文件树深入遍历的最大文件夹嵌套层级,防止庞大项目深度过大产生卡顿。
|
|
464
|
+
</span>
|
|
466
465
|
</div>
|
|
467
466
|
<div className="dsh-wf-ctrl">
|
|
468
467
|
<div className="dsh-wf-seg">
|
|
469
|
-
{[8, 16,
|
|
468
|
+
{[8, 16, 24, 32].map(d => (
|
|
470
469
|
<button
|
|
471
470
|
key={d}
|
|
472
471
|
type="button"
|
|
473
|
-
className={config.maxDepth === d ? 'dsh-wf-seg-
|
|
472
|
+
className={`dsh-wf-seg-btn ${config.maxDepth === d ? 'dsh-wf-seg-active' : ''}`}
|
|
474
473
|
onClick={() => handleDepthSelect(d)}
|
|
475
474
|
>
|
|
476
|
-
{d}
|
|
475
|
+
{d} 层
|
|
477
476
|
</button>
|
|
478
477
|
))}
|
|
479
478
|
</div>
|
|
480
479
|
<input
|
|
481
480
|
type="number"
|
|
482
|
-
min=
|
|
483
|
-
max=
|
|
481
|
+
min={1}
|
|
482
|
+
max={100}
|
|
484
483
|
className="dsh-wf-input"
|
|
485
484
|
value={config.maxDepth}
|
|
486
485
|
onChange={handleDepthInput}
|
|
487
|
-
title="
|
|
486
|
+
title="手动输入层级深度 (1-100)"
|
|
488
487
|
/>
|
|
489
488
|
</div>
|
|
490
489
|
</div>
|
|
491
490
|
|
|
492
491
|
<div className="dsh-wf-row">
|
|
493
492
|
<div className="dsh-wf-label-box">
|
|
494
|
-
<
|
|
495
|
-
<
|
|
496
|
-
|
|
497
|
-
</
|
|
493
|
+
<span className="dsh-wf-label">显示以点开头的隐藏文件</span>
|
|
494
|
+
<span className="dsh-wf-hint">
|
|
495
|
+
开启后,文件树中将展现如 .env, .editorconfig 等以点开头的配置文件(.git 等缓存黑名单除外)。
|
|
496
|
+
</span>
|
|
498
497
|
</div>
|
|
499
498
|
<div className="dsh-wf-ctrl">
|
|
500
499
|
<label className="dsh-wf-toggle">
|
|
@@ -512,14 +511,10 @@ export function WorkspaceFilesSettingsCard(): JSX.Element {
|
|
|
512
511
|
|
|
513
512
|
<div className="dsh-wf-footer">
|
|
514
513
|
<div>
|
|
515
|
-
{savedTick
|
|
514
|
+
{savedTick && (
|
|
516
515
|
<span className="dsh-wf-saved-hint">
|
|
517
516
|
<IconCheck size={12} />
|
|
518
|
-
|
|
519
|
-
</span>
|
|
520
|
-
) : (
|
|
521
|
-
<span style={{ fontSize: 11, color: 'var(--dsw-alias-label-tertiary, #8b93a1)' }}>
|
|
522
|
-
设置改动即时生效并同步至工作区文件树
|
|
517
|
+
配置已自动保存并实时同步
|
|
523
518
|
</span>
|
|
524
519
|
)}
|
|
525
520
|
</div>
|
|
@@ -537,6 +532,90 @@ export function WorkspaceFilesSettingsCard(): JSX.Element {
|
|
|
537
532
|
)
|
|
538
533
|
}
|
|
539
534
|
|
|
535
|
+
/* ── High-Performance Flattened Visible Row Model ── */
|
|
536
|
+
export interface VisibleFlatRow {
|
|
537
|
+
readonly id: string
|
|
538
|
+
readonly name: string
|
|
539
|
+
readonly path: string
|
|
540
|
+
readonly isDirectory: boolean
|
|
541
|
+
readonly depth: number
|
|
542
|
+
readonly isExpanded: boolean
|
|
543
|
+
readonly hasChildren: boolean
|
|
544
|
+
readonly childCount: number
|
|
545
|
+
readonly size?: number
|
|
546
|
+
readonly item: FileItem
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* 线性遍历计算可视行。未展开目录的子节点绝不遍历,彻底解决 DOM 节点爆炸问题。
|
|
551
|
+
*/
|
|
552
|
+
function flattenVisibleTree(
|
|
553
|
+
items: FileItem[],
|
|
554
|
+
expandedPaths: Set<string>,
|
|
555
|
+
depth = 0,
|
|
556
|
+
out: VisibleFlatRow[] = [],
|
|
557
|
+
): VisibleFlatRow[] {
|
|
558
|
+
for (const it of items) {
|
|
559
|
+
const isDir = it.isDirectory
|
|
560
|
+
const hasChildren = isDir && Array.isArray(it.children) && it.children.length > 0
|
|
561
|
+
const isExpanded = isDir && expandedPaths.has(it.path)
|
|
562
|
+
const childCount = hasChildren ? it.children!.length : 0
|
|
563
|
+
|
|
564
|
+
out.push({
|
|
565
|
+
id: it.path,
|
|
566
|
+
name: it.name,
|
|
567
|
+
path: it.path,
|
|
568
|
+
isDirectory: isDir,
|
|
569
|
+
depth,
|
|
570
|
+
isExpanded,
|
|
571
|
+
hasChildren,
|
|
572
|
+
childCount,
|
|
573
|
+
size: it.size,
|
|
574
|
+
item: it,
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
if (isDir && isExpanded && hasChildren) {
|
|
578
|
+
flattenVisibleTree(it.children!, expandedPaths, depth + 1, out)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return out
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* 树形过滤模式:仅保留包含匹配项的分支并保持展开
|
|
586
|
+
*/
|
|
587
|
+
function filterTreeWithAutoExpand(
|
|
588
|
+
nodes: FileItem[],
|
|
589
|
+
q: string,
|
|
590
|
+
): { filteredItems: FileItem[]; autoExpandPaths: Set<string> } {
|
|
591
|
+
const autoExpand = new Set<string>()
|
|
592
|
+
|
|
593
|
+
function filterNode(node: FileItem): FileItem | null {
|
|
594
|
+
const matchSelf = node.name.toLowerCase().includes(q) || node.path.toLowerCase().includes(q)
|
|
595
|
+
if (node.isDirectory) {
|
|
596
|
+
const children: FileItem[] = []
|
|
597
|
+
for (const child of node.children || []) {
|
|
598
|
+
const c = filterNode(child)
|
|
599
|
+
if (c) children.push(c)
|
|
600
|
+
}
|
|
601
|
+
if (matchSelf || children.length > 0) {
|
|
602
|
+
autoExpand.add(node.path)
|
|
603
|
+
return { ...node, children }
|
|
604
|
+
}
|
|
605
|
+
return null
|
|
606
|
+
}
|
|
607
|
+
return matchSelf ? node : null
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const result: FileItem[] = []
|
|
611
|
+
for (const it of nodes) {
|
|
612
|
+
const f = filterNode(it)
|
|
613
|
+
if (f) result.push(f)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return { filteredItems: result, autoExpandPaths: autoExpand }
|
|
617
|
+
}
|
|
618
|
+
|
|
540
619
|
/* ── Main Workspace Files View ── */
|
|
541
620
|
function WorkspaceFilesView({
|
|
542
621
|
sessionId,
|
|
@@ -550,11 +629,15 @@ function WorkspaceFilesView({
|
|
|
550
629
|
const [data, setData] = useState<WorkspaceFilesResult | null>(null)
|
|
551
630
|
const [loading, setLoading] = useState(true)
|
|
552
631
|
const [error, setError] = useState<string | null>(null)
|
|
553
|
-
const [
|
|
632
|
+
const [inputQuery, setInputQuery] = useState('')
|
|
554
633
|
const [toast, setToast] = useState<string | null>(null)
|
|
555
634
|
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => new Set())
|
|
556
635
|
const [activeConfig, setActiveConfig] = useState<WorkspaceFilesConfig>(() => loadWorkspaceFilesConfig())
|
|
557
636
|
const [showHidden, setShowHidden] = useState(() => loadWorkspaceFilesConfig().showHidden)
|
|
637
|
+
const [searchViewMode, setSearchViewMode] = useState<'flat' | 'tree'>('flat')
|
|
638
|
+
|
|
639
|
+
// 使用 React 并发特性分离高频打字与重度树过滤
|
|
640
|
+
const deferredQuery = useDeferredValue(inputQuery.trim().toLowerCase())
|
|
558
641
|
|
|
559
642
|
const toastTimer = useRef<number | null>(null)
|
|
560
643
|
|
|
@@ -564,6 +647,10 @@ function WorkspaceFilesView({
|
|
|
564
647
|
toastTimer.current = window.setTimeout(() => setToast(null), 2000)
|
|
565
648
|
}, [])
|
|
566
649
|
|
|
650
|
+
useEffect(() => {
|
|
651
|
+
ensureFilesStyle()
|
|
652
|
+
}, [])
|
|
653
|
+
|
|
567
654
|
useEffect(() => {
|
|
568
655
|
const handleCfgChange = (e: any) => {
|
|
569
656
|
if (e.detail) {
|
|
@@ -588,12 +675,10 @@ function WorkspaceFilesView({
|
|
|
588
675
|
})
|
|
589
676
|
setData(res)
|
|
590
677
|
|
|
591
|
-
// 默认折叠,并从 localStorage 恢复已记录的展开状态
|
|
592
678
|
setExpandedPaths(prev => {
|
|
593
679
|
if (prev.size > 0) return prev
|
|
594
680
|
const stored = loadStoredExpandedPaths(res.root || cwd)
|
|
595
681
|
if (stored !== null) return stored
|
|
596
|
-
// 默认全部关闭,不展开任何目录
|
|
597
682
|
return new Set<string>()
|
|
598
683
|
})
|
|
599
684
|
} catch (err: any) {
|
|
@@ -607,7 +692,7 @@ function WorkspaceFilesView({
|
|
|
607
692
|
void loadTree()
|
|
608
693
|
}, [loadTree])
|
|
609
694
|
|
|
610
|
-
const handleToggleExpand = (path: string) => {
|
|
695
|
+
const handleToggleExpand = useCallback((path: string) => {
|
|
611
696
|
setExpandedPaths(prev => {
|
|
612
697
|
const next = new Set(prev)
|
|
613
698
|
if (next.has(path)) next.delete(path)
|
|
@@ -615,9 +700,9 @@ function WorkspaceFilesView({
|
|
|
615
700
|
saveStoredExpandedPaths(data?.root || cwd, next)
|
|
616
701
|
return next
|
|
617
702
|
})
|
|
618
|
-
}
|
|
703
|
+
}, [data?.root, cwd])
|
|
619
704
|
|
|
620
|
-
const handleExpandAll = () => {
|
|
705
|
+
const handleExpandAll = useCallback(() => {
|
|
621
706
|
if (!data) return
|
|
622
707
|
const all = new Set<string>()
|
|
623
708
|
function collect(items: FileItem[]) {
|
|
@@ -631,16 +716,16 @@ function WorkspaceFilesView({
|
|
|
631
716
|
collect(data.items)
|
|
632
717
|
setExpandedPaths(all)
|
|
633
718
|
saveStoredExpandedPaths(data.root || cwd, all)
|
|
634
|
-
}
|
|
719
|
+
}, [data, cwd])
|
|
635
720
|
|
|
636
|
-
const handleCollapseAll = () => {
|
|
721
|
+
const handleCollapseAll = useCallback(() => {
|
|
637
722
|
const empty = new Set<string>()
|
|
638
723
|
setExpandedPaths(empty)
|
|
639
724
|
saveStoredExpandedPaths(data?.root || cwd, empty)
|
|
640
|
-
}
|
|
725
|
+
}, [data?.root, cwd])
|
|
641
726
|
|
|
642
|
-
//
|
|
643
|
-
const handleJumpToFolderInTree = (dirPath: string) => {
|
|
727
|
+
// 搜索时点击文件夹:跳回树形视图并展开该目录及全部祖先路径,平滑定位
|
|
728
|
+
const handleJumpToFolderInTree = useCallback((dirPath: string) => {
|
|
644
729
|
const parts = dirPath.split('/')
|
|
645
730
|
const toExpand: string[] = []
|
|
646
731
|
let acc = ''
|
|
@@ -659,11 +744,9 @@ function WorkspaceFilesView({
|
|
|
659
744
|
return next
|
|
660
745
|
})
|
|
661
746
|
|
|
662
|
-
|
|
663
|
-
setQuery('')
|
|
747
|
+
setInputQuery('')
|
|
664
748
|
showToastMsg(`已定位并展开目录: ${dirPath}/`)
|
|
665
749
|
|
|
666
|
-
// 平滑滚动定位到该目录节点
|
|
667
750
|
setTimeout(() => {
|
|
668
751
|
try {
|
|
669
752
|
const el = document.querySelector(`[data-tree-path="${dirPath}"]`)
|
|
@@ -672,18 +755,16 @@ function WorkspaceFilesView({
|
|
|
672
755
|
}
|
|
673
756
|
} catch {}
|
|
674
757
|
}, 60)
|
|
675
|
-
}
|
|
758
|
+
}, [data?.root, cwd, showToastMsg])
|
|
676
759
|
|
|
677
|
-
|
|
678
|
-
const handleCopyPath = (e: React.MouseEvent, path: string) => {
|
|
760
|
+
const handleCopyPath = useCallback((e: React.MouseEvent, path: string) => {
|
|
679
761
|
e.stopPropagation()
|
|
680
762
|
void navigator.clipboard.writeText(path).then(() => {
|
|
681
763
|
showToastMsg(`已复制路径: ${path}`)
|
|
682
764
|
})
|
|
683
|
-
}
|
|
765
|
+
}, [showToastMsg])
|
|
684
766
|
|
|
685
|
-
|
|
686
|
-
const handleMention = (e: React.MouseEvent, itemOrPath: string | FileItem) => {
|
|
767
|
+
const handleMention = useCallback((e: React.MouseEvent, itemOrPath: string | FileItem) => {
|
|
687
768
|
e.stopPropagation()
|
|
688
769
|
const itemObj: FileItem = typeof itemOrPath === 'string'
|
|
689
770
|
? { name: itemOrPath.split('/').pop() || itemOrPath, path: itemOrPath, isDirectory: false }
|
|
@@ -691,24 +772,17 @@ function WorkspaceFilesView({
|
|
|
691
772
|
|
|
692
773
|
const mentionText = formatMentionText(itemObj.path, itemObj.isDirectory)
|
|
693
774
|
|
|
694
|
-
// 1. 优先尝试向会话输入框插入官方原生 ReferenceChipNode 胶囊卡片
|
|
695
775
|
let inserted = false
|
|
696
776
|
try {
|
|
697
777
|
inserted = tryInsertNativeReferenceChip(globalCtx, sessionId, itemObj)
|
|
698
|
-
} catch
|
|
699
|
-
console.warn('[dsh-workspace-files] 原生 Chip 插入失败:', err)
|
|
700
|
-
}
|
|
778
|
+
} catch {}
|
|
701
779
|
|
|
702
|
-
// 2. 若原生 Chip 插入未成功,无缝降级为规范纯文本插入
|
|
703
780
|
if (!inserted) {
|
|
704
781
|
try {
|
|
705
782
|
inserted = insertMentionToComposer(mentionText)
|
|
706
|
-
} catch
|
|
707
|
-
console.warn('[dsh-workspace-files] 降级纯文本插入失败:', err)
|
|
708
|
-
}
|
|
783
|
+
} catch {}
|
|
709
784
|
}
|
|
710
785
|
|
|
711
|
-
// 写入剪贴板作为双重保障
|
|
712
786
|
const displayName = itemObj.isDirectory ? `${itemObj.name}/` : itemObj.name
|
|
713
787
|
void navigator.clipboard.writeText(mentionText.trim()).then(() => {
|
|
714
788
|
if (inserted) {
|
|
@@ -717,10 +791,9 @@ function WorkspaceFilesView({
|
|
|
717
791
|
showToastMsg(`已复制引用: ${mentionText.trim()}`)
|
|
718
792
|
}
|
|
719
793
|
})
|
|
720
|
-
}
|
|
794
|
+
}, [sessionId, showToastMsg])
|
|
721
795
|
|
|
722
|
-
|
|
723
|
-
const handleRevealInExplorer = (e: React.MouseEvent, item: FileItem) => {
|
|
796
|
+
const handleRevealInExplorer = useCallback((e: React.MouseEvent, item: FileItem) => {
|
|
724
797
|
e.stopPropagation()
|
|
725
798
|
fetch('/dsh-workspace-files/api/files.open', {
|
|
726
799
|
method: 'POST',
|
|
@@ -744,10 +817,9 @@ function WorkspaceFilesView({
|
|
|
744
817
|
.catch(err => {
|
|
745
818
|
showToastMsg(`请求失败: ${err?.message || String(err)}`)
|
|
746
819
|
})
|
|
747
|
-
}
|
|
820
|
+
}, [sessionId, cwd, showToastMsg])
|
|
748
821
|
|
|
749
|
-
|
|
750
|
-
const handleOpenDefaultApp = (e: React.MouseEvent, item: FileItem) => {
|
|
822
|
+
const handleOpenDefaultApp = useCallback((e: React.MouseEvent, item: FileItem) => {
|
|
751
823
|
e.stopPropagation()
|
|
752
824
|
fetch('/dsh-workspace-files/api/files.open', {
|
|
753
825
|
method: 'POST',
|
|
@@ -771,88 +843,49 @@ function WorkspaceFilesView({
|
|
|
771
843
|
.catch(err => {
|
|
772
844
|
showToastMsg(`请求失败: ${err?.message || String(err)}`)
|
|
773
845
|
})
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const [searchViewMode, setSearchViewMode] = useState<'flat' | 'tree'>('flat')
|
|
777
|
-
const [searchExpandedPaths, setSearchExpandedPaths] = useState<Set<string>>(() => new Set())
|
|
846
|
+
}, [sessionId, cwd, showToastMsg])
|
|
778
847
|
|
|
779
|
-
|
|
780
|
-
const { filteredTreeItems, flatSearchResults } = useMemo(() => {
|
|
781
|
-
if (!data) return { filteredTreeItems: [], flatSearchResults: [] }
|
|
782
|
-
const q = query.trim().toLowerCase()
|
|
783
|
-
if (!q) return { filteredTreeItems: data.items, flatSearchResults: [] }
|
|
848
|
+
const isSearchActive = Boolean(deferredQuery)
|
|
784
849
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
850
|
+
// 1. 扁平搜索结果计算(限定前 150 项,秒速完成)
|
|
851
|
+
const flatSearchResults = useMemo(() => {
|
|
852
|
+
if (!data || !isSearchActive) return []
|
|
853
|
+
const results: Array<{ item: FileItem; parentDir: string }> = []
|
|
854
|
+
function traverse(items: FileItem[], parentDir: string) {
|
|
788
855
|
for (const it of items) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
856
|
+
if (it.name.toLowerCase().includes(deferredQuery) || it.path.toLowerCase().includes(deferredQuery)) {
|
|
857
|
+
results.push({ item: it, parentDir })
|
|
858
|
+
if (results.length >= 300) return
|
|
792
859
|
}
|
|
793
860
|
if (it.isDirectory && it.children) {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
traverseFlat(data.items, '')
|
|
799
|
-
|
|
800
|
-
// 树形过滤集合
|
|
801
|
-
function filterNode(node: FileItem): FileItem | null {
|
|
802
|
-
const selfMatch = node.name.toLowerCase().includes(q) || node.path.toLowerCase().includes(q)
|
|
803
|
-
if (node.isDirectory) {
|
|
804
|
-
const filteredChildren: FileItem[] = []
|
|
805
|
-
for (const child of node.children || []) {
|
|
806
|
-
const matchedChild = filterNode(child)
|
|
807
|
-
if (matchedChild) filteredChildren.push(matchedChild)
|
|
861
|
+
traverse(it.children, it.path)
|
|
862
|
+
if (results.length >= 300) return
|
|
808
863
|
}
|
|
809
|
-
if (selfMatch || filteredChildren.length > 0) {
|
|
810
|
-
return { ...node, children: filteredChildren }
|
|
811
|
-
}
|
|
812
|
-
return null
|
|
813
864
|
}
|
|
814
|
-
return selfMatch ? node : null
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
const tree: FileItem[] = []
|
|
818
|
-
for (const rootNode of data.items) {
|
|
819
|
-
const matched = filterNode(rootNode)
|
|
820
|
-
if (matched) tree.push(matched)
|
|
821
865
|
}
|
|
822
|
-
|
|
823
|
-
return
|
|
824
|
-
}, [data,
|
|
825
|
-
|
|
826
|
-
//
|
|
827
|
-
|
|
828
|
-
if (!
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
function collect(items: FileItem[]) {
|
|
832
|
-
for (const it of items) {
|
|
833
|
-
if (it.isDirectory) {
|
|
834
|
-
const selfMatch = it.name.toLowerCase().includes(q) || it.path.toLowerCase().includes(q)
|
|
835
|
-
const childMatch = it.children?.some(c => c.name.toLowerCase().includes(q) || c.path.toLowerCase().includes(q))
|
|
836
|
-
if (selfMatch || childMatch) autoExpand.add(it.path)
|
|
837
|
-
if (it.children) collect(it.children)
|
|
838
|
-
}
|
|
839
|
-
}
|
|
866
|
+
traverse(data.items, '')
|
|
867
|
+
return results
|
|
868
|
+
}, [data, deferredQuery, isSearchActive])
|
|
869
|
+
|
|
870
|
+
// 2. 扁平可见行计算(常规树与搜索树),O(N_visible) 极速生成
|
|
871
|
+
const visibleFlatRows = useMemo(() => {
|
|
872
|
+
if (!data) return []
|
|
873
|
+
if (!isSearchActive) {
|
|
874
|
+
return flattenVisibleTree(data.items, expandedPaths)
|
|
840
875
|
}
|
|
841
|
-
collect(data.items)
|
|
842
|
-
setSearchExpandedPaths(autoExpand)
|
|
843
|
-
}, [query, data])
|
|
844
876
|
|
|
845
|
-
|
|
877
|
+
// 搜索树模式
|
|
878
|
+
const { filteredItems, autoExpandPaths } = filterTreeWithAutoExpand(data.items, deferredQuery)
|
|
879
|
+
return flattenVisibleTree(filteredItems, autoExpandPaths)
|
|
880
|
+
}, [data, isSearchActive, deferredQuery, expandedPaths])
|
|
846
881
|
|
|
847
882
|
return (
|
|
848
883
|
<div className="dsh-files-root">
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
{/* Header */}
|
|
884
|
+
{/* 1. Header Banner */}
|
|
852
885
|
<header className="dsh-files-header">
|
|
853
886
|
<div className="dsh-files-header-info">
|
|
854
887
|
<div className="dsh-files-title-row">
|
|
855
|
-
<span className="dsh-files-title"
|
|
888
|
+
<span className="dsh-files-title">工作区文件</span>
|
|
856
889
|
{data && (
|
|
857
890
|
<span className="dsh-files-stats-badge">
|
|
858
891
|
{data.totalFiles} 文件 · {data.totalDirs} 目录
|
|
@@ -865,26 +898,26 @@ function WorkspaceFilesView({
|
|
|
865
898
|
</div>
|
|
866
899
|
</header>
|
|
867
900
|
|
|
868
|
-
{/* Search
|
|
901
|
+
{/* 2. Search Toolbar (Clean Elevated Input + Motion Action Pills) */}
|
|
869
902
|
<div className="dsh-files-toolbar">
|
|
870
903
|
<div className="dsh-files-search-wrap">
|
|
871
904
|
<span className="dsh-files-search-icon"><IconSearch size={13} /></span>
|
|
872
905
|
<input
|
|
873
906
|
type="text"
|
|
874
907
|
className="dsh-files-search-input"
|
|
875
|
-
placeholder="
|
|
876
|
-
value={
|
|
877
|
-
onChange={e =>
|
|
908
|
+
placeholder="搜索文件或路径 (Esc 清空)..."
|
|
909
|
+
value={inputQuery}
|
|
910
|
+
onChange={e => setInputQuery(e.target.value)}
|
|
878
911
|
onKeyDown={e => {
|
|
879
|
-
if (e.key === 'Escape')
|
|
912
|
+
if (e.key === 'Escape') setInputQuery('')
|
|
880
913
|
}}
|
|
881
914
|
/>
|
|
882
|
-
{
|
|
915
|
+
{inputQuery && (
|
|
883
916
|
<button
|
|
884
917
|
type="button"
|
|
885
918
|
className="dsh-files-clear-btn"
|
|
886
|
-
onClick={() =>
|
|
887
|
-
title="
|
|
919
|
+
onClick={() => setInputQuery('')}
|
|
920
|
+
title="清空搜索"
|
|
888
921
|
>
|
|
889
922
|
<IconClose size={12} />
|
|
890
923
|
</button>
|
|
@@ -897,7 +930,7 @@ function WorkspaceFilesView({
|
|
|
897
930
|
type="button"
|
|
898
931
|
className="dsh-files-tool-btn is-active"
|
|
899
932
|
onClick={() => setSearchViewMode(searchViewMode === 'flat' ? 'tree' : 'flat')}
|
|
900
|
-
title={searchViewMode === 'flat' ? '
|
|
933
|
+
title={searchViewMode === 'flat' ? '切为树形匹配视图' : '切为扁平列表视图'}
|
|
901
934
|
>
|
|
902
935
|
{searchViewMode === 'flat' ? <IconTree size={13} /> : <IconList size={13} />}
|
|
903
936
|
</button>
|
|
@@ -907,7 +940,7 @@ function WorkspaceFilesView({
|
|
|
907
940
|
type="button"
|
|
908
941
|
className="dsh-files-tool-btn"
|
|
909
942
|
onClick={handleExpandAll}
|
|
910
|
-
title="
|
|
943
|
+
title="展开全部目录"
|
|
911
944
|
>
|
|
912
945
|
<IconExpandAll size={13} />
|
|
913
946
|
</button>
|
|
@@ -915,7 +948,7 @@ function WorkspaceFilesView({
|
|
|
915
948
|
type="button"
|
|
916
949
|
className="dsh-files-tool-btn"
|
|
917
950
|
onClick={handleCollapseAll}
|
|
918
|
-
title="
|
|
951
|
+
title="折叠全部目录"
|
|
919
952
|
>
|
|
920
953
|
<IconCollapseAll size={13} />
|
|
921
954
|
</button>
|
|
@@ -926,9 +959,9 @@ function WorkspaceFilesView({
|
|
|
926
959
|
type="button"
|
|
927
960
|
className={`dsh-files-tool-btn ${showHidden ? 'is-active' : ''}`}
|
|
928
961
|
onClick={() => setShowHidden(!showHidden)}
|
|
929
|
-
title={showHidden ? '
|
|
962
|
+
title={showHidden ? '隐藏以点开头的文件' : '显示以点开头的文件'}
|
|
930
963
|
>
|
|
931
|
-
<span style={{ fontSize:
|
|
964
|
+
<span style={{ fontSize: 10.5, fontWeight: 700 }}>.*</span>
|
|
932
965
|
</button>
|
|
933
966
|
<button
|
|
934
967
|
type="button"
|
|
@@ -942,7 +975,7 @@ function WorkspaceFilesView({
|
|
|
942
975
|
</div>
|
|
943
976
|
</div>
|
|
944
977
|
|
|
945
|
-
{/* Error
|
|
978
|
+
{/* Error Alert */}
|
|
946
979
|
{error && (
|
|
947
980
|
<div className="dsh-files-banner-error">
|
|
948
981
|
<span>{error}</span>
|
|
@@ -952,24 +985,24 @@ function WorkspaceFilesView({
|
|
|
952
985
|
</div>
|
|
953
986
|
)}
|
|
954
987
|
|
|
955
|
-
{/*
|
|
988
|
+
{/* 3. High-Performance Virtualized-Ready Content */}
|
|
956
989
|
<div className="dsh-files-body">
|
|
957
990
|
{loading && !data ? (
|
|
958
991
|
<div className="dsh-files-centered">
|
|
959
992
|
<div className="dsh-files-spinner" />
|
|
960
|
-
<span
|
|
993
|
+
<span>正在索引工作区文件…</span>
|
|
961
994
|
</div>
|
|
962
995
|
) : isSearchActive && flatSearchResults.length === 0 ? (
|
|
963
996
|
<div className="dsh-files-empty">
|
|
964
|
-
<div className="dsh-files-empty-icon"><IconFolder size={
|
|
965
|
-
<div className="dsh-files-empty-title"
|
|
966
|
-
<div className="dsh-files-empty-detail"
|
|
997
|
+
<div className="dsh-files-empty-icon"><IconFolder size={28} /></div>
|
|
998
|
+
<div className="dsh-files-empty-title">未匹配到相关文件</div>
|
|
999
|
+
<div className="dsh-files-empty-detail">请尝试更换关键字或按 Esc 恢复完整目录树。</div>
|
|
967
1000
|
</div>
|
|
968
1001
|
) : !isSearchActive && (!data || data.items.length === 0) ? (
|
|
969
1002
|
<div className="dsh-files-empty">
|
|
970
|
-
<div className="dsh-files-empty-icon"><IconFolder size={
|
|
1003
|
+
<div className="dsh-files-empty-icon"><IconFolder size={28} /></div>
|
|
971
1004
|
<div className="dsh-files-empty-title">工作区为空</div>
|
|
972
|
-
<div className="dsh-files-empty-detail"
|
|
1005
|
+
<div className="dsh-files-empty-detail">当前工作目录下暂无可展示的文件。</div>
|
|
973
1006
|
</div>
|
|
974
1007
|
) : isSearchActive && searchViewMode === 'flat' ? (
|
|
975
1008
|
/* ── 扁平搜索结果列表 ── */
|
|
@@ -977,13 +1010,13 @@ function WorkspaceFilesView({
|
|
|
977
1010
|
<div className="dsh-files-search-header">
|
|
978
1011
|
<span className="dsh-files-search-count">
|
|
979
1012
|
找到 {flatSearchResults.length} 个匹配项
|
|
980
|
-
{flatSearchResults.length > 150 ? '
|
|
1013
|
+
{flatSearchResults.length > 150 ? '(已展示前 150 项)' : ''}
|
|
981
1014
|
</span>
|
|
982
1015
|
<button
|
|
983
1016
|
type="button"
|
|
984
1017
|
className="dsh-files-view-toggle-btn"
|
|
985
1018
|
onClick={() => setSearchViewMode('tree')}
|
|
986
|
-
title="
|
|
1019
|
+
title="切换为树形视图"
|
|
987
1020
|
>
|
|
988
1021
|
<IconTree size={12} />
|
|
989
1022
|
<span>切为树形</span>
|
|
@@ -1001,7 +1034,7 @@ function WorkspaceFilesView({
|
|
|
1001
1034
|
onOpenFile(item.path)
|
|
1002
1035
|
}
|
|
1003
1036
|
}}
|
|
1004
|
-
title={item.isDirectory ?
|
|
1037
|
+
title={item.isDirectory ? `点击定位并展开目录: ${item.path}/` : `点击预览打开: ${item.path}`}
|
|
1005
1038
|
>
|
|
1006
1039
|
<span className="dsh-files-flat-icon">
|
|
1007
1040
|
{item.isDirectory ? <IconFolder size={14} /> : <FileGlyphIcon filename={item.name} size={14} />}
|
|
@@ -1009,7 +1042,7 @@ function WorkspaceFilesView({
|
|
|
1009
1042
|
<div className="dsh-files-flat-info">
|
|
1010
1043
|
<div className="dsh-files-flat-top">
|
|
1011
1044
|
<span className="dsh-files-flat-name">
|
|
1012
|
-
<HighlightText text={item.name} query={
|
|
1045
|
+
<HighlightText text={item.name} query={deferredQuery} />
|
|
1013
1046
|
{item.isDirectory && '/'}
|
|
1014
1047
|
</span>
|
|
1015
1048
|
{item.size !== undefined && (
|
|
@@ -1030,16 +1063,16 @@ function WorkspaceFilesView({
|
|
|
1030
1063
|
type="button"
|
|
1031
1064
|
className="dsh-files-action-btn dsh-files-action-at"
|
|
1032
1065
|
onClick={e => handleMention(e, item)}
|
|
1033
|
-
title={
|
|
1066
|
+
title={`@ 引用${item.isDirectory ? '目录' : '文件'}到会话`}
|
|
1034
1067
|
>
|
|
1035
|
-
<IconAt size={
|
|
1068
|
+
<IconAt size={12} />
|
|
1036
1069
|
<span className="dsh-files-action-at-text">@引用</span>
|
|
1037
1070
|
</button>
|
|
1038
1071
|
<button
|
|
1039
1072
|
type="button"
|
|
1040
1073
|
className="dsh-files-action-btn"
|
|
1041
1074
|
onClick={e => handleRevealInExplorer(e, item)}
|
|
1042
|
-
title=
|
|
1075
|
+
title="在文件资源管理器中显示"
|
|
1043
1076
|
>
|
|
1044
1077
|
<IconExplorer size={12} />
|
|
1045
1078
|
</button>
|
|
@@ -1047,7 +1080,7 @@ function WorkspaceFilesView({
|
|
|
1047
1080
|
type="button"
|
|
1048
1081
|
className="dsh-files-action-btn"
|
|
1049
1082
|
onClick={e => handleOpenDefaultApp(e, item)}
|
|
1050
|
-
title=
|
|
1083
|
+
title="用系统默认应用打开"
|
|
1051
1084
|
>
|
|
1052
1085
|
<IconExternalApp size={12} />
|
|
1053
1086
|
</button>
|
|
@@ -1062,19 +1095,14 @@ function WorkspaceFilesView({
|
|
|
1062
1095
|
</div>
|
|
1063
1096
|
</div>
|
|
1064
1097
|
))}
|
|
1065
|
-
{flatSearchResults.length > 150 && (
|
|
1066
|
-
<div style={{ padding: '10px 14px', fontSize: 11, textAlign: 'center', color: 'var(--dsw-alias-label-tertiary, #8b93a1)', borderTop: '1px dashed var(--dsw-alias-border-l2, #e5e7eb)' }}>
|
|
1067
|
-
匹配项较多,已限制展示前 150 项以保证流畅度,请输入更详细关键字
|
|
1068
|
-
</div>
|
|
1069
|
-
)}
|
|
1070
1098
|
</div>
|
|
1071
1099
|
</div>
|
|
1072
1100
|
) : (
|
|
1073
|
-
/* ──
|
|
1101
|
+
/* ── 扁平单层映射可视树(高帧率 0 递归开销) ── */
|
|
1074
1102
|
<div className="dsh-files-tree">
|
|
1075
1103
|
{isSearchActive && (
|
|
1076
1104
|
<div className="dsh-files-search-header">
|
|
1077
|
-
<span className="dsh-files-search-count"
|
|
1105
|
+
<span className="dsh-files-search-count">树形匹配(共 {visibleFlatRows.length} 项)</span>
|
|
1078
1106
|
<button
|
|
1079
1107
|
type="button"
|
|
1080
1108
|
className="dsh-files-view-toggle-btn"
|
|
@@ -1086,38 +1114,24 @@ function WorkspaceFilesView({
|
|
|
1086
1114
|
</button>
|
|
1087
1115
|
</div>
|
|
1088
1116
|
)}
|
|
1089
|
-
{
|
|
1090
|
-
<
|
|
1091
|
-
key={
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
isSearchMode={isSearchActive}
|
|
1096
|
-
expandedPaths={isSearchActive ? searchExpandedPaths : expandedPaths}
|
|
1097
|
-
onToggleExpand={(path: string) => {
|
|
1098
|
-
if (isSearchActive) {
|
|
1099
|
-
setSearchExpandedPaths(prev => {
|
|
1100
|
-
const next = new Set(prev)
|
|
1101
|
-
if (next.has(path)) next.delete(path)
|
|
1102
|
-
else next.add(path)
|
|
1103
|
-
return next
|
|
1104
|
-
})
|
|
1105
|
-
} else {
|
|
1106
|
-
handleToggleExpand(path)
|
|
1107
|
-
}
|
|
1108
|
-
}}
|
|
1117
|
+
{visibleFlatRows.map(row => (
|
|
1118
|
+
<FileTreeRow
|
|
1119
|
+
key={row.id}
|
|
1120
|
+
row={row}
|
|
1121
|
+
query={deferredQuery}
|
|
1122
|
+
onToggleExpand={handleToggleExpand}
|
|
1109
1123
|
onOpenFile={onOpenFile}
|
|
1110
1124
|
onMention={handleMention}
|
|
1111
|
-
onCopyPath={handleCopyPath}
|
|
1112
1125
|
onRevealInExplorer={handleRevealInExplorer}
|
|
1113
1126
|
onOpenDefaultApp={handleOpenDefaultApp}
|
|
1127
|
+
onCopyPath={handleCopyPath}
|
|
1114
1128
|
/>
|
|
1115
1129
|
))}
|
|
1116
1130
|
</div>
|
|
1117
1131
|
)}
|
|
1118
1132
|
</div>
|
|
1119
1133
|
|
|
1120
|
-
{/* Toast
|
|
1134
|
+
{/* Floating Animated Toast */}
|
|
1121
1135
|
{toast && (
|
|
1122
1136
|
<div className="dsh-files-toast">
|
|
1123
1137
|
<IconCheck size={12} />
|
|
@@ -1128,165 +1142,141 @@ function WorkspaceFilesView({
|
|
|
1128
1142
|
)
|
|
1129
1143
|
}
|
|
1130
1144
|
|
|
1131
|
-
/* ── Tree
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
depth: number
|
|
1145
|
+
/* ── Memoized Flat Tree Row Component ── */
|
|
1146
|
+
interface FileTreeRowProps {
|
|
1147
|
+
row: VisibleFlatRow
|
|
1135
1148
|
query: string
|
|
1136
|
-
isSearchMode: boolean
|
|
1137
|
-
expandedPaths: Set<string>
|
|
1138
1149
|
onToggleExpand: (path: string) => void
|
|
1139
1150
|
onOpenFile: (path: string) => void
|
|
1140
1151
|
onMention: (e: React.MouseEvent, item: FileItem) => void
|
|
1141
|
-
onCopyPath: (e: React.MouseEvent, path: string) => void
|
|
1142
1152
|
onRevealInExplorer: (e: React.MouseEvent, item: FileItem) => void
|
|
1143
1153
|
onOpenDefaultApp: (e: React.MouseEvent, item: FileItem) => void
|
|
1144
|
-
|
|
1154
|
+
onCopyPath: (e: React.MouseEvent, path: string) => void
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const FileTreeRow = memo(function FileTreeRow(props: FileTreeRowProps): JSX.Element {
|
|
1145
1158
|
const {
|
|
1146
|
-
|
|
1147
|
-
depth,
|
|
1159
|
+
row,
|
|
1148
1160
|
query,
|
|
1149
|
-
isSearchMode,
|
|
1150
|
-
expandedPaths,
|
|
1151
1161
|
onToggleExpand,
|
|
1152
1162
|
onOpenFile,
|
|
1153
1163
|
onMention,
|
|
1154
|
-
onCopyPath,
|
|
1155
1164
|
onRevealInExplorer,
|
|
1156
1165
|
onOpenDefaultApp,
|
|
1166
|
+
onCopyPath,
|
|
1157
1167
|
} = props
|
|
1158
|
-
const isExpanded = expandedPaths.has(item.path)
|
|
1159
1168
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1169
|
+
const indentPx = row.depth * 14 + 8
|
|
1170
|
+
|
|
1171
|
+
if (row.isDirectory) {
|
|
1162
1172
|
return (
|
|
1163
|
-
<div
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
>
|
|
1170
|
-
<
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
</
|
|
1173
|
+
<div
|
|
1174
|
+
className="dsh-files-dir-row"
|
|
1175
|
+
data-tree-path={row.path}
|
|
1176
|
+
style={{ paddingLeft: `${indentPx}px` }}
|
|
1177
|
+
onClick={() => onToggleExpand(row.path)}
|
|
1178
|
+
>
|
|
1179
|
+
<span className={`dsh-files-chevron-wrap ${row.isExpanded ? 'is-expanded' : ''}`}>
|
|
1180
|
+
<IconChevronRight size={11} />
|
|
1181
|
+
</span>
|
|
1182
|
+
<span className="dsh-files-folder-icon">
|
|
1183
|
+
<IconFolder size={14} open={row.isExpanded} />
|
|
1184
|
+
</span>
|
|
1185
|
+
<span className="dsh-files-folder-name" title={row.path}>
|
|
1186
|
+
<HighlightText text={row.name} query={query} />
|
|
1187
|
+
</span>
|
|
1188
|
+
<span className="dsh-files-count-badge">{row.childCount}</span>
|
|
1189
|
+
|
|
1190
|
+
<div className="dsh-files-row-actions">
|
|
1191
|
+
<button
|
|
1192
|
+
type="button"
|
|
1193
|
+
className="dsh-files-action-btn dsh-files-action-at"
|
|
1194
|
+
onClick={e => onMention(e, row.item)}
|
|
1195
|
+
title="一键 @ 引用此目录到会话"
|
|
1196
|
+
>
|
|
1197
|
+
<IconAt size={12} />
|
|
1198
|
+
<span className="dsh-files-action-at-text">@引用</span>
|
|
1199
|
+
</button>
|
|
1200
|
+
<button
|
|
1201
|
+
type="button"
|
|
1202
|
+
className="dsh-files-action-btn"
|
|
1203
|
+
onClick={e => onRevealInExplorer(e, row.item)}
|
|
1204
|
+
title="在文件资源管理器中打开此目录"
|
|
1205
|
+
>
|
|
1206
|
+
<IconExplorer size={12} />
|
|
1207
|
+
</button>
|
|
1208
|
+
<button
|
|
1209
|
+
type="button"
|
|
1210
|
+
className="dsh-files-action-btn"
|
|
1211
|
+
onClick={e => onOpenDefaultApp(e, row.item)}
|
|
1212
|
+
title="用系统默认应用打开此目录"
|
|
1213
|
+
>
|
|
1214
|
+
<IconExternalApp size={12} />
|
|
1215
|
+
</button>
|
|
1216
|
+
<button
|
|
1217
|
+
type="button"
|
|
1218
|
+
className="dsh-files-action-btn"
|
|
1219
|
+
onClick={e => onCopyPath(e, row.path)}
|
|
1220
|
+
title="复制目录路径"
|
|
1221
|
+
>
|
|
1222
|
+
<IconCopy size={12} />
|
|
1223
|
+
</button>
|
|
1214
1224
|
</div>
|
|
1215
|
-
{isExpanded && item.children && (
|
|
1216
|
-
<div className="dsh-files-children">
|
|
1217
|
-
{item.children.map(child => (
|
|
1218
|
-
<FileTreeNode
|
|
1219
|
-
key={child.path}
|
|
1220
|
-
item={child}
|
|
1221
|
-
depth={depth + 1}
|
|
1222
|
-
query={query}
|
|
1223
|
-
isSearchMode={isSearchMode}
|
|
1224
|
-
expandedPaths={expandedPaths}
|
|
1225
|
-
onToggleExpand={onToggleExpand}
|
|
1226
|
-
onOpenFile={onOpenFile}
|
|
1227
|
-
onMention={onMention}
|
|
1228
|
-
onCopyPath={onCopyPath}
|
|
1229
|
-
onRevealInExplorer={onRevealInExplorer}
|
|
1230
|
-
onOpenDefaultApp={onOpenDefaultApp}
|
|
1231
|
-
/>
|
|
1232
|
-
))}
|
|
1233
|
-
</div>
|
|
1234
|
-
)}
|
|
1235
1225
|
</div>
|
|
1236
1226
|
)
|
|
1237
1227
|
}
|
|
1238
1228
|
|
|
1239
|
-
// File item row
|
|
1240
1229
|
return (
|
|
1241
1230
|
<div
|
|
1242
1231
|
className="dsh-files-row"
|
|
1243
|
-
data-tree-path={
|
|
1244
|
-
style={{ paddingLeft: `${
|
|
1245
|
-
onClick={() => onOpenFile(
|
|
1246
|
-
title={
|
|
1232
|
+
data-tree-path={row.path}
|
|
1233
|
+
style={{ paddingLeft: `${indentPx}px` }}
|
|
1234
|
+
onClick={() => onOpenFile(row.path)}
|
|
1235
|
+
title={`点击打开文件: ${row.path}`}
|
|
1247
1236
|
>
|
|
1248
|
-
<
|
|
1237
|
+
<span className="dsh-files-file-glyph">
|
|
1238
|
+
<FileGlyphIcon filename={row.name} size={14} />
|
|
1239
|
+
</span>
|
|
1249
1240
|
<span className="dsh-files-name">
|
|
1250
|
-
<HighlightText text={
|
|
1241
|
+
<HighlightText text={row.name} query={query} />
|
|
1251
1242
|
</span>
|
|
1252
1243
|
|
|
1253
|
-
{
|
|
1254
|
-
<span className="dsh-files-size" title={`大小: ${
|
|
1255
|
-
{formatFileSize(
|
|
1244
|
+
{row.size !== undefined && (
|
|
1245
|
+
<span className="dsh-files-size" title={`大小: ${row.size} 字节`}>
|
|
1246
|
+
{formatFileSize(row.size)}
|
|
1256
1247
|
</span>
|
|
1257
1248
|
)}
|
|
1258
1249
|
|
|
1259
|
-
{/* Row Hover Actions */}
|
|
1260
1250
|
<div className="dsh-files-row-actions">
|
|
1261
1251
|
<button
|
|
1262
1252
|
type="button"
|
|
1263
1253
|
className="dsh-files-action-btn dsh-files-action-at"
|
|
1264
|
-
onClick={e => onMention(e, item)}
|
|
1265
|
-
title="一键 @
|
|
1254
|
+
onClick={e => onMention(e, row.item)}
|
|
1255
|
+
title="一键 @ 引用此文件到会话"
|
|
1266
1256
|
>
|
|
1267
|
-
<IconAt size={
|
|
1257
|
+
<IconAt size={12} />
|
|
1268
1258
|
<span className="dsh-files-action-at-text">@引用</span>
|
|
1269
1259
|
</button>
|
|
1270
1260
|
<button
|
|
1271
1261
|
type="button"
|
|
1272
1262
|
className="dsh-files-action-btn"
|
|
1273
|
-
onClick={e => onRevealInExplorer(e, item)}
|
|
1274
|
-
title="
|
|
1263
|
+
onClick={e => onRevealInExplorer(e, row.item)}
|
|
1264
|
+
title="在文件资源管理器中定位"
|
|
1275
1265
|
>
|
|
1276
1266
|
<IconExplorer size={12} />
|
|
1277
1267
|
</button>
|
|
1278
1268
|
<button
|
|
1279
1269
|
type="button"
|
|
1280
1270
|
className="dsh-files-action-btn"
|
|
1281
|
-
onClick={e => onOpenDefaultApp(e, item)}
|
|
1282
|
-
title="
|
|
1271
|
+
onClick={e => onOpenDefaultApp(e, row.item)}
|
|
1272
|
+
title="用系统默认应用打开"
|
|
1283
1273
|
>
|
|
1284
1274
|
<IconExternalApp size={12} />
|
|
1285
1275
|
</button>
|
|
1286
1276
|
<button
|
|
1287
1277
|
type="button"
|
|
1288
1278
|
className="dsh-files-action-btn"
|
|
1289
|
-
onClick={e => onCopyPath(e,
|
|
1279
|
+
onClick={e => onCopyPath(e, row.path)}
|
|
1290
1280
|
title="复制相对路径"
|
|
1291
1281
|
>
|
|
1292
1282
|
<IconCopy size={12} />
|
|
@@ -1294,18 +1284,25 @@ function FileTreeNode(props: {
|
|
|
1294
1284
|
</div>
|
|
1295
1285
|
</div>
|
|
1296
1286
|
)
|
|
1297
|
-
}
|
|
1287
|
+
}, (prev, next) => {
|
|
1288
|
+
return (
|
|
1289
|
+
prev.row.id === next.row.id &&
|
|
1290
|
+
prev.row.isExpanded === next.row.isExpanded &&
|
|
1291
|
+
prev.row.depth === next.row.depth &&
|
|
1292
|
+
prev.row.childCount === next.row.childCount &&
|
|
1293
|
+
prev.query === next.query
|
|
1294
|
+
)
|
|
1295
|
+
})
|
|
1298
1296
|
|
|
1299
1297
|
/* ── Helpers ── */
|
|
1300
1298
|
function HighlightText({ text, query }: { text: string; query: string }): JSX.Element {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
const index = text.toLowerCase().indexOf(q)
|
|
1299
|
+
if (!query) return <span>{text}</span>
|
|
1300
|
+
const index = text.toLowerCase().indexOf(query)
|
|
1304
1301
|
if (index === -1) return <span>{text}</span>
|
|
1305
1302
|
|
|
1306
1303
|
const before = text.slice(0, index)
|
|
1307
|
-
const match = text.slice(index, index +
|
|
1308
|
-
const after = text.slice(index +
|
|
1304
|
+
const match = text.slice(index, index + query.length)
|
|
1305
|
+
const after = text.slice(index + query.length)
|
|
1309
1306
|
|
|
1310
1307
|
return (
|
|
1311
1308
|
<span>
|
|
@@ -1324,12 +1321,6 @@ function formatFileSize(bytes: number): string {
|
|
|
1324
1321
|
return `${mb.toFixed(1)} MB`
|
|
1325
1322
|
}
|
|
1326
1323
|
|
|
1327
|
-
/**
|
|
1328
|
-
* 按照 DSH 官方规范格式化引用路径:
|
|
1329
|
-
* - 纯路径无空格:@path
|
|
1330
|
-
* - 路径含空格:@"path"
|
|
1331
|
-
* - 目录末尾保留斜杠:@folder/ 或 @"folder/"
|
|
1332
|
-
*/
|
|
1333
1324
|
function formatMentionText(path: string, isDirectory = false): string {
|
|
1334
1325
|
const cleanPath = path.replace(/\\/g, '/').replace(/^\.?\//, '')
|
|
1335
1326
|
const finalPath = isDirectory ? (cleanPath.endsWith('/') ? cleanPath : `${cleanPath}/`) : cleanPath
|
|
@@ -1362,11 +1353,7 @@ function saveStoredExpandedPaths(workspaceRoot: string | undefined, paths: Set<s
|
|
|
1362
1353
|
} catch {}
|
|
1363
1354
|
}
|
|
1364
1355
|
|
|
1365
|
-
/**
|
|
1366
|
-
* 从当前 DOM 的 React Fiber 树或 Cordis RootContext 中提取当前活跃输入框的 SessionInputShell 实例
|
|
1367
|
-
*/
|
|
1368
1356
|
function findComposerShell(ctx: Context | null, sessionId?: string): any {
|
|
1369
|
-
// 1. 优先从 DOM 的 React Fiber 提取(直接命中当前活跃输入框的组件 props)
|
|
1370
1357
|
try {
|
|
1371
1358
|
const composerDom = document.querySelector<HTMLElement>(
|
|
1372
1359
|
'[data-composer-input="true"], [data-composer-input], [data-composer-card], div[role="textbox"][contenteditable="true"]'
|
|
@@ -1391,11 +1378,8 @@ function findComposerShell(ctx: Context | null, sessionId?: string): any {
|
|
|
1391
1378
|
}
|
|
1392
1379
|
}
|
|
1393
1380
|
}
|
|
1394
|
-
} catch
|
|
1395
|
-
console.debug('[dsh-workspace-files] 从 Fiber 查找 keyboard 失败:', e)
|
|
1396
|
-
}
|
|
1381
|
+
} catch {}
|
|
1397
1382
|
|
|
1398
|
-
// 2. 从 Cordis RootContext 查找
|
|
1399
1383
|
if (ctx) {
|
|
1400
1384
|
try {
|
|
1401
1385
|
const root: any = (ctx as any).root || ctx
|
|
@@ -1411,30 +1395,22 @@ function findComposerShell(ctx: Context | null, sessionId?: string): any {
|
|
|
1411
1395
|
return values[values.length - 1]
|
|
1412
1396
|
}
|
|
1413
1397
|
}
|
|
1414
|
-
} catch
|
|
1415
|
-
console.debug('[dsh-workspace-files] 从 RootContext 查找 shell 失败:', e)
|
|
1416
|
-
}
|
|
1398
|
+
} catch {}
|
|
1417
1399
|
}
|
|
1418
1400
|
|
|
1419
1401
|
return null
|
|
1420
1402
|
}
|
|
1421
1403
|
|
|
1422
|
-
/**
|
|
1423
|
-
* 尝试向会话输入框插入原生的 ReferenceChipNode(官方胶囊 Tag 卡片)
|
|
1424
|
-
*/
|
|
1425
1404
|
function tryInsertNativeReferenceChip(ctx: Context | null, sessionId: string | undefined, item: FileItem): boolean {
|
|
1426
1405
|
if (typeof document === 'undefined') return false
|
|
1427
1406
|
try {
|
|
1428
|
-
// 聚焦输入框
|
|
1429
1407
|
const composerDom = document.querySelector<HTMLElement>(
|
|
1430
1408
|
'[data-composer-input="true"], [data-composer-input], [data-composer-card] [contenteditable="true"], div[role="textbox"][contenteditable="true"]'
|
|
1431
1409
|
)
|
|
1432
1410
|
if (composerDom) composerDom.focus()
|
|
1433
1411
|
|
|
1434
1412
|
const shell = findComposerShell(ctx, sessionId)
|
|
1435
|
-
if (!shell)
|
|
1436
|
-
return false
|
|
1437
|
-
}
|
|
1413
|
+
if (!shell) return false
|
|
1438
1414
|
|
|
1439
1415
|
const cleanPath = item.path.replace(/\\/g, '/').replace(/^\.?\//, '')
|
|
1440
1416
|
const mention = item.isDirectory ? (cleanPath.endsWith('/') ? `@${cleanPath}` : `@${cleanPath}/`) : `@${cleanPath}`
|
|
@@ -1453,7 +1429,6 @@ function tryInsertNativeReferenceChip(ctx: Context | null, sessionId: string | u
|
|
|
1453
1429
|
let start = caret
|
|
1454
1430
|
const end = caret
|
|
1455
1431
|
|
|
1456
|
-
// 智能消除:若当前光标前是 '@',回退一个字符将其吃掉替换
|
|
1457
1432
|
if (start > 0 && text[start - 1] === '@') {
|
|
1458
1433
|
start = start - 1
|
|
1459
1434
|
}
|
|
@@ -1464,33 +1439,23 @@ function tryInsertNativeReferenceChip(ctx: Context | null, sessionId: string | u
|
|
|
1464
1439
|
end,
|
|
1465
1440
|
}
|
|
1466
1441
|
|
|
1467
|
-
// 优先调用原生 shell.insertReference 插入 Chip Tag
|
|
1468
1442
|
if (typeof shell.insertReference === 'function') {
|
|
1469
1443
|
const ok = shell.insertReference(reference, span)
|
|
1470
1444
|
if (ok) return true
|
|
1471
1445
|
}
|
|
1472
1446
|
|
|
1473
|
-
// 备用:尝试通过 session actx 派发事件
|
|
1474
1447
|
const actx = shell.deps?.actx
|
|
1475
1448
|
if (actx && typeof actx.bail === 'function') {
|
|
1476
1449
|
const ok = actx.bail(actx, 'slash/input-insert-reference', { reference, span })
|
|
1477
1450
|
if (ok === true) return true
|
|
1478
1451
|
}
|
|
1479
|
-
} catch
|
|
1480
|
-
console.debug('[dsh-workspace-files] 插入原生 Chip 异常:', err)
|
|
1481
|
-
}
|
|
1452
|
+
} catch {}
|
|
1482
1453
|
return false
|
|
1483
1454
|
}
|
|
1484
1455
|
|
|
1485
|
-
/**
|
|
1486
|
-
* 将引用文本注入到 DSH 当前会话的主输入框。
|
|
1487
|
-
* 兼容 DSH 官方基于 Lexical 的 ComposerContentEditable (div[data-composer-input])
|
|
1488
|
-
* 以及传统的 textarea / input,并自动消除用户手动输入的冗余 '@'。
|
|
1489
|
-
*/
|
|
1490
1456
|
function insertMentionToComposer(mentionText: string): boolean {
|
|
1491
1457
|
if (typeof document === 'undefined') return false
|
|
1492
1458
|
|
|
1493
|
-
// 1. 查找 DSH 官方输入框(Lexical contenteditable 容器优先)
|
|
1494
1459
|
const candidates = [
|
|
1495
1460
|
document.querySelector<HTMLElement>('[data-composer-input="true"]'),
|
|
1496
1461
|
document.querySelector<HTMLElement>('[data-composer-input]'),
|
|
@@ -1504,7 +1469,6 @@ function insertMentionToComposer(mentionText: string): boolean {
|
|
|
1504
1469
|
|
|
1505
1470
|
target.focus()
|
|
1506
1471
|
|
|
1507
|
-
// 策略 A: contenteditable (DSH 官方 Lexical 编辑器)
|
|
1508
1472
|
if (target.getAttribute('contenteditable') === 'true' || target.isContentEditable) {
|
|
1509
1473
|
const sel = window.getSelection()
|
|
1510
1474
|
if (sel) {
|
|
@@ -1518,11 +1482,10 @@ function insertMentionToComposer(mentionText: string): boolean {
|
|
|
1518
1482
|
if (!isInTarget) {
|
|
1519
1483
|
const range = document.createRange()
|
|
1520
1484
|
range.selectNodeContents(target)
|
|
1521
|
-
range.collapse(false)
|
|
1485
|
+
range.collapse(false)
|
|
1522
1486
|
sel.removeAllRanges()
|
|
1523
1487
|
sel.addRange(range)
|
|
1524
1488
|
} else {
|
|
1525
|
-
// 智能消除:若光标前一个字符是已键入的 '@',将其纳入选区进行覆盖替换,避免出现 '@@file'
|
|
1526
1489
|
const range = sel.getRangeAt(0)
|
|
1527
1490
|
if (range.collapsed && range.startContainer.nodeType === Node.TEXT_NODE) {
|
|
1528
1491
|
const nodeText = range.startContainer.textContent || ''
|
|
@@ -1536,7 +1499,6 @@ function insertMentionToComposer(mentionText: string): boolean {
|
|
|
1536
1499
|
}
|
|
1537
1500
|
}
|
|
1538
1501
|
|
|
1539
|
-
// 原生 execCommand('insertText') 会直接触发 Lexical 的 input/beforeinput 事件流并更新 React 状态
|
|
1540
1502
|
let success = false
|
|
1541
1503
|
try {
|
|
1542
1504
|
success = document.execCommand('insertText', false, mentionText)
|
|
@@ -1558,13 +1520,11 @@ function insertMentionToComposer(mentionText: string): boolean {
|
|
|
1558
1520
|
return success
|
|
1559
1521
|
}
|
|
1560
1522
|
|
|
1561
|
-
// 策略 B: 普通 textarea / input
|
|
1562
1523
|
if (target instanceof HTMLTextAreaElement || target instanceof HTMLInputElement) {
|
|
1563
1524
|
let start = target.selectionStart ?? target.value.length
|
|
1564
1525
|
const end = target.selectionEnd ?? target.value.length
|
|
1565
1526
|
const prev = target.value
|
|
1566
1527
|
|
|
1567
|
-
// 智能消除:若前面紧随 '@',覆盖它
|
|
1568
1528
|
if (start > 0 && prev[start - 1] === '@') {
|
|
1569
1529
|
start = start - 1
|
|
1570
1530
|
}
|
|
@@ -1628,18 +1588,23 @@ async function call<T = any>(method: string, payload: Record<string, unknown> =
|
|
|
1628
1588
|
return envelope.value
|
|
1629
1589
|
}
|
|
1630
1590
|
|
|
1631
|
-
/* ── CSS
|
|
1632
|
-
function
|
|
1633
|
-
|
|
1634
|
-
|
|
1591
|
+
/* ── Singleton Dynamic CSS Injection (Zero React re-evaluation) ── */
|
|
1592
|
+
function ensureFilesStyle() {
|
|
1593
|
+
if (typeof document === 'undefined') return
|
|
1594
|
+
const id = 'dsh-workspace-files-core-style'
|
|
1595
|
+
if (document.getElementById(id)) return
|
|
1596
|
+
|
|
1597
|
+
const style = document.createElement('style')
|
|
1598
|
+
style.id = id
|
|
1599
|
+
style.textContent = `
|
|
1635
1600
|
.dsh-files-root {
|
|
1636
1601
|
display: flex;
|
|
1637
1602
|
flex-direction: column;
|
|
1638
1603
|
height: 100%;
|
|
1639
1604
|
min-height: 0;
|
|
1640
1605
|
box-sizing: border-box;
|
|
1641
|
-
background: var(--dsw-alias-
|
|
1642
|
-
color: var(--dsw-alias-label-primary
|
|
1606
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
1607
|
+
color: var(--dsw-alias-label-primary);
|
|
1643
1608
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1644
1609
|
font-size: 12px;
|
|
1645
1610
|
line-height: 1.4;
|
|
@@ -1655,7 +1620,8 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1655
1620
|
justify-content: space-between;
|
|
1656
1621
|
gap: 8px;
|
|
1657
1622
|
padding: 8px 12px;
|
|
1658
|
-
border-bottom:
|
|
1623
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l1);
|
|
1624
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
1659
1625
|
}
|
|
1660
1626
|
.dsh-files-header-info {
|
|
1661
1627
|
flex: 1;
|
|
@@ -1669,33 +1635,34 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1669
1635
|
.dsh-files-title {
|
|
1670
1636
|
font-weight: 600;
|
|
1671
1637
|
font-size: 13px;
|
|
1672
|
-
color: var(--dsw-alias-label-primary
|
|
1638
|
+
color: var(--dsw-alias-label-primary);
|
|
1673
1639
|
}
|
|
1674
1640
|
.dsh-files-stats-badge {
|
|
1675
1641
|
font-size: 10px;
|
|
1676
1642
|
font-weight: 500;
|
|
1677
|
-
color: var(--dsw-alias-label-tertiary
|
|
1678
|
-
background: var(--dsw-alias-
|
|
1679
|
-
padding: 1px
|
|
1643
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1644
|
+
background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);
|
|
1645
|
+
padding: 1px 7px;
|
|
1680
1646
|
border-radius: 99px;
|
|
1681
1647
|
}
|
|
1682
1648
|
.dsh-files-root-path {
|
|
1683
1649
|
font-size: 11px;
|
|
1684
|
-
color: var(--dsw-alias-label-tertiary
|
|
1650
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1685
1651
|
overflow: hidden;
|
|
1686
1652
|
text-overflow: ellipsis;
|
|
1687
1653
|
white-space: nowrap;
|
|
1688
|
-
margin-top:
|
|
1654
|
+
margin-top: 2px;
|
|
1689
1655
|
}
|
|
1690
1656
|
|
|
1691
|
-
/* ── Toolbar &
|
|
1657
|
+
/* ── Toolbar & Pure White Input ── */
|
|
1692
1658
|
.dsh-files-toolbar {
|
|
1693
1659
|
display: flex;
|
|
1694
1660
|
align-items: center;
|
|
1695
1661
|
gap: 6px;
|
|
1696
|
-
padding: 6px
|
|
1697
|
-
border-bottom:
|
|
1698
|
-
background: var(--dsw-alias-
|
|
1662
|
+
padding: 6px 10px;
|
|
1663
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l2);
|
|
1664
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
1665
|
+
flex-shrink: 0;
|
|
1699
1666
|
}
|
|
1700
1667
|
.dsh-files-search-wrap {
|
|
1701
1668
|
position: relative;
|
|
@@ -1706,70 +1673,77 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1706
1673
|
}
|
|
1707
1674
|
.dsh-files-search-icon {
|
|
1708
1675
|
position: absolute;
|
|
1709
|
-
left:
|
|
1710
|
-
color: var(--dsw-alias-label-tertiary
|
|
1676
|
+
left: 8px;
|
|
1677
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1711
1678
|
pointer-events: none;
|
|
1712
1679
|
display: flex;
|
|
1713
1680
|
}
|
|
1714
1681
|
.dsh-files-search-input {
|
|
1715
1682
|
box-sizing: border-box;
|
|
1716
1683
|
width: 100%;
|
|
1717
|
-
height:
|
|
1718
|
-
padding: 0
|
|
1719
|
-
border:
|
|
1720
|
-
border-radius:
|
|
1721
|
-
background: var(--dsw-alias-
|
|
1722
|
-
color:
|
|
1723
|
-
font-size:
|
|
1684
|
+
height: 28px;
|
|
1685
|
+
padding: 0 24px 0 28px;
|
|
1686
|
+
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
1687
|
+
border-radius: var(--dsw-radius-sm, 6px);
|
|
1688
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
1689
|
+
color: var(--dsw-alias-label-primary);
|
|
1690
|
+
font-size: 11px;
|
|
1724
1691
|
outline: none;
|
|
1725
|
-
transition:
|
|
1692
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
|
|
1726
1693
|
}
|
|
1727
1694
|
.dsh-files-search-input:focus {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-
|
|
1695
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
1696
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
1697
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent);
|
|
1731
1698
|
}
|
|
1732
1699
|
.dsh-files-clear-btn {
|
|
1733
1700
|
position: absolute;
|
|
1734
|
-
right:
|
|
1701
|
+
right: 6px;
|
|
1735
1702
|
border: none;
|
|
1736
1703
|
background: transparent;
|
|
1737
|
-
color: var(--dsw-alias-label-tertiary
|
|
1704
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1738
1705
|
cursor: pointer;
|
|
1739
1706
|
padding: 2px;
|
|
1740
1707
|
display: flex;
|
|
1708
|
+
border-radius: 50%;
|
|
1709
|
+
transition: all 0.12s ease;
|
|
1741
1710
|
}
|
|
1742
1711
|
.dsh-files-clear-btn:hover {
|
|
1743
|
-
color: var(--dsw-alias-label-primary
|
|
1712
|
+
color: var(--dsw-alias-label-primary);
|
|
1713
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1744
1714
|
}
|
|
1745
1715
|
.dsh-files-toolbar-btns {
|
|
1746
1716
|
display: flex;
|
|
1747
1717
|
align-items: center;
|
|
1748
1718
|
gap: 2px;
|
|
1719
|
+
flex-shrink: 0;
|
|
1749
1720
|
}
|
|
1750
1721
|
.dsh-files-tool-btn {
|
|
1751
1722
|
display: inline-flex;
|
|
1752
1723
|
align-items: center;
|
|
1753
1724
|
justify-content: center;
|
|
1754
|
-
width:
|
|
1755
|
-
height:
|
|
1725
|
+
width: 26px;
|
|
1726
|
+
height: 26px;
|
|
1756
1727
|
border: none;
|
|
1757
|
-
border-radius: 4px;
|
|
1728
|
+
border-radius: var(--dsw-radius-xs, 4px);
|
|
1758
1729
|
background: transparent;
|
|
1759
|
-
color: var(--dsw-alias-label-secondary
|
|
1730
|
+
color: var(--dsw-alias-label-secondary);
|
|
1760
1731
|
cursor: pointer;
|
|
1761
|
-
transition: all 0.
|
|
1732
|
+
transition: all 0.15s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1762
1733
|
}
|
|
1763
1734
|
.dsh-files-tool-btn:hover:not(:disabled) {
|
|
1764
|
-
color: var(--dsw-alias-label-primary
|
|
1765
|
-
background: var(--dsw-alias-interactive-bg-hover
|
|
1735
|
+
color: var(--dsw-alias-label-primary);
|
|
1736
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1737
|
+
}
|
|
1738
|
+
.dsh-files-tool-btn:active:not(:disabled) {
|
|
1739
|
+
transform: scale(0.94);
|
|
1766
1740
|
}
|
|
1767
1741
|
.dsh-files-tool-btn.is-active {
|
|
1768
|
-
color: var(--dsw-alias-
|
|
1769
|
-
background: var(--dsw-alias-
|
|
1742
|
+
color: var(--dsw-alias-state-business-primary);
|
|
1743
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent);
|
|
1770
1744
|
}
|
|
1771
1745
|
.dsh-files-tool-btn:disabled {
|
|
1772
|
-
opacity: 0.
|
|
1746
|
+
opacity: 0.35;
|
|
1773
1747
|
cursor: not-allowed;
|
|
1774
1748
|
}
|
|
1775
1749
|
|
|
@@ -1781,10 +1755,8 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1781
1755
|
overflow-x: hidden;
|
|
1782
1756
|
padding: 4px 0;
|
|
1783
1757
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
flex-direction: column;
|
|
1787
|
-
}
|
|
1758
|
+
|
|
1759
|
+
/* ── Directory Row (with Motion Chevron) ── */
|
|
1788
1760
|
.dsh-files-dir-row {
|
|
1789
1761
|
display: flex;
|
|
1790
1762
|
align-items: center;
|
|
@@ -1794,29 +1766,35 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1794
1766
|
cursor: pointer;
|
|
1795
1767
|
user-select: none;
|
|
1796
1768
|
position: relative;
|
|
1797
|
-
color: var(--dsw-alias-label-
|
|
1769
|
+
color: var(--dsw-alias-label-primary);
|
|
1770
|
+
border-radius: var(--dsw-radius-xs, 4px);
|
|
1771
|
+
margin: 0 4px;
|
|
1798
1772
|
transition: background 0.12s ease;
|
|
1799
1773
|
}
|
|
1800
1774
|
.dsh-files-dir-row:hover {
|
|
1801
|
-
background: var(--dsw-alias-interactive-bg-hover
|
|
1802
|
-
color: var(--dsw-alias-label-primary, inherit);
|
|
1775
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1803
1776
|
}
|
|
1804
|
-
.dsh-files-chevron {
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
display: inline-
|
|
1808
|
-
|
|
1777
|
+
.dsh-files-chevron-wrap {
|
|
1778
|
+
width: 12px;
|
|
1779
|
+
height: 12px;
|
|
1780
|
+
display: inline-flex;
|
|
1781
|
+
align-items: center;
|
|
1782
|
+
justify-content: center;
|
|
1783
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1784
|
+
transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1785
|
+
flex-shrink: 0;
|
|
1809
1786
|
}
|
|
1810
|
-
.dsh-files-chevron
|
|
1811
|
-
transform: rotate(
|
|
1787
|
+
.dsh-files-chevron-wrap.is-expanded {
|
|
1788
|
+
transform: rotate(90deg);
|
|
1812
1789
|
}
|
|
1813
1790
|
.dsh-files-folder-icon {
|
|
1814
1791
|
display: flex;
|
|
1792
|
+
flex-shrink: 0;
|
|
1815
1793
|
}
|
|
1816
1794
|
.dsh-files-folder-name {
|
|
1817
1795
|
flex: 1;
|
|
1818
1796
|
min-width: 0;
|
|
1819
|
-
font-weight:
|
|
1797
|
+
font-weight: 550;
|
|
1820
1798
|
font-size: 12px;
|
|
1821
1799
|
overflow: hidden;
|
|
1822
1800
|
text-overflow: ellipsis;
|
|
@@ -1827,8 +1805,8 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1827
1805
|
font-weight: 600;
|
|
1828
1806
|
padding: 0 5px;
|
|
1829
1807
|
border-radius: 99px;
|
|
1830
|
-
background: var(--dsw-alias-
|
|
1831
|
-
color: var(--dsw-alias-label-tertiary
|
|
1808
|
+
background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);
|
|
1809
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1832
1810
|
}
|
|
1833
1811
|
|
|
1834
1812
|
/* ── File Row ── */
|
|
@@ -1841,44 +1819,95 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1841
1819
|
cursor: pointer;
|
|
1842
1820
|
user-select: none;
|
|
1843
1821
|
position: relative;
|
|
1822
|
+
border-radius: var(--dsw-radius-xs, 4px);
|
|
1823
|
+
margin: 0 4px;
|
|
1844
1824
|
transition: background 0.12s ease;
|
|
1845
1825
|
}
|
|
1846
1826
|
.dsh-files-row:hover {
|
|
1847
|
-
background: var(--dsw-alias-interactive-bg-hover
|
|
1827
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1828
|
+
}
|
|
1829
|
+
.dsh-files-file-glyph {
|
|
1830
|
+
margin-left: 12px;
|
|
1831
|
+
display: inline-flex;
|
|
1832
|
+
align-items: center;
|
|
1833
|
+
justify-content: center;
|
|
1848
1834
|
}
|
|
1849
1835
|
.dsh-files-name {
|
|
1850
1836
|
flex: 1;
|
|
1851
1837
|
min-width: 0;
|
|
1852
1838
|
font-size: 12px;
|
|
1853
|
-
color: var(--dsw-alias-label-primary
|
|
1839
|
+
color: var(--dsw-alias-label-primary);
|
|
1854
1840
|
overflow: hidden;
|
|
1855
1841
|
text-overflow: ellipsis;
|
|
1856
1842
|
white-space: nowrap;
|
|
1857
1843
|
}
|
|
1858
1844
|
.dsh-files-size {
|
|
1859
1845
|
font-size: 10px;
|
|
1860
|
-
color: var(--dsw-alias-label-tertiary
|
|
1846
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1861
1847
|
flex-shrink: 0;
|
|
1862
1848
|
margin-right: 4px;
|
|
1863
1849
|
}
|
|
1864
1850
|
|
|
1865
|
-
/* ── Hover
|
|
1851
|
+
/* ── Micro-Interaction Hover Action Pills ── */
|
|
1866
1852
|
.dsh-files-row-actions {
|
|
1867
|
-
display:
|
|
1853
|
+
display: flex;
|
|
1868
1854
|
align-items: center;
|
|
1869
|
-
gap:
|
|
1855
|
+
gap: 2px;
|
|
1870
1856
|
flex-shrink: 0;
|
|
1857
|
+
opacity: 0;
|
|
1858
|
+
transform: translateX(4px);
|
|
1859
|
+
pointer-events: none;
|
|
1860
|
+
transition: opacity 0.15s cubic-bezier(0.16, 1, 0.3, 1), transform 0.15s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1861
|
+
margin-left: auto;
|
|
1871
1862
|
}
|
|
1872
1863
|
.dsh-files-row:hover .dsh-files-row-actions,
|
|
1873
1864
|
.dsh-files-dir-row:hover .dsh-files-row-actions,
|
|
1874
1865
|
.dsh-files-flat-row:hover .dsh-files-row-actions {
|
|
1875
|
-
|
|
1866
|
+
opacity: 1;
|
|
1867
|
+
transform: translateX(0);
|
|
1868
|
+
pointer-events: auto;
|
|
1876
1869
|
}
|
|
1877
1870
|
.dsh-files-row:hover .dsh-files-size,
|
|
1878
1871
|
.dsh-files-dir-row:hover .dsh-files-count-badge,
|
|
1879
1872
|
.dsh-files-flat-row:hover .dsh-files-size {
|
|
1880
1873
|
display: none;
|
|
1881
1874
|
}
|
|
1875
|
+
.dsh-files-action-btn {
|
|
1876
|
+
display: inline-flex;
|
|
1877
|
+
align-items: center;
|
|
1878
|
+
justify-content: center;
|
|
1879
|
+
gap: 3px;
|
|
1880
|
+
height: 20px;
|
|
1881
|
+
padding: 0 5px;
|
|
1882
|
+
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
1883
|
+
border-radius: var(--dsw-radius-xs, 3px);
|
|
1884
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
1885
|
+
color: var(--dsw-alias-label-secondary);
|
|
1886
|
+
cursor: pointer;
|
|
1887
|
+
transition: all 0.12s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1888
|
+
}
|
|
1889
|
+
.dsh-files-action-btn:hover {
|
|
1890
|
+
color: var(--dsw-alias-label-primary);
|
|
1891
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
1892
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1893
|
+
}
|
|
1894
|
+
.dsh-files-action-btn:active {
|
|
1895
|
+
transform: scale(0.92);
|
|
1896
|
+
}
|
|
1897
|
+
.dsh-files-action-at {
|
|
1898
|
+
color: var(--dsw-alias-state-business-primary);
|
|
1899
|
+
border-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 35%, transparent);
|
|
1900
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);
|
|
1901
|
+
}
|
|
1902
|
+
.dsh-files-action-at:hover {
|
|
1903
|
+
color: var(--dsw-alias-label-primary-inverted, #ffffff);
|
|
1904
|
+
background: var(--dsw-alias-state-business-primary);
|
|
1905
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
1906
|
+
}
|
|
1907
|
+
.dsh-files-action-at-text {
|
|
1908
|
+
font-size: 10px;
|
|
1909
|
+
font-weight: 600;
|
|
1910
|
+
}
|
|
1882
1911
|
|
|
1883
1912
|
/* ── Flat Search Result List ── */
|
|
1884
1913
|
.dsh-files-search-container {
|
|
@@ -1891,9 +1920,9 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1891
1920
|
justify-content: space-between;
|
|
1892
1921
|
padding: 6px 12px;
|
|
1893
1922
|
font-size: 11px;
|
|
1894
|
-
color: var(--dsw-alias-label-tertiary
|
|
1895
|
-
background: color-mix(in srgb, var(--dsw-alias-
|
|
1896
|
-
border-bottom:
|
|
1923
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1924
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 4%, transparent);
|
|
1925
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l2);
|
|
1897
1926
|
}
|
|
1898
1927
|
.dsh-files-search-count {
|
|
1899
1928
|
font-weight: 500;
|
|
@@ -1902,20 +1931,20 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1902
1931
|
display: inline-flex;
|
|
1903
1932
|
align-items: center;
|
|
1904
1933
|
gap: 4px;
|
|
1905
|
-
border:
|
|
1906
|
-
background: var(--dsw-alias-
|
|
1907
|
-
color: var(--dsw-alias-
|
|
1934
|
+
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
1935
|
+
background: var(--dsw-alias-fill-base, #ffffff);
|
|
1936
|
+
color: var(--dsw-alias-state-business-primary);
|
|
1908
1937
|
cursor: pointer;
|
|
1909
1938
|
font-size: 10.5px;
|
|
1910
1939
|
font-weight: 500;
|
|
1911
1940
|
padding: 2px 7px;
|
|
1912
|
-
border-radius: 4px;
|
|
1913
|
-
transition: all 0.12s
|
|
1941
|
+
border-radius: var(--dsw-radius-xs, 4px);
|
|
1942
|
+
transition: all 0.12s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1914
1943
|
}
|
|
1915
1944
|
.dsh-files-view-toggle-btn:hover {
|
|
1916
|
-
background: var(--dsw-alias-
|
|
1917
|
-
color: #ffffff;
|
|
1918
|
-
border-color: var(--dsw-alias-
|
|
1945
|
+
background: var(--dsw-alias-state-business-primary);
|
|
1946
|
+
color: var(--dsw-alias-label-primary-inverted, #ffffff);
|
|
1947
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
1919
1948
|
}
|
|
1920
1949
|
.dsh-files-flat-list {
|
|
1921
1950
|
padding: 4px 0;
|
|
@@ -1924,7 +1953,7 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1924
1953
|
display: flex;
|
|
1925
1954
|
align-items: center;
|
|
1926
1955
|
gap: 8px;
|
|
1927
|
-
padding:
|
|
1956
|
+
padding: 5px 12px;
|
|
1928
1957
|
cursor: pointer;
|
|
1929
1958
|
user-select: none;
|
|
1930
1959
|
position: relative;
|
|
@@ -1932,7 +1961,7 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1932
1961
|
min-height: 32px;
|
|
1933
1962
|
}
|
|
1934
1963
|
.dsh-files-flat-row:hover {
|
|
1935
|
-
background: var(--dsw-alias-interactive-bg-hover
|
|
1964
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1936
1965
|
}
|
|
1937
1966
|
.dsh-files-flat-icon {
|
|
1938
1967
|
display: flex;
|
|
@@ -1956,22 +1985,21 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1956
1985
|
.dsh-files-flat-name {
|
|
1957
1986
|
font-size: 12px;
|
|
1958
1987
|
font-weight: 500;
|
|
1959
|
-
color: var(--dsw-alias-label-primary
|
|
1988
|
+
color: var(--dsw-alias-label-primary);
|
|
1960
1989
|
overflow: hidden;
|
|
1961
1990
|
text-overflow: ellipsis;
|
|
1962
1991
|
white-space: nowrap;
|
|
1963
1992
|
}
|
|
1964
1993
|
.dsh-files-flat-parent {
|
|
1965
1994
|
font-size: 10.5px;
|
|
1966
|
-
color: var(--dsw-alias-label-tertiary
|
|
1995
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1967
1996
|
overflow: hidden;
|
|
1968
1997
|
text-overflow: ellipsis;
|
|
1969
1998
|
white-space: nowrap;
|
|
1970
|
-
opacity: 0.8;
|
|
1971
1999
|
}
|
|
1972
2000
|
.dsh-files-jump-hint {
|
|
1973
2001
|
font-size: 10px;
|
|
1974
|
-
color: var(--dsw-alias-
|
|
2002
|
+
color: var(--dsw-alias-state-business-primary);
|
|
1975
2003
|
opacity: 0;
|
|
1976
2004
|
transition: opacity 0.12s ease;
|
|
1977
2005
|
margin-right: 6px;
|
|
@@ -1979,49 +2007,16 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
1979
2007
|
white-space: nowrap;
|
|
1980
2008
|
}
|
|
1981
2009
|
.dsh-files-flat-row:hover .dsh-files-jump-hint {
|
|
1982
|
-
opacity: 0.
|
|
2010
|
+
opacity: 0.9;
|
|
1983
2011
|
}
|
|
1984
2012
|
.dsh-files-flat-row.is-directory:hover {
|
|
1985
|
-
background: color-mix(in srgb, var(--dsw-alias-
|
|
1986
|
-
}
|
|
1987
|
-
.dsh-files-action-btn {
|
|
1988
|
-
display: inline-flex;
|
|
1989
|
-
align-items: center;
|
|
1990
|
-
justify-content: center;
|
|
1991
|
-
gap: 3px;
|
|
1992
|
-
height: 20px;
|
|
1993
|
-
padding: 0 5px;
|
|
1994
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(118,137,166,.25));
|
|
1995
|
-
border-radius: 3px;
|
|
1996
|
-
background: var(--dsw-alias-container-bg, var(--dsw-alias-bg-base, #ffffff));
|
|
1997
|
-
color: var(--dsw-alias-label-secondary, #6f7f9b);
|
|
1998
|
-
cursor: pointer;
|
|
1999
|
-
transition: all 0.12s ease;
|
|
2000
|
-
}
|
|
2001
|
-
.dsh-files-action-btn:hover {
|
|
2002
|
-
color: var(--dsw-alias-label-primary, inherit);
|
|
2003
|
-
border-color: var(--dsw-alias-border-l3, rgba(118,137,166,.4));
|
|
2004
|
-
background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.12));
|
|
2005
|
-
}
|
|
2006
|
-
.dsh-files-action-at {
|
|
2007
|
-
color: var(--dsw-alias-brand-primary, #4b70e2);
|
|
2008
|
-
border-color: color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 30%, transparent);
|
|
2009
|
-
background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 8%, transparent);
|
|
2010
|
-
}
|
|
2011
|
-
.dsh-files-action-at:hover {
|
|
2012
|
-
color: #ffffff;
|
|
2013
|
-
background: var(--dsw-alias-brand-primary, #4b70e2);
|
|
2014
|
-
border-color: var(--dsw-alias-brand-primary, #4b70e2);
|
|
2015
|
-
}
|
|
2016
|
-
.dsh-files-action-at-text {
|
|
2017
|
-
font-size: 10px;
|
|
2018
|
-
font-weight: 600;
|
|
2013
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);
|
|
2019
2014
|
}
|
|
2020
2015
|
|
|
2021
2016
|
/* ── Highlight & Toast ── */
|
|
2022
2017
|
.dsh-files-highlight {
|
|
2023
|
-
background: color-mix(in srgb, var(--dsw-alias-
|
|
2024
|
-
color: var(--dsw-alias-
|
|
2018
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 22%, transparent);
|
|
2019
|
+
color: var(--dsw-alias-state-business-primary);
|
|
2025
2020
|
font-weight: 600;
|
|
2026
2021
|
border-radius: 2px;
|
|
2027
2022
|
padding: 0 1px;
|
|
@@ -2035,12 +2030,12 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
2035
2030
|
align-items: center;
|
|
2036
2031
|
gap: 6px;
|
|
2037
2032
|
padding: 6px 12px;
|
|
2038
|
-
background: var(--dsw-alias-label-primary
|
|
2039
|
-
color: var(--dsw-alias-
|
|
2033
|
+
background: var(--dsw-alias-label-primary);
|
|
2034
|
+
color: var(--dsw-alias-fill-base, #ffffff);
|
|
2040
2035
|
font-size: 11px;
|
|
2041
2036
|
border-radius: 99px;
|
|
2042
|
-
box-shadow: 0 4px 16px rgba(0,0,0
|
|
2043
|
-
animation: dsh-files-fade-in 0.
|
|
2037
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.22);
|
|
2038
|
+
animation: dsh-files-fade-in 0.16s cubic-bezier(0.16, 1, 0.3, 1);
|
|
2044
2039
|
z-index: 100;
|
|
2045
2040
|
pointer-events: none;
|
|
2046
2041
|
white-space: nowrap;
|
|
@@ -2058,13 +2053,13 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
2058
2053
|
justify-content: center;
|
|
2059
2054
|
height: 180px;
|
|
2060
2055
|
gap: 10px;
|
|
2061
|
-
color: var(--dsw-alias-label-secondary
|
|
2056
|
+
color: var(--dsw-alias-label-secondary);
|
|
2062
2057
|
}
|
|
2063
2058
|
.dsh-files-spinner {
|
|
2064
2059
|
width: 18px;
|
|
2065
2060
|
height: 18px;
|
|
2066
|
-
border: 2px solid var(--dsw-alias-border-
|
|
2067
|
-
border-top-color: var(--dsw-alias-
|
|
2061
|
+
border: 2px solid var(--dsw-alias-border-l1);
|
|
2062
|
+
border-top-color: var(--dsw-alias-state-business-primary);
|
|
2068
2063
|
border-radius: 50%;
|
|
2069
2064
|
animation: dsh-files-spin 0.8s linear infinite;
|
|
2070
2065
|
}
|
|
@@ -2078,7 +2073,7 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
2078
2073
|
justify-content: center;
|
|
2079
2074
|
padding: 36px 16px;
|
|
2080
2075
|
text-align: center;
|
|
2081
|
-
color: var(--dsw-alias-label-secondary
|
|
2076
|
+
color: var(--dsw-alias-label-secondary);
|
|
2082
2077
|
}
|
|
2083
2078
|
.dsh-files-empty-icon {
|
|
2084
2079
|
margin-bottom: 8px;
|
|
@@ -2091,7 +2086,7 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
2091
2086
|
}
|
|
2092
2087
|
.dsh-files-empty-detail {
|
|
2093
2088
|
font-size: 11.5px;
|
|
2094
|
-
color: var(--dsw-alias-label-tertiary
|
|
2089
|
+
color: var(--dsw-alias-label-tertiary);
|
|
2095
2090
|
}
|
|
2096
2091
|
.dsh-files-banner-error {
|
|
2097
2092
|
display: flex;
|
|
@@ -2100,9 +2095,9 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
2100
2095
|
gap: 8px;
|
|
2101
2096
|
padding: 6px 12px;
|
|
2102
2097
|
font-size: 11.5px;
|
|
2103
|
-
color: var(--dsw-alias-state-
|
|
2104
|
-
background: color-mix(in srgb, var(--dsw-alias-state-
|
|
2105
|
-
border-bottom:
|
|
2098
|
+
color: var(--dsw-alias-state-danger-primary, #e34c59);
|
|
2099
|
+
background: color-mix(in srgb, var(--dsw-alias-state-danger-primary, #e34c59) 10%, transparent);
|
|
2100
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l1);
|
|
2106
2101
|
}
|
|
2107
2102
|
.dsh-files-banner-close {
|
|
2108
2103
|
border: none;
|
|
@@ -2110,6 +2105,6 @@ function GlobalFilesStyle(): JSX.Element {
|
|
|
2110
2105
|
color: inherit;
|
|
2111
2106
|
cursor: pointer;
|
|
2112
2107
|
}
|
|
2113
|
-
`
|
|
2114
|
-
)
|
|
2108
|
+
`
|
|
2109
|
+
document.head.appendChild(style)
|
|
2115
2110
|
}
|