dsh-better-workspace 0.6.0 → 0.7.1

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 CHANGED
@@ -59,7 +59,7 @@ web/ ← 虚拟分组(不是真实目录,纯命名层级)
59
59
  ### 其余亮点
60
60
 
61
61
  - **文件夹式收起**:点工作区行 = 收起/展开该工作区的全部会话(收起后行上保留会话数角标);会话分组同样可收起;搜索时自动全部展开。
62
- - **拖拽还原原版能力**:工作区行可拖拽——拖到另一工作区行上/下半即调整顺序(写回宿主 registry 序),拖到分组行则移动进该分组;会话行可拖拽——同工作区拖到另一会话上/下调整顺序,拖到会话子分组即移动进该分组。显示顺序 = 宿主手动序,拖拽结果立即生效。
62
+ - **拖拽还原原版能力**:工作区行可拖拽——拖到另一工作区行上/下半即调整顺序(写回宿主 registry 序),拖到分组行则移动进该分组;会话行可拖拽——同工作区拖到另一会话上/下调整顺序,拖到会话子分组即移动进该分组。显示顺序 = 宿主手动序,拖拽结果立即生效。拖拽工作区期间,单链合并显示的行(如 工作/A)临时展开回文件夹树——路径里的每一级都是可投放的分组行,松手后自动合并回去。
63
63
  - **不丢原生能力**:会话行(打开/重命名/分叉/归档)、每工作区「新会话」、当前会话高亮、官方状态灯(蓝色运行环 / 琥珀待交互 / 绿色完成提醒)、**活跃定时任务角标**(有活动 Schedule 的会话显示闹钟图标,与官方行一致)、未分组会话兜底、中英双语跟随界面语言。
64
64
  - **对话空态页同样生效**:会话空态页的「添加工作区」菜单走的也是本插件的拾取交互,同样带所属分组弹窗。
65
65
 
package/README_EN.md CHANGED
@@ -57,7 +57,7 @@ Session titles nest on `/` too (e.g. `test1/plugin maintenance`). Session groups
57
57
  ### More highlights
58
58
 
59
59
  - **Folder-style collapse**: clicking a workspace row collapses/expands all of its sessions (the row keeps a session counter when collapsed); session groups collapse too; search force-expands everything.
60
- - **Native drag restored**: drag a workspace row above/below another to reorder (writes back the host registry order) or onto a folder row to move it into that group; drag session rows to reorder within the same workspace or onto a session group to move. Display order = host manual order, drag results are visible immediately.
60
+ - **Native drag restored**: drag a workspace row above/below another to reorder (writes back the host registry order) or onto a folder row to move it into that group; drag session rows to reorder within the same workspace or onto a session group to move. Display order = host manual order, drag results are visible immediately. While dragging a workspace, merged single-chain rows (e.g. work/A) temporarily re-expand into folder rows — every level of the path becomes a drop target, and chains merge back when the drag ends.
61
61
  - **Native capabilities kept**: open/rename/fork/archive sessions, per-workspace new-session, current-session highlight, official status dots (blue running ring / amber pending / green done), **active-schedule badge** (alarm clock icon for sessions with active Schedule records, matching the official rows), ungrouped-session fallback, zh/en localization.
62
62
  - **Conversation hero too**: the empty-state “Add workspace” menu uses the same picking interaction with the same group popup.
63
63
 
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-better-workspace",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "main": "./src/index.js",
5
5
  "description": "侧边栏工作区层级树:名称中的 / 即虚拟分组(web/前端 · web/后端),添加工作区的目录流附所属分组弹窗,重命名即时重排层级,可新建空分组。Hierarchy tree for the DSH sidebar workspace list.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-better-workspace",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "DSH web plugin: a hierarchical workspace tree for the sidebar. Workspace titles containing \"/\" group into virtual folders (web/前端 · web/后端); the add-workspace directory flow gains a parent-group popup; renames re-derive the tree; a new-folder button creates empty levels.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -43,7 +43,8 @@
43
43
  "LICENSE"
44
44
  ],
45
45
  "scripts": {
46
- "test": "node --test tests/smoke.mjs"
46
+ "test": "node --test tests/smoke.mjs",
47
+ "version": "node -e \"const fs=require('fs');const m=JSON.parse(fs.readFileSync('dsh.plugin.json','utf8'));const v=JSON.parse(fs.readFileSync('package.json','utf8')).version;m.version=v;fs.writeFileSync('dsh.plugin.json',JSON.stringify(m,null,2)+'\\n')\" && git add dsh.plugin.json"
47
48
  },
