dsh-ide-git 0.13.4 → 0.14.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 +16 -3
- package/README_EN.md +15 -2
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +1033 -36
- package/src/index.js +7 -0
package/src/client.js
CHANGED
|
@@ -156,6 +156,15 @@ window.__ModuleLoader__.load({
|
|
|
156
156
|
'menu.sendFilePathToChat': '发送文件路径到对话',
|
|
157
157
|
'send.commitText': 'git 提交 {hash}「{subject}」(仓库 {repo})',
|
|
158
158
|
'send.branchText': 'git 分支 {name}(仓库 {repo})',
|
|
159
|
+
'menu.sendDiffLinesToChat': '发送选中行到对话',
|
|
160
|
+
'menu.sendDiffSnippetToChat': '发送选中片段到对话',
|
|
161
|
+
'menu.copyDiffRef': '复制选中引用',
|
|
162
|
+
'send.diffLinesCommit': '{lines}(提交 {hash}「{subject}」@ {branch})',
|
|
163
|
+
'send.diffLinesWorktree': '{lines}(工作区{state})',
|
|
164
|
+
'send.diffStaged': '已暂存',
|
|
165
|
+
'send.diffUnstaged': '未提交',
|
|
166
|
+
'send.diffNewLines': '第 {lines} 行',
|
|
167
|
+
'send.diffOldLines': '第 {lines} 行(旧行号)',
|
|
159
168
|
'history.empty': '这个仓库还没有提交',
|
|
160
169
|
'history.loadMore': '加载更多',
|
|
161
170
|
'history.filter': '文本或哈希',
|
|
@@ -186,6 +195,10 @@ window.__ModuleLoader__.load({
|
|
|
186
195
|
'settings.placement.title': '面板放置',
|
|
187
196
|
'settings.placement.tab': '在主对话区显示 Git 标签页',
|
|
188
197
|
'settings.placement.tabHint': '关闭后仍可从右侧栏或底部面板打开。',
|
|
198
|
+
'settings.placement.immersive': '沉浸模式(占满全高,隐藏输入框)',
|
|
199
|
+
'settings.placement.immersiveHint': '开启后 Git 标签页占满整个会话区高度,输入框自动收起;审批与提问仍会正常显示。',
|
|
200
|
+
'preview.title': 'Git 预览',
|
|
201
|
+
'preview.hint': '点击文件或提交的「显示差异」,差异会在侧边栏这里打开。',
|
|
189
202
|
'settings.rail.title': '动作条按钮',
|
|
190
203
|
'settings.rail.hint': '勾选要显示在动作条上的动作;顺序固定为默认顺序,取消勾选即隐藏。',
|
|
191
204
|
'settings.rail.unavailable': '当前宿主没有设置服务,请用面板内的「动作条设置」。',
|
|
@@ -358,6 +371,15 @@ window.__ModuleLoader__.load({
|
|
|
358
371
|
'menu.sendFilePathToChat': 'Send file path to chat',
|
|
359
372
|
'send.commitText': 'git commit {hash} "{subject}" (repo {repo})',
|
|
360
373
|
'send.branchText': 'git branch {name} (repo {repo})',
|
|
374
|
+
'menu.sendDiffLinesToChat': 'Send selected lines to chat',
|
|
375
|
+
'menu.sendDiffSnippetToChat': 'Send selected snippet to chat',
|
|
376
|
+
'menu.copyDiffRef': 'Copy the selection reference',
|
|
377
|
+
'send.diffLinesCommit': '{lines} (commit {hash} "{subject}" @ {branch})',
|
|
378
|
+
'send.diffLinesWorktree': '{lines} (worktree, {state})',
|
|
379
|
+
'send.diffStaged': 'staged',
|
|
380
|
+
'send.diffUnstaged': 'uncommitted',
|
|
381
|
+
'send.diffNewLines': 'lines {lines}',
|
|
382
|
+
'send.diffOldLines': 'lines {lines} (old numbering)',
|
|
361
383
|
'history.empty': 'This repository has no commits yet',
|
|
362
384
|
'history.loadMore': 'Load more',
|
|
363
385
|
'history.filter': 'Text or hash',
|
|
@@ -388,6 +410,10 @@ window.__ModuleLoader__.load({
|
|
|
388
410
|
'settings.placement.title': 'Placement',
|
|
389
411
|
'settings.placement.tab': 'Show the Git tab in the conversation area',
|
|
390
412
|
'settings.placement.tabHint': 'Off, the panel stays reachable from the right sidebar and the bottom panel.',
|
|
413
|
+
'settings.placement.immersive': 'Immersive mode (full height, hidden composer)',
|
|
414
|
+
'settings.placement.immersiveHint': 'On, the Git tab fills the whole conversation area and the composer steps aside; approvals and questions still show.',
|
|
415
|
+
'preview.title': 'Git Preview',
|
|
416
|
+
'preview.hint': 'Click “Show diff” on a file or commit — the diff opens here in the sidebar.',
|
|
391
417
|
'settings.rail.title': 'Action rail buttons',
|
|
392
418
|
'settings.rail.hint': 'Tick the actions shown on the rail. The order is fixed; unticking one hides it.',
|
|
393
419
|
'settings.rail.unavailable': 'This host has no settings service; use the in-panel “Action rail settings”.',
|
|
@@ -568,6 +594,15 @@ window.__ModuleLoader__.load({
|
|
|
568
594
|
'menu.sendFilePathToChat': '傳送檔案路徑至對話',
|
|
569
595
|
'send.commitText': 'git 提交 {hash}「{subject}」(存放庫 {repo})',
|
|
570
596
|
'send.branchText': 'git 分支 {name}(存放庫 {repo})',
|
|
597
|
+
'menu.sendDiffLinesToChat': '傳送選中行到對話',
|
|
598
|
+
'menu.sendDiffSnippetToChat': '傳送選中片段到對話',
|
|
599
|
+
'menu.copyDiffRef': '複製選中引用',
|
|
600
|
+
'send.diffLinesCommit': '{lines}(提交 {hash}「{subject}」@ {branch})',
|
|
601
|
+
'send.diffLinesWorktree': '{lines}(工作區{state})',
|
|
602
|
+
'send.diffStaged': '已暫存',
|
|
603
|
+
'send.diffUnstaged': '未提交',
|
|
604
|
+
'send.diffNewLines': '第 {lines} 行',
|
|
605
|
+
'send.diffOldLines': '第 {lines} 行(舊行號)',
|
|
571
606
|
'history.empty': '呢個存放庫仲未有提交',
|
|
572
607
|
'history.loadMore': '載入更多',
|
|
573
608
|
'history.filter': '文字或雜湊',
|
|
@@ -598,6 +633,10 @@ window.__ModuleLoader__.load({
|
|
|
598
633
|
'settings.placement.title': '面板放置',
|
|
599
634
|
'settings.placement.tab': '喺主對話區顯示 Git 分頁',
|
|
600
635
|
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
636
|
+
'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
|
|
637
|
+
'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
|
|
638
|
+
'preview.title': 'Git 預覽',
|
|
639
|
+
'preview.hint': '喺檔案或提交撳「顯示差異」,差異就會喺側邊欄度開啟。',
|
|
601
640
|
'settings.rail.title': '動作列按鈕',
|
|
602
641
|
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
603
642
|
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
@@ -770,6 +809,15 @@ window.__ModuleLoader__.load({
|
|
|
770
809
|
'menu.sendFilePathToChat': '傳送檔案路徑至對話',
|
|
771
810
|
'send.commitText': 'git 提交 {hash}「{subject}」(版本庫 {repo})',
|
|
772
811
|
'send.branchText': 'git 分支 {name}(版本庫 {repo})',
|
|
812
|
+
'menu.sendDiffLinesToChat': '傳送選取行至對話',
|
|
813
|
+
'menu.sendDiffSnippetToChat': '傳送選取片段至對話',
|
|
814
|
+
'menu.copyDiffRef': '複製選取的引用',
|
|
815
|
+
'send.diffLinesCommit': '{lines}(提交 {hash}「{subject}」@ {branch})',
|
|
816
|
+
'send.diffLinesWorktree': '{lines}(工作區{state})',
|
|
817
|
+
'send.diffStaged': '已暫存',
|
|
818
|
+
'send.diffUnstaged': '未提交',
|
|
819
|
+
'send.diffNewLines': '第 {lines} 行',
|
|
820
|
+
'send.diffOldLines': '第 {lines} 行(舊行號)',
|
|
773
821
|
'history.empty': '這個版本庫還沒有提交',
|
|
774
822
|
'history.loadMore': '載入更多',
|
|
775
823
|
'history.filter': '文字或雜湊',
|
|
@@ -800,6 +848,10 @@ window.__ModuleLoader__.load({
|
|
|
800
848
|
'settings.placement.title': '面板放置',
|
|
801
849
|
'settings.placement.tab': '在主對話區顯示 Git 分頁',
|
|
802
850
|
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
851
|
+
'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
|
|
852
|
+
'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個對話區高度,輸入框自動收起;審核與提問仍會正常顯示。',
|
|
853
|
+
'preview.title': 'Git 預覽',
|
|
854
|
+
'preview.hint': '點選檔案或提交的「顯示差異」,差異就會在側邊欄開啟。',
|
|
803
855
|
'settings.rail.title': '動作列按鈕',
|
|
804
856
|
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
805
857
|
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
@@ -972,6 +1024,15 @@ window.__ModuleLoader__.load({
|
|
|
972
1024
|
'menu.sendFilePathToChat': '傳送檔案路徑至對話',
|
|
973
1025
|
'send.commitText': 'git 提交 {hash}「{subject}」(存放庫 {repo})',
|
|
974
1026
|
'send.branchText': 'git 分支 {name}(存放庫 {repo})',
|
|
1027
|
+
'menu.sendDiffLinesToChat': '傳送選中行至對話',
|
|
1028
|
+
'menu.sendDiffSnippetToChat': '傳送選中片段至對話',
|
|
1029
|
+
'menu.copyDiffRef': '複製選中引用',
|
|
1030
|
+
'send.diffLinesCommit': '{lines}(提交 {hash}「{subject}」@ {branch})',
|
|
1031
|
+
'send.diffLinesWorktree': '{lines}(工作區{state})',
|
|
1032
|
+
'send.diffStaged': '已暫存',
|
|
1033
|
+
'send.diffUnstaged': '未提交',
|
|
1034
|
+
'send.diffNewLines': '第 {lines} 行',
|
|
1035
|
+
'send.diffOldLines': '第 {lines} 行(舊行號)',
|
|
975
1036
|
'history.empty': '呢個存放庫仲未有提交',
|
|
976
1037
|
'history.loadMore': '載入更多',
|
|
977
1038
|
'history.filter': '文字或雜湊',
|
|
@@ -1002,6 +1063,10 @@ window.__ModuleLoader__.load({
|
|
|
1002
1063
|
'settings.placement.title': '面板放置',
|
|
1003
1064
|
'settings.placement.tab': '喺主對話區顯示 Git 分頁',
|
|
1004
1065
|
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
1066
|
+
'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
|
|
1067
|
+
'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
|
|
1068
|
+
'preview.title': 'Git 預覽',
|
|
1069
|
+
'preview.hint': '喺檔案或提交撳「顯示差異」,差異就會喺側邊欄度開啟。',
|
|
1005
1070
|
'settings.rail.title': '動作列按鈕',
|
|
1006
1071
|
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
1007
1072
|
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
@@ -1174,6 +1239,15 @@ window.__ModuleLoader__.load({
|
|
|
1174
1239
|
'menu.sendFilePathToChat': 'ファイルパスをチャットへ送る',
|
|
1175
1240
|
'send.commitText': 'git コミット {hash}「{subject}」(リポジトリ {repo})',
|
|
1176
1241
|
'send.branchText': 'git ブランチ {name}(リポジトリ {repo})',
|
|
1242
|
+
'menu.sendDiffLinesToChat': '選択した行をチャットへ送る',
|
|
1243
|
+
'menu.sendDiffSnippetToChat': '選択した範囲をチャットへ送る',
|
|
1244
|
+
'menu.copyDiffRef': '選択範囲の参照をコピー',
|
|
1245
|
+
'send.diffLinesCommit': '{lines}(コミット {hash}「{subject}」@ {branch})',
|
|
1246
|
+
'send.diffLinesWorktree': '{lines}(作業ツリー、{state})',
|
|
1247
|
+
'send.diffStaged': 'ステージ済み',
|
|
1248
|
+
'send.diffUnstaged': '未コミット',
|
|
1249
|
+
'send.diffNewLines': '{lines} 行目',
|
|
1250
|
+
'send.diffOldLines': '{lines} 行目(旧番号)',
|
|
1177
1251
|
'history.empty': 'このリポジトリにはまだコミットがありません',
|
|
1178
1252
|
'history.loadMore': 'さらに読み込む',
|
|
1179
1253
|
'history.filter': 'テキストまたはハッシュ',
|
|
@@ -1204,6 +1278,10 @@ window.__ModuleLoader__.load({
|
|
|
1204
1278
|
'settings.placement.title': 'パネル配置',
|
|
1205
1279
|
'settings.placement.tab': '会話エリアに Git タブを表示',
|
|
1206
1280
|
'settings.placement.tabHint': 'オフにしても右サイドバーと下部パネルから開けます。',
|
|
1281
|
+
'settings.placement.immersive': '没入モード(全高表示・入力欄を非表示)',
|
|
1282
|
+
'settings.placement.immersiveHint': 'オンにすると Git タブが会話エリアの高さいっぱいに広がり、入力欄は引っ込みます。承認や質問は通常どおり表示されます。',
|
|
1283
|
+
'preview.title': 'Git プレビュー',
|
|
1284
|
+
'preview.hint': 'ファイルやコミットの「差分を表示」をクリックすると、差分がここに開きます。',
|
|
1207
1285
|
'settings.rail.title': 'アクションレールのボタン',
|
|
1208
1286
|
'settings.rail.hint': 'レールに表示する操作にチェックを入れてください。順序は固定で、チェックを外すと非表示になります。',
|
|
1209
1287
|
'settings.rail.unavailable': 'このホストには設定サービスがないため、パネル内の「アクションレール設定」を使ってください。',
|
|
@@ -1376,6 +1454,15 @@ window.__ModuleLoader__.load({
|
|
|
1376
1454
|
'menu.sendFilePathToChat': '파일 경로를 대화로 보내기',
|
|
1377
1455
|
'send.commitText': 'git 커밋 {hash} "{subject}" (저장소 {repo})',
|
|
1378
1456
|
'send.branchText': 'git 브랜치 {name} (저장소 {repo})',
|
|
1457
|
+
'menu.sendDiffLinesToChat': '선택한 줄 대화로 보내기',
|
|
1458
|
+
'menu.sendDiffSnippetToChat': '선택한 조각 대화로 보내기',
|
|
1459
|
+
'menu.copyDiffRef': '선택 범위 참조 복사',
|
|
1460
|
+
'send.diffLinesCommit': '{lines}(커밋 {hash} "{subject}" @ {branch})',
|
|
1461
|
+
'send.diffLinesWorktree': '{lines}(작업 트리, {state})',
|
|
1462
|
+
'send.diffStaged': '스테이징됨',
|
|
1463
|
+
'send.diffUnstaged': '커밋되지 않음',
|
|
1464
|
+
'send.diffNewLines': '{lines}행',
|
|
1465
|
+
'send.diffOldLines': '{lines}행(이전 번호)',
|
|
1379
1466
|
'history.empty': '이 저장소에는 아직 커밋이 없습니다',
|
|
1380
1467
|
'history.loadMore': '더 불러오기',
|
|
1381
1468
|
'history.filter': '텍스트 또는 해시',
|
|
@@ -1406,6 +1493,10 @@ window.__ModuleLoader__.load({
|
|
|
1406
1493
|
'settings.placement.title': '패널 배치',
|
|
1407
1494
|
'settings.placement.tab': '대화 영역에 Git 탭 표시',
|
|
1408
1495
|
'settings.placement.tabHint': '꺼도 오른쪽 사이드바와 하단 패널에서 열 수 있습니다.',
|
|
1496
|
+
'settings.placement.immersive': '몰입 모드(전체 높이, 입력창 숨김)',
|
|
1497
|
+
'settings.placement.immersiveHint': '켜면 Git 탭이 대화 영역 전체 높이를 차지하고 입력창은 숨겨집니다. 승인과 질문은 정상적으로 표시됩니다.',
|
|
1498
|
+
'preview.title': 'Git 미리보기',
|
|
1499
|
+
'preview.hint': '파일이나 커밋의 「차이 보기」를 클릭하면 차이가 사이드바 여기에 열립니다.',
|
|
1409
1500
|
'settings.rail.title': '액션 레일 버튼',
|
|
1410
1501
|
'settings.rail.hint': '레일에 표시할 동작을 선택하세요. 순서는 고정이며 선택을 해제하면 숨겨집니다.',
|
|
1411
1502
|
'settings.rail.unavailable': '이 호스트에는 설정 서비스가 없으므로 패널의 "액션 레일 설정"을 사용하세요.',
|
|
@@ -1578,6 +1669,15 @@ window.__ModuleLoader__.load({
|
|
|
1578
1669
|
'menu.sendFilePathToChat': 'Dateipfad im Chat senden',
|
|
1579
1670
|
'send.commitText': 'git-Commit {hash} "{subject}" (Repository {repo})',
|
|
1580
1671
|
'send.branchText': 'git-Branch {name} (Repository {repo})',
|
|
1672
|
+
'menu.sendDiffLinesToChat': 'Ausgewählte Zeilen in den Chat senden',
|
|
1673
|
+
'menu.sendDiffSnippetToChat': 'Ausgewählten Ausschnitt in den Chat senden',
|
|
1674
|
+
'menu.copyDiffRef': 'Auswahlbezug kopieren',
|
|
1675
|
+
'send.diffLinesCommit': '{lines} (Commit {hash} "{subject}" @ {branch})',
|
|
1676
|
+
'send.diffLinesWorktree': '{lines} (Arbeitsbaum, {state})',
|
|
1677
|
+
'send.diffStaged': 'staged',
|
|
1678
|
+
'send.diffUnstaged': 'nicht committet',
|
|
1679
|
+
'send.diffNewLines': 'Zeilen {lines}',
|
|
1680
|
+
'send.diffOldLines': 'Zeilen {lines} (alte Nummerierung)',
|
|
1581
1681
|
'history.empty': 'Dieses Repository hat noch keine Commits',
|
|
1582
1682
|
'history.loadMore': 'Mehr laden',
|
|
1583
1683
|
'history.filter': 'Text oder Hash',
|
|
@@ -1608,6 +1708,10 @@ window.__ModuleLoader__.load({
|
|
|
1608
1708
|
'settings.placement.title': 'Panel-Platzierung',
|
|
1609
1709
|
'settings.placement.tab': 'Git-Tab im Gesprächsbereich anzeigen',
|
|
1610
1710
|
'settings.placement.tabHint': 'Ausgeschaltet bleibt das Panel über rechte Seitenleiste und unteres Panel erreichbar.',
|
|
1711
|
+
'settings.placement.immersive': 'Immersiver Modus (volle Höhe, ausgeblendetes Eingabefeld)',
|
|
1712
|
+
'settings.placement.immersiveHint': 'Eingeschaltet füllt der Git-Tab die gesamte Gesprächshöhe und das Eingabefeld weicht zurück; Freigaben und Rückfragen bleiben sichtbar.',
|
|
1713
|
+
'preview.title': 'Git-Vorschau',
|
|
1714
|
+
'preview.hint': '„Diff anzeigen“ an einer Datei oder einem Commit öffnet den Diff hier in der Seitenleiste.',
|
|
1611
1715
|
'settings.rail.title': 'Schaltflächen der Aktionsleiste',
|
|
1612
1716
|
'settings.rail.hint': 'Die auf der Leiste sichtbaren Aktionen ankreuzen. Die Reihenfolge ist fest; abwählen blendet eine aus.',
|
|
1613
1717
|
'settings.rail.unavailable': 'Dieser Host hat keinen Einstellungsdienst; nutze die „Aktionsleisten-Einstellungen“ im Panel.',
|
|
@@ -1780,6 +1884,15 @@ window.__ModuleLoader__.load({
|
|
|
1780
1884
|
'menu.sendFilePathToChat': 'Envoyer le chemin du fichier dans la conversation',
|
|
1781
1885
|
'send.commitText': 'commit git {hash} « {subject} » (dépôt {repo})',
|
|
1782
1886
|
'send.branchText': 'branche git {name} (dépôt {repo})',
|
|
1887
|
+
'menu.sendDiffLinesToChat': 'Envoyer les lignes sélectionnées dans la conversation',
|
|
1888
|
+
'menu.sendDiffSnippetToChat': 'Envoyer l\'extrait sélectionné dans la conversation',
|
|
1889
|
+
'menu.copyDiffRef': 'Copier la référence de la sélection',
|
|
1890
|
+
'send.diffLinesCommit': '{lines} (commit {hash} « {subject} » @ {branch})',
|
|
1891
|
+
'send.diffLinesWorktree': '{lines} (arbre de travail, {state})',
|
|
1892
|
+
'send.diffStaged': 'indexé',
|
|
1893
|
+
'send.diffUnstaged': 'non commité',
|
|
1894
|
+
'send.diffNewLines': 'lignes {lines}',
|
|
1895
|
+
'send.diffOldLines': 'lignes {lines} (ancienne numérotation)',
|
|
1783
1896
|
'history.empty': 'Ce dépôt n\'a pas encore de commit',
|
|
1784
1897
|
'history.loadMore': 'Charger plus',
|
|
1785
1898
|
'history.filter': 'Texte ou empreinte',
|
|
@@ -1810,6 +1923,10 @@ window.__ModuleLoader__.load({
|
|
|
1810
1923
|
'settings.placement.title': 'Placement du panneau',
|
|
1811
1924
|
'settings.placement.tab': 'Afficher l’onglet Git dans la zone de conversation',
|
|
1812
1925
|
'settings.placement.tabHint': 'Désactivé, le panneau reste accessible depuis la barre latérale droite et le panneau inférieur.',
|
|
1926
|
+
'settings.placement.immersive': 'Mode immersif (pleine hauteur, zone de saisie masquée)',
|
|
1927
|
+
'settings.placement.immersiveHint': 'Activé, l’onglet Git occupe toute la hauteur de la conversation et la zone de saisie s’efface ; approbations et questions restent visibles.',
|
|
1928
|
+
'preview.title': 'Aperçu Git',
|
|
1929
|
+
'preview.hint': 'Cliquez sur « Afficher le diff » sur un fichier ou un commit : le diff s’ouvre ici, dans la barre latérale.',
|
|
1813
1930
|
'settings.rail.title': 'Boutons de la barre d’actions',
|
|
1814
1931
|
'settings.rail.hint': 'Cochez les actions affichées sur la barre. L’ordre est fixe ; décocher masque une action.',
|
|
1815
1932
|
'settings.rail.unavailable': 'Cet hôte n’a pas de service de réglages ; utilisez « Réglages de la barre d’actions » dans le panneau.',
|
|
@@ -1982,6 +2099,15 @@ window.__ModuleLoader__.load({
|
|
|
1982
2099
|
'menu.sendFilePathToChat': 'Отправить путь к файлу в чат',
|
|
1983
2100
|
'send.commitText': 'git-коммит {hash} «{subject}» (репозиторий {repo})',
|
|
1984
2101
|
'send.branchText': 'git-ветка {name} (репозиторий {repo})',
|
|
2102
|
+
'menu.sendDiffLinesToChat': 'Отправить выбранные строки в чат',
|
|
2103
|
+
'menu.sendDiffSnippetToChat': 'Отправить выбранный фрагмент в чат',
|
|
2104
|
+
'menu.copyDiffRef': 'Скопировать ссылку на выделение',
|
|
2105
|
+
'send.diffLinesCommit': '{lines} (коммит {hash} «{subject}» @ {branch})',
|
|
2106
|
+
'send.diffLinesWorktree': '{lines} (рабочее дерево, {state})',
|
|
2107
|
+
'send.diffStaged': 'в индексе',
|
|
2108
|
+
'send.diffUnstaged': 'не закоммичено',
|
|
2109
|
+
'send.diffNewLines': 'строки {lines}',
|
|
2110
|
+
'send.diffOldLines': 'строки {lines} (старая нумерация)',
|
|
1985
2111
|
'history.empty': 'В этом репозитории ещё нет коммитов',
|
|
1986
2112
|
'history.loadMore': 'Загрузить ещё',
|
|
1987
2113
|
'history.filter': 'Текст или хеш',
|
|
@@ -2012,6 +2138,10 @@ window.__ModuleLoader__.load({
|
|
|
2012
2138
|
'settings.placement.title': 'Размещение панели',
|
|
2013
2139
|
'settings.placement.tab': 'Показывать вкладку Git в области диалога',
|
|
2014
2140
|
'settings.placement.tabHint': 'В выключенном виде панель доступна из правой боковой панели и нижней панели.',
|
|
2141
|
+
'settings.placement.immersive': 'Полноэкранный режим (во всю высоту, без поля ввода)',
|
|
2142
|
+
'settings.placement.immersiveHint': 'Во включённом виде вкладка Git занимает всю высоту области диалога, поле ввода скрывается; подтверждения и вопросы остаются на виду.',
|
|
2143
|
+
'preview.title': 'Git-предпросмотр',
|
|
2144
|
+
'preview.hint': 'Нажмите «Показать различия» у файла или коммита — различия откроются здесь, в боковой панели.',
|
|
2015
2145
|
'settings.rail.title': 'Кнопки панели действий',
|
|
2016
2146
|
'settings.rail.hint': 'Отметьте действия, показываемые на панели. Порядок фиксирован; снятие флажка скрывает действие.',
|
|
2017
2147
|
'settings.rail.unavailable': 'На этом хосте нет службы настроек; используйте «Настройки панели действий» в панели.',
|
|
@@ -2184,6 +2314,15 @@ window.__ModuleLoader__.load({
|
|
|
2184
2314
|
'menu.sendFilePathToChat': 'Enviar caminho do arquivo para a conversa',
|
|
2185
2315
|
'send.commitText': 'commit git {hash} "{subject}" (repositório {repo})',
|
|
2186
2316
|
'send.branchText': 'branch git {name} (repositório {repo})',
|
|
2317
|
+
'menu.sendDiffLinesToChat': 'Enviar linhas selecionadas para a conversa',
|
|
2318
|
+
'menu.sendDiffSnippetToChat': 'Enviar trecho selecionado para a conversa',
|
|
2319
|
+
'menu.copyDiffRef': 'Copiar a referência da seleção',
|
|
2320
|
+
'send.diffLinesCommit': '{lines} (commit {hash} "{subject}" @ {branch})',
|
|
2321
|
+
'send.diffLinesWorktree': '{lines} (árvore de trabalho, {state})',
|
|
2322
|
+
'send.diffStaged': 'preparado',
|
|
2323
|
+
'send.diffUnstaged': 'não commitado',
|
|
2324
|
+
'send.diffNewLines': 'linhas {lines}',
|
|
2325
|
+
'send.diffOldLines': 'linhas {lines} (numeração antiga)',
|
|
2187
2326
|
'history.empty': 'Este repositório ainda não tem commits',
|
|
2188
2327
|
'history.loadMore': 'Carregar mais',
|
|
2189
2328
|
'history.filter': 'Texto ou hash',
|
|
@@ -2214,6 +2353,10 @@ window.__ModuleLoader__.load({
|
|
|
2214
2353
|
'settings.placement.title': 'Posição do painel',
|
|
2215
2354
|
'settings.placement.tab': 'Mostrar a aba Git na área de conversa',
|
|
2216
2355
|
'settings.placement.tabHint': 'Desligado, o painel continua disponível na barra lateral direita e no painel inferior.',
|
|
2356
|
+
'settings.placement.immersive': 'Modo imersivo (altura total, campo de entrada oculto)',
|
|
2357
|
+
'settings.placement.immersiveHint': 'Ativado, a aba Git ocupa toda a altura da conversa e o campo de entrada fica de lado; aprovações e perguntas continuam visíveis.',
|
|
2358
|
+
'preview.title': 'Pré-visualização do Git',
|
|
2359
|
+
'preview.hint': 'Clique em “Mostrar diff” num arquivo ou commit — o diff abre aqui, na barra lateral.',
|
|
2217
2360
|
'settings.rail.title': 'Botões da barra de ações',
|
|
2218
2361
|
'settings.rail.hint': 'Marque as ações exibidas na barra. A ordem é fixa; desmarcar oculta uma ação.',
|
|
2219
2362
|
'settings.rail.unavailable': 'Este host não tem serviço de configurações; use «Configurações da barra de ações» no painel.',
|
|
@@ -2386,6 +2529,15 @@ window.__ModuleLoader__.load({
|
|
|
2386
2529
|
'menu.sendFilePathToChat': 'Invia il percorso del file alla conversazione',
|
|
2387
2530
|
'send.commitText': 'commit git {hash} "{subject}" (repository {repo})',
|
|
2388
2531
|
'send.branchText': 'branch git {name} (repository {repo})',
|
|
2532
|
+
'menu.sendDiffLinesToChat': 'Invia le righe selezionate alla conversazione',
|
|
2533
|
+
'menu.sendDiffSnippetToChat': 'Invia il frammento selezionato alla conversazione',
|
|
2534
|
+
'menu.copyDiffRef': 'Copia il riferimento alla selezione',
|
|
2535
|
+
'send.diffLinesCommit': '{lines} (commit {hash} "{subject}" @ {branch})',
|
|
2536
|
+
'send.diffLinesWorktree': '{lines} (albero di lavoro, {state})',
|
|
2537
|
+
'send.diffStaged': 'in stage',
|
|
2538
|
+
'send.diffUnstaged': 'senza commit',
|
|
2539
|
+
'send.diffNewLines': 'righe {lines}',
|
|
2540
|
+
'send.diffOldLines': 'righe {lines} (numerazione precedente)',
|
|
2389
2541
|
'history.empty': 'Questo repository non ha ancora commit',
|
|
2390
2542
|
'history.loadMore': 'Carica altro',
|
|
2391
2543
|
'history.filter': 'Testo o hash',
|
|
@@ -2416,6 +2568,10 @@ window.__ModuleLoader__.load({
|
|
|
2416
2568
|
'settings.placement.title': 'Posizione del pannello',
|
|
2417
2569
|
'settings.placement.tab': 'Mostra la scheda Git nell’area conversazione',
|
|
2418
2570
|
'settings.placement.tabHint': 'Da spento, il pannello resta raggiungibile dalla barra laterale destra e dal pannello inferiore.',
|
|
2571
|
+
'settings.placement.immersive': 'Modalità immersiva (altezza piena, input nascosto)',
|
|
2572
|
+
'settings.placement.immersiveHint': 'Attiva, la scheda Git occupa tutta l’altezza della conversazione e la barra di input resta nascosta; approvazioni e domande restano visibili.',
|
|
2573
|
+
'preview.title': 'Anteprima Git',
|
|
2574
|
+
'preview.hint': 'Fai clic su «Mostra diff» su un file o un commit: il diff si apre qui, nella barra laterale.',
|
|
2419
2575
|
'settings.rail.title': 'Pulsanti della barra azioni',
|
|
2420
2576
|
'settings.rail.hint': 'Seleziona le azioni mostrate sulla barra. L’ordine è fisso; deselezionando se ne nasconde una.',
|
|
2421
2577
|
'settings.rail.unavailable': 'Questo host non ha un servizio di impostazioni; usa «Impostazioni barra azioni» nel pannello.',
|
|
@@ -2588,6 +2744,15 @@ window.__ModuleLoader__.load({
|
|
|
2588
2744
|
'menu.sendFilePathToChat': 'Bestandspad naar het gesprek verzenden',
|
|
2589
2745
|
'send.commitText': 'git-commit {hash} "{subject}" (repository {repo})',
|
|
2590
2746
|
'send.branchText': 'git-branch {name} (repository {repo})',
|
|
2747
|
+
'menu.sendDiffLinesToChat': 'Geselecteerde regels naar het gesprek verzenden',
|
|
2748
|
+
'menu.sendDiffSnippetToChat': 'Geselecteerd fragment naar het gesprek verzenden',
|
|
2749
|
+
'menu.copyDiffRef': 'Selectiereferentie kopiëren',
|
|
2750
|
+
'send.diffLinesCommit': '{lines} (commit {hash} "{subject}" @ {branch})',
|
|
2751
|
+
'send.diffLinesWorktree': '{lines} (werkboom, {state})',
|
|
2752
|
+
'send.diffStaged': 'staged',
|
|
2753
|
+
'send.diffUnstaged': 'niet vastgelegd',
|
|
2754
|
+
'send.diffNewLines': 'regels {lines}',
|
|
2755
|
+
'send.diffOldLines': 'regels {lines} (oude nummering)',
|
|
2591
2756
|
'history.empty': 'Deze repository heeft nog geen commits',
|
|
2592
2757
|
'history.loadMore': 'Meer laden',
|
|
2593
2758
|
'history.filter': 'Tekst of hash',
|
|
@@ -2618,6 +2783,10 @@ window.__ModuleLoader__.load({
|
|
|
2618
2783
|
'settings.placement.title': 'Paneelplaatsing',
|
|
2619
2784
|
'settings.placement.tab': 'Git-tabblad in het gespreksgebied tonen',
|
|
2620
2785
|
'settings.placement.tabHint': 'Uitgeschakeld blijft het paneel bereikbaar via de rechterzijbalk en het onderpaneel.',
|
|
2786
|
+
'settings.placement.immersive': 'Meeslepende modus (volledige hoogte, verborgen invoerveld)',
|
|
2787
|
+
'settings.placement.immersiveHint': 'Aan: het Git-tabblad vult de hele gespreksruimte en het invoerveld wijkt; goedkeuringen en vragen blijven zichtbaar.',
|
|
2788
|
+
'preview.title': 'Git-voorbeeld',
|
|
2789
|
+
'preview.hint': 'Klik op „Diff tonen“ bij een bestand of commit — de diff opent hier in de zijbalk.',
|
|
2621
2790
|
'settings.rail.title': 'Knoppen van de actiebalk',
|
|
2622
2791
|
'settings.rail.hint': 'Vink de acties aan die op de balk verschijnen. De volgorde ligt vast; uitvinken verbergt er een.',
|
|
2623
2792
|
'settings.rail.unavailable': 'Deze host heeft geen instellingenservice; gebruik "Actiebalk-instellingen" in het paneel.',
|
|
@@ -2790,6 +2959,15 @@ window.__ModuleLoader__.load({
|
|
|
2790
2959
|
'menu.sendFilePathToChat': 'Wyślij ścieżkę pliku do rozmowy',
|
|
2791
2960
|
'send.commitText': 'commit git {hash} „{subject}" (repozytorium {repo})',
|
|
2792
2961
|
'send.branchText': 'gałąź git {name} (repozytorium {repo})',
|
|
2962
|
+
'menu.sendDiffLinesToChat': 'Wyślij zaznaczone wiersze do rozmowy',
|
|
2963
|
+
'menu.sendDiffSnippetToChat': 'Wyślij zaznaczony fragment do rozmowy',
|
|
2964
|
+
'menu.copyDiffRef': 'Skopiuj odwołanie do zaznaczenia',
|
|
2965
|
+
'send.diffLinesCommit': '{lines} (commit {hash} „{subject}" @ {branch})',
|
|
2966
|
+
'send.diffLinesWorktree': '{lines} (drzewo robocze, {state})',
|
|
2967
|
+
'send.diffStaged': 'przygotowane',
|
|
2968
|
+
'send.diffUnstaged': 'niezacommitowane',
|
|
2969
|
+
'send.diffNewLines': 'wiersze {lines}',
|
|
2970
|
+
'send.diffOldLines': 'wiersze {lines} (stara numeracja)',
|
|
2793
2971
|
'history.empty': 'To repozytorium nie ma jeszcze commitów',
|
|
2794
2972
|
'history.loadMore': 'Wczytaj więcej',
|
|
2795
2973
|
'history.filter': 'Tekst lub hash',
|
|
@@ -2820,6 +2998,10 @@ window.__ModuleLoader__.load({
|
|
|
2820
2998
|
'settings.placement.title': 'Rozmieszczenie panelu',
|
|
2821
2999
|
'settings.placement.tab': 'Pokazuj kartę Git w obszarze rozmowy',
|
|
2822
3000
|
'settings.placement.tabHint': 'Po wyłączeniu panel pozostaje dostępny z prawego paska bocznego i dolnego panelu.',
|
|
3001
|
+
'settings.placement.immersive': 'Tryb immersyjny (pełna wysokość, ukryte pole wpisywania)',
|
|
3002
|
+
'settings.placement.immersiveHint': 'Po włączeniu karta Git wypełnia całą wysokość rozmowy, a pole wpisywania się chowa; zatwierdzenia i pytania pozostają widoczne.',
|
|
3003
|
+
'preview.title': 'Podgląd Git',
|
|
3004
|
+
'preview.hint': 'Kliknij „Pokaż różnice” przy pliku lub commicie — różnice otworzą się tutaj, na pasku bocznym.',
|
|
2823
3005
|
'settings.rail.title': 'Przyciski paska akcji',
|
|
2824
3006
|
'settings.rail.hint': 'Zaznacz akcje widoczne na pasku. Kolejność jest stała; odznaczenie ukrywa akcję.',
|
|
2825
3007
|
'settings.rail.unavailable': 'Ten host nie ma usługi ustawień; użyj „Ustawienia paska akcji” w panelu.',
|
|
@@ -2992,6 +3174,15 @@ window.__ModuleLoader__.load({
|
|
|
2992
3174
|
'menu.sendFilePathToChat': 'Skicka filsökvägen till samtalet',
|
|
2993
3175
|
'send.commitText': 'git-commit {hash} ”{subject}” (arkiv {repo})',
|
|
2994
3176
|
'send.branchText': 'git-gren {name} (arkiv {repo})',
|
|
3177
|
+
'menu.sendDiffLinesToChat': 'Skicka markerade rader till samtalet',
|
|
3178
|
+
'menu.sendDiffSnippetToChat': 'Skicka markerat utdrag till samtalet',
|
|
3179
|
+
'menu.copyDiffRef': 'Kopiera markeringens referens',
|
|
3180
|
+
'send.diffLinesCommit': '{lines} (incheckning {hash} ”{subject}” @ {branch})',
|
|
3181
|
+
'send.diffLinesWorktree': '{lines} (arbetskopia, {state})',
|
|
3182
|
+
'send.diffStaged': 'staged',
|
|
3183
|
+
'send.diffUnstaged': 'ej incheckad',
|
|
3184
|
+
'send.diffNewLines': 'rader {lines}',
|
|
3185
|
+
'send.diffOldLines': 'rader {lines} (gammal numrering)',
|
|
2995
3186
|
'history.empty': 'Det här arkivet har inga commits ännu',
|
|
2996
3187
|
'history.loadMore': 'Ladda mer',
|
|
2997
3188
|
'history.filter': 'Text eller hash',
|
|
@@ -3022,6 +3213,10 @@ window.__ModuleLoader__.load({
|
|
|
3022
3213
|
'settings.placement.title': 'Panelplacering',
|
|
3023
3214
|
'settings.placement.tab': 'Visa Git-fliken i konversationsytan',
|
|
3024
3215
|
'settings.placement.tabHint': 'Avstängd finns panelen kvar via höger sidopanel och nedre panelen.',
|
|
3216
|
+
'settings.placement.immersive': 'Uppslukande läge (full höjd, dolt inmatningsfält)',
|
|
3217
|
+
'settings.placement.immersiveHint': 'På: Git-fliken fyller hela konversationsytan och inmatningsfältet träder åt sidan; godkännanden och frågor visas fortfarande.',
|
|
3218
|
+
'preview.title': 'Git-förhandsvisning',
|
|
3219
|
+
'preview.hint': 'Klicka på ”Visa diff” på en fil eller en commit — diffen öppnas här i sidopanelen.',
|
|
3025
3220
|
'settings.rail.title': 'Knappar i åtgärdsfältet',
|
|
3026
3221
|
'settings.rail.hint': 'Markera de åtgärder som visas i fältet. Ordningen är fast; avmarkering döljer en.',
|
|
3027
3222
|
'settings.rail.unavailable': 'Denna värd saknar inställningstjänst; använd ”Åtgärdsfältets inställningar” i panelen.',
|
|
@@ -3194,6 +3389,15 @@ window.__ModuleLoader__.load({
|
|
|
3194
3389
|
'menu.sendFilePathToChat': 'Dosya yolunu sohbete gönder',
|
|
3195
3390
|
'send.commitText': 'git commit {hash} "{subject}" (depo {repo})',
|
|
3196
3391
|
'send.branchText': 'git dalı {name} (depo {repo})',
|
|
3392
|
+
'menu.sendDiffLinesToChat': 'Seçili satırları sohbete gönder',
|
|
3393
|
+
'menu.sendDiffSnippetToChat': 'Seçili parçayı sohbete gönder',
|
|
3394
|
+
'menu.copyDiffRef': 'Seçim referansını kopyala',
|
|
3395
|
+
'send.diffLinesCommit': '{lines} (işleme {hash} "{subject}" @ {branch})',
|
|
3396
|
+
'send.diffLinesWorktree': '{lines} (çalışma ağacı, {state})',
|
|
3397
|
+
'send.diffStaged': 'hazırlanmış',
|
|
3398
|
+
'send.diffUnstaged': 'işlenmemiş',
|
|
3399
|
+
'send.diffNewLines': 'satırlar {lines}',
|
|
3400
|
+
'send.diffOldLines': 'satırlar {lines} (eski numaralandırma)',
|
|
3197
3401
|
'history.empty': 'Bu depoda henüz commit yok',
|
|
3198
3402
|
'history.loadMore': 'Daha fazla yükle',
|
|
3199
3403
|
'history.filter': 'Metin veya hash',
|
|
@@ -3224,6 +3428,10 @@ window.__ModuleLoader__.load({
|
|
|
3224
3428
|
'settings.placement.title': 'Panel yerleşimi',
|
|
3225
3429
|
'settings.placement.tab': 'Konuşma alanında Git sekmesini göster',
|
|
3226
3430
|
'settings.placement.tabHint': 'Kapatıldığında panel sağ kenar çubuğundan ve alt panelden açılabilir.',
|
|
3431
|
+
'settings.placement.immersive': 'Odaklanma modu (tam yükseklik, gizli giriş alanı)',
|
|
3432
|
+
'settings.placement.immersiveHint': 'Açıkken Git sekmesi tüm konuşma alanını kaplar ve giriş alanı gizlenir; onaylar ve sorular yine de görünür.',
|
|
3433
|
+
'preview.title': 'Git Önizlemesi',
|
|
3434
|
+
'preview.hint': 'Bir dosya veya commit üzerinde „Farkı göster”e tıklayın — fark burada, kenar çubuğunda açılır.',
|
|
3227
3435
|
'settings.rail.title': 'Eylem çubuğu düğmeleri',
|
|
3228
3436
|
'settings.rail.hint': 'Çubukta gösterilecek eylemleri işaretleyin. Sıra sabittir; işareti kaldırmak eylemi gizler.',
|
|
3229
3437
|
'settings.rail.unavailable': 'Bu ana bilgisayarda ayar hizmeti yok; paneldeki "Eylem çubuğu ayarları"nı kullanın.',
|
|
@@ -3396,6 +3604,15 @@ window.__ModuleLoader__.load({
|
|
|
3396
3604
|
'menu.sendFilePathToChat': 'Kirim jalur berkas ke obrolan',
|
|
3397
3605
|
'send.commitText': 'commit git {hash} "{subject}" (repositori {repo})',
|
|
3398
3606
|
'send.branchText': 'cabang git {name} (repositori {repo})',
|
|
3607
|
+
'menu.sendDiffLinesToChat': 'Kirim baris terpilih ke obrolan',
|
|
3608
|
+
'menu.sendDiffSnippetToChat': 'Kirim cuplikan terpilih ke obrolan',
|
|
3609
|
+
'menu.copyDiffRef': 'Salin referensi pilihan',
|
|
3610
|
+
'send.diffLinesCommit': '{lines} (commit {hash} "{subject}" @ {branch})',
|
|
3611
|
+
'send.diffLinesWorktree': '{lines} (pohon kerja, {state})',
|
|
3612
|
+
'send.diffStaged': 'di-stage',
|
|
3613
|
+
'send.diffUnstaged': 'belum di-commit',
|
|
3614
|
+
'send.diffNewLines': 'baris {lines}',
|
|
3615
|
+
'send.diffOldLines': 'baris {lines} (penomoran lama)',
|
|
3399
3616
|
'history.empty': 'Repositori ini belum punya commit',
|
|
3400
3617
|
'history.loadMore': 'Muat lebih banyak',
|
|
3401
3618
|
'history.filter': 'Teks atau hash',
|
|
@@ -3426,6 +3643,10 @@ window.__ModuleLoader__.load({
|
|
|
3426
3643
|
'settings.placement.title': 'Penempatan panel',
|
|
3427
3644
|
'settings.placement.tab': 'Tampilkan tab Git di area percakapan',
|
|
3428
3645
|
'settings.placement.tabHint': 'Dimatikan, panel tetap bisa dibuka dari bilah sisi kanan dan panel bawah.',
|
|
3646
|
+
'settings.placement.immersive': 'Mode imersif (tinggi penuh, kolom masukan tersembunyi)',
|
|
3647
|
+
'settings.placement.immersiveHint': 'Aktif: tab Git memenuhi seluruh tinggi area percakapan dan kolom masukan mengalah; persetujuan dan pertanyaan tetap tampil.',
|
|
3648
|
+
'preview.title': 'Pratinjau Git',
|
|
3649
|
+
'preview.hint': 'Klik “Tampilkan diff” pada file atau commit — diff terbuka di sini, di bilah sisi.',
|
|
3429
3650
|
'settings.rail.title': 'Tombol bilah aksi',
|
|
3430
3651
|
'settings.rail.hint': 'Centang aksi yang ditampilkan di bilah. Urutannya tetap; menghapus centang menyembunyikannya.',
|
|
3431
3652
|
'settings.rail.unavailable': 'Host ini tidak punya layanan pengaturan; gunakan "Pengaturan bilah aksi" di panel.',
|
|
@@ -3598,6 +3819,15 @@ window.__ModuleLoader__.load({
|
|
|
3598
3819
|
'menu.sendFilePathToChat': 'Gửi đường dẫn tệp vào hội thoại',
|
|
3599
3820
|
'send.commitText': 'git commit {hash} "{subject}" (kho {repo})',
|
|
3600
3821
|
'send.branchText': 'nhánh git {name} (kho {repo})',
|
|
3822
|
+
'menu.sendDiffLinesToChat': 'Gửi các dòng đã chọn vào hội thoại',
|
|
3823
|
+
'menu.sendDiffSnippetToChat': 'Gửi đoạn đã chọn vào hội thoại',
|
|
3824
|
+
'menu.copyDiffRef': 'Sao chép tham chiếu vùng chọn',
|
|
3825
|
+
'send.diffLinesCommit': '{lines} (commit {hash} "{subject}" @ {branch})',
|
|
3826
|
+
'send.diffLinesWorktree': '{lines} (cây làm việc, {state})',
|
|
3827
|
+
'send.diffStaged': 'đã staged',
|
|
3828
|
+
'send.diffUnstaged': 'chưa commit',
|
|
3829
|
+
'send.diffNewLines': 'dòng {lines}',
|
|
3830
|
+
'send.diffOldLines': 'dòng {lines} (đánh số cũ)',
|
|
3601
3831
|
'history.empty': 'Kho này chưa có commit nào',
|
|
3602
3832
|
'history.loadMore': 'Tải thêm',
|
|
3603
3833
|
'history.filter': 'Văn bản hoặc hash',
|
|
@@ -3628,6 +3858,10 @@ window.__ModuleLoader__.load({
|
|
|
3628
3858
|
'settings.placement.title': 'Vị trí bảng',
|
|
3629
3859
|
'settings.placement.tab': 'Hiện thẻ Git trong vùng hội thoại',
|
|
3630
3860
|
'settings.placement.tabHint': 'Tắt rồi, bảng vẫn mở được từ thanh bên phải và bảng dưới.',
|
|
3861
|
+
'settings.placement.immersive': 'Chế độ đắm chìm (chiều cao đầy đủ, ẩn ô nhập)',
|
|
3862
|
+
'settings.placement.immersiveHint': 'Bật: thẻ Git chiếm toàn bộ chiều cao vùng hội thoại và ô nhập thu vào; phê duyệt và câu hỏi vẫn hiển thị bình thường.',
|
|
3863
|
+
'preview.title': 'Xem trước Git',
|
|
3864
|
+
'preview.hint': 'Bấm “Xem diff” trên tệp hoặc commit — diff sẽ mở ở đây, trên thanh bên.',
|
|
3631
3865
|
'settings.rail.title': 'Nút thanh tác vụ',
|
|
3632
3866
|
'settings.rail.hint': 'Chọn các tác vụ hiển thị trên thanh. Thứ tự cố định; bỏ chọn sẽ ẩn tác vụ đó.',
|
|
3633
3867
|
'settings.rail.unavailable': 'Máy chủ này không có dịch vụ cài đặt; hãy dùng “Cài đặt thanh tác vụ” trong bảng điều khiển.',
|
|
@@ -3800,6 +4034,15 @@ window.__ModuleLoader__.load({
|
|
|
3800
4034
|
'menu.sendFilePathToChat': 'إرسال مسار الملف إلى المحادثة',
|
|
3801
4035
|
'send.commitText': 'التزام git {hash} "{subject}" (المستودع {repo})',
|
|
3802
4036
|
'send.branchText': 'فرع git {name} (المستودع {repo})',
|
|
4037
|
+
'menu.sendDiffLinesToChat': 'إرسال الأسطر المحددة إلى المحادثة',
|
|
4038
|
+
'menu.sendDiffSnippetToChat': 'إرسال المقتطف المحدد إلى المحادثة',
|
|
4039
|
+
'menu.copyDiffRef': 'نسخ مرجع التحديد',
|
|
4040
|
+
'send.diffLinesCommit': '{lines} (التزام {hash} "{subject}" @ {branch})',
|
|
4041
|
+
'send.diffLinesWorktree': '{lines} (شجرة العمل، {state})',
|
|
4042
|
+
'send.diffStaged': 'مُرحَّل',
|
|
4043
|
+
'send.diffUnstaged': 'غير مُلتزم',
|
|
4044
|
+
'send.diffNewLines': 'الأسطر {lines}',
|
|
4045
|
+
'send.diffOldLines': 'الأسطر {lines} (ترقيم قديم)',
|
|
3803
4046
|
'history.empty': 'لا توجد التزامات في هذا المستودع بعد',
|
|
3804
4047
|
'history.loadMore': 'تحميل المزيد',
|
|
3805
4048
|
'history.filter': 'نص أو تجزئة',
|
|
@@ -3830,6 +4073,10 @@ window.__ModuleLoader__.load({
|
|
|
3830
4073
|
'settings.placement.title': 'موضع اللوحة',
|
|
3831
4074
|
'settings.placement.tab': 'إظهار تبويب Git في منطقة المحادثة',
|
|
3832
4075
|
'settings.placement.tabHint': 'عند الإيقاف تبقى اللوحة متاحة من الشريط الجانبي الأيمن واللوحة السفلية.',
|
|
4076
|
+
'settings.placement.immersive': 'الوضع الغامر (ارتفاع كامل، إخفاء صندوق الكتابة)',
|
|
4077
|
+
'settings.placement.immersiveHint': 'عند التشغيل يملأ تبويب Git منطقة المحادثة بكامل ارتفاعها ويتراجع صندوق الكتابة؛ تظل الموافقات والأسئلة ظاهرة كالمعتاد.',
|
|
4078
|
+
'preview.title': 'معاينة Git',
|
|
4079
|
+
'preview.hint': 'انقر «عرض الفروق» على ملف أو commit — تُفتح الفروق هنا في الشريط الجانبي.',
|
|
3833
4080
|
'settings.rail.title': 'أزرار شريط الإجراءات',
|
|
3834
4081
|
'settings.rail.hint': 'حدّد الإجراءات المعروضة على الشريط. الترتيب ثابت، وإلغاء التحديد يخفي الإجراء.',
|
|
3835
4082
|
'settings.rail.unavailable': 'لا تتوفر خدمة إعدادات على هذا المضيف؛ استخدم «إعدادات شريط الإجراءات» في اللوحة.',
|
|
@@ -4002,6 +4249,15 @@ window.__ModuleLoader__.load({
|
|
|
4002
4249
|
'menu.sendFilePathToChat': 'फ़ाइल पथ बातचीत में भेजें',
|
|
4003
4250
|
'send.commitText': 'git कमिट {hash} "{subject}" (रिपॉज़िटरी {repo})',
|
|
4004
4251
|
'send.branchText': 'git ब्रांच {name} (रिपॉज़िटरी {repo})',
|
|
4252
|
+
'menu.sendDiffLinesToChat': 'चयनित पंक्तियाँ बातचीत में भेजें',
|
|
4253
|
+
'menu.sendDiffSnippetToChat': 'चयनित अंश बातचीत में भेजें',
|
|
4254
|
+
'menu.copyDiffRef': 'चयन का संदर्भ कॉपी करें',
|
|
4255
|
+
'send.diffLinesCommit': '{lines} (कमिट {hash} "{subject}" @ {branch})',
|
|
4256
|
+
'send.diffLinesWorktree': '{lines} (वर्कट्री, {state})',
|
|
4257
|
+
'send.diffStaged': 'स्टेज्ड',
|
|
4258
|
+
'send.diffUnstaged': 'अनकमिटेड',
|
|
4259
|
+
'send.diffNewLines': 'पंक्तियाँ {lines}',
|
|
4260
|
+
'send.diffOldLines': 'पंक्तियाँ {lines} (पुरानी क्रमांक)',
|
|
4005
4261
|
'history.empty': 'इस रिपॉज़िटरी में अभी कोई कमिट नहीं है',
|
|
4006
4262
|
'history.loadMore': 'और लोड करें',
|
|
4007
4263
|
'history.filter': 'टेक्स्ट या हैश',
|
|
@@ -4032,6 +4288,10 @@ window.__ModuleLoader__.load({
|
|
|
4032
4288
|
'settings.placement.title': 'पैनल प्लेसमेंट',
|
|
4033
4289
|
'settings.placement.tab': 'संवाद क्षेत्र में Git टैब दिखाएँ',
|
|
4034
4290
|
'settings.placement.tabHint': 'बंद करने पर भी पैनल दाएँ साइडबार और निचले पैनल से खुलता है।',
|
|
4291
|
+
'settings.placement.immersive': 'इमर्सिव मोड (पूरी ऊँचाई, छिपा इनपुट बॉक्स)',
|
|
4292
|
+
'settings.placement.immersiveHint': 'चालू होने पर Git टैब पूरे संवाद क्षेत्र की ऊँचाई घेर लेता है और इनपुट बॉक्स हट जाता है; अनुमोदन और प्रश्न फिर भी दिखते हैं।',
|
|
4293
|
+
'preview.title': 'Git पूर्वावलोकन',
|
|
4294
|
+
'preview.hint': 'फ़ाइल या कमिट पर “अंतर दिखाएँ” पर क्लिक करें — अंतर यहीं साइडबार में खुलेगा।',
|
|
4035
4295
|
'settings.rail.title': 'एक्शन रेल बटन',
|
|
4036
4296
|
'settings.rail.hint': 'रेल पर दिखने वाली क्रियाएँ चुनें। क्रम निश्चित है; चुनाव हटाने पर वह छिप जाती है।',
|
|
4037
4297
|
'settings.rail.unavailable': 'इस होस्ट में सेटिंग सेवा नहीं है; पैनल में «एक्शन रेल सेटिंग» का उपयोग करें।',
|
|
@@ -4204,6 +4464,15 @@ window.__ModuleLoader__.load({
|
|
|
4204
4464
|
'menu.sendFilePathToChat': 'ส่งพาธไฟล์ไปยังบทสนทนา',
|
|
4205
4465
|
'send.commitText': 'git คอมมิต {hash} "{subject}" (รีโพซิทอรี {repo})',
|
|
4206
4466
|
'send.branchText': 'git บรานช์ {name} (รีโพซิทอรี {repo})',
|
|
4467
|
+
'menu.sendDiffLinesToChat': 'ส่งบรรทัดที่เลือกไปยังบทสนทนา',
|
|
4468
|
+
'menu.sendDiffSnippetToChat': 'ส่งข้อความที่เลือกไปยังบทสนทนา',
|
|
4469
|
+
'menu.copyDiffRef': 'คัดลอกการอ้างอิงที่เลือก',
|
|
4470
|
+
'send.diffLinesCommit': '{lines} (คอมมิต {hash} "{subject}" @ {branch})',
|
|
4471
|
+
'send.diffLinesWorktree': '{lines} (แผนผังงาน, {state})',
|
|
4472
|
+
'send.diffStaged': 'สเตจแล้ว',
|
|
4473
|
+
'send.diffUnstaged': 'ยังไม่คอมมิต',
|
|
4474
|
+
'send.diffNewLines': 'บรรทัด {lines}',
|
|
4475
|
+
'send.diffOldLines': 'บรรทัด {lines} (เลขบรรทัดเดิม)',
|
|
4207
4476
|
'history.empty': 'รีโพซิทอรีนี้ยังไม่มีคอมมิต',
|
|
4208
4477
|
'history.loadMore': 'โหลดเพิ่ม',
|
|
4209
4478
|
'history.filter': 'ข้อความหรือแฮช',
|
|
@@ -4234,6 +4503,10 @@ window.__ModuleLoader__.load({
|
|
|
4234
4503
|
'settings.placement.title': 'ตำแหน่งแผง',
|
|
4235
4504
|
'settings.placement.tab': 'แสดงแท็บ Git ในพื้นที่บทสนทนา',
|
|
4236
4505
|
'settings.placement.tabHint': 'ปิดแล้วยังเปิดแผงจากแถบข้างขวาหรือแผงล่างได้',
|
|
4506
|
+
'settings.placement.immersive': 'โหมดดื่มด่ำ (เต็มความสูง, ซ่อนกล่องพิมพ์)',
|
|
4507
|
+
'settings.placement.immersiveHint': 'เมื่อเปิด แท็บ Git จะกินพื้นที่สูงทั้งหมดของบทสนทนาและกล่องพิมพ์จะถอยออก; การอนุมัติและคำถามยังแสดงตามปกติ',
|
|
4508
|
+
'preview.title': 'ดูตัวอย่าง Git',
|
|
4509
|
+
'preview.hint': 'คลิก “แสดงความต่าง” ที่ไฟล์หรือคอมมิต — ความต่างจะเปิดที่นี่ในแถบข้าง',
|
|
4237
4510
|
'settings.rail.title': 'ปุ่มแถบการทำงาน',
|
|
4238
4511
|
'settings.rail.hint': 'ทำเครื่องหมายการทำงานที่จะแสดงบนแถบ ลำดับคงที่; ยกเลิกเครื่องหมายเพื่อซ่อน',
|
|
4239
4512
|
'settings.rail.unavailable': 'โฮสต์นี้ไม่มีบริการตั้งค่า ให้ใช้ «ตั้งค่าแถบการทำงาน» ในแผง',
|
|
@@ -4488,16 +4761,19 @@ window.__ModuleLoader__.load({
|
|
|
4488
4761
|
is still essentially correct, so it seeds the states and a background
|
|
4489
4762
|
refresh lands the fresh data a moment later. Pure functions; the panel
|
|
4490
4763
|
only ever reads them at effect time and writes after a fetch. */
|
|
4491
|
-
/* Two
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4764
|
+
/* Two seeds age very differently. The repos scan is the SLOW part (a
|
|
4765
|
+
multi-repo workspace walks directories) and its result barely changes,
|
|
4766
|
+
so its seed keeps a 10-minute TTL: freshness is the background refresh's
|
|
4767
|
+
job — every mount refreshes anyway — the TTL only decides how old a seed
|
|
4768
|
+
may be to still paint the first screen. The page seed (summary /
|
|
4769
|
+
branches / log) used to expire after 30s, which is exactly the user
|
|
4770
|
+
complaint this queue answers ("come back to the panel after a while and
|
|
4771
|
+
everything re-loads"): it is a KEEP-WARM QUEUE now — no expiry at all,
|
|
4772
|
+
an LRU of three workspaces. Every page in the queue paints instantly no
|
|
4773
|
+
matter how long the panel stayed closed; the mount refresh re-proves
|
|
4774
|
+
every field silently right after, so freshness never regresses. */
|
|
4499
4775
|
const PAINT_REPOS_TTL_MS = 600_000
|
|
4500
|
-
const
|
|
4776
|
+
const PAINT_PAGE_KEEP_WARM = 3
|
|
4501
4777
|
const paintRepos = new Map() /* cwd -> { at, data } */
|
|
4502
4778
|
const paintPage = new Map() /* repoRoot -> { at, summary, branches, commits, hasMore } */
|
|
4503
4779
|
const paintRoot = new Map() /* cwd -> last repoRoot the user was looking at */
|
|
@@ -4537,6 +4813,13 @@ window.__ModuleLoader__.load({
|
|
|
4537
4813
|
}
|
|
4538
4814
|
feed(paintRepos, parsed.repos)
|
|
4539
4815
|
feed(paintPage, parsed.page)
|
|
4816
|
+
/* The queue cap applies to restored pages too: storage may hold more
|
|
4817
|
+
than the live queue does (an older build wrote 16), trim to the same
|
|
4818
|
+
three — restored insertion order mirrors the order that was in
|
|
4819
|
+
memory, so the survivors are the most recent pages. */
|
|
4820
|
+
while (paintPage.size > PAINT_PAGE_KEEP_WARM) {
|
|
4821
|
+
paintPage.delete(paintPage.keys().next().value)
|
|
4822
|
+
}
|
|
4540
4823
|
}
|
|
4541
4824
|
|
|
4542
4825
|
function persistPaint() {
|
|
@@ -4566,13 +4849,24 @@ window.__ModuleLoader__.load({
|
|
|
4566
4849
|
function readPaintPage(repoRoot) {
|
|
4567
4850
|
hydratePaint()
|
|
4568
4851
|
const entry = paintPage.get(repoRoot)
|
|
4569
|
-
if (entry === undefined
|
|
4852
|
+
if (entry === undefined) return undefined
|
|
4853
|
+
/* A read is a USE: re-insert at the tail so eviction tracks what the
|
|
4854
|
+
user actually visits, not which page happened to be written last. */
|
|
4855
|
+
paintPage.delete(repoRoot)
|
|
4856
|
+
paintPage.set(repoRoot, entry)
|
|
4570
4857
|
return entry
|
|
4571
4858
|
}
|
|
4572
4859
|
|
|
4573
4860
|
function writePaintPage(repoRoot, page) {
|
|
4574
4861
|
paintPage.set(repoRoot, Object.assign({ at: Date.now() }, page))
|
|
4575
|
-
|
|
4862
|
+
/* The keep-warm queue cap: three workspaces stay painted, the fourth
|
|
4863
|
+
evicts the least-recently-used one. Pure LRU over insertion order —
|
|
4864
|
+
reads re-tail themselves (readPaintPage), so the survivor set is the
|
|
4865
|
+
three repo pages the user actually keeps coming back to. */
|
|
4866
|
+
while (paintPage.size > PAINT_PAGE_KEEP_WARM) {
|
|
4867
|
+
const oldest = paintPage.keys().next().value
|
|
4868
|
+
paintPage.delete(oldest)
|
|
4869
|
+
}
|
|
4576
4870
|
persistPaint()
|
|
4577
4871
|
}
|
|
4578
4872
|
|
|
@@ -4829,6 +5123,19 @@ window.__ModuleLoader__.load({
|
|
|
4829
5123
|
return snapshot
|
|
4830
5124
|
}
|
|
4831
5125
|
|
|
5126
|
+
/* The conversation seat's immersive mode (issue #8 round 3, placement
|
|
5127
|
+
group): the view rides the host's composer-overlay contract so it fills
|
|
5128
|
+
the WHOLE conversation height, and the composer seat hides while the Git
|
|
5129
|
+
view owns the ring. Default ON — that full-height space is why the tab
|
|
5130
|
+
placement exists at all; the settings card flips it live. A host without
|
|
5131
|
+
the row Config keeps the immersive seat (same policy as the tab switch). */
|
|
5132
|
+
function conversationImmersiveEnabled() {
|
|
5133
|
+
const ready = railFormReady()
|
|
5134
|
+
if (ready === null) return true
|
|
5135
|
+
const value = ready.value !== null && typeof ready.value === 'object' ? ready.value.immersive : undefined
|
|
5136
|
+
return value !== false
|
|
5137
|
+
}
|
|
5138
|
+
|
|
4832
5139
|
function readRailConfig() {
|
|
4833
5140
|
const ready = railFormReady()
|
|
4834
5141
|
if (ready !== null) return railConfigOfValues(ready.value)
|
|
@@ -5495,20 +5802,47 @@ window.__ModuleLoader__.load({
|
|
|
5495
5802
|
} catch (error) { void error; return null }
|
|
5496
5803
|
}
|
|
5497
5804
|
|
|
5805
|
+
/* The composer carries TWO text projections (ui-conversation projection.ts):
|
|
5806
|
+
detectText feeds TokenSpan coordinates — every chip counts as ONE U+FFFC
|
|
5807
|
+
there — while the published InputState.draft is the clipboard projection,
|
|
5808
|
+
where a chip expands to its full `@path`. The insert verbs take DETECT
|
|
5809
|
+
spans, and selectSpan rejects `end > detectLength` outright (span-map.ts),
|
|
5810
|
+
so a tail span built from draft.length is correct only while the draft has
|
|
5811
|
+
NO chips. With one in the draft it points past the document end and the
|
|
5812
|
+
edit silently fails — exactly the "chip landed, the line-range sentence
|
|
5813
|
+
never did" real-machine report against v0.13.5 (and any plain-text send
|
|
5814
|
+
into a draft that already holds a chip). Convert instead: subtract every
|
|
5815
|
+
occurrence's (clipboard length - 1). */
|
|
5816
|
+
function detectTailSpanOf(state) {
|
|
5817
|
+
if (state === undefined || state === null || typeof state.draft !== 'string' || typeof state.draftRev !== 'number') return null
|
|
5818
|
+
let at = state.draft.length
|
|
5819
|
+
const occurrences = Array.isArray(state.occurrences) ? state.occurrences : []
|
|
5820
|
+
for (const occ of occurrences) {
|
|
5821
|
+
if (occ === null || typeof occ !== 'object') continue
|
|
5822
|
+
const width = typeof occ.length === 'number' && occ.length >= 0
|
|
5823
|
+
? occ.length
|
|
5824
|
+
: (typeof occ.clipboardText === 'string' ? occ.clipboardText.length : 1)
|
|
5825
|
+
at -= Math.max(0, width - 1)
|
|
5826
|
+
}
|
|
5827
|
+
if (at < 0) return null
|
|
5828
|
+
return { start: at, end: at, draftRev: state.draftRev }
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5498
5831
|
/* apply(actx, input, span) → boolean. The span is an EMPTY span at the draft
|
|
5499
|
-
tail in
|
|
5500
|
-
|
|
5501
|
-
re-read once and retry (the brief's advice:
|
|
5502
|
-
busy phases (adjudicating/submitting) also
|
|
5503
|
-
same clipboard fallback, which is the
|
|
5832
|
+
tail in DETECT coordinates (the coordinates the CAS and selectSpan
|
|
5833
|
+
compare — see detectTailSpanOf). A false answer is the user typing
|
|
5834
|
+
between our read and write — re-read once and retry (the brief's advice:
|
|
5835
|
+
one retry, no spinning). The busy phases (adjudicating/submitting) also
|
|
5836
|
+
answer false; that lands on the same clipboard fallback, which is the
|
|
5837
|
+
honest outcome. */
|
|
5504
5838
|
function insertIntoComposer(ctx, sessionId, apply) {
|
|
5505
5839
|
const target = composerTargetOf(ctx, sessionId)
|
|
5506
5840
|
if (target === null) return false
|
|
5507
5841
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
5508
5842
|
let state
|
|
5509
5843
|
try { state = target.input.state.getSnapshot() } catch (error) { void error; return false }
|
|
5510
|
-
|
|
5511
|
-
|
|
5844
|
+
const span = detectTailSpanOf(state)
|
|
5845
|
+
if (span === null) return false
|
|
5512
5846
|
let applied = false
|
|
5513
5847
|
try { applied = apply(target.actx, target.input, span) === true } catch (error) { void error; applied = false }
|
|
5514
5848
|
if (applied === true) {
|
|
@@ -5576,7 +5910,11 @@ window.__ModuleLoader__.load({
|
|
|
5576
5910
|
} else if (line.indexOf('@@') === 0) {
|
|
5577
5911
|
kind = 'hunk'
|
|
5578
5912
|
const match = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line)
|
|
5579
|
-
|
|
5913
|
+
// The @@ header names the FIRST content line (git semantics), but the
|
|
5914
|
+
// counters below are PRE-INCREMENTED before they land in a row — so
|
|
5915
|
+
// seed one less. Off by one otherwise: v0.13.3's hand-drawn gutter
|
|
5916
|
+
// numbered every line one too high (surfaced by the selection core).
|
|
5917
|
+
if (match !== null) { oldLine = Number.parseInt(match[1], 10) - 1; newLine = Number.parseInt(match[2], 10) - 1 }
|
|
5580
5918
|
} else if (line.indexOf('+') === 0) { kind = 'add'; newLine += 1 }
|
|
5581
5919
|
else if (line.indexOf('-') === 0) { kind = 'del'; oldLine += 1 }
|
|
5582
5920
|
else if (line.indexOf('\\') === 0) { kind = 'meta' }
|
|
@@ -5590,6 +5928,250 @@ window.__ModuleLoader__.load({
|
|
|
5590
5928
|
return out
|
|
5591
5929
|
}
|
|
5592
5930
|
|
|
5931
|
+
/* ---- diff selection core
|
|
5932
|
+
Pure line-number plumbing for the diff-pane context menu (v0.13.5).
|
|
5933
|
+
The host DiffBlock draws rows WITHOUT line-number attributes — a row is
|
|
5934
|
+
`<div class={line + kindClass}>{row.text}</div>` (DiffBlock.tsx:204) and
|
|
5935
|
+
the `+`/`-`/two-space prefixes live in CSS `::before` content — so the
|
|
5936
|
+
menu reconstructs numbers by replaying the ROW KINDS in document order
|
|
5937
|
+
from the patch's first hunk header. The hand-drawn rows already carry
|
|
5938
|
+
parsed numbers (diffLines above). Kept DOM-free between the markers so
|
|
5939
|
+
tests/smoke.mjs can slice the block and drive the real implementation. */
|
|
5940
|
+
|
|
5941
|
+
const DIFF_SNIPPET_MAX = 600
|
|
5942
|
+
|
|
5943
|
+
/* First hunk header of a single-file patch → the absolute numbers the
|
|
5944
|
+
replay starts from (`@@ -a,b +c,d @@`: a/c are the sides' first lines).
|
|
5945
|
+
A patch without any header (binary, mode-only) has no coordinates. */
|
|
5946
|
+
function hunkStartOf(patch) {
|
|
5947
|
+
const headers = hunkHeadersOf(patch)
|
|
5948
|
+
return headers === null ? null : headers[0]
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5951
|
+
/* Every hunk header in patch order: a multi-hunk diff restarts BOTH
|
|
5952
|
+
counters at each `@@` line, so the replay needs one seed per hunk. */
|
|
5953
|
+
function hunkHeadersOf(patch) {
|
|
5954
|
+
const text = typeof patch === 'string' ? patch : ''
|
|
5955
|
+
if (text.indexOf('@@') < 0) return null
|
|
5956
|
+
const headers = []
|
|
5957
|
+
const pattern = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/gm
|
|
5958
|
+
let match = pattern.exec(text)
|
|
5959
|
+
while (match !== null) {
|
|
5960
|
+
headers.push({ oldLine: Number.parseInt(match[1], 10), newLine: Number.parseInt(match[2], 10) })
|
|
5961
|
+
match = pattern.exec(text)
|
|
5962
|
+
}
|
|
5963
|
+
return headers.length === 0 ? null : headers
|
|
5964
|
+
}
|
|
5965
|
+
|
|
5966
|
+
/* getComputedStyle(el, '::before').content → the row kind DiffBlock
|
|
5967
|
+
paints: `- ` del, `+ ` add, two spaces context, nothing for path/gap
|
|
5968
|
+
chrome. Browsers quote the value ("+ "); strip quotes before comparing. */
|
|
5969
|
+
function beforeContentKind(content) {
|
|
5970
|
+
if (typeof content !== 'string') return null
|
|
5971
|
+
const value = content.length >= 2 && content.charAt(0) === '"' && content.charAt(content.length - 1) === '"'
|
|
5972
|
+
? content.slice(1, -1)
|
|
5973
|
+
: content
|
|
5974
|
+
if (value === '- ') return 'del'
|
|
5975
|
+
if (value === '+ ') return 'add'
|
|
5976
|
+
if (value === ' ') return 'ctx'
|
|
5977
|
+
return null
|
|
5978
|
+
}
|
|
5979
|
+
|
|
5980
|
+
/* Replay kinds[0..to] (document order), collecting the span covered by
|
|
5981
|
+
kinds[from..to]. Each hunk restarts BOTH counters from its own header:
|
|
5982
|
+
a chrome row (path/gap, kind null) marks the boundary, and the next
|
|
5983
|
+
content row consumes the NEXT header in patch order — the path row at
|
|
5984
|
+
the top then the gap rows between hunks. New-side numbers win; a purely
|
|
5985
|
+
deleted span reports the OLD numbers with old: true — deleted rows
|
|
5986
|
+
exist on that side only. */
|
|
5987
|
+
function lineSpanFromKinds(kinds, from, to, headers) {
|
|
5988
|
+
if (Array.isArray(kinds) === false || Array.isArray(headers) === false || headers.length === 0) return null
|
|
5989
|
+
if (!(from >= 0 && to >= from && to < kinds.length)) return null
|
|
5990
|
+
let headerIndex = 0
|
|
5991
|
+
let oldLine = 0
|
|
5992
|
+
let newLine = 0
|
|
5993
|
+
let started = false
|
|
5994
|
+
let newFrom = null
|
|
5995
|
+
let newTo = null
|
|
5996
|
+
let oldFrom = null
|
|
5997
|
+
let oldTo = null
|
|
5998
|
+
for (let index = 0; index <= to; index += 1) {
|
|
5999
|
+
const kind = kinds[index]
|
|
6000
|
+
if (kind !== 'del' && kind !== 'add' && kind !== 'ctx') {
|
|
6001
|
+
started = false // chrome row: the next content row starts a new hunk
|
|
6002
|
+
continue
|
|
6003
|
+
}
|
|
6004
|
+
if (started === false) {
|
|
6005
|
+
if (headerIndex >= headers.length) break // more hunks than headers: stop, don't guess
|
|
6006
|
+
const header = headers[headerIndex]
|
|
6007
|
+
headerIndex += 1
|
|
6008
|
+
oldLine = header.oldLine
|
|
6009
|
+
newLine = header.newLine
|
|
6010
|
+
started = true
|
|
6011
|
+
}
|
|
6012
|
+
let oldHit = null
|
|
6013
|
+
let newHit = null
|
|
6014
|
+
if (kind === 'del') { oldHit = oldLine; oldLine += 1 }
|
|
6015
|
+
else if (kind === 'add') { newHit = newLine; newLine += 1 }
|
|
6016
|
+
else { oldHit = oldLine; newHit = newLine; oldLine += 1; newLine += 1 }
|
|
6017
|
+
if (index >= from) {
|
|
6018
|
+
if (oldHit !== null) { oldFrom = oldFrom === null ? oldHit : oldFrom; oldTo = oldHit }
|
|
6019
|
+
if (newHit !== null) { newFrom = newFrom === null ? newHit : newFrom; newTo = newHit }
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
if (newFrom !== null) return { start: newFrom, end: newTo === null ? newFrom : newTo, old: false }
|
|
6023
|
+
if (oldFrom !== null) return { start: oldFrom, end: oldTo === null ? oldFrom : oldTo, old: true }
|
|
6024
|
+
return null
|
|
6025
|
+
}
|
|
6026
|
+
|
|
6027
|
+
/* The hand-drawn rows already carry parsed numbers: reduce the selected
|
|
6028
|
+
row range exactly like lineSpanFromKinds reduces replayed kinds. */
|
|
6029
|
+
function rowSpanOfRows(rows, from, to) {
|
|
6030
|
+
if (Array.isArray(rows) === false) return null
|
|
6031
|
+
if (!(from >= 0 && to >= from && to < rows.length)) return null
|
|
6032
|
+
let newFrom = null
|
|
6033
|
+
let newTo = null
|
|
6034
|
+
let oldFrom = null
|
|
6035
|
+
let oldTo = null
|
|
6036
|
+
for (let index = from; index <= to; index += 1) {
|
|
6037
|
+
const row = rows[index]
|
|
6038
|
+
if (row === undefined || row === null) continue
|
|
6039
|
+
const newHit = typeof row.newLine === 'number' ? row.newLine : null
|
|
6040
|
+
const oldHit = typeof row.oldLine === 'number' ? row.oldLine : null
|
|
6041
|
+
if (newHit !== null) { newFrom = newFrom === null ? newHit : newFrom; newTo = newHit }
|
|
6042
|
+
if (oldHit !== null) { oldFrom = oldFrom === null ? oldHit : oldFrom; oldTo = oldHit }
|
|
6043
|
+
}
|
|
6044
|
+
if (newFrom !== null) return { start: newFrom, end: newTo === null ? newFrom : newTo, old: false }
|
|
6045
|
+
if (oldFrom !== null) return { start: oldFrom, end: oldTo === null ? oldFrom : oldTo, old: true }
|
|
6046
|
+
return null
|
|
6047
|
+
}
|
|
6048
|
+
|
|
6049
|
+
/* '7' or '7–9' — the {lines} value the payload templates receive. */
|
|
6050
|
+
function linesLabelOf(start, end) {
|
|
6051
|
+
return start === end ? String(start) : String(start) + '–' + String(end)
|
|
6052
|
+
}
|
|
6053
|
+
|
|
6054
|
+
/* The localized line-span fragment the payload templates embed: new-side
|
|
6055
|
+
numbers read plain, old-side numbers say so instead of pretending. */
|
|
6056
|
+
function lineSpanLabelOf(t, span) {
|
|
6057
|
+
const lines = linesLabelOf(span.start, span.end)
|
|
6058
|
+
return fill(t(span.old === true ? 'send.diffOldLines' : 'send.diffNewLines'), { lines: lines })
|
|
6059
|
+
}
|
|
6060
|
+
|
|
6061
|
+
/* The self-describing line reference text for one resolved span.
|
|
6062
|
+
Commit context carries hash + subject (truncated like the commit send)
|
|
6063
|
+
and the HEAD branch; the worktree pane says staged or uncommitted. */
|
|
6064
|
+
function diffSpanTextOf(t, context, span, branchName) {
|
|
6065
|
+
const lines = lineSpanLabelOf(t, span)
|
|
6066
|
+
if (context === null || context === undefined || context.kind !== 'commit') {
|
|
6067
|
+
return fill(t('send.diffLinesWorktree'), {
|
|
6068
|
+
lines: lines,
|
|
6069
|
+
state: t(context !== null && context !== undefined && context.staged === true ? 'send.diffStaged' : 'send.diffUnstaged'),
|
|
6070
|
+
})
|
|
6071
|
+
}
|
|
6072
|
+
const subject = typeof context.subject === 'string' ? context.subject.trim().slice(0, 60) : ''
|
|
6073
|
+
return fill(t('send.diffLinesCommit'), {
|
|
6074
|
+
lines: lines,
|
|
6075
|
+
hash: typeof context.hash === 'string' && context.hash !== '' ? context.hash.slice(0, 7) : '—',
|
|
6076
|
+
subject: subject === '' ? '—' : subject,
|
|
6077
|
+
branch: typeof branchName === 'string' && branchName !== '' ? branchName : '—',
|
|
6078
|
+
})
|
|
6079
|
+
}
|
|
6080
|
+
|
|
6081
|
+
/* ---- end diff selection core */
|
|
6082
|
+
|
|
6083
|
+
/* ---------- DOM side of the diff-pane context menu (browser only) ----------
|
|
6084
|
+
SSR never calls these: they run inside the pane's onContextMenu, after
|
|
6085
|
+
the selection has been captured. The row element under a selection
|
|
6086
|
+
endpoint is either a hand-drawn `.dig-diff-line`, or a DiffBlock row —
|
|
6087
|
+
a div child of the body whose OWN parent carries the `data-diff`
|
|
6088
|
+
marker. `data-diff` is a component attribute (DiffBlock.tsx:199), not a
|
|
6089
|
+
hashed CSS-module class, so the lookup does not bet on build-time class
|
|
6090
|
+
renaming; the row KIND comes from the `::before` content the kinds are
|
|
6091
|
+
painted with, for the same reason. */
|
|
6092
|
+
|
|
6093
|
+
function savedSelectionOf() {
|
|
6094
|
+
try {
|
|
6095
|
+
const selection = window.getSelection()
|
|
6096
|
+
if (selection === null || selection.rangeCount === 0) return null
|
|
6097
|
+
const text = String(selection)
|
|
6098
|
+
if (text === '') return null
|
|
6099
|
+
return { text: text, range: selection.getRangeAt(0).cloneRange() }
|
|
6100
|
+
} catch (error) { void error; return null }
|
|
6101
|
+
}
|
|
6102
|
+
|
|
6103
|
+
function selectionRowOf(node) {
|
|
6104
|
+
let element = node === null || node === undefined ? null
|
|
6105
|
+
: node.nodeType === 3 ? (node.parentElement === null ? null : node.parentElement)
|
|
6106
|
+
: node.nodeType === 1 ? node
|
|
6107
|
+
: null
|
|
6108
|
+
for (let hop = 0; element !== null && element !== undefined && hop < 8; hop += 1) {
|
|
6109
|
+
if (element.classList !== undefined && element.classList.contains('dig-diff-line') === true) {
|
|
6110
|
+
return { kind: 'drawn', row: element, body: element.parentElement }
|
|
6111
|
+
}
|
|
6112
|
+
const parent = element.parentElement
|
|
6113
|
+
if (parent !== null && parent.getAttribute !== undefined && parent.hasAttribute('data-diff') === false) {
|
|
6114
|
+
const root = parent.parentElement
|
|
6115
|
+
if (root !== null && root.getAttribute !== undefined && root.hasAttribute('data-diff') === true) {
|
|
6116
|
+
return { kind: 'host', row: element, body: parent }
|
|
6117
|
+
}
|
|
6118
|
+
}
|
|
6119
|
+
element = parent
|
|
6120
|
+
}
|
|
6121
|
+
return null
|
|
6122
|
+
}
|
|
6123
|
+
|
|
6124
|
+
/* The rendered rows of a DiffBlock body, with the kind each one paints.
|
|
6125
|
+
The (never-rendered at this panel's maxLines) fold toggle is a button,
|
|
6126
|
+
not a div — skipped so it cannot shift the row indexes. */
|
|
6127
|
+
function hostRowKinds(body) {
|
|
6128
|
+
const children = body === null || body === undefined ? [] : body.children
|
|
6129
|
+
const rows = []
|
|
6130
|
+
const kinds = []
|
|
6131
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
6132
|
+
const child = children[index]
|
|
6133
|
+
if (child === null || child === undefined || child.tagName === 'BUTTON') continue
|
|
6134
|
+
rows.push(child)
|
|
6135
|
+
let kind = null
|
|
6136
|
+
try { kind = beforeContentKind(window.getComputedStyle(child, '::before').content) } catch (error) { void error; kind = null }
|
|
6137
|
+
kinds.push(kind)
|
|
6138
|
+
}
|
|
6139
|
+
return { rows: rows, kinds: kinds }
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6142
|
+
/* { kind: 'lines', span } for a selection inside ONE diff body, or
|
|
6143
|
+
{ kind: 'snippet' } when the rows are there but the numbers cannot be
|
|
6144
|
+
reconstructed. null (rows missing entirely) means "not a diff
|
|
6145
|
+
selection": the menu keeps its old behaviour. */
|
|
6146
|
+
function resolveDiffSelection(patch, saved) {
|
|
6147
|
+
if (saved === null || saved === undefined || saved.text === '' || saved.range === null || saved.range === undefined) return null
|
|
6148
|
+
const from = selectionRowOf(saved.range.startContainer)
|
|
6149
|
+
const to = selectionRowOf(saved.range.endContainer)
|
|
6150
|
+
if (from === null || to === null) return null
|
|
6151
|
+
if (from.kind !== to.kind || from.body !== to.body) return { kind: 'snippet', text: saved.text }
|
|
6152
|
+
let span = null
|
|
6153
|
+
if (from.kind === 'drawn') {
|
|
6154
|
+
const rows = diffLines(patch)
|
|
6155
|
+
const all = from.body === null || from.body === undefined ? [] : Array.prototype.slice.call(from.body.querySelectorAll('.dig-diff-line'))
|
|
6156
|
+
const fromIndex = all.indexOf(from.row)
|
|
6157
|
+
const toIndex = all.indexOf(to.row)
|
|
6158
|
+
if (fromIndex >= 0 && toIndex >= 0) span = rowSpanOfRows(rows, Math.min(fromIndex, toIndex), Math.max(fromIndex, toIndex))
|
|
6159
|
+
} else {
|
|
6160
|
+
const headers = hunkHeadersOf(patch)
|
|
6161
|
+
if (headers !== null) {
|
|
6162
|
+
const collected = hostRowKinds(from.body)
|
|
6163
|
+
const fromIndex = collected.rows.indexOf(from.row)
|
|
6164
|
+
const toIndex = collected.rows.indexOf(to.row)
|
|
6165
|
+
if (fromIndex >= 0 && toIndex >= 0) {
|
|
6166
|
+
span = lineSpanFromKinds(collected.kinds, Math.min(fromIndex, toIndex), Math.max(fromIndex, toIndex), headers)
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
if (span === null) return { kind: 'snippet', text: saved.text }
|
|
6171
|
+
return { kind: 'lines', span: span, text: saved.text }
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
|
|
5593
6175
|
/* DiffBlock's own 16-line cap would REPLACE scrolling with a fold button;
|
|
5594
6176
|
this panel keeps the old contract (render everything, scroll inside the
|
|
5595
6177
|
.dig-diff-pane max-height), so the cap is pushed out of reach and the
|
|
@@ -6191,6 +6773,50 @@ window.__ModuleLoader__.load({
|
|
|
6191
6773
|
E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.placement.tab'))),
|
|
6192
6774
|
E('div', { className: 'dig-settings-subnote' }, t('settings.placement.tabHint')))
|
|
6193
6775
|
})(),
|
|
6776
|
+
(() => {
|
|
6777
|
+
/* Immersive mode (issue #8 round 3): the conversation seat's
|
|
6778
|
+
full-height overlay layout + hidden composer. Default ON — the
|
|
6779
|
+
full-height space is what the tab placement is for; flipping it
|
|
6780
|
+
re-renders the seat live (the ConversationPanel subscribes to the
|
|
6781
|
+
same config). */
|
|
6782
|
+
const field = 'immersive'
|
|
6783
|
+
const inputId = 'dig-placement-' + field
|
|
6784
|
+
const shown = Object.prototype.hasOwnProperty.call(intent.current, field)
|
|
6785
|
+
? intent.current[field] === true
|
|
6786
|
+
: values[field] !== false
|
|
6787
|
+
const toggle = (event) => {
|
|
6788
|
+
if (readOnly === true) { setNote(t('settings.rail.readonly')); return }
|
|
6789
|
+
const fromControl = event !== undefined && event !== null && event.target !== null
|
|
6790
|
+
&& event.target !== undefined && typeof event.target.checked === 'boolean'
|
|
6791
|
+
? event.target.checked
|
|
6792
|
+
: null
|
|
6793
|
+
const wanted = fromControl !== null
|
|
6794
|
+
? fromControl
|
|
6795
|
+
: (Object.prototype.hasOwnProperty.call(intent.current, field)
|
|
6796
|
+
? intent.current[field] !== true
|
|
6797
|
+
: values[field] !== false) !== true
|
|
6798
|
+
intent.current[field] = wanted
|
|
6799
|
+
dirty.current.set(field, wanted)
|
|
6800
|
+
setPending((now) => Object.assign({}, now, { [field]: wanted }))
|
|
6801
|
+
setNote('')
|
|
6802
|
+
bump()
|
|
6803
|
+
scheduleFlush()
|
|
6804
|
+
}
|
|
6805
|
+
return E('div', { key: field },
|
|
6806
|
+
E('div', { className: 'dig-settings-row', 'data-placement-row': field },
|
|
6807
|
+
E('input', {
|
|
6808
|
+
id: inputId,
|
|
6809
|
+
type: 'checkbox',
|
|
6810
|
+
className: 'dig-settings-check',
|
|
6811
|
+
'aria-labelledby': inputId + '-label',
|
|
6812
|
+
checked: shown,
|
|
6813
|
+
disabled: readOnly === true,
|
|
6814
|
+
onChange: toggle,
|
|
6815
|
+
}),
|
|
6816
|
+
E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: 'commit', size: 13 })),
|
|
6817
|
+
E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.placement.immersive'))),
|
|
6818
|
+
E('div', { className: 'dig-settings-subnote' }, t('settings.placement.immersiveHint')))
|
|
6819
|
+
})(),
|
|
6194
6820
|
E('div', { className: 'dig-settings-sub' }, t('settings.commit.title')),
|
|
6195
6821
|
(() => {
|
|
6196
6822
|
/* The model row is a picker over the host's provider catalog; while
|
|
@@ -7157,6 +7783,11 @@ window.__ModuleLoader__.load({
|
|
|
7157
7783
|
const [selectedPath, setSelectedPath] = useState(null)
|
|
7158
7784
|
const [patch, setPatch] = useState('')
|
|
7159
7785
|
const [patchLoading, setPatchLoading] = useState(false)
|
|
7786
|
+
/* What the open diff pane shows (v0.13.5): the pane is shared by the
|
|
7787
|
+
commit detail (hash + subject) and the working tree (staged or not),
|
|
7788
|
+
and the selection menu's payload must say which. Cleared everywhere
|
|
7789
|
+
the patch is cleared. */
|
|
7790
|
+
const [diffContext, setDiffContext] = useState(null)
|
|
7160
7791
|
const [menu, setMenu] = useState(null)
|
|
7161
7792
|
const [dialog, setDialog] = useState(null)
|
|
7162
7793
|
const [tick, setTick] = useState(0)
|
|
@@ -7213,8 +7844,23 @@ window.__ModuleLoader__.load({
|
|
|
7213
7844
|
props.openSettings()
|
|
7214
7845
|
}
|
|
7215
7846
|
|
|
7847
|
+
/* The immersive conversation seat (v0.14) hands the panel a SQUARE-ish
|
|
7848
|
+
box by construction: the conversation column clamps content width
|
|
7849
|
+
(~920px) while the seat fills the view's whole height, so the
|
|
7850
|
+
bottom-workbench-vs-sidebar ratio (`width >= height * 1.15`) can never
|
|
7851
|
+
hold there and the panel would flip to the stacked layout. The seat is
|
|
7852
|
+
a tool window — neither a horizontal dock nor a narrow rail — so it
|
|
7853
|
+
always takes the columns chrome (compact still wins: a truly narrow
|
|
7854
|
+
viewport keeps the single-row layout). */
|
|
7855
|
+
const [immersiveSeat, setImmersiveSeat] = useState(false)
|
|
7856
|
+
useMeasureLayoutEffect(() => {
|
|
7857
|
+
const node = hostRef.current
|
|
7858
|
+
const holder = node === null || typeof node.closest !== 'function' ? null : node.closest('.dig-conversation-seat[data-immersive="1"]')
|
|
7859
|
+
setImmersiveSeat(holder !== null)
|
|
7860
|
+
})
|
|
7216
7861
|
const compact = size.width > 0 && size.width < COMPACT_MAX_WIDTH
|
|
7217
|
-
const columns =
|
|
7862
|
+
const columns = (immersiveSeat === true && compact === false)
|
|
7863
|
+
|| (!compact && size.width >= 600 && size.width >= size.height * 1.15)
|
|
7218
7864
|
|
|
7219
7865
|
/* ---------- draggable panes (issue #5) ---------- */
|
|
7220
7866
|
|
|
@@ -7523,6 +8169,7 @@ window.__ModuleLoader__.load({
|
|
|
7523
8169
|
setDetail(null)
|
|
7524
8170
|
setSelectedHash(null)
|
|
7525
8171
|
setPatch('')
|
|
8172
|
+
setDiffContext(null)
|
|
7526
8173
|
setView('history')
|
|
7527
8174
|
setPathFilter('')
|
|
7528
8175
|
}, [sessionId])
|
|
@@ -7540,10 +8187,92 @@ window.__ModuleLoader__.load({
|
|
|
7540
8187
|
}
|
|
7541
8188
|
}, [base])
|
|
7542
8189
|
|
|
8190
|
+
/* ---------- external diff preview (user request, 2026-10-08) ---------- */
|
|
8191
|
+
/* 「点击文件预览」不再内嵌:预览交给 better-sidebar 内置的 diff tab
|
|
8192
|
+
(type 'diff',Hidden 内置页,DiffTab 自己经它自家 git 通道拉 patch,
|
|
8193
|
+
我们只递引用)。placement 跟随点击发生的容器——底部工作台里点开在
|
|
8194
|
+
底部、右栏里点开在右栏,走底座的官方 onOpenDiff 交接(props 注入,
|
|
8195
|
+
placement 由底座 store 决定);主对话区座位与原生座位没有该通道,
|
|
8196
|
+
直接 betterSidebar.openTab 定向右栏(target: 'right')。两通道皆
|
|
8197
|
+
不可用(无底座 / 老底座)→ 返回 false,调用方落回内嵌 diff 窗。
|
|
8198
|
+
ref 的 worktree + repoRoot 都填本面板的仓库根:DiffTab 的加载器把
|
|
8199
|
+
repoRoot 并进它自己的 git 调用,多仓库工作区不会落错仓库。 */
|
|
8200
|
+
const previewDiff = useCallback((seed) => {
|
|
8201
|
+
if (repoRoot === null) return false
|
|
8202
|
+
const commitHash = seed.hash !== undefined && seed.hash !== null ? String(seed.hash) : null
|
|
8203
|
+
const ref = commitHash !== null
|
|
8204
|
+
? { kind: 'commit', hash: commitHash.slice(0, 7), hashFull: commitHash, subject: seed.subject || '', worktree: repoRoot, repoRoot: repoRoot }
|
|
8205
|
+
: { kind: 'worktree', path: seed.path, staged: seed.staged === true, untracked: seed.untracked === true ? true : undefined, worktree: repoRoot, repoRoot: repoRoot }
|
|
8206
|
+
const title = commitHash !== null
|
|
8207
|
+
? commitHash.slice(0, 7) + (seed.subject ? ' ' + seed.subject : '')
|
|
8208
|
+
: baseName(String(seed.path))
|
|
8209
|
+
const id = commitHash !== null
|
|
8210
|
+
? 'diff:c:' + encodeURIComponent(repoRoot) + ':' + commitHash
|
|
8211
|
+
: 'diff:w:' + encodeURIComponent(repoRoot) + ':' + (seed.staged === true ? 's' : 'u') + ':' + seed.path
|
|
8212
|
+
const tab = { id: id, type: 'diff', title: title, diff: ref }
|
|
8213
|
+
/* Seat channel first — the bottom workbench opens its own diff tab,
|
|
8214
|
+
the right column opens it there; placement is the base's call. */
|
|
8215
|
+
if (typeof props.openDiffExternal === 'function') {
|
|
8216
|
+
try {
|
|
8217
|
+
props.openDiffExternal(tab)
|
|
8218
|
+
return true
|
|
8219
|
+
} catch (error) { void error }
|
|
8220
|
+
}
|
|
8221
|
+
/* Conversation seat / native seat: aim at the right sidebar. The
|
|
8222
|
+
capability gate keeps an older base (pre-targetedOpen) from
|
|
8223
|
+
receiving a field it cannot place; any refusal falls through to
|
|
8224
|
+
the inline pane below. */
|
|
8225
|
+
let betterSidebar = undefined
|
|
8226
|
+
try { betterSidebar = props.ctx === undefined || props.ctx === null ? undefined : props.ctx.get('betterSidebar') } catch (error) { void error }
|
|
8227
|
+
if (betterSidebar !== undefined && betterSidebar !== null && typeof betterSidebar.openTab === 'function') {
|
|
8228
|
+
const capabilities = betterSidebar.capabilities
|
|
8229
|
+
const targeted = capabilities === undefined || capabilities === null || (Array.isArray(capabilities) && capabilities.indexOf('targetedOpen') >= 0)
|
|
8230
|
+
if (targeted === true) {
|
|
8231
|
+
try {
|
|
8232
|
+
const scopeObject = scope || {}
|
|
8233
|
+
betterSidebar.openTab({ type: 'diff', title: title, id: id, diff: ref, target: 'right' }, { sessionId: scopeObject.sessionId, cwd: scopeObject.cwd })
|
|
8234
|
+
return true
|
|
8235
|
+
} catch (error) { void error }
|
|
8236
|
+
}
|
|
8237
|
+
}
|
|
8238
|
+
/* Native preview tab: the base is absent (or too old), so the plugin's
|
|
8239
|
+
OWN second native right-sidebar type takes the preview. The seed
|
|
8240
|
+
carries cwd (the route requires it) + repoRoot (skips the rev-parse);
|
|
8241
|
+
the tab is a singleton per pane — a second click re-seeds it in
|
|
8242
|
+
place instead of stacking tabs. */
|
|
8243
|
+
let sidebarRight = undefined
|
|
8244
|
+
try { sidebarRight = props.ctx === undefined || props.ctx === null ? undefined : props.ctx.get('sidebarRight') } catch (error) { void error }
|
|
8245
|
+
if (sidebarRight !== undefined && sidebarRight !== null && typeof sidebarRight.openTab === 'function') {
|
|
8246
|
+
try {
|
|
8247
|
+
const scopeObject = scope || {}
|
|
8248
|
+
previewStore.set(commitHash !== null
|
|
8249
|
+
? { hash: commitHash, subject: seed.subject || '', cwd: scopeObject.cwd, repoRoot: repoRoot }
|
|
8250
|
+
: { path: seed.path, staged: seed.staged === true, cwd: scopeObject.cwd, repoRoot: repoRoot })
|
|
8251
|
+
sidebarRight.openTab(NATIVE_PREVIEW_KIND)
|
|
8252
|
+
return true
|
|
8253
|
+
} catch (error) { void error }
|
|
8254
|
+
}
|
|
8255
|
+
return false
|
|
8256
|
+
}, [repoRoot, scope, props.openDiffExternal, props.ctx])
|
|
8257
|
+
|
|
8258
|
+
/* The one diff verb every surface calls (row click, row menu, detail
|
|
8259
|
+
file menu, working-diff shortcut): external first, inline fallback. */
|
|
8260
|
+
const showDiff = useCallback((seed) => {
|
|
8261
|
+
if (previewDiff(seed) === true) return
|
|
8262
|
+
if (seed.hash !== undefined && seed.hash !== null) {
|
|
8263
|
+
setDiffContext({ kind: 'commit', hash: seed.hash, subject: seed.subject || '' })
|
|
8264
|
+
void openDiff({ hash: seed.hash, path: seed.path })
|
|
8265
|
+
return
|
|
8266
|
+
}
|
|
8267
|
+
setDiffContext({ kind: 'worktree', staged: seed.staged === true })
|
|
8268
|
+
void openDiff({ path: seed.path, staged: seed.staged === true })
|
|
8269
|
+
}, [previewDiff, openDiff])
|
|
8270
|
+
|
|
7543
8271
|
const selectCommit = useCallback(async (commit) => {
|
|
7544
8272
|
setSelectedHash(commit.hash)
|
|
7545
8273
|
setSelectedPath(null)
|
|
7546
8274
|
setPatch('')
|
|
8275
|
+
setDiffContext(null)
|
|
7547
8276
|
setView('detail')
|
|
7548
8277
|
setDetail(null)
|
|
7549
8278
|
try {
|
|
@@ -7620,6 +8349,41 @@ window.__ModuleLoader__.load({
|
|
|
7620
8349
|
and a toast says so. */
|
|
7621
8350
|
const sendToComposer = useCallback((payload) => {
|
|
7622
8351
|
const applied = insertIntoComposer(props.ctx, sessionId, (actx, input, span) => {
|
|
8352
|
+
if (payload.mention !== undefined && payload.text !== undefined) {
|
|
8353
|
+
// Chip + trailing sentence (v0.13.5 diff line refs): the @file chip
|
|
8354
|
+
// first, then the line-range text at the NEW draft tail (the chip
|
|
8355
|
+
// changed both the draft and its rev). The facade appends a
|
|
8356
|
+
// separating space after a chip when none follows, so the text leg
|
|
8357
|
+
// goes in verbatim. If the text leg loses its CAS race twice the
|
|
8358
|
+
// chip still stands — the reference made it, that reads as sent.
|
|
8359
|
+
let chipApplied = false
|
|
8360
|
+
try {
|
|
8361
|
+
chipApplied = input.insertReference({
|
|
8362
|
+
source: 'reference',
|
|
8363
|
+
ref: payload.mention,
|
|
8364
|
+
label: payload.label,
|
|
8365
|
+
appearance: payload.appearance,
|
|
8366
|
+
clipboardText: payload.mention,
|
|
8367
|
+
}, span) === true
|
|
8368
|
+
} catch (error) { void error; chipApplied = false }
|
|
8369
|
+
if (chipApplied === false) return false
|
|
8370
|
+
try {
|
|
8371
|
+
let state = input.state.getSnapshot()
|
|
8372
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
8373
|
+
// The chip changed BOTH projections; the tail must be rebuilt
|
|
8374
|
+
// in detect coordinates (see detectTailSpanOf), or selectSpan
|
|
8375
|
+
// rejects it and the sentence silently vanishes.
|
|
8376
|
+
const tail = detectTailSpanOf(state)
|
|
8377
|
+
if (tail !== null) {
|
|
8378
|
+
let textApplied = false
|
|
8379
|
+
try { textApplied = actx.bail(actx, 'slash/input-insert-text', { text: payload.text, span: tail }) === true } catch (error) { void error; textApplied = false }
|
|
8380
|
+
if (textApplied === true) return true
|
|
8381
|
+
}
|
|
8382
|
+
try { state = input.state.getSnapshot() } catch (error) { void error; return true }
|
|
8383
|
+
}
|
|
8384
|
+
} catch (error) { void error }
|
|
8385
|
+
return true
|
|
8386
|
+
}
|
|
7623
8387
|
if (payload.appearance !== undefined) {
|
|
7624
8388
|
return input.insertReference({
|
|
7625
8389
|
source: 'reference',
|
|
@@ -7717,7 +8481,7 @@ window.__ModuleLoader__.load({
|
|
|
7717
8481
|
|
|
7718
8482
|
const changeMenu = useCallback((event, item, group) => {
|
|
7719
8483
|
const items = [
|
|
7720
|
-
{ id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => {
|
|
8484
|
+
{ id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => { showDiff({ path: item.path, staged: group === 'staged', untracked: group === 'untracked' }) } },
|
|
7721
8485
|
{ id: 'sendRef', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => sendFileReferenceToChat(item.path) },
|
|
7722
8486
|
{ id: 'sendPath', icon: 'file', tone: 'secondary', label: t('menu.sendFilePathToChat'), run: () => sendToComposer({ text: item.path, fallback: item.path }) },
|
|
7723
8487
|
null,
|
|
@@ -7729,20 +8493,87 @@ window.__ModuleLoader__.load({
|
|
|
7729
8493
|
{ id: 'copy', icon: 'tag', tone: 'secondary', label: t('action.copyPath'), run: () => { copyText(item.path) } },
|
|
7730
8494
|
]
|
|
7731
8495
|
openMenuAt(event, items)
|
|
7732
|
-
}, [
|
|
8496
|
+
}, [showDiff, run, t, openMenuAt, sendFileReferenceToChat, sendToComposer])
|
|
7733
8497
|
|
|
7734
8498
|
/* The commit-detail file list shares the working-tree row menu, minus the
|
|
7735
8499
|
staging verbs: a committed file's diff replays against its commit. */
|
|
7736
8500
|
const detailFileMenu = useCallback((event, file) => {
|
|
7737
8501
|
openMenuAt(event, [
|
|
7738
|
-
{ id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => {
|
|
8502
|
+
{ id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => {
|
|
8503
|
+
setSelectedPath(file.path)
|
|
8504
|
+
showDiff({
|
|
8505
|
+
hash: detail === null || detail === undefined ? undefined : detail.hash,
|
|
8506
|
+
subject: detail === null || detail === undefined ? '' : detail.subject,
|
|
8507
|
+
path: file.path,
|
|
8508
|
+
})
|
|
8509
|
+
} },
|
|
7739
8510
|
null,
|
|
7740
8511
|
{ id: 'sendRef', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => sendFileReferenceToChat(file.path) },
|
|
7741
8512
|
{ id: 'sendPath', icon: 'file', tone: 'secondary', label: t('menu.sendFilePathToChat'), run: () => sendToComposer({ text: file.path, fallback: file.path }) },
|
|
7742
8513
|
null,
|
|
7743
8514
|
{ id: 'copy', icon: 'tag', tone: 'secondary', label: t('action.copyPath'), run: () => { copyText(file.path) } },
|
|
7744
8515
|
])
|
|
7745
|
-
}, [detail,
|
|
8516
|
+
}, [detail, showDiff, openMenuAt, sendFileReferenceToChat, sendToComposer, t])
|
|
8517
|
+
|
|
8518
|
+
/* The diff pane's own context menu (v0.13.5): select text in the diff,
|
|
8519
|
+
right-click, send a self-describing line reference to the composer.
|
|
8520
|
+
The window selection is read BEFORE the menu opens — some platforms
|
|
8521
|
+
clear it the moment a context menu shows — and the whole payload is
|
|
8522
|
+
computed right here, so the menu items run from captured values and
|
|
8523
|
+
never depend on a selection that may no longer exist. A selection
|
|
8524
|
+
outside the diff rows keeps the browser's own menu: preventDefault is
|
|
8525
|
+
skipped and no item appears (the old behaviour).
|
|
8526
|
+
|
|
8527
|
+
LAST-RESORT SEMANTICS (v0.13.5 forward compat): the rows under this
|
|
8528
|
+
pane may be HOST-rendered (ui-primitives DiffBlock), and a future DSH
|
|
8529
|
+
is free to add its own context-menu surface to that component. React
|
|
8530
|
+
dispatches synthetic contextmenu handlers in bubble order — innermost
|
|
8531
|
+
first — so an inner handler that claimed the event (opened its menu
|
|
8532
|
+
or called preventDefault) has already set defaultPrevented by the
|
|
8533
|
+
time this container handler runs. That check is the FIRST statement:
|
|
8534
|
+
if the host handled it we return and the host menu stands alone —
|
|
8535
|
+
never two menus at once, never an override. An unusable selection
|
|
8536
|
+
also returns without preventDefault (browser menu preserved). Only a
|
|
8537
|
+
valid diff selection that NOTHING else handled reaches our menu.
|
|
8538
|
+
UPGRADE MEMO (diff-右键向前兼容): DiffBlock today exposes NO menu
|
|
8539
|
+
surface and NO extension prop — DiffBlockProps is exactly
|
|
8540
|
+
{ diffs, labels, maxLines, className } (ui-primitives DiffBlock.tsx);
|
|
8541
|
+
its only stable hook is the `data-diff` marker on the root div, which
|
|
8542
|
+
this pane already keys on. When upstream ships a menu surface (most
|
|
8543
|
+
likely an onRowContextMenu / items prop mounted on the row div or the
|
|
8544
|
+
body container), MIGRATE to that prop — feed it our line-reference
|
|
8545
|
+
items and RETIRE this container-level handler — the absorption
|
|
8546
|
+
pattern dsh-better-workspace uses (host menu surface + host
|
|
8547
|
+
dictionary, own entries appended last), not an event race. Until
|
|
8548
|
+
then this handler stays deliberately last-resort. */
|
|
8549
|
+
const onDiffPaneContextMenu = useCallback((event) => {
|
|
8550
|
+
if (event.defaultPrevented === true) return // an inner (host DiffBlock) handler already owns this event
|
|
8551
|
+
const saved = savedSelectionOf()
|
|
8552
|
+
const resolved = saved === null ? null : resolveDiffSelection(patch, saved)
|
|
8553
|
+
if (saved === null || resolved === null) return
|
|
8554
|
+
event.preventDefault()
|
|
8555
|
+
const path = selectedPath
|
|
8556
|
+
const hasFile = typeof path === 'string' && path !== ''
|
|
8557
|
+
const branchName = branches === null ? '' : branches.branch
|
|
8558
|
+
const mention = hasFile === true ? fileMentionOf(path) : null
|
|
8559
|
+
const snippet = resolved.text.length > DIFF_SNIPPET_MAX ? resolved.text.slice(0, DIFF_SNIPPET_MAX) : resolved.text
|
|
8560
|
+
const text = resolved.kind === 'lines' && hasFile === true
|
|
8561
|
+
? diffSpanTextOf(t, diffContext, resolved.span, branchName)
|
|
8562
|
+
: snippet
|
|
8563
|
+
const plain = (mention === null ? (hasFile === true ? path : '') : mention) + ' ' + text
|
|
8564
|
+
const sendChip = () => {
|
|
8565
|
+
if (mention === null || hasFile === false) { sendToComposer({ text: text, fallback: text }); return }
|
|
8566
|
+
sendToComposer({ mention: mention, label: baseName(path), appearance: 'file', text: text, fallback: plain })
|
|
8567
|
+
}
|
|
8568
|
+
const items = [
|
|
8569
|
+
hasFile === true && resolved.kind === 'lines'
|
|
8570
|
+
? { id: 'sendDiffLines', icon: 'send', tone: 'accent', label: t('menu.sendDiffLinesToChat'), run: sendChip }
|
|
8571
|
+
: { id: 'sendDiffSnippet', icon: 'send', tone: 'accent', label: t(resolved.kind === 'lines' ? 'menu.sendDiffLinesToChat' : 'menu.sendDiffSnippetToChat'), run: sendChip },
|
|
8572
|
+
null,
|
|
8573
|
+
{ id: 'copyDiffRef', icon: 'tag', tone: 'secondary', label: t('menu.copyDiffRef'), run: () => { copyText(plain) } },
|
|
8574
|
+
]
|
|
8575
|
+
openMenuAt(event, items)
|
|
8576
|
+
}, [patch, selectedPath, diffContext, branches, openMenuAt, sendToComposer, t])
|
|
7746
8577
|
|
|
7747
8578
|
const submitDialog = useCallback(async (state, value) => {
|
|
7748
8579
|
setDialog(null)
|
|
@@ -7915,17 +8746,21 @@ window.__ModuleLoader__.load({
|
|
|
7915
8746
|
onStageAll: (entries) => { void run('stage', { paths: entries.map((entry) => entry.path) }) },
|
|
7916
8747
|
onUnstageAll: () => { void run('unstage', { paths: (summary === null ? [] : summary.changes.staged).map((entry) => entry.path) }) },
|
|
7917
8748
|
onDiscard: (item, group) => setDialog({ kind: 'discard', item: item, group: group }),
|
|
7918
|
-
onDiff: (item, group) => {
|
|
8749
|
+
onDiff: (item, group) => { showDiff({ path: item.path, staged: group === 'staged', untracked: group === 'untracked' }) },
|
|
7919
8750
|
onChangeMenu: changeMenu,
|
|
7920
8751
|
})
|
|
7921
8752
|
|
|
7922
8753
|
const historyPane = view === 'detail'
|
|
7923
8754
|
? E(CommitDetail, {
|
|
7924
8755
|
t: t, detail: detail, selectedPath: selectedPath,
|
|
7925
|
-
onBack: () => { setView('history'); setSelectedPath(null); setPatch('') },
|
|
8756
|
+
onBack: () => { setView('history'); setSelectedPath(null); setPatch(''); setDiffContext(null) },
|
|
7926
8757
|
onSelectFile: (file) => {
|
|
7927
8758
|
setSelectedPath(file.path)
|
|
7928
|
-
|
|
8759
|
+
showDiff({
|
|
8760
|
+
hash: detail === null || detail === undefined ? undefined : detail.hash,
|
|
8761
|
+
subject: detail === null || detail === undefined ? '' : detail.subject,
|
|
8762
|
+
path: file.path,
|
|
8763
|
+
})
|
|
7929
8764
|
},
|
|
7930
8765
|
onFileMenu: detailFileMenu,
|
|
7931
8766
|
})
|
|
@@ -7938,10 +8773,10 @@ window.__ModuleLoader__.load({
|
|
|
7938
8773
|
onLoadMore: () => { void guard(() => loadCommits(commits.length)) },
|
|
7939
8774
|
})
|
|
7940
8775
|
|
|
7941
|
-
const diffPane = patch === '' && patchLoading === false ? null : E('div', { className: 'dig-diff-pane', 'data-pane': 'diff', style: paneStyle('diff') },
|
|
8776
|
+
const diffPane = patch === '' && patchLoading === false ? null : E('div', { className: 'dig-diff-pane', 'data-pane': 'diff', style: paneStyle('diff'), onContextMenu: onDiffPaneContextMenu },
|
|
7942
8777
|
E('div', { className: 'dig-diff-head' },
|
|
7943
8778
|
E('span', { className: 'dig-mono dig-diff-path' }, selectedPath === null ? '' : selectedPath),
|
|
7944
|
-
E('button', { type: 'button', className: 'dig-icon-btn', onClick: () => { setPatch(''); setSelectedPath(null) } }, E(Icon, { name: 'close', size: 12 }))),
|
|
8779
|
+
E('button', { type: 'button', className: 'dig-icon-btn', onClick: () => { setPatch(''); setSelectedPath(null); setDiffContext(null) } }, E(Icon, { name: 'close', size: 12 }))),
|
|
7945
8780
|
E(DiffBody, { patch: patch, loading: patchLoading, t: t }))
|
|
7946
8781
|
|
|
7947
8782
|
/* ---------- rail (IDE-style left action strip) ---------- */
|
|
@@ -7976,7 +8811,7 @@ window.__ModuleLoader__.load({
|
|
|
7976
8811
|
['untracked', changes.untracked, false],
|
|
7977
8812
|
]
|
|
7978
8813
|
for (const entry of order) {
|
|
7979
|
-
if (entry[1].length > 0) {
|
|
8814
|
+
if (entry[1].length > 0) { showDiff({ path: entry[1][0].path, staged: entry[2] }); return }
|
|
7980
8815
|
}
|
|
7981
8816
|
setNote(t('note.noChanges'))
|
|
7982
8817
|
}
|
|
@@ -8180,7 +9015,7 @@ window.__ModuleLoader__.load({
|
|
|
8180
9015
|
E('div', { className: 'dig-compact-bar' },
|
|
8181
9016
|
E(Segmented, {
|
|
8182
9017
|
value: view === 'detail' ? 'history' : view,
|
|
8183
|
-
onChange: (next) => { setView(next); setPatch('') },
|
|
9018
|
+
onChange: (next) => { setView(next); setPatch(''); setDiffContext(null) },
|
|
8184
9019
|
items: [
|
|
8185
9020
|
{ id: 'changes', label: fill(t('seg.changes'), { n: dirty }) },
|
|
8186
9021
|
{ id: 'history', label: t('seg.history') },
|
|
@@ -8657,6 +9492,37 @@ window.__ModuleLoader__.load({
|
|
|
8657
9492
|
'.dig-menu-danger,.dig-menu-danger .dig-menu-label{color:var(--dsw-alias-state-error-primary)}',
|
|
8658
9493
|
'.dig-menu-disabled{opacity:.38;cursor:default}',
|
|
8659
9494
|
'.dig-menu-sep{height:1px;margin:3px 6px;background:var(--dsw-alias-hairline,var(--dsw-alias-border-l1))}',
|
|
9495
|
+
/* ---- immersive conversation seat (issue #8 round 3) ---- */
|
|
9496
|
+
/* The seat itself carries `data-conversation-composer-overlay` (the
|
|
9497
|
+
host's own opt-in, what TrajectoryView rides): the host CSS flips the
|
|
9498
|
+
view area to `flex: 1 1 0; min-height: 0; overflow: hidden` and the
|
|
9499
|
+
composer seat to an overlay. What the host does NOT do is hide the
|
|
9500
|
+
composer — that is ours, and it is a single structural rule patterned
|
|
9501
|
+
after dsh-context's `.lc-root` rule: while a Git view owns the
|
|
9502
|
+
conversation ring, the composer seat displays none. The :not() chain
|
|
9503
|
+
is the SAFETY VALVE and its attribute names are verified against the
|
|
9504
|
+
host sources (ui-approval ApprovalPanel `data-approval-key`,
|
|
9505
|
+
ui-user-questions QuestionComposer `data-question-key`, PlanReviewPanel
|
|
9506
|
+
`data-plan-review-key`): a blocking interaction riding the seat must
|
|
9507
|
+
stay reachable even though the plain input card is hidden, or a user
|
|
9508
|
+
sitting in the Git view could never approve anything. */
|
|
9509
|
+
'[data-conversation-scroll]:has(.dig-conversation-seat[data-immersive="1"]) > [data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}',
|
|
9510
|
+
/* The transcript-width drag strips are FOLLOWING SIBLINGS of the shared
|
|
9511
|
+
scroller (children of the conversation body), full-height and
|
|
9512
|
+
z-index 8: over a full-bleed Git pane they glow and drag straight
|
|
9513
|
+
through it. Same sibling combinator lesson dsh-context recorded in
|
|
9514
|
+
its own issue #50 — a descendant combinator here matches nothing. */
|
|
9515
|
+
'[data-conversation-scroll]:has(.dig-conversation-seat[data-immersive="1"]) ~ [data-width-handle]{display:none}',
|
|
9516
|
+
/* ---- native preview tab (external diff preview) ---- */
|
|
9517
|
+
/* The body mounts straight into the right-sidebar pane: full height,
|
|
9518
|
+
internal scrolling in the body region, a one-line header for the
|
|
9519
|
+
subject/path. Same tokens as the rest of the panel — no hard-coded
|
|
9520
|
+
surfaces. */
|
|
9521
|
+
'.dig-preview-root{position:relative;height:100%;display:flex;flex-direction:column;min-height:0;background:var(--dsw-alias-bg-base)}',
|
|
9522
|
+
'.dig-preview-head{flex:none;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-l1));font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
9523
|
+
'.dig-preview-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
9524
|
+
'.dig-preview-body{flex:1;min-height:0;overflow:auto;padding:8px 12px;display:flex;flex-direction:column}',
|
|
9525
|
+
'.dig-preview-body .dig-diff-host,.dig-preview-body .dig-diff{flex:1;min-height:0}',
|
|
8660
9526
|
].join('\n')
|
|
8661
9527
|
|
|
8662
9528
|
/* ============================== plugin ============================== */
|
|
@@ -8672,6 +9538,34 @@ window.__ModuleLoader__.load({
|
|
|
8672
9538
|
Chat (0) and Trajectory (10), and after dsh-context's tab (20) when both
|
|
8673
9539
|
are installed. */
|
|
8674
9540
|
const CONVERSATION_VIEW_ID = 'ide-git'
|
|
9541
|
+
/* The native preview tab (external diff preview, 2026-10-08): a SECOND
|
|
9542
|
+
native right-sidebar type that exists only to show one diff. It carries
|
|
9543
|
+
no guide entry on purpose — nothing on the start page opens it; it opens
|
|
9544
|
+
itself when a diff verb fires with no better-sidebar to hand off to.
|
|
9545
|
+
Content follows the module-level store below, so every seat (the
|
|
9546
|
+
conversation tab, the native panel) feeds the same single preview. */
|
|
9547
|
+
const NATIVE_PREVIEW_ID = 'dsh-ide-git-preview'
|
|
9548
|
+
const NATIVE_PREVIEW_KIND = 'ide-git-preview'
|
|
9549
|
+
|
|
9550
|
+
/* The one diff seed the native preview tab shows. set() bumps a monotonous
|
|
9551
|
+
counter so a re-click of the SAME file still re-fetches; subscribers get
|
|
9552
|
+
the barest notify and re-read .seed themselves. Memory-only: after a
|
|
9553
|
+
reload the tab (if the layout restored it) shows the empty hint again —
|
|
9554
|
+
the next click re-seeds it. */
|
|
9555
|
+
const previewStore = {
|
|
9556
|
+
seed: null,
|
|
9557
|
+
listeners: new Set(),
|
|
9558
|
+
set(seed) {
|
|
9559
|
+
this.seed = seed
|
|
9560
|
+
for (const listener of [...this.listeners]) {
|
|
9561
|
+
try { listener() } catch (error) { void error }
|
|
9562
|
+
}
|
|
9563
|
+
},
|
|
9564
|
+
subscribe(listener) {
|
|
9565
|
+
this.listeners.add(listener)
|
|
9566
|
+
return () => { this.listeners.delete(listener) }
|
|
9567
|
+
},
|
|
9568
|
+
}
|
|
8675
9569
|
|
|
8676
9570
|
/* The native seat hands a tab the standard props; the panel wants
|
|
8677
9571
|
`{ scope, t }`. A session's working directory is the path of the
|
|
@@ -8701,7 +9595,7 @@ window.__ModuleLoader__.load({
|
|
|
8701
9595
|
const openSettings = navigation !== undefined && navigation !== null && typeof navigation.openBundle === 'function'
|
|
8702
9596
|
? () => { navigation.openBundle(SETTINGS_BUNDLE) }
|
|
8703
9597
|
: null
|
|
8704
|
-
return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings, ctx: props.ctx })
|
|
9598
|
+
return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings, ctx: props.ctx, openDiffExternal: props.openDiffExternal })
|
|
8705
9599
|
}
|
|
8706
9600
|
|
|
8707
9601
|
/* The conversation-area seat (issue #8): the same panel as a view tab
|
|
@@ -8739,6 +9633,21 @@ window.__ModuleLoader__.load({
|
|
|
8739
9633
|
const ctx = props.ctx
|
|
8740
9634
|
const sessionId = typeof props.sessionId === 'string' ? props.sessionId : ''
|
|
8741
9635
|
const [cwd, setCwd] = useState(() => sessionCwdOf(ctx, sessionId))
|
|
9636
|
+
/* Immersive mode (issue #8 round 3): when on, the seat declares the
|
|
9637
|
+
host's composer-overlay contract (`data-conversation-composer-overlay`)
|
|
9638
|
+
and the conversation view area goes `flex: 1 1 0; min-height: 0;
|
|
9639
|
+
overflow: hidden` — the panel fills the WHOLE conversation height and
|
|
9640
|
+
scrolls inside itself, with the composer seat hidden away (CSS keeps
|
|
9641
|
+
the blocking approvals visible). The v0.13.2 pin-to-scrollport
|
|
9642
|
+
machinery below is the FALLBACK for immersive-off: it exists because
|
|
9643
|
+
the active view area is `flex: 1 0 auto` (content height) in the
|
|
9644
|
+
normal layout, which is exactly what the overlay contract replaces. */
|
|
9645
|
+
const [immersive, setImmersive] = useState(() => conversationImmersiveEnabled())
|
|
9646
|
+
useEffect(() => {
|
|
9647
|
+
const sync = () => { setImmersive(conversationImmersiveEnabled()) }
|
|
9648
|
+
sync()
|
|
9649
|
+
return subscribeRailConfig(sync)
|
|
9650
|
+
}, [])
|
|
8742
9651
|
/* The active conversation's view area is `flex: 1 0 auto; min-height:
|
|
8743
9652
|
auto` — it GROWS with content and scrolls inside the shared
|
|
8744
9653
|
conversation scrollport. An auto-height panel there measured
|
|
@@ -8753,6 +9662,12 @@ window.__ModuleLoader__.load({
|
|
|
8753
9662
|
useEffect(() => {
|
|
8754
9663
|
const el = seatRef.current
|
|
8755
9664
|
if (el === null) return undefined
|
|
9665
|
+
/* Immersive ON: the host's overlay layout owns this seat's height
|
|
9666
|
+
(the view area is the flex child now) — no pinning, no convergence,
|
|
9667
|
+
none of the scrollport arithmetic below applies. Toggling the
|
|
9668
|
+
setting re-runs this effect through the dependency below, so the
|
|
9669
|
+
fallback machinery spins back up on the way off. */
|
|
9670
|
+
if (immersive === true) return undefined
|
|
8756
9671
|
/* Leftover wheel scroll, root cause: the composer seat is sticky, but a
|
|
8757
9672
|
sticky box still occupies FLOW space inside the scrollport, so even
|
|
8758
9673
|
with the panel seat pinned to the visible height the scrollHeight
|
|
@@ -8837,7 +9752,7 @@ window.__ModuleLoader__.load({
|
|
|
8837
9752
|
if (observer !== undefined) observer.disconnect()
|
|
8838
9753
|
window.removeEventListener('resize', measure)
|
|
8839
9754
|
}
|
|
8840
|
-
}, [])
|
|
9755
|
+
}, [immersive])
|
|
8841
9756
|
useEffect(() => {
|
|
8842
9757
|
let alive = true
|
|
8843
9758
|
const sync = () => { if (alive === true) setCwd(sessionCwdOf(ctx, sessionId)) }
|
|
@@ -8861,11 +9776,20 @@ window.__ModuleLoader__.load({
|
|
|
8861
9776
|
}
|
|
8862
9777
|
}, [ctx, sessionId])
|
|
8863
9778
|
const scope = useMemo(() => ({ cwd: cwd, sessionId: sessionId }), [cwd, sessionId])
|
|
8864
|
-
|
|
9779
|
+
/* The overlay marker rides the SEAT element (a viewArea descendant): the
|
|
9780
|
+
host's `.scrollBody:has([data-conversation-composer-overlay])` rules
|
|
9781
|
+
pick it up from anywhere inside, flipping the view area to
|
|
9782
|
+
`flex: 1 1 0; min-height: 0; overflow: hidden` (full-height, internal
|
|
9783
|
+
scrolling) and the composer seat to an overlay. `data-immersive`
|
|
9784
|
+
drives the plugin's own CSS (composer hide + width-handle hide). */
|
|
9785
|
+
const immersiveAttrs = immersive === true
|
|
9786
|
+
? { 'data-conversation-composer-overlay': '', 'data-immersive': '1' }
|
|
9787
|
+
: {}
|
|
9788
|
+
return E('div', Object.assign({
|
|
8865
9789
|
ref: seatRef,
|
|
8866
9790
|
className: 'dig-conversation-seat',
|
|
8867
|
-
style: seatHeight === null ? undefined : { height: seatHeight + 'px' },
|
|
8868
|
-
}, E(LocaleLive, { ctx: ctx, scope: scope, t: props.t, visible: true }))
|
|
9791
|
+
style: immersive === true ? { height: '100%' } : (seatHeight === null ? undefined : { height: seatHeight + 'px' }),
|
|
9792
|
+
}, immersiveAttrs), E(LocaleLive, { ctx: ctx, scope: scope, t: props.t, visible: true }))
|
|
8869
9793
|
}, (prev, next) => prev.sessionId === next.sessionId && prev.t === next.t && prev.ctx === next.ctx)
|
|
8870
9794
|
|
|
8871
9795
|
function NativePanel(props) {
|
|
@@ -8890,6 +9814,55 @@ window.__ModuleLoader__.load({
|
|
|
8890
9814
|
return E(LocaleLive, { ctx: props.ctx, scope: scope, t: props.t, visible: true, dock: dock })
|
|
8891
9815
|
}
|
|
8892
9816
|
|
|
9817
|
+
/* The native preview tab's body (external diff preview): shows exactly one
|
|
9818
|
+
diff — whatever the store holds — fetched fresh from the host route on
|
|
9819
|
+
every seed. The seed carries cwd + repoRoot so a multi-root workspace
|
|
9820
|
+
lands on the right repository (the route requires cwd; repoRoot skips
|
|
9821
|
+
the rev-parse). Rendered through the SAME DiffBody the inline pane uses,
|
|
9822
|
+
so host DiffBlock reuse, the hand-drawn fallback and the binary/empty
|
|
9823
|
+
states are all identical. */
|
|
9824
|
+
function NativePreviewPanel(props) {
|
|
9825
|
+
const t = props.t
|
|
9826
|
+
const [seed, setSeed] = useState(() => previewStore.seed)
|
|
9827
|
+
useEffect(() => previewStore.subscribe(() => { setSeed(previewStore.seed) }), [])
|
|
9828
|
+
const seedKey = seed === null
|
|
9829
|
+
? ''
|
|
9830
|
+
: (seed.hash !== undefined && seed.hash !== null ? 'c:' + String(seed.hash) : 'w:' + String(seed.staged === true) + ':' + String(seed.path))
|
|
9831
|
+
const [state, setState] = useState({ loading: false, patch: '', error: null })
|
|
9832
|
+
useEffect(() => {
|
|
9833
|
+
if (seed === null) return undefined
|
|
9834
|
+
let alive = true
|
|
9835
|
+
setState({ loading: true, patch: '', error: null })
|
|
9836
|
+
void (async () => {
|
|
9837
|
+
try {
|
|
9838
|
+
const payload = { cwd: seed.cwd }
|
|
9839
|
+
if (seed.repoRoot !== undefined && seed.repoRoot !== null && seed.repoRoot !== '') payload.repoRoot = seed.repoRoot
|
|
9840
|
+
if (seed.hash !== undefined && seed.hash !== null && seed.hash !== '') payload.hash = seed.hash
|
|
9841
|
+
else { payload.path = seed.path; payload.staged = seed.staged === true }
|
|
9842
|
+
const data = await request('diff', payload)
|
|
9843
|
+
if (alive === true) setState({ loading: false, patch: data === undefined || data === null || typeof data.patch !== 'string' ? '' : data.patch, error: null })
|
|
9844
|
+
} catch (error) {
|
|
9845
|
+
if (alive === true) setState({ loading: false, patch: '', error: error instanceof Error ? error.message : String(error) })
|
|
9846
|
+
}
|
|
9847
|
+
})()
|
|
9848
|
+
return () => { alive = false }
|
|
9849
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
9850
|
+
}, [seedKey])
|
|
9851
|
+
const title = seed === null
|
|
9852
|
+
? ''
|
|
9853
|
+
: (seed.hash !== undefined && seed.hash !== null
|
|
9854
|
+
? String(seed.hash).slice(0, 7) + (seed.subject ? ' ' + seed.subject : '')
|
|
9855
|
+
: String(seed.path))
|
|
9856
|
+
return E('div', { className: 'dig-preview-root' },
|
|
9857
|
+
E('div', { className: 'dig-preview-head' },
|
|
9858
|
+
E('span', { className: 'dig-preview-title', title: title }, title === '' ? t('preview.title') : title)),
|
|
9859
|
+
seed === null
|
|
9860
|
+
? E('div', { className: 'dig-preview-body' }, E('div', { className: 'dig-empty' }, t('preview.hint')))
|
|
9861
|
+
: state.error !== null
|
|
9862
|
+
? E('div', { className: 'dig-preview-body' }, E('div', { className: 'dig-empty' }, state.error))
|
|
9863
|
+
: E('div', { className: 'dig-preview-body' }, E(DiffBody, { patch: state.patch, loading: state.loading, binary: false, t: t })))
|
|
9864
|
+
}
|
|
9865
|
+
|
|
8893
9866
|
function apply(ctx) {
|
|
8894
9867
|
const style = document.createElement('style')
|
|
8895
9868
|
style.setAttribute('data-dsh-ide-git', '')
|
|
@@ -8953,7 +9926,24 @@ window.__ModuleLoader__.load({
|
|
|
8953
9926
|
{ name: 'sidebar.right.pane.tab', key: NATIVE_ID },
|
|
8954
9927
|
(tabProps) => E(NativePanel, Object.assign({}, tabProps, { t: t, ctx: ctx })),
|
|
8955
9928
|
))
|
|
9929
|
+
/* The preview type has NO guide entry on purpose (start-page
|
|
9930
|
+
destinations are the main panel's job): it exists to receive the
|
|
9931
|
+
openTab this plugin fires when a diff verb runs without a
|
|
9932
|
+
better-sidebar to hand off to. Omitting `guide` is the registry's
|
|
9933
|
+
documented way to stay off the guide page. */
|
|
9934
|
+
const offPreviewType = tabs.register({
|
|
9935
|
+
id: NATIVE_PREVIEW_ID,
|
|
9936
|
+
kind: NATIVE_PREVIEW_KIND,
|
|
9937
|
+
title: () => t('preview.title'),
|
|
9938
|
+
keepMounted: true,
|
|
9939
|
+
})
|
|
9940
|
+
const offPreviewBody = slots.inject('sidebar.right.pane.tab', () => slots.register(
|
|
9941
|
+
{ name: 'sidebar.right.pane.tab', key: NATIVE_PREVIEW_ID },
|
|
9942
|
+
(tabProps) => E(NativePreviewPanel, Object.assign({}, tabProps, { t: t, ctx: ctx })),
|
|
9943
|
+
))
|
|
8956
9944
|
return () => {
|
|
9945
|
+
try { offPreviewBody() } catch (error) { void error }
|
|
9946
|
+
try { offPreviewType() } catch (error) { void error }
|
|
8957
9947
|
try { offBody() } catch (error) { void error }
|
|
8958
9948
|
try { offType() } catch (error) { void error }
|
|
8959
9949
|
}
|
|
@@ -8986,6 +9976,13 @@ window.__ModuleLoader__.load({
|
|
|
8986
9976
|
scope: tabProps.scope,
|
|
8987
9977
|
t: t,
|
|
8988
9978
|
visible: tabProps.visible,
|
|
9979
|
+
/* The base's own diff-tab hand-off (OpenTabSeed type 'diff').
|
|
9980
|
+
Where the diff tab lands — bottom workbench vs right column —
|
|
9981
|
+
is the base's store decision, made where the click happened:
|
|
9982
|
+
exactly the "preview beside this panel" behavior the seat
|
|
9983
|
+
should have. Absent on an older base; previewDiff falls back
|
|
9984
|
+
to the service openTab, then to the inline pane. */
|
|
9985
|
+
openDiffExternal: typeof tabProps.onOpenDiff === 'function' ? tabProps.onOpenDiff : undefined,
|
|
8989
9986
|
}),
|
|
8990
9987
|
})
|
|
8991
9988
|
/* The base can go away at RUNTIME (disabled in the plugin panel, or
|