dsh-ide-git 0.16.0 → 0.16.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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-ide-git",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "main": "./src/index.js",
5
5
  "description": "IDE 级 Git 操作面板:分支树 + 提交图谱 + 变更/暂存/提交 + 提交详情。装了 dsh-better-sidebar 时挂成它的原生 tab(右侧栏 + 底部工作台);没装时直接挂 DSH 原生右侧栏,单独安装即可用。JetBrains 风格 Git 工具窗口 for DeepSeek Harness.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ide-git",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "DSH plugin: an IDE-grade Git tool window as a native dsh-better-sidebar tab — branch tree, commit graph, changes, commit details, JetBrains-style actions, in the right sidebar and the bottom panel. | DSH 插件:IDE 级 Git 工具窗口,以 dsh-better-sidebar 原生 Tab 挂载——分支树 / 提交图谱 / 变更与提交详情 / JetBrains 风格操作,右侧栏与底部面板双布局自适应。",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -8243,7 +8243,7 @@ window.__ModuleLoader__.load({
8243
8243
  fetch — a BROKEN stem: the references come down, nothing is merged;
8244
8244
  pull — a solid stem pointing down: the remote lands here;
8245
8245
  push — a solid stem pointing up: the local goes out. */
8246
- fetch: ['M8 2v2', 'M8 5.6v2.2', 'M4.6 6.4 8 9.8l3.4-3.4', 'M3 13h10'],
8246
+ fetch: ['M8 2v1.6', 'M8 4.8v1.6', 'M8 7.7v1.6', 'M4.6 6.4 8 9.8l3.4-3.4', 'M3 13h10'],
8247
8247
  pull: ['M8 2v7.8', 'M4.6 6.4 8 9.8l3.4-3.4', 'M3 13h10'],
8248
8248
  push: ['M8 9.8V2.2', 'M4.6 5.6 8 2.2l3.4 3.4', 'M3 13h10'],
8249
8249
  plus: ['M8 3v10', 'M3 8h10'],
@@ -8314,9 +8314,9 @@ window.__ModuleLoader__.load({
8314
8314
  const cardRef = useRef(null)
8315
8315
  const closeRef = useRef(props.onClose)
8316
8316
  const [position, setPosition] = useState({ left: props.anchor.x, top: props.anchor.y })
8317
- /* The open submenu: { items, left, top } in wrapper-relative pixels, or
8318
- null. Hover-driven — moving onto a different parent row swaps or
8319
- closes it, so no timers are needed. */
8317
+ /* The open submenu: { items, top, flip } or null. Hover-driven — moving
8318
+ onto a different parent row swaps or closes it, so no timers are
8319
+ needed. Horizontal placement is pure CSS against the wrapper. */
8320
8320
  const [sub, setSub] = useState(null)
8321
8321
  useEffect(() => { closeRef.current = props.onClose })
8322
8322
  // Layout effect: the clamp below runs before the browser paints, so the menu
@@ -8358,10 +8358,11 @@ window.__ModuleLoader__.load({
8358
8358
  const cardRect = card.getBoundingClientRect()
8359
8359
  const itemRect = event.currentTarget.getBoundingClientRect()
8360
8360
  const bounds = props.boundsWidth === undefined ? 0 : props.boundsWidth
8361
- const SUB_W = 220
8362
- let left = cardRect.width + 2
8363
- if (bounds > 0 && position.left + left + SUB_W > bounds - 4) left = -SUB_W - 2
8364
- setSub({ items: item.items, left: left, top: Math.max(0, itemRect.top - cardRect.top - 4) })
8361
+ /* Direction only — the flush offset is CSS (`left:100%` / `right:100%`
8362
+ against the wrapper, whose width IS the parent card), so the seam is
8363
+ zero no matter how wide the submenu actually renders. */
8364
+ const flip = bounds > 0 && position.left + cardRect.width + 220 > bounds - 4
8365
+ setSub({ items: item.items, flip: flip, top: Math.max(0, itemRect.top - cardRect.top - 4) })
8365
8366
  }
8366
8367
  const renderItem = (item, index, inSub) => {
8367
8368
  if (item === null) return E('div', { key: 'sep-' + index, className: 'dig-menu-sep' })
@@ -8402,7 +8403,7 @@ window.__ModuleLoader__.load({
8402
8403
  E('div', { className: 'dig-menu', ref: cardRef, 'data-menu-material': 'translucent' },
8403
8404
  E('div', { className: 'dig-menu-scroll' },
8404
8405
  props.items.map((item, index) => renderItem(item, index, false)))),
8405
- sub === null ? null : E('div', { className: 'dig-menu dig-menu-sub', 'data-menu-material': 'translucent', style: { left: sub.left + 'px', top: sub.top + 'px' } },
8406
+ sub === null ? null : E('div', { className: 'dig-menu dig-menu-sub', 'data-menu-material': 'translucent', style: sub.flip === true ? { right: '100%', top: sub.top + 'px' } : { left: '100%', top: sub.top + 'px' } },
8406
8407
  E('div', { className: 'dig-menu-scroll' },
8407
8408
  sub.items.map((item, index) => renderItem(item, index, true)))))
8408
8409
  }
@@ -9440,7 +9441,11 @@ window.__ModuleLoader__.load({
9440
9441
  const groupBy = props.groupBy === 'dir' ? 'dir' : 'flat'
9441
9442
  const setGroupBy = props.onGroupBy
9442
9443
  const sortKey = props.sortKey === undefined ? 'git' : props.sortKey
9443
- const [foldedDirs, setFoldedDirs] = useState({})
9444
+ /* foldedDirs lifted too (v0.16.1): the header row lost its four buttons —
9445
+ everything they did lives in the ⋯ menu now, so the menu drives this
9446
+ state directly through props. */
9447
+ const foldedDirs = props.foldedDirs === undefined ? {} : props.foldedDirs
9448
+ const setFoldedDirs = props.onFoldedDirs
9444
9449
  const changes = summary === null ? null : summary.changes
9445
9450
  const conflicted = changes === null ? [] : changes.conflicted
9446
9451
  const staged = changes === null ? [] : changes.staged
@@ -9568,14 +9573,6 @@ window.__ModuleLoader__.load({
9568
9573
  let used = 0
9569
9574
  while (winLast < changeItems.length && used < winLimit) { used += changeItems[winLast].h; winLast += 1 }
9570
9575
  const visibleItems = changeItems.slice(winFirst, winLast)
9571
- const foldAll = (value) => {
9572
- const next = {}
9573
- if (value === true) {
9574
- const groups = [['conflicted', conflicted], ['staged', staged], ['unstaged', unstaged], ['untracked', untracked], ['ignored', ignored]]
9575
- for (const pair of groups) for (const item of pair[1]) next[pair[0] + '|' + folderOf(item)] = true
9576
- }
9577
- setFoldedDirs(next)
9578
- }
9579
9576
  const aiConfirmDialog = aiAsk !== true ? null : E(ConfirmDialog, {
9580
9577
  t: t,
9581
9578
  title: t('changes.aiOverwriteTitle'),
@@ -9654,35 +9651,18 @@ window.__ModuleLoader__.load({
9654
9651
  E('span', null, t('changes.title')),
9655
9652
  E('span', { className: 'dig-count' }, String(total))),
9656
9653
  E('span', { className: 'dig-topbar-spacer' }),
9657
- E('button', {
9658
- type: 'button',
9659
- className: 'dig-icon-btn dig-icon-btn-small' + (groupBy === 'dir' ? ' dig-icon-btn-active' : ''),
9660
- title: t('changes.groupBy') + ': ' + (groupBy === 'dir' ? t('changes.groupDir') : t('changes.groupFlat')),
9661
- onClick: () => setGroupBy((value) => (value === 'dir' ? 'flat' : 'dir')),
9662
- }, E(Icon, { name: 'folder', size: 13 })),
9663
- E('button', {
9664
- type: 'button', className: 'dig-icon-btn dig-icon-btn-small',
9665
- title: t('changes.expandAll'), disabled: groupBy !== 'dir',
9666
- onClick: () => foldAll(false),
9667
- }, E(Icon, { name: 'expand', size: 13 })),
9668
- E('button', {
9669
- type: 'button', className: 'dig-icon-btn dig-icon-btn-small',
9670
- title: t('changes.foldAll'), disabled: groupBy !== 'dir',
9671
- onClick: () => foldAll(true),
9672
- }, E(Icon, { name: 'collapse', size: 13 })),
9673
- E('button', {
9674
- type: 'button',
9675
- className: 'dig-icon-btn dig-icon-btn-small' + (props.showIgnored === true ? ' dig-icon-btn-active' : ''),
9676
- title: t('changes.showIgnored'),
9677
- onClick: () => props.onToggleIgnored(),
9678
- }, E(Icon, { name: props.showIgnored === true ? 'eye' : 'eyeOff', size: 13 })),
9654
+ /* The four header toggles (group-by / expand / fold / ignored) moved
9655
+ INTO the ⋯ menu (v0.16.1, user request): one consistent entry point
9656
+ instead of five small buttons, same actions, same state. */
9679
9657
  props.onMoreMenu === undefined ? null : E('button', {
9680
9658
  type: 'button',
9681
- className: 'dig-icon-btn dig-icon-btn-small',
9659
+ /* Same 24px footprint and 15px glyph as the rail buttons above — the
9660
+ user asked for one consistent size across both rows. */
9661
+ className: 'dig-icon-btn',
9682
9662
  title: t('toolbar.more'),
9683
9663
  'data-action': 'more',
9684
9664
  onClick: (event) => props.onMoreMenu(event),
9685
- }, E(Icon, { name: 'more', size: 13 })))
9665
+ }, E(Icon, { name: 'more', size: 15 })))
9686
9666
  return E('div', { className: 'dig-changes' },
9687
9667
  aiConfirmDialog,
9688
9668
  head,
@@ -10511,6 +10491,10 @@ window.__ModuleLoader__.load({
10511
10491
  fetches only when a remote exists. */
10512
10492
  const remoteReady = branches !== null && Array.isArray(branches.remotes) && branches.remotes.length > 0
10513
10493
  const [refreshSpinning, setRefreshSpinning] = useState(false)
10494
+ /* Fold state of the per-directory groups, lifted from ChangesPanel so the
10495
+ ⋯ menu's 展开全部/折叠全部 entries drive the same map the folder
10496
+ headers toggle. */
10497
+ const [foldedDirs, setFoldedDirs] = useState({})
10514
10498
  const refresh = useCallback(async (opts) => {
10515
10499
  if (repoRoot === null) return
10516
10500
  const withFetch = opts !== null && typeof opts === 'object' && opts.fetchRemote === true && remoteReady === true
@@ -11325,6 +11309,26 @@ window.__ModuleLoader__.load({
11325
11309
  { id: 'sort-path', label: t('more.sortPath'), active: sortKey === 'path', run: sort('path') },
11326
11310
  { id: 'sort-name', label: t('more.sortName'), active: sortKey === 'name', run: sort('name') },
11327
11311
  { id: 'sort-status', label: t('more.sortStatus'), active: sortKey === 'status', run: sort('status') },
11312
+ null,
11313
+ /* The old header toggles live here now (v0.16.1): fold/unfold need
11314
+ the directory map, which only exists when grouping is on. */
11315
+ { id: 'view-expand', label: t('changes.expandAll'), disabled: groupBy !== 'dir', reason, run: () => setFoldedDirs({}) },
11316
+ { id: 'view-fold', label: t('changes.foldAll'), disabled: groupBy !== 'dir', reason, run: () => {
11317
+ const next = {}
11318
+ const changes = summary === null ? null : summary.changes
11319
+ if (changes !== null) {
11320
+ const groups = [['conflicted', changes.conflicted], ['staged', changes.staged], ['unstaged', changes.unstaged], ['untracked', changes.untracked], ['ignored', changes.ignored === undefined ? [] : changes.ignored]]
11321
+ for (const pair of groups) {
11322
+ for (const item of pair[1]) {
11323
+ const dir = dirName(item.path)
11324
+ next[pair[0] + '|' + (dir === '' ? t('changes.rootDir') : dir)] = true
11325
+ }
11326
+ }
11327
+ }
11328
+ setFoldedDirs(next)
11329
+ } },
11330
+ null,
11331
+ { id: 'view-ignored', label: t('changes.showIgnored'), active: showIgnored === true, run: () => setShowIgnored(showIgnored !== true) },
11328
11332
  ] },
11329
11333
  null,
11330
11334
  /* —— top-level sync (VS Code header row) —— */
@@ -11423,6 +11427,7 @@ window.__ModuleLoader__.load({
11423
11427
  const changesPane = E(ChangesPanel, {
11424
11428
  t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
11425
11429
  groupBy: groupBy, sortKey: sortKey, onGroupBy: setGroupBy,
11430
+ foldedDirs: foldedDirs, onFoldedDirs: setFoldedDirs,
11426
11431
  onMoreMenu: moreMenu,
11427
11432
  request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings, base: base,
11428
11433
  showIgnored: showIgnored,
@@ -12242,13 +12247,15 @@ window.__ModuleLoader__.load({
12242
12247
  '.dig-dialog-text{color:var(--dsw-alias-label-secondary);white-space:pre-wrap}',
12243
12248
  '.dig-dialog-actions{display:flex;justify-content:flex-end;gap:8px}',
12244
12249
  '.dig-dialog-actions .dig-btn-primary{margin-left:0}',
12245
- /* The wrapper carries the panel-relative anchor and the outside-click ref;
12246
- the card(s) are plain children. The submenu is a second card offset to
12247
- the parent card's edge, flipping left when the panel runs out of room
12248
- (see ContextMenu.openSub). */
12249
- '.dig-menu-wrap{position:absolute;z-index:70}',
12250
- '.dig-menu{box-sizing:border-box;min-width:min(200px,calc(100% - 8px));max-width:calc(100% - 8px);max-height:calc(100% - 8px);padding:4px;border:0;border-radius:var(--dsw-radius-md,12px);background:transparent;box-shadow:var(--dsw-elevation-prominent,0 10px 28px rgba(0,0,0,.35));--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);isolation:isolate;display:flex;flex-direction:column}',
12251
- '.dig-menu-sub{position:absolute;min-width:180px;max-width:260px;z-index:71}',
12250
+ /* The wrapper carries the panel-relative anchor and the outside-click ref,
12251
+ plus the ONLY percentage widths (its containing block is .dig-root, so
12252
+ `max-width` resolves against the panel — on the card it would resolve
12253
+ against the shrink-to-fit wrapper and collapse the menu, cutting labels).
12254
+ The submenu is a second card offset to the parent card's edge, flipping
12255
+ left when the panel runs out of room (see ContextMenu.openSub). */
12256
+ '.dig-menu-wrap{position:absolute;z-index:70;width:max-content;max-width:calc(100% - 8px)}',
12257
+ '.dig-menu{box-sizing:border-box;min-width:200px;max-height:calc(100% - 8px);padding:4px;border:0;border-radius:var(--dsw-radius-md,12px);background:transparent;box-shadow:var(--dsw-elevation-prominent,0 10px 28px rgba(0,0,0,.35));--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);isolation:isolate;display:flex;flex-direction:column}',
12258
+ '.dig-menu-sub{position:absolute;min-width:180px;max-width:300px;z-index:71}',
12252
12259
  '.dig-menu::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1)));-webkit-backdrop-filter:var(--dsw-menu-backdrop-filter,blur(14px) saturate(1.2));backdrop-filter:var(--dsw-menu-backdrop-filter,blur(14px) saturate(1.2));pointer-events:none}',
12253
12260
  '.dig-menu-scroll{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;display:flex;flex-direction:column}',
12254
12261
  '.dig-menu-item{display:flex;align-items:center;gap:8px;padding:4px 8px;border:none;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;text-align:left;border-radius:var(--dsw-radius-sm,8px);cursor:pointer;white-space:nowrap;overflow:hidden}',