dsh-ide-git 0.25.0 → 0.27.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 +6 -3
- package/README_EN.md +6 -3
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +496 -104
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ DSH 自带的 Git 面板覆盖「暂存 / 提交 / 还原 / 看历史」;`dsh-id
|
|
|
16
16
|
- **想指给 AI 一段代码**:在 diff 窗里选中几行、右键,把「哪个文件哪几行」(附提交或工作区上下文)直接发进当前会话的输入框。
|
|
17
17
|
- **想比较分支**:分支右键即可与当前分支比较,结果按文件与提交数汇报。
|
|
18
18
|
- **操作要够全**:签出、从任意分支/提交新建分支、重命名、删除、合并到当前、变基当前到此、新建标签、cherry-pick、还原提交、重置(保留/丢弃)、fetch / pull --ff-only / push(确认后)。
|
|
19
|
-
- **输入框里就能看 Git**:输入框工具条上有仓库与分支胶囊(着色的未提交 / 可拉取 / 待推送计数)
|
|
19
|
+
- **输入框里就能看 Git**:输入框工具条上有仓库与分支胶囊(着色的未提交 / 可拉取 / 待推送计数),点击弹出可搜索的切换器——仓库按名称/路径过滤,分支按 `/` 折成文件夹树(`fix/xxx` 一眼看清),输入即滤、方向键 + Enter 可选;多仓库工作区两边都能切,切换与面板实时联动,设置里可关(默认开)。切到新会话也不用等——插件会在你点开会话时就预热首屏数据,面板秒开。
|
|
20
20
|
|
|
21
21
|
## 效果
|
|
22
22
|
|
|
@@ -85,19 +85,22 @@ DSH 自带的 Git 面板覆盖「暂存 / 提交 / 还原 / 看历史」;`dsh-id
|
|
|
85
85
|
|
|
86
86
|
| 面 | 形态 | 本插件的布局 |
|
|
87
87
|
|---|---|---|
|
|
88
|
-
| 右侧栏(原生的,或底座桥接过来的) | 窄而高 | `stack
|
|
88
|
+
| 右侧栏(原生的,或底座桥接过来的) | 窄而高 | `stack`:变更区在上,下方是「历史 / 差异」双页签(点变更文件自动切到差异),分支树可收起 |
|
|
89
89
|
| 底部工作台(底座自有) | 宽而扁 | `columns`:分支树 · 提交图谱 · 变更与提交框,三栏并排——就是 JetBrains Git 工具窗口的样子 |
|
|
90
|
+
| 侧栏被拖得很窄(< 400px) | 窄 | `compact`:变更列表在上,下方是「历史 / 差异」双页签,分支树可收起 |
|
|
90
91
|
|
|
91
|
-
插件不猜自己在哪儿,而是用 `ResizeObserver` 量自己的容器:宽 ≥ 600px 且宽 ≥ 高 × 1.15
|
|
92
|
+
插件不猜自己在哪儿,而是用 `ResizeObserver` 量自己的容器:宽 ≥ 600px 且宽 ≥ 高 × 1.15 走三栏,宽 < 400px 走紧凑页签,其余走纵向堆叠(宽度说了算,高度没有否决权);两种纵向布局都把「历史 / 差异」做成页签,点变更文件自动切到差异。自由浮窗、移动端抽屉同样自适应。
|
|
92
93
|
|
|
93
94
|
## 功能一览
|
|
94
95
|
|
|
95
96
|
| 区域 | 能力 |
|
|
96
97
|
|---|---|
|
|
97
98
|
| 动作条 | IDEA 式左侧竖排(底部工作台)/ 顶栏下一行横排(右侧栏):刷新、新建分支、签出、删除、比较、显示差异、贮藏、新建标签、收藏、抓取、拉取、推送。**按可用空间自动决定放几个**,放不下的收进 `⋯ 更多`;末尾 `⚙ 设置` 可拖动排序、逐项显隐(存 `dsh-ide-git.rail.v1`)。每个按钮按当前状态点亮/置灰(没有其他分支就不能删除/签出/比较,没有远程就不能抓取/推送,没有上游就不能拉取,没有变更就没有差异可看) |
|
|
99
|
+
| 窄栏页签 | 宽度 < 600px 的两种纵向布局里:变更列表固定在上方,下方是「历史 / 差异」两个页签——点变更文件自动切到「差异」,历史列表与它的滚动位置都不再被顶走;两区之间的分隔条可拖动、双击复位(尺寸与 `dsh-ide-git.panes.v1` 其它面板同一份存储) |
|
|
98
100
|
| 悬停提示 | 鼠标停在图标按钮上 120ms,指针旁弹出它的操作名——竖排动作条里在图标右侧(跟着该行垂直居中,贴窗口右缘时翻到左侧),其余在下方;置灰的动作连原因一起给出(如 `隐藏 · 当前状态不可用`)。提示卡挂在窗口层,底部工作台裁不到;扫过一排图标不闪,按住鼠标拖拽 / 滚动 / 缩放时立即收起 |
|
|
99
101
|
| 状态行 | 当前分支、上游、`↑ahead ↓behind`、stash 数量、忙碌指示 |
|
|
100
102
|
| 输入框状态条 | 宿主输入框工具条里的仓库/分支胶囊(着色 ±未提交 / ↓可拉取 / ↑待推送),点击切换仓库与分支,与面板双向联动;单仓库只切分支,多仓库两边都切;设置可关(默认开) |
|
|
103
|
+
| 仓库/分支切换器 | 面板顶部与输入框胶囊共用同一个可搜索选择器:仓库按名称、路径、克隆目录或当前分支过滤,并标出各仓当前分支;分支按 `/` 折成文件夹树,输入时自动展开命中的目录,方向键 + Enter 可选,过滤器为空时目录可手动折叠 |
|
|
101
104
|
| 分支树 | `HEAD / 本地 / 远程 / 标签` 分组(可折叠)、过滤框、星标当前分支、ahead/behind 角标、worktree 占用标记;本地蓝 / 远程紫 / 标签黄 / HEAD 绿 |
|
|
102
105
|
| 工作树 | 动作条新增「工作树」视图:列出主工作树与全部链接工作树(当前 / 主工作树 / 已锁定 / 可清理标记、分支、短 HEAD、路径),可新建(新分支或挂已有分支,可指定起点)、在面板中打开、复制路径、移除(有未提交改动需二次强制确认)、清理失效记录;与官方「变更请求」面板并存 |
|
|
103
106
|
| 分支右键 | 签出、将当前分支变基到此、合并到当前分支、与当前分支比较、从该分支新建分支、重命名、删除(当前分支禁用)、更新(Fetch)、推送、发送到对话(分支名 + 仓库) |
|
package/README_EN.md
CHANGED
|
@@ -75,17 +75,20 @@ The same panel shows up on surfaces of very different shapes, so the layout meas
|
|
|
75
75
|
|
|
76
76
|
| Surface | Shape | Layout here |
|
|
77
77
|
|---|---|---|
|
|
78
|
-
| Right sidebar (native, or bridged by better-sidebar) | narrow + tall | `stack`: changes
|
|
78
|
+
| Right sidebar (native, or bridged by better-sidebar) | narrow + tall | `stack`: changes on top, history and diff as two tabs below (a click on a file switches to `Diff`), collapsible branch pane |
|
|
79
79
|
| Bottom workbench (better-sidebar only) | wide + flat | `columns`: tree / graph / changes — the JetBrains Git tool window shape |
|
|
80
|
+
| Sidebar dragged very narrow (< 400px) | narrow | `compact`: changes on top, history and diff as two tabs below, collapsible tree |
|
|
80
81
|
|
|
81
|
-
The panel never guesses: it measures itself with a `ResizeObserver` (width ≥ 600px and width ≥ 1.15 × height → columns, otherwise stack — width decides, height has no veto),
|
|
82
|
+
The panel never guesses: it measures itself with a `ResizeObserver` (width ≥ 600px and width ≥ 1.15 × height → columns, width < 400px → the compact tabs, otherwise stack — width decides, height has no veto); both stacked layouts make history and diff tabs, and a click on a changed file flips to `Diff`. Free-floating windows and the mobile drawer adapt too.
|
|
82
83
|
|
|
83
84
|
## Features
|
|
84
85
|
|
|
85
86
|
- 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).
|
|
87
|
+
- Tabs in the stacked layouts: below 600px (both `stack` and `compact`) the changes list keeps the top of the panel and the lower window is a `History` / `Diff` tab pair — opening a changed file switches to `Diff`, so the history list (and its scroll position) is never pushed away; the divider above that window drags and double-click resets (same store as the other panes, `dsh-ide-git.panes.v1`).
|
|
86
88
|
- 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.
|
|
87
89
|
- Status line: branch, upstream, ahead/behind, stash count, busy indicator.
|
|
88
|
-
- Composer status pill: repository/branch capsule in the host input-bar tool row (colored uncommitted / pullable / to-push counters), click to
|
|
90
|
+
- Composer status pill: repository/branch capsule in the host input-bar tool row (colored uncommitted / pullable / to-push counters), click to open a searchable switcher — repositories filter by name or path, branches fold on `/` into a folder tree (`fix/xxx` stays legible), typing filters immediately, arrow keys + Enter pick. 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.
|
|
91
|
+
- Repo/branch switcher: the panel header and the composer pills share one searchable picker. Repositories filter by name, path, clone directory or current branch and show each clone's checked-out branch; branches fold on `/` into a folder tree that opens matching folders while typing, arrow keys + Enter pick, and folders stay collapsible on an empty filter.
|
|
89
92
|
- Branch tree: HEAD / Local / Remote / Tags groups, filter box, ahead/behind badges, worktree marker; local blue / remote violet / tag amber / HEAD green.
|
|
90
93
|
- Worktrees: a rail view listing the primary and every linked worktree (current / primary / locked / prunable badges, branch, short HEAD, path). Create one on a new branch or attach an existing branch (with an optional start point), open it in the panel, copy its path, remove it (a dirty tree asks for a second, forced confirmation), and prune stale records. It coexists with the official change-request panel.
|
|
91
94
|
- Branch context menu: checkout, rebase current onto this, merge into current, compare with current, new branch from here, rename, delete, fetch, push, send to chat (branch + repo).
|
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.27.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.27.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
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* chrome:
|
|
10
10
|
*
|
|
11
11
|
* columns wide and tall enough : repo/branch bar | tree | graph | changes
|
|
12
|
-
* stack narrow and tall : changes over
|
|
13
|
-
* compact
|
|
12
|
+
* stack narrow and tall : changes over a tabbed history/diff window
|
|
13
|
+
* compact very narrow : changes on top + the same tabbed window
|
|
14
14
|
*
|
|
15
15
|
* Theming: no hard-coded surfaces. Every surface is a --dsw-alias-* token, so
|
|
16
16
|
* transparent themes and background plugins (dsh-any-background) show through,
|
|
@@ -90,6 +90,8 @@ window.__ModuleLoader__.load({
|
|
|
90
90
|
'repo.nested': '子仓库',
|
|
91
91
|
'repo.none': '当前工作区不是 Git 仓库',
|
|
92
92
|
'repo.pick': '选择要查看的仓库',
|
|
93
|
+
'repo.filter': '搜索仓库',
|
|
94
|
+
'picker.empty': '没有匹配项',
|
|
93
95
|
'repo.empty': '这个工作区下没有找到 Git 仓库',
|
|
94
96
|
'repo.switch': '切换仓库',
|
|
95
97
|
'branch.switch': '切换分支',
|
|
@@ -462,6 +464,8 @@ window.__ModuleLoader__.load({
|
|
|
462
464
|
'repo.nested': 'nested',
|
|
463
465
|
'repo.none': 'This workspace is not a Git repository',
|
|
464
466
|
'repo.pick': 'Pick a repository to inspect',
|
|
467
|
+
'repo.filter': 'Search repositories',
|
|
468
|
+
'picker.empty': 'No matches',
|
|
465
469
|
'repo.empty': 'No Git repository found in this workspace',
|
|
466
470
|
'repo.switch': 'Switch repository',
|
|
467
471
|
'branch.switch': 'Switch branch',
|
|
@@ -842,6 +846,8 @@ window.__ModuleLoader__.load({
|
|
|
842
846
|
'repo.nested': '巢狀',
|
|
843
847
|
'repo.none': '呢個工作區唔係 Git 存放庫',
|
|
844
848
|
'repo.pick': '揀一個存放庫嚟睇',
|
|
849
|
+
'repo.filter': '搵存放庫',
|
|
850
|
+
'picker.empty': '冇相符項目',
|
|
845
851
|
'repo.empty': '呢個工作區搵唔到 Git 存放庫',
|
|
846
852
|
'repo.switch': '切換存放庫',
|
|
847
853
|
'branch.switch': '切換分支',
|
|
@@ -1214,6 +1220,8 @@ window.__ModuleLoader__.load({
|
|
|
1214
1220
|
'repo.nested': '巢狀',
|
|
1215
1221
|
'repo.none': '這個工作區不是 Git 版本庫',
|
|
1216
1222
|
'repo.pick': '選擇要檢視的版本庫',
|
|
1223
|
+
'repo.filter': '搜尋版本庫',
|
|
1224
|
+
'picker.empty': '沒有相符項目',
|
|
1217
1225
|
'repo.empty': '這個工作區找不到 Git 版本庫',
|
|
1218
1226
|
'repo.switch': '切換版本庫',
|
|
1219
1227
|
'branch.switch': '切換分支',
|
|
@@ -1586,6 +1594,8 @@ window.__ModuleLoader__.load({
|
|
|
1586
1594
|
'repo.nested': '巢狀',
|
|
1587
1595
|
'repo.none': '呢個工作區唔係 Git 存放庫',
|
|
1588
1596
|
'repo.pick': '揀一個存放庫嚟睇',
|
|
1597
|
+
'repo.filter': '搵存放庫',
|
|
1598
|
+
'picker.empty': '冇相符項目',
|
|
1589
1599
|
'repo.empty': '呢個工作區搵唔到 Git 存放庫',
|
|
1590
1600
|
'repo.switch': '切換存放庫',
|
|
1591
1601
|
'branch.switch': '切換分支',
|
|
@@ -1958,6 +1968,8 @@ window.__ModuleLoader__.load({
|
|
|
1958
1968
|
'repo.nested': 'ネスト',
|
|
1959
1969
|
'repo.none': 'このワークスペースは Git リポジトリではありません',
|
|
1960
1970
|
'repo.pick': '確認するリポジトリを選択',
|
|
1971
|
+
'repo.filter': 'リポジトリを検索',
|
|
1972
|
+
'picker.empty': '一致する項目がありません',
|
|
1961
1973
|
'repo.empty': 'このワークスペースに Git リポジトリが見つかりません',
|
|
1962
1974
|
'repo.switch': 'リポジトリを切り替え',
|
|
1963
1975
|
'branch.switch': 'ブランチを切り替え',
|
|
@@ -2330,6 +2342,8 @@ window.__ModuleLoader__.load({
|
|
|
2330
2342
|
'repo.nested': '중첩',
|
|
2331
2343
|
'repo.none': '이 워크스페이스는 Git 저장소가 아닙니다',
|
|
2332
2344
|
'repo.pick': '확인할 저장소를 선택하세요',
|
|
2345
|
+
'repo.filter': '저장소 검색',
|
|
2346
|
+
'picker.empty': '일치하는 항목이 없습니다',
|
|
2333
2347
|
'repo.empty': '이 워크스페이스에서 Git 저장소를 찾을 수 없습니다',
|
|
2334
2348
|
'repo.switch': '저장소 전환',
|
|
2335
2349
|
'branch.switch': '브랜치 전환',
|
|
@@ -2702,6 +2716,8 @@ window.__ModuleLoader__.load({
|
|
|
2702
2716
|
'repo.nested': 'verschachtelt',
|
|
2703
2717
|
'repo.none': 'Dieser Arbeitsbereich ist kein Git-Repository',
|
|
2704
2718
|
'repo.pick': 'Repository zum Ansehen wählen',
|
|
2719
|
+
'repo.filter': 'Repositories durchsuchen',
|
|
2720
|
+
'picker.empty': 'Keine Treffer',
|
|
2705
2721
|
'repo.empty': 'In diesem Arbeitsbereich wurde kein Git-Repository gefunden',
|
|
2706
2722
|
'repo.switch': 'Repository wechseln',
|
|
2707
2723
|
'branch.switch': 'Branch wechseln',
|
|
@@ -3074,6 +3090,8 @@ window.__ModuleLoader__.load({
|
|
|
3074
3090
|
'repo.nested': 'imbriqué',
|
|
3075
3091
|
'repo.none': 'Cet espace de travail n\'est pas un dépôt Git',
|
|
3076
3092
|
'repo.pick': 'Choisir un dépôt à inspecter',
|
|
3093
|
+
'repo.filter': 'Rechercher des dépôts',
|
|
3094
|
+
'picker.empty': 'Aucun résultat',
|
|
3077
3095
|
'repo.empty': 'Aucun dépôt Git trouvé dans cet espace de travail',
|
|
3078
3096
|
'repo.switch': 'Changer de dépôt',
|
|
3079
3097
|
'branch.switch': 'Changer de branche',
|
|
@@ -3446,6 +3464,8 @@ window.__ModuleLoader__.load({
|
|
|
3446
3464
|
'repo.nested': 'вложенный',
|
|
3447
3465
|
'repo.none': 'Эта рабочая область не является репозиторием Git',
|
|
3448
3466
|
'repo.pick': 'Выберите репозиторий для просмотра',
|
|
3467
|
+
'repo.filter': 'Поиск репозиториев',
|
|
3468
|
+
'picker.empty': 'Нет совпадений',
|
|
3449
3469
|
'repo.empty': 'В этой рабочей области репозиторий Git не найден',
|
|
3450
3470
|
'repo.switch': 'Сменить репозиторий',
|
|
3451
3471
|
'branch.switch': 'Сменить ветку',
|
|
@@ -3818,6 +3838,8 @@ window.__ModuleLoader__.load({
|
|
|
3818
3838
|
'repo.nested': 'aninhado',
|
|
3819
3839
|
'repo.none': 'Esta área de trabalho não é um repositório Git',
|
|
3820
3840
|
'repo.pick': 'Escolha um repositório para inspecionar',
|
|
3841
|
+
'repo.filter': 'Pesquisar repositórios',
|
|
3842
|
+
'picker.empty': 'Nenhum resultado',
|
|
3821
3843
|
'repo.empty': 'Nenhum repositório Git encontrado nesta área de trabalho',
|
|
3822
3844
|
'repo.switch': 'Trocar de repositório',
|
|
3823
3845
|
'branch.switch': 'Trocar de branch',
|
|
@@ -4190,6 +4212,8 @@ window.__ModuleLoader__.load({
|
|
|
4190
4212
|
'repo.nested': 'annidato',
|
|
4191
4213
|
'repo.none': 'Questa area di lavoro non è un repository Git',
|
|
4192
4214
|
'repo.pick': 'Scegli un repository da esaminare',
|
|
4215
|
+
'repo.filter': 'Cerca repository',
|
|
4216
|
+
'picker.empty': 'Nessuna corrispondenza',
|
|
4193
4217
|
'repo.empty': 'Nessun repository Git trovato in questa area di lavoro',
|
|
4194
4218
|
'repo.switch': 'Cambia repository',
|
|
4195
4219
|
'branch.switch': 'Cambia branch',
|
|
@@ -4562,6 +4586,8 @@ window.__ModuleLoader__.load({
|
|
|
4562
4586
|
'repo.nested': 'genest',
|
|
4563
4587
|
'repo.none': 'Deze werkruimte is geen Git-repository',
|
|
4564
4588
|
'repo.pick': 'Kies een repository om te bekijken',
|
|
4589
|
+
'repo.filter': 'Repositories zoeken',
|
|
4590
|
+
'picker.empty': 'Geen resultaten',
|
|
4565
4591
|
'repo.empty': 'Geen Git-repository gevonden in deze werkruimte',
|
|
4566
4592
|
'repo.switch': 'Wissel van repository',
|
|
4567
4593
|
'branch.switch': 'Wissel van branch',
|
|
@@ -4934,6 +4960,8 @@ window.__ModuleLoader__.load({
|
|
|
4934
4960
|
'repo.nested': 'zagnieżdżone',
|
|
4935
4961
|
'repo.none': 'Ten obszar roboczy nie jest repozytorium Git',
|
|
4936
4962
|
'repo.pick': 'Wybierz repozytorium do podglądu',
|
|
4963
|
+
'repo.filter': 'Szukaj repozytoriów',
|
|
4964
|
+
'picker.empty': 'Brak dopasowań',
|
|
4937
4965
|
'repo.empty': 'W tym obszarze roboczym nie znaleziono repozytorium Git',
|
|
4938
4966
|
'repo.switch': 'Zmień repozytorium',
|
|
4939
4967
|
'branch.switch': 'Zmień gałąź',
|
|
@@ -5306,6 +5334,8 @@ window.__ModuleLoader__.load({
|
|
|
5306
5334
|
'repo.nested': 'nästlad',
|
|
5307
5335
|
'repo.none': 'Den här arbetsytan är inte ett Git-arkiv',
|
|
5308
5336
|
'repo.pick': 'Välj ett arkiv att granska',
|
|
5337
|
+
'repo.filter': 'Sök bland arkiv',
|
|
5338
|
+
'picker.empty': 'Inga träffar',
|
|
5309
5339
|
'repo.empty': 'Inget Git-arkiv hittades i den här arbetsytan',
|
|
5310
5340
|
'repo.switch': 'Byt arkiv',
|
|
5311
5341
|
'branch.switch': 'Byt gren',
|
|
@@ -5678,6 +5708,8 @@ window.__ModuleLoader__.load({
|
|
|
5678
5708
|
'repo.nested': 'iç içe',
|
|
5679
5709
|
'repo.none': 'Bu çalışma alanı bir Git deposu değil',
|
|
5680
5710
|
'repo.pick': 'İncelenecek depoyu seçin',
|
|
5711
|
+
'repo.filter': 'Depo ara',
|
|
5712
|
+
'picker.empty': 'Eşleşme yok',
|
|
5681
5713
|
'repo.empty': 'Bu çalışma alanında Git deposu bulunamadı',
|
|
5682
5714
|
'repo.switch': 'Depoyu değiştir',
|
|
5683
5715
|
'branch.switch': 'Dalı değiştir',
|
|
@@ -6050,6 +6082,8 @@ window.__ModuleLoader__.load({
|
|
|
6050
6082
|
'repo.nested': 'bersarang',
|
|
6051
6083
|
'repo.none': 'Ruang kerja ini bukan repositori Git',
|
|
6052
6084
|
'repo.pick': 'Pilih repositori untuk diperiksa',
|
|
6085
|
+
'repo.filter': 'Cari repositori',
|
|
6086
|
+
'picker.empty': 'Tidak ada kecocokan',
|
|
6053
6087
|
'repo.empty': 'Tidak ada repositori Git di ruang kerja ini',
|
|
6054
6088
|
'repo.switch': 'Ganti repositori',
|
|
6055
6089
|
'branch.switch': 'Ganti branch',
|
|
@@ -6422,6 +6456,8 @@ window.__ModuleLoader__.load({
|
|
|
6422
6456
|
'repo.nested': 'lồng nhau',
|
|
6423
6457
|
'repo.none': 'Không gian làm việc này không phải kho Git',
|
|
6424
6458
|
'repo.pick': 'Chọn một kho để xem',
|
|
6459
|
+
'repo.filter': 'Tìm kho',
|
|
6460
|
+
'picker.empty': 'Không có kết quả phù hợp',
|
|
6425
6461
|
'repo.empty': 'Không tìm thấy kho Git trong không gian làm việc này',
|
|
6426
6462
|
'repo.switch': 'Chuyển kho',
|
|
6427
6463
|
'branch.switch': 'Chuyển nhánh',
|
|
@@ -6794,6 +6830,8 @@ window.__ModuleLoader__.load({
|
|
|
6794
6830
|
'repo.nested': 'متشعّب',
|
|
6795
6831
|
'repo.none': 'مساحة العمل هذه ليست مستودع Git',
|
|
6796
6832
|
'repo.pick': 'اختر مستودعًا لعرضه',
|
|
6833
|
+
'repo.filter': 'البحث عن المستودعات',
|
|
6834
|
+
'picker.empty': 'لا توجد نتائج مطابقة',
|
|
6797
6835
|
'repo.empty': 'لم يُعثر على مستودع Git في مساحة العمل هذه',
|
|
6798
6836
|
'repo.switch': 'تبديل المستودع',
|
|
6799
6837
|
'branch.switch': 'تبديل الفرع',
|
|
@@ -7166,6 +7204,8 @@ window.__ModuleLoader__.load({
|
|
|
7166
7204
|
'repo.nested': 'नेस्टेड',
|
|
7167
7205
|
'repo.none': 'यह कार्यक्षेत्र Git रिपॉज़िटरी नहीं है',
|
|
7168
7206
|
'repo.pick': 'देखने के लिए रिपॉज़िटरी चुनें',
|
|
7207
|
+
'repo.filter': 'रिपॉज़िटरी खोजें',
|
|
7208
|
+
'picker.empty': 'कोई मेल नहीं',
|
|
7169
7209
|
'repo.empty': 'इस कार्यक्षेत्र में कोई Git रिपॉज़िटरी नहीं मिली',
|
|
7170
7210
|
'repo.switch': 'रिपॉज़िटरी बदलें',
|
|
7171
7211
|
'branch.switch': 'शाखा बदलें',
|
|
@@ -7538,6 +7578,8 @@ window.__ModuleLoader__.load({
|
|
|
7538
7578
|
'repo.nested': 'ซ้อนกัน',
|
|
7539
7579
|
'repo.none': 'เวิร์กสเปซนี้ไม่ใช่รีโพซิทอรี Git',
|
|
7540
7580
|
'repo.pick': 'เลือกรีโพซิทอรีที่จะดู',
|
|
7581
|
+
'repo.filter': 'ค้นหารีโพซิทอรี',
|
|
7582
|
+
'picker.empty': 'ไม่มีรายการที่ตรงกัน',
|
|
7541
7583
|
'repo.empty': 'ไม่พบรีโพซิทอรี Git ในเวิร์กสเปซนี้',
|
|
7542
7584
|
'repo.switch': 'สลับรีโพซิทอรี',
|
|
7543
7585
|
'branch.switch': 'สลับแบรนช์',
|
|
@@ -8852,8 +8894,12 @@ window.__ModuleLoader__.load({
|
|
|
8852
8894
|
panes measure width (columns). */
|
|
8853
8895
|
const PANE_CHROME_KEYS = {
|
|
8854
8896
|
columns: ['tree', 'changes', 'diff'],
|
|
8855
|
-
|
|
8856
|
-
|
|
8897
|
+
/* v0.26.1: BOTH stacked chromes put the history list and the diff in one
|
|
8898
|
+
TABBED window (history | diff) under the changes list, so the diff is
|
|
8899
|
+
no longer a pane of its own here — it fills the tab body. Only the
|
|
8900
|
+
three-column chrome still splits the diff out of the middle column. */
|
|
8901
|
+
stack: ['tree', 'changes'],
|
|
8902
|
+
compact: ['tree', 'changes'],
|
|
8857
8903
|
}
|
|
8858
8904
|
/* Whether a key measures a height (stacked chromes) or a width (columns).
|
|
8859
8905
|
This is the chrome's DEFAULT for the tree/changes panes; the diff pane
|
|
@@ -8869,9 +8915,8 @@ window.__ModuleLoader__.load({
|
|
|
8869
8915
|
'columns:diff': { min: 120, max: 900 },
|
|
8870
8916
|
'stack:tree': { min: 100, max: 420 },
|
|
8871
8917
|
'stack:changes': { min: 120, max: 460 },
|
|
8872
|
-
'stack:diff': { min: 120, max: 900 },
|
|
8873
8918
|
'compact:tree': { min: 100, max: 420 },
|
|
8874
|
-
'compact:
|
|
8919
|
+
'compact:changes': { min: 120, max: 460 },
|
|
8875
8920
|
}
|
|
8876
8921
|
/* Today's fixed sizes stay the default — and stay in the stylesheet: a pane
|
|
8877
8922
|
the user never dragged keeps exactly the look it had before, while a
|
|
@@ -8884,6 +8929,7 @@ window.__ModuleLoader__.load({
|
|
|
8884
8929
|
'stack:tree': { ratio: 0.36 },
|
|
8885
8930
|
'stack:changes': { ratio: 0.46 },
|
|
8886
8931
|
'compact:tree': { ratio: 0.42 },
|
|
8932
|
+
'compact:changes': { ratio: 0.46 },
|
|
8887
8933
|
}
|
|
8888
8934
|
const PANE_MAIN_MIN_W = 240
|
|
8889
8935
|
const PANE_MAIN_MIN_H = 150
|
|
@@ -8899,7 +8945,7 @@ window.__ModuleLoader__.load({
|
|
|
8899
8945
|
history↔diff separator lives inside the middle pane, so it is not counted
|
|
8900
8946
|
against the body's own budget. */
|
|
8901
8947
|
const PANE_GUTTER_PX = 8
|
|
8902
|
-
const PANE_BODY_GUTTERS = { columns: 2, stack: 2, compact:
|
|
8948
|
+
const PANE_BODY_GUTTERS = { columns: 2, stack: 2, compact: 2 }
|
|
8903
8949
|
const PANE_BUCKETS = Object.keys(PANE_CHROME_KEYS).reduce(
|
|
8904
8950
|
(all, chrome) => all.concat([chrome + ':wide', chrome + ':tall']), [])
|
|
8905
8951
|
|
|
@@ -10469,12 +10515,187 @@ window.__ModuleLoader__.load({
|
|
|
10469
10515
|
return E('div', { className: 'dig-menu-wrap', ref: ref, style: { left: position.left + 'px', top: position.top + 'px' } },
|
|
10470
10516
|
E('div', { className: 'dig-menu', ref: cardRef, 'data-menu-material': 'translucent' },
|
|
10471
10517
|
E('div', { className: 'dig-menu-scroll' },
|
|
10472
|
-
props.
|
|
10518
|
+
props.body === undefined
|
|
10519
|
+
? (Array.isArray(props.items) ? props.items : []).map((item, index) => renderItem(item, index, false))
|
|
10520
|
+
: props.body)),
|
|
10473
10521
|
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' } },
|
|
10474
10522
|
E('div', { className: 'dig-menu-scroll' },
|
|
10475
10523
|
sub.items.map((item, index) => renderItem(item, index, true)))))
|
|
10476
10524
|
}
|
|
10477
10525
|
|
|
10526
|
+
/* Searchable picker body shared by the panel header switchers and the
|
|
10527
|
+
composer pills. It owns no positioning: the panel renders it as a
|
|
10528
|
+
ContextMenu body, the composer inside its own .dig-cs-menu. Repositories
|
|
10529
|
+
are a flat, filtered list; branches keep their slash namespace as a
|
|
10530
|
+
collapsible tree, and a non-empty query opens every folder so a match
|
|
10531
|
+
is never hidden behind a fold. */
|
|
10532
|
+
function SearchPicker(props) {
|
|
10533
|
+
const t = props.t
|
|
10534
|
+
const mode = props.mode === 'branch' ? 'branch' : 'repo'
|
|
10535
|
+
const [needle, setNeedle] = useState('')
|
|
10536
|
+
const [folded, setFolded] = useState({})
|
|
10537
|
+
const [active, setActive] = useState(0)
|
|
10538
|
+
const inputRef = useRef(null)
|
|
10539
|
+
const listRef = useRef(null)
|
|
10540
|
+
useEffect(() => {
|
|
10541
|
+
const input = inputRef.current
|
|
10542
|
+
if (input !== null && typeof input.focus === 'function') input.focus({ preventScroll: true })
|
|
10543
|
+
}, [])
|
|
10544
|
+
const query = needle.trim().toLowerCase()
|
|
10545
|
+
const matches = (value) => query === '' || String(value === undefined || value === null ? '' : value).toLowerCase().indexOf(query) >= 0
|
|
10546
|
+
const rows = []
|
|
10547
|
+
if (mode === 'repo') {
|
|
10548
|
+
const repos = Array.isArray(props.repos) ? props.repos : []
|
|
10549
|
+
for (const repo of repos) {
|
|
10550
|
+
const name = repo.name === undefined || repo.name === null ? baseNameOf(repo.path) : String(repo.name)
|
|
10551
|
+
if (query !== '' && matches(name) === false && matches(repo.path) === false && matches(repo.sub) === false && matches(repo.branch) === false) continue
|
|
10552
|
+
rows.push({ key: 'repo:' + String(repo.path), kind: 'repo', repo: repo, name: name })
|
|
10553
|
+
}
|
|
10554
|
+
} else {
|
|
10555
|
+
const entries = (Array.isArray(props.branches) ? props.branches : []).filter((entry) => matches(entry.name))
|
|
10556
|
+
const root = { folders: new Map(), leaves: [] }
|
|
10557
|
+
for (const entry of entries) {
|
|
10558
|
+
const parts = String(entry.name).split('/')
|
|
10559
|
+
let node = root
|
|
10560
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
10561
|
+
const key = parts[index]
|
|
10562
|
+
if (node.folders.has(key) === false) node.folders.set(key, { folders: new Map(), leaves: [] })
|
|
10563
|
+
node = node.folders.get(key)
|
|
10564
|
+
}
|
|
10565
|
+
node.leaves.push({ entry: entry, label: parts[parts.length - 1] })
|
|
10566
|
+
}
|
|
10567
|
+
const countLeaves = (node) => node.leaves.length + Array.from(node.folders.values()).reduce((sum, child) => sum + countLeaves(child), 0)
|
|
10568
|
+
const walk = (node, prefix, depth) => {
|
|
10569
|
+
for (const name of Array.from(node.folders.keys()).sort()) {
|
|
10570
|
+
const id = prefix === '' ? name : prefix + '/' + name
|
|
10571
|
+
const open = query !== '' || folded[id] !== true
|
|
10572
|
+
rows.push({ key: 'dir:' + id, kind: 'folder', id: id, name: name, depth: depth, count: countLeaves(node.folders.get(name)), open: open })
|
|
10573
|
+
if (open === true) walk(node.folders.get(name), id, depth + 1)
|
|
10574
|
+
}
|
|
10575
|
+
for (const leaf of node.leaves) {
|
|
10576
|
+
const entry = leaf.entry
|
|
10577
|
+
const worktree = entry.worktree !== null && entry.worktree !== undefined
|
|
10578
|
+
const busy = typeof props.busyName === 'string' && props.busyName !== ''
|
|
10579
|
+
rows.push({
|
|
10580
|
+
key: 'branch:' + String(entry.name), kind: 'branch', entry: entry, label: leaf.label, depth: depth,
|
|
10581
|
+
disabled: worktree === true || busy === true, worktree: worktree,
|
|
10582
|
+
})
|
|
10583
|
+
}
|
|
10584
|
+
}
|
|
10585
|
+
walk(root, '', 0)
|
|
10586
|
+
}
|
|
10587
|
+
const selectable = rows.filter((row) => row.kind !== 'folder' && row.disabled !== true)
|
|
10588
|
+
const activeIndex = selectable.length === 0 ? -1 : Math.min(active, selectable.length - 1)
|
|
10589
|
+
const activeRow = activeIndex < 0 ? null : selectable[activeIndex]
|
|
10590
|
+
const activeKey = activeRow === null ? '' : activeRow.key
|
|
10591
|
+
useEffect(() => {
|
|
10592
|
+
const host = listRef.current
|
|
10593
|
+
if (host === null || activeKey === '') return
|
|
10594
|
+
const found = Array.from(host.querySelectorAll('[data-pick-key]')).find((row) => row.getAttribute('data-pick-key') === activeKey)
|
|
10595
|
+
if (found !== undefined && typeof found.scrollIntoView === 'function') found.scrollIntoView({ block: 'nearest' })
|
|
10596
|
+
}, [activeKey])
|
|
10597
|
+
const pickRow = (row) => {
|
|
10598
|
+
if (row === null || row.disabled === true || typeof props.onPick !== 'function') return
|
|
10599
|
+
if (row.kind === 'repo') props.onPick(row.repo.path, row.repo)
|
|
10600
|
+
else if (row.kind === 'branch') props.onPick(row.entry.name, row.entry)
|
|
10601
|
+
}
|
|
10602
|
+
const onKeyDown = (event) => {
|
|
10603
|
+
if (event.key === 'Escape') {
|
|
10604
|
+
event.preventDefault()
|
|
10605
|
+
event.stopPropagation()
|
|
10606
|
+
if (typeof props.onClose === 'function') props.onClose()
|
|
10607
|
+
return
|
|
10608
|
+
}
|
|
10609
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
10610
|
+
if (selectable.length === 0) return
|
|
10611
|
+
event.preventDefault()
|
|
10612
|
+
event.stopPropagation()
|
|
10613
|
+
setActive((value) => {
|
|
10614
|
+
const current = Math.min(value, selectable.length - 1)
|
|
10615
|
+
return event.key === 'ArrowDown' ? (current + 1) % selectable.length : (current - 1 + selectable.length) % selectable.length
|
|
10616
|
+
})
|
|
10617
|
+
return
|
|
10618
|
+
}
|
|
10619
|
+
if (event.key === 'Enter') {
|
|
10620
|
+
event.preventDefault()
|
|
10621
|
+
event.stopPropagation()
|
|
10622
|
+
pickRow(activeRow)
|
|
10623
|
+
}
|
|
10624
|
+
}
|
|
10625
|
+
const currentRepo = mode === 'repo' && props.current !== undefined && props.current !== null ? String(props.current) : ''
|
|
10626
|
+
const currentBranch = mode === 'branch' && props.current !== undefined && props.current !== null ? String(props.current) : ''
|
|
10627
|
+
const rowClass = (row) => 'dig-pick-row'
|
|
10628
|
+
+ (activeKey === row.key ? ' dig-pick-row-active' : '')
|
|
10629
|
+
+ ((currentRepo !== '' && row.kind === 'repo' && String(row.repo.path) === currentRepo) ? ' dig-pick-row-current' : '')
|
|
10630
|
+
+ ((currentBranch !== '' && row.kind === 'branch' && String(row.entry.name) === currentBranch) ? ' dig-pick-row-current' : '')
|
|
10631
|
+
+ (row.disabled === true ? ' dig-pick-row-disabled' : '')
|
|
10632
|
+
const rowsView = rows.map((row) => {
|
|
10633
|
+
if (row.kind === 'folder') {
|
|
10634
|
+
return E('button', {
|
|
10635
|
+
key: row.key, type: 'button',
|
|
10636
|
+
className: 'dig-pick-folder' + (row.open !== true ? ' dig-pick-folder-closed' : ''),
|
|
10637
|
+
style: { paddingLeft: (6 + row.depth * 14) + 'px' },
|
|
10638
|
+
onClick: () => { if (query !== '') return; setFolded((previous) => Object.assign({}, previous, { [row.id]: row.open === true })) },
|
|
10639
|
+
},
|
|
10640
|
+
E('span', { className: 'dig-pick-chevron' + (row.open === true ? ' dig-pick-chevron-open' : '') }, E(Icon, { name: 'chevron', size: 10 })),
|
|
10641
|
+
E(Icon, { name: 'folder', size: 12 }),
|
|
10642
|
+
E('span', { className: 'dig-pick-name' }, row.name),
|
|
10643
|
+
E('span', { className: 'dig-pick-count' }, String(row.count)))
|
|
10644
|
+
}
|
|
10645
|
+
if (row.kind === 'repo') {
|
|
10646
|
+
return E('button', {
|
|
10647
|
+
key: row.key, type: 'button', className: rowClass(row), 'data-pick-key': row.key,
|
|
10648
|
+
title: row.repo.path,
|
|
10649
|
+
onMouseEnter: () => { const index = selectable.indexOf(row); if (index >= 0) setActive(index) },
|
|
10650
|
+
onClick: () => pickRow(row),
|
|
10651
|
+
},
|
|
10652
|
+
E(Icon, { name: 'folder', size: 13 }),
|
|
10653
|
+
E('span', { className: 'dig-pick-main' },
|
|
10654
|
+
E('span', { className: 'dig-pick-name' }, row.name),
|
|
10655
|
+
row.repo.sub !== undefined && row.repo.sub !== null && row.repo.sub !== '' && row.repo.sub !== row.name
|
|
10656
|
+
? E('span', { className: 'dig-pick-sub' }, row.repo.sub)
|
|
10657
|
+
: null),
|
|
10658
|
+
row.repo.branch === undefined || row.repo.branch === null || row.repo.branch === ''
|
|
10659
|
+
? null
|
|
10660
|
+
: E('span', { className: 'dig-pick-badge' }, row.repo.branch))
|
|
10661
|
+
}
|
|
10662
|
+
const entry = row.entry
|
|
10663
|
+
const head = entry.head === true
|
|
10664
|
+
const worktreeNote = row.worktree === true
|
|
10665
|
+
? (props.worktreeNote === undefined ? t('branches.worktree') : props.worktreeNote)
|
|
10666
|
+
: null
|
|
10667
|
+
return E('button', {
|
|
10668
|
+
key: row.key, type: 'button',
|
|
10669
|
+
className: rowClass(row) + (head === true ? ' dig-pick-row-head' : ''),
|
|
10670
|
+
'data-pick-key': row.key,
|
|
10671
|
+
style: { paddingLeft: (6 + row.depth * 14) + 'px' },
|
|
10672
|
+
disabled: row.disabled === true,
|
|
10673
|
+
title: String(entry.name) + (worktreeNote === null ? '' : ' · ' + worktreeNote),
|
|
10674
|
+
onMouseEnter: () => { const index = selectable.indexOf(row); if (index >= 0) setActive(index) },
|
|
10675
|
+
onClick: () => pickRow(row),
|
|
10676
|
+
},
|
|
10677
|
+
E('span', { className: 'dig-pick-icon dig-tone-' + (head === true ? 'warn' : 'accent') }, E(Icon, { name: head === true ? 'star' : 'branch', size: 12 })),
|
|
10678
|
+
E('span', { className: 'dig-pick-name' }, row.label),
|
|
10679
|
+
worktreeNote === null ? null : E('span', { className: 'dig-pick-note' }, worktreeNote),
|
|
10680
|
+
entry.ahead > 0 ? E('span', { className: 'dig-pick-count' }, '↑' + String(entry.ahead)) : null,
|
|
10681
|
+
entry.behind > 0 ? E('span', { className: 'dig-pick-count' }, '↓' + String(entry.behind)) : null)
|
|
10682
|
+
})
|
|
10683
|
+
return E('div', { className: 'dig-pick dig-pick-' + mode },
|
|
10684
|
+
E('div', { className: 'dig-pick-search' },
|
|
10685
|
+
E('input', {
|
|
10686
|
+
ref: inputRef,
|
|
10687
|
+
className: 'dig-input dig-input-compact dig-pick-input',
|
|
10688
|
+
value: needle,
|
|
10689
|
+
spellCheck: false,
|
|
10690
|
+
placeholder: mode === 'repo' ? t('repo.filter') : t('branches.filter'),
|
|
10691
|
+
'aria-label': mode === 'repo' ? t('repo.filter') : t('branches.filter'),
|
|
10692
|
+
onChange: (event) => { setNeedle(event.target.value); setActive(0) },
|
|
10693
|
+
onKeyDown: onKeyDown,
|
|
10694
|
+
})),
|
|
10695
|
+
E('div', { className: 'dig-pick-list', ref: listRef, role: 'listbox' },
|
|
10696
|
+
rowsView.length === 0 ? E('div', { className: 'dig-pick-empty' }, t('picker.empty')) : rowsView))
|
|
10697
|
+
}
|
|
10698
|
+
|
|
10478
10699
|
/* Lets the user choose which actions the rail shows and in which order. The
|
|
10479
10700
|
list is drag-sortable and arrow-sortable (the arrows also work with a
|
|
10480
10701
|
keyboard); visibility is a per-action toggle, not a delete, so a hidden
|
|
@@ -12027,10 +12248,12 @@ window.__ModuleLoader__.load({
|
|
|
12027
12248
|
return
|
|
12028
12249
|
}
|
|
12029
12250
|
anchorRef.current = key
|
|
12030
|
-
/* Plain click
|
|
12031
|
-
the
|
|
12032
|
-
|
|
12251
|
+
/* Plain click SELECTS and shows the diff (v0.25.1, user request): in the
|
|
12252
|
+
narrow right column the list replaces the pane, so a click that only
|
|
12253
|
+
highlighted the row looked like "nothing happened". Modifier clicks
|
|
12254
|
+
above stay pure multi-select; ignored rows have no diff to show. */
|
|
12033
12255
|
setSelection((previous) => previous.size === 1 && previous.has(key) === true ? previous : new Set([key]))
|
|
12256
|
+
if (group !== 'ignored' && props.onDiff !== undefined) props.onDiff(item, group)
|
|
12034
12257
|
}, [setSelection, anchorRef, props.onDiff])
|
|
12035
12258
|
/* Rubber-band selection now comes from the shared useRubberBand hook
|
|
12036
12259
|
(see its comment): the fixed row heights + the prefix-sum offsets
|
|
@@ -13165,11 +13388,37 @@ window.__ModuleLoader__.load({
|
|
|
13165
13388
|
const [selectedPath, setSelectedPath] = useState(null)
|
|
13166
13389
|
const [patch, setPatch] = useState('')
|
|
13167
13390
|
const [patchLoading, setPatchLoading] = useState(false)
|
|
13391
|
+
/* The compact chrome's lower window is tabbed (history | diff) instead of
|
|
13392
|
+
split, so opening a file does not take the history list away: the diff
|
|
13393
|
+
gets its own tab and the user can switch back (v0.26.0). */
|
|
13394
|
+
const [splitTab, setSplitTab] = useState('history')
|
|
13395
|
+
useEffect(() => {
|
|
13396
|
+
if (splitTab === 'diff' && patch === '' && patchLoading === false) setSplitTab('history')
|
|
13397
|
+
}, [splitTab, patch, patchLoading])
|
|
13398
|
+
/* v0.26.3: the row fires onDiff from its single click now, so a double
|
|
13399
|
+
click used to open the same diff three times — and the responses raced
|
|
13400
|
+
(a slow first request could land after the second and paint the wrong
|
|
13401
|
+
file under the new title). `diffSeq` stamps each request and only the
|
|
13402
|
+
newest one is allowed to write; `diffLast` swallows the burst a double
|
|
13403
|
+
click produces. */
|
|
13404
|
+
const diffSeq = useRef(0)
|
|
13405
|
+
const diffLast = useRef({ key: '', at: 0 })
|
|
13168
13406
|
/* What the open diff pane shows (v0.13.5): the pane is shared by the
|
|
13169
13407
|
commit detail (hash + subject) and the working tree (staged or not),
|
|
13170
13408
|
and the selection menu's payload must say which. Cleared everywhere
|
|
13171
13409
|
the patch is cleared. */
|
|
13172
13410
|
const [diffContext, setDiffContext] = useState(null)
|
|
13411
|
+
/* v0.27: closing / switching away must also invalidate the request in
|
|
13412
|
+
flight and reset the double-click guard. Without the stamp bump a
|
|
13413
|
+
late response repainted the closed pane; without the diffLast reset
|
|
13414
|
+
reopening the same file within 400ms was swallowed. */
|
|
13415
|
+
const resetDiff = useCallback(() => {
|
|
13416
|
+
diffSeq.current += 1
|
|
13417
|
+
diffLast.current = { key: '', at: 0 }
|
|
13418
|
+
setPatch('')
|
|
13419
|
+
setPatchLoading(false)
|
|
13420
|
+
setDiffContext(null)
|
|
13421
|
+
}, [])
|
|
13173
13422
|
/* ---- multi-select (issue #13): the change list's selection lives here
|
|
13174
13423
|
so the right-click menu, the keyboard and the selection bar act on one
|
|
13175
13424
|
set; the branch tree and the history keep theirs closer to their rows
|
|
@@ -13203,6 +13452,10 @@ window.__ModuleLoader__.load({
|
|
|
13203
13452
|
const onChangeSelection = useCallback((update) => { setChangeSelection(update); clearPicksExcept('changes') }, [clearPicksExcept])
|
|
13204
13453
|
const onCommitPicks = useCallback((update) => { setPickedCommits(update); clearPicksExcept('history') }, [clearPicksExcept])
|
|
13205
13454
|
const [menu, setMenu] = useState(null)
|
|
13455
|
+
/* The header repo/branch switchers need a search box and a branch tree,
|
|
13456
|
+
which a plain ContextMenu item list cannot express; they share the
|
|
13457
|
+
same anchored surface through its `body` prop. */
|
|
13458
|
+
const [picker, setPicker] = useState(null)
|
|
13206
13459
|
const [dialog, setDialog] = useState(null)
|
|
13207
13460
|
const [tick, setTick] = useState(0)
|
|
13208
13461
|
const [favTick, setFavTick] = useState(0)
|
|
@@ -13818,11 +14071,10 @@ window.__ModuleLoader__.load({
|
|
|
13818
14071
|
headRef.current = null
|
|
13819
14072
|
setDetail(null)
|
|
13820
14073
|
setSelectedHash(null)
|
|
13821
|
-
|
|
13822
|
-
setDiffContext(null)
|
|
14074
|
+
resetDiff()
|
|
13823
14075
|
setView('history')
|
|
13824
14076
|
setPathFilter('')
|
|
13825
|
-
}, [sessionId])
|
|
14077
|
+
}, [sessionId, resetDiff])
|
|
13826
14078
|
|
|
13827
14079
|
/* The composer status pill and this panel are separate trees; when the
|
|
13828
14080
|
pill (or any other seat) switches the repository or the branch, this
|
|
@@ -13849,16 +14101,24 @@ window.__ModuleLoader__.load({
|
|
|
13849
14101
|
}, [sessionId, repoRoot, pickRepo, refresh])
|
|
13850
14102
|
|
|
13851
14103
|
const openDiff = useCallback(async (seed) => {
|
|
14104
|
+
const key = JSON.stringify([base, seed])
|
|
14105
|
+
const now = Date.now()
|
|
14106
|
+
if (key === diffLast.current.key && now - diffLast.current.at < 400) return
|
|
14107
|
+
diffLast.current = { key: key, at: now }
|
|
14108
|
+
const run = diffSeq.current + 1
|
|
14109
|
+
diffSeq.current = run
|
|
13852
14110
|
setPatchLoading(true)
|
|
13853
14111
|
setPatch('')
|
|
13854
14112
|
try {
|
|
13855
14113
|
const data = await request('diff', Object.assign({}, base, seed))
|
|
14114
|
+
if (diffSeq.current !== run) return
|
|
13856
14115
|
setPatch(data.patch)
|
|
13857
14116
|
} catch (caught) {
|
|
14117
|
+
if (diffSeq.current !== run) return
|
|
13858
14118
|
setErrorCode(null)
|
|
13859
14119
|
setError(caught instanceof Error ? caught.message : String(caught))
|
|
13860
14120
|
} finally {
|
|
13861
|
-
setPatchLoading(false)
|
|
14121
|
+
if (diffSeq.current === run) setPatchLoading(false)
|
|
13862
14122
|
}
|
|
13863
14123
|
}, [base])
|
|
13864
14124
|
|
|
@@ -13939,6 +14199,11 @@ window.__ModuleLoader__.load({
|
|
|
13939
14199
|
file menu, working-diff shortcut): external first, inline fallback. */
|
|
13940
14200
|
const showDiff = useCallback((seed) => {
|
|
13941
14201
|
if (previewDiff(seed) === true) return
|
|
14202
|
+
/* The pane header names the file; its close button clears the selection
|
|
14203
|
+
again, so the inline path owns selectedPath here too. */
|
|
14204
|
+
setSelectedPath(seed.path === undefined ? null : seed.path)
|
|
14205
|
+
/* Compact chrome: the diff lives in a tab, so opening a file shows it. */
|
|
14206
|
+
setSplitTab('diff')
|
|
13942
14207
|
if (seed.hash !== undefined && seed.hash !== null) {
|
|
13943
14208
|
setDiffContext({ kind: 'commit', hash: seed.hash, subject: seed.subject || '' })
|
|
13944
14209
|
void openDiff({ hash: seed.hash, path: seed.path })
|
|
@@ -13951,8 +14216,7 @@ window.__ModuleLoader__.load({
|
|
|
13951
14216
|
const selectCommit = useCallback(async (commit) => {
|
|
13952
14217
|
setSelectedHash(commit.hash)
|
|
13953
14218
|
setSelectedPath(null)
|
|
13954
|
-
|
|
13955
|
-
setDiffContext(null)
|
|
14219
|
+
resetDiff()
|
|
13956
14220
|
setView('detail')
|
|
13957
14221
|
setDetail(null)
|
|
13958
14222
|
try {
|
|
@@ -13961,7 +14225,7 @@ window.__ModuleLoader__.load({
|
|
|
13961
14225
|
setErrorCode(null)
|
|
13962
14226
|
setError(caught instanceof Error ? caught.message : String(caught))
|
|
13963
14227
|
}
|
|
13964
|
-
}, [base])
|
|
14228
|
+
}, [base, resetDiff])
|
|
13965
14229
|
|
|
13966
14230
|
const checkout = useCallback(async (entry) => {
|
|
13967
14231
|
if (entry.tag === true) { void run('checkout', { branch: entry.name }); return }
|
|
@@ -13988,6 +14252,7 @@ window.__ModuleLoader__.load({
|
|
|
13988
14252
|
const openMenuAt = useCallback((event, items) => {
|
|
13989
14253
|
const element = hostRef.current
|
|
13990
14254
|
const rect = element === null ? null : element.getBoundingClientRect()
|
|
14255
|
+
setPicker(null)
|
|
13991
14256
|
setMenu({
|
|
13992
14257
|
x: event.clientX - (rect === null ? 0 : rect.left),
|
|
13993
14258
|
y: event.clientY - (rect === null ? 0 : rect.top),
|
|
@@ -13997,6 +14262,22 @@ window.__ModuleLoader__.load({
|
|
|
13997
14262
|
})
|
|
13998
14263
|
}, [])
|
|
13999
14264
|
|
|
14265
|
+
/* Same anchor math as openMenuAt, but the body is a SearchPicker instead
|
|
14266
|
+
of a fixed item list. Keeping it separate means the generic context
|
|
14267
|
+
menus do not grow search state they never use. */
|
|
14268
|
+
const openPickerAt = useCallback((event, kind) => {
|
|
14269
|
+
const element = hostRef.current
|
|
14270
|
+
const rect = element === null ? null : element.getBoundingClientRect()
|
|
14271
|
+
setMenu(null)
|
|
14272
|
+
setPicker({
|
|
14273
|
+
kind: kind,
|
|
14274
|
+
x: event.clientX - (rect === null ? 0 : rect.left),
|
|
14275
|
+
y: event.clientY - (rect === null ? 0 : rect.top),
|
|
14276
|
+
boundsWidth: rect === null ? 0 : Math.round(rect.width),
|
|
14277
|
+
boundsHeight: rect === null ? 0 : Math.round(rect.height),
|
|
14278
|
+
})
|
|
14279
|
+
}, [])
|
|
14280
|
+
|
|
14000
14281
|
/* ---------- toast plumbing ---------- */
|
|
14001
14282
|
/* Declared BEFORE the context menus on purpose: the send-to-chat bindings
|
|
14002
14283
|
and every menu builder close over pushToast, and a hooks dependency
|
|
@@ -14712,7 +14993,13 @@ window.__ModuleLoader__.load({
|
|
|
14712
14993
|
|
|
14713
14994
|
/* ---------- shared pieces ---------- */
|
|
14714
14995
|
|
|
14715
|
-
|
|
14996
|
+
/* The scanned row keeps the branch it had when the workspace was
|
|
14997
|
+
listed; after a checkout the branch pill and the tree move on, so the
|
|
14998
|
+
repo label must follow the live HEAD instead of naming the old
|
|
14999
|
+
branch. */
|
|
15000
|
+
const repoOptions = repoState === null ? [] : repoState.repos.map((repo) => (
|
|
15001
|
+
repo.path === repoRoot && branches !== null ? Object.assign({}, repo, { branch: branches.branch }) : repo
|
|
15002
|
+
))
|
|
14716
15003
|
/* A linked worktree opened from the worktree view is a repository root
|
|
14717
15004
|
the workspace scan never listed. Without a header entry the top bar
|
|
14718
15005
|
kept naming the primary checkout while every branch and change below
|
|
@@ -14734,27 +15021,12 @@ window.__ModuleLoader__.load({
|
|
|
14734
15021
|
popovers reuse openMenuAt, so they clamp to the panel box and share
|
|
14735
15022
|
the opaque, frosted menu surface instead of the OS select popup. */
|
|
14736
15023
|
const openRepoMenu = useCallback((event) => {
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
icon: 'folder',
|
|
14740
|
-
tone: 'accent',
|
|
14741
|
-
label: repo.name + (repo.branch === null || repo.branch === undefined ? '' : ' · ' + repo.branch),
|
|
14742
|
-
active: repo.path === repoRoot,
|
|
14743
|
-
run: () => { if (repo.path !== repoRoot) pickRepo(repo.path) },
|
|
14744
|
-
})))
|
|
14745
|
-
}, [openMenuAt, headerRepos, repoRoot, pickRepo])
|
|
15024
|
+
openPickerAt(event, 'repo')
|
|
15025
|
+
}, [openPickerAt])
|
|
14746
15026
|
|
|
14747
15027
|
const openBranchSwitchMenu = useCallback((event) => {
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
id: 'switch:' + entry.name,
|
|
14751
|
-
icon: entry.head === true ? 'star' : 'branch',
|
|
14752
|
-
tone: entry.head === true ? 'warn' : 'accent',
|
|
14753
|
-
label: entry.name,
|
|
14754
|
-
active: entry.head === true,
|
|
14755
|
-
run: () => { if (entry.head !== true) void checkout(entry) },
|
|
14756
|
-
})))
|
|
14757
|
-
}, [openMenuAt, branches, checkout])
|
|
15028
|
+
openPickerAt(event, 'branch')
|
|
15029
|
+
}, [openPickerAt])
|
|
14758
15030
|
|
|
14759
15031
|
const topBar = E('div', { className: 'dig-topbar' },
|
|
14760
15032
|
E(RepoSelect, {
|
|
@@ -14815,7 +15087,7 @@ window.__ModuleLoader__.load({
|
|
|
14815
15087
|
const historyPane = view === 'detail'
|
|
14816
15088
|
? E(CommitDetail, {
|
|
14817
15089
|
t: t, detail: detail, selectedPath: selectedPath,
|
|
14818
|
-
onBack: () => { setView('history'); setSelectedPath(null);
|
|
15090
|
+
onBack: () => { setView('history'); setSelectedPath(null); resetDiff() },
|
|
14819
15091
|
/* Plain click only highlights the file; the DOUBLE-click opens its
|
|
14820
15092
|
diff (v0.18.3, one click model across every list). */
|
|
14821
15093
|
onSelectFile: (file) => { setSelectedPath(file.path) },
|
|
@@ -14846,16 +15118,20 @@ window.__ModuleLoader__.load({
|
|
|
14846
15118
|
request: request,
|
|
14847
15119
|
})
|
|
14848
15120
|
|
|
14849
|
-
|
|
15121
|
+
/* The compact chrome renders the diff INSTEAD of the changes list (the
|
|
15122
|
+
narrow right column has no neighbour to show it beside), so it needs a
|
|
15123
|
+
filling variant; every other chrome keeps the 55%-tall split pane. */
|
|
15124
|
+
const diffPaneOf = (fill) => patch === '' && patchLoading === false ? null : E('div', { className: 'dig-diff-pane' + (fill === true ? ' dig-diff-pane-fill' : ''), 'data-pane': 'diff', style: fill === true ? null : paneStyle('diff'), onContextMenu: onDiffPaneContextMenu },
|
|
14850
15125
|
E('div', { className: 'dig-diff-head' },
|
|
14851
15126
|
E('span', { className: 'dig-mono dig-diff-path' }, selectedPath === null ? '' : selectedPath),
|
|
14852
15127
|
E('button', {
|
|
14853
15128
|
type: 'button',
|
|
14854
15129
|
className: 'dig-icon-btn',
|
|
14855
15130
|
title: t('toast.close'),
|
|
14856
|
-
onClick: () => {
|
|
15131
|
+
onClick: () => { resetDiff(); setSelectedPath(null); setSplitTab('history') },
|
|
14857
15132
|
}, E(Icon, { name: 'close', size: 12 }))),
|
|
14858
15133
|
E(DiffBody, { patch: patch, loading: patchLoading, t: t }))
|
|
15134
|
+
const diffPane = diffPaneOf(false)
|
|
14859
15135
|
|
|
14860
15136
|
/* ---------- rail (IDE-style left action strip) ---------- */
|
|
14861
15137
|
|
|
@@ -15231,7 +15507,7 @@ window.__ModuleLoader__.load({
|
|
|
15231
15507
|
else if (spec.id === 'tree') { entry.active = treeOpen; entry.run = () => foldTree(!treeOpen) }
|
|
15232
15508
|
else if (spec.id === 'worktrees') {
|
|
15233
15509
|
entry.active = view === 'worktrees'
|
|
15234
|
-
entry.run = () => {
|
|
15510
|
+
entry.run = () => { resetDiff(); setView(view === 'worktrees' ? 'history' : 'worktrees') }
|
|
15235
15511
|
}
|
|
15236
15512
|
else if (spec.id === 'float') {
|
|
15237
15513
|
// Label AND icon follow where the panel actually is: floating panels get
|
|
@@ -15377,9 +15653,56 @@ window.__ModuleLoader__.load({
|
|
|
15377
15653
|
const worktrees = summary !== null && Array.isArray(summary.worktrees) ? summary.worktrees : []
|
|
15378
15654
|
|
|
15379
15655
|
/* The history↔diff separator only exists while a diff is on screen, and it
|
|
15380
|
-
lives inside the middle pane — the diff is the pane below it.
|
|
15656
|
+
lives inside the middle pane — the diff is the pane below it. Only the
|
|
15657
|
+
three-column chrome keeps that split (v0.26.1). */
|
|
15381
15658
|
const diffGutter = diffPane === null ? null : gutter('diff', 'next')
|
|
15382
15659
|
|
|
15660
|
+
/* v0.26.1 — the stacked chromes (stack and compact) show the history list
|
|
15661
|
+
and the diff in ONE tabbed window below the changes list: clicking a
|
|
15662
|
+
file flips that window to the diff tab instead of splitting the diff out
|
|
15663
|
+
and squeezing the history. `withCount` only matters on compact, whose
|
|
15664
|
+
changes head (and with it the "changes {n}" badge) is hidden; `withTree`
|
|
15665
|
+
adds the tree toggle that chrome has no other way to reach. */
|
|
15666
|
+
const noDiff = patch === '' && patchLoading === false
|
|
15667
|
+
const fillDiff = diffPaneOf(true)
|
|
15668
|
+
const splitTabs = (withCount, withTree) => E('div', { className: 'dig-split-tabs' },
|
|
15669
|
+
withCount === true ? E('span', { className: 'dig-compact-count' }, fill(t('seg.changes'), { n: dirty })) : null,
|
|
15670
|
+
E('button', {
|
|
15671
|
+
type: 'button',
|
|
15672
|
+
className: 'dig-tab',
|
|
15673
|
+
'data-active': splitTab === 'history' ? 'true' : 'false',
|
|
15674
|
+
'data-action': 'history-tab',
|
|
15675
|
+
onClick: () => setSplitTab('history'),
|
|
15676
|
+
}, t('seg.history')),
|
|
15677
|
+
E('button', {
|
|
15678
|
+
type: 'button',
|
|
15679
|
+
className: 'dig-tab',
|
|
15680
|
+
'data-active': splitTab === 'diff' ? 'true' : 'false',
|
|
15681
|
+
'data-action': 'diff-tab',
|
|
15682
|
+
disabled: noDiff,
|
|
15683
|
+
title: noDiff ? t('diff.empty') : t('diff.codeLabel'),
|
|
15684
|
+
onClick: () => setSplitTab('diff'),
|
|
15685
|
+
}, t('diff.codeLabel')),
|
|
15686
|
+
E('span', { className: 'dig-topbar-spacer' }),
|
|
15687
|
+
withTree === true ? E('button', {
|
|
15688
|
+
type: 'button',
|
|
15689
|
+
className: 'dig-icon-btn' + (treeOpen ? ' dig-icon-btn-active' : ''),
|
|
15690
|
+
'data-action': 'tree',
|
|
15691
|
+
'aria-pressed': treeOpen ? 'true' : 'false',
|
|
15692
|
+
title: t('toolbar.tree'),
|
|
15693
|
+
onClick: () => foldTree(!treeOpen),
|
|
15694
|
+
}, E(Icon, { name: treeOpen ? 'eye' : 'eyeOff' })) : null)
|
|
15695
|
+
|
|
15696
|
+
/* v0.26.3: a tabbed window keeps BOTH panes mounted and only hides the
|
|
15697
|
+
inactive one. Swapping the elements out unmounted the history list (and
|
|
15698
|
+
the commit detail) on every diff switch, which threw away its scroll
|
|
15699
|
+
position and the windowed list's state. `display:none` keeps the
|
|
15700
|
+
scrollTop (verified in Chromium). The hidden diff measures 0 tall and
|
|
15701
|
+
is dropped by measurePanes' `live > 0` filter, so pane geometry stays
|
|
15702
|
+
honest. */
|
|
15703
|
+
const diffActive = splitTab === 'diff' && fillDiff !== null
|
|
15704
|
+
const tabPane = (active, node) => node === null ? null : E('div', { className: 'dig-tab-pane' + (active === true ? '' : ' dig-tab-pane-hidden') }, node)
|
|
15705
|
+
|
|
15383
15706
|
let body
|
|
15384
15707
|
if (repoState === null) {
|
|
15385
15708
|
/* The scan is still running: a neutral loading state, never the
|
|
@@ -15403,31 +15726,19 @@ window.__ModuleLoader__.load({
|
|
|
15403
15726
|
onRemove: (tree) => setDialog({ kind: 'worktreeRemove', tree: tree, force: false }),
|
|
15404
15727
|
}))
|
|
15405
15728
|
} else if (compact) {
|
|
15729
|
+
/* v0.26.0: the changes list sits on TOP and the lower window is a tab
|
|
15730
|
+
pair (history | diff) instead of a split — opening a file no longer
|
|
15731
|
+
takes the history list away, and the changes list never disappears.
|
|
15732
|
+
The diff tab only exists while there is a patch to show. */
|
|
15406
15733
|
body = E('div', { className: 'dig-body dig-body-compact', ref: bodyRef },
|
|
15407
|
-
E('div', { className: 'dig-compact-bar' },
|
|
15408
|
-
E(Segmented, {
|
|
15409
|
-
value: view === 'detail' ? 'history' : view,
|
|
15410
|
-
onChange: (next) => { setView(next); setPatch(''); setDiffContext(null) },
|
|
15411
|
-
items: [
|
|
15412
|
-
{ id: 'changes', label: fill(t('seg.changes'), { n: dirty }) },
|
|
15413
|
-
{ id: 'history', label: t('seg.history') },
|
|
15414
|
-
],
|
|
15415
|
-
}),
|
|
15416
|
-
E('span', { className: 'dig-topbar-spacer' }),
|
|
15417
|
-
// Two-way on purpose: treeOpen is persisted, so a header button that
|
|
15418
|
-
// only rendered while the tree was OPEN left a folded tree with no way
|
|
15419
|
-
// back on this chrome (the rail now carries one too, for the others).
|
|
15420
|
-
E('button', {
|
|
15421
|
-
type: 'button',
|
|
15422
|
-
className: 'dig-icon-btn' + (treeOpen ? ' dig-icon-btn-active' : ''),
|
|
15423
|
-
'data-action': 'tree',
|
|
15424
|
-
'aria-pressed': treeOpen ? 'true' : 'false',
|
|
15425
|
-
title: t('toolbar.tree'),
|
|
15426
|
-
onClick: () => foldTree(!treeOpen),
|
|
15427
|
-
}, E(Icon, { name: treeOpen ? 'eye' : 'eyeOff' }))),
|
|
15428
15734
|
treeOpen ? E('div', { className: 'dig-compact-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: onBranchPicks, clearSignal: pickEpochs.tree })) : null,
|
|
15429
15735
|
treeOpen ? gutter('tree', 'prev') : null,
|
|
15430
|
-
|
|
15736
|
+
E('div', { className: 'dig-pane dig-pane-changes-stack', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane),
|
|
15737
|
+
gutter('changes', 'prev'),
|
|
15738
|
+
E('div', { className: 'dig-pane dig-pane-main' },
|
|
15739
|
+
splitTabs(true, true),
|
|
15740
|
+
tabPane(diffActive === false, historyPane),
|
|
15741
|
+
fillDiff === null ? null : tabPane(diffActive, fillDiff)))
|
|
15431
15742
|
} else if (columns) {
|
|
15432
15743
|
body = E('div', { className: 'dig-body dig-body-columns', ref: bodyRef },
|
|
15433
15744
|
treeOpen ? E('div', { className: 'dig-pane dig-pane-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: onBranchPicks, clearSignal: pickEpochs.tree })) : null,
|
|
@@ -15441,7 +15752,13 @@ window.__ModuleLoader__.load({
|
|
|
15441
15752
|
treeOpen ? gutter('tree', 'prev') : null,
|
|
15442
15753
|
E('div', { className: 'dig-pane dig-pane-changes-stack', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane),
|
|
15443
15754
|
gutter('changes', 'prev'),
|
|
15444
|
-
|
|
15755
|
+
/* v0.26.1: below the commit box the history list and the diff share
|
|
15756
|
+
one tabbed window — a click on a changed file flips to the diff tab
|
|
15757
|
+
and leaves the list (and its scroll position) alone. */
|
|
15758
|
+
E('div', { className: 'dig-pane dig-pane-main' },
|
|
15759
|
+
splitTabs(false, false),
|
|
15760
|
+
tabPane(diffActive === false, historyPane),
|
|
15761
|
+
fillDiff === null ? null : tabPane(diffActive, fillDiff)))
|
|
15445
15762
|
}
|
|
15446
15763
|
|
|
15447
15764
|
/* ---------- overlays ---------- */
|
|
@@ -15457,6 +15774,37 @@ window.__ModuleLoader__.load({
|
|
|
15457
15774
|
onClose: () => setMenu(null),
|
|
15458
15775
|
}))
|
|
15459
15776
|
}
|
|
15777
|
+
if (picker !== null) {
|
|
15778
|
+
/* The panel header's repo/branch switchers share the ContextMenu
|
|
15779
|
+
surface (same clamping and outside-click), but their body is the
|
|
15780
|
+
searchable picker instead of an item list. */
|
|
15781
|
+
const branchList = branches === null ? [] : branches.local
|
|
15782
|
+
overlays.push(E(ContextMenu, {
|
|
15783
|
+
key: 'picker',
|
|
15784
|
+
anchor: { x: picker.x, y: picker.y },
|
|
15785
|
+
boundsWidth: picker.boundsWidth,
|
|
15786
|
+
boundsHeight: picker.boundsHeight,
|
|
15787
|
+
items: [],
|
|
15788
|
+
body: E(SearchPicker, {
|
|
15789
|
+
key: picker.kind,
|
|
15790
|
+
mode: picker.kind,
|
|
15791
|
+
t: t,
|
|
15792
|
+
repos: picker.kind === 'repo' ? headerRepos : [],
|
|
15793
|
+
branches: picker.kind === 'branch' ? branchList : [],
|
|
15794
|
+
current: picker.kind === 'repo' ? repoRoot : (branches === null ? '' : branches.branch),
|
|
15795
|
+
onPick: (value, row) => {
|
|
15796
|
+
setPicker(null)
|
|
15797
|
+
if (picker.kind === 'repo') {
|
|
15798
|
+
if (value !== repoRoot) pickRepo(value)
|
|
15799
|
+
return
|
|
15800
|
+
}
|
|
15801
|
+
if (row.head !== true) void checkout(row)
|
|
15802
|
+
},
|
|
15803
|
+
onClose: () => setPicker(null),
|
|
15804
|
+
}),
|
|
15805
|
+
onClose: () => setPicker(null),
|
|
15806
|
+
}))
|
|
15807
|
+
}
|
|
15460
15808
|
if (railSettings === true) {
|
|
15461
15809
|
overlays.push(E(RailSettings, {
|
|
15462
15810
|
key: 'rail-settings',
|
|
@@ -15957,7 +16305,21 @@ window.__ModuleLoader__.load({
|
|
|
15957
16305
|
'.dig-pane-main{flex:1;min-width:0}',
|
|
15958
16306
|
'.dig-pane-changes{width:290px;flex:none;border-left:1px solid var(--dsw-alias-border-l2)}',
|
|
15959
16307
|
'.dig-pane-changes-stack{max-height:46%;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
15960
|
-
|
|
16308
|
+
/* v0.26.1: both stacked chromes show history and diff in one tabbed
|
|
16309
|
+
window; the strip is the window's tab bar (the compact chrome also
|
|
16310
|
+
hosts the changes count and the tree toggle in it). */
|
|
16311
|
+
'.dig-split-tabs{display:flex;align-items:center;gap:4px;padding:4px 6px;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
16312
|
+
/* v0.26.3: the tabbed window's bodies stay mounted; the inactive one is
|
|
16313
|
+
hidden so the history list keeps its scroll position and the detail
|
|
16314
|
+
keeps its expanded state. */
|
|
16315
|
+
'.dig-tab-pane{display:flex;flex-direction:column;flex:1;min-height:0;min-width:0}',
|
|
16316
|
+
'.dig-tab-pane-hidden{display:none}',
|
|
16317
|
+
'.dig-compact-count{color:var(--dsw-alias-label-secondary);font-weight:500;font-size:11px;padding:0 2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:45%}',
|
|
16318
|
+
'.dig-tab{border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;font-weight:500;padding:2px 10px;border-radius:var(--dsw-radius-sm,8px);cursor:pointer;white-space:nowrap;flex:none}',
|
|
16319
|
+
'.dig-tab:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
16320
|
+
'.dig-tab[data-active="true"]{background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary)}',
|
|
16321
|
+
'.dig-tab:disabled{opacity:.45;cursor:default}',
|
|
16322
|
+
'.dig-tab:disabled:hover{background:transparent;color:var(--dsw-alias-label-secondary)}',
|
|
15961
16323
|
'.dig-compact-tree{max-height:42%;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
15962
16324
|
// A divider: an 8px hit area (a fingertip needs it) painted as a 1px hairline
|
|
15963
16325
|
// that lights up on hover, focus or while dragging. touch-action:none is what
|
|
@@ -15991,6 +16353,29 @@ window.__ModuleLoader__.load({
|
|
|
15991
16353
|
'.dig-picker-name{flex:none;font-weight:600}',
|
|
15992
16354
|
'.dig-picker-kind{color:var(--dsw-alias-label-tertiary);flex:none}',
|
|
15993
16355
|
'.dig-picker-path{color:var(--dsw-alias-label-tertiary);margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:50%}',
|
|
16356
|
+
/* v0.27 searchable repo / branch picker (panel header + composer pill).
|
|
16357
|
+
The list scrolls, the search field stays pinned on top. */
|
|
16358
|
+
'.dig-pick{display:flex;flex-direction:column;gap:2px;width:300px;max-width:100%;min-height:0;color:var(--dsw-alias-label-primary)}',
|
|
16359
|
+
'.dig-pick-search{position:sticky;top:0;z-index:1;padding:2px 2px 6px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1))}',
|
|
16360
|
+
'.dig-pick-list{display:flex;flex-direction:column;gap:1px;overflow:auto;min-height:0;max-height:300px;overscroll-behavior:contain}',
|
|
16361
|
+
'.dig-pick-row{display:flex;align-items:center;gap:6px;width:100%;padding:4px 8px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:inherit;font:inherit;font-size:13px;text-align:left;cursor:pointer;min-width:0}',
|
|
16362
|
+
'.dig-pick-row:hover:not(:disabled),.dig-pick-folder:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
16363
|
+
'.dig-pick-row-active{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
16364
|
+
'.dig-pick-row-current{color:var(--dsw-alias-brand-primary)}',
|
|
16365
|
+
'.dig-pick-row-disabled{opacity:.45;cursor:default}',
|
|
16366
|
+
'.dig-pick-row-head{font-weight:600}',
|
|
16367
|
+
'.dig-pick-folder{display:flex;align-items:center;gap:5px;width:100%;padding:3px 8px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;text-align:left;cursor:pointer;min-width:0}',
|
|
16368
|
+
'.dig-pick-chevron{display:inline-flex;flex:none;transition:transform .12s ease;opacity:.6}',
|
|
16369
|
+
'.dig-pick-chevron-open{transform:rotate(90deg)}',
|
|
16370
|
+
'.dig-pick-main{display:flex;flex-direction:column;min-width:0;flex:1}',
|
|
16371
|
+
'.dig-pick-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
16372
|
+
'.dig-pick-sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:12px}',
|
|
16373
|
+
'.dig-pick-icon{display:inline-flex;flex:none}',
|
|
16374
|
+
'.dig-pick-badge{margin-left:auto;flex:none;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0 6px;border-radius:999px;corner-shape:round;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:10px;line-height:16px}',
|
|
16375
|
+
'.dig-pick-note{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px}',
|
|
16376
|
+
'.dig-pick-count{margin-left:auto;flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;font-variant-numeric:tabular-nums}',
|
|
16377
|
+
'.dig-pick-empty{padding:10px 8px;color:var(--dsw-alias-label-tertiary);font-size:12px}',
|
|
16378
|
+
'.dig-cs-menu.dig-cs-picker{width:300px;min-width:260px;max-width:340px;max-height:360px;padding:4px;overflow:hidden}',
|
|
15994
16379
|
/* position:relative: the marquee box paints against this wrapper (the
|
|
15995
16380
|
scroll container is offset by the search field — same rule as
|
|
15996
16381
|
.dig-history above). */
|
|
@@ -16013,6 +16398,11 @@ window.__ModuleLoader__.load({
|
|
|
16013
16398
|
'.dig-folder-head:hover{color:var(--dsw-alias-label-primary)}',
|
|
16014
16399
|
'.dig-folder-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
16015
16400
|
'.dig-count{margin-left:auto;opacity:.6;font-weight:400}',
|
|
16401
|
+
/* v0.26.2: the changes head is the one count that belongs to the WORD, not
|
|
16402
|
+
to the right edge — .dig-count pushes itself to the far right (fine for the
|
|
16403
|
+
branch groups), so the changes header opts back in to sitting right after
|
|
16404
|
+
its title (user report: the number floated in the middle of the row). */
|
|
16405
|
+
'.dig-changes-head .dig-count{margin-left:0}',
|
|
16016
16406
|
'.dig-chevron{display:inline-flex;transform:rotate(-90deg);transition:transform .12s ease}',
|
|
16017
16407
|
'.dig-chevron-open{transform:rotate(0deg)}',
|
|
16018
16408
|
'.dig-row{display:flex;align-items:center;gap:5px;padding:2px 8px;cursor:pointer;white-space:nowrap;overflow:hidden}',
|
|
@@ -16065,7 +16455,10 @@ window.__ModuleLoader__.load({
|
|
|
16065
16455
|
filter bar, and painting from the wrong rect was the v0.19.2 drift. */
|
|
16066
16456
|
'.dig-history{display:flex;flex-direction:column;min-height:0;flex:1;position:relative}',
|
|
16067
16457
|
'.dig-history-scroll{flex:1;overflow:auto}',
|
|
16068
|
-
'
|
|
16458
|
+
// The row's left edge follows the bars above it (filter row / compact
|
|
16459
|
+
// switch: 4px 6px padding), so the time column and the graph start on the
|
|
16460
|
+
// same inset as the input box instead of flush against the pane edge.
|
|
16461
|
+
'.dig-commit{display:flex;align-items:center;gap:6px;padding:1px 8px 1px 6px;cursor:pointer;height:26px;overflow:hidden}',
|
|
16069
16462
|
'.dig-commit:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
16070
16463
|
/* Same theme-aware gray mix as .dig-row-selected (see it). */
|
|
16071
16464
|
'.dig-commit-selected{background:color-mix(in srgb,var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)) 58%,var(--dsw-static-neutral-bluish-75,var(--dsw-alias-bg-layer-3)) 42%);color:var(--dsw-alias-label-primary);box-shadow:inset 2px 0 0 var(--dsw-alias-brand-primary)}',
|
|
@@ -16176,6 +16569,7 @@ window.__ModuleLoader__.load({
|
|
|
16176
16569
|
'.dig-detail-files{display:flex;flex-direction:column}',
|
|
16177
16570
|
'.dig-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}',
|
|
16178
16571
|
'.dig-diff-pane{flex:none;max-height:55%;display:flex;flex-direction:column;min-height:0;border-top:1px solid var(--dsw-alias-border-l2)}',
|
|
16572
|
+
'.dig-diff-pane-fill{flex:1;max-height:none;border-top:none}',
|
|
16179
16573
|
'.dig-diff-head{display:flex;align-items:center;gap:8px;padding:3px 8px;color:var(--dsw-alias-label-tertiary);flex:none;min-width:0}',
|
|
16180
16574
|
'.dig-diff-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}',
|
|
16181
16575
|
'.dig-diff{flex:1;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:calc(12px + var(--dsh-code-font-delta, 0px));line-height:1.5;font-weight:500;min-height:0}',
|
|
@@ -17180,50 +17574,48 @@ window.__ModuleLoader__.load({
|
|
|
17180
17574
|
behind > 0 ? E('span', { className: 'dig-cs-badge dig-cs-behind', title: t('composer.behindHint') }, '↓' + String(behind)) : null,
|
|
17181
17575
|
ahead > 0 ? E('span', { className: 'dig-cs-badge dig-cs-ahead', title: t('composer.aheadHint') }, '↑' + String(ahead)) : null,
|
|
17182
17576
|
error !== '' ? E('span', { className: 'dig-cs-error', title: error }, '×') : null,
|
|
17183
|
-
menu === 'repo' ? E('div', { className: 'dig-cs-menu', 'data-cordis-panel': '' },
|
|
17577
|
+
menu === 'repo' ? E('div', { className: 'dig-cs-menu dig-cs-picker', 'data-cordis-panel': '' },
|
|
17184
17578
|
/* v0.19.2: one row per checkout — the repository's own name first
|
|
17185
17579
|
(origin URL basename, falling back to the directory name), and the
|
|
17186
17580
|
single-level directory name as a small sub line only when the two
|
|
17187
17581
|
differ (two clones of one repository stay tellable apart). The
|
|
17188
|
-
full path lives in the tooltip, not in the row.
|
|
17189
|
-
|
|
17190
|
-
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17582
|
+
full path lives in the tooltip, not in the row. v0.27 adds the
|
|
17583
|
+
search field so seven clones need no scrolling to find one. */
|
|
17584
|
+
E(SearchPicker, {
|
|
17585
|
+
key: 'composer-repo',
|
|
17586
|
+
mode: 'repo',
|
|
17587
|
+
t: t,
|
|
17588
|
+
repos: data.repos.map((row) => {
|
|
17589
|
+
const dirName = baseNameOf(row.path)
|
|
17590
|
+
const displayName = row.origin !== undefined && row.origin !== '' ? row.origin : dirName
|
|
17591
|
+
return {
|
|
17592
|
+
path: row.path,
|
|
17593
|
+
name: displayName,
|
|
17594
|
+
sub: dirName !== displayName ? dirName : undefined,
|
|
17595
|
+
}
|
|
17596
|
+
}),
|
|
17597
|
+
current: repoRoot,
|
|
17598
|
+
onPick: (path) => pickRepoRoot(path),
|
|
17599
|
+
onClose: () => setMenu(null),
|
|
17204
17600
|
}))
|
|
17205
17601
|
: null,
|
|
17206
17602
|
menu === 'branch' ? (branchLoading === true || branchList === null
|
|
17207
17603
|
? E('div', { className: 'dig-cs-menu', 'data-cordis-panel': '' }, E('div', { className: 'dig-cs-item dig-cs-static' }, '…'))
|
|
17208
|
-
: E('div', { className: 'dig-cs-menu', 'data-cordis-panel': '' },
|
|
17209
|
-
|
|
17210
|
-
key:
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
? E('span', { className: 'dig-cs-item-counts' },
|
|
17224
|
-
entry.ahead > 0 ? E('span', { className: 'dig-cs-ahead' }, '↑' + String(entry.ahead)) : null,
|
|
17225
|
-
entry.behind > 0 ? E('span', { className: 'dig-cs-behind' }, '↓' + String(entry.behind)) : null)
|
|
17226
|
-
: null))))
|
|
17604
|
+
: E('div', { className: 'dig-cs-menu dig-cs-picker', 'data-cordis-panel': '' },
|
|
17605
|
+
E(SearchPicker, {
|
|
17606
|
+
key: 'composer-branch',
|
|
17607
|
+
mode: 'branch',
|
|
17608
|
+
t: t,
|
|
17609
|
+
branches: branchList.local,
|
|
17610
|
+
current: branchName === null ? '' : branchName,
|
|
17611
|
+
busyName: checkingOut,
|
|
17612
|
+
worktreeNote: t('composer.inWorktree'),
|
|
17613
|
+
onPick: (name, entry) => {
|
|
17614
|
+
if (entry.head === true) { setMenu(null); return }
|
|
17615
|
+
void checkoutBranch(name)
|
|
17616
|
+
},
|
|
17617
|
+
onClose: () => setMenu(null),
|
|
17618
|
+
})))
|
|
17227
17619
|
: null,
|
|
17228
17620
|
)
|
|
17229
17621
|
}
|