dsh-ide-git 0.5.4 → 0.5.6

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.5.4",
3
+ "version": "0.5.6",
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.5.4",
3
+ "version": "0.5.6",
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": {
@@ -13,7 +13,8 @@
13
13
  "main": "src/index.js",
14
14
  "exports": {
15
15
  ".": "./src/index.js",
16
- "./client": "./src/client.js"
16
+ "./client": "./src/client.js",
17
+ "./package.json": "./package.json"
17
18
  },
18
19
  "engines": {
19
20
  "node": ">=18",
@@ -44,7 +45,7 @@
44
45
  "LICENSE"
45
46
  ],
46
47
  "scripts": {
47
- "test": "node --test tests/smoke.mjs tests/api.test.mjs tests/graph.test.mjs",
48
+ "test": "node --test tests/smoke.mjs tests/api.test.mjs tests/graph.test.mjs tests/repos.test.mjs",
48
49
  "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"
49
50
  },
50
51
  "keywords": [
package/src/client.js CHANGED
@@ -3536,6 +3536,12 @@ window.__ModuleLoader__.load({
3536
3536
  const present = new Set()
3537
3537
  for (const commit of commits) present.add(commit.hash)
3538
3538
  const lanes = []
3539
+ /* The current branch keeps the LEFTMOST column whenever it is on the
3540
+ board: pre-seeding its hash makes that commit claim lane 0 when its
3541
+ row arrives, so HEAD's line never drifts to the right of a side
3542
+ branch (the rest of the lanes fill newest-first, see below). */
3543
+ const headCommit = commits.find((commit) => Array.isArray(commit.refs) && commit.refs.some((ref) => String(ref).indexOf('HEAD') >= 0))
3544
+ if (headCommit !== undefined) lanes.push(headCommit.hash)
3539
3545
  const rows = []
3540
3546
  for (const commit of commits) {
3541
3547
  const before = lanes.slice()
@@ -3593,30 +3599,46 @@ window.__ModuleLoader__.load({
3593
3599
  centre where the lane genuinely starts (a branch tip) or ends (a root).
3594
3600
  v0.1.7 started every non-own lane at 0 and every own lane at height/2, which
3595
3601
  left a visible gap between two consecutive commits on the same lane. */
3596
- function GraphCell(props) {
3597
- const row = props.row
3598
- const height = props.height
3599
- const width = Math.min(Math.max(1, row.width), GRAPH_MAX_LANES) * LANE_WIDTH + 8
3602
+ /* Vertical lane segments for one row. A lane that this row OPENS through an
3603
+ edge curve (it was empty above, and the curve lands at the row bottom) must
3604
+ NOT also get a vertical segment from the row centre: the two overlapped and
3605
+ left a floating hook where the line visibly started in mid-air — reported
3606
+ twice by the user as "断开的线/无端点的分叉". The curve alone carries the
3607
+ connection, and the next row's segment starts at its top edge. */
3608
+ function laneSegments(row, height) {
3600
3609
  const middle = height / 2
3601
- const children = []
3602
- /* Lanes beyond the cap share the last column: the geometry stays bounded
3603
- and the subject column keeps its room. */
3604
- const x = (lane) => 8 + Math.min(lane, GRAPH_MAX_LANES - 1) * LANE_WIDTH
3605
- const color = (lane) => LANE_COLORS[lane % LANE_COLORS.length]
3606
3610
  const depth = Math.max(row.before.length, row.after.length)
3611
+ const segments = []
3607
3612
  for (let lane = 0; lane < depth; lane += 1) {
3608
3613
  const above = lane < row.before.length ? row.before[lane] : null
3609
3614
  const below = lane < row.after.length ? row.after[lane] : null
3610
3615
  const hasTop = above !== null && above !== undefined
3611
3616
  const hasBottom = below !== null && below !== undefined
3612
3617
  if (hasTop === false && hasBottom === false) continue
3618
+ if (hasTop === false && row.parentLanes.indexOf(lane) >= 0) continue
3613
3619
  const y1 = hasTop ? 0 : middle
3614
3620
  const y2 = hasBottom ? height : middle
3615
3621
  if (y1 === y2) continue
3622
+ segments.push({ lane: lane, y1: y1, y2: y2 })
3623
+ }
3624
+ return segments
3625
+ }
3626
+
3627
+ function GraphCell(props) {
3628
+ const row = props.row
3629
+ const height = props.height
3630
+ const width = Math.min(Math.max(1, row.width), GRAPH_MAX_LANES) * LANE_WIDTH + 8
3631
+ const middle = height / 2
3632
+ const children = []
3633
+ /* Lanes beyond the cap share the last column: the geometry stays bounded
3634
+ and the subject column keeps its room. */
3635
+ const x = (lane) => 8 + Math.min(lane, GRAPH_MAX_LANES - 1) * LANE_WIDTH
3636
+ const color = (lane) => LANE_COLORS[lane % LANE_COLORS.length]
3637
+ for (const segment of laneSegments(row, height)) {
3616
3638
  children.push(E('line', {
3617
- key: 'lane-' + lane,
3618
- x1: x(lane), y1: y1, x2: x(lane), y2: y2,
3619
- stroke: color(lane), strokeWidth: 2, strokeLinecap: 'round',
3639
+ key: 'lane-' + segment.lane,
3640
+ x1: x(segment.lane), y1: segment.y1, x2: x(segment.lane), y2: segment.y2,
3641
+ stroke: color(segment.lane), strokeWidth: 2, strokeLinecap: 'round',
3620
3642
  }))
3621
3643
  }
3622
3644
  for (const parentLane of row.parentLanes) {
@@ -3953,6 +3975,25 @@ window.__ModuleLoader__.load({
3953
3975
  E(Icon, { name: 'chevron', size: 12 })))
3954
3976
  }
3955
3977
 
3978
+ /* The history filters were the LAST native <select>s: their popup is drawn
3979
+ by the OS, so a dark translucent panel still got a light system list —
3980
+ reported on v0.5.4 ("only the one at the top was replaced"). Same
3981
+ treatment as the header switchers: a button over a panel-internal menu. */
3982
+ function FilterSelect(props) {
3983
+ const options = Array.isArray(props.options) ? props.options : []
3984
+ let current = options[0]
3985
+ for (const option of options) { if (option.value === props.value) { current = option; break } }
3986
+ const label = props.label + ': ' + (current === undefined ? '' : current.label)
3987
+ return E('button', {
3988
+ type: 'button',
3989
+ className: 'dig-filter-select' + (props.value === '' ? '' : ' dig-filter-on'),
3990
+ title: props.hint === undefined ? label : props.hint,
3991
+ onClick: (event) => { if (props.onOpenMenu !== undefined) props.onOpenMenu(event, options) },
3992
+ },
3993
+ E('span', { className: 'dig-filter-label' }, label),
3994
+ E(Icon, { name: 'chevron', size: 11 }))
3995
+ }
3996
+
3956
3997
  function RepoPicker(props) {
3957
3998
  const t = props.t
3958
3999
  const repos = props.repos
@@ -4375,31 +4416,45 @@ window.__ModuleLoader__.load({
4375
4416
  applyPath('')
4376
4417
  }
4377
4418
 
4378
- const filterBar = props.hideSearch === true ? null : E('div', { className: 'dig-filters' },
4419
+ /* Every filter opens the same in-panel menu; a checked row marks the active
4420
+ value, so the button never has to spell the whole list out. */
4421
+ const openFilterMenu = (event, options, currentValue, onPick) => {
4422
+ if (props.openMenuAt === undefined) return
4423
+ props.openMenuAt(event, options.map((option) => ({
4424
+ id: 'filter:' + String(option.value),
4425
+ icon: option.value === '' ? 'filter' : 'check',
4426
+ tone: option.value === '' ? 'secondary' : 'accent',
4427
+ label: option.label,
4428
+ active: option.value === currentValue,
4429
+ run: () => onPick(option.value),
4430
+ })))
4431
+ }
4432
+ const allOption = { value: '', label: t('filter.all') }
4433
+ const filterBar = props.hideSearch === true ? null : E('div', { className: 'dig-filters' },
4379
4434
  E('input', {
4380
4435
  className: 'dig-input dig-input-compact dig-filter-text', value: text, spellCheck: false,
4381
4436
  placeholder: t('history.filter'),
4382
4437
  onChange: (event) => setText(event.target.value),
4383
4438
  }),
4384
- E('select', {
4385
- className: 'dig-filter-select', value: refName, title: t('filter.branch'),
4386
- onChange: (event) => setRefName(event.target.value),
4387
- }, [E('option', { key: '', value: '' }, t('filter.branch') + ': ' + t('filter.all'))].concat(
4388
- refOptions.map((name) => E('option', { key: name, value: name }, name)))),
4389
- E('select', {
4390
- className: 'dig-filter-select', value: author, title: t('filter.user'),
4391
- onChange: (event) => setAuthor(event.target.value),
4392
- }, [E('option', { key: '', value: '' }, t('filter.user') + ': ' + t('filter.all'))].concat(
4393
- authors.map((name) => E('option', { key: name, value: name }, name)))),
4394
- E('select', {
4395
- className: 'dig-filter-select', value: since, title: t('filter.date'),
4396
- onChange: (event) => setSince(event.target.value),
4397
- },
4398
- E('option', { value: '' }, t('filter.date') + ': ' + t('filter.all')),
4399
- E('option', { value: 'today' }, t('filter.today')),
4400
- E('option', { value: 'week' }, t('filter.week')),
4401
- E('option', { value: 'month' }, t('filter.month')),
4402
- E('option', { value: 'year' }, t('filter.year'))),
4439
+ E(FilterSelect, {
4440
+ label: t('filter.branch'), value: refName, hint: t('filter.branch'),
4441
+ options: [allOption].concat(refOptions.map((name) => ({ value: name, label: name }))),
4442
+ onOpenMenu: (event, options) => openFilterMenu(event, options, refName, setRefName),
4443
+ }),
4444
+ E(FilterSelect, {
4445
+ label: t('filter.user'), value: author, hint: t('filter.user'),
4446
+ options: [allOption].concat(authors.map((name) => ({ value: name, label: name }))),
4447
+ onOpenMenu: (event, options) => openFilterMenu(event, options, author, setAuthor),
4448
+ }),
4449
+ E(FilterSelect, {
4450
+ label: t('filter.date'), value: since, hint: t('filter.date'),
4451
+ options: [allOption,
4452
+ { value: 'today', label: t('filter.today') },
4453
+ { value: 'week', label: t('filter.week') },
4454
+ { value: 'month', label: t('filter.month') },
4455
+ { value: 'year', label: t('filter.year') }],
4456
+ onOpenMenu: (event, options) => openFilterMenu(event, options, since, setSince),
4457
+ }),
4403
4458
  E('form', {
4404
4459
  className: 'dig-filter-path',
4405
4460
  onSubmit: (event) => { event.preventDefault(); applyPath(pathDraft.trim()) },
@@ -4992,7 +5047,7 @@ window.__ModuleLoader__.load({
4992
5047
  })
4993
5048
  : E(HistoryList, {
4994
5049
  t: t, commits: commits, hasMore: hasMore, busy: busy, selectedHash: selectedHash,
4995
- hideSearch: compact, pathFilter: pathFilter,
5050
+ hideSearch: compact, pathFilter: pathFilter, openMenuAt: openMenuAt,
4996
5051
  onPathFilter: (value) => setPathFilter(value),
4997
5052
  onSelect: (commit) => { void selectCommit(commit) },
4998
5053
  onMenu: commitMenu,
@@ -5497,6 +5552,9 @@ window.__ModuleLoader__.load({
5497
5552
  '.dig-filter-text{flex:1 1 90px;min-width:80px;width:auto}',
5498
5553
  '.dig-filter-select{flex:none;max-width:118px;appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;color:var(--dsw-alias-label-secondary);font:inherit;font-weight:500;height:22px;padding:0 4px;cursor:pointer;text-overflow:ellipsis}',
5499
5554
  '.dig-filter-select:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
5555
+ '.dig-filter-select{display:inline-flex;align-items:center;gap:3px}',
5556
+ '.dig-filter-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:96px}',
5557
+ '.dig-filter-on{color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}',
5500
5558
  '.dig-filter-path{flex:0 1 112px;min-width:70px;display:flex}',
5501
5559
  '.dig-load-more{margin:6px auto;display:block;padding:3px 12px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer}',
5502
5560
  '.dig-load-more:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',