dsh-better-workspace 0.34.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +81 -75
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
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.
|
|
3
|
+
"version": "0.35.0",
|
|
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
|
@@ -4794,7 +4794,11 @@ window.__ModuleLoader__.load({
|
|
|
4794
4794
|
// cancels it, so text still starts and truncates where it did.
|
|
4795
4795
|
'.bw-row-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:3px;margin:-3px}',
|
|
4796
4796
|
'.bw-session-row .bw-row-label{padding:0;margin:0 6px 0 4px}',
|
|
4797
|
-
|
|
4797
|
+
/* Marquee: Chromium keeps painting the ellipsis at the right edge of a
|
|
4798
|
+
scrolling ellipsis element (engine behavior; official rows share it).
|
|
4799
|
+
Switch to clip while scrolling — the fade masks carry both edges and
|
|
4800
|
+
the full heading reads cleanly; the resting state keeps ellipsis. */
|
|
4801
|
+
'.bw-session-row .bw-row-label[data-scrolled]{text-overflow:clip;mask-image:linear-gradient(to right,transparent,#000 12px);-webkit-mask-image:linear-gradient(to right,transparent,#000 12px)}',
|
|
4798
4802
|
'.bw-session-row .bw-row-label[data-clipped]{mask-image:linear-gradient(to left,transparent,#000 12px);-webkit-mask-image:linear-gradient(to left,transparent,#000 12px)}',
|
|
4799
4803
|
'.bw-session-row .bw-row-label[data-scrolled][data-clipped]{mask-image:linear-gradient(to right,transparent,#000 12px,#000 calc(100% - 12px),transparent);-webkit-mask-image:linear-gradient(to right,transparent,#000 12px,#000 calc(100% - 12px),transparent)}',
|
|
4800
4804
|
'.bw-row-count{flex:none;display:none;font-size:12px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
|
|
@@ -6226,58 +6230,28 @@ window.__ModuleLoader__.load({
|
|
|
6226
6230
|
}
|
|
6227
6231
|
const archivedTitle = official('row.archived')
|
|
6228
6232
|
const pinnedTitle = official('row.pinned')
|
|
6229
|
-
//
|
|
6230
|
-
//
|
|
6231
|
-
//
|
|
6232
|
-
//
|
|
6233
|
-
//
|
|
6234
|
-
//
|
|
6235
|
-
//
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
const [menuOpen, setMenuOpen] = React.useState(false)
|
|
6239
|
-
const menuOpenPair = React.useMemo(() => [menuOpen, setMenuOpen], [menuOpen])
|
|
6240
|
-
const menuSeatChildren = inlineMenu && Array.isArray(sessionActions.menuSeat)
|
|
6241
|
-
? sessionActions.menuSeat.map((seat, index) => E(ShippedSeatOccupant, {
|
|
6242
|
-
key: 'menu-seat-' + index,
|
|
6243
|
-
seat,
|
|
6244
|
-
ownerProps: { sessionId: node.id, displayTitle: node.leaf || node.title },
|
|
6245
|
-
t: seat.t,
|
|
6246
|
-
// The menu.item hook-context factory (ui-slots) hands occupants
|
|
6247
|
-
// the open-state pair; reproduced here for the in-row menu.
|
|
6248
|
-
useMenuOpenState: () => menuOpenPair,
|
|
6249
|
-
}))
|
|
6250
|
-
: null
|
|
6251
|
-
// The shipped row.action seat renders the OFFICIAL pin/archive quick
|
|
6252
|
-
// buttons; our own fallback buttons step aside so the row never shows
|
|
6253
|
-
// two sets (same rule as the schedule mark's leading cell).
|
|
6233
|
+
// Row-trailing quick actions: the shipped row.action seat renders the
|
|
6234
|
+
// OFFICIAL pin/archive buttons; our own fallback buttons render only
|
|
6235
|
+
// where the seat has no entries, so the row never shows two sets. The
|
|
6236
|
+
// in-row "..." menu (added v0.34.0) is RETIRED (issue #12): the context
|
|
6237
|
+
// menu already carries every entry, and the menu.item occupants depend
|
|
6238
|
+
// on slot-level inject hooks (useShortcuts on 0.2.1-rc.2) that an
|
|
6239
|
+
// in-row reproduction cannot supply — one missing hook took the whole
|
|
6240
|
+
// sidebar down. Each seat occupant sits in a QuietBoundary so a broken
|
|
6241
|
+
// seat degrades to a missing button, never a blank panel.
|
|
6254
6242
|
const quickSeatUsed = Array.isArray(sessionActions.rowActionSeat) && sessionActions.rowActionSeat.length > 0
|
|
6255
|
-
const strip = !node.blank && sessionActions && (
|
|
6243
|
+
const strip = !node.blank && sessionActions && sessionActions.quick && (quickSeatUsed || sessionActions.pin || sessionActions.archive)
|
|
6256
6244
|
? E('span', { className: 'bw-row-actions', onClick: (e) => e.stopPropagation() },
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
title: sessionActions.triggerTitle,
|
|
6268
|
-
onClick: () => setMenuOpen((open) => !open),
|
|
6269
|
-
}, icon('IconEllipsisOutline16', 15)),
|
|
6270
|
-
items: sessionActions.menuItems(node),
|
|
6271
|
-
onSelect: (id) => { setMenuOpen(false); sessionActions.pick(node, id) },
|
|
6272
|
-
}, menuSeatChildren)
|
|
6273
|
-
: sessionActions.trigger ? E('button', {
|
|
6274
|
-
type: 'button',
|
|
6275
|
-
className: 'bw-icon-btn',
|
|
6276
|
-
'aria-label': official('actions.session.aria', { name: node.leaf || node.title }) || '...',
|
|
6277
|
-
title: sessionActions.triggerTitle,
|
|
6278
|
-
onClick: (e) => sessionActions.openMenu(node, e),
|
|
6279
|
-
}, icon('IconEllipsisOutline16', 15)) : null,
|
|
6280
|
-
!quickSeatUsed && sessionActions.quick && sessionActions.pin && !node.archived ? E('button', {
|
|
6245
|
+
quickSeatUsed
|
|
6246
|
+
? sessionActions.rowActionSeat.map((seat, index) => E(QuietBoundary, { key: 'row-action-seat-' + index },
|
|
6247
|
+
E(ShippedSeatOccupant, {
|
|
6248
|
+
key: 'row-action-seat-' + index,
|
|
6249
|
+
seat,
|
|
6250
|
+
ownerProps: { sessionId: node.id, displayTitle: node.leaf || node.title },
|
|
6251
|
+
t: seat.t,
|
|
6252
|
+
})))
|
|
6253
|
+
: null,
|
|
6254
|
+
!quickSeatUsed && sessionActions.pin && !node.archived ? E('button', {
|
|
6281
6255
|
type: 'button',
|
|
6282
6256
|
className: 'bw-icon-btn',
|
|
6283
6257
|
'aria-label': official(node.pinned ? 'menu.unpinSession' : 'menu.pinSession') || '',
|
|
@@ -6302,7 +6276,6 @@ window.__ModuleLoader__.load({
|
|
|
6302
6276
|
let row = E('div', {
|
|
6303
6277
|
className: cls('bw-row', 'bw-session-row', current && 'bw-row-current',
|
|
6304
6278
|
node.archived && 'bw-row-archived',
|
|
6305
|
-
menuOpen && 'menu-open',
|
|
6306
6279
|
dropHalf === 'before' && 'bw-drop-before', dropHalf === 'after' && 'bw-drop-after'),
|
|
6307
6280
|
'data-row-key': dataRowKey,
|
|
6308
6281
|
style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}) },
|
|
@@ -6361,7 +6334,6 @@ window.__ModuleLoader__.load({
|
|
|
6361
6334
|
content: sessionActions.hoverBody(node, status, state),
|
|
6362
6335
|
openDelayMs: 800,
|
|
6363
6336
|
grouped: true,
|
|
6364
|
-
disabled: menuOpen,
|
|
6365
6337
|
// copyText present REQUIRES both labels (primitives HoverCard
|
|
6366
6338
|
// contract): copy only renders where the official copy survives.
|
|
6367
6339
|
}, copyText && copyLabel && copiedLabel ? { copyText, copyLabel, copiedLabel } : {}))
|
|
@@ -7246,7 +7218,14 @@ window.__ModuleLoader__.load({
|
|
|
7246
7218
|
/* --------- settings → plug-ins card (accordion like official cards) --------- */
|
|
7247
7219
|
|
|
7248
7220
|
function BetterWorkspacePluginCard(props) {
|
|
7249
|
-
const { useStore, actions, t } = props
|
|
7221
|
+
const { useStore: injectedUseStore, actions: injectedActions, localStore: localStoreProp, t } = props
|
|
7222
|
+
// Issue #13: same private-store switch as the browser seat — the card
|
|
7223
|
+
// keeps working no matter what a shadowing occupant does to the props.
|
|
7224
|
+
const localStoreFace = typeof localStoreProp === 'function' ? localStoreProp() : null
|
|
7225
|
+
const useStore = localStoreFace && typeof localStoreFace.useStore === 'function'
|
|
7226
|
+
? localStoreFace.useStore
|
|
7227
|
+
: injectedUseStore
|
|
7228
|
+
const actions = localStoreFace && localStoreFace.actions ? localStoreFace.actions : injectedActions
|
|
7250
7229
|
// The bundle page (plugins.bundle.config, dsh 0.1.6-alpha.2+) renders
|
|
7251
7230
|
// this card inline with view: 'page' — no accordion chrome there, the
|
|
7252
7231
|
// settings body sits directly under a one-line title.
|
|
@@ -7549,7 +7528,8 @@ window.__ModuleLoader__.load({
|
|
|
7549
7528
|
const {
|
|
7550
7529
|
wide, expandSidebar,
|
|
7551
7530
|
useSessions, useSessionPendingInteraction, useSessionStatus, useWorkspaces,
|
|
7552
|
-
useStore, actions,
|
|
7531
|
+
useStore: injectedUseStore, actions: injectedActions,
|
|
7532
|
+
localStore: localStoreProp,
|
|
7553
7533
|
startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace,
|
|
7554
7534
|
archiveSession, createWorkspace, pinSession, unpinSession, unarchiveSession,
|
|
7555
7535
|
insertWorkspaceBefore, insertSessionBefore,
|
|
@@ -7561,6 +7541,17 @@ window.__ModuleLoader__.load({
|
|
|
7561
7541
|
presetFaceOf,
|
|
7562
7542
|
t,
|
|
7563
7543
|
} = props
|
|
7544
|
+
// Issue #13: local reads and writes go through OUR store instance, never
|
|
7545
|
+
// the renderer-synthesized props — a shadowing occupant can swap those
|
|
7546
|
+
// props for its own store (issue #13). Host projections (useSessions /
|
|
7547
|
+
// useWorkspaces) stay on the injected props; only the plugin's OWN view
|
|
7548
|
+
// state switches. Names keep their original spelling so the component
|
|
7549
|
+
// body needs no other change.
|
|
7550
|
+
const localStoreFace = typeof localStoreProp === 'function' ? localStoreProp() : null
|
|
7551
|
+
const useStore = localStoreFace && typeof localStoreFace.useStore === 'function'
|
|
7552
|
+
? localStoreFace.useStore
|
|
7553
|
+
: injectedUseStore
|
|
7554
|
+
const actions = localStoreFace && localStoreFace.actions ? localStoreFace.actions : injectedActions
|
|
7564
7555
|
// v0.32.2: resolve the stable getter ONCE per render (see
|
|
7565
7556
|
// browserInjected for why this is a function and not a value prop).
|
|
7566
7557
|
// Every consumer below keeps reading this local, so a late-arriving
|
|
@@ -7774,13 +7765,14 @@ window.__ModuleLoader__.load({
|
|
|
7774
7765
|
// The shipped schedule mark's seat occupant (see scheduleSeatOf); absent
|
|
7775
7766
|
// whenever the Schedule overlay is off, which is the shipped default.
|
|
7776
7767
|
const scheduleSeat = typeof scheduleSeatReader === 'function' ? scheduleSeatReader() : null
|
|
7777
|
-
// The shipped session-row seats (official Rows.tsx):
|
|
7778
|
-
// occupants
|
|
7779
|
-
// occupants become the official pin/archive quick buttons (our own
|
|
7768
|
+
// The shipped session-row seats (official Rows.tsx): row.action
|
|
7769
|
+
// occupants render the official pin/archive quick buttons (our own
|
|
7780
7770
|
// fallback buttons step aside), session.row.hover decorates the hover
|
|
7781
|
-
// card.
|
|
7771
|
+
// card. The menu.item seat is deliberately NOT read anymore: its
|
|
7772
|
+
// occupants need slot-level inject hooks (useShortcuts on 0.2.1-rc.2)
|
|
7773
|
+
// and the in-row menu they anchored was retired in issue #12. All read
|
|
7774
|
+
// off the ledger like the schedule mark.
|
|
7782
7775
|
const seatEntriesOf = typeof seatEntriesReader === 'function' ? seatEntriesReader : null
|
|
7783
|
-
const rowMenuSeat = seatEntriesOf ? seatEntriesOf('sidebar.workspaces.session.menu.item') : []
|
|
7784
7776
|
const rowActionSeat = seatEntriesOf ? seatEntriesOf('sidebar.workspaces.session.row.action') : []
|
|
7785
7777
|
const hoverSeat = seatEntriesOf ? seatEntriesOf('sidebar.session.row.hover') : []
|
|
7786
7778
|
/** Effective catalog row of one shipped command (current keybinding, label). */
|
|
@@ -8431,22 +8423,13 @@ window.__ModuleLoader__.load({
|
|
|
8431
8423
|
}
|
|
8432
8424
|
: undefined,
|
|
8433
8425
|
openMenu: (node, e) => openCtx('session', node, e),
|
|
8434
|
-
//
|
|
8435
|
-
//
|
|
8436
|
-
//
|
|
8437
|
-
//
|
|
8438
|
-
//
|
|
8439
|
-
//
|
|
8440
|
-
// own (bw extensions: move-group / customize) falls back to the
|
|
8441
|
-
// ctx surface's handler through a transient payload.
|
|
8442
|
-
menuItems: (node) => (typeof sessionMenuEntriesFor === 'function' ? sessionMenuEntriesFor(node) : []),
|
|
8443
|
-
menuSeat: rowMenuSeat,
|
|
8426
|
+
// Row-trailing quick actions: the shipped row.action seat carries the
|
|
8427
|
+
// official pin/archive buttons; our own buttons render only where the
|
|
8428
|
+
// seat is empty. The in-row "..." menu is RETIRED (issue #12 — its
|
|
8429
|
+
// menu.item occupants need slot-level inject hooks an in-row
|
|
8430
|
+
// reproduction cannot supply); the context menu keeps every entry
|
|
8431
|
+
// through sessionMenuEntriesFor + handleCtxPick.
|
|
8444
8432
|
rowActionSeat,
|
|
8445
|
-
pick: (node, id) => {
|
|
8446
|
-
if (typeof pickSessionAction === 'function' && pickSessionAction(node, id)) return
|
|
8447
|
-
setCtx({ kind: 'session', payload: node, x: 0, y: 0 })
|
|
8448
|
-
if (typeof handleCtxPick === 'function') handleCtxPick(id)
|
|
8449
|
-
},
|
|
8450
8433
|
rename: (node) => setDialog({ kind: 'sess-rename', session: node }),
|
|
8451
8434
|
hoverBody: (node, status) => sessionHoverBody(node, status),
|
|
8452
8435
|
explainArchived: () => {
|
|
@@ -10321,6 +10304,10 @@ window.__ModuleLoader__.load({
|
|
|
10321
10304
|
// closure live, so every render and every menu build re-asks; hosts
|
|
10322
10305
|
// without the service keep returning null, which simply hides the row.
|
|
10323
10306
|
presetFaceOf: () => presetFace,
|
|
10307
|
+
// Issue #13: the PRIVATE store face — a stable getter like presetFaceOf
|
|
10308
|
+
// (the slot renderer caches inject results; keep the same discipline
|
|
10309
|
+
// even though this face never changes).
|
|
10310
|
+
localStore: () => ({ useStore: useViewStore, actions: viewInstance.actions }),
|
|
10324
10311
|
// The shipped sidebar's OWN face (dsh 0.1.7-rc.2): the shortcut request
|
|
10325
10312
|
// channel and the catalog of effective keybindings. Both belong to the
|
|
10326
10313
|
// entry this seat shadows, so they are read off the ledger and consumed
|
|
@@ -10435,6 +10422,22 @@ window.__ModuleLoader__.load({
|
|
|
10435
10422
|
// One shared store handle: the browser and the settings page must see the
|
|
10436
10423
|
// same persisted state (expansion, folder list, prefs, styling).
|
|
10437
10424
|
const viewStore = createViewStore()
|
|
10425
|
+
// Issue #13 hardening: our OWN store instance. The renderer synthesizes
|
|
10426
|
+
// props.useStore/actions from the seat's store handle — and a third-party
|
|
10427
|
+
// occupant wrapper (im-connect, issue #13) that shadows the workspace seat
|
|
10428
|
+
// replaces those props with ITS channel store, silently severing every
|
|
10429
|
+
// local read and write. The bundle therefore keeps one PRIVATE instance,
|
|
10430
|
+
// handed to the components through the inject face: local state no longer
|
|
10431
|
+
// depends on what the renderer (or anything shadowing it) put into props.
|
|
10432
|
+
// Same persist key, so on a healthy host both instances hydrate the same
|
|
10433
|
+
// snapshot; the renderer-side instance simply never writes again (no
|
|
10434
|
+
// consumer dispatches it) and ours becomes the only live one.
|
|
10435
|
+
const viewInstance = viewStore.create()
|
|
10436
|
+
const useViewStore = typeof React.useSyncExternalStore === 'function'
|
|
10437
|
+
? (selector) => React.useSyncExternalStore(
|
|
10438
|
+
viewInstance.subscribe,
|
|
10439
|
+
() => { try { return selector(viewInstance.getSnapshot()) } catch (error) { return undefined } })
|
|
10440
|
+
: null
|
|
10438
10441
|
|
|
10439
10442
|
// Cold-restart title fallback (see the title-cache block). Hydration
|
|
10440
10443
|
// reads localStorage synchronously here, before the first render, so a
|
|
@@ -10510,6 +10513,9 @@ window.__ModuleLoader__.load({
|
|
|
10510
10513
|
key: 'dsh-better-workspace',
|
|
10511
10514
|
locale: NS,
|
|
10512
10515
|
store: viewStore,
|
|
10516
|
+
// Issue #13: the settings card reads/writes through our PRIVATE store
|
|
10517
|
+
// instance too (same rationale as the browser seat).
|
|
10518
|
+
inject: () => ({ localStore: () => ({ useStore: useViewStore, actions: viewInstance.actions }) }),
|
|
10513
10519
|
},
|
|
10514
10520
|
BetterWorkspacePluginCard,
|
|
10515
10521
|
))
|