48
49
  "keywords": [
49
50
  "dsh",
package/src/client.js CHANGED
@@ -65,7 +65,7 @@ window.__ModuleLoader__.load({
65
65
  'settings.expand': '展开',
66
66
  'settings.collapse': '收起',
67
67
  'settings.compactChains': '单链分组折叠显示',
68
- 'settings.compactChains.hint': '单层链合并为一行,出现多个子级时自动展开为树状;展开状态与自定义外观保存在当前浏览器。',
68
+ 'settings.compactChains.hint': '单层链合并为一行,出现多个子级时自动展开为树状;拖拽工作区期间单链临时展开回文件夹树,可放入任意一级;展开状态与自定义外观保存在当前浏览器。',
69
69
  'custom.title': '自定义外观',
70
70
  'custom.color': '颜色',
71
71
  'custom.glow': '发光',
@@ -151,7 +151,7 @@ window.__ModuleLoader__.load({
151
151
  'settings.expand': 'Expand',
152
152
  'settings.collapse': 'Collapse',
153
153
  'settings.compactChains': 'Merge single-child chains',
154
- 'settings.compactChains.hint': 'Single-child chains merge into one row; levels with multiple children expand as a tree. State and custom styling persist in this browser.',
154
+ 'settings.compactChains.hint': 'Single-child chains merge into one row; levels with multiple children expand as a tree. Chains re-expand into folder rows while you drag a workspace, so it can drop into any level. State and custom styling persist in this browser.',
155
155
  'custom.title': 'Customize',
156
156
  'custom.color': 'Color',
157
157
  'custom.glow': 'Glow',
@@ -1157,6 +1157,12 @@ window.__ModuleLoader__.load({
1157
1157
  const [customize, setCustomize] = React.useState(null) // { kind, entryKey, name }
1158
1158
  const [errorText, setErrorText] = React.useState(null)
1159
1159
  const [drag, setDrag] = React.useState(null) // { kind: 'workspace'|'session', source, over } | null
1160
+ // Workspace drags arm their state one frame LATE (see workspaceDragEvents):
1161
+ // arming synchronously re-renders during the dragstart dispatch, the chain
1162
+ // expansion inserts rows above the drag source, the cursor leaves the
1163
+ // source element, and Chromium cancels the whole gesture. dragEnd clears
1164
+ // the timer so a same-tick cancel never leaves a ghost drag behind.
1165
+ const wsDragArmTimer = React.useRef(null)
1160
1166
  const normalizedQuery = query.trim().toLowerCase()
1161
1167
  const now = Date.now()
1162
1168
 
@@ -1209,11 +1215,19 @@ window.__ModuleLoader__.load({
1209
1215
  ungrouped.sort((a, b) => b.updatedAt - a.updatedAt)
1210
1216
  }
1211
1217
 
1218
+ // While a workspace drag is active, single-child chains render UNCOMPRESSED:
1219
+ // a merged "group/workspace" row hides every folder level of the chain inside
1220
+ // its label, and those levels are exactly the drop targets for "move into
1221
+ // this group". Suspending the merge for the drag's duration re-exposes each
1222
+ // level as a real folder row (folder expansion defaults apply); when the
1223
+ // drag ends, the chains merge back. Session drags keep the merged view —
1224
+ // their drop targets live inside workspace rows, which compression merges.
1225
+ const draggingWorkspace = drag !== null && drag.kind === 'workspace'
1212
1226
  const tree = React.useMemo(() => {
1213
1227
  const built = buildTree(items, storeFolders)
1214
- if (!compactChains) return built
1228
+ if (!compactChains || draggingWorkspace) return built
1215
1229
  return { ...built, folders: built.folders.map(compressTree), workspaces: built.workspaces }
1216
- }, [items, storeFolders, compactChains])
1230
+ }, [items, storeFolders, compactChains, draggingWorkspace])
1217
1231
 
1218
1232
  const sessionsOf = (workspace) => {
1219
1233
  const rows = []
@@ -1316,9 +1330,28 @@ window.__ModuleLoader__.load({
1316
1330
  event.dataTransfer.effectAllowed = 'move'
1317
1331
  event.dataTransfer.setData('text/plain', workspace.workspaceId)
1318
1332
  } catch { /* drag payload is best-effort */ }
1319
- setDrag({ kind: 'workspace', source: { workspaceId: workspace.workspaceId, leaf: workspace.leaf, folderPath: workspace.folderPath || '' }, over: null })
1333
+ // Drag identity derives from the RAW title, never the displayed leaf:
1334
+ // a compressed single-chain row shows "group/workspace" as its leaf with
1335
+ // folderPath '' — carrying that would make move-into / cross-folder
1336
+ // drops rebuild titles like "x/group/workspace". The real leaf and
1337
+ // folder keep every drop target's rename correct.
1338
+ const segs = splitTitleSegs(workspace.title)
1339
+ const sourceLeaf = segs.length > 0 ? segs[segs.length - 1] : (workspace.leaf || String(workspace.workspaceId))
1340
+ const sourceFolder = segs.length > 1 ? segs.slice(0, -1).join('/') : ''
1341
+ // Deferred by one frame on purpose (Chromium cancels a just-started
1342
+ // drag whose source element is moved out from under the cursor; see
1343
+ // wsDragArmTimer above). By the next frame the gesture has settled
1344
+ // and the chain expansion is an ordinary mid-drag update.
1345
+ if (wsDragArmTimer.current !== null) clearTimeout(wsDragArmTimer.current)
1346
+ wsDragArmTimer.current = setTimeout(() => {
1347
+ wsDragArmTimer.current = null
1348
+ setDrag({ kind: 'workspace', source: { workspaceId: workspace.workspaceId, leaf: sourceLeaf, folderPath: sourceFolder }, over: null })
1349
+ }, 0)
1350
+ },
1351
+ onDragEnd: () => {
1352
+ if (wsDragArmTimer.current !== null) { clearTimeout(wsDragArmTimer.current); wsDragArmTimer.current = null }
1353
+ setDrag(null)
1320
1354
  },
1321
- onDragEnd: () => setDrag(null),
1322
1355
  onDragOver: (event) => {
1323
1356
  if (!dragMatches('workspace')) return
1324
1357
  event.preventDefault()