@lyhue1991/dsh-soup 0.4.9 → 0.4.10
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/lib/client.js +78 -21
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -392,6 +392,31 @@ window.__ModuleLoader__.load({
|
|
|
392
392
|
// while a blank conversation is mounted. Keep late async responses from an
|
|
393
393
|
// older caller from overwriting the current session's shared store.
|
|
394
394
|
var sessionTrackGeneration = 0
|
|
395
|
+
|
|
396
|
+
// 目录展开状态按 cwd 隔离。刷新/删除/改名/移动后先重拉根目录,
|
|
397
|
+
// 再按这些路径递归重拉仍存在的已展开目录;切换项目时才清空。
|
|
398
|
+
var expandedDirs = new Set()
|
|
399
|
+
var expandedDirsCwd = null
|
|
400
|
+
|
|
401
|
+
function forgetExpandedPaths(paths) {
|
|
402
|
+
var dropped = paths.map(function (p) { return [p, p + '/'] })
|
|
403
|
+
expandedDirs = new Set(Array.from(expandedDirs).filter(function (p) {
|
|
404
|
+
return !dropped.some(function (pair) { return p === pair[0] || p.startsWith(pair[1]) })
|
|
405
|
+
}))
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function rekeyExpandedPaths(from, to) {
|
|
409
|
+
var moved = Array.from(expandedDirs).filter(function (p) {
|
|
410
|
+
return p === from || p.startsWith(from + '/')
|
|
411
|
+
}).sort(function (a, b) { return a.length - b.length })
|
|
412
|
+
if (!moved.length) return
|
|
413
|
+
var next = new Set(Array.from(expandedDirs).filter(function (p) {
|
|
414
|
+
return p !== from && !p.startsWith(from + '/')
|
|
415
|
+
}))
|
|
416
|
+
moved.forEach(function (p) { next.add(to + p.slice(from.length)) })
|
|
417
|
+
expandedDirs = next
|
|
418
|
+
}
|
|
419
|
+
|
|
395
420
|
function setState(patch) {
|
|
396
421
|
state = Object.assign({}, state, patch)
|
|
397
422
|
listeners.forEach(function (fn) { fn() })
|
|
@@ -484,6 +509,11 @@ window.__ModuleLoader__.load({
|
|
|
484
509
|
activeSessionId = sessionId || null
|
|
485
510
|
var cwd = await defaultCwd(sessionId, cwdHint)
|
|
486
511
|
if (generation !== sessionTrackGeneration) return
|
|
512
|
+
// 换项目时展开状态没有跨项目意义;回到同一项目则保留。
|
|
513
|
+
if (expandedDirsCwd !== cwd) {
|
|
514
|
+
expandedDirs.clear()
|
|
515
|
+
expandedDirsCwd = cwd
|
|
516
|
+
}
|
|
487
517
|
// 会话切换:预览列表按新 cwd 裁剪(范围外自动关闭),并清掉上一路径的旧错误横幅
|
|
488
518
|
var files = pruneFilesToScope(state.files, cwd)
|
|
489
519
|
lastMtimes = {} // 换了目录,旧 mtime 基线全部作废(首轮 tick 重建基线)
|
|
@@ -496,7 +526,26 @@ window.__ModuleLoader__.load({
|
|
|
496
526
|
async function refresh() {
|
|
497
527
|
if (!state.cwd) return
|
|
498
528
|
var items = await loadDir(state.cwd)
|
|
499
|
-
if (items)
|
|
529
|
+
if (!items) return
|
|
530
|
+
await restoreExpandedChildren(items)
|
|
531
|
+
setState({ error: '', tree: items })
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** 根目录刷新后,按记住的路径递归恢复已展开目录。 */
|
|
535
|
+
async function restoreExpandedChildren(nodes) {
|
|
536
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
537
|
+
var node = nodes[i]
|
|
538
|
+
if (node.type !== 'directory' || !expandedDirs.has(node.path)) continue
|
|
539
|
+
var items = await loadDir(node.path)
|
|
540
|
+
if (!items) {
|
|
541
|
+
expandedDirs.delete(node.path)
|
|
542
|
+
continue
|
|
543
|
+
}
|
|
544
|
+
node.open = true
|
|
545
|
+
node.children = items
|
|
546
|
+
items.forEach(function (child) { child.parent = node })
|
|
547
|
+
await restoreExpandedChildren(items)
|
|
548
|
+
}
|
|
500
549
|
}
|
|
501
550
|
|
|
502
551
|
// ------------------------------------------------------------------
|
|
@@ -624,23 +673,7 @@ window.__ModuleLoader__.load({
|
|
|
624
673
|
/** 手动刷新:重拉根目录,已展开的子目录逐个重拉、保持展开不收起。 */
|
|
625
674
|
async function refreshAll() {
|
|
626
675
|
if (!state.cwd) return
|
|
627
|
-
var expanded = []
|
|
628
|
-
var walk = function (nodes) {
|
|
629
|
-
for (var i = 0; i < nodes.length; i++) {
|
|
630
|
-
var n = nodes[i]
|
|
631
|
-
if (n.type === 'directory' && n.children != null) {
|
|
632
|
-
if (n.path !== state.cwd) expanded.push({ path: n.path, open: !!n.open })
|
|
633
|
-
if (n.open && n.children.length) walk(n.children)
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
walk(state.tree)
|
|
638
676
|
await refresh()
|
|
639
|
-
for (var j = 0; j < expanded.length; j++) {
|
|
640
|
-
if (!expanded[j].open) continue
|
|
641
|
-
var node = findNode(state.tree, expanded[j].path)
|
|
642
|
-
if (node) await reloadDirNode(node)
|
|
643
|
-
}
|
|
644
677
|
}
|
|
645
678
|
|
|
646
679
|
async function toggleNode(node) {
|
|
@@ -652,10 +685,16 @@ window.__ModuleLoader__.load({
|
|
|
652
685
|
node.loading = false
|
|
653
686
|
if (items) items.forEach(function (c) { c.parent = node })
|
|
654
687
|
node.children = items || []
|
|
655
|
-
node.open =
|
|
688
|
+
node.open = !!items
|
|
689
|
+
if (items) expandedDirs.add(node.path)
|
|
690
|
+
// 拖拽可能把一个已展开目录移进此前收起的父目录。父目录首次展开时,
|
|
691
|
+
// 也要立即恢复其中被记住的子目录展开状态。
|
|
692
|
+
if (items) await restoreExpandedChildren(items)
|
|
656
693
|
setState({ tree: state.tree })
|
|
657
694
|
} else {
|
|
658
695
|
node.open = !node.open
|
|
696
|
+
if (node.open) expandedDirs.add(node.path)
|
|
697
|
+
else expandedDirs.delete(node.path)
|
|
659
698
|
setState({ tree: state.tree })
|
|
660
699
|
}
|
|
661
700
|
}
|
|
@@ -940,11 +979,14 @@ window.__ModuleLoader__.load({
|
|
|
940
979
|
var paths = Array.from(state.selected)
|
|
941
980
|
setState({ menu: null })
|
|
942
981
|
var errors = []
|
|
982
|
+
var trashed = []
|
|
943
983
|
for (var i = 0; i < paths.length; i++) {
|
|
944
984
|
var res = await rpc('trash', { path: paths[i] })
|
|
945
|
-
if (
|
|
985
|
+
if (res && res.ok) trashed.push(paths[i])
|
|
986
|
+
else errors.push(baseName(paths[i]) + ': ' + ((res && res.error) || T('explorer.actionFail')) + ((res && res.hint) ? '(' + res.hint + ')' : ''))
|
|
946
987
|
}
|
|
947
988
|
if (errors.length) setState({ error: errors.join(';') })
|
|
989
|
+
forgetExpandedPaths(trashed)
|
|
948
990
|
setState({ selected: new Set(), lastIndex: null })
|
|
949
991
|
await refresh()
|
|
950
992
|
}
|
|
@@ -958,12 +1000,18 @@ window.__ModuleLoader__.load({
|
|
|
958
1000
|
}
|
|
959
1001
|
var dir = parentOf(path)
|
|
960
1002
|
var dest = pathJoin(dir, name)
|
|
1003
|
+
var node = findNode(state.tree, path)
|
|
1004
|
+
var isDir = node && node.type === 'directory'
|
|
961
1005
|
var sel = new Set(state.selected)
|
|
962
1006
|
if (sel.has(path)) { sel.delete(path); sel.add(dest) }
|
|
963
1007
|
setState({ renaming: null, selected: sel })
|
|
964
1008
|
;(async function () {
|
|
965
1009
|
var res = await rpc('move', { from: path, to: dest })
|
|
966
|
-
if (
|
|
1010
|
+
if (res && res.ok) {
|
|
1011
|
+
if (isDir) rekeyExpandedPaths(path, dest)
|
|
1012
|
+
} else {
|
|
1013
|
+
setState({ error: (res && res.error) || T('explorer.renameFail') })
|
|
1014
|
+
}
|
|
967
1015
|
await refresh()
|
|
968
1016
|
})()
|
|
969
1017
|
}
|
|
@@ -1040,6 +1088,7 @@ window.__ModuleLoader__.load({
|
|
|
1040
1088
|
|
|
1041
1089
|
async function movePaths(paths, targetDir) {
|
|
1042
1090
|
var errors = []
|
|
1091
|
+
var moved = []
|
|
1043
1092
|
for (var i = 0; i < paths.length; i++) {
|
|
1044
1093
|
var p = paths[i]
|
|
1045
1094
|
if (p === '/' || !p) continue
|
|
@@ -1047,10 +1096,18 @@ window.__ModuleLoader__.load({
|
|
|
1047
1096
|
var dest = pathJoin(targetDir, name)
|
|
1048
1097
|
if (dest === p) continue
|
|
1049
1098
|
if (targetDir === p || targetDir.startsWith(p + '/')) { errors.push(T('explorer.moveIntoSelf', { path: name })); continue }
|
|
1099
|
+
var node = findNode(state.tree, p)
|
|
1050
1100
|
var res = await rpc('move', { from: p, to: dest })
|
|
1051
|
-
if (
|
|
1101
|
+
if (res && res.ok) {
|
|
1102
|
+
moved.push({ from: p, to: dest, isDir: node && node.type === 'directory' })
|
|
1103
|
+
} else {
|
|
1104
|
+
errors.push(name + ': ' + ((res && res.error) || T('explorer.actionFail')))
|
|
1105
|
+
}
|
|
1052
1106
|
}
|
|
1053
1107
|
setState({ dragPaths: null })
|
|
1108
|
+
moved.forEach(function (item) {
|
|
1109
|
+
if (item.isDir) rekeyExpandedPaths(item.from, item.to)
|
|
1110
|
+
})
|
|
1054
1111
|
if (errors.length) setState({ error: errors.join(';') })
|
|
1055
1112
|
await refresh()
|
|
1056
1113
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lyhue1991/dsh-soup",
|
|
3
3
|
"description": "DSH 项目资源管理器:右侧 details 列文件树(并列网格、宽度与左侧工作区一致 264-420px),有 Session log 时在其右侧、空白新会话在输入框上方右端提供 📁 切换按钮;支持多选、双击展开/打开、右键菜单(打开/系统打开/重命名/废纸篓/新建/复制路径)、拖拽移动、上传。文件「打开」在会话区新增「文件」标签页(原生 conversation.view,与对话/轨迹同级):文本可编辑 ⌘S 保存、图片预览、二进制提示。附带模型吞吐徽标:输入框正上方显示「正在等待模型...」与彩色 t/s 徽标(数据来自 llm/stream 真实流)。附带 GoalBar 多行展示:把原生单行截断 GoalBar 放开为多行完整显示,编辑用 textarea,工具与数据仍走原生 goal。",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.10",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|