dsh-ide-git 0.14.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +894 -93
- package/src/index.js +152 -21
package/src/client.js
CHANGED
|
@@ -21,6 +21,7 @@ window.__ModuleLoader__.load({
|
|
|
21
21
|
factory: (require) => {
|
|
22
22
|
const React = require('react')
|
|
23
23
|
const E = React.createElement
|
|
24
|
+
const Fragment = React.Fragment
|
|
24
25
|
const useState = React.useState
|
|
25
26
|
const useEffect = React.useEffect
|
|
26
27
|
const useMemo = React.useMemo
|
|
@@ -186,6 +187,22 @@ window.__ModuleLoader__.load({
|
|
|
186
187
|
'diff.copy': '复制',
|
|
187
188
|
'diff.copied': '已复制',
|
|
188
189
|
'toolbar.refresh': '刷新',
|
|
190
|
+
'toolbar.more': '更多操作',
|
|
191
|
+
'more.sortGit': 'Git 顺序',
|
|
192
|
+
'more.sortPath': '按路径',
|
|
193
|
+
'more.sortName': '按名称',
|
|
194
|
+
'more.sortStatus': '按状态',
|
|
195
|
+
'more.clone': '克隆...',
|
|
196
|
+
'more.stashPop': '弹出最近贮藏',
|
|
197
|
+
'more.deleteTag': '删除标签...',
|
|
198
|
+
'more.gitOutput': '显示 Git 输出',
|
|
199
|
+
'clone.title': '克隆仓库',
|
|
200
|
+
'clone.url': '仓库 URL',
|
|
201
|
+
'clone.dir': '目标目录(绝对路径)',
|
|
202
|
+
'clone.run': '克隆',
|
|
203
|
+
'prompt.deleteTag': '要删除的标签名',
|
|
204
|
+
'gitLog.title': 'Git 输出',
|
|
205
|
+
'gitLog.empty': '暂无 Git 运行记录',
|
|
189
206
|
'toolbar.newBranch': '新建分支',
|
|
190
207
|
'toolbar.fetch': '抓取(Fetch)',
|
|
191
208
|
'toolbar.pull': '拉取(Pull)',
|
|
@@ -411,6 +428,22 @@ window.__ModuleLoader__.load({
|
|
|
411
428
|
'diff.copy': 'Copy',
|
|
412
429
|
'diff.copied': 'Copied',
|
|
413
430
|
'toolbar.refresh': 'Refresh',
|
|
431
|
+
'toolbar.more': 'More actions',
|
|
432
|
+
'more.sortGit': 'Git order',
|
|
433
|
+
'more.sortPath': 'By path',
|
|
434
|
+
'more.sortName': 'By name',
|
|
435
|
+
'more.sortStatus': 'By status',
|
|
436
|
+
'more.clone': 'Clone...',
|
|
437
|
+
'more.stashPop': 'Pop latest stash',
|
|
438
|
+
'more.deleteTag': 'Delete tag...',
|
|
439
|
+
'more.gitOutput': 'Show Git output',
|
|
440
|
+
'clone.title': 'Clone repository',
|
|
441
|
+
'clone.url': 'Repository URL',
|
|
442
|
+
'clone.dir': 'Target directory (absolute path)',
|
|
443
|
+
'clone.run': 'Clone',
|
|
444
|
+
'prompt.deleteTag': 'Tag name to delete',
|
|
445
|
+
'gitLog.title': 'Git output',
|
|
446
|
+
'gitLog.empty': 'No git runs yet',
|
|
414
447
|
'toolbar.newBranch': 'New branch',
|
|
415
448
|
'toolbar.fetch': 'Fetch',
|
|
416
449
|
'toolbar.pull': 'Pull',
|
|
@@ -644,6 +677,22 @@ window.__ModuleLoader__.load({
|
|
|
644
677
|
'diff.copy': '複製',
|
|
645
678
|
'diff.copied': '已複製',
|
|
646
679
|
'toolbar.refresh': '重新整理',
|
|
680
|
+
'toolbar.more': '更多操作',
|
|
681
|
+
'more.sortGit': 'Git 順序',
|
|
682
|
+
'more.sortPath': '按路徑',
|
|
683
|
+
'more.sortName': '按名稱',
|
|
684
|
+
'more.sortStatus': '按狀態',
|
|
685
|
+
'more.clone': '複製存放庫...',
|
|
686
|
+
'more.stashPop': '彈出最近貯藏',
|
|
687
|
+
'more.deleteTag': '刪除標籤...',
|
|
688
|
+
'more.gitOutput': '顯示 Git 輸出',
|
|
689
|
+
'clone.title': '複製存放庫',
|
|
690
|
+
'clone.url': '存放庫 URL',
|
|
691
|
+
'clone.dir': '目標目錄(絕對路徑)',
|
|
692
|
+
'clone.run': '複製',
|
|
693
|
+
'prompt.deleteTag': '要刪除的標籤名',
|
|
694
|
+
'gitLog.title': 'Git 輸出',
|
|
695
|
+
'gitLog.empty': '暫無 Git 執行記錄',
|
|
647
696
|
'toolbar.newBranch': '新建分支',
|
|
648
697
|
'toolbar.fetch': '抓取',
|
|
649
698
|
'toolbar.pull': '拉取',
|
|
@@ -869,6 +918,22 @@ window.__ModuleLoader__.load({
|
|
|
869
918
|
'diff.copy': '複製',
|
|
870
919
|
'diff.copied': '已複製',
|
|
871
920
|
'toolbar.refresh': '重新整理',
|
|
921
|
+
'toolbar.more': '更多操作',
|
|
922
|
+
'more.sortGit': 'Git 順序',
|
|
923
|
+
'more.sortPath': '按路徑',
|
|
924
|
+
'more.sortName': '按名稱',
|
|
925
|
+
'more.sortStatus': '按狀態',
|
|
926
|
+
'more.clone': '複製存放庫...',
|
|
927
|
+
'more.stashPop': '取出最近擱置',
|
|
928
|
+
'more.deleteTag': '刪除標籤...',
|
|
929
|
+
'more.gitOutput': '顯示 Git 輸出',
|
|
930
|
+
'clone.title': '複製存放庫',
|
|
931
|
+
'clone.url': '存放庫 URL',
|
|
932
|
+
'clone.dir': '目標目錄(絕對路徑)',
|
|
933
|
+
'clone.run': '複製',
|
|
934
|
+
'prompt.deleteTag': '要刪除的標籤名',
|
|
935
|
+
'gitLog.title': 'Git 輸出',
|
|
936
|
+
'gitLog.empty': '暫無 Git 執行記錄',
|
|
872
937
|
'toolbar.newBranch': '新建分支',
|
|
873
938
|
'toolbar.fetch': '抓取',
|
|
874
939
|
'toolbar.pull': '拉取',
|
|
@@ -1094,6 +1159,22 @@ window.__ModuleLoader__.load({
|
|
|
1094
1159
|
'diff.copy': '複製',
|
|
1095
1160
|
'diff.copied': '已複製',
|
|
1096
1161
|
'toolbar.refresh': '重新整理',
|
|
1162
|
+
'toolbar.more': '更多操作',
|
|
1163
|
+
'more.sortGit': 'Git 順序',
|
|
1164
|
+
'more.sortPath': '按路徑',
|
|
1165
|
+
'more.sortName': '按名稱',
|
|
1166
|
+
'more.sortStatus': '按狀態',
|
|
1167
|
+
'more.clone': '複製存放庫...',
|
|
1168
|
+
'more.stashPop': '彈出最近貯藏',
|
|
1169
|
+
'more.deleteTag': '刪除標籤...',
|
|
1170
|
+
'more.gitOutput': '顯示 Git 輸出',
|
|
1171
|
+
'clone.title': '複製存放庫',
|
|
1172
|
+
'clone.url': '存放庫 URL',
|
|
1173
|
+
'clone.dir': '目標目錄(絕對路徑)',
|
|
1174
|
+
'clone.run': '複製',
|
|
1175
|
+
'prompt.deleteTag': '要刪除的標籤名',
|
|
1176
|
+
'gitLog.title': 'Git 輸出',
|
|
1177
|
+
'gitLog.empty': '暫無 Git 執行記錄',
|
|
1097
1178
|
'toolbar.newBranch': '新建分支',
|
|
1098
1179
|
'toolbar.fetch': '抓取',
|
|
1099
1180
|
'toolbar.pull': '拉取',
|
|
@@ -1319,6 +1400,22 @@ window.__ModuleLoader__.load({
|
|
|
1319
1400
|
'diff.copy': 'コピー',
|
|
1320
1401
|
'diff.copied': 'コピーしました',
|
|
1321
1402
|
'toolbar.refresh': '更新',
|
|
1403
|
+
'toolbar.more': 'その他の操作',
|
|
1404
|
+
'more.sortGit': 'Git の順序',
|
|
1405
|
+
'more.sortPath': 'パス順',
|
|
1406
|
+
'more.sortName': '名前順',
|
|
1407
|
+
'more.sortStatus': '状態順',
|
|
1408
|
+
'more.clone': 'リポジトリを複製...',
|
|
1409
|
+
'more.stashPop': '最新のスタッシュをポップ',
|
|
1410
|
+
'more.deleteTag': 'タグを削除...',
|
|
1411
|
+
'more.gitOutput': 'Git 出力を表示',
|
|
1412
|
+
'clone.title': 'リポジトリを複製',
|
|
1413
|
+
'clone.url': 'リポジトリ URL',
|
|
1414
|
+
'clone.dir': '複製先ディレクトリ(絶対パス)',
|
|
1415
|
+
'clone.run': '複製',
|
|
1416
|
+
'prompt.deleteTag': '削除するタグ名',
|
|
1417
|
+
'gitLog.title': 'Git 出力',
|
|
1418
|
+
'gitLog.empty': 'Git 実行履歴はまだありません',
|
|
1322
1419
|
'toolbar.newBranch': '新しいブランチ',
|
|
1323
1420
|
'toolbar.fetch': 'フェッチ',
|
|
1324
1421
|
'toolbar.pull': 'プル',
|
|
@@ -1544,6 +1641,22 @@ window.__ModuleLoader__.load({
|
|
|
1544
1641
|
'diff.copy': '복사',
|
|
1545
1642
|
'diff.copied': '복사됨',
|
|
1546
1643
|
'toolbar.refresh': '새로 고침',
|
|
1644
|
+
'toolbar.more': '더 보기',
|
|
1645
|
+
'more.sortGit': 'Git 순서',
|
|
1646
|
+
'more.sortPath': '경로순',
|
|
1647
|
+
'more.sortName': '이름순',
|
|
1648
|
+
'more.sortStatus': '상태순',
|
|
1649
|
+
'more.clone': '복제...',
|
|
1650
|
+
'more.stashPop': '최신 스태시 팝',
|
|
1651
|
+
'more.deleteTag': '태그 삭제...',
|
|
1652
|
+
'more.gitOutput': 'Git 출력 표시',
|
|
1653
|
+
'clone.title': '리포지토리 복제',
|
|
1654
|
+
'clone.url': '리포지토리 URL',
|
|
1655
|
+
'clone.dir': '대상 디렉터리(절대 경로)',
|
|
1656
|
+
'clone.run': '복제',
|
|
1657
|
+
'prompt.deleteTag': '삭제할 태그 이름',
|
|
1658
|
+
'gitLog.title': 'Git 출력',
|
|
1659
|
+
'gitLog.empty': '아직 Git 실행 기록이 없습니다',
|
|
1547
1660
|
'toolbar.newBranch': '새 브랜치',
|
|
1548
1661
|
'toolbar.fetch': '페치',
|
|
1549
1662
|
'toolbar.pull': '풀',
|
|
@@ -1769,6 +1882,22 @@ window.__ModuleLoader__.load({
|
|
|
1769
1882
|
'diff.copy': 'Kopieren',
|
|
1770
1883
|
'diff.copied': 'Kopiert',
|
|
1771
1884
|
'toolbar.refresh': 'Aktualisieren',
|
|
1885
|
+
'toolbar.more': 'Weitere Aktionen',
|
|
1886
|
+
'more.sortGit': 'Git-Reihenfolge',
|
|
1887
|
+
'more.sortPath': 'Nach Pfad',
|
|
1888
|
+
'more.sortName': 'Nach Name',
|
|
1889
|
+
'more.sortStatus': 'Nach Status',
|
|
1890
|
+
'more.clone': 'Repository klonen...',
|
|
1891
|
+
'more.stashPop': 'Letzten Stash poppen',
|
|
1892
|
+
'more.deleteTag': 'Tag löschen...',
|
|
1893
|
+
'more.gitOutput': 'Git-Ausgabe anzeigen',
|
|
1894
|
+
'clone.title': 'Repository klonen',
|
|
1895
|
+
'clone.url': 'Repository-URL',
|
|
1896
|
+
'clone.dir': 'Zielverzeichnis (absoluter Pfad)',
|
|
1897
|
+
'clone.run': 'Klonen',
|
|
1898
|
+
'prompt.deleteTag': 'Zu löschender Tag-Name',
|
|
1899
|
+
'gitLog.title': 'Git-Ausgabe',
|
|
1900
|
+
'gitLog.empty': 'Noch keine Git-Ausführungen',
|
|
1772
1901
|
'toolbar.newBranch': 'Neuer Branch',
|
|
1773
1902
|
'toolbar.fetch': 'Fetch',
|
|
1774
1903
|
'toolbar.pull': 'Pull',
|
|
@@ -1994,6 +2123,22 @@ window.__ModuleLoader__.load({
|
|
|
1994
2123
|
'diff.copy': 'Copier',
|
|
1995
2124
|
'diff.copied': 'Copié',
|
|
1996
2125
|
'toolbar.refresh': 'Rafraîchir',
|
|
2126
|
+
'toolbar.more': 'Actions supplémentaires',
|
|
2127
|
+
'more.sortGit': 'Ordre Git',
|
|
2128
|
+
'more.sortPath': 'Par chemin',
|
|
2129
|
+
'more.sortName': 'Par nom',
|
|
2130
|
+
'more.sortStatus': 'Par statut',
|
|
2131
|
+
'more.clone': 'Cloner...',
|
|
2132
|
+
'more.stashPop': 'Pop du dernier stash',
|
|
2133
|
+
'more.deleteTag': 'Supprimer l\'étiquette...',
|
|
2134
|
+
'more.gitOutput': 'Afficher la sortie Git',
|
|
2135
|
+
'clone.title': 'Cloner le dépôt',
|
|
2136
|
+
'clone.url': 'URL du dépôt',
|
|
2137
|
+
'clone.dir': 'Répertoire cible (chemin absolu)',
|
|
2138
|
+
'clone.run': 'Cloner',
|
|
2139
|
+
'prompt.deleteTag': 'Nom de l\'étiquette à supprimer',
|
|
2140
|
+
'gitLog.title': 'Sortie Git',
|
|
2141
|
+
'gitLog.empty': 'Aucune exécution Git pour l\'instant',
|
|
1997
2142
|
'toolbar.newBranch': 'Nouvelle branche',
|
|
1998
2143
|
'toolbar.fetch': 'Récupérer',
|
|
1999
2144
|
'toolbar.pull': 'Tirer',
|
|
@@ -2219,6 +2364,22 @@ window.__ModuleLoader__.load({
|
|
|
2219
2364
|
'diff.copy': 'Копировать',
|
|
2220
2365
|
'diff.copied': 'Скопировано',
|
|
2221
2366
|
'toolbar.refresh': 'Обновить',
|
|
2367
|
+
'toolbar.more': 'Дополнительные действия',
|
|
2368
|
+
'more.sortGit': 'Порядок Git',
|
|
2369
|
+
'more.sortPath': 'По пути',
|
|
2370
|
+
'more.sortName': 'По имени',
|
|
2371
|
+
'more.sortStatus': 'По состоянию',
|
|
2372
|
+
'more.clone': 'Клонировать...',
|
|
2373
|
+
'more.stashPop': 'Pop последнего stash',
|
|
2374
|
+
'more.deleteTag': 'Удалить тег...',
|
|
2375
|
+
'more.gitOutput': 'Показать вывод Git',
|
|
2376
|
+
'clone.title': 'Клонировать репозиторий',
|
|
2377
|
+
'clone.url': 'URL репозитория',
|
|
2378
|
+
'clone.dir': 'Целевой каталог (абсолютный путь)',
|
|
2379
|
+
'clone.run': 'Клонировать',
|
|
2380
|
+
'prompt.deleteTag': 'Имя тега для удаления',
|
|
2381
|
+
'gitLog.title': 'Вывод Git',
|
|
2382
|
+
'gitLog.empty': 'Запусков Git пока нет',
|
|
2222
2383
|
'toolbar.newBranch': 'Новая ветка',
|
|
2223
2384
|
'toolbar.fetch': 'Получить',
|
|
2224
2385
|
'toolbar.pull': 'Забрать',
|
|
@@ -2444,6 +2605,22 @@ window.__ModuleLoader__.load({
|
|
|
2444
2605
|
'diff.copy': 'Copiar',
|
|
2445
2606
|
'diff.copied': 'Copiado',
|
|
2446
2607
|
'toolbar.refresh': 'Atualizar',
|
|
2608
|
+
'toolbar.more': 'Mais ações',
|
|
2609
|
+
'more.sortGit': 'Ordem do Git',
|
|
2610
|
+
'more.sortPath': 'Por caminho',
|
|
2611
|
+
'more.sortName': 'Por nome',
|
|
2612
|
+
'more.sortStatus': 'Por status',
|
|
2613
|
+
'more.clone': 'Clonar...',
|
|
2614
|
+
'more.stashPop': 'Pop do último stash',
|
|
2615
|
+
'more.deleteTag': 'Excluir tag...',
|
|
2616
|
+
'more.gitOutput': 'Mostrar saída do Git',
|
|
2617
|
+
'clone.title': 'Clonar repositório',
|
|
2618
|
+
'clone.url': 'URL do repositório',
|
|
2619
|
+
'clone.dir': 'Diretório de destino (caminho absoluto)',
|
|
2620
|
+
'clone.run': 'Clonar',
|
|
2621
|
+
'prompt.deleteTag': 'Nome da tag a excluir',
|
|
2622
|
+
'gitLog.title': 'Saída do Git',
|
|
2623
|
+
'gitLog.empty': 'Ainda não há execuções do Git',
|
|
2447
2624
|
'toolbar.newBranch': 'Nova branch',
|
|
2448
2625
|
'toolbar.fetch': 'Buscar',
|
|
2449
2626
|
'toolbar.pull': 'Puxar',
|
|
@@ -2669,6 +2846,22 @@ window.__ModuleLoader__.load({
|
|
|
2669
2846
|
'diff.copy': 'Copia',
|
|
2670
2847
|
'diff.copied': 'Copiato',
|
|
2671
2848
|
'toolbar.refresh': 'Aggiorna',
|
|
2849
|
+
'toolbar.more': 'Altre azioni',
|
|
2850
|
+
'more.sortGit': 'Ordine Git',
|
|
2851
|
+
'more.sortPath': 'Per percorso',
|
|
2852
|
+
'more.sortName': 'Per nome',
|
|
2853
|
+
'more.sortStatus': 'Per stato',
|
|
2854
|
+
'more.clone': 'Clona...',
|
|
2855
|
+
'more.stashPop': 'Pop dell\'ultimo stash',
|
|
2856
|
+
'more.deleteTag': 'Elimina tag...',
|
|
2857
|
+
'more.gitOutput': 'Mostra output Git',
|
|
2858
|
+
'clone.title': 'Clona repository',
|
|
2859
|
+
'clone.url': 'URL del repository',
|
|
2860
|
+
'clone.dir': 'Directory di destinazione (percorso assoluto)',
|
|
2861
|
+
'clone.run': 'Clona',
|
|
2862
|
+
'prompt.deleteTag': 'Nome del tag da eliminare',
|
|
2863
|
+
'gitLog.title': 'Output Git',
|
|
2864
|
+
'gitLog.empty': 'Nessuna esecuzione Git registrata',
|
|
2672
2865
|
'toolbar.newBranch': 'Nuovo branch',
|
|
2673
2866
|
'toolbar.fetch': 'Fetch',
|
|
2674
2867
|
'toolbar.pull': 'Pull',
|
|
@@ -2894,6 +3087,22 @@ window.__ModuleLoader__.load({
|
|
|
2894
3087
|
'diff.copy': 'Kopiëren',
|
|
2895
3088
|
'diff.copied': 'Gekopieerd',
|
|
2896
3089
|
'toolbar.refresh': 'Vernieuwen',
|
|
3090
|
+
'toolbar.more': 'Meer acties',
|
|
3091
|
+
'more.sortGit': 'Git-volgorde',
|
|
3092
|
+
'more.sortPath': 'Op pad',
|
|
3093
|
+
'more.sortName': 'Op naam',
|
|
3094
|
+
'more.sortStatus': 'Op status',
|
|
3095
|
+
'more.clone': 'Klonen...',
|
|
3096
|
+
'more.stashPop': 'Pop laatste stash',
|
|
3097
|
+
'more.deleteTag': 'Tag verwijderen...',
|
|
3098
|
+
'more.gitOutput': 'Git-uitvoer tonen',
|
|
3099
|
+
'clone.title': 'Repository klonen',
|
|
3100
|
+
'clone.url': 'Repository-URL',
|
|
3101
|
+
'clone.dir': 'Doelmap (absoluut pad)',
|
|
3102
|
+
'clone.run': 'Klonen',
|
|
3103
|
+
'prompt.deleteTag': 'Naam van de te verwijderen tag',
|
|
3104
|
+
'gitLog.title': 'Git-uitvoer',
|
|
3105
|
+
'gitLog.empty': 'Nog geen Git-uitvoeringen',
|
|
2897
3106
|
'toolbar.newBranch': 'Nieuwe branch',
|
|
2898
3107
|
'toolbar.fetch': 'Fetch',
|
|
2899
3108
|
'toolbar.pull': 'Pull',
|
|
@@ -3119,6 +3328,22 @@ window.__ModuleLoader__.load({
|
|
|
3119
3328
|
'diff.copy': 'Kopiuj',
|
|
3120
3329
|
'diff.copied': 'Skopiowano',
|
|
3121
3330
|
'toolbar.refresh': 'Odśwież',
|
|
3331
|
+
'toolbar.more': 'Więcej akcji',
|
|
3332
|
+
'more.sortGit': 'Kolejność Gita',
|
|
3333
|
+
'more.sortPath': 'Według ścieżki',
|
|
3334
|
+
'more.sortName': 'Według nazwy',
|
|
3335
|
+
'more.sortStatus': 'Według stanu',
|
|
3336
|
+
'more.clone': 'Klonuj...',
|
|
3337
|
+
'more.stashPop': 'Pop ostatniego stash',
|
|
3338
|
+
'more.deleteTag': 'Usuń tag...',
|
|
3339
|
+
'more.gitOutput': 'Pokaż dane wyjściowe Git',
|
|
3340
|
+
'clone.title': 'Klonuj repozytorium',
|
|
3341
|
+
'clone.url': 'URL repozytorium',
|
|
3342
|
+
'clone.dir': 'Katalog docelowy (ścieżka bezwzględna)',
|
|
3343
|
+
'clone.run': 'Klonuj',
|
|
3344
|
+
'prompt.deleteTag': 'Nazwa tagu do usunięcia',
|
|
3345
|
+
'gitLog.title': 'Dane wyjściowe Git',
|
|
3346
|
+
'gitLog.empty': 'Brak zapisanych wykonań Git',
|
|
3122
3347
|
'toolbar.newBranch': 'Nowa gałąź',
|
|
3123
3348
|
'toolbar.fetch': 'Pobierz',
|
|
3124
3349
|
'toolbar.pull': 'Ściągnij',
|
|
@@ -3344,6 +3569,22 @@ window.__ModuleLoader__.load({
|
|
|
3344
3569
|
'diff.copy': 'Kopiera',
|
|
3345
3570
|
'diff.copied': 'Kopierat',
|
|
3346
3571
|
'toolbar.refresh': 'Uppdatera',
|
|
3572
|
+
'toolbar.more': 'Fler åtgärder',
|
|
3573
|
+
'more.sortGit': 'Git-ordning',
|
|
3574
|
+
'more.sortPath': 'Efter sökväg',
|
|
3575
|
+
'more.sortName': 'Efter namn',
|
|
3576
|
+
'more.sortStatus': 'Efter status',
|
|
3577
|
+
'more.clone': 'Klona...',
|
|
3578
|
+
'more.stashPop': 'Pop:a senaste stash',
|
|
3579
|
+
'more.deleteTag': 'Ta bort tagg...',
|
|
3580
|
+
'more.gitOutput': 'Visa Git-utdata',
|
|
3581
|
+
'clone.title': 'Klona lagringsplats',
|
|
3582
|
+
'clone.url': 'URL till lagringsplats',
|
|
3583
|
+
'clone.dir': 'Målkatalog (absolut sökväg)',
|
|
3584
|
+
'clone.run': 'Klona',
|
|
3585
|
+
'prompt.deleteTag': 'Tagnamn som ska tas bort',
|
|
3586
|
+
'gitLog.title': 'Git-utdata',
|
|
3587
|
+
'gitLog.empty': 'Inga Git-körningar ännu',
|
|
3347
3588
|
'toolbar.newBranch': 'Ny gren',
|
|
3348
3589
|
'toolbar.fetch': 'Fetch',
|
|
3349
3590
|
'toolbar.pull': 'Pull',
|
|
@@ -3569,6 +3810,22 @@ window.__ModuleLoader__.load({
|
|
|
3569
3810
|
'diff.copy': 'Kopyala',
|
|
3570
3811
|
'diff.copied': 'Kopyalandı',
|
|
3571
3812
|
'toolbar.refresh': 'Yenile',
|
|
3813
|
+
'toolbar.more': 'Diğer eylemler',
|
|
3814
|
+
'more.sortGit': 'Git sırası',
|
|
3815
|
+
'more.sortPath': 'Yola göre',
|
|
3816
|
+
'more.sortName': 'Ada göre',
|
|
3817
|
+
'more.sortStatus': 'Duruma göre',
|
|
3818
|
+
'more.clone': 'Klonla...',
|
|
3819
|
+
'more.stashPop': 'Son stash\'i aç',
|
|
3820
|
+
'more.deleteTag': 'Etiket sil...',
|
|
3821
|
+
'more.gitOutput': 'Git çıktısını göster',
|
|
3822
|
+
'clone.title': 'Depoyu klonla',
|
|
3823
|
+
'clone.url': 'Depo URL\'si',
|
|
3824
|
+
'clone.dir': 'Hedef dizin (mutlak yol)',
|
|
3825
|
+
'clone.run': 'Klonla',
|
|
3826
|
+
'prompt.deleteTag': 'Silinecek etiket adı',
|
|
3827
|
+
'gitLog.title': 'Git çıktısı',
|
|
3828
|
+
'gitLog.empty': 'Henüz Git çalıştırması yok',
|
|
3572
3829
|
'toolbar.newBranch': 'Yeni dal',
|
|
3573
3830
|
'toolbar.fetch': 'Fetch',
|
|
3574
3831
|
'toolbar.pull': 'Pull',
|
|
@@ -3794,6 +4051,22 @@ window.__ModuleLoader__.load({
|
|
|
3794
4051
|
'diff.copy': 'Salin',
|
|
3795
4052
|
'diff.copied': 'Tersalin',
|
|
3796
4053
|
'toolbar.refresh': 'Segarkan',
|
|
4054
|
+
'toolbar.more': 'Tindakan lainnya',
|
|
4055
|
+
'more.sortGit': 'Urutan Git',
|
|
4056
|
+
'more.sortPath': 'Menurut jalur',
|
|
4057
|
+
'more.sortName': 'Menurut nama',
|
|
4058
|
+
'more.sortStatus': 'Menurut status',
|
|
4059
|
+
'more.clone': 'Kloning...',
|
|
4060
|
+
'more.stashPop': 'Pop stash terbaru',
|
|
4061
|
+
'more.deleteTag': 'Hapus tag...',
|
|
4062
|
+
'more.gitOutput': 'Tampilkan keluaran Git',
|
|
4063
|
+
'clone.title': 'Kloning repositori',
|
|
4064
|
+
'clone.url': 'URL repositori',
|
|
4065
|
+
'clone.dir': 'Direktori target (jalur absolut)',
|
|
4066
|
+
'clone.run': 'Kloning',
|
|
4067
|
+
'prompt.deleteTag': 'Nama tag yang akan dihapus',
|
|
4068
|
+
'gitLog.title': 'Keluaran Git',
|
|
4069
|
+
'gitLog.empty': 'Belum ada eksekusi Git',
|
|
3797
4070
|
'toolbar.newBranch': 'Branch baru',
|
|
3798
4071
|
'toolbar.fetch': 'Fetch',
|
|
3799
4072
|
'toolbar.pull': 'Pull',
|
|
@@ -4019,6 +4292,22 @@ window.__ModuleLoader__.load({
|
|
|
4019
4292
|
'diff.copy': 'Sao chép',
|
|
4020
4293
|
'diff.copied': 'Đã sao chép',
|
|
4021
4294
|
'toolbar.refresh': 'Làm mới',
|
|
4295
|
+
'toolbar.more': 'Thao tác khác',
|
|
4296
|
+
'more.sortGit': 'Thứ tự Git',
|
|
4297
|
+
'more.sortPath': 'Theo đường dẫn',
|
|
4298
|
+
'more.sortName': 'Theo tên',
|
|
4299
|
+
'more.sortStatus': 'Theo trạng thái',
|
|
4300
|
+
'more.clone': 'Clone...',
|
|
4301
|
+
'more.stashPop': 'Pop stash gần nhất',
|
|
4302
|
+
'more.deleteTag': 'Xóa thẻ...',
|
|
4303
|
+
'more.gitOutput': 'Hiện đầu ra Git',
|
|
4304
|
+
'clone.title': 'Clone kho',
|
|
4305
|
+
'clone.url': 'URL kho',
|
|
4306
|
+
'clone.dir': 'Thư mục đích (đường dẫn tuyệt đối)',
|
|
4307
|
+
'clone.run': 'Clone',
|
|
4308
|
+
'prompt.deleteTag': 'Tên thẻ cần xóa',
|
|
4309
|
+
'gitLog.title': 'Đầu ra Git',
|
|
4310
|
+
'gitLog.empty': 'Chưa có lần chạy Git nào',
|
|
4022
4311
|
'toolbar.newBranch': 'Nhánh mới',
|
|
4023
4312
|
'toolbar.fetch': 'Fetch',
|
|
4024
4313
|
'toolbar.pull': 'Pull',
|
|
@@ -4244,6 +4533,22 @@ window.__ModuleLoader__.load({
|
|
|
4244
4533
|
'diff.copy': 'نسخ',
|
|
4245
4534
|
'diff.copied': 'تم النسخ',
|
|
4246
4535
|
'toolbar.refresh': 'تحديث',
|
|
4536
|
+
'toolbar.more': 'مزيد من الإجراءات',
|
|
4537
|
+
'more.sortGit': 'ترتيب Git',
|
|
4538
|
+
'more.sortPath': 'حسب المسار',
|
|
4539
|
+
'more.sortName': 'حسب الاسم',
|
|
4540
|
+
'more.sortStatus': 'حسب الحالة',
|
|
4541
|
+
'more.clone': 'استنساخ...',
|
|
4542
|
+
'more.stashPop': 'تفريغ آخر stash',
|
|
4543
|
+
'more.deleteTag': 'حذف الوسم...',
|
|
4544
|
+
'more.gitOutput': 'إظهار مخرجات Git',
|
|
4545
|
+
'clone.title': 'استنساخ المستودع',
|
|
4546
|
+
'clone.url': 'رابط المستودع',
|
|
4547
|
+
'clone.dir': 'المجلد الهدف (مسار مطلق)',
|
|
4548
|
+
'clone.run': 'استنساخ',
|
|
4549
|
+
'prompt.deleteTag': 'اسم الوسم المطلوب حذفه',
|
|
4550
|
+
'gitLog.title': 'مخرجات Git',
|
|
4551
|
+
'gitLog.empty': 'لا توجد عمليات Git بعد',
|
|
4247
4552
|
'toolbar.newBranch': 'فرع جديد',
|
|
4248
4553
|
'toolbar.fetch': 'جلب',
|
|
4249
4554
|
'toolbar.pull': 'سحب',
|
|
@@ -4469,6 +4774,22 @@ window.__ModuleLoader__.load({
|
|
|
4469
4774
|
'diff.copy': 'कॉपी करें',
|
|
4470
4775
|
'diff.copied': 'कॉपी हो गया',
|
|
4471
4776
|
'toolbar.refresh': 'ताज़ा करें',
|
|
4777
|
+
'toolbar.more': 'अधिक क्रियाएँ',
|
|
4778
|
+
'more.sortGit': 'Git क्रम',
|
|
4779
|
+
'more.sortPath': 'पथ से',
|
|
4780
|
+
'more.sortName': 'नाम से',
|
|
4781
|
+
'more.sortStatus': 'स्थिति से',
|
|
4782
|
+
'more.clone': 'क्लोन करें...',
|
|
4783
|
+
'more.stashPop': 'नवीनतम स्टैश पॉप करें',
|
|
4784
|
+
'more.deleteTag': 'टैग हटाएँ...',
|
|
4785
|
+
'more.gitOutput': 'Git आउटपुट दिखाएँ',
|
|
4786
|
+
'clone.title': 'रिपॉज़िटरी क्लोन करें',
|
|
4787
|
+
'clone.url': 'रिपॉज़िटरी URL',
|
|
4788
|
+
'clone.dir': 'लक्ष्य डायरेक्टरी (पूर्ण पथ)',
|
|
4789
|
+
'clone.run': 'क्लोन करें',
|
|
4790
|
+
'prompt.deleteTag': 'हटाने के लिए टैग नाम',
|
|
4791
|
+
'gitLog.title': 'Git आउटपुट',
|
|
4792
|
+
'gitLog.empty': 'अभी तक कोई Git रन नहीं',
|
|
4472
4793
|
'toolbar.newBranch': 'नई शाखा',
|
|
4473
4794
|
'toolbar.fetch': 'फ़ेच',
|
|
4474
4795
|
'toolbar.pull': 'पुल',
|
|
@@ -4694,6 +5015,22 @@ window.__ModuleLoader__.load({
|
|
|
4694
5015
|
'diff.copy': 'คัดลอก',
|
|
4695
5016
|
'diff.copied': 'คัดลอกแล้ว',
|
|
4696
5017
|
'toolbar.refresh': 'รีเฟรช',
|
|
5018
|
+
'toolbar.more': 'การกระทำเพิ่มเติม',
|
|
5019
|
+
'more.sortGit': 'ลำดับ Git',
|
|
5020
|
+
'more.sortPath': 'ตามเส้นทาง',
|
|
5021
|
+
'more.sortName': 'ตามชื่อ',
|
|
5022
|
+
'more.sortStatus': 'ตามสถานะ',
|
|
5023
|
+
'more.clone': 'โคลน...',
|
|
5024
|
+
'more.stashPop': 'ป๊อป stash ล่าสุด',
|
|
5025
|
+
'more.deleteTag': 'ลบแท็ก...',
|
|
5026
|
+
'more.gitOutput': 'แสดงเอาต์พุต Git',
|
|
5027
|
+
'clone.title': 'โคลนรีโพซิทอรี',
|
|
5028
|
+
'clone.url': 'URL ของรีโพซิทอรี',
|
|
5029
|
+
'clone.dir': 'ไดเรกทอรีเป้าหมาย (พาธสัมบูรณ์)',
|
|
5030
|
+
'clone.run': 'โคลน',
|
|
5031
|
+
'prompt.deleteTag': 'ชื่อแท็กที่จะลบ',
|
|
5032
|
+
'gitLog.title': 'เอาต์พุต Git',
|
|
5033
|
+
'gitLog.empty': 'ยังไม่มีการรัน Git',
|
|
4697
5034
|
'toolbar.newBranch': 'แบรนช์ใหม่',
|
|
4698
5035
|
'toolbar.fetch': 'Fetch',
|
|
4699
5036
|
'toolbar.pull': 'Pull',
|
|
@@ -5247,6 +5584,10 @@ window.__ModuleLoader__.load({
|
|
|
5247
5584
|
{ id: 'fetch', icon: 'fetch', key: 'toolbar.fetch', tone: 'cyan' },
|
|
5248
5585
|
{ id: 'pull', icon: 'pull', key: 'toolbar.pull', tone: 'accent' },
|
|
5249
5586
|
{ id: 'push', icon: 'push', key: 'toolbar.push', tone: 'success' },
|
|
5587
|
+
/* VS Code-style "..." menu: view mode, sort, and every repository action
|
|
5588
|
+
that has no rail button of its own. A rail action (not a chrome
|
|
5589
|
+
button) so the user can hide/move it like any other, per invariant 14. */
|
|
5590
|
+
{ id: 'more', icon: 'more', key: 'toolbar.more', tone: 'secondary' },
|
|
5250
5591
|
]
|
|
5251
5592
|
const RAIL_IDS = RAIL_SPECS.map((spec) => spec.id)
|
|
5252
5593
|
/* Actions that arrange the view rather than touch the repository, so a running
|
|
@@ -5933,7 +6274,10 @@ window.__ModuleLoader__.load({
|
|
|
5933
6274
|
return segments
|
|
5934
6275
|
}
|
|
5935
6276
|
|
|
5936
|
-
|
|
6277
|
+
/* Memoised: rows come from a useMemo, so a row object is referentially
|
|
6278
|
+
stable across re-renders unless the commit list actually changed — the
|
|
6279
|
+
26px graph cells then skip re-rendering on every filter keystroke. */
|
|
6280
|
+
const GraphCell = memo(function GraphCell(props) {
|
|
5937
6281
|
const row = props.row
|
|
5938
6282
|
const height = props.height
|
|
5939
6283
|
const width = Math.min(Math.max(1, row.width), GRAPH_MAX_LANES) * LANE_WIDTH + 8
|
|
@@ -5946,6 +6290,7 @@ window.__ModuleLoader__.load({
|
|
|
5946
6290
|
for (const segment of laneSegments(row, height)) {
|
|
5947
6291
|
children.push(E('line', {
|
|
5948
6292
|
key: 'lane-' + segment.lane,
|
|
6293
|
+
'data-lane': String(segment.lane),
|
|
5949
6294
|
x1: x(segment.lane), y1: segment.y1, x2: x(segment.lane), y2: segment.y2,
|
|
5950
6295
|
stroke: color(segment.lane), strokeWidth: 2, strokeLinecap: 'round',
|
|
5951
6296
|
}))
|
|
@@ -5954,6 +6299,7 @@ window.__ModuleLoader__.load({
|
|
|
5954
6299
|
if (parentLane === row.lane) continue
|
|
5955
6300
|
children.push(E('path', {
|
|
5956
6301
|
key: 'edge-' + parentLane,
|
|
6302
|
+
'data-lane': String(parentLane),
|
|
5957
6303
|
d: 'M ' + x(row.lane) + ' ' + middle + ' C ' + x(row.lane) + ' ' + height + ', ' + x(parentLane) + ' ' + middle + ', ' + x(parentLane) + ' ' + height,
|
|
5958
6304
|
fill: 'none',
|
|
5959
6305
|
stroke: color(parentLane),
|
|
@@ -5963,10 +6309,10 @@ window.__ModuleLoader__.load({
|
|
|
5963
6309
|
}
|
|
5964
6310
|
const isHead = Array.isArray(row.commit.refs) && row.commit.refs.some((ref) => String(ref).indexOf('HEAD') >= 0)
|
|
5965
6311
|
children.push(isHead
|
|
5966
|
-
? E('circle', { key: 'dot', cx: x(row.lane), cy: middle, r: 3.8, fill: 'none', stroke: color(row.lane), strokeWidth: 2 })
|
|
5967
|
-
: E('circle', { key: 'dot', cx: x(row.lane), cy: middle, r: 3.6, fill: color(row.lane) }))
|
|
6312
|
+
? E('circle', { key: 'dot', 'data-lane': String(row.lane), cx: x(row.lane), cy: middle, r: 3.8, fill: 'none', stroke: color(row.lane), strokeWidth: 2 })
|
|
6313
|
+
: E('circle', { key: 'dot', 'data-lane': String(row.lane), cx: x(row.lane), cy: middle, r: 3.6, fill: color(row.lane) }))
|
|
5968
6314
|
return E('svg', { className: 'dig-graph', width: width, height: height, viewBox: '0 0 ' + width + ' ' + height }, children)
|
|
5969
|
-
}
|
|
6315
|
+
}, (prev, next) => prev.row === next.row && prev.height === next.height)
|
|
5970
6316
|
|
|
5971
6317
|
/* ============================== composer send ============================== */
|
|
5972
6318
|
/* "Send to chat": drop a reference chip / short text into the CURRENT
|
|
@@ -7596,6 +7942,68 @@ window.__ModuleLoader__.load({
|
|
|
7596
7942
|
E('button', { type: 'button', className: 'dig-btn dig-btn-primary', onClick: submit }, props.okLabel))))
|
|
7597
7943
|
}
|
|
7598
7944
|
|
|
7945
|
+
/* VS Code's "Clone...": a URL plus an absolute target path. Two inputs in
|
|
7946
|
+
one dialog — chaining two single prompts would forget the URL by the
|
|
7947
|
+
time the directory is typed. */
|
|
7948
|
+
function CloneDialog(props) {
|
|
7949
|
+
const [url, setUrl] = useState('')
|
|
7950
|
+
const [dir, setDir] = useState('')
|
|
7951
|
+
const ref = useRef(null)
|
|
7952
|
+
useEffect(() => { if (ref.current !== null) ref.current.focus() }, [])
|
|
7953
|
+
const ready = url.trim() !== '' && dir.trim() !== ''
|
|
7954
|
+
const submit = () => { if (ready === true) props.onSubmit(url.trim(), dir.trim()) }
|
|
7955
|
+
const onKey = (event) => {
|
|
7956
|
+
if (event.key === 'Enter') { event.preventDefault(); submit() }
|
|
7957
|
+
if (event.key === 'Escape') { event.preventDefault(); props.onCancel() }
|
|
7958
|
+
}
|
|
7959
|
+
return E('div', { className: 'dig-overlay' },
|
|
7960
|
+
E('div', { className: 'dig-dialog' },
|
|
7961
|
+
E('div', { className: 'dig-dialog-title' }, props.title),
|
|
7962
|
+
E('input', {
|
|
7963
|
+
ref: ref, className: 'dig-input', value: url, spellCheck: false,
|
|
7964
|
+
placeholder: props.urlPlaceholder === undefined ? '' : props.urlPlaceholder,
|
|
7965
|
+
onChange: (event) => setUrl(event.target.value), onKeyDown: onKey,
|
|
7966
|
+
}),
|
|
7967
|
+
E('input', {
|
|
7968
|
+
className: 'dig-input', value: dir, spellCheck: false,
|
|
7969
|
+
placeholder: props.dirPlaceholder === undefined ? '' : props.dirPlaceholder,
|
|
7970
|
+
onChange: (event) => setDir(event.target.value), onKeyDown: onKey,
|
|
7971
|
+
}),
|
|
7972
|
+
E('div', { className: 'dig-dialog-actions' },
|
|
7973
|
+
E('button', { type: 'button', className: 'dig-btn', onClick: props.onCancel }, props.cancelLabel),
|
|
7974
|
+
E('button', { type: 'button', className: 'dig-btn dig-btn-primary', disabled: ready === false, onClick: submit }, props.okLabel))))
|
|
7975
|
+
}
|
|
7976
|
+
|
|
7977
|
+
/* VS Code's "Git output" view (showOutput): the tail of the host's git-run
|
|
7978
|
+
ring buffer, fetched through the gitLogs method. A debugging view —
|
|
7979
|
+
opened on demand, refreshed by hand; newest land at the bottom and the
|
|
7980
|
+
body auto-scrolls to them. Panel-internal absolute positioning (13). */
|
|
7981
|
+
function GitOutputDrawer(props) {
|
|
7982
|
+
const t = props.t
|
|
7983
|
+
const ref = useRef(null)
|
|
7984
|
+
useEffect(() => { if (ref.current !== null) ref.current.scrollTop = ref.current.scrollHeight }, [props.entries])
|
|
7985
|
+
return E('div', { className: 'dig-gitlog' },
|
|
7986
|
+
E('div', { className: 'dig-gitlog-head' },
|
|
7987
|
+
E('span', { className: 'dig-gitlog-title' }, t('gitLog.title')),
|
|
7988
|
+
E('span', { className: 'dig-topbar-spacer' }),
|
|
7989
|
+
E('button', { type: 'button', className: 'dig-icon-btn dig-icon-btn-small', title: t('toolbar.refresh'), onClick: props.onRefresh }, E(Icon, { name: 'refresh', size: 13 })),
|
|
7990
|
+
E('button', { type: 'button', className: 'dig-icon-btn dig-icon-btn-small', title: t('confirm.cancel'), onClick: props.onClose }, E(Icon, { name: 'collapse', size: 13 }))),
|
|
7991
|
+
props.entries.length === 0
|
|
7992
|
+
? E('div', { className: 'dig-gitlog-empty' }, t('gitLog.empty'))
|
|
7993
|
+
: E('div', { className: 'dig-gitlog-body', ref: ref },
|
|
7994
|
+
props.entries.map((entry, index) => E('div', {
|
|
7995
|
+
key: index,
|
|
7996
|
+
className: 'dig-gitlog-row' + (String(entry.code) === '0' ? '' : ' dig-gitlog-row-bad'),
|
|
7997
|
+
},
|
|
7998
|
+
E('div', { className: 'dig-gitlog-meta' },
|
|
7999
|
+
E('span', { className: 'dig-gitlog-code' }, String(entry.code)),
|
|
8000
|
+
E('span', { className: 'dig-gitlog-ms' }, String(entry.ms) + 'ms'),
|
|
8001
|
+
E('span', { className: 'dig-gitlog-at' }, String(entry.at)),
|
|
8002
|
+
E('span', { className: 'dig-gitlog-argv' }, String(entry.argv))),
|
|
8003
|
+
entry.out === '' && entry.err === '' ? null
|
|
8004
|
+
: E('pre', { className: 'dig-gitlog-pre' }, String(entry.err) === '' ? String(entry.out) : String(entry.out) + '\n' + String(entry.err))))))
|
|
8005
|
+
}
|
|
8006
|
+
|
|
7599
8007
|
/* A destructive confirm: focus starts on Cancel (never on the red button), Enter
|
|
7600
8008
|
only submits when the dialog asked for a typed confirmation, and passing
|
|
7601
8009
|
requireText turns the dialog into a type-the-name gate. */
|
|
@@ -7726,7 +8134,7 @@ window.__ModuleLoader__.load({
|
|
|
7726
8134
|
system colours over a dark translucent panel) and was unreadable. */
|
|
7727
8135
|
let current = null
|
|
7728
8136
|
for (const repo of props.repos) { if (repo.path === props.value) { current = repo; break } }
|
|
7729
|
-
return E('span', { className: 'dig-select-wrap', title: t('repo.switch') },
|
|
8137
|
+
return E('span', { className: 'dig-select-wrap', title: current === null ? t('repo.switch') : current.path },
|
|
7730
8138
|
E(Icon, { name: 'folder', size: 12 }),
|
|
7731
8139
|
E('button', {
|
|
7732
8140
|
type: 'button',
|
|
@@ -7913,7 +8321,15 @@ window.__ModuleLoader__.load({
|
|
|
7913
8321
|
|
|
7914
8322
|
/* ============================== changes ============================== */
|
|
7915
8323
|
|
|
7916
|
-
|
|
8324
|
+
/* Memoised with a hand-rolled comparison (issue #11): the callbacks are
|
|
8325
|
+
inline arrows and change identity on every parent render, so default
|
|
8326
|
+
shallow memo would never hit. Rows re-render only when their `item`
|
|
8327
|
+
reference changes — which is exactly what the two-phase stats merge
|
|
8328
|
+
produces (untouched rows keep their object). Stale-callback safety:
|
|
8329
|
+
every callback closes over `run`/`base`, and those only change together
|
|
8330
|
+
with a repo switch, which lands as a brand-new summary array — i.e.
|
|
8331
|
+
new `item` references, so the rows do re-render with fresh closures. */
|
|
8332
|
+
const ChangeRow = memo(function ChangeRow(props) {
|
|
7917
8333
|
const t = props.t
|
|
7918
8334
|
const item = props.item
|
|
7919
8335
|
const raw = item.index === '?' ? '?' : (item.index + item.worktree).trim()
|
|
@@ -7939,7 +8355,7 @@ window.__ModuleLoader__.load({
|
|
|
7939
8355
|
? E('button', { type: 'button', className: 'dig-mini', title: t('action.unstage'), onClick: (event) => { event.stopPropagation(); props.onUnstage(item) } }, E(Icon, { name: 'minus', size: 12 }))
|
|
7940
8356
|
: E('button', { type: 'button', className: 'dig-mini', title: t('action.stage'), onClick: (event) => { event.stopPropagation(); props.onStage(item) } }, E(Icon, { name: 'plus', size: 12 }))),
|
|
7941
8357
|
readOnly ? null : E('button', { type: 'button', className: 'dig-mini', title: t('action.discard'), onClick: (event) => { event.stopPropagation(); props.onDiscard(item, props.group) } }, E(Icon, { name: 'undo', size: 12 })))
|
|
7942
|
-
}
|
|
8358
|
+
}, (prev, next) => prev.item === next.item && prev.group === next.group && prev.t === next.t)
|
|
7943
8359
|
|
|
7944
8360
|
function ChangesPanel(props) {
|
|
7945
8361
|
const t = props.t
|
|
@@ -7965,7 +8381,12 @@ window.__ModuleLoader__.load({
|
|
|
7965
8381
|
// clusters each group's files under their folder, the way "Group by:
|
|
7966
8382
|
// Directory" does in an IDE. Folders are addressed by <group>|<dir>, so a
|
|
7967
8383
|
// refresh never loses which ones the user folded away.
|
|
7968
|
-
|
|
8384
|
+
// Lifted to the panel (v0.15): the "..." menu drives the same state, so
|
|
8385
|
+
// the header toggle and the menu can never disagree. The alias pair keeps
|
|
8386
|
+
// the body reads below untouched.
|
|
8387
|
+
const groupBy = props.groupBy === 'dir' ? 'dir' : 'flat'
|
|
8388
|
+
const setGroupBy = props.onGroupBy
|
|
8389
|
+
const sortKey = props.sortKey === undefined ? 'git' : props.sortKey
|
|
7969
8390
|
const [foldedDirs, setFoldedDirs] = useState({})
|
|
7970
8391
|
const changes = summary === null ? null : summary.changes
|
|
7971
8392
|
const conflicted = changes === null ? [] : changes.conflicted
|
|
@@ -7980,13 +8401,19 @@ window.__ModuleLoader__.load({
|
|
|
7980
8401
|
setAiNote(null)
|
|
7981
8402
|
try {
|
|
7982
8403
|
const settings = props.commitSettings === undefined ? { model: '', reasoning: '', prompt: '' } : props.commitSettings
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
8404
|
+
/* `base` carries the picked repository ({ cwd, repoRoot }) exactly like
|
|
8405
|
+
every other request: without it the host resolves the repository from
|
|
8406
|
+
the session cwd, which need not be one (several sibling repos). */
|
|
8407
|
+
const data = await props.request('commit-message', Object.assign(
|
|
8408
|
+
{},
|
|
8409
|
+
props.base === undefined ? { cwd: props.cwd } : props.base,
|
|
8410
|
+
{
|
|
8411
|
+
sessionId: props.sessionId,
|
|
8412
|
+
model: settings.model,
|
|
8413
|
+
reasoningEffort: settings.reasoning,
|
|
8414
|
+
prompt: settings.prompt,
|
|
8415
|
+
},
|
|
8416
|
+
))
|
|
7990
8417
|
setAiUndo(message === '' ? null : message)
|
|
7991
8418
|
setMessage(typeof data.message === 'string' ? data.message : '')
|
|
7992
8419
|
/* Say what the model did NOT see, instead of quietly sending less. */
|
|
@@ -8015,29 +8442,79 @@ window.__ModuleLoader__.load({
|
|
|
8015
8442
|
const dir = dirName(item.path)
|
|
8016
8443
|
return dir === '' ? t('changes.rootDir') : dir
|
|
8017
8444
|
}
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
return Array.from(folders.keys()).sort().map((name) => {
|
|
8027
|
-
const id = key + '|' + name
|
|
8028
|
-
const open = foldedDirs[id] !== true
|
|
8029
|
-
const list = folders.get(name)
|
|
8030
|
-
return E('div', { className: 'dig-folder', key: id },
|
|
8031
|
-
E('button', {
|
|
8032
|
-
type: 'button', className: 'dig-folder-head',
|
|
8033
|
-
onClick: () => setFoldedDirs((previous) => Object.assign({}, previous, { [id]: previous[id] !== true })),
|
|
8034
|
-
},
|
|
8035
|
-
E('span', { className: 'dig-chevron' + (open ? ' dig-chevron-open' : '') }, E(Icon, { name: 'chevron', size: 10 })),
|
|
8036
|
-
E('span', { className: 'dig-folder-name' }, name),
|
|
8037
|
-
E('span', { className: 'dig-count' }, String(list.length))),
|
|
8038
|
-
open ? list.map((item) => entryRow(key, item)) : null)
|
|
8039
|
-
})
|
|
8445
|
+
/* The "..." menu's sort orders (v0.15). 'git' keeps git's own status
|
|
8446
|
+
order; the others are pure client-side sorts of already-loaded
|
|
8447
|
+
entries — no extra git calls, no re-fetch. */
|
|
8448
|
+
const sortedOf = (entries) => {
|
|
8449
|
+
if (sortKey === 'path') return entries.slice().sort((a, b) => a.path.localeCompare(b.path))
|
|
8450
|
+
if (sortKey === 'name') return entries.slice().sort((a, b) => baseName(a.path).localeCompare(baseName(b.path)) || a.path.localeCompare(b.path))
|
|
8451
|
+
if (sortKey === 'status') return entries.slice().sort((a, b) => (a.index + a.worktree).localeCompare(b.index + b.worktree) || a.path.localeCompare(b.path))
|
|
8452
|
+
return entries
|
|
8040
8453
|
}
|
|
8454
|
+
/* Windowing: the change list is flattened into (group-head | folder-head
|
|
8455
|
+
| row) items with FIXED heights (see ITEM_HEIGHTS + the CSS pins), a
|
|
8456
|
+
prefix-sum index locates the visible window, and only that slice is
|
|
8457
|
+
mounted. A tree with thousands of files scrolls at frame rate. */
|
|
8458
|
+
const changesScrollRef = useRef(null)
|
|
8459
|
+
const changesViewport = useViewportBox(changesScrollRef)
|
|
8460
|
+
const changeItems = useMemo(() => {
|
|
8461
|
+
const out = []
|
|
8462
|
+
const groupHead = (key, title, entries) => E('div', { className: 'dig-group-head' },
|
|
8463
|
+
E('span', null, title),
|
|
8464
|
+
key === 'staged' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onUnstageAll() }, t('changes.unstageAll')) : null,
|
|
8465
|
+
key === 'unstaged' || key === 'untracked' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onStageAll(entries) }, t('changes.stageAll')) : null)
|
|
8466
|
+
const emitRows = (key, entries) => {
|
|
8467
|
+
const sorted = sortedOf(entries)
|
|
8468
|
+
if (groupBy !== 'dir') {
|
|
8469
|
+
for (const item of sorted) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item) })
|
|
8470
|
+
return
|
|
8471
|
+
}
|
|
8472
|
+
const folders = new Map()
|
|
8473
|
+
for (const item of sorted) {
|
|
8474
|
+
const name = folderOf(item)
|
|
8475
|
+
if (folders.has(name) === false) folders.set(name, [])
|
|
8476
|
+
folders.get(name).push(item)
|
|
8477
|
+
}
|
|
8478
|
+
for (const name of Array.from(folders.keys()).sort()) {
|
|
8479
|
+
const id = key + '|' + name
|
|
8480
|
+
const open = foldedDirs[id] !== true
|
|
8481
|
+
const list = folders.get(name)
|
|
8482
|
+
out.push({
|
|
8483
|
+
kind: 'folder', key: 'folder:' + id, h: ITEM_HEIGHTS.folder,
|
|
8484
|
+
node: E('button', {
|
|
8485
|
+
type: 'button', className: 'dig-folder-head',
|
|
8486
|
+
onClick: () => setFoldedDirs((previous) => Object.assign({}, previous, { [id]: previous[id] !== true })),
|
|
8487
|
+
},
|
|
8488
|
+
E('span', { className: 'dig-chevron' + (open ? ' dig-chevron-open' : '') }, E(Icon, { name: 'chevron', size: 10 })),
|
|
8489
|
+
E('span', { className: 'dig-folder-name' }, name),
|
|
8490
|
+
E('span', { className: 'dig-count' }, String(list.length))),
|
|
8491
|
+
})
|
|
8492
|
+
if (open === true) for (const item of list) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item) })
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
const emitGroup = (key, title, entries) => {
|
|
8496
|
+
if (entries.length === 0) return
|
|
8497
|
+
out.push({ kind: 'group', key: 'group:' + key, h: ITEM_HEIGHTS.group, node: groupHead(key, title, entries) })
|
|
8498
|
+
emitRows(key, entries)
|
|
8499
|
+
}
|
|
8500
|
+
emitGroup('conflicted', fill(t('counts.conflicted'), { n: conflicted.length }), conflicted)
|
|
8501
|
+
emitGroup('staged', fill(t('counts.staged'), { n: staged.length }), staged)
|
|
8502
|
+
emitGroup('unstaged', fill(t('counts.unstaged'), { n: unstaged.length }), unstaged)
|
|
8503
|
+
emitGroup('untracked', fill(t('counts.untracked'), { n: untracked.length }), untracked)
|
|
8504
|
+
emitGroup('ignored', fill(t('counts.ignored'), { n: ignored.length }), ignored)
|
|
8505
|
+
return out
|
|
8506
|
+
}, [conflicted, staged, unstaged, untracked, ignored, groupBy, foldedDirs, sortKey, t, props.onUnstageAll, props.onStageAll])
|
|
8507
|
+
const changeOffsets = useMemo(() => {
|
|
8508
|
+
const sums = [0]
|
|
8509
|
+
for (const item of changeItems) sums.push(sums[sums.length - 1] + item.h)
|
|
8510
|
+
return sums
|
|
8511
|
+
}, [changeItems])
|
|
8512
|
+
const winFirst = Math.max(0, windowStart(changeOffsets, changesViewport.scrollTop) - WINDOW_OVERSCAN)
|
|
8513
|
+
const winLimit = Math.ceil(changesViewport.viewH / ITEM_HEIGHTS.row) + WINDOW_OVERSCAN * 3
|
|
8514
|
+
let winLast = winFirst
|
|
8515
|
+
let used = 0
|
|
8516
|
+
while (winLast < changeItems.length && used < winLimit) { used += changeItems[winLast].h; winLast += 1 }
|
|
8517
|
+
const visibleItems = changeItems.slice(winFirst, winLast)
|
|
8041
8518
|
const foldAll = (value) => {
|
|
8042
8519
|
const next = {}
|
|
8043
8520
|
if (value === true) {
|
|
@@ -8046,12 +8523,6 @@ window.__ModuleLoader__.load({
|
|
|
8046
8523
|
}
|
|
8047
8524
|
setFoldedDirs(next)
|
|
8048
8525
|
}
|
|
8049
|
-
const group = (key, title, entries) => entries.length === 0 ? null : E('div', { className: 'dig-group', key: key },
|
|
8050
|
-
E('div', { className: 'dig-group-head' },
|
|
8051
|
-
E('span', null, title),
|
|
8052
|
-
key === 'staged' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onUnstageAll() }, t('changes.unstageAll')) : null,
|
|
8053
|
-
key === 'unstaged' || key === 'untracked' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onStageAll(entries) }, t('changes.stageAll')) : null),
|
|
8054
|
-
rowsOf(key, entries))
|
|
8055
8526
|
const aiConfirmDialog = aiAsk !== true ? null : E(ConfirmDialog, {
|
|
8056
8527
|
t: t,
|
|
8057
8528
|
title: t('changes.aiOverwriteTitle'),
|
|
@@ -8156,14 +8627,14 @@ window.__ModuleLoader__.load({
|
|
|
8156
8627
|
aiConfirmDialog,
|
|
8157
8628
|
head,
|
|
8158
8629
|
E('div', { className: 'dig-changes-body' },
|
|
8159
|
-
E('div', { className: 'dig-changes-list' },
|
|
8630
|
+
E('div', { className: 'dig-changes-list', ref: changesScrollRef },
|
|
8160
8631
|
collapsed ? null : [
|
|
8161
8632
|
total === 0 && ignored.length === 0 ? E('div', { className: 'dig-empty', key: 'empty' }, t('changes.empty')) : null,
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8633
|
+
/* Windowed slice: spacers above/below stand in for the unmounted
|
|
8634
|
+
rows (fixed heights, see ITEM_HEIGHTS). */
|
|
8635
|
+
winFirst > 0 ? E('div', { key: 'pad-top', style: { height: String(changeOffsets[winFirst]) + 'px' } }) : null,
|
|
8636
|
+
visibleItems.map((item) => E(Fragment, { key: item.key }, item.node)),
|
|
8637
|
+
winLast < changeItems.length ? E('div', { key: 'pad-bottom', style: { height: String(changeOffsets[changeItems.length] - changeOffsets[winLast]) + 'px' } }) : null,
|
|
8167
8638
|
]),
|
|
8168
8639
|
composer))
|
|
8169
8640
|
}
|
|
@@ -8187,6 +8658,144 @@ window.__ModuleLoader__.load({
|
|
|
8187
8658
|
return { name: value, kind: value.indexOf('/') >= 0 ? 'remote' : 'local' }
|
|
8188
8659
|
}
|
|
8189
8660
|
|
|
8661
|
+
/* Display form of a decoration: a HEAD decoration names the branch it points
|
|
8662
|
+
at, so the badge says "HEAD → main" — where HEAD sits must be readable at
|
|
8663
|
+
a glance (GitLens-style), not only inferable from the hollow dot. */
|
|
8664
|
+
function refLabel(decoration) {
|
|
8665
|
+
if (decoration.kind !== 'head') return decoration.name
|
|
8666
|
+
return decoration.name === 'HEAD' ? 'HEAD' : 'HEAD → ' + decoration.name
|
|
8667
|
+
}
|
|
8668
|
+
|
|
8669
|
+
/* ---- list windowing (issue #11 follow-up: hundreds of rows stay smooth) ----
|
|
8670
|
+
Both long lists (history rows, change rows) render only what the viewport
|
|
8671
|
+
can see plus an overscan skirt; spacer divs keep the scrollbar honest.
|
|
8672
|
+
Row heights are FIXED constants — the CSS pins each row kind to an exact
|
|
8673
|
+
height, so a prefix-sum index plus binary search locates the first
|
|
8674
|
+
visible item without measuring. */
|
|
8675
|
+
const HISTORY_ROW_H = 28 /* .dig-commit: height 26 + 2×1px padding */
|
|
8676
|
+
const ITEM_HEIGHTS = { group: 21, folder: 16, row: 24 }
|
|
8677
|
+
const WINDOW_OVERSCAN = 6
|
|
8678
|
+
|
|
8679
|
+
/* Tracks a scroll container's scrollTop (rAF-throttled) and clientHeight
|
|
8680
|
+
(ResizeObserver). SSR-safe: the effect never runs on the server. */
|
|
8681
|
+
function useViewportBox(scrollRef) {
|
|
8682
|
+
const [scrollTop, setScrollTop] = useState(0)
|
|
8683
|
+
const [viewH, setViewH] = useState(360)
|
|
8684
|
+
useEffect(() => {
|
|
8685
|
+
const el = scrollRef.current
|
|
8686
|
+
if (el === null || typeof ResizeObserver !== 'function') return undefined
|
|
8687
|
+
const observer = new ResizeObserver(() => setViewH(el.clientHeight))
|
|
8688
|
+
observer.observe(el)
|
|
8689
|
+
setViewH(el.clientHeight)
|
|
8690
|
+
let frame = 0
|
|
8691
|
+
const onScroll = () => {
|
|
8692
|
+
if (frame !== 0) return
|
|
8693
|
+
frame = requestAnimationFrame(() => { frame = 0; setScrollTop(el.scrollTop) })
|
|
8694
|
+
}
|
|
8695
|
+
el.addEventListener('scroll', onScroll, { passive: true })
|
|
8696
|
+
return () => {
|
|
8697
|
+
observer.disconnect()
|
|
8698
|
+
el.removeEventListener('scroll', onScroll)
|
|
8699
|
+
if (frame !== 0) cancelAnimationFrame(frame)
|
|
8700
|
+
}
|
|
8701
|
+
}, [scrollRef])
|
|
8702
|
+
return { scrollTop: scrollTop, viewH: viewH }
|
|
8703
|
+
}
|
|
8704
|
+
|
|
8705
|
+
/* First index whose cumulative offset exceeds `at` (binary search over the
|
|
8706
|
+
prefix-sum array; offsets.length === items.length + 1). */
|
|
8707
|
+
function windowStart(offsets, at) {
|
|
8708
|
+
let low = 0
|
|
8709
|
+
let high = offsets.length - 1
|
|
8710
|
+
while (low < high) {
|
|
8711
|
+
const mid = (low + high) >> 1
|
|
8712
|
+
if (offsets[mid] <= at) low = mid + 1
|
|
8713
|
+
else high = mid
|
|
8714
|
+
}
|
|
8715
|
+
return Math.max(0, low - 1)
|
|
8716
|
+
}
|
|
8717
|
+
|
|
8718
|
+
/* One history row, memoised on its data (commit + graph row + selection):
|
|
8719
|
+
filter keystrokes re-render the list shell without touching unchanged
|
|
8720
|
+
rows, and the +N overflow badge opens the hidden refs in a menu instead
|
|
8721
|
+
of hiding them behind a bare count. */
|
|
8722
|
+
const REF_ICONS = { head: 'star', local: 'branch', remote: 'fetch', tag: 'tag' }
|
|
8723
|
+
const REF_TONES = { head: 'success', local: 'accent', remote: 'violet', tag: 'warn' }
|
|
8724
|
+
const CommitRow = memo(function CommitRow(props) {
|
|
8725
|
+
const commit = props.commit
|
|
8726
|
+
const decorations = (Array.isArray(commit.refs) ? commit.refs : []).map(refInfo)
|
|
8727
|
+
/* Remote folding (GitLens-style dedupe): a remote whose short name
|
|
8728
|
+
(origin/main → main) matches a LOCAL or HEAD badge on the same commit
|
|
8729
|
+
stops taking a badge of its own — the local badge's title gains a
|
|
8730
|
+
"⇅ origin/main" line and the overflow menu still lists it. Nothing is
|
|
8731
|
+
lost, the row just stops saying "main" and "origin/main" twice. */
|
|
8732
|
+
const localNames = new Set(decorations.filter((d) => d.kind === 'local' || d.kind === 'head').map((d) => d.name))
|
|
8733
|
+
const foldedBy = new Map()
|
|
8734
|
+
const kept = []
|
|
8735
|
+
for (const decoration of decorations) {
|
|
8736
|
+
if (decoration.kind === 'remote') {
|
|
8737
|
+
const slash = decoration.name.indexOf('/')
|
|
8738
|
+
const short = slash >= 0 ? decoration.name.slice(slash + 1) : decoration.name
|
|
8739
|
+
if (localNames.has(short) === true) {
|
|
8740
|
+
if (foldedBy.has(short) === false) foldedBy.set(short, [])
|
|
8741
|
+
foldedBy.get(short).push(decoration.name)
|
|
8742
|
+
continue
|
|
8743
|
+
}
|
|
8744
|
+
}
|
|
8745
|
+
kept.push(decoration)
|
|
8746
|
+
}
|
|
8747
|
+
const folded = []
|
|
8748
|
+
for (const list of foldedBy.values()) folded.push(...list)
|
|
8749
|
+
const shown = kept.slice(0, 3)
|
|
8750
|
+
const hidden = kept.length - shown.length
|
|
8751
|
+
const badgeTitle = (decoration) => {
|
|
8752
|
+
const label = refLabel(decoration)
|
|
8753
|
+
if (decoration.kind !== 'local' && decoration.kind !== 'head') return label
|
|
8754
|
+
const folds = foldedBy.get(decoration.name)
|
|
8755
|
+
return folds === undefined ? label : label + '\n⇅ ' + folds.join(', ')
|
|
8756
|
+
}
|
|
8757
|
+
return E('div', {
|
|
8758
|
+
/* data-lane feeds the hover highlight: the scroll container copies the
|
|
8759
|
+
hovered row's lane into data-hover-lane, and generated CSS dims every
|
|
8760
|
+
graph stroke whose data-lane differs — zero React re-renders. */
|
|
8761
|
+
'data-lane': String(props.row.lane),
|
|
8762
|
+
className: 'dig-commit' + (props.selected === true ? ' dig-commit-selected' : ''),
|
|
8763
|
+
onClick: () => props.onSelect(commit),
|
|
8764
|
+
onContextMenu: (event) => { event.preventDefault(); props.onMenu(event, commit) },
|
|
8765
|
+
},
|
|
8766
|
+
E('span', { className: 'dig-commit-date', title: shortDate(commit.date) }, relativeTime(commit.date)),
|
|
8767
|
+
E('span', { className: 'dig-commit-author', title: commit.author }, commit.author),
|
|
8768
|
+
E(GraphCell, { row: props.row, height: 26 }),
|
|
8769
|
+
shown.map((decoration, index) => E('span', {
|
|
8770
|
+
key: decoration.kind + ':' + decoration.name + ':' + index,
|
|
8771
|
+
className: 'dig-ref dig-ref-' + decoration.kind,
|
|
8772
|
+
title: badgeTitle(decoration),
|
|
8773
|
+
}, refLabel(decoration))),
|
|
8774
|
+
hidden > 0 || folded.length > 0 ? E('span', {
|
|
8775
|
+
className: 'dig-ref dig-ref-more',
|
|
8776
|
+
title: kept.slice(3).map(refLabel).concat(folded.map((name) => '⇅ ' + name)).join('\n'),
|
|
8777
|
+
onClick: (event) => {
|
|
8778
|
+
event.stopPropagation()
|
|
8779
|
+
if (props.openMenuAt === undefined) return
|
|
8780
|
+
const entries = kept.slice(3).map((decoration, at) => ({
|
|
8781
|
+
id: 'ref:' + at,
|
|
8782
|
+
icon: REF_ICONS[decoration.kind] === undefined ? 'branch' : REF_ICONS[decoration.kind],
|
|
8783
|
+
tone: REF_TONES[decoration.kind] === undefined ? 'secondary' : REF_TONES[decoration.kind],
|
|
8784
|
+
label: refLabel(decoration),
|
|
8785
|
+
run: () => { copyText(refLabel(decoration)) },
|
|
8786
|
+
})).concat(folded.map((name, at) => ({
|
|
8787
|
+
id: 'fold:' + at,
|
|
8788
|
+
icon: 'fetch', tone: 'violet',
|
|
8789
|
+
label: '⇅ ' + name,
|
|
8790
|
+
run: () => { copyText(name) },
|
|
8791
|
+
})))
|
|
8792
|
+
props.openMenuAt(event, entries)
|
|
8793
|
+
},
|
|
8794
|
+
}, '+' + String(hidden + folded.length)) : null,
|
|
8795
|
+
E('span', { className: 'dig-commit-subject', title: commit.subject }, commit.subject),
|
|
8796
|
+
E('span', { className: 'dig-commit-fill' }))
|
|
8797
|
+
}, (prev, next) => prev.commit === next.commit && prev.row === next.row && prev.selected === next.selected && prev.t === next.t)
|
|
8798
|
+
|
|
8190
8799
|
function sinceThreshold(key) {
|
|
8191
8800
|
if (key === 'today') { const start = new Date(); start.setHours(0, 0, 0, 0); return start.getTime() }
|
|
8192
8801
|
if (key === 'week') return Date.now() - 7 * DAY_MS
|
|
@@ -8198,6 +8807,8 @@ window.__ModuleLoader__.load({
|
|
|
8198
8807
|
function HistoryList(props) {
|
|
8199
8808
|
const t = props.t
|
|
8200
8809
|
const commits = props.commits
|
|
8810
|
+
const historyScrollRef = useRef(null)
|
|
8811
|
+
const viewport = useViewportBox(historyScrollRef)
|
|
8201
8812
|
const [text, setText] = useState('')
|
|
8202
8813
|
const [author, setAuthor] = useState('')
|
|
8203
8814
|
const [refName, setRefName] = useState('')
|
|
@@ -8320,32 +8931,40 @@ window.__ModuleLoader__.load({
|
|
|
8320
8931
|
onClick: clearAll,
|
|
8321
8932
|
}, E(Icon, { name: 'close', size: 12 })) : null)
|
|
8322
8933
|
|
|
8934
|
+
/* Windowed rendering: only the visible slice (± overscan) is mounted —
|
|
8935
|
+
"load more" into the hundreds still scrolls at frame rate. Spacers
|
|
8936
|
+
keep the scrollbar and the load-more button at honest positions. */
|
|
8937
|
+
const winStart = Math.max(0, Math.floor(viewport.scrollTop / HISTORY_ROW_H) - WINDOW_OVERSCAN)
|
|
8938
|
+
const winEnd = Math.min(rows.length, winStart + Math.ceil(viewport.viewH / HISTORY_ROW_H) + WINDOW_OVERSCAN * 2)
|
|
8939
|
+
const visibleRows = rows.slice(winStart, winEnd)
|
|
8940
|
+
|
|
8323
8941
|
return E('div', { className: 'dig-history' }, filterBar,
|
|
8324
|
-
E('div', {
|
|
8942
|
+
E('div', {
|
|
8943
|
+
className: 'dig-history-scroll',
|
|
8944
|
+
ref: historyScrollRef,
|
|
8945
|
+
/* Lane hover highlight (GitLens-style trace): copy the hovered row's
|
|
8946
|
+
lane into data-hover-lane; generated CSS dims every stroke whose
|
|
8947
|
+
data-lane differs. Direct DOM dataset writes on purpose — the
|
|
8948
|
+
alternative is re-rendering every row on each hover change. */
|
|
8949
|
+
onMouseOver: (event) => {
|
|
8950
|
+
const host = event.currentTarget
|
|
8951
|
+
const row = event.target instanceof Element ? event.target.closest('.dig-commit') : null
|
|
8952
|
+
const lane = row === null ? null : row.getAttribute('data-lane')
|
|
8953
|
+
if (lane === host.getAttribute('data-hover-lane')) return
|
|
8954
|
+
if (lane === null) host.removeAttribute('data-hover-lane')
|
|
8955
|
+
else host.setAttribute('data-hover-lane', lane)
|
|
8956
|
+
},
|
|
8957
|
+
onMouseLeave: (event) => event.currentTarget.removeAttribute('data-hover-lane'),
|
|
8958
|
+
},
|
|
8325
8959
|
rows.length === 0 ? E('div', { className: 'dig-empty' }, dirtyFilter ? t('filter.none') : t('history.empty')) : null,
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
onClick: () => props.onSelect(commit),
|
|
8335
|
-
onContextMenu: (event) => { event.preventDefault(); props.onMenu(event, commit) },
|
|
8336
|
-
},
|
|
8337
|
-
E('span', { className: 'dig-commit-date', title: shortDate(commit.date) }, relativeTime(commit.date)),
|
|
8338
|
-
E('span', { className: 'dig-commit-author', title: commit.author }, commit.author),
|
|
8339
|
-
E(GraphCell, { row: row, height: 26 }),
|
|
8340
|
-
shown.map((decoration, index) => E('span', {
|
|
8341
|
-
key: decoration.kind + ':' + decoration.name + ':' + index,
|
|
8342
|
-
className: 'dig-ref dig-ref-' + decoration.kind,
|
|
8343
|
-
title: decoration.name,
|
|
8344
|
-
}, decoration.name)),
|
|
8345
|
-
hidden > 0 ? E('span', { className: 'dig-ref dig-ref-more' }, '+' + hidden) : null,
|
|
8346
|
-
E('span', { className: 'dig-commit-subject', title: commit.subject }, commit.subject),
|
|
8347
|
-
E('span', { className: 'dig-commit-fill' }))
|
|
8348
|
-
}),
|
|
8960
|
+
winStart > 0 ? E('div', { key: 'pad-top', style: { height: String(winStart * HISTORY_ROW_H) + 'px' } }) : null,
|
|
8961
|
+
visibleRows.map((row) => E(CommitRow, {
|
|
8962
|
+
key: row.commit.hash,
|
|
8963
|
+
commit: row.commit, row: row, t: t,
|
|
8964
|
+
selected: props.selectedHash === row.commit.hash,
|
|
8965
|
+
onSelect: props.onSelect, onMenu: props.onMenu, openMenuAt: props.openMenuAt,
|
|
8966
|
+
})),
|
|
8967
|
+
winEnd < rows.length ? E('div', { key: 'pad-bottom', style: { height: String((rows.length - winEnd) * HISTORY_ROW_H) + 'px' } }) : null,
|
|
8349
8968
|
props.hasMore === true
|
|
8350
8969
|
? E('button', { type: 'button', className: 'dig-load-more', disabled: props.busy === true, onClick: props.onLoadMore }, t('history.loadMore'))
|
|
8351
8970
|
: null))
|
|
@@ -8728,9 +9347,78 @@ window.__ModuleLoader__.load({
|
|
|
8728
9347
|
return () => { cancelled = true }
|
|
8729
9348
|
}, [cwd, sessionId, tick])
|
|
8730
9349
|
|
|
9350
|
+
/* Changes view state, lifted out of ChangesPanel so the "..." menu (a
|
|
9351
|
+
rail action, invariant 14) can drive the same groupBy and the sort
|
|
9352
|
+
order it introduces — one source of truth, wherever the click came
|
|
9353
|
+
from. sortKey 'git' keeps git's own status order. */
|
|
9354
|
+
const [groupBy, setGroupBy] = useState('flat')
|
|
9355
|
+
const [sortKey, setSortKey] = useState('git')
|
|
9356
|
+
/* The Git-output drawer (VS Code showOutput): null = closed, an array =
|
|
9357
|
+
the entries it currently shows. Opened on demand, refreshed by hand or
|
|
9358
|
+
by reopening — it is a debugging view, not a live stream. */
|
|
9359
|
+
const [gitOutput, setGitOutput] = useState(null)
|
|
9360
|
+
const openGitOutput = useCallback(async () => {
|
|
9361
|
+
try {
|
|
9362
|
+
const data = await request('gitLogs', {})
|
|
9363
|
+
setGitOutput(Array.isArray(data.entries) ? data.entries : [])
|
|
9364
|
+
} catch (caught) {
|
|
9365
|
+
setError(caught instanceof Error ? caught.message : String(caught))
|
|
9366
|
+
}
|
|
9367
|
+
}, [request])
|
|
9368
|
+
|
|
9369
|
+
|
|
9370
|
+
/* Phase-two merge (issue #11): fold the [path, additions, deletions,
|
|
9371
|
+
binary] rows from the `stats` method into a deferred summary. Pure and
|
|
9372
|
+
immutable — untouched rows keep their object reference so the memoised
|
|
9373
|
+
ChangeRow skips them, and a group with no numeric change keeps its
|
|
9374
|
+
array reference so React reconciles nothing. Declared before use
|
|
9375
|
+
(hooks dependency TDZ rule). */
|
|
9376
|
+
const applyStats = (data, statsPayload) => {
|
|
9377
|
+
if (data === null || typeof data !== 'object' || statsPayload === null || typeof statsPayload !== 'object') return data
|
|
9378
|
+
const build = (entries, rows) => {
|
|
9379
|
+
if (Array.isArray(entries) !== true || entries.length === 0 || Array.isArray(rows) !== true) return entries
|
|
9380
|
+
const map = new Map()
|
|
9381
|
+
for (const row of rows) {
|
|
9382
|
+
if (Array.isArray(row) === true && row.length >= 4 && typeof row[0] === 'string') {
|
|
9383
|
+
map.set(row[0], { additions: row[1] | 0, deletions: row[2] | 0, binary: row[3] === true })
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
let touched = false
|
|
9387
|
+
const next = entries.map((entry) => {
|
|
9388
|
+
const stat = map.get(entry.path)
|
|
9389
|
+
if (stat === undefined) {
|
|
9390
|
+
if (entry.additions === 0 && entry.deletions === 0 && entry.binary !== true) return entry
|
|
9391
|
+
touched = true
|
|
9392
|
+
return Object.assign({}, entry, { additions: 0, deletions: 0, binary: false })
|
|
9393
|
+
}
|
|
9394
|
+
if (entry.additions === stat.additions && entry.deletions === stat.deletions && entry.binary === stat.binary) return entry
|
|
9395
|
+
touched = true
|
|
9396
|
+
return Object.assign({}, entry, stat)
|
|
9397
|
+
})
|
|
9398
|
+
return touched ? next : entries
|
|
9399
|
+
}
|
|
9400
|
+
const changes = data.changes
|
|
9401
|
+
const staged = build(changes.staged, statsPayload.staged)
|
|
9402
|
+
const unstaged = build(changes.unstaged, statsPayload.unstaged)
|
|
9403
|
+
if (staged === changes.staged && unstaged === changes.unstaged) return data
|
|
9404
|
+
return Object.assign({}, data, {
|
|
9405
|
+
statsDeferred: false,
|
|
9406
|
+
changes: Object.assign({}, changes, { staged: staged, unstaged: unstaged }),
|
|
9407
|
+
})
|
|
9408
|
+
}
|
|
9409
|
+
|
|
8731
9410
|
const loadSummary = useCallback(async () => {
|
|
8732
|
-
|
|
9411
|
+
/* Two-phase (issue #11): the rows land first (`stats: false` skips the
|
|
9412
|
+
two numstat passes), then the +/- numbers merge in behind them. The
|
|
9413
|
+
panel is usable the moment the rows arrive; the numbers are exact
|
|
9414
|
+
when they arrive — nothing is approximated or dropped. */
|
|
9415
|
+
const data = await request('summary', Object.assign({}, base, { ignored: showIgnored, stats: false }))
|
|
8733
9416
|
setSummary(data)
|
|
9417
|
+
if (data.statsDeferred === true) {
|
|
9418
|
+
const merged = applyStats(data, await request('stats', base))
|
|
9419
|
+
setSummary(merged)
|
|
9420
|
+
return merged
|
|
9421
|
+
}
|
|
8734
9422
|
return data
|
|
8735
9423
|
}, [base, showIgnored])
|
|
8736
9424
|
|
|
@@ -8784,9 +9472,18 @@ window.__ModuleLoader__.load({
|
|
|
8784
9472
|
|
|
8785
9473
|
useEffect(() => {
|
|
8786
9474
|
if (props.visible !== true || repoRoot === null) return undefined
|
|
9475
|
+
/* In-flight gate (issue #11): a summary over a tree with thousands of
|
|
9476
|
+
changes can outlast AUTO_REFRESH_MS, and re-entering just piles
|
|
9477
|
+
queued scans behind the repo lock (the interval deliberately does
|
|
9478
|
+
not run through `guard` — a background refresh must not flash the
|
|
9479
|
+
busy UI). Skip while the previous round is still running; the next
|
|
9480
|
+
tick re-checks. */
|
|
9481
|
+
let inFlight = false
|
|
8787
9482
|
const timer = setInterval(() => {
|
|
9483
|
+
if (inFlight === true) return
|
|
9484
|
+
inFlight = true
|
|
8788
9485
|
void (async () => {
|
|
8789
|
-
try { await loadSummary() } catch (error) { void error }
|
|
9486
|
+
try { await loadSummary() } catch (error) { void error } finally { inFlight = false }
|
|
8790
9487
|
})()
|
|
8791
9488
|
}, AUTO_REFRESH_MS)
|
|
8792
9489
|
return () => clearInterval(timer)
|
|
@@ -8885,7 +9582,7 @@ window.__ModuleLoader__.load({
|
|
|
8885
9582
|
const scopeObject = scope || {}
|
|
8886
9583
|
previewStore.set(commitHash !== null
|
|
8887
9584
|
? { hash: commitHash, subject: seed.subject || '', path: seed.path !== undefined && seed.path !== null ? seed.path : '', cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId }
|
|
8888
|
-
: { path: seed.path, staged: seed.staged === true, cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId })
|
|
9585
|
+
: { path: seed.path, staged: seed.staged === true, untracked: seed.untracked === true ? true : undefined, cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId })
|
|
8889
9586
|
sidebarRight.openTab(NATIVE_PREVIEW_KIND)
|
|
8890
9587
|
return true
|
|
8891
9588
|
} catch (error) { void error }
|
|
@@ -9216,6 +9913,7 @@ window.__ModuleLoader__.load({
|
|
|
9216
9913
|
if (state.kind === 'newBranch') { await run('checkout', { branch: value, create: true, startPoint: state.from }); return }
|
|
9217
9914
|
if (state.kind === 'renameBranch') { await run('branchRename', { from: state.from, to: value }); return }
|
|
9218
9915
|
if (state.kind === 'newTag') { await run('tagCreate', { name: value, hash: state.hash }); return }
|
|
9916
|
+
if (state.kind === 'deleteTag') { await run('tagDelete', { name: value }); return }
|
|
9219
9917
|
}, [run])
|
|
9220
9918
|
|
|
9221
9919
|
/* ---------- toasts + undo ---------- */
|
|
@@ -9337,7 +10035,7 @@ window.__ModuleLoader__.load({
|
|
|
9337
10035
|
loading: repoState === null,
|
|
9338
10036
|
onOpenMenu: openRepoMenu,
|
|
9339
10037
|
}),
|
|
9340
|
-
branches === null || branches.local.length === 0 ? null : E('span', { className: 'dig-select-wrap', title:
|
|
10038
|
+
branches === null || branches.local.length === 0 ? null : E('span', { className: 'dig-select-wrap', title: branches.branch },
|
|
9341
10039
|
E(Icon, { name: 'branch', size: 12 }),
|
|
9342
10040
|
E('button', {
|
|
9343
10041
|
type: 'button',
|
|
@@ -9368,7 +10066,8 @@ window.__ModuleLoader__.load({
|
|
|
9368
10066
|
const commitSettings = readCommitSettings()
|
|
9369
10067
|
const changesPane = E(ChangesPanel, {
|
|
9370
10068
|
t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
|
|
9371
|
-
|
|
10069
|
+
groupBy: groupBy, sortKey: sortKey, onGroupBy: setGroupBy,
|
|
10070
|
+
request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings, base: base,
|
|
9372
10071
|
showIgnored: showIgnored,
|
|
9373
10072
|
onToggleIgnored: () => setShowIgnored((value) => !value),
|
|
9374
10073
|
// Commit-and-push carries the confirm flag with it: the button already
|
|
@@ -9447,7 +10146,7 @@ window.__ModuleLoader__.load({
|
|
|
9447
10146
|
['untracked', changes.untracked, false],
|
|
9448
10147
|
]
|
|
9449
10148
|
for (const entry of order) {
|
|
9450
|
-
if (entry[1].length > 0) { showDiff({ path: entry[1][0].path, staged: entry[2] }); return }
|
|
10149
|
+
if (entry[1].length > 0) { showDiff({ path: entry[1][0].path, staged: entry[2], untracked: entry[0] === 'untracked' }); return }
|
|
9451
10150
|
}
|
|
9452
10151
|
setNote(t('note.noChanges'))
|
|
9453
10152
|
}
|
|
@@ -9470,6 +10169,48 @@ window.__ModuleLoader__.load({
|
|
|
9470
10169
|
const stashCount = summary === null ? 0 : summary.stashCount
|
|
9471
10170
|
const reason = t('action.unavailable')
|
|
9472
10171
|
|
|
10172
|
+
/* VS Code-style "..." menu — the SCM title menu: view mode, sort, sync,
|
|
10173
|
+
changes, branch/stash/tag shortcuts, and the Git-output view at the
|
|
10174
|
+
bottom. Every entry reuses the very handlers the rail rows and group
|
|
10175
|
+
heads already run, so the menu can never disagree with them. */
|
|
10176
|
+
const moreMenu = (event) => {
|
|
10177
|
+
const tagCount = branches === null || Array.isArray(branches.tags) !== true ? 0 : branches.tags.length
|
|
10178
|
+
const stageable = summary === null ? [] : summary.changes.unstaged.concat(summary.changes.untracked)
|
|
10179
|
+
const stagedPaths = summary === null ? [] : summary.changes.staged.map((entry) => entry.path)
|
|
10180
|
+
const sort = (key) => () => setSortKey(key)
|
|
10181
|
+
openMenuAt(event, [
|
|
10182
|
+
{ id: 'view-flat', icon: 'commit', tone: 'secondary', label: t('changes.groupFlat'), active: groupBy === 'flat', run: () => setGroupBy('flat') },
|
|
10183
|
+
{ id: 'view-dir', icon: 'folder', tone: 'secondary', label: t('changes.groupDir'), active: groupBy === 'dir', run: () => setGroupBy('dir') },
|
|
10184
|
+
null,
|
|
10185
|
+
{ id: 'sort-git', icon: 'commit', tone: 'secondary', label: t('more.sortGit'), active: sortKey === 'git', run: sort('git') },
|
|
10186
|
+
{ id: 'sort-path', icon: 'commit', tone: 'secondary', label: t('more.sortPath'), active: sortKey === 'path', run: sort('path') },
|
|
10187
|
+
{ id: 'sort-name', icon: 'commit', tone: 'secondary', label: t('more.sortName'), active: sortKey === 'name', run: sort('name') },
|
|
10188
|
+
{ id: 'sort-status', icon: 'commit', tone: 'secondary', label: t('more.sortStatus'), active: sortKey === 'status', run: sort('status') },
|
|
10189
|
+
null,
|
|
10190
|
+
{ id: 'mm-pull', icon: 'pull', tone: 'accent', label: t('toolbar.pull'), disabled: remoteReady !== true || tracked !== true || blocked, reason, run: () => { void run('pull', { mode: 'ff-only' }) } },
|
|
10191
|
+
{ id: 'mm-push', icon: 'push', tone: 'success', label: t('toolbar.push'), disabled: remoteReady !== true || blocked, reason, run: () => setDialog({ kind: 'push' }) },
|
|
10192
|
+
{ id: 'mm-fetch', icon: 'fetch', tone: 'cyan', label: t('toolbar.fetch'), disabled: remoteReady !== true, reason, run: () => { void run('fetch', { prune: true }) } },
|
|
10193
|
+
{ id: 'mm-checkout', icon: 'checkout', tone: 'accent', label: t('action.checkout') + '...', disabled: otherBranches.length === 0 || blocked, reason, run: (ev) => pickBranchMenu(ev, 'checkout') },
|
|
10194
|
+
{ id: 'mm-clone', icon: 'plus', tone: 'secondary', label: t('more.clone'), run: () => setDialog({ kind: 'clone' }) },
|
|
10195
|
+
null,
|
|
10196
|
+
{ id: 'mm-stage-all', icon: 'plus', tone: 'success', label: t('changes.stageAll'), disabled: stageable.length === 0, reason, run: () => { void run('stage', { paths: stageable.map((entry) => entry.path) }) } },
|
|
10197
|
+
{ id: 'mm-unstage-all', icon: 'minus', tone: 'warn', label: t('changes.unstageAll'), disabled: stagedPaths.length === 0, reason, run: () => { void run('unstage', { paths: stagedPaths }) } },
|
|
10198
|
+
null,
|
|
10199
|
+
{ id: 'mm-new-branch', icon: 'plus', tone: 'success', label: t('toolbar.newBranch'), disabled: blocked, reason, run: () => setDialog({ kind: 'newBranch' }) },
|
|
10200
|
+
{ id: 'mm-delete-branch', icon: 'trash', tone: 'danger', danger: true, label: t('action.delete'), disabled: otherBranches.length === 0, reason, run: (ev) => pickBranchMenu(ev, 'delete') },
|
|
10201
|
+
null,
|
|
10202
|
+
{ id: 'mm-stash-push', icon: 'stash', tone: 'violet', label: t('stash.push'), disabled: dirty === 0, reason, run: () => { void run('stashPush', { includeUntracked: true }) } },
|
|
10203
|
+
{ id: 'mm-stash-apply', icon: 'checkout', tone: 'accent', label: t('stash.apply'), disabled: stashCount === 0, reason, run: () => { void run('stashApply', {}) } },
|
|
10204
|
+
{ id: 'mm-stash-pop', icon: 'stash', tone: 'accent', label: t('more.stashPop'), disabled: stashCount === 0, reason, run: () => { void run('stashApply', { pop: true }) } },
|
|
10205
|
+
{ id: 'mm-stash-drop', icon: 'trash', tone: 'danger', danger: true, label: t('stash.drop'), disabled: stashCount === 0, reason, run: () => setDialog({ kind: 'stashDrop', ref: 'stash@{0}' }) },
|
|
10206
|
+
null,
|
|
10207
|
+
{ id: 'mm-new-tag', icon: 'tag', tone: 'warn', label: t('action.newTagHere'), disabled: commits.length === 0, reason, run: () => setDialog({ kind: 'newTag' }) },
|
|
10208
|
+
{ id: 'mm-delete-tag', icon: 'trash', tone: 'danger', danger: true, label: t('more.deleteTag'), disabled: tagCount === 0, reason, run: () => setDialog({ kind: 'deleteTag' }) },
|
|
10209
|
+
null,
|
|
10210
|
+
{ id: 'mm-git-output', icon: 'commit', tone: 'secondary', label: t('more.gitOutput'), run: () => { void openGitOutput() } },
|
|
10211
|
+
])
|
|
10212
|
+
}
|
|
10213
|
+
|
|
9473
10214
|
/* One descriptor per action, with the availability IDEA would use: delete /
|
|
9474
10215
|
checkout / compare need another local branch, fetch and push need a remote,
|
|
9475
10216
|
pull needs an upstream, diff needs pending changes, tag needs a commit.
|
|
@@ -9488,6 +10229,7 @@ window.__ModuleLoader__.load({
|
|
|
9488
10229
|
else if (spec.id === 'fetch') { entry.disabled = remoteReady !== true; entry.run = () => { void run('fetch', { prune: true }) } }
|
|
9489
10230
|
else if (spec.id === 'pull') { entry.disabled = remoteReady !== true || tracked !== true || blocked; entry.run = () => { void run('pull', { mode: 'ff-only' }) } }
|
|
9490
10231
|
else if (spec.id === 'push') { entry.disabled = remoteReady !== true || blocked; entry.run = () => setDialog({ kind: 'push' }) }
|
|
10232
|
+
else if (spec.id === 'more') { entry.run = (event) => moreMenu(event) }
|
|
9491
10233
|
else if (spec.id === 'tree') { entry.active = treeOpen; entry.run = () => foldTree(!treeOpen) }
|
|
9492
10234
|
else if (spec.id === 'float') {
|
|
9493
10235
|
// Label AND icon follow where the panel actually is: floating panels get
|
|
@@ -9719,8 +10461,8 @@ window.__ModuleLoader__.load({
|
|
|
9719
10461
|
onClose: () => setRailSettings(false),
|
|
9720
10462
|
}))
|
|
9721
10463
|
}
|
|
9722
|
-
if (dialog !== null && (dialog.kind === 'newBranch' || dialog.kind === 'renameBranch' || dialog.kind === 'newTag')) {
|
|
9723
|
-
const titles = { newBranch: t('prompt.newBranch'), renameBranch: t('prompt.renameBranch'), newTag: t('prompt.newTag') }
|
|
10464
|
+
if (dialog !== null && (dialog.kind === 'newBranch' || dialog.kind === 'renameBranch' || dialog.kind === 'newTag' || dialog.kind === 'deleteTag')) {
|
|
10465
|
+
const titles = { newBranch: t('prompt.newBranch'), renameBranch: t('prompt.renameBranch'), newTag: t('prompt.newTag'), deleteTag: t('prompt.deleteTag') }
|
|
9724
10466
|
overlays.push(E(PromptDialog, {
|
|
9725
10467
|
key: 'prompt',
|
|
9726
10468
|
title: titles[dialog.kind],
|
|
@@ -9731,6 +10473,17 @@ window.__ModuleLoader__.load({
|
|
|
9731
10473
|
onSubmit: (value) => { void submitDialog(dialog, value) },
|
|
9732
10474
|
}))
|
|
9733
10475
|
}
|
|
10476
|
+
if (dialog !== null && dialog.kind === 'clone') {
|
|
10477
|
+
overlays.push(E(CloneDialog, {
|
|
10478
|
+
key: 'clone',
|
|
10479
|
+
title: t('clone.title'),
|
|
10480
|
+
urlPlaceholder: t('clone.url'),
|
|
10481
|
+
dirPlaceholder: t('clone.dir'),
|
|
10482
|
+
okLabel: t('clone.run'), cancelLabel: t('confirm.cancel'),
|
|
10483
|
+
onCancel: () => setDialog(null),
|
|
10484
|
+
onSubmit: (url, dir) => { setDialog(null); void run('clone', { url: url, dir: dir }) },
|
|
10485
|
+
}))
|
|
10486
|
+
}
|
|
9734
10487
|
if (dialog !== null && dialog.kind === 'deleteBranch') {
|
|
9735
10488
|
const guarded = isProtectedBranch(dialog.name)
|
|
9736
10489
|
overlays.push(E(ConfirmDialog, {
|
|
@@ -9824,6 +10577,11 @@ window.__ModuleLoader__.load({
|
|
|
9824
10577
|
E('div', { className: 'dig-shell' },
|
|
9825
10578
|
columns === true && repoRoot !== null ? renderRail(true) : null,
|
|
9826
10579
|
body),
|
|
10580
|
+
gitOutput === null ? null : E(GitOutputDrawer, {
|
|
10581
|
+
t: t, entries: gitOutput,
|
|
10582
|
+
onRefresh: () => { void openGitOutput() },
|
|
10583
|
+
onClose: () => setGitOutput(null),
|
|
10584
|
+
}),
|
|
9827
10585
|
overlays,
|
|
9828
10586
|
E(ToastStack, { t: t, toasts: toasts }))
|
|
9829
10587
|
}
|
|
@@ -9838,23 +10596,46 @@ window.__ModuleLoader__.load({
|
|
|
9838
10596
|
/* Tokens only (--dsw-alias-*): transparent themes and background plugins
|
|
9839
10597
|
(dsh-any-background) keep showing through, like the host's own panels. */
|
|
9840
10598
|
|
|
10599
|
+
/* Lane-hover trace, generated: one rule per lane (lanes are capped at
|
|
10600
|
+
GRAPH_MAX_LANES, so this is a fixed handful). When the scroll container
|
|
10601
|
+
carries data-hover-lane="N", every graph stroke whose data-lane differs
|
|
10602
|
+
fades — the hovered lane reads as one continuous thread down the graph
|
|
10603
|
+
with zero React involvement. */
|
|
10604
|
+
const LANE_TRACE_RULES = []
|
|
10605
|
+
for (let lane = 0; lane < GRAPH_MAX_LANES; lane += 1) {
|
|
10606
|
+
LANE_TRACE_RULES.push(
|
|
10607
|
+
'.dig-history-scroll[data-hover-lane="' + lane + '"] .dig-graph line:not([data-lane="' + lane + '"])'
|
|
10608
|
+
+ ',.dig-history-scroll[data-hover-lane="' + lane + '"] .dig-graph path:not([data-lane="' + lane + '"])'
|
|
10609
|
+
+ ',.dig-history-scroll[data-hover-lane="' + lane + '"] .dig-graph circle:not([data-lane="' + lane + '"])'
|
|
10610
|
+
+ '{stroke-opacity:.18;fill-opacity:.18}',
|
|
10611
|
+
)
|
|
10612
|
+
}
|
|
10613
|
+
LANE_TRACE_RULES.push('.dig-graph line,.dig-graph path,.dig-graph circle{transition:stroke-opacity .15s,fill-opacity .15s}')
|
|
10614
|
+
|
|
9841
10615
|
const CSS = [
|
|
9842
10616
|
'.dig-root{position:relative;display:flex;flex-direction:column;height:100%;min-height:0;background:transparent;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family,system-ui,sans-serif);font-size:13px;line-height:1.5;font-weight:600;overflow:hidden}',
|
|
9843
10617
|
'.dig-topbar{display:flex;align-items:center;gap:6px;padding:4px 6px;flex:none;min-width:0;border-bottom:1px solid var(--dsw-alias-border-l2);overflow:hidden}',
|
|
9844
10618
|
'.dig-topbar-spacer{flex:1;min-width:4px}',
|
|
9845
10619
|
'.dig-busy{color:var(--dsw-alias-label-tertiary);flex:none;white-space:nowrap}',
|
|
9846
10620
|
'.dig-track{color:var(--dsw-alias-label-secondary);flex:none;font-variant-numeric:tabular-nums;white-space:nowrap}',
|
|
9847
|
-
'.dig-select-wrap{display:inline-flex;align-items:center;gap:4px;min-width:0;color:var(--dsw-alias-label-secondary)}',
|
|
9848
|
-
'.dig-select{appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;padding:1px 6px;height:22px;max-width:
|
|
10621
|
+
'.dig-select-wrap{display:inline-flex;align-items:center;gap:4px;min-width:0;flex:0 1 auto;max-width:90%;color:var(--dsw-alias-label-secondary)}',
|
|
10622
|
+
'.dig-select{appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;padding:1px 6px;height:22px;max-width:min(280px,42%);min-width:0;cursor:pointer;text-overflow:ellipsis}',
|
|
9849
10623
|
'.dig-select:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
9850
10624
|
'.dig-select:focus-visible{outline:1px solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-1px}',
|
|
9851
10625
|
'.dig-select-btn{display:inline-flex;align-items:center;gap:4px;text-align:left;white-space:nowrap;overflow:hidden}',
|
|
9852
10626
|
'.dig-select-btn .dig-repo-name{flex:1;min-width:0;max-width:none}',
|
|
9853
|
-
'.dig-select-repo{max-width:
|
|
10627
|
+
'.dig-select-repo{max-width:none}',
|
|
9854
10628
|
'.dig-select option,.dig-filter-select option{background:var(--dsw-alias-bg-layer-1,#22262e);color:var(--dsw-alias-label-primary,#e6e8ee)}',
|
|
9855
|
-
|
|
10629
|
+
/* Long branch names (the topbar picker): the cap lives on the WRAP (a
|
|
10630
|
+
direct topbar child, so the percentage resolves against the bar). The
|
|
10631
|
+
button grows a SECOND LINE instead of ellipsising — a sidebar is
|
|
10632
|
+
~400px wide, and a 600px branch name simply cannot fit on one line,
|
|
10633
|
+
but two lines hold it (the user-facing rule: show the WHOLE name).
|
|
10634
|
+
The full name also rides on the wrapper's title. */
|
|
10635
|
+
'.dig-select-branch{max-width:none;height:auto;min-height:22px;align-items:center}',
|
|
10636
|
+
'.dig-select-branch .dig-repo-name{white-space:normal;overflow-wrap:anywhere;word-break:break-word;line-height:1.3}',
|
|
9856
10637
|
'.dig-repo-static{display:inline-flex;align-items:center;gap:5px;min-width:0;color:var(--dsw-alias-label-secondary)}',
|
|
9857
|
-
'.dig-repo-name{color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:
|
|
10638
|
+
'.dig-repo-name{color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:none}',
|
|
9858
10639
|
'.dig-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;padding:0;flex:none}',
|
|
9859
10640
|
'.dig-icon-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
9860
10641
|
'.dig-icon-btn:disabled{opacity:.4;cursor:default}',
|
|
@@ -10003,7 +10784,7 @@ window.__ModuleLoader__.load({
|
|
|
10003
10784
|
'.dig-folder{display:flex;flex-direction:column}',
|
|
10004
10785
|
'.dig-branch-folder{display:flex;flex-direction:column}',
|
|
10005
10786
|
'.dig-row-picked{background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover));color:var(--dsw-alias-label-primary)}',
|
|
10006
|
-
'.dig-folder-head{display:flex;align-items:center;gap:6px;width:100%;padding:1px 8px 1px 4px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer;text-align:left}',
|
|
10787
|
+
'.dig-folder-head{display:flex;align-items:center;gap:6px;width:100%;padding:1px 8px 1px 4px;height:14px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer;text-align:left}',
|
|
10007
10788
|
'.dig-folder-head:hover{color:var(--dsw-alias-label-primary)}',
|
|
10008
10789
|
'.dig-folder-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
10009
10790
|
'.dig-count{margin-left:auto;opacity:.6;font-weight:400}',
|
|
@@ -10038,7 +10819,7 @@ window.__ModuleLoader__.load({
|
|
|
10038
10819
|
'.dig-ref-local{color:var(--dsw-alias-brand-primary)}',
|
|
10039
10820
|
'.dig-ref-remote{color:#b083f0}',
|
|
10040
10821
|
'.dig-ref-tag{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary))}',
|
|
10041
|
-
'.dig-ref-more{color:var(--dsw-alias-label-tertiary)}',
|
|
10822
|
+
'.dig-ref-more{color:var(--dsw-alias-label-tertiary);cursor:pointer}',
|
|
10042
10823
|
'.dig-filters{display:flex;align-items:center;gap:4px;padding:4px 6px;flex:none;min-width:0;overflow-x:auto;overflow-y:hidden;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
10043
10824
|
'.dig-filter-text{flex:1 1 90px;min-width:80px;width:auto}',
|
|
10044
10825
|
'.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:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-secondary);font:inherit;font-weight:500;height:22px;padding:0 4px;cursor:pointer;text-overflow:ellipsis}',
|
|
@@ -10054,10 +10835,10 @@ window.__ModuleLoader__.load({
|
|
|
10054
10835
|
'.dig-changes-body{display:flex;flex-direction:column;min-height:0;flex:1}',
|
|
10055
10836
|
'.dig-changes-list{flex:1;overflow:auto;min-height:0}',
|
|
10056
10837
|
'.dig-group{display:flex;flex-direction:column}',
|
|
10057
|
-
'.dig-group-head{display:flex;align-items:center;gap:8px;padding:2px 8px;color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:600}',
|
|
10838
|
+
'.dig-group-head{display:flex;align-items:center;gap:8px;padding:2px 8px;height:17px;color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:600}',
|
|
10058
10839
|
'.dig-link{margin-left:auto;border:none;background:transparent;color:var(--dsw-alias-brand-primary);font:inherit;cursor:pointer;padding:0;flex:none}',
|
|
10059
10840
|
'.dig-link:hover{text-decoration:underline}',
|
|
10060
|
-
'.dig-row-file{gap:6px}',
|
|
10841
|
+
'.dig-row-file{gap:6px;height:20px}',
|
|
10061
10842
|
'.dig-file-status{flex:none;width:14px;text-align:center;font-weight:700;font-size:10px;color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-secondary))}',
|
|
10062
10843
|
'.dig-file-status-A{color:var(--dsw-alias-state-success-primary)}',
|
|
10063
10844
|
'.dig-file-status-D{color:var(--dsw-alias-state-error-primary)}',
|
|
@@ -10112,6 +10893,21 @@ window.__ModuleLoader__.load({
|
|
|
10112
10893
|
'.dig-file-icon{flex:none;opacity:.95}',
|
|
10113
10894
|
'.dig-overlay{position:absolute;inset:0;padding:8px;box-sizing:border-box;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.42));-webkit-backdrop-filter:var(--dsw-mask-blur,none);backdrop-filter:var(--dsw-mask-blur,none);display:flex;align-items:center;justify-content:center;z-index:60}',
|
|
10114
10895
|
'.dig-dialog{box-sizing:border-box;min-width:min(240px,100%);max-width:min(420px,94%);max-height:100%;overflow:auto;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border:0;border-radius:var(--dsw-radius-panel,28px);padding:14px;display:flex;flex-direction:column;gap:10px;box-shadow:var(--dsw-elevation-prominent,0 12px 32px rgba(0,0,0,.35))}',
|
|
10896
|
+
/* Git-output drawer (VS Code showOutput): panel-internal absolute bottom
|
|
10897
|
+
sheet (invariant 13). */
|
|
10898
|
+
'.dig-gitlog{position:absolute;left:0;right:0;bottom:0;z-index:55;height:46%;min-height:132px;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border-radius:var(--dsw-radius-panel,28px) var(--dsw-radius-panel,28px) 0 0;box-shadow:var(--dsw-elevation-prominent,0 -8px 28px rgba(0,0,0,.3))}',
|
|
10899
|
+
'.dig-gitlog-head{display:flex;align-items:center;gap:4px;padding:8px 14px 6px;flex:none}',
|
|
10900
|
+
'.dig-gitlog-title{font-weight:600}',
|
|
10901
|
+
'.dig-gitlog-empty{padding:10px 14px;color:var(--dsw-alias-label-tertiary)}',
|
|
10902
|
+
'.dig-gitlog-body{flex:1;min-height:0;overflow:auto;padding:0 14px 10px;font-weight:400}',
|
|
10903
|
+
'.dig-gitlog-row{padding:6px 0;border-top:1px solid var(--dsw-alias-border-l1,transparent)}',
|
|
10904
|
+
'.dig-gitlog-row-bad .dig-gitlog-code{color:var(--dsw-alias-state-danger,#e5484d);font-weight:700}',
|
|
10905
|
+
'.dig-gitlog-meta{display:flex;gap:8px;align-items:baseline;flex-wrap:wrap}',
|
|
10906
|
+
'.dig-gitlog-code{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}',
|
|
10907
|
+
'.dig-gitlog-ms{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}',
|
|
10908
|
+
'.dig-gitlog-at{color:var(--dsw-alias-label-tertiary);font-size:11px}',
|
|
10909
|
+
'.dig-gitlog-argv{font-weight:600;word-break:break-all}',
|
|
10910
|
+
'.dig-gitlog-pre{margin:4px 0 0;padding:6px 8px;border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));overflow:auto;max-height:160px;white-space:pre-wrap;word-break:break-all;font-size:11px}',
|
|
10115
10911
|
'.dig-dialog-wide{min-width:min(360px,92%)}',
|
|
10116
10912
|
'.dig-dialog-title{font-weight:600}',
|
|
10117
10913
|
'.dig-dialog-text{color:var(--dsw-alias-label-secondary);white-space:pre-wrap}',
|
|
@@ -10213,7 +11009,7 @@ window.__ModuleLoader__.load({
|
|
|
10213
11009
|
'.dig-lf-tool svg{width:15px;height:15px}',
|
|
10214
11010
|
'.dig-lf-tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}',
|
|
10215
11011
|
'.dig-lf-tool[aria-pressed=true] .dig-lf-compareIcon{transform:rotate(90deg)}',
|
|
10216
|
-
].join('\n')
|
|
11012
|
+
].concat(LANE_TRACE_RULES).join('\n')
|
|
10217
11013
|
|
|
10218
11014
|
/* ============================== plugin ============================== */
|
|
10219
11015
|
|
|
@@ -10519,7 +11315,7 @@ window.__ModuleLoader__.load({
|
|
|
10519
11315
|
? ''
|
|
10520
11316
|
: (seed.hash !== undefined && seed.hash !== null
|
|
10521
11317
|
? 'c:' + String(seed.hash) + ':' + String(seed.path !== undefined && seed.path !== null ? seed.path : '')
|
|
10522
|
-
: 'w:' + String(seed.staged === true) + ':' + String(seed.path))
|
|
11318
|
+
: 'w:' + String(seed.staged === true) + ':' + String(seed.untracked === true) + ':' + String(seed.path))
|
|
10523
11319
|
const [state, setState] = useState({ loading: false, patch: '', error: null })
|
|
10524
11320
|
const [refreshTick, setRefreshTick] = useState(0)
|
|
10525
11321
|
const [copied, setCopied] = useState(false)
|
|
@@ -10539,7 +11335,12 @@ window.__ModuleLoader__.load({
|
|
|
10539
11335
|
since the route exists): the preview follows the exact file
|
|
10540
11336
|
the user clicked instead of the commit's first file. */
|
|
10541
11337
|
if (seed.path !== undefined && seed.path !== null && seed.path !== '') payload.path = seed.path
|
|
10542
|
-
} else {
|
|
11338
|
+
} else {
|
|
11339
|
+
payload.path = seed.path; payload.staged = seed.staged === true
|
|
11340
|
+
/* untracked files never ride `git diff -- path` (no index entry):
|
|
11341
|
+
the host switches to a --no-index /dev/null new-file patch. */
|
|
11342
|
+
if (seed.untracked === true) payload.untracked = true
|
|
11343
|
+
}
|
|
10543
11344
|
const data = await request('diff', payload)
|
|
10544
11345
|
if (alive === true) setState({ loading: false, patch: data === undefined || data === null || typeof data.patch !== 'string' ? '' : data.patch, error: null })
|
|
10545
11346
|
} catch (error) {
|