dsh-ide-git 0.24.0 → 0.25.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/README.md +1 -0
- package/README_EN.md +1 -0
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +205 -1
package/README.md
CHANGED
|
@@ -95,6 +95,7 @@ DSH 自带的 Git 面板覆盖「暂存 / 提交 / 还原 / 看历史」;`dsh-id
|
|
|
95
95
|
| 区域 | 能力 |
|
|
96
96
|
|---|---|
|
|
97
97
|
| 动作条 | IDEA 式左侧竖排(底部工作台)/ 顶栏下一行横排(右侧栏):刷新、新建分支、签出、删除、比较、显示差异、贮藏、新建标签、收藏、抓取、拉取、推送。**按可用空间自动决定放几个**,放不下的收进 `⋯ 更多`;末尾 `⚙ 设置` 可拖动排序、逐项显隐(存 `dsh-ide-git.rail.v1`)。每个按钮按当前状态点亮/置灰(没有其他分支就不能删除/签出/比较,没有远程就不能抓取/推送,没有上游就不能拉取,没有变更就没有差异可看) |
|
|
98
|
+
| 悬停提示 | 鼠标停在图标按钮上 120ms,指针旁弹出它的操作名——竖排动作条里在图标右侧(跟着该行垂直居中,贴窗口右缘时翻到左侧),其余在下方;置灰的动作连原因一起给出(如 `隐藏 · 当前状态不可用`)。提示卡挂在窗口层,底部工作台裁不到;扫过一排图标不闪,按住鼠标拖拽 / 滚动 / 缩放时立即收起 |
|
|
98
99
|
| 状态行 | 当前分支、上游、`↑ahead ↓behind`、stash 数量、忙碌指示 |
|
|
99
100
|
| 输入框状态条 | 宿主输入框工具条里的仓库/分支胶囊(着色 ±未提交 / ↓可拉取 / ↑待推送),点击切换仓库与分支,与面板双向联动;单仓库只切分支,多仓库两边都切;设置可关(默认开) |
|
|
100
101
|
| 分支树 | `HEAD / 本地 / 远程 / 标签` 分组(可折叠)、过滤框、星标当前分支、ahead/behind 角标、worktree 占用标记;本地蓝 / 远程紫 / 标签黄 / HEAD 绿 |
|
package/README_EN.md
CHANGED
|
@@ -83,6 +83,7 @@ The panel never guesses: it measures itself with a `ResizeObserver` (width ≥ 6
|
|
|
83
83
|
## Features
|
|
84
84
|
|
|
85
85
|
- Action rail: the JetBrains-style vertical strip in the bottom workbench (and one horizontal row under the header in the right sidebar) with refresh, new branch, checkout, delete, compare, show diff, stash, new tag, favorite, fetch, pull and push. **It shows as many buttons as fit**, folds the rest into a `⋯ more` menu, and pins a `⚙ settings` button that lets you reorder actions and toggle each of them (stored in `dsh-ide-git.rail.v1`). Every action lights up or greys out with the current state (no other branch → delete / checkout / compare off, no remote → fetch / push off, no upstream → pull off, no changes → diff off).
|
|
86
|
+
- Hover hints: rest the pointer on an icon button for 120ms and its action name appears beside it — to the RIGHT of a vertical rail icon (centred on that row, flipping to the left when the window's right edge is in the way) and below everywhere else; greyed-out actions explain themselves too (e.g. `Hide · Not available in the current state`). The hint card lives on the window layer, so the bottom workbench cannot clip it — sweeping across a row of icons does not flash, and a pressed pointer / scroll / resize drops it immediately.
|
|
86
87
|
- Status line: branch, upstream, ahead/behind, stash count, busy indicator.
|
|
87
88
|
- Composer status pill: repository/branch capsule in the host input-bar tool row (colored uncommitted / pullable / to-push counters), click to switch repo and branch — kept in sync with the panel both ways; single-repo workspaces switch branches only, multi-repo workspaces switch both. Off-switch in the settings (on by default). Sessions are pre-warmed on switch so the panel opens instantly.
|
|
88
89
|
- Branch tree: HEAD / Local / Remote / Tags groups, filter box, ahead/behind badges, worktree marker; local blue / remote violet / tag amber / HEAD green.
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-ide-git",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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.
|
|
3
|
+
"version": "0.25.0",
|
|
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
|
@@ -14849,7 +14849,12 @@ window.__ModuleLoader__.load({
|
|
|
14849
14849
|
const diffPane = patch === '' && patchLoading === false ? null : E('div', { className: 'dig-diff-pane', 'data-pane': 'diff', style: paneStyle('diff'), onContextMenu: onDiffPaneContextMenu },
|
|
14850
14850
|
E('div', { className: 'dig-diff-head' },
|
|
14851
14851
|
E('span', { className: 'dig-mono dig-diff-path' }, selectedPath === null ? '' : selectedPath),
|
|
14852
|
-
E('button', {
|
|
14852
|
+
E('button', {
|
|
14853
|
+
type: 'button',
|
|
14854
|
+
className: 'dig-icon-btn',
|
|
14855
|
+
title: t('toast.close'),
|
|
14856
|
+
onClick: () => { setPatch(''); setSelectedPath(null); setDiffContext(null) },
|
|
14857
|
+
}, E(Icon, { name: 'close', size: 12 }))),
|
|
14853
14858
|
E(DiffBody, { patch: patch, loading: patchLoading, t: t }))
|
|
14854
14859
|
|
|
14855
14860
|
/* ---------- rail (IDE-style left action strip) ---------- */
|
|
@@ -16422,6 +16427,13 @@ window.__ModuleLoader__.load({
|
|
|
16422
16427
|
'.dig-wt-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500}',
|
|
16423
16428
|
'.dig-wt-error{padding:6px 8px;margin-bottom:8px;border-radius:var(--dsw-radius-sm,8px);background:rgba(220,38,38,.12);color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;word-break:break-word}',
|
|
16424
16429
|
'body[data-ds-dark-theme] .dig-wt-error{background:rgba(248,113,113,.16)}',
|
|
16430
|
+
/* ---- icon tooltip ---- */
|
|
16431
|
+
/* A body child, not a panel child: the bottom workbench declares
|
|
16432
|
+
`contain: layout style`, so a fixed element inside it would take the
|
|
16433
|
+
panel box as its containing block and land off-screen (invariant 13).
|
|
16434
|
+
Styled from the same menu tokens so it reads as part of the product. */
|
|
16435
|
+
'.dig-tip{position:fixed;z-index:9999;display:none;box-sizing:border-box;max-width:280px;padding:5px 8px;border-radius:var(--dsw-radius-sm,8px);background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1)));color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-elevation-prominent,0 8px 22px rgba(0,0,0,.32));font-family:var(--dsw-font-family,system-ui,sans-serif);font-size:calc(11px + var(--dsh-content-font-delta-secondary,0px));font-weight:500;line-height:1.45;text-align:left;white-space:normal;overflow-wrap:anywhere;pointer-events:none}',
|
|
16436
|
+
'.dig-tip[data-show="true"]{display:block}',
|
|
16425
16437
|
].concat(LANE_TRACE_RULES).join('\n')
|
|
16426
16438
|
|
|
16427
16439
|
/* ============================== plugin ============================== */
|
|
@@ -17518,6 +17530,193 @@ window.__ModuleLoader__.load({
|
|
|
17518
17530
|
}))))
|
|
17519
17531
|
}
|
|
17520
17532
|
|
|
17533
|
+
/* ---- icon tooltips ----
|
|
17534
|
+
Every icon action already carries its name in `title` (the rail adds the
|
|
17535
|
+
disabled reason; the toolbar / mini / pill buttons carry the action
|
|
17536
|
+
verb). Native title bubbles are the wrong surface for this product: the
|
|
17537
|
+
bottom workbench is a dense horizontal strip, the bubble needs a full
|
|
17538
|
+
second of rest, it is drawn in the browser's own styling, and over a
|
|
17539
|
+
DISABLED rail button it never appears at all. Draw one themed card
|
|
17540
|
+
instead, shared by every seat (panel, composer pill, diff preview) and
|
|
17541
|
+
hung off <body> — see the `.dig-tip` note in CSS for why it can't live
|
|
17542
|
+
inside the panel. The card mirrors the anchor's own `title`, which is
|
|
17543
|
+
removed while the card is up so the native bubble never doubles it. */
|
|
17544
|
+
const TIP_ID = 'dsh-ide-git-tip'
|
|
17545
|
+
const TIP_DELAY_MS = 120
|
|
17546
|
+
const TIP_GAP = 6
|
|
17547
|
+
/* Only this plugin's own surfaces explain themselves. `.dig-root` is the
|
|
17548
|
+
panel (native and better-sidebar seats alike), `.dig-cs-root` the
|
|
17549
|
+
composer status pill, `.dig-preview-root` the external diff seat. */
|
|
17550
|
+
const TIP_ROOTS = '.dig-root, .dig-cs-root, .dig-preview-root'
|
|
17551
|
+
|
|
17552
|
+
function installHoverTips() {
|
|
17553
|
+
/* The SSR self-check drives apply() with a minimal document stub: it has
|
|
17554
|
+
no body, no id lookup and no event targets. Those environments get the
|
|
17555
|
+
no-op installer rather than a throw during plugin activation. */
|
|
17556
|
+
if (typeof document === 'undefined' || document.body == null
|
|
17557
|
+
|| typeof document.createElement !== 'function'
|
|
17558
|
+
|| typeof document.getElementById !== 'function'
|
|
17559
|
+
|| typeof document.addEventListener !== 'function'
|
|
17560
|
+
|| typeof window === 'undefined'
|
|
17561
|
+
|| typeof window.addEventListener !== 'function') return () => {}
|
|
17562
|
+
/* HMR / re-apply: never stack two cards (or two listener sets on one). */
|
|
17563
|
+
const stale = document.getElementById(TIP_ID)
|
|
17564
|
+
if (stale !== null) stale.remove()
|
|
17565
|
+
const card = document.createElement('div')
|
|
17566
|
+
card.id = TIP_ID
|
|
17567
|
+
card.className = 'dig-tip'
|
|
17568
|
+
card.setAttribute('data-dsh-ide-git', '')
|
|
17569
|
+
card.setAttribute('role', 'tooltip')
|
|
17570
|
+
card.setAttribute('aria-hidden', 'true')
|
|
17571
|
+
document.body.appendChild(card)
|
|
17572
|
+
|
|
17573
|
+
/* `anchor` is the button the pointer is on (or about to rest on, while
|
|
17574
|
+
`timer` runs); `shown` is the one whose title the card has taken. */
|
|
17575
|
+
let anchor = null
|
|
17576
|
+
let shown = null
|
|
17577
|
+
let takenTitle = null
|
|
17578
|
+
let timer = null
|
|
17579
|
+
|
|
17580
|
+
const restore = () => {
|
|
17581
|
+
if (shown === null) return
|
|
17582
|
+
/* React may have rewritten the attribute while the card was up (toggle
|
|
17583
|
+
buttons swap their copy): only put the taken text back when nothing
|
|
17584
|
+
else has claimed the slot, so a fresh label is never clobbered. */
|
|
17585
|
+
try {
|
|
17586
|
+
if (shown.getAttribute('title') === null && takenTitle !== null) shown.setAttribute('title', takenTitle)
|
|
17587
|
+
} catch (error) { void error }
|
|
17588
|
+
shown = null
|
|
17589
|
+
takenTitle = null
|
|
17590
|
+
}
|
|
17591
|
+
|
|
17592
|
+
const hide = () => {
|
|
17593
|
+
if (timer !== null) { clearTimeout(timer); timer = null }
|
|
17594
|
+
anchor = null
|
|
17595
|
+
restore()
|
|
17596
|
+
card.removeAttribute('data-show')
|
|
17597
|
+
card.setAttribute('aria-hidden', 'true')
|
|
17598
|
+
card.textContent = ''
|
|
17599
|
+
}
|
|
17600
|
+
|
|
17601
|
+
/* Viewport coordinates on a body child: no panel containment to fight. */
|
|
17602
|
+
const place = () => {
|
|
17603
|
+
const rect = anchor.getBoundingClientRect()
|
|
17604
|
+
const width = card.offsetWidth
|
|
17605
|
+
const height = card.offsetHeight
|
|
17606
|
+
const viewportW = window.innerWidth || document.documentElement.clientWidth || 0
|
|
17607
|
+
const viewportH = window.innerHeight || document.documentElement.clientHeight || 0
|
|
17608
|
+
/* The vertical action rail is a 24px column on the panel's left edge:
|
|
17609
|
+
a card under an icon there would cover the icons below it, so this one
|
|
17610
|
+
sits to its right and follows the row. (The horizontal rail uses
|
|
17611
|
+
`.dig-rail-row`, so the `closest` query only matches the vertical
|
|
17612
|
+
seat — top rows keep the below-the-button placement.) */
|
|
17613
|
+
if (anchor.closest('.dig-rail') !== null) {
|
|
17614
|
+
let left = rect.left + rect.width + TIP_GAP
|
|
17615
|
+
if (left + width > viewportW - 4) left = rect.left - TIP_GAP - width
|
|
17616
|
+
if (left < 4) left = 4
|
|
17617
|
+
let top = rect.top + (rect.height - height) / 2
|
|
17618
|
+
if (top + height > viewportH - 4) top = viewportH - height - 4
|
|
17619
|
+
if (top < 4) top = 4
|
|
17620
|
+
card.style.left = Math.round(left) + 'px'
|
|
17621
|
+
card.style.top = Math.round(top) + 'px'
|
|
17622
|
+
return
|
|
17623
|
+
}
|
|
17624
|
+
/* Below the anchor; flip above when the bottom edge would clip. */
|
|
17625
|
+
let top = rect.bottom + TIP_GAP
|
|
17626
|
+
if (top + height > viewportH - 4) top = rect.top - height - TIP_GAP
|
|
17627
|
+
if (top < 4) top = 4
|
|
17628
|
+
let left = rect.left + (rect.width - width) / 2
|
|
17629
|
+
if (left + width > viewportW - 4) left = viewportW - width - 4
|
|
17630
|
+
if (left < 4) left = 4
|
|
17631
|
+
card.style.left = Math.round(left) + 'px'
|
|
17632
|
+
card.style.top = Math.round(top) + 'px'
|
|
17633
|
+
}
|
|
17634
|
+
|
|
17635
|
+
const reveal = () => {
|
|
17636
|
+
timer = null
|
|
17637
|
+
if (anchor === null) return
|
|
17638
|
+
const label = anchor.getAttribute('title')
|
|
17639
|
+
if (label === null || label === '') { hide(); return }
|
|
17640
|
+
takenTitle = label
|
|
17641
|
+
try { anchor.removeAttribute('title') } catch (error) { void error }
|
|
17642
|
+
shown = anchor
|
|
17643
|
+
card.textContent = label
|
|
17644
|
+
card.setAttribute('data-show', 'true')
|
|
17645
|
+
card.removeAttribute('aria-hidden')
|
|
17646
|
+
place()
|
|
17647
|
+
}
|
|
17648
|
+
|
|
17649
|
+
/* Whatever sits under the pointer, not whatever received the event: a
|
|
17650
|
+
disabled button still hit-tests (rail icons), and the SVG glyph inside
|
|
17651
|
+
the button is usually the element under the cursor. `closest` walks up
|
|
17652
|
+
to the button. The label is read here, but the anchor's own copy is
|
|
17653
|
+
already taken while its card is up — so the shown button answers from
|
|
17654
|
+
the saved text, otherwise it would stop counting as a candidate the
|
|
17655
|
+
moment the card opened. */
|
|
17656
|
+
const buttonAt = (x, y) => {
|
|
17657
|
+
if (typeof document.elementFromPoint !== 'function') return null
|
|
17658
|
+
let hit = null
|
|
17659
|
+
try { hit = document.elementFromPoint(x, y) } catch (error) { void error; return null }
|
|
17660
|
+
if (hit === null || hit === undefined || typeof hit.closest !== 'function') return null
|
|
17661
|
+
if (hit.closest(TIP_ROOTS) === null) return null
|
|
17662
|
+
return hit.closest('button, [role="button"]')
|
|
17663
|
+
}
|
|
17664
|
+
|
|
17665
|
+
const labelOf = (button) => {
|
|
17666
|
+
if (button === shown) return takenTitle
|
|
17667
|
+
const label = button.getAttribute('title')
|
|
17668
|
+
return label === null || label === '' ? null : label
|
|
17669
|
+
}
|
|
17670
|
+
|
|
17671
|
+
const onPointerMove = (event) => {
|
|
17672
|
+
if (event !== null && event !== undefined && event.pointerType === 'touch') return
|
|
17673
|
+
/* A drag is not a hover: reordering rail actions, dragging a divider or
|
|
17674
|
+
selecting rows all sweep across buttons with a pressed pointer, and
|
|
17675
|
+
no card should pop up under the drag. */
|
|
17676
|
+
if (event !== null && event !== undefined && typeof event.buttons === 'number' && event.buttons !== 0) {
|
|
17677
|
+
if (anchor !== null || timer !== null || shown !== null) hide()
|
|
17678
|
+
return
|
|
17679
|
+
}
|
|
17680
|
+
const button = buttonAt(event === null || event === undefined ? 0 : event.clientX, event === null || event === undefined ? 0 : event.clientY)
|
|
17681
|
+
if (button === null || labelOf(button) === null) {
|
|
17682
|
+
if (anchor !== null || timer !== null || shown !== null) hide()
|
|
17683
|
+
return
|
|
17684
|
+
}
|
|
17685
|
+
if (button === anchor) return
|
|
17686
|
+
/* Another button: the old card (and the title it took) goes now, the
|
|
17687
|
+
new one waits out the grace period — sweeping the rail must not
|
|
17688
|
+
flash a card per icon. */
|
|
17689
|
+
if (timer !== null) { clearTimeout(timer); timer = null }
|
|
17690
|
+
restore()
|
|
17691
|
+
card.removeAttribute('data-show')
|
|
17692
|
+
anchor = button
|
|
17693
|
+
timer = setTimeout(reveal, TIP_DELAY_MS)
|
|
17694
|
+
}
|
|
17695
|
+
|
|
17696
|
+
document.addEventListener('pointermove', onPointerMove, { passive: true })
|
|
17697
|
+
document.addEventListener('pointerdown', hide, true)
|
|
17698
|
+
document.addEventListener('pointerleave', hide)
|
|
17699
|
+
document.addEventListener('wheel', hide, { passive: true, capture: true })
|
|
17700
|
+
/* Scroll and resize move the anchor under a card that cannot follow it:
|
|
17701
|
+
the card would lie about where the button is. Drop it and let the next
|
|
17702
|
+
pointer move re-open it. */
|
|
17703
|
+
window.addEventListener('scroll', hide, true)
|
|
17704
|
+
window.addEventListener('resize', hide)
|
|
17705
|
+
window.addEventListener('blur', hide)
|
|
17706
|
+
|
|
17707
|
+
return () => {
|
|
17708
|
+
hide()
|
|
17709
|
+
document.removeEventListener('pointermove', onPointerMove)
|
|
17710
|
+
document.removeEventListener('pointerdown', hide, true)
|
|
17711
|
+
document.removeEventListener('pointerleave', hide)
|
|
17712
|
+
document.removeEventListener('wheel', hide, { capture: true })
|
|
17713
|
+
window.removeEventListener('scroll', hide, true)
|
|
17714
|
+
window.removeEventListener('resize', hide)
|
|
17715
|
+
window.removeEventListener('blur', hide)
|
|
17716
|
+
card.remove()
|
|
17717
|
+
}
|
|
17718
|
+
}
|
|
17719
|
+
|
|
17521
17720
|
function apply(ctx) {
|
|
17522
17721
|
const style = document.createElement('style')
|
|
17523
17722
|
style.setAttribute('data-dsh-ide-git', '')
|
|
@@ -17525,6 +17724,11 @@ window.__ModuleLoader__.load({
|
|
|
17525
17724
|
document.head.appendChild(style)
|
|
17526
17725
|
ctx.effect(() => () => { style.remove() }, 'dsh-ide-git: panel styles')
|
|
17527
17726
|
|
|
17727
|
+
/* Icon affordances explain themselves on hover wherever this plugin
|
|
17728
|
+
draws one — the panel in any seat, the composer pill, the diff
|
|
17729
|
+
preview. One document-level card covers them all. */
|
|
17730
|
+
ctx.effect(() => installHoverTips(), 'dsh-ide-git: icon tooltips')
|
|
17731
|
+
|
|
17528
17732
|
const t = translatorOf(ctx)
|
|
17529
17733
|
|
|
17530
17734
|
/* DSH's own locale registry: bind this plugin's namespace so host-side
|