dsh-better-workspace 0.36.9 → 0.36.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/README.md +1 -1
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +186 -8
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
### 其余亮点
|
|
70
70
|
|
|
71
71
|
- **文件夹式收起**:点工作区行 = 收起/展开该工作区的全部会话(收起后行上保留会话数角标;角标 v0.34.0 起默认隐藏、对齐官方,设置卡「显示会话数」可开);会话分组同样可收起;搜索时自动全部展开。
|
|
72
|
-
- **拖拽还原原版能力**:工作区行可拖拽——拖到另一工作区行上/下半即调整顺序(写回宿主 registry 序),拖到分组行中带则移动进该分组(改写名称前缀);**工作区分组行同样可拖拽(v0.36.0 起,三带悬停)**——拖到分组行时行分三个落区:上下 **30% 边带**=与该组**排序**(拖完立即重排、刷新后保留),**中带**=整组移入(组成员逐个改写名称前缀,与「重命名分组」同一语义;拖到自身/自己的子孙组上会被拒绝);**组行与工作区行同处一条混合序列(v0.36.5 起)**:一个层级里的行顺序只有一份(组行与裸工作区行交错),工作区可以拖到分组行的边带插到该组前面/后面,分组也可以拖到工作区行前面/后面——不再有「组永远排在裸工作区之前」的限制;拖到**另一层**的边带是**放置**(v0.36.6):工作区离开原组(或进入目标层)并插到落点,「插到第一个」画哪落哪(跨磁盘层不放行——磁盘层是文件系统事实);**拖入黑框统一(v0.36.7)**:工作区行拖到分组行中带 = 改名前缀进组(黑框反馈);组行拖到**它所属的磁盘层工作区行 / 直接父级分组行**中带 = 整组「放入」该容器、排到该层末尾(标题与磁盘位置都不动,同一套黑框视觉)——排序走边带、放入走中带,两类操作视觉上彻底分开;**容器防环(v0.36.9)**:磁盘层的容器工作区不能被拖进它自己的名称组或子工作区——那会把容器改名成自己的子孙(如「工作/用户目录」甚至「<id>/用户目录」),现在整行拒绝、不留任何落点反馈,排序与放入父容器(v0.36.7)
|
|
72
|
+
- **拖拽还原原版能力**:工作区行可拖拽——拖到另一工作区行上/下半即调整顺序(写回宿主 registry 序),拖到分组行中带则移动进该分组(改写名称前缀);**工作区分组行同样可拖拽(v0.36.0 起,三带悬停)**——拖到分组行时行分三个落区:上下 **30% 边带**=与该组**排序**(拖完立即重排、刷新后保留),**中带**=整组移入(组成员逐个改写名称前缀,与「重命名分组」同一语义;拖到自身/自己的子孙组上会被拒绝);**组行与工作区行同处一条混合序列(v0.36.5 起)**:一个层级里的行顺序只有一份(组行与裸工作区行交错),工作区可以拖到分组行的边带插到该组前面/后面,分组也可以拖到工作区行前面/后面——不再有「组永远排在裸工作区之前」的限制;拖到**另一层**的边带是**放置**(v0.36.6):工作区离开原组(或进入目标层)并插到落点,「插到第一个」画哪落哪(跨磁盘层不放行——磁盘层是文件系统事实);**拖入黑框统一(v0.36.7)**:工作区行拖到分组行中带 = 改名前缀进组(黑框反馈);组行拖到**它所属的磁盘层工作区行 / 直接父级分组行**中带 = 整组「放入」该容器、排到该层末尾(标题与磁盘位置都不动,同一套黑框视觉)——排序走边带、放入走中带,两类操作视觉上彻底分开;**容器防环(v0.36.9)**:磁盘层的容器工作区不能被拖进它自己的名称组或子工作区——那会把容器改名成自己的子孙(如「工作/用户目录」甚至「<id>/用户目录」),现在整行拒绝、不留任何落点反馈,排序与放入父容器(v0.36.7)不受影响;**平级组嵌套与提升(v0.36.10)**:组行拖到平级组行的**中带**=变成它的**子组**(成员标题落到新组之下,`工作/平行帮` → `工作/Fis管理后台/平行帮`,天然不撞名——旧的并入语义会在双方都有同名子工作区时报 name-conflict、整树不动);组行拖到**另一层工作区行的边带**(同磁盘、不在自己子树内)=**整组提升**到那行所在层并锚定在落点旁(「把平行帮拖到和用户目录、工作平级」);已打开的排序槽不再锁死判定——指针推回行**中带**即可翻成放入黑框,嵌套与排序随时互切;**静止松手兜底**:Chromium 把指针静止约一秒后的松手吞成 dragend 时,按最后悬停的落点补提交(Esc 取消不受影响),不再「松手没反应」;**撑开让位(v0.36.4)**:进入排序边带立刻把目标行撑开一行(工作区行与分组行同一套视觉),判定按**行未撑开时的原始位置**算——指针停在槽空出的新区里保持当前方向不闪烁;槽已打开的行只按原始中线二选一(上半=插在前,下半=插在后,**不会翻成「移入」**),三带里的中带 INTO 只在首次进入时决定,随时可改主意;会话行可拖拽——同组内拖到另一会话上/下即调整顺序,拖到**别组的会话行**会把名字前缀改写到那个组(拖到根层的行 = 移出分组),拖到会话子分组行即移动进该分组,拖到**自己的工作区行**或**树里任何组外的地方**(含空白区域)同样移出分组 —— 拖拽期间工作区行整行高亮、树底部有一条淡虚线提示这个出口;带分组前缀的会话行右键还多一项「移出分组」。显示顺序 = 宿主手动序(分组行 = 父容器下的持久化组序),拖拽结果立即生效。**磁盘层是文件系统事实,不随拖拽改变**:跨磁盘层不放行(边带不接管光标、不画落点线),同磁盘层内按落点锚定。拖拽期间树形保持不变(v0.36.4):单链合并行不再临时展开、归组成功也不自动展开目标组——指针下的行不会突然换人,展开状态只在点击时改变。
|
|
73
73
|
- **不丢原生能力**:会话行(打开/重命名/分叉/归档;**双击标题**重命名,v0.34.0 起;**右键是唯一菜单入口**——行内「⋯」已退役,悬停只留快捷钮,工作区行同样只留新会话钮,v0.35.1 起)、**官方行样式逐项对齐**(v0.34.0 起:恒定 16px 状态槽——运行转圈不再挤动标题、悬停 800ms 弹官方 HoverCard(标题/路径/时间/状态,可复制)、超长标题悬停匀速滚动、活动链工作区文件夹图标着蓝、拖放指示用官方斜纹图案)、每工作区「新会话」(点 + 总是先展开该工作区,让新会话立刻可见)、当前会话高亮、官方状态灯(蓝色运行环 / 琥珀待交互 / 绿色完成提醒)、**状态呼吸灯**(折叠藏起的状态灯向外冒泡:工作区/分组行图标按状态色呼吸发光——琥珀待交互/绿完成,自定义过发光的标题一起呼吸;运行中只在会话行本层蓝色呼吸,不外渗;可在设置关闭)、**活跃定时任务角标**(有活动 Schedule 的会话显示闹钟图标;v0.23.0 起直接渲染官方 `sidebar.session.row.leading` 座位的占用者,与官方行同源)、未分组会话兜底、界面文案跟随 DSH 的语言设置(内置 21 种语言,含简繁中文)。
|
|
74
74
|
- **新增项自动配色 + 图标(v0.25.0,默认关闭)**:设置卡里一个开关,打开后**新创建的会话**自动分到一个内置色、**新工作区**再额外分到一个图标,懒得逐个设置的人直接开箱即用。颜色来自**定死的 13 个槽位**,每个槽位配**两色**——浅色主题用深色系、深色主题用亮色系——按「洗牌袋 + 近期回避窗口」抽取:一轮之内绝不重复,跨轮也不会立刻撞上刚出过的 3 个,连续新建几项肉眼可辨。**可读性是真达标**:每个色在**对应主题**背景下的对比度都 **≥4.5**,即 WCAG **正文级 AA**(对比度是脚本真算的,测试逐色断言;浅色主题按比纯白更暗的悬停面、深色主题按最亮的面这种保守取值)。行里存的是**槽位 id(`auto:3`)而不是色值**,所以**切换深浅主题时颜色自动换成同色系的另一套**,既不用重新设置,也不会失配。**只影响打开开关之后新建的项**:你手动设置过外观的行(存的是 hex)永不被覆盖,关闭开关也不回收已分配的样式,同一项不会被二次随机。图标从既有图标候选集里取、按宿主实际导出过滤;**图标只给工作区**——会话行的那个位置是官方状态灯与日程角标,没有图标位。
|
|
75
75
|
- **官方新语义与快捷键都接上(v0.23.0,对齐 dsh 0.1.7-rc.2)**:① 归档筛选是官方新版三态直选(**隐藏已归档** / **全部对话(显示已归档)** / **仅显示已归档**);选「仅显示已归档」时**没有归档会话的工作区整行隐藏**,空列表显示「暂无已归档会话」+「查看其他会话」一键回退;② 官方快捷键 **⌥⌘K 搜索 / ⌥⌘O 添加工作区 / ⌥⌘N 新会话**(桌面端为 ⌘K / ⌘O / ⌘N)在本插件的自绘侧栏里**真的生效**——搜索键打开并聚焦本插件的搜索框、添加键走同一个官方目录流(原生宿主弹系统选择器,browse 宿主弹应用内入口提示),按钮文案还会带上宿主当前的键位;③ 首次使用的工作区显示为本地化的「默认工作区」,不再露出磁盘目录名。
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.10",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树。Workspace tree for the DSH sidebar workspace list, nested by official disk folders with optional \"/\"-name grouping; the New Session workspace picker shows the same tree.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-workspace",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.10",
|
|
4
4
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树(同一份视图选项即时同步);添加工作区走官方目录流。Workspace tree for the DSH sidebar: disk-path nesting (official workspace-tree semantics), with optional \"/\"-name grouping (off by default), and the same tree in the New Session workspace picker; add-workspace rides the official directory flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -8014,6 +8014,21 @@ window.__ModuleLoader__.load({
|
|
|
8014
8014
|
// source element, and Chromium cancels the whole gesture. dragEnd clears
|
|
8015
8015
|
// the timer so a same-tick cancel never leaves a ghost drag behind.
|
|
8016
8016
|
const wsDragArmTimer = React.useRef(null)
|
|
8017
|
+
// v0.36.10: Chromium CANCELS a drag whose pointer rests long enough —
|
|
8018
|
+
// the mouse-up arrives as dragleave+dragend and 'drop' never fires
|
|
8019
|
+
// (probe-measured: resting ~1-2s loses the drop every time). The
|
|
8020
|
+
// user reads that as "松手没反应". dragStateRef mirrors {drag} for a
|
|
8021
|
+
// dragEnd fallback that honors the armed {over}; dragDroppedRef
|
|
8022
|
+
// tells dragEnd a real drop already committed; Escape still cancels.
|
|
8023
|
+
const dragStateRef = React.useRef(null)
|
|
8024
|
+
const dragDroppedRef = React.useRef(false)
|
|
8025
|
+
const dragCancelledRef = React.useRef(false)
|
|
8026
|
+
React.useEffect(() => { dragStateRef.current = drag }, [drag])
|
|
8027
|
+
React.useEffect(() => {
|
|
8028
|
+
const onKey = (event) => { if (event.key === 'Escape') dragCancelledRef.current = true }
|
|
8029
|
+
window.addEventListener('keydown', onKey, true)
|
|
8030
|
+
return () => window.removeEventListener('keydown', onKey, true)
|
|
8031
|
+
}, [])
|
|
8017
8032
|
// (v0.36.3) The 600ms group-hover auto-expand is retired: it kept
|
|
8018
8033
|
// opening groups under a passing cursor ("变下级会被撑开"). The
|
|
8019
8034
|
// deterministic reveal now lives in commitFolderMoveInto — the target
|
|
@@ -8832,6 +8847,18 @@ window.__ModuleLoader__.load({
|
|
|
8832
8847
|
if (rel > 0.7) return 'after'
|
|
8833
8848
|
return 'into'
|
|
8834
8849
|
}
|
|
8850
|
+
// v0.36.10: TRUE when the pointer sits in the row's ORIGINAL content
|
|
8851
|
+
// box (a possibly-open slot's padding excluded). While this row holds
|
|
8852
|
+
// a reorder slot the padding band keeps the slot verdict, and the
|
|
8853
|
+
// content box re-judges the three bands — that is what keeps the
|
|
8854
|
+
// INTO middle reachable after a slot opened. The old slotOpen lock
|
|
8855
|
+
// answered before/after forever, so a pointer that entered through an
|
|
8856
|
+
// edge band could never reach the middle: "拖到另一个下面(嵌套)" was
|
|
8857
|
+
// unreachable through the natural approach path.
|
|
8858
|
+
const pointerInRowBox = (event) => {
|
|
8859
|
+
const box = originalBoxOf(event.currentTarget)
|
|
8860
|
+
return event.clientY >= box.top && event.clientY <= box.bottom
|
|
8861
|
+
}
|
|
8835
8862
|
const dragMatches = (kind) => drag !== null && drag.kind === kind
|
|
8836
8863
|
// A refused hover must DROP the highlight it already owns: leaving a
|
|
8837
8864
|
// stale {over} in place drew the previous target's INTO frame on a row
|
|
@@ -8902,7 +8929,23 @@ window.__ModuleLoader__.load({
|
|
|
8902
8929
|
},
|
|
8903
8930
|
onDragEnd: () => {
|
|
8904
8931
|
if (wsDragArmTimer.current !== null) { clearTimeout(wsDragArmTimer.current); wsDragArmTimer.current = null }
|
|
8932
|
+
const committed = dragDroppedRef.current
|
|
8933
|
+
dragDroppedRef.current = false
|
|
8934
|
+
const cancelled = dragCancelledRef.current
|
|
8935
|
+
dragCancelledRef.current = false
|
|
8936
|
+
const live = dragStateRef.current
|
|
8905
8937
|
setDrag(null)
|
|
8938
|
+
// v0.36.10: same resting-drag fallback as the folder dragEnd.
|
|
8939
|
+
if (committed || cancelled || !live || live.kind !== 'workspace' || !live.over) return
|
|
8940
|
+
const over = live.over
|
|
8941
|
+
if (over.kind === 'folder') {
|
|
8942
|
+
const zone = over.zone === 'before' || over.zone === 'after' ? over.zone : 'into'
|
|
8943
|
+
if (zone === 'into') commitWorkspaceMoveInto(live.source, over.target)
|
|
8944
|
+
else if ((live.source.levelKey || '') === parentKeyOf(over.target)) commitLevelReorder({ kind: 'workspace', source: live.source }, { kind: 'folder', target: over.target }, zone)
|
|
8945
|
+
else commitWorkspaceLift(live.source, over.target, zone)
|
|
8946
|
+
} else if (over.kind === 'workspace' && over.half && over.workspace) {
|
|
8947
|
+
commitWorkspaceDrop(over.workspace, over.half)
|
|
8948
|
+
}
|
|
8906
8949
|
},
|
|
8907
8950
|
onDragOver: (event) => {
|
|
8908
8951
|
// A SESSION dragged onto the workspace row lands at the root level of
|
|
@@ -8943,8 +8986,28 @@ window.__ModuleLoader__.load({
|
|
|
8943
8986
|
? current
|
|
8944
8987
|
: (current ? { ...current, over: { kind: 'ws-level', target: workspace.workspaceId } } : current))
|
|
8945
8988
|
} else {
|
|
8946
|
-
|
|
8947
|
-
|
|
8989
|
+
// v0.36.10 GROUP LIFT: an EDGE band on a cross-level row of
|
|
8990
|
+
// the SAME disk scope lifts the whole group out to this
|
|
8991
|
+
// row's level, anchored at the row — "把平行帮拖到和用户
|
|
8992
|
+
// 目录、工作平级". The row must not live inside the dragged
|
|
8993
|
+
// group (that would nest the group under itself) and not on
|
|
8994
|
+
// another disk (a title rename cannot cross disks).
|
|
8995
|
+
const half = rowHalf(event)
|
|
8996
|
+
const sourceScope = diskScopeOf(drag.source.idPath)
|
|
8997
|
+
const targetDisk = diskParentMap.get(workspace.workspaceId) || workspace.workspaceId
|
|
8998
|
+
const targetLevel = workspace.levelKey || ''
|
|
8999
|
+
const insideSource = targetLevel === drag.source.idPath || targetLevel.startsWith(drag.source.idPath + '/')
|
|
9000
|
+
if (half !== null && sourceScope !== null && targetDisk === sourceScope && !insideSource) {
|
|
9001
|
+
event.preventDefault()
|
|
9002
|
+
event.stopPropagation()
|
|
9003
|
+
try { event.dataTransfer.dropEffect = 'move' } catch { }
|
|
9004
|
+
setDrag(current => (current && current.over && current.over.kind === 'workspace' && current.over.target === workspace.workspaceId && current.over.half === half)
|
|
9005
|
+
? current
|
|
9006
|
+
: (current ? { ...current, over: { kind: 'workspace', target: workspace.workspaceId, half, workspace, folderLift: true } } : current))
|
|
9007
|
+
} else {
|
|
9008
|
+
clearOver('workspace', workspace.workspaceId)
|
|
9009
|
+
clearOver('ws-level', workspace.workspaceId)
|
|
9010
|
+
}
|
|
8948
9011
|
}
|
|
8949
9012
|
return
|
|
8950
9013
|
}
|
|
@@ -8953,9 +9016,11 @@ window.__ModuleLoader__.load({
|
|
|
8953
9016
|
try { event.dataTransfer.dropEffect = 'move' } catch { }
|
|
8954
9017
|
const half = rowHalf(event)
|
|
8955
9018
|
if (half === null) return
|
|
9019
|
+
// The row object rides along for the dragEnd fallback (same
|
|
9020
|
+
// reason as the folder over's node above).
|
|
8956
9021
|
setDrag(current => (current && current.over && current.over.kind === 'workspace' && current.over.target === workspace.workspaceId && current.over.half === half)
|
|
8957
9022
|
? current
|
|
8958
|
-
: (current ? { ...current, over: { kind: 'workspace', target: workspace.workspaceId, half } } : current))
|
|
9023
|
+
: (current ? { ...current, over: { kind: 'workspace', target: workspace.workspaceId, half, workspace } } : current))
|
|
8959
9024
|
return
|
|
8960
9025
|
}
|
|
8961
9026
|
if (!dragMatches('workspace')) return
|
|
@@ -8978,7 +9043,7 @@ window.__ModuleLoader__.load({
|
|
|
8978
9043
|
if (half === null) return
|
|
8979
9044
|
setDrag(current => (current && current.over && current.over.kind === 'workspace' && current.over.target === workspace.workspaceId && current.over.half === half)
|
|
8980
9045
|
? current
|
|
8981
|
-
: (current ? { ...current, over: { kind: 'workspace', target: workspace.workspaceId, half } } : current))
|
|
9046
|
+
: (current ? { ...current, over: { kind: 'workspace', target: workspace.workspaceId, half, workspace } } : current))
|
|
8982
9047
|
},
|
|
8983
9048
|
onDrop: (event) => {
|
|
8984
9049
|
if (dragMatches('session')) {
|
|
@@ -8997,12 +9062,23 @@ window.__ModuleLoader__.load({
|
|
|
8997
9062
|
if (sourceLevel === (workspace.subPrefix || '')) {
|
|
8998
9063
|
event.preventDefault()
|
|
8999
9064
|
event.stopPropagation()
|
|
9065
|
+
dragDroppedRef.current = true
|
|
9000
9066
|
commitFolderMoveToLevelEnd(drag.source)
|
|
9067
|
+
} else {
|
|
9068
|
+
// v0.36.10: the lift band armed in onDragOver.
|
|
9069
|
+
const over = drag.over
|
|
9070
|
+
if (over && over.kind === 'workspace' && over.target === workspace.workspaceId && over.folderLift && over.half) {
|
|
9071
|
+
event.preventDefault()
|
|
9072
|
+
event.stopPropagation()
|
|
9073
|
+
dragDroppedRef.current = true
|
|
9074
|
+
commitFolderLift(drag.source, workspace, over.half)
|
|
9075
|
+
}
|
|
9001
9076
|
}
|
|
9002
9077
|
return
|
|
9003
9078
|
}
|
|
9004
9079
|
event.preventDefault()
|
|
9005
9080
|
event.stopPropagation()
|
|
9081
|
+
dragDroppedRef.current = true
|
|
9006
9082
|
const half = drag.over && drag.over.kind === 'workspace' && drag.over.target === workspace.workspaceId ? drag.over.half : rowHalf(event)
|
|
9007
9083
|
commitLevelReorder({ kind: 'folder', source: drag.source }, { kind: 'workspace', workspace }, half || 'before')
|
|
9008
9084
|
return
|
|
@@ -9013,6 +9089,7 @@ window.__ModuleLoader__.load({
|
|
|
9013
9089
|
if (isOwnDescendantScope(diskParentMap.get(workspace.workspaceId), drag.source.workspaceId)) return
|
|
9014
9090
|
event.preventDefault()
|
|
9015
9091
|
event.stopPropagation()
|
|
9092
|
+
dragDroppedRef.current = true
|
|
9016
9093
|
const half = drag.over && drag.over.kind === 'workspace' && drag.over.target === workspace.workspaceId ? drag.over.half : rowHalf(event)
|
|
9017
9094
|
commitWorkspaceDrop(workspace, half)
|
|
9018
9095
|
},
|
|
@@ -9132,7 +9209,29 @@ window.__ModuleLoader__.load({
|
|
|
9132
9209
|
},
|
|
9133
9210
|
onDragEnd: () => {
|
|
9134
9211
|
if (wsDragArmTimer.current !== null) { clearTimeout(wsDragArmTimer.current); wsDragArmTimer.current = null }
|
|
9212
|
+
const committed = dragDroppedRef.current
|
|
9213
|
+
dragDroppedRef.current = false
|
|
9214
|
+
const cancelled = dragCancelledRef.current
|
|
9215
|
+
dragCancelledRef.current = false
|
|
9216
|
+
const live = dragStateRef.current
|
|
9135
9217
|
setDrag(null)
|
|
9218
|
+
// v0.36.10: no drop AND not cancelled means Chromium killed a
|
|
9219
|
+
// RESTING drag (mouse-up as dragleave+dragend, drop never fires).
|
|
9220
|
+
// Honor the armed {over} instead of throwing the gesture away —
|
|
9221
|
+
// the user aimed at a visible slot/into for seconds. Escape still
|
|
9222
|
+
// cancels for real.
|
|
9223
|
+
if (committed || cancelled || !live || live.kind !== 'folder' || !live.over) return
|
|
9224
|
+
const over = live.over
|
|
9225
|
+
if (over.kind === 'folder') {
|
|
9226
|
+
if (over.zone === 'before' || over.zone === 'after') commitFolderReorder(live.source, over.target, over.zone, over.node)
|
|
9227
|
+
else commitFolderMoveInto(live.source, over.target)
|
|
9228
|
+
} else if (over.kind === 'ws-level') {
|
|
9229
|
+
commitFolderMoveToLevelEnd(live.source)
|
|
9230
|
+
} else if (over.kind === 'workspace' && over.half && over.folderLift && over.workspace) {
|
|
9231
|
+
commitFolderLift(live.source, over.workspace, over.half)
|
|
9232
|
+
} else if (over.kind === 'workspace' && over.half && over.workspace) {
|
|
9233
|
+
commitLevelReorder({ kind: 'folder', source: live.source }, { kind: 'workspace', workspace: over.workspace }, over.half)
|
|
9234
|
+
}
|
|
9136
9235
|
},
|
|
9137
9236
|
onDragOver: (event) => {
|
|
9138
9237
|
// Three bands on a group row: the outer 30% bands reorder this
|
|
@@ -9158,7 +9257,13 @@ window.__ModuleLoader__.load({
|
|
|
9158
9257
|
// The three bands still decide the FIRST entry, so INTO (move the
|
|
9159
9258
|
// source inside this group) stays reachable.
|
|
9160
9259
|
const slotOpen = drag.over !== null && drag.over.kind === 'folder' && drag.over.target === idPath && (drag.over.zone === 'before' || drag.over.zone === 'after')
|
|
9161
|
-
|
|
9260
|
+
// v0.36.10: an open slot no longer locks the verdict. The slot's
|
|
9261
|
+
// padding band keeps the slot (rowHalf is null there and the
|
|
9262
|
+
// zone-null branch below holds), but the ORIGINAL content box
|
|
9263
|
+
// re-judges the three bands — the INTO middle stays reachable.
|
|
9264
|
+
const zone = slotOpen
|
|
9265
|
+
? (pointerInRowBox(event) ? rowZone(event) : rowHalf(event))
|
|
9266
|
+
: rowZone(event)
|
|
9162
9267
|
const isFolder = dragMatches('folder')
|
|
9163
9268
|
const isWorkspace = dragMatches('workspace')
|
|
9164
9269
|
if (isFolder) {
|
|
@@ -9226,9 +9331,12 @@ window.__ModuleLoader__.load({
|
|
|
9226
9331
|
event.stopPropagation()
|
|
9227
9332
|
try { event.dataTransfer.dropEffect = 'move' } catch { }
|
|
9228
9333
|
const reorder = zone !== 'into'
|
|
9334
|
+
// The row's node rides along: the dragEnd fallback (a Chromium
|
|
9335
|
+
// resting drag that never fires drop) commits from {over} alone
|
|
9336
|
+
// and needs it for the target-block anchor.
|
|
9229
9337
|
const nextOver = reorder
|
|
9230
|
-
? { kind: 'folder', target: idPath, zone }
|
|
9231
|
-
: { kind: 'folder', target: idPath, zone: 'into' }
|
|
9338
|
+
? { kind: 'folder', target: idPath, zone, node }
|
|
9339
|
+
: { kind: 'folder', target: idPath, zone: 'into', node }
|
|
9232
9340
|
setDrag(current => (current && current.over && current.over.kind === 'folder' && current.over.target === idPath && current.over.zone === nextOver.zone)
|
|
9233
9341
|
? current
|
|
9234
9342
|
: (current ? { ...current, over: nextOver } : current))
|
|
@@ -9237,6 +9345,7 @@ window.__ModuleLoader__.load({
|
|
|
9237
9345
|
if (dragMatches('folder')) {
|
|
9238
9346
|
event.preventDefault()
|
|
9239
9347
|
event.stopPropagation()
|
|
9348
|
+
dragDroppedRef.current = true
|
|
9240
9349
|
const zone = drag.over && drag.over.kind === 'folder' && drag.over.target === idPath ? drag.over.zone : 'into'
|
|
9241
9350
|
if (zone === 'before' || zone === 'after') commitFolderReorder(drag.source, idPath, zone, node)
|
|
9242
9351
|
else commitFolderMoveInto(drag.source, idPath)
|
|
@@ -9248,6 +9357,7 @@ window.__ModuleLoader__.load({
|
|
|
9248
9357
|
if (isOwnDescendantScope(diskScopeOf(idPath), drag.source.workspaceId)) return
|
|
9249
9358
|
event.preventDefault()
|
|
9250
9359
|
event.stopPropagation()
|
|
9360
|
+
dragDroppedRef.current = true
|
|
9251
9361
|
// The live {over} wins; the geometry is the fallback for a drop that
|
|
9252
9362
|
// lands before any hover frame recorded a band on this row.
|
|
9253
9363
|
const overZone = drag.over && drag.over.kind === 'folder' && drag.over.target === idPath && (drag.over.zone === 'before' || drag.over.zone === 'after') ? drag.over.zone : null
|
|
@@ -9470,6 +9580,47 @@ window.__ModuleLoader__.load({
|
|
|
9470
9580
|
// limitation as that dialog: same-named groups on different disk
|
|
9471
9581
|
// levels move together). Self / descendant targets are already
|
|
9472
9582
|
// refused at hover time; re-checked here as belt and braces.
|
|
9583
|
+
// v0.36.10 GROUP LIFT. Dropping a group row on a cross-level
|
|
9584
|
+
// workspace row's EDGE band (same disk scope) means: take the whole
|
|
9585
|
+
// group out of its parent, give it the target level's name prefix and
|
|
9586
|
+
// place it beside that row — "把平行帮拖到和用户目录、工作平级".
|
|
9587
|
+
// Mirrors commitWorkspaceLift: member renames plus a level token,
|
|
9588
|
+
// never a disk move; the hover gate already checked scope and nesting.
|
|
9589
|
+
const commitFolderLift = (source, workspace, zone) => {
|
|
9590
|
+
setDrag(null)
|
|
9591
|
+
if (!canDragWorkspace) { console.warn('[bw] folder lift refused: workspace reorder unavailable on this host'); return }
|
|
9592
|
+
const targetLevel = workspace.levelKey || ''
|
|
9593
|
+
if (targetLevel === source.idPath || targetLevel.startsWith(source.idPath + '/')) { console.warn('[bw] folder lift refused: target level inside the source', targetLevel); return }
|
|
9594
|
+
const sourceLeaf = namePathOf(source.idPath).split('/').pop()
|
|
9595
|
+
// The target level's name prefix: '' = the disk root (the group
|
|
9596
|
+
// becomes a plain top-level group), otherwise the level key's name
|
|
9597
|
+
// path after the owner separator.
|
|
9598
|
+
const cutAt = targetLevel.lastIndexOf('//')
|
|
9599
|
+
const levelNamePath = cutAt === -1 ? '' : targetLevel.slice(cutAt + 2)
|
|
9600
|
+
const newSourcePath = levelNamePath === '' ? sourceLeaf : levelNamePath + '/' + sourceLeaf
|
|
9601
|
+
const prefix = source.path + '/'
|
|
9602
|
+
const affected = (items || []).filter((w) => String(w.title || '').startsWith(prefix))
|
|
9603
|
+
if (!affected.length) { console.warn('[bw] folder lift refused: no members under', source.path); return }
|
|
9604
|
+
Promise.resolve()
|
|
9605
|
+
.then(async () => {
|
|
9606
|
+
for (const w of affected) {
|
|
9607
|
+
await renameWorkspace(w.workspaceId, newSourcePath + String(w.title).slice(source.path.length))
|
|
9608
|
+
}
|
|
9609
|
+
})
|
|
9610
|
+
.then(() => {
|
|
9611
|
+
removeLevelToken(source.levelKey || '', sourceLeaf)
|
|
9612
|
+
insertLevelToken(targetLevel, sourceLeaf, 'w:' + workspace.workspaceId, zone === 'after' ? 'after' : 'before')
|
|
9613
|
+
// Carry the group's own member order across to its NEW level
|
|
9614
|
+
// key, so the members keep their inside order after the rename.
|
|
9615
|
+
const oldContainer = folderContainerOf(source.idPath)
|
|
9616
|
+
if (oldContainer && typeof actions.setFolderOrder === 'function') {
|
|
9617
|
+
const owner = ownerPrefixOf(source.idPath)
|
|
9618
|
+
const newKey = owner === '' ? newSourcePath : owner + '//' + newSourcePath
|
|
9619
|
+
if (newKey !== source.idPath) actions.setFolderOrder(newKey, levelSeqOf(oldContainer).map(levelTokenOf))
|
|
9620
|
+
}
|
|
9621
|
+
})
|
|
9622
|
+
.catch(fail)
|
|
9623
|
+
}
|
|
9473
9624
|
const commitFolderMoveInto = (source, targetIdPath) => {
|
|
9474
9625
|
setDrag(null)
|
|
9475
9626
|
const targetPath = namePathOf(targetIdPath)
|
|
@@ -9486,10 +9637,37 @@ window.__ModuleLoader__.load({
|
|
|
9486
9637
|
const prefix = source.path + '/'
|
|
9487
9638
|
const affected = (items || []).filter((w) => String(w.title || '').startsWith(prefix))
|
|
9488
9639
|
if (!affected.length) { console.warn('[bw] folder move refused: no members under', source.path); return }
|
|
9640
|
+
// v0.36.10 NEST semantics: the group keeps its identity UNDER the
|
|
9641
|
+
// target (工作/平行帮 → 工作/Fis管理后台/平行帮). The old prefix
|
|
9642
|
+
// swap merged the members straight into the target's own level —
|
|
9643
|
+
// "平行帮/Web" became "Fis管理后台/Web", colliding with the
|
|
9644
|
+
// target's own Web and dying on workspace/name-conflict before
|
|
9645
|
+
// anything moved: the "平级的没法拖动" report. Nesting names every
|
|
9646
|
+
// member BELOW the moved group, which cannot collide with the
|
|
9647
|
+
// target's direct children.
|
|
9648
|
+
const sourceLeaf = namePathOf(source.idPath).split('/').pop()
|
|
9649
|
+
const newSourcePath = targetPath + '/' + sourceLeaf
|
|
9489
9650
|
Promise.resolve()
|
|
9490
9651
|
.then(async () => {
|
|
9491
9652
|
for (const w of affected) {
|
|
9492
|
-
await renameWorkspace(w.workspaceId,
|
|
9653
|
+
await renameWorkspace(w.workspaceId, newSourcePath + String(w.title).slice(source.path.length))
|
|
9654
|
+
}
|
|
9655
|
+
})
|
|
9656
|
+
.then(() => {
|
|
9657
|
+
// The group token moves from the source's level to INSIDE the
|
|
9658
|
+
// target group, and the member order follows to the new key.
|
|
9659
|
+
removeLevelToken(source.levelKey || '', sourceLeaf)
|
|
9660
|
+
const targetContainer = folderContainerOf(targetIdPath)
|
|
9661
|
+
if (targetContainer) {
|
|
9662
|
+
const tokens = levelSeqOf(targetContainer).map(levelTokenOf)
|
|
9663
|
+
if (tokens.length) insertLevelToken(targetIdPath, sourceLeaf, tokens[tokens.length - 1], 'after')
|
|
9664
|
+
else if (typeof actions.setFolderOrder === 'function') actions.setFolderOrder(targetIdPath, [sourceLeaf])
|
|
9665
|
+
}
|
|
9666
|
+
const oldContainer = folderContainerOf(source.idPath)
|
|
9667
|
+
if (oldContainer && typeof actions.setFolderOrder === 'function') {
|
|
9668
|
+
const owner = ownerPrefixOf(source.idPath)
|
|
9669
|
+
const newKey = owner === '' ? newSourcePath : owner + '//' + newSourcePath
|
|
9670
|
+
if (newKey !== source.idPath) actions.setFolderOrder(newKey, levelSeqOf(oldContainer).map(levelTokenOf))
|
|
9493
9671
|
}
|
|
9494
9672
|
})
|
|
9495
9673
|
// (v0.36.4) NO automatic expand after a move-into. v0.36.3 opened
|