dsh-ide-git 0.7.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/package.json +3 -2
- package/src/client.js +1882 -29
- package/src/index.js +260 -0
package/src/client.js
CHANGED
|
@@ -38,6 +38,9 @@ window.__ModuleLoader__.load({
|
|
|
38
38
|
would escape the mount and 404 behind any strict proxy. */
|
|
39
39
|
const API_BASE = 'dsh-ide-git/api'
|
|
40
40
|
const TAB_ID = 'dsh-ide-git:panel'
|
|
41
|
+
/* The bundle's package name: the key the Plugins page uses for a bundle's own
|
|
42
|
+
page and its `plugins.bundle.config` slot. */
|
|
43
|
+
const SETTINGS_BUNDLE = 'dsh-ide-git'
|
|
41
44
|
/* The namespace this plugin's dictionaries are published under in the DSH
|
|
42
45
|
locale registry. */
|
|
43
46
|
const LOCALE_NS = 'dsh-ide-git'
|
|
@@ -115,6 +118,11 @@ window.__ModuleLoader__.load({
|
|
|
115
118
|
'action.showDiff': '显示差异',
|
|
116
119
|
'action.copyPath': '复制路径',
|
|
117
120
|
'action.checkout': '签出',
|
|
121
|
+
'action.float': '浮动本页',
|
|
122
|
+
'action.dock': '收回停靠',
|
|
123
|
+
'action.split': '分栏',
|
|
124
|
+
'action.fullscreen': '全屏',
|
|
125
|
+
'action.exitFullscreen': '退出全屏',
|
|
118
126
|
'action.newBranchFrom': '从该分支新建分支...',
|
|
119
127
|
'action.newBranchHere': '在此新建分支...',
|
|
120
128
|
'action.rename': '重命名...',
|
|
@@ -138,6 +146,8 @@ window.__ModuleLoader__.load({
|
|
|
138
146
|
'detail.back': '返回历史',
|
|
139
147
|
'detail.noFiles': '没有文件变更',
|
|
140
148
|
'diff.empty': '没有可显示的文本差异',
|
|
149
|
+
'pane.diff': '差异视图',
|
|
150
|
+
'pane.resize': '拖动调整{name}尺寸,双击复位,方向键微调',
|
|
141
151
|
'diff.binary': '二进制文件',
|
|
142
152
|
'diff.loading': '正在加载差异...',
|
|
143
153
|
'toolbar.refresh': '刷新',
|
|
@@ -148,6 +158,28 @@ window.__ModuleLoader__.load({
|
|
|
148
158
|
'toolbar.tree': '分支栏',
|
|
149
159
|
'rail.more': '更多操作',
|
|
150
160
|
'rail.settings': '动作条设置',
|
|
161
|
+
'rail.openSettings': '打开插件设置',
|
|
162
|
+
'settings.rail.title': '动作条按钮',
|
|
163
|
+
'settings.rail.hint': '勾选要显示在动作条上的动作;顺序固定为默认顺序,取消勾选即隐藏。',
|
|
164
|
+
'settings.rail.unavailable': '当前宿主没有设置服务,请用面板内的「动作条设置」。',
|
|
165
|
+
'settings.commit.title': 'AI 提交信息',
|
|
166
|
+
'settings.commit.model': '使用的模型',
|
|
167
|
+
'settings.commit.modelHint': '留空 = 跟随当前会话(provider/model)',
|
|
168
|
+
'settings.commit.reasoning': '思考强度',
|
|
169
|
+
'settings.commit.reasoningHint': '留空 = 模型默认',
|
|
170
|
+
'settings.commit.prompt': '补充提示词',
|
|
171
|
+
'settings.commit.promptHint': '例如:用 Conventional Commits,subject 用中文,不超过 72 字符',
|
|
172
|
+
'settings.commit.tooLong': '超过 {n} 个字符的部分不会保存',
|
|
173
|
+
'changes.aiWrite': 'AI 写提交信息',
|
|
174
|
+
'changes.aiBusy': '正在生成…',
|
|
175
|
+
'changes.aiQuota': '会消耗你的额度',
|
|
176
|
+
'changes.aiOverwriteTitle': '覆盖当前草稿?',
|
|
177
|
+
'changes.aiOverwriteText': 'AI 生成的内容会替换你已经输入的提交信息(可以撤销)。',
|
|
178
|
+
'changes.aiUndo': '撤销,恢复我的草稿',
|
|
179
|
+
'changes.aiFailed': '生成失败:',
|
|
180
|
+
'changes.aiNotice': '部分变更没有发给模型(已截断或丢弃),信息可能不完整。',
|
|
181
|
+
'settings.rail.writeFailed': '设置没写进去,已退回原状态;请重试。',
|
|
182
|
+
'settings.rail.readonly': '当前宿主把配置设为只读,开关不可修改。',
|
|
151
183
|
'rail.settingsHint': '拖动或用箭头调整顺序,眼睛图标控制显示',
|
|
152
184
|
'rail.reset': '恢复默认',
|
|
153
185
|
'rail.up': '上移',
|
|
@@ -269,6 +301,11 @@ window.__ModuleLoader__.load({
|
|
|
269
301
|
'action.showDiff': 'Show diff',
|
|
270
302
|
'action.copyPath': 'Copy path',
|
|
271
303
|
'action.checkout': 'Checkout',
|
|
304
|
+
'action.float': 'Float this page',
|
|
305
|
+
'action.dock': 'Dock back',
|
|
306
|
+
'action.split': 'Split pane',
|
|
307
|
+
'action.fullscreen': 'Full screen',
|
|
308
|
+
'action.exitFullscreen': 'Exit full screen',
|
|
272
309
|
'action.newBranchFrom': 'New branch from here...',
|
|
273
310
|
'action.newBranchHere': 'New branch here...',
|
|
274
311
|
'action.rename': 'Rename...',
|
|
@@ -292,6 +329,8 @@ window.__ModuleLoader__.load({
|
|
|
292
329
|
'detail.back': 'Back to history',
|
|
293
330
|
'detail.noFiles': 'No file changes',
|
|
294
331
|
'diff.empty': 'No textual diff to show',
|
|
332
|
+
'pane.diff': 'diff view',
|
|
333
|
+
'pane.resize': 'Drag to resize {name}: double-click resets, arrow keys nudge',
|
|
295
334
|
'diff.binary': 'Binary file',
|
|
296
335
|
'diff.loading': 'Loading diff...',
|
|
297
336
|
'toolbar.refresh': 'Refresh',
|
|
@@ -302,6 +341,28 @@ window.__ModuleLoader__.load({
|
|
|
302
341
|
'toolbar.tree': 'Branch pane',
|
|
303
342
|
'rail.more': 'More actions',
|
|
304
343
|
'rail.settings': 'Action bar settings',
|
|
344
|
+
'rail.openSettings': 'Open plugin settings',
|
|
345
|
+
'settings.rail.title': 'Action rail buttons',
|
|
346
|
+
'settings.rail.hint': 'Tick the actions shown on the rail. The order is fixed; unticking one hides it.',
|
|
347
|
+
'settings.rail.unavailable': 'This host has no settings service; use the in-panel “Action rail settings”.',
|
|
348
|
+
'settings.commit.title': 'AI commit message',
|
|
349
|
+
'settings.commit.model': 'Model',
|
|
350
|
+
'settings.commit.modelHint': 'Empty = follow this session (provider/model)',
|
|
351
|
+
'settings.commit.reasoning': 'Reasoning effort',
|
|
352
|
+
'settings.commit.reasoningHint': 'Empty = the model\'s default',
|
|
353
|
+
'settings.commit.prompt': 'Extra instructions',
|
|
354
|
+
'settings.commit.promptHint': 'e.g. Conventional Commits, Chinese subject, at most 72 characters',
|
|
355
|
+
'settings.commit.tooLong': 'Only the first {n} characters are saved',
|
|
356
|
+
'changes.aiWrite': 'Write with AI',
|
|
357
|
+
'changes.aiBusy': 'Writing…',
|
|
358
|
+
'changes.aiQuota': 'Uses your own quota',
|
|
359
|
+
'changes.aiOverwriteTitle': 'Replace the current draft?',
|
|
360
|
+
'changes.aiOverwriteText': 'The AI text will replace the commit message you typed (you can undo it).',
|
|
361
|
+
'changes.aiUndo': 'Undo, bring my draft back',
|
|
362
|
+
'changes.aiFailed': 'Could not write it:',
|
|
363
|
+
'changes.aiNotice': 'Some changes were not sent to the model (truncated or dropped); the message may be incomplete.',
|
|
364
|
+
'settings.rail.writeFailed': 'The setting did not save; the switch went back. Try again.',
|
|
365
|
+
'settings.rail.readonly': 'This host serves the configuration read-only.',
|
|
305
366
|
'rail.settingsHint': 'Drag or use the arrows to reorder; the eye toggles visibility',
|
|
306
367
|
'rail.reset': 'Restore defaults',
|
|
307
368
|
'rail.up': 'Move up',
|
|
@@ -431,6 +492,11 @@ window.__ModuleLoader__.load({
|
|
|
431
492
|
'action.showDiff': '顯示差異',
|
|
432
493
|
'action.copyPath': '複製路徑',
|
|
433
494
|
'action.checkout': '簽出',
|
|
495
|
+
'action.float': '浮動本頁',
|
|
496
|
+
'action.dock': '收回停靠',
|
|
497
|
+
'action.split': '分欄',
|
|
498
|
+
'action.fullscreen': '全螢幕',
|
|
499
|
+
'action.exitFullscreen': '結束全螢幕',
|
|
434
500
|
'action.newBranchFrom': '由此新建分支...',
|
|
435
501
|
'action.newBranchHere': '喺呢度新建分支...',
|
|
436
502
|
'action.rename': '重新命名...',
|
|
@@ -454,6 +520,8 @@ window.__ModuleLoader__.load({
|
|
|
454
520
|
'detail.back': '返回歷史',
|
|
455
521
|
'detail.noFiles': '冇檔案變更',
|
|
456
522
|
'diff.empty': '冇文字差異可以顯示',
|
|
523
|
+
'pane.diff': '差異檢視',
|
|
524
|
+
'pane.resize': '拖動調整{name}尺寸,雙擊復位,方向鍵微調',
|
|
457
525
|
'diff.binary': '二進位檔案',
|
|
458
526
|
'diff.loading': '正在載入差異...',
|
|
459
527
|
'toolbar.refresh': '重新整理',
|
|
@@ -464,6 +532,28 @@ window.__ModuleLoader__.load({
|
|
|
464
532
|
'toolbar.tree': '分支面板',
|
|
465
533
|
'rail.more': '更多操作',
|
|
466
534
|
'rail.settings': '動作列設定',
|
|
535
|
+
'rail.openSettings': '開啟外掛設定',
|
|
536
|
+
'settings.rail.title': '動作列按鈕',
|
|
537
|
+
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
538
|
+
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
539
|
+
'settings.commit.title': 'AI 提交訊息',
|
|
540
|
+
'settings.commit.model': '使用嘅模型',
|
|
541
|
+
'settings.commit.modelHint': '留空 = 跟隨當前對話(provider/model)',
|
|
542
|
+
'settings.commit.reasoning': '思考強度',
|
|
543
|
+
'settings.commit.reasoningHint': '留空 = 模型預設',
|
|
544
|
+
'settings.commit.prompt': '補充提示詞',
|
|
545
|
+
'settings.commit.promptHint': '例如:用 Conventional Commits,subject 用中文,唔超過 72 字',
|
|
546
|
+
'settings.commit.tooLong': '超過 {n} 個字嘅部分唔會儲存',
|
|
547
|
+
'changes.aiWrite': 'AI 寫提交訊息',
|
|
548
|
+
'changes.aiBusy': '生成緊…',
|
|
549
|
+
'changes.aiQuota': '會消耗你嘅額度',
|
|
550
|
+
'changes.aiOverwriteTitle': '覆蓋當前草稿?',
|
|
551
|
+
'changes.aiOverwriteText': 'AI 生成嘅內容會換走你打咗嘅提交訊息(可以撤銷)。',
|
|
552
|
+
'changes.aiUndo': '撤銷,還原我嘅草稿',
|
|
553
|
+
'changes.aiFailed': '生成失敗:',
|
|
554
|
+
'changes.aiNotice': '有部分變更冇發俾模型(已截斷或丟棄),訊息可能唔完整。',
|
|
555
|
+
'settings.rail.writeFailed': '設定寫唔入,已經退回原狀;請再試。',
|
|
556
|
+
'settings.rail.readonly': '目前宿主將設定設為唯讀,開關改唔到。',
|
|
467
557
|
'rail.settingsHint': '拖曳或用箭嘴排序;眼睛圖示切換顯示',
|
|
468
558
|
'rail.reset': '還原預設',
|
|
469
559
|
'rail.up': '上移',
|
|
@@ -585,6 +675,11 @@ window.__ModuleLoader__.load({
|
|
|
585
675
|
'action.showDiff': '顯示差異',
|
|
586
676
|
'action.copyPath': '複製路徑',
|
|
587
677
|
'action.checkout': '簽出',
|
|
678
|
+
'action.float': '浮動本頁',
|
|
679
|
+
'action.dock': '收回停靠',
|
|
680
|
+
'action.split': '分割欄',
|
|
681
|
+
'action.fullscreen': '全螢幕',
|
|
682
|
+
'action.exitFullscreen': '結束全螢幕',
|
|
588
683
|
'action.newBranchFrom': '由此新建分支...',
|
|
589
684
|
'action.newBranchHere': '在此新建分支...',
|
|
590
685
|
'action.rename': '重新命名...',
|
|
@@ -608,6 +703,8 @@ window.__ModuleLoader__.load({
|
|
|
608
703
|
'detail.back': '返回歷史',
|
|
609
704
|
'detail.noFiles': '沒有檔案變更',
|
|
610
705
|
'diff.empty': '沒有文字差異可顯示',
|
|
706
|
+
'pane.diff': '差異檢視',
|
|
707
|
+
'pane.resize': '拖曳調整{name}尺寸,雙擊重設,方向鍵微調',
|
|
611
708
|
'diff.binary': '二進位檔案',
|
|
612
709
|
'diff.loading': '正在載入差異...',
|
|
613
710
|
'toolbar.refresh': '重新整理',
|
|
@@ -618,6 +715,28 @@ window.__ModuleLoader__.load({
|
|
|
618
715
|
'toolbar.tree': '分支面板',
|
|
619
716
|
'rail.more': '更多操作',
|
|
620
717
|
'rail.settings': '動作列設定',
|
|
718
|
+
'rail.openSettings': '開啟外掛設定',
|
|
719
|
+
'settings.rail.title': '動作列按鈕',
|
|
720
|
+
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
721
|
+
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
722
|
+
'settings.commit.title': 'AI 提交訊息',
|
|
723
|
+
'settings.commit.model': '使用的模型',
|
|
724
|
+
'settings.commit.modelHint': '留空 = 跟隨目前工作階段(provider/model)',
|
|
725
|
+
'settings.commit.reasoning': '思考強度',
|
|
726
|
+
'settings.commit.reasoningHint': '留空 = 模型預設',
|
|
727
|
+
'settings.commit.prompt': '補充提示詞',
|
|
728
|
+
'settings.commit.promptHint': '例如:用 Conventional Commits,subject 用中文,不超過 72 字元',
|
|
729
|
+
'settings.commit.tooLong': '超過 {n} 個字元的部分不會儲存',
|
|
730
|
+
'changes.aiWrite': 'AI 寫提交訊息',
|
|
731
|
+
'changes.aiBusy': '正在生成…',
|
|
732
|
+
'changes.aiQuota': '會消耗你的額度',
|
|
733
|
+
'changes.aiOverwriteTitle': '覆蓋目前的草稿?',
|
|
734
|
+
'changes.aiOverwriteText': 'AI 生成的內容會取代你已輸入的提交訊息(可以復原)。',
|
|
735
|
+
'changes.aiUndo': '復原,還原我的草稿',
|
|
736
|
+
'changes.aiFailed': '生成失敗:',
|
|
737
|
+
'changes.aiNotice': '部分變更沒有傳給模型(已截斷或捨棄),訊息可能不完整。',
|
|
738
|
+
'settings.rail.writeFailed': '設定沒有寫入,已退回原狀態;請重試。',
|
|
739
|
+
'settings.rail.readonly': '目前宿主將設定設為唯讀,開關無法修改。',
|
|
621
740
|
'rail.settingsHint': '拖曳或用箭頭排序;眼睛圖示切換顯示',
|
|
622
741
|
'rail.reset': '還原預設',
|
|
623
742
|
'rail.up': '上移',
|
|
@@ -739,6 +858,11 @@ window.__ModuleLoader__.load({
|
|
|
739
858
|
'action.showDiff': '顯示差異',
|
|
740
859
|
'action.copyPath': '複製路徑',
|
|
741
860
|
'action.checkout': '簽出',
|
|
861
|
+
'action.float': '浮動本頁',
|
|
862
|
+
'action.dock': '收回停靠',
|
|
863
|
+
'action.split': '分欄',
|
|
864
|
+
'action.fullscreen': '全螢幕',
|
|
865
|
+
'action.exitFullscreen': '結束全螢幕',
|
|
742
866
|
'action.newBranchFrom': '由此新建分支...',
|
|
743
867
|
'action.newBranchHere': '喺呢度新建分支...',
|
|
744
868
|
'action.rename': '重新命名...',
|
|
@@ -762,6 +886,8 @@ window.__ModuleLoader__.load({
|
|
|
762
886
|
'detail.back': '返回歷史',
|
|
763
887
|
'detail.noFiles': '冇檔案變更',
|
|
764
888
|
'diff.empty': '冇文字差異可以顯示',
|
|
889
|
+
'pane.diff': '差異檢視',
|
|
890
|
+
'pane.resize': '拖動調整{name}尺寸,雙擊復位,方向鍵微調',
|
|
765
891
|
'diff.binary': '二進位檔案',
|
|
766
892
|
'diff.loading': '正在載入差異...',
|
|
767
893
|
'toolbar.refresh': '重新整理',
|
|
@@ -772,6 +898,28 @@ window.__ModuleLoader__.load({
|
|
|
772
898
|
'toolbar.tree': '分支面板',
|
|
773
899
|
'rail.more': '更多操作',
|
|
774
900
|
'rail.settings': '動作列設定',
|
|
901
|
+
'rail.openSettings': '開啟外掛設定',
|
|
902
|
+
'settings.rail.title': '動作列按鈕',
|
|
903
|
+
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
904
|
+
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
905
|
+
'settings.commit.title': 'AI 提交訊息',
|
|
906
|
+
'settings.commit.model': '使用的模型',
|
|
907
|
+
'settings.commit.modelHint': '留空 = 跟隨目前工作階段(provider/model)',
|
|
908
|
+
'settings.commit.reasoning': '思考強度',
|
|
909
|
+
'settings.commit.reasoningHint': '留空 = 模型預設',
|
|
910
|
+
'settings.commit.prompt': '補充提示詞',
|
|
911
|
+
'settings.commit.promptHint': '例如:用 Conventional Commits,subject 用中文,不超過 72 字元',
|
|
912
|
+
'settings.commit.tooLong': '超過 {n} 個字元的部分不會儲存',
|
|
913
|
+
'changes.aiWrite': 'AI 寫提交訊息',
|
|
914
|
+
'changes.aiBusy': '正在生成…',
|
|
915
|
+
'changes.aiQuota': '會消耗你的額度',
|
|
916
|
+
'changes.aiOverwriteTitle': '覆蓋目前的草稿?',
|
|
917
|
+
'changes.aiOverwriteText': 'AI 生成的內容會取代你已輸入的提交訊息(可以復原)。',
|
|
918
|
+
'changes.aiUndo': '復原,還原我的草稿',
|
|
919
|
+
'changes.aiFailed': '生成失敗:',
|
|
920
|
+
'changes.aiNotice': '部分變更沒有傳給模型(已截斷或捨棄),訊息可能不完整。',
|
|
921
|
+
'settings.rail.writeFailed': '設定沒有寫入,已退回原狀態;請重試。',
|
|
922
|
+
'settings.rail.readonly': '目前宿主將設定設為唯讀,開關無法修改。',
|
|
775
923
|
'rail.settingsHint': '拖曳或用箭嘴排序;眼睛圖示切換顯示',
|
|
776
924
|
'rail.reset': '還原預設',
|
|
777
925
|
'rail.up': '上移',
|
|
@@ -893,6 +1041,11 @@ window.__ModuleLoader__.load({
|
|
|
893
1041
|
'action.showDiff': '差分を表示',
|
|
894
1042
|
'action.copyPath': 'パスをコピー',
|
|
895
1043
|
'action.checkout': 'チェックアウト',
|
|
1044
|
+
'action.float': 'このページを浮動表示',
|
|
1045
|
+
'action.dock': 'ドックに戻す',
|
|
1046
|
+
'action.split': '分割',
|
|
1047
|
+
'action.fullscreen': '全画面',
|
|
1048
|
+
'action.exitFullscreen': '全画面を終了',
|
|
896
1049
|
'action.newBranchFrom': 'ここから新しいブランチ...',
|
|
897
1050
|
'action.newBranchHere': 'ここに新しいブランチ...',
|
|
898
1051
|
'action.rename': '名前を変更...',
|
|
@@ -916,6 +1069,8 @@ window.__ModuleLoader__.load({
|
|
|
916
1069
|
'detail.back': '履歴に戻る',
|
|
917
1070
|
'detail.noFiles': 'ファイルの変更はありません',
|
|
918
1071
|
'diff.empty': '表示できるテキスト差分がありません',
|
|
1072
|
+
'pane.diff': '差分ビュー',
|
|
1073
|
+
'pane.resize': '{name}のサイズをドラッグで調整(ダブルクリックでリセット、矢印キーで微調整)',
|
|
919
1074
|
'diff.binary': 'バイナリファイル',
|
|
920
1075
|
'diff.loading': '差分を読み込み中...',
|
|
921
1076
|
'toolbar.refresh': '更新',
|
|
@@ -926,6 +1081,28 @@ window.__ModuleLoader__.load({
|
|
|
926
1081
|
'toolbar.tree': 'ブランチペイン',
|
|
927
1082
|
'rail.more': 'その他の操作',
|
|
928
1083
|
'rail.settings': 'アクションバーの設定',
|
|
1084
|
+
'rail.openSettings': 'プラグイン設定を開く',
|
|
1085
|
+
'settings.rail.title': 'アクションレールのボタン',
|
|
1086
|
+
'settings.rail.hint': 'レールに表示する操作にチェックを入れてください。順序は固定で、チェックを外すと非表示になります。',
|
|
1087
|
+
'settings.rail.unavailable': 'このホストには設定サービスがないため、パネル内の「アクションレール設定」を使ってください。',
|
|
1088
|
+
'settings.commit.title': 'AI コミットメッセージ',
|
|
1089
|
+
'settings.commit.model': '使用するモデル',
|
|
1090
|
+
'settings.commit.modelHint': '空欄 = 現在のセッションに従う(provider/model)',
|
|
1091
|
+
'settings.commit.reasoning': '思考の強度',
|
|
1092
|
+
'settings.commit.reasoningHint': '空欄 = モデルの既定値',
|
|
1093
|
+
'settings.commit.prompt': '追加の指示',
|
|
1094
|
+
'settings.commit.promptHint': '例:Conventional Commits、subject は日本語、72 文字以内',
|
|
1095
|
+
'settings.commit.tooLong': '{n} 文字を超える部分は保存されません',
|
|
1096
|
+
'changes.aiWrite': 'AI で書く',
|
|
1097
|
+
'changes.aiBusy': '生成中…',
|
|
1098
|
+
'changes.aiQuota': 'あなたの利用枠を消費します',
|
|
1099
|
+
'changes.aiOverwriteTitle': '現在の下書きを置き換えますか?',
|
|
1100
|
+
'changes.aiOverwriteText': 'AI の文章が入力済みのコミットメッセージを置き換えます(元に戻せます)。',
|
|
1101
|
+
'changes.aiUndo': '元に戻す',
|
|
1102
|
+
'changes.aiFailed': '生成できませんでした:',
|
|
1103
|
+
'changes.aiNotice': '一部の変更はモデルに送られていません(切り詰めまたは除外)。内容が不完全な可能性があります。',
|
|
1104
|
+
'settings.rail.writeFailed': '設定を保存できませんでした。元の状態に戻しました。もう一度お試しください。',
|
|
1105
|
+
'settings.rail.readonly': 'このホストは設定を読み取り専用で提供しています。',
|
|
929
1106
|
'rail.settingsHint': 'ドラッグまたは矢印で並べ替え、目のアイコンで表示を切り替え',
|
|
930
1107
|
'rail.reset': '既定に戻す',
|
|
931
1108
|
'rail.up': '上へ',
|
|
@@ -1047,6 +1224,11 @@ window.__ModuleLoader__.load({
|
|
|
1047
1224
|
'action.showDiff': '차이 보기',
|
|
1048
1225
|
'action.copyPath': '경로 복사',
|
|
1049
1226
|
'action.checkout': '체크아웃',
|
|
1227
|
+
'action.float': '이 페이지 띄우기',
|
|
1228
|
+
'action.dock': '도킹으로 복귀',
|
|
1229
|
+
'action.split': '분할',
|
|
1230
|
+
'action.fullscreen': '전체 화면',
|
|
1231
|
+
'action.exitFullscreen': '전체 화면 종료',
|
|
1050
1232
|
'action.newBranchFrom': '여기서 새 브랜치...',
|
|
1051
1233
|
'action.newBranchHere': '여기에 새 브랜치...',
|
|
1052
1234
|
'action.rename': '이름 바꾸기...',
|
|
@@ -1070,6 +1252,8 @@ window.__ModuleLoader__.load({
|
|
|
1070
1252
|
'detail.back': '기록으로 돌아가기',
|
|
1071
1253
|
'detail.noFiles': '파일 변경 없음',
|
|
1072
1254
|
'diff.empty': '표시할 텍스트 차이가 없습니다',
|
|
1255
|
+
'pane.diff': '차이 보기',
|
|
1256
|
+
'pane.resize': '{name} 크기를 드래그해 조절(더블클릭 초기화, 화살표 키 미세 조정)',
|
|
1073
1257
|
'diff.binary': '바이너리 파일',
|
|
1074
1258
|
'diff.loading': '차이 불러오는 중...',
|
|
1075
1259
|
'toolbar.refresh': '새로 고침',
|
|
@@ -1080,6 +1264,28 @@ window.__ModuleLoader__.load({
|
|
|
1080
1264
|
'toolbar.tree': '브랜치 창',
|
|
1081
1265
|
'rail.more': '추가 작업',
|
|
1082
1266
|
'rail.settings': '작업 표시줄 설정',
|
|
1267
|
+
'rail.openSettings': '플러그인 설정 열기',
|
|
1268
|
+
'settings.rail.title': '액션 레일 버튼',
|
|
1269
|
+
'settings.rail.hint': '레일에 표시할 동작을 선택하세요. 순서는 고정이며 선택을 해제하면 숨겨집니다.',
|
|
1270
|
+
'settings.rail.unavailable': '이 호스트에는 설정 서비스가 없으므로 패널의 "액션 레일 설정"을 사용하세요.',
|
|
1271
|
+
'settings.commit.title': 'AI 커밋 메시지',
|
|
1272
|
+
'settings.commit.model': '사용할 모델',
|
|
1273
|
+
'settings.commit.modelHint': '비우면 현재 세션을 따릅니다(provider/model)',
|
|
1274
|
+
'settings.commit.reasoning': '추론 강도',
|
|
1275
|
+
'settings.commit.reasoningHint': '비우면 모델 기본값',
|
|
1276
|
+
'settings.commit.prompt': '추가 지시',
|
|
1277
|
+
'settings.commit.promptHint': '예: Conventional Commits, 제목은 한국어, 72자 이내',
|
|
1278
|
+
'settings.commit.tooLong': '{n}자를 넘는 부분은 저장되지 않습니다',
|
|
1279
|
+
'changes.aiWrite': 'AI로 작성',
|
|
1280
|
+
'changes.aiBusy': '생성 중…',
|
|
1281
|
+
'changes.aiQuota': '사용자의 할당량을 소모합니다',
|
|
1282
|
+
'changes.aiOverwriteTitle': '현재 초안을 바꿀까요?',
|
|
1283
|
+
'changes.aiOverwriteText': 'AI 텍스트가 입력한 커밋 메시지를 대체합니다(되돌릴 수 있습니다).',
|
|
1284
|
+
'changes.aiUndo': '되돌리기',
|
|
1285
|
+
'changes.aiFailed': '생성하지 못했습니다:',
|
|
1286
|
+
'changes.aiNotice': '일부 변경이 모델에 전달되지 않았습니다(잘림 또는 제외). 내용이 불완전할 수 있습니다.',
|
|
1287
|
+
'settings.rail.writeFailed': '설정이 저장되지 않아 원래 상태로 되돌렸습니다. 다시 시도하세요.',
|
|
1288
|
+
'settings.rail.readonly': '이 호스트는 설정을 읽기 전용으로 제공합니다.',
|
|
1083
1289
|
'rail.settingsHint': '끌어서 또는 화살표로 순서를 바꾸고, 눈 아이콘으로 표시를 전환합니다',
|
|
1084
1290
|
'rail.reset': '기본값 복원',
|
|
1085
1291
|
'rail.up': '위로',
|
|
@@ -1201,6 +1407,11 @@ window.__ModuleLoader__.load({
|
|
|
1201
1407
|
'action.showDiff': 'Diff anzeigen',
|
|
1202
1408
|
'action.copyPath': 'Pfad kopieren',
|
|
1203
1409
|
'action.checkout': 'Auschecken',
|
|
1410
|
+
'action.float': 'Diese Seite schweben lassen',
|
|
1411
|
+
'action.dock': 'Zurück andocken',
|
|
1412
|
+
'action.split': 'Teilen',
|
|
1413
|
+
'action.fullscreen': 'Vollbild',
|
|
1414
|
+
'action.exitFullscreen': 'Vollbild beenden',
|
|
1204
1415
|
'action.newBranchFrom': 'Neuer Branch von hier...',
|
|
1205
1416
|
'action.newBranchHere': 'Neuer Branch hier...',
|
|
1206
1417
|
'action.rename': 'Umbenennen...',
|
|
@@ -1224,6 +1435,8 @@ window.__ModuleLoader__.load({
|
|
|
1224
1435
|
'detail.back': 'Zurück zum Verlauf',
|
|
1225
1436
|
'detail.noFiles': 'Keine Dateiänderungen',
|
|
1226
1437
|
'diff.empty': 'Kein textueller Diff vorhanden',
|
|
1438
|
+
'pane.diff': 'Diff-Ansicht',
|
|
1439
|
+
'pane.resize': 'Größe von {name} per Ziehen ändern (Doppelklick setzt zurück, Pfeiltasten justieren)',
|
|
1227
1440
|
'diff.binary': 'Binärdatei',
|
|
1228
1441
|
'diff.loading': 'Diff wird geladen...',
|
|
1229
1442
|
'toolbar.refresh': 'Aktualisieren',
|
|
@@ -1234,6 +1447,28 @@ window.__ModuleLoader__.load({
|
|
|
1234
1447
|
'toolbar.tree': 'Branch-Bereich',
|
|
1235
1448
|
'rail.more': 'Weitere Aktionen',
|
|
1236
1449
|
'rail.settings': 'Einstellungen der Aktionsleiste',
|
|
1450
|
+
'rail.openSettings': 'Plugin-Einstellungen öffnen',
|
|
1451
|
+
'settings.rail.title': 'Schaltflächen der Aktionsleiste',
|
|
1452
|
+
'settings.rail.hint': 'Die auf der Leiste sichtbaren Aktionen ankreuzen. Die Reihenfolge ist fest; abwählen blendet eine aus.',
|
|
1453
|
+
'settings.rail.unavailable': 'Dieser Host hat keinen Einstellungsdienst; nutze die „Aktionsleisten-Einstellungen“ im Panel.',
|
|
1454
|
+
'settings.commit.title': 'KI-Commit-Nachricht',
|
|
1455
|
+
'settings.commit.model': 'Modell',
|
|
1456
|
+
'settings.commit.modelHint': 'Leer = dieser Sitzung folgen (provider/model)',
|
|
1457
|
+
'settings.commit.reasoning': 'Denkaufwand',
|
|
1458
|
+
'settings.commit.reasoningHint': 'Leer = Standard des Modells',
|
|
1459
|
+
'settings.commit.prompt': 'Zusätzliche Anweisungen',
|
|
1460
|
+
'settings.commit.promptHint': 'z. B. Conventional Commits, Betreff auf Deutsch, höchstens 72 Zeichen',
|
|
1461
|
+
'settings.commit.tooLong': 'Nur die ersten {n} Zeichen werden gespeichert',
|
|
1462
|
+
'changes.aiWrite': 'Mit KI schreiben',
|
|
1463
|
+
'changes.aiBusy': 'Wird geschrieben…',
|
|
1464
|
+
'changes.aiQuota': 'Verbraucht dein eigenes Kontingent',
|
|
1465
|
+
'changes.aiOverwriteTitle': 'Aktuellen Entwurf ersetzen?',
|
|
1466
|
+
'changes.aiOverwriteText': 'Der KI-Text ersetzt deine eingegebene Commit-Nachricht (rückgängig möglich).',
|
|
1467
|
+
'changes.aiUndo': 'Rückgängig',
|
|
1468
|
+
'changes.aiFailed': 'Konnte nicht schreiben:',
|
|
1469
|
+
'changes.aiNotice': 'Einige Änderungen wurden nicht ans Modell gesendet (gekürzt oder verworfen); die Nachricht kann unvollständig sein.',
|
|
1470
|
+
'settings.rail.writeFailed': 'Die Einstellung wurde nicht gespeichert; der Schalter ist zurückgesprungen. Bitte erneut versuchen.',
|
|
1471
|
+
'settings.rail.readonly': 'Dieser Host liefert die Konfiguration schreibgeschützt.',
|
|
1237
1472
|
'rail.settingsHint': 'Ziehen oder Pfeile zum Sortieren, das Auge schaltet die Sichtbarkeit um',
|
|
1238
1473
|
'rail.reset': 'Standard wiederherstellen',
|
|
1239
1474
|
'rail.up': 'Nach oben',
|
|
@@ -1355,6 +1590,11 @@ window.__ModuleLoader__.load({
|
|
|
1355
1590
|
'action.showDiff': 'Afficher le diff',
|
|
1356
1591
|
'action.copyPath': 'Copier le chemin',
|
|
1357
1592
|
'action.checkout': 'Extraire',
|
|
1593
|
+
'action.float': 'Détacher cette page',
|
|
1594
|
+
'action.dock': 'Réancrer',
|
|
1595
|
+
'action.split': 'Fractionner',
|
|
1596
|
+
'action.fullscreen': 'Plein écran',
|
|
1597
|
+
'action.exitFullscreen': 'Quitter le plein écran',
|
|
1358
1598
|
'action.newBranchFrom': 'Nouvelle branche depuis ici...',
|
|
1359
1599
|
'action.newBranchHere': 'Nouvelle branche ici...',
|
|
1360
1600
|
'action.rename': 'Renommer...',
|
|
@@ -1378,6 +1618,8 @@ window.__ModuleLoader__.load({
|
|
|
1378
1618
|
'detail.back': 'Retour à l\'historique',
|
|
1379
1619
|
'detail.noFiles': 'Aucune modification de fichier',
|
|
1380
1620
|
'diff.empty': 'Aucun diff textuel à afficher',
|
|
1621
|
+
'pane.diff': 'vue du diff',
|
|
1622
|
+
'pane.resize': 'Glisser pour redimensionner {name} (double-clic pour réinitialiser, flèches pour ajuster)',
|
|
1381
1623
|
'diff.binary': 'Fichier binaire',
|
|
1382
1624
|
'diff.loading': 'Chargement du diff...',
|
|
1383
1625
|
'toolbar.refresh': 'Rafraîchir',
|
|
@@ -1388,6 +1630,28 @@ window.__ModuleLoader__.load({
|
|
|
1388
1630
|
'toolbar.tree': 'Volet des branches',
|
|
1389
1631
|
'rail.more': 'Plus d\'actions',
|
|
1390
1632
|
'rail.settings': 'Réglages de la barre d\'actions',
|
|
1633
|
+
'rail.openSettings': 'Ouvrir les réglages du plugin',
|
|
1634
|
+
'settings.rail.title': 'Boutons de la barre d’actions',
|
|
1635
|
+
'settings.rail.hint': 'Cochez les actions affichées sur la barre. L’ordre est fixe ; décocher masque une action.',
|
|
1636
|
+
'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.',
|
|
1637
|
+
'settings.commit.title': 'Message de commit par IA',
|
|
1638
|
+
'settings.commit.model': 'Modèle',
|
|
1639
|
+
'settings.commit.modelHint': 'Vide = suivre cette session (provider/model)',
|
|
1640
|
+
'settings.commit.reasoning': 'Effort de raisonnement',
|
|
1641
|
+
'settings.commit.reasoningHint': 'Vide = valeur par défaut du modèle',
|
|
1642
|
+
'settings.commit.prompt': 'Instructions supplémentaires',
|
|
1643
|
+
'settings.commit.promptHint': 'ex. Conventional Commits, sujet en français, 72 caractères maximum',
|
|
1644
|
+
'settings.commit.tooLong': 'Seuls les {n} premiers caractères sont enregistrés',
|
|
1645
|
+
'changes.aiWrite': 'Écrire avec l’IA',
|
|
1646
|
+
'changes.aiBusy': 'Rédaction…',
|
|
1647
|
+
'changes.aiQuota': 'Consomme votre propre quota',
|
|
1648
|
+
'changes.aiOverwriteTitle': 'Remplacer le brouillon actuel ?',
|
|
1649
|
+
'changes.aiOverwriteText': 'Le texte de l’IA remplacera le message saisi (annulable).',
|
|
1650
|
+
'changes.aiUndo': 'Annuler, reprendre mon brouillon',
|
|
1651
|
+
'changes.aiFailed': 'Échec de la rédaction :',
|
|
1652
|
+
'changes.aiNotice': 'Certaines modifications n’ont pas été envoyées au modèle (tronquées ou écartées) ; le message peut être incomplet.',
|
|
1653
|
+
'settings.rail.writeFailed': 'Le réglage n’a pas été enregistré ; l’interrupteur est revenu en place. Réessayez.',
|
|
1654
|
+
'settings.rail.readonly': 'Cet hôte fournit la configuration en lecture seule.',
|
|
1391
1655
|
'rail.settingsHint': 'Glisser ou utiliser les flèches pour réordonner ; l\'œil bascule la visibilité',
|
|
1392
1656
|
'rail.reset': 'Rétablir les valeurs par défaut',
|
|
1393
1657
|
'rail.up': 'Monter',
|
|
@@ -1509,6 +1773,11 @@ window.__ModuleLoader__.load({
|
|
|
1509
1773
|
'action.showDiff': 'Показать различия',
|
|
1510
1774
|
'action.copyPath': 'Скопировать путь',
|
|
1511
1775
|
'action.checkout': 'Переключиться',
|
|
1776
|
+
'action.float': 'Открепить страницу',
|
|
1777
|
+
'action.dock': 'Вернуть в док',
|
|
1778
|
+
'action.split': 'Разделить',
|
|
1779
|
+
'action.fullscreen': 'Полный экран',
|
|
1780
|
+
'action.exitFullscreen': 'Выйти из полного экрана',
|
|
1512
1781
|
'action.newBranchFrom': 'Новая ветка отсюда...',
|
|
1513
1782
|
'action.newBranchHere': 'Новая ветка здесь...',
|
|
1514
1783
|
'action.rename': 'Переименовать...',
|
|
@@ -1532,6 +1801,8 @@ window.__ModuleLoader__.load({
|
|
|
1532
1801
|
'detail.back': 'Назад к истории',
|
|
1533
1802
|
'detail.noFiles': 'Файлы не изменялись',
|
|
1534
1803
|
'diff.empty': 'Нет текстовых различий для показа',
|
|
1804
|
+
'pane.diff': 'просмотр diff',
|
|
1805
|
+
'pane.resize': 'Перетащите, чтобы изменить размер {name} (двойной клик — сброс, стрелки — точная настройка)',
|
|
1535
1806
|
'diff.binary': 'Двоичный файл',
|
|
1536
1807
|
'diff.loading': 'Загрузка различий...',
|
|
1537
1808
|
'toolbar.refresh': 'Обновить',
|
|
@@ -1542,6 +1813,28 @@ window.__ModuleLoader__.load({
|
|
|
1542
1813
|
'toolbar.tree': 'Панель веток',
|
|
1543
1814
|
'rail.more': 'Другие действия',
|
|
1544
1815
|
'rail.settings': 'Настройки панели действий',
|
|
1816
|
+
'rail.openSettings': 'Открыть настройки плагина',
|
|
1817
|
+
'settings.rail.title': 'Кнопки панели действий',
|
|
1818
|
+
'settings.rail.hint': 'Отметьте действия, показываемые на панели. Порядок фиксирован; снятие флажка скрывает действие.',
|
|
1819
|
+
'settings.rail.unavailable': 'На этом хосте нет службы настроек; используйте «Настройки панели действий» в панели.',
|
|
1820
|
+
'settings.commit.title': 'Сообщение коммита от ИИ',
|
|
1821
|
+
'settings.commit.model': 'Модель',
|
|
1822
|
+
'settings.commit.modelHint': 'Пусто = как в этой сессии (provider/model)',
|
|
1823
|
+
'settings.commit.reasoning': 'Усилие рассуждения',
|
|
1824
|
+
'settings.commit.reasoningHint': 'Пусто = по умолчанию у модели',
|
|
1825
|
+
'settings.commit.prompt': 'Дополнительные указания',
|
|
1826
|
+
'settings.commit.promptHint': 'напр. Conventional Commits, тема по-русски, до 72 символов',
|
|
1827
|
+
'settings.commit.tooLong': 'Сохраняются только первые {n} символов',
|
|
1828
|
+
'changes.aiWrite': 'Написать с ИИ',
|
|
1829
|
+
'changes.aiBusy': 'Генерация…',
|
|
1830
|
+
'changes.aiQuota': 'Расходует вашу квоту',
|
|
1831
|
+
'changes.aiOverwriteTitle': 'Заменить текущий черновик?',
|
|
1832
|
+
'changes.aiOverwriteText': 'Текст ИИ заменит введённое сообщение коммита (можно отменить).',
|
|
1833
|
+
'changes.aiUndo': 'Отменить',
|
|
1834
|
+
'changes.aiFailed': 'Не удалось сгенерировать:',
|
|
1835
|
+
'changes.aiNotice': 'Часть изменений не отправлена модели (обрезана или отброшена); сообщение может быть неполным.',
|
|
1836
|
+
'settings.rail.writeFailed': 'Настройка не сохранилась; переключатель вернулся назад. Попробуйте снова.',
|
|
1837
|
+
'settings.rail.readonly': 'Этот хост отдаёт конфигурацию только для чтения.',
|
|
1545
1838
|
'rail.settingsHint': 'Перетаскивайте или используйте стрелки для порядка; значок глаза переключает видимость',
|
|
1546
1839
|
'rail.reset': 'Вернуть значения по умолчанию',
|
|
1547
1840
|
'rail.up': 'Вверх',
|
|
@@ -1663,6 +1956,11 @@ window.__ModuleLoader__.load({
|
|
|
1663
1956
|
'action.showDiff': 'Mostrar diff',
|
|
1664
1957
|
'action.copyPath': 'Copiar caminho',
|
|
1665
1958
|
'action.checkout': 'Checkout',
|
|
1959
|
+
'action.float': 'Flutuar esta página',
|
|
1960
|
+
'action.dock': 'Reacoplar',
|
|
1961
|
+
'action.split': 'Dividir',
|
|
1962
|
+
'action.fullscreen': 'Tela cheia',
|
|
1963
|
+
'action.exitFullscreen': 'Sair da tela cheia',
|
|
1666
1964
|
'action.newBranchFrom': 'Nova branch a partir daqui...',
|
|
1667
1965
|
'action.newBranchHere': 'Nova branch aqui...',
|
|
1668
1966
|
'action.rename': 'Renomear...',
|
|
@@ -1686,6 +1984,8 @@ window.__ModuleLoader__.load({
|
|
|
1686
1984
|
'detail.back': 'Voltar ao histórico',
|
|
1687
1985
|
'detail.noFiles': 'Nenhuma alteração de arquivo',
|
|
1688
1986
|
'diff.empty': 'Nenhum diff textual para mostrar',
|
|
1987
|
+
'pane.diff': 'visualização do diff',
|
|
1988
|
+
'pane.resize': 'Arraste para redimensionar {name} (duplo clique restaura, setas ajustam)',
|
|
1689
1989
|
'diff.binary': 'Arquivo binário',
|
|
1690
1990
|
'diff.loading': 'Carregando diff...',
|
|
1691
1991
|
'toolbar.refresh': 'Atualizar',
|
|
@@ -1696,6 +1996,28 @@ window.__ModuleLoader__.load({
|
|
|
1696
1996
|
'toolbar.tree': 'Painel de branches',
|
|
1697
1997
|
'rail.more': 'Mais ações',
|
|
1698
1998
|
'rail.settings': 'Configurações da barra de ações',
|
|
1999
|
+
'rail.openSettings': 'Abrir configurações do plugin',
|
|
2000
|
+
'settings.rail.title': 'Botões da barra de ações',
|
|
2001
|
+
'settings.rail.hint': 'Marque as ações exibidas na barra. A ordem é fixa; desmarcar oculta uma ação.',
|
|
2002
|
+
'settings.rail.unavailable': 'Este host não tem serviço de configurações; use «Configurações da barra de ações» no painel.',
|
|
2003
|
+
'settings.commit.title': 'Mensagem de commit por IA',
|
|
2004
|
+
'settings.commit.model': 'Modelo',
|
|
2005
|
+
'settings.commit.modelHint': 'Vazio = seguir esta sessão (provider/model)',
|
|
2006
|
+
'settings.commit.reasoning': 'Esforço de raciocínio',
|
|
2007
|
+
'settings.commit.reasoningHint': 'Vazio = padrão do modelo',
|
|
2008
|
+
'settings.commit.prompt': 'Instruções extras',
|
|
2009
|
+
'settings.commit.promptHint': 'ex.: Conventional Commits, assunto em português, até 72 caracteres',
|
|
2010
|
+
'settings.commit.tooLong': 'Só os primeiros {n} caracteres são salvos',
|
|
2011
|
+
'changes.aiWrite': 'Escrever com IA',
|
|
2012
|
+
'changes.aiBusy': 'Gerando…',
|
|
2013
|
+
'changes.aiQuota': 'Consome a sua cota',
|
|
2014
|
+
'changes.aiOverwriteTitle': 'Substituir o rascunho atual?',
|
|
2015
|
+
'changes.aiOverwriteText': 'O texto da IA substituirá a mensagem que você digitou (dá para desfazer).',
|
|
2016
|
+
'changes.aiUndo': 'Desfazer',
|
|
2017
|
+
'changes.aiFailed': 'Não foi possível gerar:',
|
|
2018
|
+
'changes.aiNotice': 'Algumas alterações não foram enviadas ao modelo (truncadas ou descartadas); a mensagem pode ficar incompleta.',
|
|
2019
|
+
'settings.rail.writeFailed': 'A configuração não foi salva; o interruptor voltou ao lugar. Tente de novo.',
|
|
2020
|
+
'settings.rail.readonly': 'Este host fornece a configuração somente para leitura.',
|
|
1699
2021
|
'rail.settingsHint': 'Arraste ou use as setas para reordenar; o olho alterna a visibilidade',
|
|
1700
2022
|
'rail.reset': 'Restaurar padrões',
|
|
1701
2023
|
'rail.up': 'Mover para cima',
|
|
@@ -1817,6 +2139,11 @@ window.__ModuleLoader__.load({
|
|
|
1817
2139
|
'action.showDiff': 'Mostra il diff',
|
|
1818
2140
|
'action.copyPath': 'Copia percorso',
|
|
1819
2141
|
'action.checkout': 'Checkout',
|
|
2142
|
+
'action.float': 'Fluttua questa pagina',
|
|
2143
|
+
'action.dock': 'Riaggancia',
|
|
2144
|
+
'action.split': 'Dividi',
|
|
2145
|
+
'action.fullscreen': 'Schermo intero',
|
|
2146
|
+
'action.exitFullscreen': 'Esci da schermo intero',
|
|
1820
2147
|
'action.newBranchFrom': 'Nuovo branch da qui...',
|
|
1821
2148
|
'action.newBranchHere': 'Nuovo branch qui...',
|
|
1822
2149
|
'action.rename': 'Rinomina...',
|
|
@@ -1840,6 +2167,8 @@ window.__ModuleLoader__.load({
|
|
|
1840
2167
|
'detail.back': 'Torna alla cronologia',
|
|
1841
2168
|
'detail.noFiles': 'Nessuna modifica ai file',
|
|
1842
2169
|
'diff.empty': 'Nessun diff testuale da mostrare',
|
|
2170
|
+
'pane.diff': 'vista diff',
|
|
2171
|
+
'pane.resize': 'Trascina per ridimensionare {name} (doppio clic ripristina, frecce per regolare)',
|
|
1843
2172
|
'diff.binary': 'File binario',
|
|
1844
2173
|
'diff.loading': 'Caricamento del diff...',
|
|
1845
2174
|
'toolbar.refresh': 'Aggiorna',
|
|
@@ -1850,6 +2179,28 @@ window.__ModuleLoader__.load({
|
|
|
1850
2179
|
'toolbar.tree': 'Riquadro dei branch',
|
|
1851
2180
|
'rail.more': 'Altre azioni',
|
|
1852
2181
|
'rail.settings': 'Impostazioni della barra delle azioni',
|
|
2182
|
+
'rail.openSettings': 'Apri le impostazioni del plugin',
|
|
2183
|
+
'settings.rail.title': 'Pulsanti della barra azioni',
|
|
2184
|
+
'settings.rail.hint': 'Seleziona le azioni mostrate sulla barra. L’ordine è fisso; deselezionando se ne nasconde una.',
|
|
2185
|
+
'settings.rail.unavailable': 'Questo host non ha un servizio di impostazioni; usa «Impostazioni barra azioni» nel pannello.',
|
|
2186
|
+
'settings.commit.title': 'Messaggio di commit con IA',
|
|
2187
|
+
'settings.commit.model': 'Modello',
|
|
2188
|
+
'settings.commit.modelHint': 'Vuoto = segui questa sessione (provider/model)',
|
|
2189
|
+
'settings.commit.reasoning': 'Sforzo di ragionamento',
|
|
2190
|
+
'settings.commit.reasoningHint': 'Vuoto = predefinito del modello',
|
|
2191
|
+
'settings.commit.prompt': 'Istruzioni aggiuntive',
|
|
2192
|
+
'settings.commit.promptHint': 'es. Conventional Commits, oggetto in italiano, max 72 caratteri',
|
|
2193
|
+
'settings.commit.tooLong': 'Solo i primi {n} caratteri vengono salvati',
|
|
2194
|
+
'changes.aiWrite': 'Scrivi con l’IA',
|
|
2195
|
+
'changes.aiBusy': 'Generazione…',
|
|
2196
|
+
'changes.aiQuota': 'Consuma la tua quota',
|
|
2197
|
+
'changes.aiOverwriteTitle': 'Sostituire la bozza attuale?',
|
|
2198
|
+
'changes.aiOverwriteText': 'Il testo dell’IA sostituirà il messaggio digitato (puoi annullare).',
|
|
2199
|
+
'changes.aiUndo': 'Annulla',
|
|
2200
|
+
'changes.aiFailed': 'Scrittura non riuscita:',
|
|
2201
|
+
'changes.aiNotice': 'Alcune modifiche non sono state inviate al modello (troncate o scartate); il messaggio può essere incompleto.',
|
|
2202
|
+
'settings.rail.writeFailed': 'L’impostazione non è stata salvata; l’interruttore è tornato indietro. Riprova.',
|
|
2203
|
+
'settings.rail.readonly': 'Questo host fornisce la configurazione in sola lettura.',
|
|
1853
2204
|
'rail.settingsHint': 'Trascina o usa le frecce per riordinare; l’occhio attiva o disattiva la visibilità',
|
|
1854
2205
|
'rail.reset': 'Ripristina i valori predefiniti',
|
|
1855
2206
|
'rail.up': 'Sposta su',
|
|
@@ -1971,6 +2322,11 @@ window.__ModuleLoader__.load({
|
|
|
1971
2322
|
'action.showDiff': 'Diff tonen',
|
|
1972
2323
|
'action.copyPath': 'Pad kopiëren',
|
|
1973
2324
|
'action.checkout': 'Uitchecken',
|
|
2325
|
+
'action.float': 'Deze pagina losmaken',
|
|
2326
|
+
'action.dock': 'Terugdokken',
|
|
2327
|
+
'action.split': 'Splitsen',
|
|
2328
|
+
'action.fullscreen': 'Volledig scherm',
|
|
2329
|
+
'action.exitFullscreen': 'Volledig scherm sluiten',
|
|
1974
2330
|
'action.newBranchFrom': 'Nieuwe branch vanaf hier...',
|
|
1975
2331
|
'action.newBranchHere': 'Nieuwe branch hier...',
|
|
1976
2332
|
'action.rename': 'Hernoemen...',
|
|
@@ -1994,6 +2350,8 @@ window.__ModuleLoader__.load({
|
|
|
1994
2350
|
'detail.back': 'Terug naar geschiedenis',
|
|
1995
2351
|
'detail.noFiles': 'Geen bestandswijzigingen',
|
|
1996
2352
|
'diff.empty': 'Geen tekstuele diff om te tonen',
|
|
2353
|
+
'pane.diff': 'diff-weergave',
|
|
2354
|
+
'pane.resize': 'Sleep om {name} te vergroten (dubbelklik herstelt, pijltjes stellen fijn af)',
|
|
1997
2355
|
'diff.binary': 'Binair bestand',
|
|
1998
2356
|
'diff.loading': 'Diff laden...',
|
|
1999
2357
|
'toolbar.refresh': 'Vernieuwen',
|
|
@@ -2004,6 +2362,28 @@ window.__ModuleLoader__.load({
|
|
|
2004
2362
|
'toolbar.tree': 'Branchpaneel',
|
|
2005
2363
|
'rail.more': 'Meer acties',
|
|
2006
2364
|
'rail.settings': 'Instellingen van de actiebalk',
|
|
2365
|
+
'rail.openSettings': 'Plugin-instellingen openen',
|
|
2366
|
+
'settings.rail.title': 'Knoppen van de actiebalk',
|
|
2367
|
+
'settings.rail.hint': 'Vink de acties aan die op de balk verschijnen. De volgorde ligt vast; uitvinken verbergt er een.',
|
|
2368
|
+
'settings.rail.unavailable': 'Deze host heeft geen instellingenservice; gebruik "Actiebalk-instellingen" in het paneel.',
|
|
2369
|
+
'settings.commit.title': 'AI-commitbericht',
|
|
2370
|
+
'settings.commit.model': 'Model',
|
|
2371
|
+
'settings.commit.modelHint': 'Leeg = deze sessie volgen (provider/model)',
|
|
2372
|
+
'settings.commit.reasoning': 'Redeneerinspanning',
|
|
2373
|
+
'settings.commit.reasoningHint': 'Leeg = standaard van het model',
|
|
2374
|
+
'settings.commit.prompt': 'Extra instructies',
|
|
2375
|
+
'settings.commit.promptHint': 'bv. Conventional Commits, onderwerp in het Nederlands, max. 72 tekens',
|
|
2376
|
+
'settings.commit.tooLong': 'Alleen de eerste {n} tekens worden opgeslagen',
|
|
2377
|
+
'changes.aiWrite': 'Met AI schrijven',
|
|
2378
|
+
'changes.aiBusy': 'Bezig…',
|
|
2379
|
+
'changes.aiQuota': 'Verbruikt je eigen quota',
|
|
2380
|
+
'changes.aiOverwriteTitle': 'Huidig concept vervangen?',
|
|
2381
|
+
'changes.aiOverwriteText': 'De AI-tekst vervangt je getypte commitbericht (ongedaan te maken).',
|
|
2382
|
+
'changes.aiUndo': 'Ongedaan maken',
|
|
2383
|
+
'changes.aiFailed': 'Kon niet schrijven:',
|
|
2384
|
+
'changes.aiNotice': 'Sommige wijzigingen zijn niet naar het model gestuurd (afgekapt of overgeslagen); het bericht kan onvolledig zijn.',
|
|
2385
|
+
'settings.rail.writeFailed': 'De instelling is niet opgeslagen; de schakelaar sprong terug. Probeer opnieuw.',
|
|
2386
|
+
'settings.rail.readonly': 'Deze host levert de configuratie alleen-lezen.',
|
|
2007
2387
|
'rail.settingsHint': 'Sleep of gebruik de pijlen om te sorteren; het oog schakelt de zichtbaarheid',
|
|
2008
2388
|
'rail.reset': 'Standaardwaarden herstellen',
|
|
2009
2389
|
'rail.up': 'Omhoog',
|
|
@@ -2125,6 +2505,11 @@ window.__ModuleLoader__.load({
|
|
|
2125
2505
|
'action.showDiff': 'Pokaż różnice',
|
|
2126
2506
|
'action.copyPath': 'Kopiuj ścieżkę',
|
|
2127
2507
|
'action.checkout': 'Przełącz',
|
|
2508
|
+
'action.float': 'Odczep tę stronę',
|
|
2509
|
+
'action.dock': 'Zadokuj z powrotem',
|
|
2510
|
+
'action.split': 'Podziel',
|
|
2511
|
+
'action.fullscreen': 'Pełny ekran',
|
|
2512
|
+
'action.exitFullscreen': 'Wyjdź z pełnego ekranu',
|
|
2128
2513
|
'action.newBranchFrom': 'Nowa gałąź stąd...',
|
|
2129
2514
|
'action.newBranchHere': 'Nowa gałąź tutaj...',
|
|
2130
2515
|
'action.rename': 'Zmień nazwę...',
|
|
@@ -2148,6 +2533,8 @@ window.__ModuleLoader__.load({
|
|
|
2148
2533
|
'detail.back': 'Powrót do historii',
|
|
2149
2534
|
'detail.noFiles': 'Brak zmian w plikach',
|
|
2150
2535
|
'diff.empty': 'Brak różnic tekstowych do pokazania',
|
|
2536
|
+
'pane.diff': 'widok diff',
|
|
2537
|
+
'pane.resize': 'Przeciągnij, aby zmienić rozmiar {name} (podwójne kliknięcie przywraca, strzałki korygują)',
|
|
2151
2538
|
'diff.binary': 'Plik binarny',
|
|
2152
2539
|
'diff.loading': 'Wczytywanie różnic...',
|
|
2153
2540
|
'toolbar.refresh': 'Odśwież',
|
|
@@ -2158,6 +2545,28 @@ window.__ModuleLoader__.load({
|
|
|
2158
2545
|
'toolbar.tree': 'Panel gałęzi',
|
|
2159
2546
|
'rail.more': 'Więcej działań',
|
|
2160
2547
|
'rail.settings': 'Ustawienia paska działań',
|
|
2548
|
+
'rail.openSettings': 'Otwórz ustawienia wtyczki',
|
|
2549
|
+
'settings.rail.title': 'Przyciski paska akcji',
|
|
2550
|
+
'settings.rail.hint': 'Zaznacz akcje widoczne na pasku. Kolejność jest stała; odznaczenie ukrywa akcję.',
|
|
2551
|
+
'settings.rail.unavailable': 'Ten host nie ma usługi ustawień; użyj „Ustawienia paska akcji” w panelu.',
|
|
2552
|
+
'settings.commit.title': 'Wiadomość commita od AI',
|
|
2553
|
+
'settings.commit.model': 'Model',
|
|
2554
|
+
'settings.commit.modelHint': 'Puste = zgodnie z tą sesją (provider/model)',
|
|
2555
|
+
'settings.commit.reasoning': 'Poziom rozumowania',
|
|
2556
|
+
'settings.commit.reasoningHint': 'Puste = domyślne modelu',
|
|
2557
|
+
'settings.commit.prompt': 'Dodatkowe instrukcje',
|
|
2558
|
+
'settings.commit.promptHint': 'np. Conventional Commits, temat po polsku, maks. 72 znaki',
|
|
2559
|
+
'settings.commit.tooLong': 'Zapisywane jest tylko pierwszych {n} znaków',
|
|
2560
|
+
'changes.aiWrite': 'Napisz z AI',
|
|
2561
|
+
'changes.aiBusy': 'Generowanie…',
|
|
2562
|
+
'changes.aiQuota': 'Zużywa twój limit',
|
|
2563
|
+
'changes.aiOverwriteTitle': 'Zastąpić bieżący szkic?',
|
|
2564
|
+
'changes.aiOverwriteText': 'Tekst AI zastąpi wpisaną wiadomość commita (można cofnąć).',
|
|
2565
|
+
'changes.aiUndo': 'Cofnij',
|
|
2566
|
+
'changes.aiFailed': 'Nie udało się wygenerować:',
|
|
2567
|
+
'changes.aiNotice': 'Część zmian nie trafiła do modelu (przycięta lub pominięta); wiadomość może być niepełna.',
|
|
2568
|
+
'settings.rail.writeFailed': 'Ustawienie nie zostało zapisane; przełącznik wrócił na miejsce. Spróbuj ponownie.',
|
|
2569
|
+
'settings.rail.readonly': 'Ten host udostępnia konfigurację tylko do odczytu.',
|
|
2161
2570
|
'rail.settingsHint': 'Przeciągnij lub użyj strzałek, aby zmienić kolejność; oko przełącza widoczność',
|
|
2162
2571
|
'rail.reset': 'Przywróć domyślne',
|
|
2163
2572
|
'rail.up': 'W górę',
|
|
@@ -2279,6 +2688,11 @@ window.__ModuleLoader__.load({
|
|
|
2279
2688
|
'action.showDiff': 'Visa diff',
|
|
2280
2689
|
'action.copyPath': 'Kopiera sökväg',
|
|
2281
2690
|
'action.checkout': 'Checka ut',
|
|
2691
|
+
'action.float': 'Friställ sidan',
|
|
2692
|
+
'action.dock': 'Docka tillbaka',
|
|
2693
|
+
'action.split': 'Dela',
|
|
2694
|
+
'action.fullscreen': 'Helskärm',
|
|
2695
|
+
'action.exitFullscreen': 'Avsluta helskärm',
|
|
2282
2696
|
'action.newBranchFrom': 'Ny gren härifrån...',
|
|
2283
2697
|
'action.newBranchHere': 'Ny gren här...',
|
|
2284
2698
|
'action.rename': 'Byt namn...',
|
|
@@ -2302,6 +2716,8 @@ window.__ModuleLoader__.load({
|
|
|
2302
2716
|
'detail.back': 'Tillbaka till historiken',
|
|
2303
2717
|
'detail.noFiles': 'Inga filändringar',
|
|
2304
2718
|
'diff.empty': 'Ingen textdiff att visa',
|
|
2719
|
+
'pane.diff': 'diff-vy',
|
|
2720
|
+
'pane.resize': 'Dra för att ändra storleken på {name} (dubbelklick återställer, piltangenter finjusterar)',
|
|
2305
2721
|
'diff.binary': 'Binär fil',
|
|
2306
2722
|
'diff.loading': 'Laddar diff...',
|
|
2307
2723
|
'toolbar.refresh': 'Uppdatera',
|
|
@@ -2312,6 +2728,28 @@ window.__ModuleLoader__.load({
|
|
|
2312
2728
|
'toolbar.tree': 'Grenpanel',
|
|
2313
2729
|
'rail.more': 'Fler åtgärder',
|
|
2314
2730
|
'rail.settings': 'Inställningar för åtgärdsfältet',
|
|
2731
|
+
'rail.openSettings': 'Öppna plugininställningar',
|
|
2732
|
+
'settings.rail.title': 'Knappar i åtgärdsfältet',
|
|
2733
|
+
'settings.rail.hint': 'Markera de åtgärder som visas i fältet. Ordningen är fast; avmarkering döljer en.',
|
|
2734
|
+
'settings.rail.unavailable': 'Denna värd saknar inställningstjänst; använd ”Åtgärdsfältets inställningar” i panelen.',
|
|
2735
|
+
'settings.commit.title': 'AI-commitmeddelande',
|
|
2736
|
+
'settings.commit.model': 'Modell',
|
|
2737
|
+
'settings.commit.modelHint': 'Tomt = följ denna session (provider/model)',
|
|
2738
|
+
'settings.commit.reasoning': 'Resonemangsinsats',
|
|
2739
|
+
'settings.commit.reasoningHint': 'Tomt = modellens standard',
|
|
2740
|
+
'settings.commit.prompt': 'Extra instruktioner',
|
|
2741
|
+
'settings.commit.promptHint': 't.ex. Conventional Commits, ämne på svenska, högst 72 tecken',
|
|
2742
|
+
'settings.commit.tooLong': 'Endast de första {n} tecknen sparas',
|
|
2743
|
+
'changes.aiWrite': 'Skriv med AI',
|
|
2744
|
+
'changes.aiBusy': 'Skriver…',
|
|
2745
|
+
'changes.aiQuota': 'Använder din egen kvot',
|
|
2746
|
+
'changes.aiOverwriteTitle': 'Ersätt nuvarande utkast?',
|
|
2747
|
+
'changes.aiOverwriteText': 'AI-texten ersätter commitmeddelandet du skrev (går att ångra).',
|
|
2748
|
+
'changes.aiUndo': 'Ångra',
|
|
2749
|
+
'changes.aiFailed': 'Kunde inte skriva:',
|
|
2750
|
+
'changes.aiNotice': 'Vissa ändringar skickades inte till modellen (kapade eller utelämnade); meddelandet kan vara ofullständigt.',
|
|
2751
|
+
'settings.rail.writeFailed': 'Inställningen sparades inte; reglaget gick tillbaka. Försök igen.',
|
|
2752
|
+
'settings.rail.readonly': 'Denna värd tillhandahåller konfigurationen skrivskyddad.',
|
|
2315
2753
|
'rail.settingsHint': 'Dra eller använd pilarna för att sortera; ögat växlar synlighet',
|
|
2316
2754
|
'rail.reset': 'Återställ standard',
|
|
2317
2755
|
'rail.up': 'Flytta upp',
|
|
@@ -2433,6 +2871,11 @@ window.__ModuleLoader__.load({
|
|
|
2433
2871
|
'action.showDiff': 'Farkı göster',
|
|
2434
2872
|
'action.copyPath': 'Yolu kopyala',
|
|
2435
2873
|
'action.checkout': 'Çıkar',
|
|
2874
|
+
'action.float': 'Bu sayfayı yüzdür',
|
|
2875
|
+
'action.dock': 'Yuvaya geri al',
|
|
2876
|
+
'action.split': 'Böl',
|
|
2877
|
+
'action.fullscreen': 'Tam ekran',
|
|
2878
|
+
'action.exitFullscreen': 'Tam ekrandan çık',
|
|
2436
2879
|
'action.newBranchFrom': 'Buradan yeni dal...',
|
|
2437
2880
|
'action.newBranchHere': 'Burada yeni dal...',
|
|
2438
2881
|
'action.rename': 'Yeniden adlandır...',
|
|
@@ -2456,6 +2899,8 @@ window.__ModuleLoader__.load({
|
|
|
2456
2899
|
'detail.back': 'Geçmişe dön',
|
|
2457
2900
|
'detail.noFiles': 'Dosya değişikliği yok',
|
|
2458
2901
|
'diff.empty': 'Gösterilecek metin farkı yok',
|
|
2902
|
+
'pane.diff': 'diff görünümü',
|
|
2903
|
+
'pane.resize': '{name} boyutunu sürükleyerek ayarlayın (çift tıklama sıfırlar, ok tuşları ince ayar yapar)',
|
|
2459
2904
|
'diff.binary': 'İkili dosya',
|
|
2460
2905
|
'diff.loading': 'Fark yükleniyor...',
|
|
2461
2906
|
'toolbar.refresh': 'Yenile',
|
|
@@ -2466,6 +2911,28 @@ window.__ModuleLoader__.load({
|
|
|
2466
2911
|
'toolbar.tree': 'Dal paneli',
|
|
2467
2912
|
'rail.more': 'Diğer eylemler',
|
|
2468
2913
|
'rail.settings': 'Eylem çubuğu ayarları',
|
|
2914
|
+
'rail.openSettings': 'Eklenti ayarlarını aç',
|
|
2915
|
+
'settings.rail.title': 'Eylem çubuğu düğmeleri',
|
|
2916
|
+
'settings.rail.hint': 'Çubukta gösterilecek eylemleri işaretleyin. Sıra sabittir; işareti kaldırmak eylemi gizler.',
|
|
2917
|
+
'settings.rail.unavailable': 'Bu ana bilgisayarda ayar hizmeti yok; paneldeki "Eylem çubuğu ayarları"nı kullanın.',
|
|
2918
|
+
'settings.commit.title': 'AI commit mesajı',
|
|
2919
|
+
'settings.commit.model': 'Model',
|
|
2920
|
+
'settings.commit.modelHint': 'Boş = bu oturumu izle (provider/model)',
|
|
2921
|
+
'settings.commit.reasoning': 'Düşünme yoğunluğu',
|
|
2922
|
+
'settings.commit.reasoningHint': 'Boş = modelin varsayılanı',
|
|
2923
|
+
'settings.commit.prompt': 'Ek yönergeler',
|
|
2924
|
+
'settings.commit.promptHint': 'örn. Conventional Commits, konu Türkçe, en fazla 72 karakter',
|
|
2925
|
+
'settings.commit.tooLong': 'Yalnızca ilk {n} karakter kaydedilir',
|
|
2926
|
+
'changes.aiWrite': 'AI ile yaz',
|
|
2927
|
+
'changes.aiBusy': 'Yazılıyor…',
|
|
2928
|
+
'changes.aiQuota': 'Kendi kotanızı harcar',
|
|
2929
|
+
'changes.aiOverwriteTitle': 'Geçerli taslağın yerine geçilsin mi?',
|
|
2930
|
+
'changes.aiOverwriteText': 'AI metni yazdığınız commit mesajının yerine geçer (geri alınabilir).',
|
|
2931
|
+
'changes.aiUndo': 'Geri al',
|
|
2932
|
+
'changes.aiFailed': 'Yazılamadı:',
|
|
2933
|
+
'changes.aiNotice': 'Bazı değişiklikler modele gönderilmedi (kısaltıldı veya atlandı); mesaj eksik olabilir.',
|
|
2934
|
+
'settings.rail.writeFailed': 'Ayar kaydedilemedi; anahtar eski konumuna döndü. Yeniden deneyin.',
|
|
2935
|
+
'settings.rail.readonly': 'Bu ana bilgisayar yapılandırmayı salt okunur sunuyor.',
|
|
2469
2936
|
'rail.settingsHint': 'Sıralamak için sürükleyin veya okları kullanın; göz simgesi görünürlüğü değiştirir',
|
|
2470
2937
|
'rail.reset': 'Varsayılanlara dön',
|
|
2471
2938
|
'rail.up': 'Yukarı taşı',
|
|
@@ -2587,6 +3054,11 @@ window.__ModuleLoader__.load({
|
|
|
2587
3054
|
'action.showDiff': 'Tampilkan diff',
|
|
2588
3055
|
'action.copyPath': 'Salin jalur',
|
|
2589
3056
|
'action.checkout': 'Checkout',
|
|
3057
|
+
'action.float': 'Apungkan halaman ini',
|
|
3058
|
+
'action.dock': 'Kembalikan ke dok',
|
|
3059
|
+
'action.split': 'Bagi',
|
|
3060
|
+
'action.fullscreen': 'Layar penuh',
|
|
3061
|
+
'action.exitFullscreen': 'Keluar dari layar penuh',
|
|
2590
3062
|
'action.newBranchFrom': 'Branch baru dari sini...',
|
|
2591
3063
|
'action.newBranchHere': 'Branch baru di sini...',
|
|
2592
3064
|
'action.rename': 'Ganti nama...',
|
|
@@ -2610,6 +3082,8 @@ window.__ModuleLoader__.load({
|
|
|
2610
3082
|
'detail.back': 'Kembali ke riwayat',
|
|
2611
3083
|
'detail.noFiles': 'Tidak ada perubahan berkas',
|
|
2612
3084
|
'diff.empty': 'Tidak ada diff teks untuk ditampilkan',
|
|
3085
|
+
'pane.diff': 'tampilan diff',
|
|
3086
|
+
'pane.resize': 'Seret untuk mengubah ukuran {name} (klik ganda mengatur ulang, tombol panah menyesuaikan)',
|
|
2613
3087
|
'diff.binary': 'Berkas biner',
|
|
2614
3088
|
'diff.loading': 'Memuat diff...',
|
|
2615
3089
|
'toolbar.refresh': 'Segarkan',
|
|
@@ -2620,6 +3094,28 @@ window.__ModuleLoader__.load({
|
|
|
2620
3094
|
'toolbar.tree': 'Panel branch',
|
|
2621
3095
|
'rail.more': 'Tindakan lain',
|
|
2622
3096
|
'rail.settings': 'Pengaturan bilah tindakan',
|
|
3097
|
+
'rail.openSettings': 'Buka pengaturan plugin',
|
|
3098
|
+
'settings.rail.title': 'Tombol bilah aksi',
|
|
3099
|
+
'settings.rail.hint': 'Centang aksi yang ditampilkan di bilah. Urutannya tetap; menghapus centang menyembunyikannya.',
|
|
3100
|
+
'settings.rail.unavailable': 'Host ini tidak punya layanan pengaturan; gunakan "Pengaturan bilah aksi" di panel.',
|
|
3101
|
+
'settings.commit.title': 'Pesan commit AI',
|
|
3102
|
+
'settings.commit.model': 'Model',
|
|
3103
|
+
'settings.commit.modelHint': 'Kosong = ikuti sesi ini (provider/model)',
|
|
3104
|
+
'settings.commit.reasoning': 'Intensitas penalaran',
|
|
3105
|
+
'settings.commit.reasoningHint': 'Kosong = bawaan model',
|
|
3106
|
+
'settings.commit.prompt': 'Instruksi tambahan',
|
|
3107
|
+
'settings.commit.promptHint': 'mis. Conventional Commits, subjek bahasa Indonesia, maks 72 karakter',
|
|
3108
|
+
'settings.commit.tooLong': 'Hanya {n} karakter pertama yang disimpan',
|
|
3109
|
+
'changes.aiWrite': 'Tulis dengan AI',
|
|
3110
|
+
'changes.aiBusy': 'Menulis…',
|
|
3111
|
+
'changes.aiQuota': 'Memakai kuota Anda',
|
|
3112
|
+
'changes.aiOverwriteTitle': 'Ganti draf saat ini?',
|
|
3113
|
+
'changes.aiOverwriteText': 'Teks AI akan menggantikan pesan commit yang Anda tulis (bisa dibatalkan).',
|
|
3114
|
+
'changes.aiUndo': 'Batalkan',
|
|
3115
|
+
'changes.aiFailed': 'Gagal menulis:',
|
|
3116
|
+
'changes.aiNotice': 'Sebagian perubahan tidak dikirim ke model (dipotong atau dibuang); pesan bisa tidak lengkap.',
|
|
3117
|
+
'settings.rail.writeFailed': 'Pengaturan tidak tersimpan; tombol kembali ke posisi semula. Coba lagi.',
|
|
3118
|
+
'settings.rail.readonly': 'Host ini menyajikan konfigurasi hanya-baca.',
|
|
2623
3119
|
'rail.settingsHint': 'Seret atau pakai panah untuk mengurutkan; ikon mata mengalihkan visibilitas',
|
|
2624
3120
|
'rail.reset': 'Kembalikan default',
|
|
2625
3121
|
'rail.up': 'Naikkan',
|
|
@@ -2741,6 +3237,11 @@ window.__ModuleLoader__.load({
|
|
|
2741
3237
|
'action.showDiff': 'Xem diff',
|
|
2742
3238
|
'action.copyPath': 'Sao chép đường dẫn',
|
|
2743
3239
|
'action.checkout': 'Checkout',
|
|
3240
|
+
'action.float': 'Tách trang này',
|
|
3241
|
+
'action.dock': 'Ghép lại vào dock',
|
|
3242
|
+
'action.split': 'Chia',
|
|
3243
|
+
'action.fullscreen': 'Toàn màn hình',
|
|
3244
|
+
'action.exitFullscreen': 'Thoát toàn màn hình',
|
|
2744
3245
|
'action.newBranchFrom': 'Nhánh mới từ đây...',
|
|
2745
3246
|
'action.newBranchHere': 'Nhánh mới tại đây...',
|
|
2746
3247
|
'action.rename': 'Đổi tên...',
|
|
@@ -2764,6 +3265,8 @@ window.__ModuleLoader__.load({
|
|
|
2764
3265
|
'detail.back': 'Quay lại lịch sử',
|
|
2765
3266
|
'detail.noFiles': 'Không có thay đổi tệp',
|
|
2766
3267
|
'diff.empty': 'Không có diff văn bản để hiển thị',
|
|
3268
|
+
'pane.diff': 'chế độ xem diff',
|
|
3269
|
+
'pane.resize': 'Kéo để đổi kích thước {name} (nhấp đúp để đặt lại, phím mũi tên để tinh chỉnh)',
|
|
2767
3270
|
'diff.binary': 'Tệp nhị phân',
|
|
2768
3271
|
'diff.loading': 'Đang tải diff...',
|
|
2769
3272
|
'toolbar.refresh': 'Làm mới',
|
|
@@ -2774,6 +3277,28 @@ window.__ModuleLoader__.load({
|
|
|
2774
3277
|
'toolbar.tree': 'Bảng nhánh',
|
|
2775
3278
|
'rail.more': 'Thao tác khác',
|
|
2776
3279
|
'rail.settings': 'Cài đặt thanh thao tác',
|
|
3280
|
+
'rail.openSettings': 'Mở cài đặt plugin',
|
|
3281
|
+
'settings.rail.title': 'Nút thanh tác vụ',
|
|
3282
|
+
'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ụ đó.',
|
|
3283
|
+
'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.',
|
|
3284
|
+
'settings.commit.title': 'Thông điệp commit do AI viết',
|
|
3285
|
+
'settings.commit.model': 'Mô hình',
|
|
3286
|
+
'settings.commit.modelHint': 'Trống = theo phiên này (provider/model)',
|
|
3287
|
+
'settings.commit.reasoning': 'Mức suy luận',
|
|
3288
|
+
'settings.commit.reasoningHint': 'Trống = mặc định của mô hình',
|
|
3289
|
+
'settings.commit.prompt': 'Chỉ dẫn bổ sung',
|
|
3290
|
+
'settings.commit.promptHint': 'ví dụ Conventional Commits, tiêu đề tiếng Việt, tối đa 72 ký tự',
|
|
3291
|
+
'settings.commit.tooLong': 'Chỉ {n} ký tự đầu được lưu',
|
|
3292
|
+
'changes.aiWrite': 'Viết bằng AI',
|
|
3293
|
+
'changes.aiBusy': 'Đang viết…',
|
|
3294
|
+
'changes.aiQuota': 'Dùng hạn mức của bạn',
|
|
3295
|
+
'changes.aiOverwriteTitle': 'Thay bản nháp hiện tại?',
|
|
3296
|
+
'changes.aiOverwriteText': 'Nội dung AI sẽ thay thế thông điệp commit bạn đã nhập (có thể hoàn tác).',
|
|
3297
|
+
'changes.aiUndo': 'Hoàn tác',
|
|
3298
|
+
'changes.aiFailed': 'Không viết được:',
|
|
3299
|
+
'changes.aiNotice': 'Một số thay đổi chưa gửi tới mô hình (bị cắt hoặc bỏ); thông điệp có thể chưa đầy đủ.',
|
|
3300
|
+
'settings.rail.writeFailed': 'Cài đặt không lưu được; công tắc đã trở về trạng thái cũ. Hãy thử lại.',
|
|
3301
|
+
'settings.rail.readonly': 'Máy chủ này chỉ cung cấp cấu hình ở chế độ chỉ đọc.',
|
|
2777
3302
|
'rail.settingsHint': 'Kéo hoặc dùng mũi tên để sắp xếp; biểu tượng con mắt bật tắt hiển thị',
|
|
2778
3303
|
'rail.reset': 'Khôi phục mặc định',
|
|
2779
3304
|
'rail.up': 'Di chuyển lên',
|
|
@@ -2895,6 +3420,11 @@ window.__ModuleLoader__.load({
|
|
|
2895
3420
|
'action.showDiff': 'عرض الفروق',
|
|
2896
3421
|
'action.copyPath': 'نسخ المسار',
|
|
2897
3422
|
'action.checkout': 'تبديل',
|
|
3423
|
+
'action.float': 'تعويم هذه الصفحة',
|
|
3424
|
+
'action.dock': 'الإرجاع إلى الرصيف',
|
|
3425
|
+
'action.split': 'تقسيم',
|
|
3426
|
+
'action.fullscreen': 'ملء الشاشة',
|
|
3427
|
+
'action.exitFullscreen': 'إنهاء ملء الشاشة',
|
|
2898
3428
|
'action.newBranchFrom': 'فرع جديد من هنا...',
|
|
2899
3429
|
'action.newBranchHere': 'فرع جديد هنا...',
|
|
2900
3430
|
'action.rename': 'إعادة التسمية...',
|
|
@@ -2918,6 +3448,8 @@ window.__ModuleLoader__.load({
|
|
|
2918
3448
|
'detail.back': 'رجوع إلى السجل',
|
|
2919
3449
|
'detail.noFiles': 'لا توجد تغييرات في الملفات',
|
|
2920
3450
|
'diff.empty': 'لا توجد فروق نصية لعرضها',
|
|
3451
|
+
'pane.diff': 'عرض الفروق',
|
|
3452
|
+
'pane.resize': 'اسحب لتغيير حجم {name} (النقر المزدوج يعيد الضبط، ومفاتيح الأسهم للضبط الدقيق)',
|
|
2921
3453
|
'diff.binary': 'ملف ثنائي',
|
|
2922
3454
|
'diff.loading': 'جارٍ تحميل الفروق...',
|
|
2923
3455
|
'toolbar.refresh': 'تحديث',
|
|
@@ -2928,6 +3460,28 @@ window.__ModuleLoader__.load({
|
|
|
2928
3460
|
'toolbar.tree': 'لوحة الفروع',
|
|
2929
3461
|
'rail.more': 'مزيد من الإجراءات',
|
|
2930
3462
|
'rail.settings': 'إعدادات شريط الإجراءات',
|
|
3463
|
+
'rail.openSettings': 'فتح إعدادات الإضافة',
|
|
3464
|
+
'settings.rail.title': 'أزرار شريط الإجراءات',
|
|
3465
|
+
'settings.rail.hint': 'حدّد الإجراءات المعروضة على الشريط. الترتيب ثابت، وإلغاء التحديد يخفي الإجراء.',
|
|
3466
|
+
'settings.rail.unavailable': 'لا تتوفر خدمة إعدادات على هذا المضيف؛ استخدم «إعدادات شريط الإجراءات» في اللوحة.',
|
|
3467
|
+
'settings.commit.title': 'رسالة إيداع بالذكاء الاصطناعي',
|
|
3468
|
+
'settings.commit.model': 'النموذج',
|
|
3469
|
+
'settings.commit.modelHint': 'فارغ = اتبع هذه الجلسة (provider/model)',
|
|
3470
|
+
'settings.commit.reasoning': 'مستوى التفكير',
|
|
3471
|
+
'settings.commit.reasoningHint': 'فارغ = الافتراضي للنموذج',
|
|
3472
|
+
'settings.commit.prompt': 'تعليمات إضافية',
|
|
3473
|
+
'settings.commit.promptHint': 'مثال: Conventional Commits، والعنوان بالعربية، وبحد أقصى 72 حرفًا',
|
|
3474
|
+
'settings.commit.tooLong': 'يُحفظ أول {n} حرفًا فقط',
|
|
3475
|
+
'changes.aiWrite': 'اكتب بالذكاء الاصطناعي',
|
|
3476
|
+
'changes.aiBusy': 'جارٍ الكتابة…',
|
|
3477
|
+
'changes.aiQuota': 'يستهلك حصتك',
|
|
3478
|
+
'changes.aiOverwriteTitle': 'استبدال المسودة الحالية؟',
|
|
3479
|
+
'changes.aiOverwriteText': 'سيستبدل نص الذكاء الاصطناعي رسالة الإيداع التي كتبتها (يمكن التراجع).',
|
|
3480
|
+
'changes.aiUndo': 'تراجع',
|
|
3481
|
+
'changes.aiFailed': 'تعذّرت الكتابة:',
|
|
3482
|
+
'changes.aiNotice': 'لم تُرسل بعض التغييرات إلى النموذج (مقتطعة أو متروكة)؛ قد تكون الرسالة ناقصة.',
|
|
3483
|
+
'settings.rail.writeFailed': 'لم يتم حفظ الإعداد؛ عاد المفتاح إلى حاله. حاول مرة أخرى.',
|
|
3484
|
+
'settings.rail.readonly': 'يوفّر هذا المضيف الإعدادات للقراءة فقط.',
|
|
2931
3485
|
'rail.settingsHint': 'اسحب أو استخدم الأسهم لإعادة الترتيب؛ وأيقونة العين تبدّل الظهور',
|
|
2932
3486
|
'rail.reset': 'استعادة الإعدادات الافتراضية',
|
|
2933
3487
|
'rail.up': 'تحريك لأعلى',
|
|
@@ -3049,6 +3603,11 @@ window.__ModuleLoader__.load({
|
|
|
3049
3603
|
'action.showDiff': 'अंतर दिखाएँ',
|
|
3050
3604
|
'action.copyPath': 'पथ कॉपी करें',
|
|
3051
3605
|
'action.checkout': 'चेकआउट',
|
|
3606
|
+
'action.float': 'इस पृष्ठ को तैराएँ',
|
|
3607
|
+
'action.dock': 'डॉक पर वापस लाएँ',
|
|
3608
|
+
'action.split': 'विभाजित करें',
|
|
3609
|
+
'action.fullscreen': 'पूर्ण स्क्रीन',
|
|
3610
|
+
'action.exitFullscreen': 'पूर्ण स्क्रीन से बाहर',
|
|
3052
3611
|
'action.newBranchFrom': 'यहाँ से नई शाखा...',
|
|
3053
3612
|
'action.newBranchHere': 'यहाँ नई शाखा...',
|
|
3054
3613
|
'action.rename': 'नाम बदलें...',
|
|
@@ -3072,6 +3631,8 @@ window.__ModuleLoader__.load({
|
|
|
3072
3631
|
'detail.back': 'इतिहास पर वापस',
|
|
3073
3632
|
'detail.noFiles': 'कोई फ़ाइल परिवर्तन नहीं',
|
|
3074
3633
|
'diff.empty': 'दिखाने के लिए कोई टेक्स्ट अंतर नहीं',
|
|
3634
|
+
'pane.diff': 'डिफ़ व्यू',
|
|
3635
|
+
'pane.resize': '{name} का आकार बदलने के लिए खींचें (डबल-क्लिक रीसेट, तीर कुंजियाँ सूक्ष्म समायोजन)',
|
|
3075
3636
|
'diff.binary': 'बाइनरी फ़ाइल',
|
|
3076
3637
|
'diff.loading': 'अंतर लोड हो रहा है...',
|
|
3077
3638
|
'toolbar.refresh': 'ताज़ा करें',
|
|
@@ -3082,6 +3643,28 @@ window.__ModuleLoader__.load({
|
|
|
3082
3643
|
'toolbar.tree': 'शाखा पैनल',
|
|
3083
3644
|
'rail.more': 'अधिक क्रियाएँ',
|
|
3084
3645
|
'rail.settings': 'क्रिया पट्टी सेटिंग्स',
|
|
3646
|
+
'rail.openSettings': 'प्लगइन सेटिंग खोलें',
|
|
3647
|
+
'settings.rail.title': 'एक्शन रेल बटन',
|
|
3648
|
+
'settings.rail.hint': 'रेल पर दिखने वाली क्रियाएँ चुनें। क्रम निश्चित है; चुनाव हटाने पर वह छिप जाती है।',
|
|
3649
|
+
'settings.rail.unavailable': 'इस होस्ट में सेटिंग सेवा नहीं है; पैनल में «एक्शन रेल सेटिंग» का उपयोग करें।',
|
|
3650
|
+
'settings.commit.title': 'AI कमिट संदेश',
|
|
3651
|
+
'settings.commit.model': 'मॉडल',
|
|
3652
|
+
'settings.commit.modelHint': 'खाली = इस सत्र का अनुसरण करें (provider/model)',
|
|
3653
|
+
'settings.commit.reasoning': 'तर्क की तीव्रता',
|
|
3654
|
+
'settings.commit.reasoningHint': 'खाली = मॉडल का डिफ़ॉल्ट',
|
|
3655
|
+
'settings.commit.prompt': 'अतिरिक्त निर्देश',
|
|
3656
|
+
'settings.commit.promptHint': 'जैसे Conventional Commits, विषय हिंदी में, अधिकतम 72 अक्षर',
|
|
3657
|
+
'settings.commit.tooLong': 'केवल पहले {n} अक्षर सहेजे जाते हैं',
|
|
3658
|
+
'changes.aiWrite': 'AI से लिखें',
|
|
3659
|
+
'changes.aiBusy': 'लिखा जा रहा है…',
|
|
3660
|
+
'changes.aiQuota': 'आपका कोटा खर्च होता है',
|
|
3661
|
+
'changes.aiOverwriteTitle': 'मौजूदा ड्राफ़्ट बदलें?',
|
|
3662
|
+
'changes.aiOverwriteText': 'AI पाठ आपके लिखे कमिट संदेश की जगह लेगा (पूर्ववत कर सकते हैं)।',
|
|
3663
|
+
'changes.aiUndo': 'पूर्ववत करें',
|
|
3664
|
+
'changes.aiFailed': 'लिख नहीं सका:',
|
|
3665
|
+
'changes.aiNotice': 'कुछ बदलाव मॉडल को नहीं भेजे गए (काटे या छोड़े गए); संदेश अधूरा हो सकता है।',
|
|
3666
|
+
'settings.rail.writeFailed': 'सेटिंग सहेजी नहीं गई; स्विच वापस पुरानी स्थिति में आ गया। फिर से कोशिश करें।',
|
|
3667
|
+
'settings.rail.readonly': 'यह होस्ट कॉन्फ़िगरेशन को केवल-पढ़ने के लिए देता है।',
|
|
3085
3668
|
'rail.settingsHint': 'क्रम बदलने के लिए खींचें या तीरों का उपयोग करें; आँख आइकन दृश्यता बदलता है',
|
|
3086
3669
|
'rail.reset': 'डिफ़ॉल्ट बहाल करें',
|
|
3087
3670
|
'rail.up': 'ऊपर ले जाएँ',
|
|
@@ -3203,6 +3786,11 @@ window.__ModuleLoader__.load({
|
|
|
3203
3786
|
'action.showDiff': 'แสดงความต่าง',
|
|
3204
3787
|
'action.copyPath': 'คัดลอกพาธ',
|
|
3205
3788
|
'action.checkout': 'เช็กเอาต์',
|
|
3789
|
+
'action.float': 'ลอยหน้านี้',
|
|
3790
|
+
'action.dock': 'กลับเข้าด็อก',
|
|
3791
|
+
'action.split': 'แบ่งหน้าต่าง',
|
|
3792
|
+
'action.fullscreen': 'เต็มหน้าจอ',
|
|
3793
|
+
'action.exitFullscreen': 'ออกจากเต็มหน้าจอ',
|
|
3206
3794
|
'action.newBranchFrom': 'แบรนช์ใหม่จากที่นี่...',
|
|
3207
3795
|
'action.newBranchHere': 'แบรนช์ใหม่ที่นี่...',
|
|
3208
3796
|
'action.rename': 'เปลี่ยนชื่อ...',
|
|
@@ -3226,6 +3814,8 @@ window.__ModuleLoader__.load({
|
|
|
3226
3814
|
'detail.back': 'กลับไปที่ประวัติ',
|
|
3227
3815
|
'detail.noFiles': 'ไม่มีการเปลี่ยนแปลงไฟล์',
|
|
3228
3816
|
'diff.empty': 'ไม่มีความต่างเชิงข้อความให้แสดง',
|
|
3817
|
+
'pane.diff': 'มุมมอง diff',
|
|
3818
|
+
'pane.resize': 'ลากเพื่อปรับขนาด {name} (ดับเบิลคลิกเพื่อรีเซ็ต ปุ่มลูกศรปรับละเอียด)',
|
|
3229
3819
|
'diff.binary': 'ไฟล์ไบนารี',
|
|
3230
3820
|
'diff.loading': 'กำลังโหลดความต่าง...',
|
|
3231
3821
|
'toolbar.refresh': 'รีเฟรช',
|
|
@@ -3236,6 +3826,28 @@ window.__ModuleLoader__.load({
|
|
|
3236
3826
|
'toolbar.tree': 'แผงแบรนช์',
|
|
3237
3827
|
'rail.more': 'การทำงานเพิ่มเติม',
|
|
3238
3828
|
'rail.settings': 'ตั้งค่าแถบการทำงาน',
|
|
3829
|
+
'rail.openSettings': 'เปิดการตั้งค่าปลั๊กอิน',
|
|
3830
|
+
'settings.rail.title': 'ปุ่มแถบการทำงาน',
|
|
3831
|
+
'settings.rail.hint': 'ทำเครื่องหมายการทำงานที่จะแสดงบนแถบ ลำดับคงที่; ยกเลิกเครื่องหมายเพื่อซ่อน',
|
|
3832
|
+
'settings.rail.unavailable': 'โฮสต์นี้ไม่มีบริการตั้งค่า ให้ใช้ «ตั้งค่าแถบการทำงาน» ในแผง',
|
|
3833
|
+
'settings.commit.title': 'ข้อความคอมมิตโดย AI',
|
|
3834
|
+
'settings.commit.model': 'โมเดล',
|
|
3835
|
+
'settings.commit.modelHint': 'ว่าง = ตามเซสชันนี้ (provider/model)',
|
|
3836
|
+
'settings.commit.reasoning': 'ระดับการใช้เหตุผล',
|
|
3837
|
+
'settings.commit.reasoningHint': 'ว่าง = ค่าเริ่มต้นของโมเดล',
|
|
3838
|
+
'settings.commit.prompt': 'คำสั่งเพิ่มเติม',
|
|
3839
|
+
'settings.commit.promptHint': 'เช่น Conventional Commits หัวข้อเป็นภาษาไทย ไม่เกิน 72 ตัวอักษร',
|
|
3840
|
+
'settings.commit.tooLong': 'บันทึกเพียง {n} ตัวอักษรแรก',
|
|
3841
|
+
'changes.aiWrite': 'เขียนด้วย AI',
|
|
3842
|
+
'changes.aiBusy': 'กำลังเขียน…',
|
|
3843
|
+
'changes.aiQuota': 'ใช้โควตาของคุณ',
|
|
3844
|
+
'changes.aiOverwriteTitle': 'แทนที่ฉบับร่างปัจจุบันหรือไม่',
|
|
3845
|
+
'changes.aiOverwriteText': 'ข้อความจาก AI จะแทนที่ข้อความคอมมิตที่คุณพิมพ์ (เลิกทำได้)',
|
|
3846
|
+
'changes.aiUndo': 'เลิกทำ',
|
|
3847
|
+
'changes.aiFailed': 'เขียนไม่สำเร็จ:',
|
|
3848
|
+
'changes.aiNotice': 'มีการเปลี่ยนแปลงบางส่วนที่ไม่ได้ส่งให้โมเดล (ถูกตัดหรือทิ้ง) ข้อความอาจไม่ครบถ้วน',
|
|
3849
|
+
'settings.rail.writeFailed': 'บันทึกการตั้งค่าไม่สำเร็จ สวิตช์กลับไปสถานะเดิม ลองอีกครั้ง',
|
|
3850
|
+
'settings.rail.readonly': 'โฮสต์นี้ให้บริการการตั้งค่าแบบอ่านอย่างเดียว',
|
|
3239
3851
|
'rail.settingsHint': 'ลากหรือใช้ลูกศรเพื่อจัดลำดับ; ไอคอนตาใช้สลับการแสดง',
|
|
3240
3852
|
'rail.reset': 'คืนค่าเริ่มต้น',
|
|
3241
3853
|
'rail.up': 'เลื่อนขึ้น',
|
|
@@ -3448,6 +4060,16 @@ window.__ModuleLoader__.load({
|
|
|
3448
4060
|
so a version that adds an action never breaks a stored config. */
|
|
3449
4061
|
const RAIL_SPECS = [
|
|
3450
4062
|
{ id: 'refresh', icon: 'refresh', key: 'toolbar.refresh', tone: 'accent' },
|
|
4063
|
+
/* View actions. The tree toggle lives here as well as in the compact header
|
|
4064
|
+
because a persisted treeOpen=false would otherwise have no way back:
|
|
4065
|
+
compact is the only chrome with a header button, and it used to render
|
|
4066
|
+
that button only while the tree was OPEN. */
|
|
4067
|
+
{ id: 'tree', icon: 'branch', key: 'toolbar.tree', tone: 'secondary' },
|
|
4068
|
+
/* The official right-sidebar dock (float / re-dock / split / full screen).
|
|
4069
|
+
They only render where that service exists — see dockFaceOf(). */
|
|
4070
|
+
{ id: 'float', icon: 'float', key: 'action.float', tone: 'accent' },
|
|
4071
|
+
{ id: 'split', icon: 'split', key: 'action.split', tone: 'secondary' },
|
|
4072
|
+
{ id: 'fullscreen', icon: 'fullscreen', key: 'action.fullscreen', tone: 'secondary' },
|
|
3451
4073
|
{ id: 'newBranch', icon: 'plus', key: 'toolbar.newBranch', tone: 'success' },
|
|
3452
4074
|
{ id: 'checkout', icon: 'checkout', key: 'action.checkout', tone: 'accent' },
|
|
3453
4075
|
{ id: 'delete', icon: 'trash', key: 'action.delete', tone: 'danger' },
|
|
@@ -3461,6 +4083,10 @@ window.__ModuleLoader__.load({
|
|
|
3461
4083
|
{ id: 'push', icon: 'push', key: 'toolbar.push', tone: 'success' },
|
|
3462
4084
|
]
|
|
3463
4085
|
const RAIL_IDS = RAIL_SPECS.map((spec) => spec.id)
|
|
4086
|
+
/* Actions that arrange the view rather than touch the repository, so a running
|
|
4087
|
+
git command never greys them out. */
|
|
4088
|
+
const RAIL_VIEW_IDS = ['tree', 'float', 'split', 'fullscreen']
|
|
4089
|
+
const RAIL_DOCK_IDS = ['float', 'split', 'fullscreen']
|
|
3464
4090
|
|
|
3465
4091
|
function normalizeRail(raw) {
|
|
3466
4092
|
const source = raw !== null && typeof raw === 'object' ? raw : {}
|
|
@@ -3478,7 +4104,78 @@ window.__ModuleLoader__.load({
|
|
|
3478
4104
|
return { order, hidden }
|
|
3479
4105
|
}
|
|
3480
4106
|
|
|
4107
|
+
/* ---------------------------------------------------------------------
|
|
4108
|
+
Where the rail config LIVES. On dsh >= 0.1.7 a plugin's configuration is
|
|
4109
|
+
the row Config (the official settings surface, persisted by the host under
|
|
4110
|
+
the loader row id) and the browser reads it through ctx.configForms — so
|
|
4111
|
+
the settings card and the rail read ONE source and nothing is written
|
|
4112
|
+
twice. An older host has no such service: there the rail keeps its
|
|
4113
|
+
localStorage home and its in-panel editor, which is the only editor those
|
|
4114
|
+
hosts have. `railForm` is that switch; null means legacy.
|
|
4115
|
+
--------------------------------------------------------------------- */
|
|
4116
|
+
const RAIL_NS = 'ide-git'
|
|
4117
|
+
/* One write window for the settings card: a burst of switches becomes one
|
|
4118
|
+
atomic mutation instead of a stream the Host can lose. */
|
|
4119
|
+
const FLUSH_MS = 250
|
|
4120
|
+
const RAIL_MIGRATED_KEY = 'dsh-ide-git.rail.v1.migrated'
|
|
4121
|
+
let railForm = null
|
|
4122
|
+
let railMigrating = false
|
|
4123
|
+
const railWatchers = new Set()
|
|
4124
|
+
|
|
4125
|
+
/* ---- rail config core (pure: tests/smoke.mjs slices this block) ---- */
|
|
4126
|
+
function railFieldOf(id) {
|
|
4127
|
+
return 'rail' + id.charAt(0).toUpperCase() + id.slice(1)
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
/* Stored booleans -> the { order, hidden } shape the rail has always used.
|
|
4131
|
+
The order is the code order now: the settings card edits one switch per
|
|
4132
|
+
action, so a stored permutation would have no editor left, and dropping it
|
|
4133
|
+
keeps this surface honest rather than half-editable. */
|
|
4134
|
+
function railConfigOfValues(values) {
|
|
4135
|
+
const source = values !== null && typeof values === 'object' ? values : {}
|
|
4136
|
+
const hidden = RAIL_IDS.filter((id) => source[railFieldOf(id)] === false)
|
|
4137
|
+
return { order: RAIL_IDS.slice(), hidden: hidden }
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
/* The inverse of railFieldOf, for the one-field write path. Returns null for a
|
|
4141
|
+
field no rail action owns, so a caller cannot invent a row-Config key. */
|
|
4142
|
+
function railIdOfField(field) {
|
|
4143
|
+
if (typeof field !== 'string' || field.indexOf('rail') !== 0 || field.length <= 4) return null
|
|
4144
|
+
const name = field.slice(4)
|
|
4145
|
+
const id = name.charAt(0).toLowerCase() + name.slice(1)
|
|
4146
|
+
return RAIL_IDS.indexOf(id) < 0 ? null : id
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
function railValuesOfConfig(config) {
|
|
4150
|
+
const values = {}
|
|
4151
|
+
for (const id of RAIL_IDS) values[railFieldOf(id)] = config.hidden.indexOf(id) < 0
|
|
4152
|
+
return values
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
/* ---- end rail config core ---- */
|
|
4156
|
+
|
|
4157
|
+
/* The three AI-commit settings live in the same row Config document the
|
|
4158
|
+
settings card writes; the panel reads them per render, so a change in the
|
|
4159
|
+
card applies to the next click without a reload. */
|
|
4160
|
+
function readCommitSettings() {
|
|
4161
|
+
let values = {}
|
|
4162
|
+
const ready = railFormReady()
|
|
4163
|
+
if (ready !== null && ready.value !== null && typeof ready.value === 'object') values = ready.value
|
|
4164
|
+
const text = (field, max) => (typeof values[field] === 'string' ? values[field].trim().slice(0, max) : '')
|
|
4165
|
+
return { model: text('commitModel', 120), reasoning: text('commitReasoning', 60), prompt: text('commitPrompt', 2000) }
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
function railFormReady() {
|
|
4169
|
+
if (railForm === null) return null
|
|
4170
|
+
const snapshot = railForm.getSnapshot()
|
|
4171
|
+
if (snapshot === null || snapshot === undefined || snapshot.status !== 'ready' || snapshot.value === undefined) return null
|
|
4172
|
+
return snapshot
|
|
4173
|
+
}
|
|
4174
|
+
|
|
3481
4175
|
function readRailConfig() {
|
|
4176
|
+
const ready = railFormReady()
|
|
4177
|
+
if (ready !== null) return railConfigOfValues(ready.value)
|
|
4178
|
+
// Legacy home (and the state before the host answers): localStorage.
|
|
3482
4179
|
try {
|
|
3483
4180
|
const raw = window.localStorage.getItem(RAIL_KEY)
|
|
3484
4181
|
if (raw === null || raw === '') return normalizeRail(null)
|
|
@@ -3487,10 +4184,413 @@ window.__ModuleLoader__.load({
|
|
|
3487
4184
|
return normalizeRail(null)
|
|
3488
4185
|
}
|
|
3489
4186
|
|
|
3490
|
-
|
|
4187
|
+
/* `form` lets a caller write through the exact handle it read from (the
|
|
4188
|
+
settings card gets the framework's own form); without it the module handle
|
|
4189
|
+
is used. When a config surface EXISTS but is not ready yet, the write is
|
|
4190
|
+
refused instead of silently landing in the legacy localStorage home — two
|
|
4191
|
+
sources would be worse than a refused edit. */
|
|
4192
|
+
function writeRailConfig(config, form) {
|
|
4193
|
+
const active = form !== undefined && form !== null && typeof form.getSnapshot === 'function' ? form : railForm
|
|
4194
|
+
let ready = null
|
|
4195
|
+
try {
|
|
4196
|
+
const snapshot = active === null || active === undefined ? null : active.getSnapshot()
|
|
4197
|
+
if (snapshot !== null && snapshot !== undefined && snapshot.status === 'ready' && snapshot.value !== undefined) ready = snapshot
|
|
4198
|
+
} catch (error) { void error }
|
|
4199
|
+
if (ready !== null) {
|
|
4200
|
+
if (ready.writable !== true || typeof active.set !== 'function') return
|
|
4201
|
+
railForm = active
|
|
4202
|
+
const current = ready.value !== null && typeof ready.value === 'object' ? ready.value : {}
|
|
4203
|
+
// Only the fields that actually changed: one write, not sixteen.
|
|
4204
|
+
for (const id of RAIL_IDS) {
|
|
4205
|
+
const field = railFieldOf(id)
|
|
4206
|
+
const visible = config.hidden.indexOf(id) < 0
|
|
4207
|
+
if (current[field] === visible) continue
|
|
4208
|
+
const written = active.set(field, visible)
|
|
4209
|
+
if (written !== undefined && written !== null && typeof written.then === 'function') {
|
|
4210
|
+
void written.then((accepted) => {
|
|
4211
|
+
if (accepted === false) console.warn('[dsh-ide-git] rail setting "' + field + '" was refused by the host')
|
|
4212
|
+
}, (error) => { console.warn('[dsh-ide-git] rail setting write failed: ' + String(error && error.message ? error.message : error)) })
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
return
|
|
4216
|
+
}
|
|
4217
|
+
if (railForm !== null) return
|
|
3491
4218
|
try { window.localStorage.setItem(RAIL_KEY, JSON.stringify(config)) } catch (error) { void error }
|
|
3492
4219
|
}
|
|
3493
4220
|
|
|
4221
|
+
/* One switch, one field, one write — computed from the value the user just
|
|
4222
|
+
chose rather than from a whole-config snapshot that may predate the click.
|
|
4223
|
+
The card edits one field at a time, and a full-config write derived from a
|
|
4224
|
+
stale render clobbers whichever sibling was toggled a moment earlier
|
|
4225
|
+
(measured on 16 rapid toggles: 4 fields never reached the row Config and
|
|
4226
|
+
reverted on reload).
|
|
4227
|
+
Returns the write's disposition so the caller can roll its optimistic value
|
|
4228
|
+
back and say so instead of leaving a switch that lies:
|
|
4229
|
+
sent:true + settled:Promise<boolean> when the Host answers
|
|
4230
|
+
sent:false, reason 'read-only' | 'not-ready' | 'unknown'. */
|
|
4231
|
+
function writeRailFields(ops, form) {
|
|
4232
|
+
const active = form !== undefined && form !== null && typeof form.getSnapshot === 'function' ? form : railForm
|
|
4233
|
+
let ready = null
|
|
4234
|
+
try {
|
|
4235
|
+
const snapshot = active === null || active === undefined ? null : active.getSnapshot()
|
|
4236
|
+
if (snapshot !== null && snapshot !== undefined && snapshot.status === 'ready' && snapshot.value !== undefined) ready = snapshot
|
|
4237
|
+
} catch (error) { void error }
|
|
4238
|
+
if (ready !== null) {
|
|
4239
|
+
if (ready.writable !== true || typeof active.mutate !== 'function') return { sent: false, reason: 'read-only' }
|
|
4240
|
+
railForm = active
|
|
4241
|
+
const settled = Promise.resolve(active.mutate(ops)).then(
|
|
4242
|
+
(accepted) => accepted !== false,
|
|
4243
|
+
(error) => {
|
|
4244
|
+
console.warn('[dsh-ide-git] rail setting write failed: ' + String(error && error.message ? error.message : error))
|
|
4245
|
+
return false
|
|
4246
|
+
},
|
|
4247
|
+
)
|
|
4248
|
+
return { sent: true, settled: settled }
|
|
4249
|
+
}
|
|
4250
|
+
/* A config surface that exists but has not answered yet must refuse rather
|
|
4251
|
+
than fall through to the legacy home: two sources would be worse. */
|
|
4252
|
+
if (railForm !== null) return { sent: false, reason: 'not-ready' }
|
|
4253
|
+
const current = readRailConfig()
|
|
4254
|
+
let hidden = current.hidden.slice()
|
|
4255
|
+
for (const op of ops) {
|
|
4256
|
+
const id = railIdOfField(String(op.path[0]))
|
|
4257
|
+
if (id === null) return { sent: false, reason: 'unknown' }
|
|
4258
|
+
const wanted = op.value === true
|
|
4259
|
+
if (wanted === true) hidden = hidden.filter((entry) => entry !== id)
|
|
4260
|
+
else if (hidden.indexOf(id) < 0) hidden = hidden.concat([id])
|
|
4261
|
+
}
|
|
4262
|
+
try { window.localStorage.setItem(RAIL_KEY, JSON.stringify({ order: current.order, hidden: hidden })) } catch (error) { void error }
|
|
4263
|
+
notifyRailConfig()
|
|
4264
|
+
return { sent: true }
|
|
4265
|
+
}
|
|
4266
|
+
|
|
4267
|
+
/* One switch's write. The card batches a burst through writeRailFields so the
|
|
4268
|
+
Host sees one atomic mutation instead of a stream of overlapping ones. */
|
|
4269
|
+
function writeRailField(field, value, form) {
|
|
4270
|
+
return writeRailFields([{ op: 'set', path: [field], value: value }], form)
|
|
4271
|
+
}
|
|
4272
|
+
|
|
4273
|
+
/* One-shot move of the pre-settings rail config into the row Config: read the
|
|
4274
|
+
legacy value ONCE, write it through, mark it done. After this the row Config
|
|
4275
|
+
is the only source (the legacy key is left untouched as a paper trail, but
|
|
4276
|
+
nothing writes it any more on this host). */
|
|
4277
|
+
function migrateRailConfig(form) {
|
|
4278
|
+
try {
|
|
4279
|
+
if (window.localStorage.getItem(RAIL_MIGRATED_KEY) !== null) return
|
|
4280
|
+
if (railMigrating === true) return
|
|
4281
|
+
const done = () => {
|
|
4282
|
+
railMigrating = false
|
|
4283
|
+
try { window.localStorage.setItem(RAIL_MIGRATED_KEY, '1') } catch (error) { void error }
|
|
4284
|
+
}
|
|
4285
|
+
const raw = window.localStorage.getItem(RAIL_KEY)
|
|
4286
|
+
if (raw === null || raw === '') { done(); return }
|
|
4287
|
+
const values = railValuesOfConfig(normalizeRail(JSON.parse(raw)))
|
|
4288
|
+
const ready = railFormReady()
|
|
4289
|
+
const accepted = ready !== null && ready.value !== null && typeof ready.value === 'object' ? ready.value : {}
|
|
4290
|
+
const ops = []
|
|
4291
|
+
for (const id of RAIL_IDS) {
|
|
4292
|
+
const field = railFieldOf(id)
|
|
4293
|
+
if (accepted[field] === values[field]) continue
|
|
4294
|
+
ops.push({ op: 'set', path: [field], value: values[field] === true })
|
|
4295
|
+
}
|
|
4296
|
+
if (ops.length === 0) { done(); return }
|
|
4297
|
+
/* ONE atomic mutation, and the marker ONLY once the Host accepted it.
|
|
4298
|
+
The per-field set() loop this replaces fired sixteen unawaited writes
|
|
4299
|
+
and stamped the marker right away, so on a real profile fifteen of the
|
|
4300
|
+
sixteen fields (and in one case all of them) never reached the row
|
|
4301
|
+
Config and were never retried — an upgrade silently lost the user's
|
|
4302
|
+
rail settings while the marker claimed success. A refusal, a failed
|
|
4303
|
+
write or a superseded one now leaves the marker unwritten so the next
|
|
4304
|
+
load migrates again. */
|
|
4305
|
+
railMigrating = true
|
|
4306
|
+
const outcome = writeRailFields(ops, form)
|
|
4307
|
+
if (outcome.sent !== true) {
|
|
4308
|
+
railMigrating = false
|
|
4309
|
+
console.warn('[dsh-ide-git] legacy rail migration was refused (' + String(outcome.reason) + '); it stays unmarked and is retried')
|
|
4310
|
+
return
|
|
4311
|
+
}
|
|
4312
|
+
if (outcome.settled === undefined) { done(); return }
|
|
4313
|
+
void outcome.settled.then((acceptedWrite) => {
|
|
4314
|
+
if (acceptedWrite === true) { done(); return }
|
|
4315
|
+
railMigrating = false
|
|
4316
|
+
console.warn('[dsh-ide-git] legacy rail migration was not accepted; it stays unmarked and is retried')
|
|
4317
|
+
})
|
|
4318
|
+
} catch (error) { void error }
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
function subscribeRailConfig(listener) {
|
|
4322
|
+
railWatchers.add(listener)
|
|
4323
|
+
return () => { railWatchers.delete(listener) }
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
function notifyRailConfig() {
|
|
4327
|
+
for (const listener of Array.from(railWatchers)) {
|
|
4328
|
+
try { listener() } catch (error) { void error }
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
/* ============================== pane sizes ============================== */
|
|
4333
|
+
|
|
4334
|
+
/* ---- pane sizing core (pure: tests/smoke.mjs slices this block and drives
|
|
4335
|
+
it through new Function, so it must stay free of window/DOM/React) ---- */
|
|
4336
|
+
|
|
4337
|
+
/* Every fixed pane size in the layout became draggable (issue #5), and the
|
|
4338
|
+
result is remembered PER SURFACE. The same registration renders in the
|
|
4339
|
+
bottom workbench (wide and flat) and in the native right sidebar (narrow
|
|
4340
|
+
and tall), and a 290px changes column that is right in one is half the
|
|
4341
|
+
panel in the other. The bucket is therefore `chrome:wide|tall`, and what
|
|
4342
|
+
is stored is a RATIO of the measured container: resizing the dock or the
|
|
4343
|
+
window rescales the panes instead of breaking the layout. */
|
|
4344
|
+
const PANES_KEY = 'dsh-ide-git.panes.v1'
|
|
4345
|
+
const PANE_CHROME_KEYS = {
|
|
4346
|
+
columns: ['tree', 'changes'],
|
|
4347
|
+
stack: ['tree', 'changes', 'diff'],
|
|
4348
|
+
compact: ['tree', 'diff'],
|
|
4349
|
+
}
|
|
4350
|
+
/* Whether a key measures a height (stacked chromes) or a width (columns). */
|
|
4351
|
+
const PANE_HEIGHT_CHROME = { columns: false, stack: true, compact: true }
|
|
4352
|
+
/* The pane a separator resizes, and the key its label is translated from. */
|
|
4353
|
+
const PANE_NAME_KEYS = { tree: 'toolbar.tree', changes: 'changes.title', diff: 'pane.diff' }
|
|
4354
|
+
/* Hard limits in px: a gutter moves a pane between these and nothing else,
|
|
4355
|
+
so no pane can ever be dragged down to zero. */
|
|
4356
|
+
const PANE_LIMITS = {
|
|
4357
|
+
'columns:tree': { min: 140, max: 560 },
|
|
4358
|
+
'columns:changes': { min: 200, max: 640 },
|
|
4359
|
+
'stack:tree': { min: 100, max: 420 },
|
|
4360
|
+
'stack:changes': { min: 120, max: 460 },
|
|
4361
|
+
'stack:diff': { min: 120, max: 900 },
|
|
4362
|
+
'compact:tree': { min: 100, max: 420 },
|
|
4363
|
+
'compact:diff': { min: 120, max: 900 },
|
|
4364
|
+
}
|
|
4365
|
+
/* Today's fixed sizes stay the default — and stay in the stylesheet: a pane
|
|
4366
|
+
the user never dragged keeps exactly the look it had before, while a
|
|
4367
|
+
dragged one is remembered as a ratio. */
|
|
4368
|
+
const PANE_DEFAULTS = {
|
|
4369
|
+
'columns:tree': { px: 200 },
|
|
4370
|
+
'columns:changes': { px: 290 },
|
|
4371
|
+
'stack:tree': { ratio: 0.36 },
|
|
4372
|
+
'stack:changes': { ratio: 0.46 },
|
|
4373
|
+
'compact:tree': { ratio: 0.42 },
|
|
4374
|
+
}
|
|
4375
|
+
const PANE_MAIN_MIN_W = 240
|
|
4376
|
+
const PANE_MAIN_MIN_H = 150
|
|
4377
|
+
/* The reserve above is the IDEAL the clamp aims for. When the panel is simply
|
|
4378
|
+
too small for every default at once, aiming for it would put a pane's
|
|
4379
|
+
ceiling BELOW the box that pane already occupies — and the first drag would
|
|
4380
|
+
then shrink it instead of growing it. Below that point the middle pane
|
|
4381
|
+
keeps this hard floor instead, and the pane's own current size always wins
|
|
4382
|
+
(see paneGeometry). */
|
|
4383
|
+
const PANE_MAIN_FLOOR_PX = 120
|
|
4384
|
+
const PANE_HISTORY_MIN_H = 120
|
|
4385
|
+
/* Hit area of one gutter, and how many of them sit in the body. The
|
|
4386
|
+
history↔diff separator lives inside the middle pane, so it is not counted
|
|
4387
|
+
against the body's own budget. */
|
|
4388
|
+
const PANE_GUTTER_PX = 8
|
|
4389
|
+
const PANE_BODY_GUTTERS = { columns: 2, stack: 2, compact: 1 }
|
|
4390
|
+
const PANE_BUCKETS = Object.keys(PANE_CHROME_KEYS).reduce(
|
|
4391
|
+
(all, chrome) => all.concat([chrome + ':wide', chrome + ':tall']), [])
|
|
4392
|
+
|
|
4393
|
+
/* Two measurements are the same when every pane they know about is. */
|
|
4394
|
+
function samePaneSizes(current, next) {
|
|
4395
|
+
const keys = Object.keys(current)
|
|
4396
|
+
if (keys.length !== Object.keys(next).length) return false
|
|
4397
|
+
for (const key of keys) { if (current[key] !== next[key]) return false }
|
|
4398
|
+
return true
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
function panBucket(chrome, box) {
|
|
4402
|
+
return chrome + (box.height > box.width ? ':tall' : ':wide')
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
/* Storage is untrusted, so this keeps normalizeRail()'s discipline: unknown
|
|
4406
|
+
buckets, unknown keys, non-finite numbers and out-of-range ratios are all
|
|
4407
|
+
dropped. A ratio is a fraction of the container, so it lives in (0, 1]. */
|
|
4408
|
+
function normalizePanes(raw) {
|
|
4409
|
+
const source = raw !== null && typeof raw === 'object' ? raw : {}
|
|
4410
|
+
const stored = source.panes !== null && typeof source.panes === 'object' ? source.panes : {}
|
|
4411
|
+
const panes = {}
|
|
4412
|
+
for (const bucket of PANE_BUCKETS) {
|
|
4413
|
+
const entry = stored[bucket]
|
|
4414
|
+
if (entry === null || typeof entry !== 'object') continue
|
|
4415
|
+
const keys = PANE_CHROME_KEYS[bucket.slice(0, bucket.indexOf(':'))]
|
|
4416
|
+
const clean = {}
|
|
4417
|
+
for (const key of keys) {
|
|
4418
|
+
const value = entry[key]
|
|
4419
|
+
if (typeof value !== 'number' || Number.isFinite(value) === false) continue
|
|
4420
|
+
if (value <= 0 || value > 1) continue
|
|
4421
|
+
clean[key] = value
|
|
4422
|
+
}
|
|
4423
|
+
if (Object.keys(clean).length > 0) panes[bucket] = clean
|
|
4424
|
+
}
|
|
4425
|
+
return { treeOpen: source.treeOpen !== false, panes: panes }
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
/* Immutable single-key update. ratio === null removes the override, which is
|
|
4429
|
+
exactly what the double-click reset does. */
|
|
4430
|
+
function withPaneRatio(config, bucket, key, ratio) {
|
|
4431
|
+
const panes = {}
|
|
4432
|
+
for (const name of PANE_BUCKETS) {
|
|
4433
|
+
if (config.panes[name] !== undefined) panes[name] = config.panes[name]
|
|
4434
|
+
}
|
|
4435
|
+
const entry = {}
|
|
4436
|
+
const current = panes[bucket] === undefined ? {} : panes[bucket]
|
|
4437
|
+
for (const name of PANE_CHROME_KEYS[bucket.slice(0, bucket.indexOf(':'))]) {
|
|
4438
|
+
if (current[name] !== undefined) entry[name] = current[name]
|
|
4439
|
+
}
|
|
4440
|
+
if (ratio === null) delete entry[key]
|
|
4441
|
+
else entry[key] = ratio
|
|
4442
|
+
if (Object.keys(entry).length === 0) delete panes[bucket]
|
|
4443
|
+
else panes[bucket] = entry
|
|
4444
|
+
return { treeOpen: config.treeOpen, panes: panes }
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
/* Stored ratios (or today's defaults) → concrete px for the current chrome,
|
|
4448
|
+
clamped against the measured container. `overrides` holds only the panes
|
|
4449
|
+
the user actually dragged (absent = leave the stylesheet default alone);
|
|
4450
|
+
`effective` is what the clamp math used; `limits` is the range each gutter
|
|
4451
|
+
is allowed to hand back. `laidOut` is what the panes actually measure right
|
|
4452
|
+
now, which beats the stylesheet default when a pane is content-sized: the
|
|
4453
|
+
stacked chrome caps the changes pane at 46%, but a three-file summary only
|
|
4454
|
+
takes 30%, and reserving the cap would freeze the tree above it. */
|
|
4455
|
+
function paneGeometry(chrome, box, config, laidOut) {
|
|
4456
|
+
const height = PANE_HEIGHT_CHROME[chrome] === true
|
|
4457
|
+
const reference = height ? box.height : box.width
|
|
4458
|
+
const bucket = panBucket(chrome, box)
|
|
4459
|
+
const ready = config !== null && typeof config === 'object'
|
|
4460
|
+
const source = ready && config.panes !== null && typeof config.panes === 'object' ? config.panes : {}
|
|
4461
|
+
const ratios = source[bucket] !== null && typeof source[bucket] === 'object' ? source[bucket] : {}
|
|
4462
|
+
const measured = laidOut !== null && typeof laidOut === 'object' ? laidOut : {}
|
|
4463
|
+
const keys = PANE_CHROME_KEYS[chrome].slice()
|
|
4464
|
+
/* A folded tree has no pane and no gutter, so it must not reserve room. */
|
|
4465
|
+
if (ready && config.treeOpen === false) {
|
|
4466
|
+
const at = keys.indexOf('tree')
|
|
4467
|
+
if (at >= 0) keys.splice(at, 1)
|
|
4468
|
+
}
|
|
4469
|
+
const overrides = {}
|
|
4470
|
+
const effective = {}
|
|
4471
|
+
const limits = {}
|
|
4472
|
+
const sizeOf = (key) => (effective[key] === undefined ? 0 : effective[key])
|
|
4473
|
+
for (const key of keys) {
|
|
4474
|
+
const spec = PANE_DEFAULTS[chrome + ':' + key]
|
|
4475
|
+
const fallback = spec === undefined ? 0 : (spec.px === undefined ? spec.ratio * reference : spec.px)
|
|
4476
|
+
const live = measured[key]
|
|
4477
|
+
effective[key] = typeof live === 'number' && Number.isFinite(live) && live > 0 ? live : fallback
|
|
4478
|
+
}
|
|
4479
|
+
const space = Math.max(0, reference - PANE_BODY_GUTTERS[chrome] * PANE_GUTTER_PX)
|
|
4480
|
+
const reserve = height ? PANE_MAIN_MIN_H : PANE_MAIN_MIN_W
|
|
4481
|
+
/* Deterministic order: changes, then tree, then diff. The diff is carved
|
|
4482
|
+
out of the middle pane, so it is clamped last — against whatever the
|
|
4483
|
+
first two left behind, minus the history list's own minimum. */
|
|
4484
|
+
for (const key of ['changes', 'tree', 'diff']) {
|
|
4485
|
+
if (keys.indexOf(key) < 0) continue
|
|
4486
|
+
const spec = PANE_LIMITS[chrome + ':' + key]
|
|
4487
|
+
const current = sizeOf(key)
|
|
4488
|
+
let ceiling = spec.max
|
|
4489
|
+
if (key === 'diff') {
|
|
4490
|
+
const main = space - sizeOf('changes') - sizeOf('tree') - PANE_GUTTER_PX
|
|
4491
|
+
ceiling = Math.min(ceiling, main - PANE_HISTORY_MIN_H)
|
|
4492
|
+
} else {
|
|
4493
|
+
let taken = 0
|
|
4494
|
+
for (const other of keys) { if (other !== key && other !== 'diff') taken += sizeOf(other) }
|
|
4495
|
+
/* Two tiers, because one number cannot serve both jobs. The IDEAL keeps
|
|
4496
|
+
the middle pane at its reserve — that is what a roomy panel gets. The
|
|
4497
|
+
relaxed tier only kicks in when the ideal would land BELOW the box
|
|
4498
|
+
this pane already occupies (a 674px column cannot hold 200 + 290 + 240
|
|
4499
|
+
at once), and it exists so the clamp degrades to "you may not grow
|
|
4500
|
+
much" instead of "the first drag to the right shrinks you". */
|
|
4501
|
+
const ideal = space - reserve - taken
|
|
4502
|
+
ceiling = Math.min(ceiling, current > ideal ? space - PANE_MAIN_FLOOR_PX - taken : ideal)
|
|
4503
|
+
}
|
|
4504
|
+
/* The clamp may refuse to GROW a pane; it may never shrink one the user
|
|
4505
|
+
did not touch — so the ceiling is never below the pane's current box,
|
|
4506
|
+
and the aria window says exactly that. */
|
|
4507
|
+
const cap = Math.max(spec.max, Math.round(current))
|
|
4508
|
+
const high = Math.min(cap, Math.max(spec.min, Math.round(ceiling), Math.round(current)))
|
|
4509
|
+
limits[key] = { min: spec.min, max: high }
|
|
4510
|
+
const ratio = ratios[key]
|
|
4511
|
+
if (typeof ratio === 'number' && Number.isFinite(ratio)) {
|
|
4512
|
+
const px = Math.round(Math.min(Math.max(ratio * reference, spec.min), high))
|
|
4513
|
+
overrides[key] = px
|
|
4514
|
+
effective[key] = px
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
return { bucket: bucket, overrides: overrides, effective: effective, limits: limits }
|
|
4518
|
+
}
|
|
4519
|
+
/* ---- end pane sizing core ---- */
|
|
4520
|
+
|
|
4521
|
+
function readPanes() {
|
|
4522
|
+
try {
|
|
4523
|
+
const raw = window.localStorage.getItem(PANES_KEY)
|
|
4524
|
+
if (raw === null || raw === '') return normalizePanes(null)
|
|
4525
|
+
return normalizePanes(JSON.parse(raw))
|
|
4526
|
+
} catch (error) { void error }
|
|
4527
|
+
return normalizePanes(null)
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
function writePanes(config) {
|
|
4531
|
+
try { window.localStorage.setItem(PANES_KEY, JSON.stringify(normalizePanes(config))) } catch (error) { void error }
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
/* ============================== dock actions ============================== */
|
|
4535
|
+
|
|
4536
|
+
/* ---- dock action core (pure: tests/smoke.mjs slices this block) ---- */
|
|
4537
|
+
|
|
4538
|
+
/* The official right sidebar (ui-sidebar-right) is the only surface that can
|
|
4539
|
+
float a tab out, dock it back, split its pane or take it full screen, and it
|
|
4540
|
+
is NOT there on every host: with dsh-better-sidebar loaded this panel lives
|
|
4541
|
+
in THAT dock, an old host has no sidebarRight at all, and even on a host that
|
|
4542
|
+
ships it the service is only provided once ui-sidebar-right has mounted —
|
|
4543
|
+
reading it during apply() returns undefined (measured on 0.1.7-rc.2). So the
|
|
4544
|
+
service is read at render time, and a missing one means these actions do not
|
|
4545
|
+
exist for this mount: no button, no error, no console noise. */
|
|
4546
|
+
function dockFaceOf(ctx) {
|
|
4547
|
+
if (ctx === null || ctx === undefined || typeof ctx.get !== 'function') return null
|
|
4548
|
+
let service
|
|
4549
|
+
try { service = ctx.get('sidebarRight') } catch (error) { return null }
|
|
4550
|
+
if (service === null || service === undefined || typeof service !== 'object') return null
|
|
4551
|
+
if (typeof service.float !== 'function' || typeof service.dock !== 'function') return null
|
|
4552
|
+
return service
|
|
4553
|
+
}
|
|
4554
|
+
|
|
4555
|
+
/* What this mount can offer. `dock` is the wiring handed down to the panel
|
|
4556
|
+
(null when nothing is wired, e.g. the better-sidebar door), `floatPaneId` is
|
|
4557
|
+
the floating pane the panel is currently drawn in, read from the float
|
|
4558
|
+
layer's own marker. The face publishes operations only — no layout snapshot —
|
|
4559
|
+
so the float state has to come from there. */
|
|
4560
|
+
function dockActionState(dock, floatPaneId) {
|
|
4561
|
+
const wired = dock !== null && typeof dock === 'object' && typeof dock.tabId === 'string'
|
|
4562
|
+
&& dock.tabId !== '' && dock.service !== null && typeof dock.service === 'object'
|
|
4563
|
+
const floating = typeof floatPaneId === 'string' && floatPaneId !== ''
|
|
4564
|
+
return {
|
|
4565
|
+
wired: wired,
|
|
4566
|
+
floating: floating,
|
|
4567
|
+
float: wired,
|
|
4568
|
+
/* A float panel hosts exactly one tab: splitting it is not a thing. */
|
|
4569
|
+
split: wired && floating === false,
|
|
4570
|
+
fullscreen: wired && typeof dock.service.toggleFullscreen === 'function',
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
/* ---- end dock action core ---- */
|
|
4574
|
+
|
|
4575
|
+
/* The slot hands every tab body its own reader; it THROWS while the tab is not
|
|
4576
|
+
committed in the session, so an unreadable tab means no dock actions rather
|
|
4577
|
+
than a broken panel. Called from the component that owns the hook. */
|
|
4578
|
+
function readDockInfo(useTabInfo, ctx) {
|
|
4579
|
+
if (typeof useTabInfo !== 'function') return null
|
|
4580
|
+
const service = dockFaceOf(ctx)
|
|
4581
|
+
if (service === null) return null
|
|
4582
|
+
let info = null
|
|
4583
|
+
try { info = useTabInfo() } catch (error) { void error }
|
|
4584
|
+
if (info === null || typeof info !== 'object' || info.tab === undefined || info.panel === undefined) return null
|
|
4585
|
+
if (typeof info.tab.id !== 'string') return null
|
|
4586
|
+
return {
|
|
4587
|
+
service: service,
|
|
4588
|
+
tabId: info.tab.id,
|
|
4589
|
+
paneId: typeof info.panel.id === 'string' ? info.panel.id : '',
|
|
4590
|
+
fullscreen: info.sidebar !== undefined && info.sidebar !== null && info.sidebar.fullscreen === true,
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
|
|
3494
4594
|
/* ============================== formatting ============================== */
|
|
3495
4595
|
|
|
3496
4596
|
function relativeTime(iso) {
|
|
@@ -3734,7 +4834,11 @@ window.__ModuleLoader__.load({
|
|
|
3734
4834
|
compare: ['M4 4h7', 'M4 12h7', 'M6.5 1.5 4 4l2.5 2.5', 'M9.5 9.5 12 12l-2.5 2.5'],
|
|
3735
4835
|
stash: ['M2.5 3.5h11V6h-11z', 'M3.5 6v6.5h9V6', 'M6.5 8.5h3'],
|
|
3736
4836
|
tag: ['M2.5 7.5 7.5 2.5h6v6l-5 5z', 'M10.5 5.5h.01'],
|
|
3737
|
-
|
|
4837
|
+
// A real gear: the toothed contour plus the centre hole, borrowed from the
|
|
4838
|
+
// official product icon set (ui-primitives IconSettingsOutlineMedium, same
|
|
4839
|
+
// 16x16 grid and currentColor stroke) because the previous entry was a
|
|
4840
|
+
// centre dot with eight rays — a sunburst, not a settings control.
|
|
4841
|
+
settings: ['M13.0107 7.79377C12.9505 7.89401 12.9205 7.94413 12.9205 7.99951C12.9205 8.0549 12.9505 8.10502 13.0106 8.20528L13.9849 9.83006C14.045 9.93029 14.0751 9.9804 14.0751 10.0358C14.0751 10.0911 14.045 10.1413 13.9849 10.2415L13.0037 11.8777C12.9468 11.9726 12.9184 12.0201 12.8725 12.0461C12.8267 12.072 12.7713 12.072 12.6607 12.072H10.6704C10.5598 12.072 10.5045 12.072 10.4586 12.098C10.4128 12.1239 10.3843 12.1714 10.3274 12.2662L9.33825 13.9142C9.28133 14.009 9.25287 14.0564 9.20703 14.0823C9.16118 14.1083 9.10588 14.1083 8.99529 14.1083H7.00486C6.89426 14.1083 6.83896 14.1083 6.79312 14.0823C6.74727 14.0564 6.71881 14.009 6.6619 13.9142L5.67273 12.2662C5.61581 12.1714 5.58735 12.1239 5.54151 12.098C5.49566 12.072 5.44036 12.072 5.32977 12.072H3.33945C3.2288 12.072 3.17347 12.072 3.12761 12.0461C3.08176 12.0201 3.0533 11.9726 2.9964 11.8777L2.0152 10.2415C1.9551 10.1413 1.92505 10.0911 1.92505 10.0358C1.92505 9.9804 1.9551 9.93029 2.0152 9.83006L2.98951 8.20528C3.04963 8.10502 3.07969 8.0549 3.07969 7.99951C3.07968 7.94413 3.04961 7.89401 2.98946 7.79377L2.01529 6.17011C1.95514 6.06987 1.92507 6.01975 1.92507 5.96437C1.92506 5.90899 1.95512 5.85886 2.01524 5.7586L2.9964 4.1224C3.0533 4.0275 3.08176 3.98005 3.12761 3.95408C3.17347 3.92811 3.2288 3.92811 3.33945 3.92811H5.32977C5.44036 3.92811 5.49566 3.92811 5.54151 3.90216C5.58735 3.87621 5.61581 3.82879 5.67273 3.73397L6.6619 2.08599C6.71881 1.99116 6.74727 1.94375 6.79312 1.9178C6.83896 1.89185 6.89426 1.89185 7.00486 1.89185H8.99529C9.10588 1.89185 9.16118 1.89185 9.20703 1.9178C9.25287 1.94375 9.28133 1.99116 9.33825 2.08599L10.3274 3.73397C10.3843 3.82879 10.4128 3.87621 10.4586 3.90216C10.5045 3.92811 10.5598 3.92811 10.6704 3.92811H12.6607C12.7713 3.92811 12.8267 3.92811 12.8725 3.95408C12.9184 3.98005 12.9468 4.0275 13.0037 4.1224L13.9849 5.7586C14.045 5.85886 14.0751 5.90899 14.0751 5.96437C14.0751 6.01975 14.045 6.06987 13.9849 6.17011L13.0107 7.79377Z', 'M8 9.75012C8.9665 9.75012 9.75 8.96662 9.75 8.00012C9.75 7.03362 8.9665 6.25012 8 6.25012C7.0335 6.25012 6.25 7.03362 6.25 8.00012C6.25 8.96662 7.0335 9.75012 8 9.75012Z'],
|
|
3738
4842
|
more: ['M3.6 8h0.01', 'M8 8h0.01', 'M12.4 8h0.01'],
|
|
3739
4843
|
eye: ['M1.5 8S4.1 3.8 8 3.8 14.5 8 14.5 8 11.9 12.2 8 12.2 1.5 8 1.5 8Z', 'M8 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z'],
|
|
3740
4844
|
eyeOff: ['M1.6 8S4.2 4 8 4c1 0 1.9.2 2.7.6', 'M13 5.6c.9 1.1 1.5 2.4 1.5 2.4S11.9 12 8 12c-.9 0-1.7-.2-2.4-.5', 'M2.6 2.6l10.8 10.8'],
|
|
@@ -3743,6 +4847,11 @@ window.__ModuleLoader__.load({
|
|
|
3743
4847
|
grip: ['M5.6 4.6h4.8', 'M5.6 8h4.8', 'M5.6 11.4h4.8'],
|
|
3744
4848
|
filter: ['M2 3.6h12l-4.6 5.2v4.2l-2.8-1.4V8.8z'],
|
|
3745
4849
|
check: ['M3.2 8.4 6.4 11.6 12.8 4.6'],
|
|
4850
|
+
// dock actions (official right sidebar): lift a panel out, drop it back into
|
|
4851
|
+
// the dock, split the pane, and the full-screen pair reuses expand/collapse.
|
|
4852
|
+
float: ['M2.5 6.5v7h7v-3', 'M6 10 13.2 2.8', 'M9.4 2.8h3.8v3.8'],
|
|
4853
|
+
dock: ['M2.5 3h11v10h-11z', 'M8 5.2v5.6', 'M5.4 8.2 8 10.8l2.6-2.6'],
|
|
4854
|
+
split: ['M2.5 3h11v10h-11z', 'M8 3v10'],
|
|
3746
4855
|
expand: ['M4.5 6.2 8 9.7l3.5-3.5', 'M3 12.4h10'],
|
|
3747
4856
|
collapse: ['M4.5 9.8 8 6.3l3.5 3.5', 'M3 3.6h10'],
|
|
3748
4857
|
}
|
|
@@ -3821,6 +4930,9 @@ window.__ModuleLoader__.load({
|
|
|
3821
4930
|
+ (item.danger === true ? ' dig-menu-danger' : '')
|
|
3822
4931
|
+ (item.disabled === true ? ' dig-menu-disabled' : '')
|
|
3823
4932
|
+ (item.active === true ? ' dig-menu-item-active' : ''),
|
|
4933
|
+
// The row keeps its id on the DOM so a `⋯ 更多` entry is addressable
|
|
4934
|
+
// (the rail's own buttons do the same; probes click through either).
|
|
4935
|
+
'data-action': item.id === undefined ? undefined : item.id,
|
|
3824
4936
|
disabled: item.disabled === true,
|
|
3825
4937
|
onClick: (event) => { closeRef.current(); item.run(event) },
|
|
3826
4938
|
},
|
|
@@ -3832,6 +4944,264 @@ window.__ModuleLoader__.load({
|
|
|
3832
4944
|
list is drag-sortable and arrow-sortable (the arrows also work with a
|
|
3833
4945
|
keyboard); visibility is a per-action toggle, not a delete, so a hidden
|
|
3834
4946
|
action keeps its position. */
|
|
4947
|
+
/* The plugin's settings card. It is the SAME surface on the plugin's detail
|
|
4948
|
+
page (slot `plugins.bundle.config`, keyed by the PACKAGE name) and in
|
|
4949
|
+
Settings → Plugins (slot `settings.plugin.item`, keyed by the settings
|
|
4950
|
+
namespace = the loader row id), and it is the only editor when the host
|
|
4951
|
+
serves the row Config — the in-panel editor is not reachable there.
|
|
4952
|
+
|
|
4953
|
+
A host without that service still renders the card (the seat exists), so it
|
|
4954
|
+
says so instead of pretending to have settings. */
|
|
4955
|
+
function RailSettingsCard(props) {
|
|
4956
|
+
const t = props.t
|
|
4957
|
+
const [tick, setTick] = useState(0)
|
|
4958
|
+
const [pending, setPending] = useState({})
|
|
4959
|
+
const [note, setNote] = useState('')
|
|
4960
|
+
/* The three AI-commit settings are free text, so they are drafted locally
|
|
4961
|
+
and written on blur / Enter — one write per edit, never one per keystroke. */
|
|
4962
|
+
const [textDraft, setTextDraft] = useState({})
|
|
4963
|
+
/* The user's latest intent per field, written SYNCHRONOUSLY. React state is
|
|
4964
|
+
not enough: two clicks inside one frame would both read the pre-click
|
|
4965
|
+
snapshot, so the second click recomputed the same target and the toggle
|
|
4966
|
+
was lost (measured: a fast double click on one switch netted one step
|
|
4967
|
+
instead of returning to where it started). */
|
|
4968
|
+
const intent = useRef({})
|
|
4969
|
+
const timers = useRef(new Set())
|
|
4970
|
+
const dirty = useRef(new Map())
|
|
4971
|
+
const pendingFlush = useRef(null)
|
|
4972
|
+
const bump = () => setTick((value) => value + 1)
|
|
4973
|
+
/* Writes the Host answers ok for can still be lost when several overlap its
|
|
4974
|
+
persist cycle: measured on this surface, 16 switches toggled 55ms apart
|
|
4975
|
+
left 3 fields at their old value in the row Config while every request
|
|
4976
|
+
answered 200, and the same 16 at 500ms apart all landed. So a switch is
|
|
4977
|
+
only "done" when the accepted document agrees — checked once after the
|
|
4978
|
+
burst settles, re-issued once, and otherwise rolled back and reported. */
|
|
4979
|
+
const reconcile = (field, want, attempt) => {
|
|
4980
|
+
const timer = setTimeout(() => {
|
|
4981
|
+
timers.current.delete(timer)
|
|
4982
|
+
let hostValue
|
|
4983
|
+
try {
|
|
4984
|
+
const snapshot = scope === null || scope === undefined ? null : scope.getSnapshot()
|
|
4985
|
+
hostValue = snapshot !== null && snapshot !== undefined && snapshot.value !== null && typeof snapshot.value === 'object' ? snapshot.value[field] : undefined
|
|
4986
|
+
} catch (error) { hostValue = undefined }
|
|
4987
|
+
if (hostValue === want) return
|
|
4988
|
+
if (attempt === 0) {
|
|
4989
|
+
const again = writeRailField(field, want, scope)
|
|
4990
|
+
if (again.sent === true) {
|
|
4991
|
+
if (again.settled !== undefined) void again.settled.then(() => { reconcile(field, want, 1) })
|
|
4992
|
+
else reconcile(field, want, 1)
|
|
4993
|
+
return
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
delete intent.current[field]
|
|
4997
|
+
setPending((now) => { const copy = Object.assign({}, now); delete copy[field]; return copy })
|
|
4998
|
+
setNote(t('settings.rail.writeFailed'))
|
|
4999
|
+
}, 600)
|
|
5000
|
+
timers.current.add(timer)
|
|
5001
|
+
}
|
|
5002
|
+
const rollbackFields = (fields) => {
|
|
5003
|
+
for (const field of fields) {
|
|
5004
|
+
delete intent.current[field]
|
|
5005
|
+
setPending((now) => { const copy = Object.assign({}, now); delete copy[field]; return copy })
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
/* One atomic mutation for the whole burst. Sixteen separate writes 55ms
|
|
5009
|
+
apart lost 3 fields inside the Host's persist cycle while every request
|
|
5010
|
+
answered 200 (the same sixteen 500ms apart all landed), so the fix is to
|
|
5011
|
+
stop producing a burst: collect the fields the user touched and submit
|
|
5012
|
+
them together, at most one mutation per window. */
|
|
5013
|
+
const commitTextField = (field, max) => {
|
|
5014
|
+
const accepted = values[field] === undefined || values[field] === null ? '' : String(values[field])
|
|
5015
|
+
const shown = Object.prototype.hasOwnProperty.call(textDraft, field) ? textDraft[field] : accepted
|
|
5016
|
+
const wanted = shown.slice(0, max)
|
|
5017
|
+
if (wanted === accepted) return
|
|
5018
|
+
const outcome = writeRailFields([{ op: 'set', path: [field], value: wanted }], scope)
|
|
5019
|
+
if (outcome.sent !== true) {
|
|
5020
|
+
setNote(t(outcome.reason === 'read-only' ? 'settings.rail.readonly' : 'settings.rail.writeFailed'))
|
|
5021
|
+
return
|
|
5022
|
+
}
|
|
5023
|
+
if (outcome.settled === undefined) return
|
|
5024
|
+
void outcome.settled.then((ok) => {
|
|
5025
|
+
if (ok !== true) { setNote(t('settings.rail.writeFailed')); return }
|
|
5026
|
+
/* The accepted document now carries it: drop the draft so the control
|
|
5027
|
+
shows the Host's own value again. */
|
|
5028
|
+
setTextDraft((now) => { const copy = Object.assign({}, now); delete copy[field]; return copy })
|
|
5029
|
+
})
|
|
5030
|
+
}
|
|
5031
|
+
const textRow = (field, labelKey, hintKey, max, multiline) => {
|
|
5032
|
+
const accepted = values[field] === undefined || values[field] === null ? '' : String(values[field])
|
|
5033
|
+
const shown = Object.prototype.hasOwnProperty.call(textDraft, field) ? textDraft[field] : accepted
|
|
5034
|
+
const id = 'dig-' + field
|
|
5035
|
+
const common = {
|
|
5036
|
+
id: id, className: 'dig-input dig-settings-input', value: shown, spellCheck: false,
|
|
5037
|
+
disabled: readOnly === true, placeholder: t(hintKey), 'aria-labelledby': id + '-label',
|
|
5038
|
+
onChange: (event) => setTextDraft((now) => Object.assign({}, now, { [field]: event.target.value })),
|
|
5039
|
+
onBlur: () => commitTextField(field, max),
|
|
5040
|
+
}
|
|
5041
|
+
if (multiline === true) common.onKeyDown = (event) => { if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) commitTextField(field, max) }
|
|
5042
|
+
else common.onKeyDown = (event) => { if (event.key === 'Enter') commitTextField(field, max) }
|
|
5043
|
+
return E('div', { className: 'dig-settings-text', key: field },
|
|
5044
|
+
E('label', { id: id + '-label', className: 'dig-settings-text-label', htmlFor: id }, t(labelKey)),
|
|
5045
|
+
multiline === true ? E('textarea', Object.assign({ rows: 3 }, common)) : E('input', Object.assign({ type: 'text' }, common)),
|
|
5046
|
+
shown.length >= max ? E('div', { className: 'dig-settings-error' }, fill(t('settings.commit.tooLong'), { n: String(max) })) : null)
|
|
5047
|
+
}
|
|
5048
|
+
const flush = () => {
|
|
5049
|
+
pendingFlush.current = null
|
|
5050
|
+
/* The pending TARGET per field, not "whatever the render-time intent
|
|
5051
|
+
happens to hold now": the accepted document can already agree with the
|
|
5052
|
+
user's new value, which clears the optimistic intent before this flush
|
|
5053
|
+
runs — reading it here would then submit the opposite of the click and
|
|
5054
|
+
write the switch back to the wrong state. */
|
|
5055
|
+
const targets = new Map(dirty.current)
|
|
5056
|
+
dirty.current.clear()
|
|
5057
|
+
const fields = Array.from(targets.keys())
|
|
5058
|
+
if (fields.length === 0) return
|
|
5059
|
+
const ops = fields.map((field) => ({ op: 'set', path: [field], value: targets.get(field) === true }))
|
|
5060
|
+
const outcome = writeRailFields(ops, scope)
|
|
5061
|
+
if (outcome.sent !== true) {
|
|
5062
|
+
rollbackFields(fields)
|
|
5063
|
+
setNote(t(outcome.reason === 'read-only' ? 'settings.rail.readonly' : 'settings.rail.writeFailed'))
|
|
5064
|
+
return
|
|
5065
|
+
}
|
|
5066
|
+
if (outcome.settled === undefined) return
|
|
5067
|
+
void outcome.settled.then((accepted) => {
|
|
5068
|
+
if (accepted !== true) {
|
|
5069
|
+
rollbackFields(fields)
|
|
5070
|
+
setNote(t('settings.rail.writeFailed'))
|
|
5071
|
+
return
|
|
5072
|
+
}
|
|
5073
|
+
for (const field of fields) reconcile(field, targets.get(field) === true, 0)
|
|
5074
|
+
})
|
|
5075
|
+
}
|
|
5076
|
+
const scheduleFlush = () => {
|
|
5077
|
+
if (pendingFlush.current !== null) return
|
|
5078
|
+
pendingFlush.current = setTimeout(() => { pendingFlush.current = null; flush() }, FLUSH_MS)
|
|
5079
|
+
timers.current.add(pendingFlush.current)
|
|
5080
|
+
}
|
|
5081
|
+
/* Two handles, one document: the slot hands the card the framework's own
|
|
5082
|
+
form for this entry (`props.scope`), and our ctx.configForms handle is the
|
|
5083
|
+
fallback — both address the same namespace, so a write through either is
|
|
5084
|
+
the same row Config write. */
|
|
5085
|
+
const scope = props.scope !== undefined && props.scope !== null && typeof props.scope.getSnapshot === 'function'
|
|
5086
|
+
? props.scope
|
|
5087
|
+
: railForm
|
|
5088
|
+
useEffect(() => {
|
|
5089
|
+
const offRail = subscribeRailConfig(bump)
|
|
5090
|
+
let offScope
|
|
5091
|
+
if (scope !== null && scope !== undefined && typeof scope.subscribe === 'function') {
|
|
5092
|
+
const unsubscribe = scope.subscribe(bump)
|
|
5093
|
+
if (typeof unsubscribe === 'function') offScope = unsubscribe
|
|
5094
|
+
}
|
|
5095
|
+
return () => {
|
|
5096
|
+
offRail()
|
|
5097
|
+
if (offScope !== undefined) offScope()
|
|
5098
|
+
for (const timer of Array.from(timers.current)) clearTimeout(timer)
|
|
5099
|
+
timers.current.clear()
|
|
5100
|
+
}
|
|
5101
|
+
}, [])
|
|
5102
|
+
void tick
|
|
5103
|
+
const head = E('div', { className: 'dig-settings-head' },
|
|
5104
|
+
E('div', { className: 'dig-settings-title' }, t('settings.rail.title')),
|
|
5105
|
+
E('div', { className: 'dig-settings-hint' }, t('settings.rail.hint')))
|
|
5106
|
+
let snapshot = null
|
|
5107
|
+
try { snapshot = scope === null || scope === undefined ? null : scope.getSnapshot() } catch (error) { void error }
|
|
5108
|
+
const status = snapshot === null || snapshot === undefined || typeof snapshot.status !== 'string' ? 'absent' : snapshot.status
|
|
5109
|
+
const values = snapshot !== null && snapshot !== undefined && snapshot.value !== null && typeof snapshot.value === 'object' ? snapshot.value : null
|
|
5110
|
+
// Drop optimistic values the host has since confirmed (post-commit effect).
|
|
5111
|
+
useEffect(() => {
|
|
5112
|
+
setPending((current) => {
|
|
5113
|
+
const keys = Object.keys(current)
|
|
5114
|
+
if (keys.length === 0 || values === null) return current
|
|
5115
|
+
const next = {}
|
|
5116
|
+
let changed = false
|
|
5117
|
+
for (const key of keys) {
|
|
5118
|
+
if (values[key] === current[key]) {
|
|
5119
|
+
changed = true
|
|
5120
|
+
/* The accepted document agrees: this intent is no longer pending.
|
|
5121
|
+
Deleting twice is harmless, so a re-invoked updater stays safe. */
|
|
5122
|
+
delete intent.current[key]
|
|
5123
|
+
continue
|
|
5124
|
+
}
|
|
5125
|
+
next[key] = current[key]
|
|
5126
|
+
}
|
|
5127
|
+
return changed ? next : current
|
|
5128
|
+
})
|
|
5129
|
+
})
|
|
5130
|
+
// Switches exist only where they can actually write: a host that does not
|
|
5131
|
+
// serve this entry's settings gets a sentence instead of dead controls.
|
|
5132
|
+
if (status !== 'ready' || values === null) {
|
|
5133
|
+
return E('div', { className: 'dig-settings', 'data-settings-status': status }, head,
|
|
5134
|
+
E('div', { className: 'dig-settings-note' }, t('settings.rail.unavailable')))
|
|
5135
|
+
}
|
|
5136
|
+
const config = railConfigOfValues(values)
|
|
5137
|
+
/* A toggle is a host round trip, so the switch would snap back to the old
|
|
5138
|
+
value for a moment and read as "stuck". The optimistic value wins until
|
|
5139
|
+
the accepted document agrees with it (then it is dropped). */
|
|
5140
|
+
const shown = (spec) => {
|
|
5141
|
+
const field = railFieldOf(spec.id)
|
|
5142
|
+
if (Object.prototype.hasOwnProperty.call(intent.current, field)) return intent.current[field]
|
|
5143
|
+
return config.hidden.indexOf(spec.id) < 0
|
|
5144
|
+
}
|
|
5145
|
+
/* A host that serves the document but refuses writes gets disabled switches
|
|
5146
|
+
and a sentence, not controls that silently do nothing. */
|
|
5147
|
+
const readOnly = snapshot.writable !== true
|
|
5148
|
+
return E('div', { className: 'dig-settings', 'data-settings-status': status, 'data-settings-readonly': readOnly === true ? 'true' : 'false' }, head,
|
|
5149
|
+
E('div', { className: readOnly === true ? 'dig-settings-note' : 'dig-settings-note dig-settings-note-hidden', 'data-settings-readonly-note': '' }, t('settings.rail.readonly')),
|
|
5150
|
+
note === '' ? null : E('div', { className: 'dig-settings-error', 'data-settings-error': '' }, note),
|
|
5151
|
+
E('div', { className: 'dig-settings-list' }, RAIL_SPECS.map((spec) => {
|
|
5152
|
+
const inputId = 'dig-rail-' + spec.id
|
|
5153
|
+
const visible = shown(spec)
|
|
5154
|
+
/* A row, not a <label>: a label that both carries htmlFor and wraps its
|
|
5155
|
+
own input toggles twice per click (once for the input, once for the
|
|
5156
|
+
label activation), which reads as a stuck switch and writes twice. */
|
|
5157
|
+
const toggle = (event) => {
|
|
5158
|
+
if (readOnly === true) { setNote(t('settings.rail.readonly')); return }
|
|
5159
|
+
const field = railFieldOf(spec.id)
|
|
5160
|
+
/* The control's own answer when the browser gives it to us: a click that
|
|
5161
|
+
lands while the previous one is still rendering would otherwise
|
|
5162
|
+
compose from the pre-click snapshot and be lost (measured: a fast
|
|
5163
|
+
double click netted one step instead of returning to its start). The
|
|
5164
|
+
row's label span is not a control, so it falls back to the
|
|
5165
|
+
synchronous intent — never to the render snapshot alone. */
|
|
5166
|
+
const fromControl = event !== undefined && event !== null && event.target !== null
|
|
5167
|
+
&& event.target !== undefined && typeof event.target.checked === 'boolean'
|
|
5168
|
+
? event.target.checked
|
|
5169
|
+
: null
|
|
5170
|
+
/* The control already holds the state the user just asked for, so it is
|
|
5171
|
+
the TARGET — inverting it here would write the opposite of the click.
|
|
5172
|
+
Without a control (the row's label span) the target is the inverse of
|
|
5173
|
+
the current intent, falling back to the accepted document. */
|
|
5174
|
+
const wanted = fromControl !== null
|
|
5175
|
+
? fromControl
|
|
5176
|
+
: (Object.prototype.hasOwnProperty.call(intent.current, field)
|
|
5177
|
+
? intent.current[field] !== true
|
|
5178
|
+
: config.hidden.indexOf(spec.id) < 0 !== true)
|
|
5179
|
+
intent.current[field] = wanted
|
|
5180
|
+
dirty.current.set(field, wanted)
|
|
5181
|
+
setPending((now) => Object.assign({}, now, { [field]: wanted }))
|
|
5182
|
+
setNote('')
|
|
5183
|
+
bump()
|
|
5184
|
+
scheduleFlush()
|
|
5185
|
+
}
|
|
5186
|
+
return E('div', { key: spec.id, className: 'dig-settings-row', 'data-rail-action': spec.id },
|
|
5187
|
+
E('input', {
|
|
5188
|
+
id: inputId,
|
|
5189
|
+
type: 'checkbox',
|
|
5190
|
+
className: 'dig-settings-check',
|
|
5191
|
+
'aria-labelledby': inputId + '-label',
|
|
5192
|
+
checked: visible,
|
|
5193
|
+
disabled: readOnly === true,
|
|
5194
|
+
onChange: toggle,
|
|
5195
|
+
}),
|
|
5196
|
+
E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: spec.icon, size: 13 })),
|
|
5197
|
+
E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t(spec.key)))
|
|
5198
|
+
})),
|
|
5199
|
+
E('div', { className: 'dig-settings-sub' }, t('settings.commit.title')),
|
|
5200
|
+
textRow('commitModel', 'settings.commit.model', 'settings.commit.modelHint', 120, false),
|
|
5201
|
+
textRow('commitReasoning', 'settings.commit.reasoning', 'settings.commit.reasoningHint', 60, false),
|
|
5202
|
+
textRow('commitPrompt', 'settings.commit.prompt', 'settings.commit.promptHint', 2000, true))
|
|
5203
|
+
}
|
|
5204
|
+
|
|
3835
5205
|
function RailSettings(props) {
|
|
3836
5206
|
const t = props.t
|
|
3837
5207
|
const [dragId, setDragId] = useState(null)
|
|
@@ -3960,6 +5330,77 @@ window.__ModuleLoader__.load({
|
|
|
3960
5330
|
}, item.label)))
|
|
3961
5331
|
}
|
|
3962
5332
|
|
|
5333
|
+
/* A draggable divider between two panes. The pane it resizes is the sibling
|
|
5334
|
+
it is rendered against, and its size is MEASURED at pointerdown — so a pane
|
|
5335
|
+
still living on its stylesheet default (a percentage cap, or a diff pane
|
|
5336
|
+
sized by its own content) drags from exactly where it looks. Pointer capture
|
|
5337
|
+
keeps the drag alive once the pointer leaves the 8px hit area, and
|
|
5338
|
+
touch-action:none is what stops a phone from scrolling the panel instead of
|
|
5339
|
+
resizing it. Arrow keys nudge (Shift = 4x), double-click resets, and the
|
|
5340
|
+
whole thing is focusable so the divider is reachable without a mouse. */
|
|
5341
|
+
function Gutter(props) {
|
|
5342
|
+
const dragRef = useRef(null)
|
|
5343
|
+
const targetOf = (node) => (props.side === 'next' ? node.nextElementSibling : node.previousElementSibling)
|
|
5344
|
+
const extentOf = (node) => {
|
|
5345
|
+
if (node === null || typeof node.getBoundingClientRect !== 'function') return 0
|
|
5346
|
+
const rect = node.getBoundingClientRect()
|
|
5347
|
+
return props.vertical === true ? rect.width : rect.height
|
|
5348
|
+
}
|
|
5349
|
+
const sign = props.side === 'next' ? -1 : 1
|
|
5350
|
+
const onPointerDown = (event) => {
|
|
5351
|
+
if (event.button !== undefined && event.button !== 0) return
|
|
5352
|
+
dragRef.current = {
|
|
5353
|
+
id: event.pointerId,
|
|
5354
|
+
at: props.vertical === true ? event.clientX : event.clientY,
|
|
5355
|
+
size: extentOf(targetOf(event.currentTarget)),
|
|
5356
|
+
}
|
|
5357
|
+
if (typeof event.currentTarget.setPointerCapture === 'function') event.currentTarget.setPointerCapture(event.pointerId)
|
|
5358
|
+
if (typeof event.currentTarget.focus === 'function') event.currentTarget.focus()
|
|
5359
|
+
props.onStart()
|
|
5360
|
+
}
|
|
5361
|
+
const onPointerMove = (event) => {
|
|
5362
|
+
const drag = dragRef.current
|
|
5363
|
+
if (drag === null || drag.id !== event.pointerId) return
|
|
5364
|
+
const at = props.vertical === true ? event.clientX : event.clientY
|
|
5365
|
+
props.onDrag(drag.size + sign * (at - drag.at))
|
|
5366
|
+
}
|
|
5367
|
+
const onPointerUp = () => {
|
|
5368
|
+
if (dragRef.current === null) return
|
|
5369
|
+
dragRef.current = null
|
|
5370
|
+
props.onEnd()
|
|
5371
|
+
}
|
|
5372
|
+
const onKeyDown = (event) => {
|
|
5373
|
+
const step = event.shiftKey === true ? 32 : 8
|
|
5374
|
+
const axis = props.vertical === true
|
|
5375
|
+
? (event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0)
|
|
5376
|
+
: (event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0)
|
|
5377
|
+
if (axis === 0) return
|
|
5378
|
+
event.preventDefault()
|
|
5379
|
+
props.onDrag(extentOf(targetOf(event.currentTarget)) + sign * axis * step)
|
|
5380
|
+
props.onEnd()
|
|
5381
|
+
}
|
|
5382
|
+
const label = fill(props.t('pane.resize'), { name: props.t(props.nameKey) })
|
|
5383
|
+
return E('div', {
|
|
5384
|
+
className: 'dig-gutter ' + (props.vertical === true ? 'dig-gutter-v' : 'dig-gutter-h')
|
|
5385
|
+
+ (props.active === true ? ' dig-gutter-active' : ''),
|
|
5386
|
+
role: 'separator',
|
|
5387
|
+
tabIndex: 0,
|
|
5388
|
+
'aria-orientation': props.vertical === true ? 'vertical' : 'horizontal',
|
|
5389
|
+
'aria-label': label,
|
|
5390
|
+
'aria-valuenow': props.value,
|
|
5391
|
+
'aria-valuemin': props.min,
|
|
5392
|
+
'aria-valuemax': props.max,
|
|
5393
|
+
title: label,
|
|
5394
|
+
onPointerDown: onPointerDown,
|
|
5395
|
+
onPointerMove: onPointerMove,
|
|
5396
|
+
onPointerUp: onPointerUp,
|
|
5397
|
+
onPointerCancel: onPointerUp,
|
|
5398
|
+
onLostPointerCapture: onPointerUp,
|
|
5399
|
+
onDoubleClick: () => props.onReset(),
|
|
5400
|
+
onKeyDown: onKeyDown,
|
|
5401
|
+
})
|
|
5402
|
+
}
|
|
5403
|
+
|
|
3963
5404
|
function RepoSelect(props) {
|
|
3964
5405
|
const t = props.t
|
|
3965
5406
|
/* While the workspace is still being scanned the panel used to print "no
|
|
@@ -4189,6 +5630,12 @@ window.__ModuleLoader__.load({
|
|
|
4189
5630
|
const [message, setMessage] = useState('')
|
|
4190
5631
|
const [amend, setAmend] = useState(false)
|
|
4191
5632
|
const [collapsed, setCollapsed] = useState(false)
|
|
5633
|
+
/* AI-written message (issue #6). The draft is kept so the write can be
|
|
5634
|
+
undone, and an existing draft is never overwritten without a word. */
|
|
5635
|
+
const [aiBusy, setAiBusy] = useState(false)
|
|
5636
|
+
const [aiNote, setAiNote] = useState(null)
|
|
5637
|
+
const [aiAsk, setAiAsk] = useState(false)
|
|
5638
|
+
const [aiUndo, setAiUndo] = useState(null)
|
|
4192
5639
|
// 'flat' keeps git's own grouping (staged / changes / untracked); 'dir'
|
|
4193
5640
|
// clusters each group's files under their folder, the way "Group by:
|
|
4194
5641
|
// Directory" does in an IDE. Folders are addressed by <group>|<dir>, so a
|
|
@@ -4202,6 +5649,32 @@ window.__ModuleLoader__.load({
|
|
|
4202
5649
|
const untracked = changes === null ? [] : changes.untracked
|
|
4203
5650
|
const ignored = changes === null || changes.ignored === undefined ? [] : changes.ignored
|
|
4204
5651
|
const total = conflicted.length + staged.length + unstaged.length + untracked.length
|
|
5652
|
+
const aiRun = async () => {
|
|
5653
|
+
setAiAsk(false)
|
|
5654
|
+
setAiBusy(true)
|
|
5655
|
+
setAiNote(null)
|
|
5656
|
+
try {
|
|
5657
|
+
const settings = props.commitSettings === undefined ? { model: '', reasoning: '', prompt: '' } : props.commitSettings
|
|
5658
|
+
const data = await props.request('commit-message', {
|
|
5659
|
+
cwd: props.cwd,
|
|
5660
|
+
sessionId: props.sessionId,
|
|
5661
|
+
model: settings.model,
|
|
5662
|
+
reasoningEffort: settings.reasoning,
|
|
5663
|
+
prompt: settings.prompt,
|
|
5664
|
+
})
|
|
5665
|
+
setAiUndo(message === '' ? null : message)
|
|
5666
|
+
setMessage(typeof data.message === 'string' ? data.message : '')
|
|
5667
|
+
/* Say what the model did NOT see, instead of quietly sending less. */
|
|
5668
|
+
const input = data.input === undefined ? {} : data.input
|
|
5669
|
+
const incomplete = input.truncated === true || (typeof input.dropped === 'number' && input.dropped > 0) || data.promptTruncated === true
|
|
5670
|
+
setAiNote(incomplete === true ? t('changes.aiNotice') : null)
|
|
5671
|
+
} catch (error) {
|
|
5672
|
+
const code = error !== null && error !== undefined && typeof error.code === 'string' ? error.code : 'unknown'
|
|
5673
|
+
setAiNote(t('changes.aiFailed') + ' [' + code + '] ' + String(error && error.message ? error.message : error))
|
|
5674
|
+
} finally {
|
|
5675
|
+
setAiBusy(false)
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
4205
5678
|
const submit = (push) => {
|
|
4206
5679
|
if (message.trim() === '' || props.busy === true) return false
|
|
4207
5680
|
props.onCommit(message, amend, push === true)
|
|
@@ -4254,6 +5727,15 @@ window.__ModuleLoader__.load({
|
|
|
4254
5727
|
key === 'staged' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onUnstageAll() }, t('changes.unstageAll')) : null,
|
|
4255
5728
|
key === 'unstaged' || key === 'untracked' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onStageAll(entries) }, t('changes.stageAll')) : null),
|
|
4256
5729
|
rowsOf(key, entries))
|
|
5730
|
+
const aiConfirmDialog = aiAsk !== true ? null : E(ConfirmDialog, {
|
|
5731
|
+
t: t,
|
|
5732
|
+
title: t('changes.aiOverwriteTitle'),
|
|
5733
|
+
text: t('changes.aiOverwriteText'),
|
|
5734
|
+
okLabel: t('confirm.ok'),
|
|
5735
|
+
cancelLabel: t('confirm.cancel'),
|
|
5736
|
+
onConfirm: () => { void aiRun() },
|
|
5737
|
+
onCancel: () => setAiAsk(false),
|
|
5738
|
+
})
|
|
4257
5739
|
const composer = props.compact === true
|
|
4258
5740
|
? E('div', { className: 'dig-commit-box dig-commit-box-compact' },
|
|
4259
5741
|
E('input', {
|
|
@@ -4264,11 +5746,18 @@ window.__ModuleLoader__.load({
|
|
|
4264
5746
|
if (event.key === 'Enter') { event.preventDefault(); submit() }
|
|
4265
5747
|
},
|
|
4266
5748
|
}),
|
|
5749
|
+
E('button', {
|
|
5750
|
+
type: 'button', className: 'dig-btn dig-btn-small dig-btn-ai',
|
|
5751
|
+
disabled: aiBusy === true || props.busy === true,
|
|
5752
|
+
title: t('changes.aiQuota'),
|
|
5753
|
+
onClick: () => { if (message.trim() === '') void aiRun(); else setAiAsk(true) },
|
|
5754
|
+
}, aiBusy === true ? t('changes.aiBusy') : t('changes.aiWrite')),
|
|
4267
5755
|
E('button', {
|
|
4268
5756
|
type: 'button', className: 'dig-btn dig-btn-primary dig-btn-small',
|
|
4269
5757
|
disabled: message.trim() === '' || props.busy === true,
|
|
4270
5758
|
onClick: () => { submit(false) },
|
|
4271
|
-
}, t('changes.commit'))
|
|
5759
|
+
}, t('changes.commit')),
|
|
5760
|
+
E('span', { className: 'dig-commit-ai-hint dig-commit-ai-hint-block' }, t('changes.aiQuota')))
|
|
4272
5761
|
: E('div', { className: 'dig-commit-box' },
|
|
4273
5762
|
E('textarea', {
|
|
4274
5763
|
className: 'dig-textarea', value: message, spellCheck: false,
|
|
@@ -4278,6 +5767,18 @@ window.__ModuleLoader__.load({
|
|
|
4278
5767
|
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); submit() }
|
|
4279
5768
|
},
|
|
4280
5769
|
}),
|
|
5770
|
+
E('div', { className: 'dig-commit-ai' },
|
|
5771
|
+
E('button', {
|
|
5772
|
+
type: 'button', className: 'dig-btn dig-btn-small dig-btn-ai',
|
|
5773
|
+
disabled: aiBusy === true || props.busy === true,
|
|
5774
|
+
onClick: () => { if (message.trim() === '') void aiRun(); else setAiAsk(true) },
|
|
5775
|
+
}, aiBusy === true ? t('changes.aiBusy') : t('changes.aiWrite')),
|
|
5776
|
+
E('span', { className: 'dig-commit-ai-hint' }, t('changes.aiQuota')),
|
|
5777
|
+
aiUndo === null ? null : E('button', {
|
|
5778
|
+
type: 'button', className: 'dig-link',
|
|
5779
|
+
onClick: () => { setMessage(aiUndo); setAiUndo(null); setAiNote(null) },
|
|
5780
|
+
}, t('changes.aiUndo'))),
|
|
5781
|
+
aiNote === null ? null : E('div', { className: 'dig-commit-ai-note', 'data-ai-note': '' }, aiNote),
|
|
4281
5782
|
E('div', { className: 'dig-commit-actions' },
|
|
4282
5783
|
E('label', { className: 'dig-check' },
|
|
4283
5784
|
E('input', { type: 'checkbox', checked: amend, onChange: (event) => setAmend(event.target.checked) }),
|
|
@@ -4327,6 +5828,7 @@ window.__ModuleLoader__.load({
|
|
|
4327
5828
|
onClick: () => props.onToggleIgnored(),
|
|
4328
5829
|
}, E(Icon, { name: props.showIgnored === true ? 'eye' : 'eyeOff', size: 13 })))
|
|
4329
5830
|
return E('div', { className: 'dig-changes' },
|
|
5831
|
+
aiConfirmDialog,
|
|
4330
5832
|
head,
|
|
4331
5833
|
E('div', { className: 'dig-changes-body' },
|
|
4332
5834
|
E('div', { className: 'dig-changes-list' },
|
|
@@ -4572,7 +6074,21 @@ window.__ModuleLoader__.load({
|
|
|
4572
6074
|
const [note, setNote] = useState(null)
|
|
4573
6075
|
const [busy, setBusy] = useState(false)
|
|
4574
6076
|
const [view, setView] = useState('history')
|
|
4575
|
-
|
|
6077
|
+
/* Pane sizes are remembered per surface (see the pane sizing core). The ref
|
|
6078
|
+
is the value the pointer handlers write through: a drag updates state for
|
|
6079
|
+
the preview and the ref for the single write that pointerup performs. */
|
|
6080
|
+
const [panes, setPanes] = useState(readPanes)
|
|
6081
|
+
const panesRef = useRef(panes)
|
|
6082
|
+
const [treeOpen, setTreeOpen] = useState(panes.treeOpen)
|
|
6083
|
+
const [paneDragging, setPaneDragging] = useState(null)
|
|
6084
|
+
const [paneSizes, setPaneSizes] = useState({})
|
|
6085
|
+
/* The float pane this panel is drawn in, or null while it is docked. The
|
|
6086
|
+
dock face exposes operations only, so the state comes from the float
|
|
6087
|
+
layer's own marker around this panel — the same marker carries the pane
|
|
6088
|
+
id dock() needs to put it back. */
|
|
6089
|
+
const [floatPaneId, setFloatPaneId] = useState(null)
|
|
6090
|
+
const bodyRef = useRef(null)
|
|
6091
|
+
const [bodyBox, setBodyBox] = useState({ width: 0, height: 0 })
|
|
4576
6092
|
const [detail, setDetail] = useState(null)
|
|
4577
6093
|
const [selectedHash, setSelectedHash] = useState(null)
|
|
4578
6094
|
const [selectedPath, setSelectedPath] = useState(null)
|
|
@@ -4584,6 +6100,7 @@ window.__ModuleLoader__.load({
|
|
|
4584
6100
|
const [favTick, setFavTick] = useState(0)
|
|
4585
6101
|
const [pathFilter, setPathFilter] = useState('')
|
|
4586
6102
|
const [railConfig, setRailConfig] = useState(readRailConfig)
|
|
6103
|
+
const [railTick, setRailTick] = useState(0)
|
|
4587
6104
|
const [railSettings, setRailSettings] = useState(false)
|
|
4588
6105
|
const [railBox, setRailBox] = useState({ width: 0, height: 0 })
|
|
4589
6106
|
const [toasts, setToasts] = useState([])
|
|
@@ -4619,9 +6136,157 @@ window.__ModuleLoader__.load({
|
|
|
4619
6136
|
// its own — v0.1.3 already made that mistake with a height < 330 test (a
|
|
4620
6137
|
// 1500x300 workbench came out single-column) and v0.3.5 still let
|
|
4621
6138
|
// height < 200 flip a 1320x180 workbench into the right-sidebar chrome.
|
|
6139
|
+
/* Where the gear goes: the plugin's own settings page when this host has
|
|
6140
|
+
both the row Config and the navigation to it, the in-panel editor
|
|
6141
|
+
otherwise (see RAIL_NS / railForm). */
|
|
6142
|
+
const railSettingsTarget = () => (railForm === null || props.openSettings === null || props.openSettings === undefined ? 'panel' : 'page')
|
|
6143
|
+
const openSettingsPage = () => {
|
|
6144
|
+
if (props.openSettings === null || props.openSettings === undefined) return
|
|
6145
|
+
props.openSettings()
|
|
6146
|
+
}
|
|
6147
|
+
|
|
4622
6148
|
const compact = size.width > 0 && size.width < COMPACT_MAX_WIDTH
|
|
4623
6149
|
const columns = !compact && size.width >= 600 && size.width >= size.height * 1.15
|
|
4624
6150
|
|
|
6151
|
+
/* ---------- draggable panes (issue #5) ---------- */
|
|
6152
|
+
|
|
6153
|
+
// Like the rail, the panes measure the box that actually holds them instead
|
|
6154
|
+
// of trusting the root probe: the column chrome keeps a vertical rail
|
|
6155
|
+
// beside the body, and the header sits above it.
|
|
6156
|
+
useEffect(() => {
|
|
6157
|
+
const element = bodyRef.current
|
|
6158
|
+
if (element === null) { setBodyBox({ width: 0, height: 0 }); return undefined }
|
|
6159
|
+
const measure = () => {
|
|
6160
|
+
const rect = element.getBoundingClientRect()
|
|
6161
|
+
setBodyBox({ width: Math.round(rect.width), height: Math.round(rect.height) })
|
|
6162
|
+
}
|
|
6163
|
+
measure()
|
|
6164
|
+
if (typeof ResizeObserver === 'function') {
|
|
6165
|
+
const observer = new ResizeObserver(measure)
|
|
6166
|
+
observer.observe(element)
|
|
6167
|
+
return () => observer.disconnect()
|
|
6168
|
+
}
|
|
6169
|
+
window.addEventListener('resize', measure)
|
|
6170
|
+
return () => window.removeEventListener('resize', measure)
|
|
6171
|
+
}, [])
|
|
6172
|
+
|
|
6173
|
+
const chrome = compact ? 'compact' : columns ? 'columns' : 'stack'
|
|
6174
|
+
const measured = bodyBox.width > 0 || bodyBox.height > 0
|
|
6175
|
+
const paneBox = measured ? bodyBox : size
|
|
6176
|
+
/* The column chrome's dividers are vertical LINES, and the panes they move
|
|
6177
|
+
are sized by width; the stacked chromes move heights. One name for that
|
|
6178
|
+
switch: it decides the divider's orientation, the inline size, the ratio
|
|
6179
|
+
reference and the drag cursor, and getting it half-right is how a height
|
|
6180
|
+
drag ends up writing a width. */
|
|
6181
|
+
const paneByWidth = PANE_HEIGHT_CHROME[chrome] !== true
|
|
6182
|
+
/* What the panes actually measure right now. The clamp needs the real box of
|
|
6183
|
+
the pane it is NOT moving: a content-sized pane is usually shorter than
|
|
6184
|
+
its own percentage cap, and reserving the cap would block the drag (or
|
|
6185
|
+
snap a stored size back on the next render). The markers are data
|
|
6186
|
+
attributes rather than class sniffing so the diff pane, nested inside the
|
|
6187
|
+
middle pane, is found the same way. */
|
|
6188
|
+
const measurePanes = () => {
|
|
6189
|
+
const found = {}
|
|
6190
|
+
const element = bodyRef.current
|
|
6191
|
+
if (element === null) return found
|
|
6192
|
+
for (const node of element.querySelectorAll('[data-pane]')) {
|
|
6193
|
+
const rect = node.getBoundingClientRect()
|
|
6194
|
+
found[node.getAttribute('data-pane')] = Math.round(paneByWidth ? rect.width : rect.height)
|
|
6195
|
+
}
|
|
6196
|
+
return found
|
|
6197
|
+
}
|
|
6198
|
+
/* Kept in state, not in a ref: a pane's box changes without the panel
|
|
6199
|
+
re-rendering (content arrives, a dock resizes), and the render that
|
|
6200
|
+
re-clamps a stored size has to see the same numbers the drag did — a drag
|
|
6201
|
+
allowed further than the render allows snaps back under the cursor. The
|
|
6202
|
+
bail-out comparison is what keeps this from looping. */
|
|
6203
|
+
useEffect(() => {
|
|
6204
|
+
const found = measurePanes()
|
|
6205
|
+
setPaneSizes((current) => (samePaneSizes(current, found) ? current : found))
|
|
6206
|
+
})
|
|
6207
|
+
useEffect(() => {
|
|
6208
|
+
const node = hostRef.current
|
|
6209
|
+
const holder = node === null || typeof node.closest !== 'function' ? null : node.closest('[data-dockkit-float]')
|
|
6210
|
+
const next = holder === null ? null : holder.getAttribute('data-dockkit-float')
|
|
6211
|
+
setFloatPaneId((current) => (current === next ? current : next))
|
|
6212
|
+
})
|
|
6213
|
+
const dock = props.dock === undefined ? null : props.dock
|
|
6214
|
+
const dockState = dockActionState(dock, floatPaneId)
|
|
6215
|
+
const paneLayout = paneGeometry(chrome, paneBox, panes, paneSizes)
|
|
6216
|
+
|
|
6217
|
+
const commitPanes = (next) => {
|
|
6218
|
+
panesRef.current = next
|
|
6219
|
+
setPanes(next)
|
|
6220
|
+
}
|
|
6221
|
+
|
|
6222
|
+
/* The gutter hands back the px it wants; the clamp lives here, and what
|
|
6223
|
+
gets stored is the ratio that reproduces it on this container. The live
|
|
6224
|
+
measurement is pushed into state alongside the new ratio, so the render
|
|
6225
|
+
that follows this gesture clamps with the very same box. */
|
|
6226
|
+
const dragPane = (key, rawPx) => {
|
|
6227
|
+
const live = measurePanes()
|
|
6228
|
+
setPaneSizes((current) => (samePaneSizes(current, live) ? current : live))
|
|
6229
|
+
const layout = paneGeometry(chrome, paneBox, panesRef.current, live)
|
|
6230
|
+
const limit = layout.limits[key]
|
|
6231
|
+
const reference = paneByWidth ? paneBox.width : paneBox.height
|
|
6232
|
+
if (limit === undefined || !(reference > 0)) return
|
|
6233
|
+
const px = Math.round(Math.min(Math.max(rawPx, limit.min), limit.max))
|
|
6234
|
+
commitPanes(withPaneRatio(panesRef.current, layout.bucket, key, px / reference))
|
|
6235
|
+
}
|
|
6236
|
+
|
|
6237
|
+
/* One write per gesture (pointerup / keyboard nudge / double-click), not one
|
|
6238
|
+
per pointermove. */
|
|
6239
|
+
const endPaneDrag = () => {
|
|
6240
|
+
setPaneDragging(null)
|
|
6241
|
+
writePanes(panesRef.current)
|
|
6242
|
+
}
|
|
6243
|
+
|
|
6244
|
+
const resetPane = (key) => {
|
|
6245
|
+
commitPanes(withPaneRatio(panesRef.current, paneLayout.bucket, key, null))
|
|
6246
|
+
writePanes(panesRef.current)
|
|
6247
|
+
}
|
|
6248
|
+
|
|
6249
|
+
const foldTree = (open) => {
|
|
6250
|
+
setTreeOpen(open)
|
|
6251
|
+
commitPanes({ treeOpen: open, panes: panesRef.current.panes })
|
|
6252
|
+
writePanes(panesRef.current)
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
/* Only a pane the user actually dragged gets an inline size; everything
|
|
6256
|
+
else keeps the stylesheet default (200px tree, 290px changes, the
|
|
6257
|
+
percentage caps) — which is also what a double-click returns to. */
|
|
6258
|
+
const paneStyle = (key) => {
|
|
6259
|
+
const px = paneLayout.overrides[key]
|
|
6260
|
+
if (px === undefined) return null
|
|
6261
|
+
return paneByWidth ? { width: px + 'px', maxWidth: px + 'px' } : { height: px + 'px', maxHeight: px + 'px' }
|
|
6262
|
+
}
|
|
6263
|
+
|
|
6264
|
+
/* A divider is a control, so it only exists when it can actually move
|
|
6265
|
+
something: no pane of that key in this chrome, or a window with no travel
|
|
6266
|
+
at all, means no element — and therefore no tab stop either (v0.8.0 shipped
|
|
6267
|
+
a 0x0 focusable separator in the columns chrome that way). */
|
|
6268
|
+
const gutter = (key, side) => {
|
|
6269
|
+
const limit = paneLayout.limits[key]
|
|
6270
|
+
if (limit === undefined || limit.max <= limit.min) return null
|
|
6271
|
+
return E(Gutter, {
|
|
6272
|
+
key: 'gutter-' + key,
|
|
6273
|
+
t: t,
|
|
6274
|
+
nameKey: PANE_NAME_KEYS[key],
|
|
6275
|
+
vertical: paneByWidth,
|
|
6276
|
+
side: side,
|
|
6277
|
+
active: paneDragging === key,
|
|
6278
|
+
// Now is clamped into [min, max]: an aria window the panel contradicts is
|
|
6279
|
+
// worse than no window at all.
|
|
6280
|
+
value: Math.min(Math.max(Math.round(paneLayout.effective[key] === undefined ? 0 : paneLayout.effective[key]), limit.min), limit.max),
|
|
6281
|
+
min: limit.min,
|
|
6282
|
+
max: limit.max,
|
|
6283
|
+
onStart: () => setPaneDragging(key),
|
|
6284
|
+
onDrag: (px) => dragPane(key, px),
|
|
6285
|
+
onEnd: endPaneDrag,
|
|
6286
|
+
onReset: () => resetPane(key),
|
|
6287
|
+
})
|
|
6288
|
+
}
|
|
6289
|
+
|
|
4625
6290
|
// The action rail measures ITSELF, not the panel: how many buttons fit is a
|
|
4626
6291
|
// function of the strip that holds them (a 1200x300 workbench and a 300x900
|
|
4627
6292
|
// sidebar disagree about that). railBox keeps the last measured span; until
|
|
@@ -5037,8 +6702,10 @@ window.__ModuleLoader__.load({
|
|
|
5037
6702
|
E('span', { className: 'dig-banner-text' }, note),
|
|
5038
6703
|
E('button', { type: 'button', className: 'dig-link', onClick: () => setNote(null) }, t('error.dismiss')))
|
|
5039
6704
|
|
|
6705
|
+
const commitSettings = readCommitSettings()
|
|
5040
6706
|
const changesPane = E(ChangesPanel, {
|
|
5041
6707
|
t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
|
|
6708
|
+
request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings,
|
|
5042
6709
|
showIgnored: showIgnored,
|
|
5043
6710
|
onToggleIgnored: () => setShowIgnored((value) => !value),
|
|
5044
6711
|
// Commit-and-push carries the confirm flag with it: the button already
|
|
@@ -5074,7 +6741,7 @@ window.__ModuleLoader__.load({
|
|
|
5074
6741
|
onLoadMore: () => { void guard(() => loadCommits(commits.length)) },
|
|
5075
6742
|
})
|
|
5076
6743
|
|
|
5077
|
-
const diffPane = patch === '' && patchLoading === false ? null : E('div', { className: 'dig-diff-pane' },
|
|
6744
|
+
const diffPane = patch === '' && patchLoading === false ? null : E('div', { className: 'dig-diff-pane', 'data-pane': 'diff', style: paneStyle('diff') },
|
|
5078
6745
|
E('div', { className: 'dig-diff-head' },
|
|
5079
6746
|
E('span', { className: 'dig-mono dig-diff-path' }, selectedPath === null ? '' : selectedPath),
|
|
5080
6747
|
E('button', { type: 'button', className: 'dig-icon-btn', onClick: () => { setPatch(''); setSelectedPath(null) } }, E(Icon, { name: 'close', size: 12 }))),
|
|
@@ -5153,15 +6820,54 @@ window.__ModuleLoader__.load({
|
|
|
5153
6820
|
else if (spec.id === 'fetch') { entry.disabled = remoteReady !== true; entry.run = () => { void run('fetch', { prune: true }) } }
|
|
5154
6821
|
else if (spec.id === 'pull') { entry.disabled = remoteReady !== true || tracked !== true || blocked; entry.run = () => { void run('pull', { mode: 'ff-only' }) } }
|
|
5155
6822
|
else if (spec.id === 'push') { entry.disabled = remoteReady !== true || blocked; entry.run = () => setDialog({ kind: 'push' }) }
|
|
5156
|
-
if (
|
|
6823
|
+
else if (spec.id === 'tree') { entry.active = treeOpen; entry.run = () => foldTree(!treeOpen) }
|
|
6824
|
+
else if (spec.id === 'float') {
|
|
6825
|
+
// Label AND icon follow where the panel actually is: floating panels get
|
|
6826
|
+
// the way back, docked ones get the way out.
|
|
6827
|
+
entry.available = dockState.float
|
|
6828
|
+
entry.active = dockState.floating
|
|
6829
|
+
entry.icon = dockState.floating === true ? 'dock' : 'float'
|
|
6830
|
+
entry.label = dockState.floating === true ? t('action.dock') : t('action.float')
|
|
6831
|
+
entry.run = () => {
|
|
6832
|
+
if (dockState.floating === true) { dock.service.dock(floatPaneId); return }
|
|
6833
|
+
dock.service.float(dock.tabId)
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
else if (spec.id === 'split') {
|
|
6837
|
+
entry.available = dockState.split
|
|
6838
|
+
entry.run = () => { dock.service.split(dock.paneId) }
|
|
6839
|
+
}
|
|
6840
|
+
else if (spec.id === 'fullscreen') {
|
|
6841
|
+
entry.available = dockState.fullscreen
|
|
6842
|
+
entry.active = dock !== null && dock.fullscreen === true
|
|
6843
|
+
entry.icon = entry.active === true ? 'collapse' : 'expand'
|
|
6844
|
+
entry.label = entry.active === true ? t('action.exitFullscreen') : t('action.fullscreen')
|
|
6845
|
+
entry.run = () => {
|
|
6846
|
+
const service = dock.service
|
|
6847
|
+
const target = typeof service.commandTarget === 'function' ? service.commandTarget(document.activeElement) : undefined
|
|
6848
|
+
if (target !== undefined && target !== null) service.toggleFullscreen(target)
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
// A running git command greys out the repository actions, never the view.
|
|
6852
|
+
if (busy === true && spec.id !== 'refresh' && RAIL_VIEW_IDS.indexOf(spec.id) < 0) entry.disabled = true
|
|
5157
6853
|
return entry
|
|
5158
6854
|
})
|
|
5159
6855
|
|
|
5160
6856
|
const railOrdered = railConfig.order
|
|
5161
6857
|
.map((id) => railActions.find((action) => action.id === id))
|
|
5162
|
-
|
|
6858
|
+
// An action this mount cannot perform is not rendered at all — hidden, not
|
|
6859
|
+
// disabled: a greyed button for a service the host does not have is noise.
|
|
6860
|
+
.filter((action) => action !== undefined && action.available !== false)
|
|
5163
6861
|
const railVisible = railOrdered.filter((action) => railConfig.hidden.indexOf(action.id) < 0)
|
|
5164
6862
|
|
|
6863
|
+
/* The rail is drawn from the row Config when the host serves it: a change
|
|
6864
|
+
made in the settings card must show up here without a reload. */
|
|
6865
|
+
useEffect(() => subscribeRailConfig(() => {
|
|
6866
|
+
setRailConfig(readRailConfig())
|
|
6867
|
+
setRailTick((value) => value + 1)
|
|
6868
|
+
}), [])
|
|
6869
|
+
void railTick
|
|
6870
|
+
|
|
5165
6871
|
const applyRail = (next) => { setRailConfig(next); writeRailConfig(next) }
|
|
5166
6872
|
const moveRailAction = (id, delta) => {
|
|
5167
6873
|
const order = railConfig.order.slice()
|
|
@@ -5211,6 +6917,8 @@ window.__ModuleLoader__.load({
|
|
|
5211
6917
|
key: action.id,
|
|
5212
6918
|
type: 'button',
|
|
5213
6919
|
className: 'dig-rail-btn dig-tone-' + action.tone + (action.active === true ? ' dig-rail-btn-active' : ''),
|
|
6920
|
+
'data-action': action.id,
|
|
6921
|
+
'aria-pressed': action.active === true ? 'true' : 'false',
|
|
5214
6922
|
title: action.disabled === true ? action.label + ' · ' + reason : action.label,
|
|
5215
6923
|
disabled: action.disabled === true,
|
|
5216
6924
|
onClick: action.run,
|
|
@@ -5238,26 +6946,40 @@ window.__ModuleLoader__.load({
|
|
|
5238
6946
|
key: 'settings',
|
|
5239
6947
|
type: 'button',
|
|
5240
6948
|
className: 'dig-rail-btn dig-rail-settings',
|
|
5241
|
-
|
|
5242
|
-
|
|
6949
|
+
'data-action': 'settings',
|
|
6950
|
+
// On a host that serves the row Config this button opens the plugin's own
|
|
6951
|
+
// settings page (where the card below the description edits the rail);
|
|
6952
|
+
// on an older host it keeps the in-panel editor. Never both.
|
|
6953
|
+
title: railSettingsTarget() === 'page' ? t('rail.openSettings') : t('rail.settings'),
|
|
6954
|
+
onClick: () => {
|
|
6955
|
+
const target = railSettingsTarget()
|
|
6956
|
+
if (target === 'page') { openSettingsPage(); return }
|
|
6957
|
+
setRailSettings(true)
|
|
6958
|
+
},
|
|
5243
6959
|
}, E(Icon, { name: 'settings', size: 15 })))
|
|
5244
6960
|
return E('div', { className: vertical ? 'dig-rail' : 'dig-rail-row', ref: railHostRef }, children)
|
|
5245
6961
|
}
|
|
5246
6962
|
|
|
5247
6963
|
/* ---------- body per chrome ---------- */
|
|
5248
6964
|
|
|
6965
|
+
/* ---------- body per chrome ---------- */
|
|
6966
|
+
|
|
6967
|
+
/* The history↔diff separator only exists while a diff is on screen, and it
|
|
6968
|
+
lives inside the middle pane — the diff is the pane below it. */
|
|
6969
|
+
const diffGutter = diffPane === null ? null : gutter('diff', 'next')
|
|
6970
|
+
|
|
5249
6971
|
let body
|
|
5250
6972
|
if (repoState === null) {
|
|
5251
6973
|
/* The scan is still running: a neutral loading state, never the
|
|
5252
6974
|
"no repository found" verdict (that text is reserved for a scan
|
|
5253
6975
|
that actually came back empty). */
|
|
5254
|
-
body = E('div', { className: 'dig-body' }, E('div', { className: 'dig-empty' }, t('status.loading')))
|
|
6976
|
+
body = E('div', { className: 'dig-body', ref: bodyRef }, E('div', { className: 'dig-empty' }, t('status.loading')))
|
|
5255
6977
|
} else if (repoRoot === null) {
|
|
5256
|
-
body = E('div', { className: 'dig-body' }, E(RepoPicker, {
|
|
6978
|
+
body = E('div', { className: 'dig-body', ref: bodyRef }, E(RepoPicker, {
|
|
5257
6979
|
t: t, cwd: cwd === undefined ? '' : cwd, repos: repoOptions, onPick: pickRepo,
|
|
5258
6980
|
}))
|
|
5259
6981
|
} else if (compact) {
|
|
5260
|
-
body = E('div', { className: 'dig-body dig-body-compact' },
|
|
6982
|
+
body = E('div', { className: 'dig-body dig-body-compact', ref: bodyRef },
|
|
5261
6983
|
E('div', { className: 'dig-compact-bar' },
|
|
5262
6984
|
E(Segmented, {
|
|
5263
6985
|
value: view === 'detail' ? 'history' : view,
|
|
@@ -5268,19 +6990,34 @@ window.__ModuleLoader__.load({
|
|
|
5268
6990
|
],
|
|
5269
6991
|
}),
|
|
5270
6992
|
E('span', { className: 'dig-topbar-spacer' }),
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
6993
|
+
// Two-way on purpose: treeOpen is persisted, so a header button that
|
|
6994
|
+
// only rendered while the tree was OPEN left a folded tree with no way
|
|
6995
|
+
// back on this chrome (the rail now carries one too, for the others).
|
|
6996
|
+
E('button', {
|
|
6997
|
+
type: 'button',
|
|
6998
|
+
className: 'dig-icon-btn' + (treeOpen ? ' dig-icon-btn-active' : ''),
|
|
6999
|
+
'data-action': 'tree',
|
|
7000
|
+
'aria-pressed': treeOpen ? 'true' : 'false',
|
|
7001
|
+
title: t('toolbar.tree'),
|
|
7002
|
+
onClick: () => foldTree(!treeOpen),
|
|
7003
|
+
}, E(Icon, { name: treeOpen ? 'eye' : 'eyeOff' }))),
|
|
7004
|
+
treeOpen ? E('div', { className: 'dig-compact-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu })) : null,
|
|
7005
|
+
treeOpen ? gutter('tree', 'prev') : null,
|
|
7006
|
+
view === 'changes' ? changesPane : E('div', { className: 'dig-pane dig-pane-main' }, historyPane, diffGutter, diffPane))
|
|
5274
7007
|
} else if (columns) {
|
|
5275
|
-
body = E('div', { className: 'dig-body dig-body-columns' },
|
|
5276
|
-
treeOpen ? E('div', { className: 'dig-pane dig-pane-tree' }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu })) : null,
|
|
5277
|
-
|
|
5278
|
-
E('div', { className: 'dig-pane dig-pane-
|
|
7008
|
+
body = E('div', { className: 'dig-body dig-body-columns', ref: bodyRef },
|
|
7009
|
+
treeOpen ? E('div', { className: 'dig-pane dig-pane-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu })) : null,
|
|
7010
|
+
treeOpen ? gutter('tree', 'prev') : null,
|
|
7011
|
+
E('div', { className: 'dig-pane dig-pane-main' }, historyPane, diffGutter, diffPane),
|
|
7012
|
+
gutter('changes', 'next'),
|
|
7013
|
+
E('div', { className: 'dig-pane dig-pane-changes', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane))
|
|
5279
7014
|
} else {
|
|
5280
|
-
body = E('div', { className: 'dig-body dig-body-stack' },
|
|
5281
|
-
treeOpen ? E('div', { className: 'dig-pane dig-pane-tree-stack' }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu })) : null,
|
|
5282
|
-
|
|
5283
|
-
E('div', { className: 'dig-pane dig-pane-
|
|
7015
|
+
body = E('div', { className: 'dig-body dig-body-stack', ref: bodyRef },
|
|
7016
|
+
treeOpen ? E('div', { className: 'dig-pane dig-pane-tree-stack', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu })) : null,
|
|
7017
|
+
treeOpen ? gutter('tree', 'prev') : null,
|
|
7018
|
+
E('div', { className: 'dig-pane dig-pane-changes-stack', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane),
|
|
7019
|
+
gutter('changes', 'prev'),
|
|
7020
|
+
E('div', { className: 'dig-pane dig-pane-main' }, historyPane, diffGutter, diffPane))
|
|
5284
7021
|
}
|
|
5285
7022
|
|
|
5286
7023
|
/* ---------- overlays ---------- */
|
|
@@ -5406,7 +7143,14 @@ window.__ModuleLoader__.load({
|
|
|
5406
7143
|
// narrow ones (the native right sidebar) get the same actions as one
|
|
5407
7144
|
// horizontal row under the repo/branch bar, and every action lives in
|
|
5408
7145
|
// exactly one place.
|
|
5409
|
-
|
|
7146
|
+
// While a divider is being dragged the whole panel stops selecting text and
|
|
7147
|
+
// keeps the resize cursor, even though the pointer has left the 8px gutter.
|
|
7148
|
+
return E('div', {
|
|
7149
|
+
className: 'dig-root'
|
|
7150
|
+
+ (compact ? ' dig-root-narrow' : '')
|
|
7151
|
+
+ (paneDragging === null ? '' : ' dig-pane-dragging' + (paneByWidth ? '-v' : '-h')),
|
|
7152
|
+
ref: hostRef,
|
|
7153
|
+
}, topBar,
|
|
5410
7154
|
columns === true || repoRoot === null ? null : renderRail(false),
|
|
5411
7155
|
banner, noteBanner,
|
|
5412
7156
|
E('div', { className: 'dig-shell' },
|
|
@@ -5470,6 +7214,35 @@ window.__ModuleLoader__.load({
|
|
|
5470
7214
|
'.dig-tone-violet{color:#b083f0}',
|
|
5471
7215
|
'.dig-tone-cyan{color:#59b0d6}',
|
|
5472
7216
|
'.dig-icon-btn-small{width:20px;height:20px;border-radius:var(--dsw-radius-xs,4px)}',
|
|
7217
|
+
// A panel dragged narrower than COMPACT_MAX_WIDTH (the compact chrome) is the
|
|
7218
|
+
// case where 'nowrap + ellipsis' stops being readable: the overlays clamp
|
|
7219
|
+
// their own width, and their text wraps inside that box instead of clipping.
|
|
7220
|
+
// The settings card (the host's plugin page renders it inside its own
|
|
7221
|
+
// section): one row per rail action, themed entirely by tokens.
|
|
7222
|
+
'.dig-settings{display:flex;flex-direction:column;gap:8px;min-width:0}',
|
|
7223
|
+
'.dig-settings-head{display:flex;flex-direction:column;gap:2px}',
|
|
7224
|
+
'.dig-settings-title{font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
7225
|
+
'.dig-settings-hint{color:var(--dsw-alias-label-tertiary);font-weight:400}',
|
|
7226
|
+
'.dig-settings-note{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary));font-weight:400}',
|
|
7227
|
+
'.dig-settings-note-hidden{display:none}',
|
|
7228
|
+
'.dig-settings-error{color:var(--dsw-alias-state-error-primary);font-weight:400}',
|
|
7229
|
+
'.dig-settings-sub{margin-top:6px;font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
7230
|
+
'.dig-settings-text{display:flex;flex-direction:column;gap:3px;min-width:0}',
|
|
7231
|
+
'.dig-settings-text-label{color:var(--dsw-alias-label-secondary);font-weight:400}',
|
|
7232
|
+
'.dig-settings-input{width:100%;box-sizing:border-box;font:inherit}',
|
|
7233
|
+
'.dig-commit-ai{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px}',
|
|
7234
|
+
'.dig-commit-ai-hint{color:var(--dsw-alias-label-tertiary);font-size:11px}',
|
|
7235
|
+
'.dig-commit-ai-hint-block{flex:1 0 100%;font-size:11px}',
|
|
7236
|
+
'.dig-commit-ai-note{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary));font-size:11px;margin-bottom:4px;word-break:break-word}',
|
|
7237
|
+
'.dig-settings-list{display:flex;flex-direction:column;gap:2px;max-height:min(46vh,360px);overflow:auto;padding:2px;border:1px solid var(--dsw-alias-hairline,var(--dsw-alias-border-l1));border-radius:var(--dsw-radius-sm,8px)}',
|
|
7238
|
+
'.dig-settings-row{display:flex;align-items:center;gap:8px;padding:3px 6px;border-radius:calc(var(--dsw-radius-sm,8px) - 2px);cursor:pointer;min-width:0}',
|
|
7239
|
+
'.dig-settings-row:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
7240
|
+
'.dig-settings-check{flex:none;accent-color:var(--dsw-alias-brand-primary)}',
|
|
7241
|
+
'.dig-settings-glyph{display:inline-flex;flex:none;color:var(--dsw-alias-label-secondary)}',
|
|
7242
|
+
'.dig-settings-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}',
|
|
7243
|
+
'.dig-root-narrow .dig-settings-label{white-space:normal}',
|
|
7244
|
+
'.dig-root-narrow .dig-menu-item{white-space:normal}',
|
|
7245
|
+
'.dig-root-narrow .dig-toast-text{white-space:normal}',
|
|
5473
7246
|
'.dig-toasts{position:absolute;right:8px;bottom:8px;display:flex;flex-direction:column;gap:6px;z-index:80;max-width:min(340px,92%)}',
|
|
5474
7247
|
'.dig-toast{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:var(--dsw-radius-sm,8px);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border:0;box-shadow:var(--dsw-elevation-prominent,0 8px 22px rgba(0,0,0,.32))}',
|
|
5475
7248
|
'.dig-toast-icon{display:inline-flex;flex:none;color:var(--dsw-alias-label-secondary)}',
|
|
@@ -5499,6 +7272,20 @@ window.__ModuleLoader__.load({
|
|
|
5499
7272
|
'.dig-pane-changes-stack{max-height:46%;flex:none;border-bottom:1px solid var(--dsw-alias-hairline,var(--dsw-alias-border-l1))}',
|
|
5500
7273
|
'.dig-compact-bar{display:flex;align-items:center;gap:6px;padding:4px 6px;flex:none}',
|
|
5501
7274
|
'.dig-compact-tree{max-height:42%;flex:none;border-bottom:1px solid var(--dsw-alias-hairline,var(--dsw-alias-border-l1))}',
|
|
7275
|
+
// A divider: an 8px hit area (a fingertip needs it) painted as a 1px hairline
|
|
7276
|
+
// that lights up on hover, focus or while dragging. touch-action:none is what
|
|
7277
|
+
// makes a touch drag resize the pane instead of scrolling the panel.
|
|
7278
|
+
'.dig-gutter{flex:none;align-self:stretch;position:relative;z-index:1;touch-action:none;background:transparent;border:0;padding:0;cursor:col-resize}',
|
|
7279
|
+
'.dig-gutter-v{width:8px}',
|
|
7280
|
+
'.dig-gutter-h{height:8px;cursor:row-resize}',
|
|
7281
|
+
'.dig-gutter::after{content:"";position:absolute;background:var(--dsw-alias-hairline,var(--dsw-alias-border-l1));transition:background .12s ease}',
|
|
7282
|
+
'.dig-gutter-v::after{top:0;bottom:0;left:50%;width:1px;margin-left:-.5px}',
|
|
7283
|
+
'.dig-gutter-h::after{left:0;right:0;top:50%;height:1px;margin-top:-.5px}',
|
|
7284
|
+
'.dig-gutter:hover::after,.dig-gutter:focus-visible::after,.dig-gutter-active::after{background:var(--dsw-alias-brand-primary)}',
|
|
7285
|
+
'.dig-gutter:focus-visible{outline:1px solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-1px}',
|
|
7286
|
+
'.dig-pane-dragging,.dig-pane-dragging *{user-select:none}',
|
|
7287
|
+
'.dig-pane-dragging-v{cursor:col-resize}',
|
|
7288
|
+
'.dig-pane-dragging-h{cursor:row-resize}',
|
|
5502
7289
|
'.dig-seg{display:inline-flex;gap:2px;padding:2px;border-radius:var(--dsw-radius-sm,8px);background:var(--dsw-alias-interactive-bg-hover);flex:none}',
|
|
5503
7290
|
'.dig-seg-item{border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-weight:500;padding:2px 10px;border-radius:calc(var(--dsw-radius-sm,8px) - 2px);cursor:pointer;white-space:nowrap}',
|
|
5504
7291
|
'.dig-seg-item:hover{color:var(--dsw-alias-label-primary)}',
|
|
@@ -5599,7 +7386,7 @@ window.__ModuleLoader__.load({
|
|
|
5599
7386
|
'.dig-row:hover .dig-mini{opacity:1}',
|
|
5600
7387
|
'.dig-mini:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
5601
7388
|
'.dig-commit-box{flex:none;padding:6px;display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--dsw-alias-hairline,var(--dsw-alias-border-l1))}',
|
|
5602
|
-
'.dig-commit-box-compact{flex-direction:row;align-items:center;gap:6px;padding:4px 6px}',
|
|
7389
|
+
'.dig-commit-box-compact{flex-direction:row;align-items:center;gap:6px;padding:4px 6px;flex-wrap:wrap}',
|
|
5603
7390
|
'.dig-textarea{width:100%;box-sizing:border-box;min-height:50px;resize:vertical;padding:5px 6px;border-radius:var(--dsw-radius-sm,8px);border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;outline:none}',
|
|
5604
7391
|
'.dig-textarea::placeholder{color:var(--dsw-alias-label-tertiary)}',
|
|
5605
7392
|
'.dig-textarea:focus{border-color:var(--dsw-alias-state-business-primary,var(--dsw-alias-brand-primary))}',
|
|
@@ -5632,13 +7419,13 @@ window.__ModuleLoader__.load({
|
|
|
5632
7419
|
'.dig-diff-hunk{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)}',
|
|
5633
7420
|
'.dig-diff-meta{color:var(--dsw-alias-label-tertiary)}',
|
|
5634
7421
|
'.dig-overlay{position:absolute;inset:0;padding:8px;box-sizing:border-box;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.42));-webkit-backdrop-filter:var(--dsw-mask-blur,none);backdrop-filter:var(--dsw-mask-blur,none);display:flex;align-items:center;justify-content:center;z-index:60}',
|
|
5635
|
-
'.dig-dialog{min-width:240px;max-width:min(420px,94%);max-height:100%;overflow:auto;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border:0;border-radius:var(--dsw-radius-panel,28px);padding:14px;display:flex;flex-direction:column;gap:10px;box-shadow:var(--dsw-elevation-prominent,0 12px 32px rgba(0,0,0,.35))}',
|
|
7422
|
+
'.dig-dialog{box-sizing:border-box;min-width:min(240px,100%);max-width:min(420px,94%);max-height:100%;overflow:auto;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border:0;border-radius:var(--dsw-radius-panel,28px);padding:14px;display:flex;flex-direction:column;gap:10px;box-shadow:var(--dsw-elevation-prominent,0 12px 32px rgba(0,0,0,.35))}',
|
|
5636
7423
|
'.dig-dialog-wide{min-width:min(360px,92%)}',
|
|
5637
7424
|
'.dig-dialog-title{font-weight:600}',
|
|
5638
7425
|
'.dig-dialog-text{color:var(--dsw-alias-label-secondary);white-space:pre-wrap}',
|
|
5639
7426
|
'.dig-dialog-actions{display:flex;justify-content:flex-end;gap:8px}',
|
|
5640
7427
|
'.dig-dialog-actions .dig-btn-primary{margin-left:0}',
|
|
5641
|
-
'.dig-menu{position:absolute;z-index:70;box-sizing:border-box;min-width:200px;max-width:calc(100% - 8px);max-height:calc(100% - 8px);padding:4px;border:0;border-radius:var(--dsw-radius-md,12px);background:transparent;box-shadow:var(--dsw-elevation-prominent,0 10px 28px rgba(0,0,0,.35));--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);isolation:isolate;display:flex;flex-direction:column}',
|
|
7428
|
+
'.dig-menu{position:absolute;z-index:70;box-sizing:border-box;min-width:min(200px,calc(100% - 8px));max-width:calc(100% - 8px);max-height:calc(100% - 8px);padding:4px;border:0;border-radius:var(--dsw-radius-md,12px);background:transparent;box-shadow:var(--dsw-elevation-prominent,0 10px 28px rgba(0,0,0,.35));--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);isolation:isolate;display:flex;flex-direction:column}',
|
|
5642
7429
|
'.dig-menu::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1)));-webkit-backdrop-filter:var(--dsw-menu-backdrop-filter,blur(14px) saturate(1.2));backdrop-filter:var(--dsw-menu-backdrop-filter,blur(14px) saturate(1.2));pointer-events:none}',
|
|
5643
7430
|
'.dig-menu-scroll{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;display:flex;flex-direction:column}',
|
|
5644
7431
|
'.dig-menu-item{display:flex;align-items:center;gap:8px;padding:4px 8px;border:none;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;text-align:left;border-radius:var(--dsw-radius-sm,8px);cursor:pointer;white-space:nowrap;overflow:hidden}',
|
|
@@ -5680,7 +7467,16 @@ window.__ModuleLoader__.load({
|
|
|
5680
7467
|
return typeof unsubscribe === 'function' ? unsubscribe : undefined
|
|
5681
7468
|
}, [])
|
|
5682
7469
|
void tick
|
|
5683
|
-
|
|
7470
|
+
/* Read at RENDER time (the service may arrive after this plugin's apply) and
|
|
7471
|
+
soft: a host without the plugin page simply keeps the in-panel editor. */
|
|
7472
|
+
let navigation
|
|
7473
|
+
try {
|
|
7474
|
+
navigation = props.ctx === undefined || props.ctx === null ? undefined : props.ctx.get('pluginNavigation')
|
|
7475
|
+
} catch (error) { navigation = undefined }
|
|
7476
|
+
const openSettings = navigation !== undefined && navigation !== null && typeof navigation.openBundle === 'function'
|
|
7477
|
+
? () => { navigation.openBundle(SETTINGS_BUNDLE) }
|
|
7478
|
+
: null
|
|
7479
|
+
return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings })
|
|
5684
7480
|
}
|
|
5685
7481
|
|
|
5686
7482
|
function NativePanel(props) {
|
|
@@ -5698,7 +7494,11 @@ window.__ModuleLoader__.load({
|
|
|
5698
7494
|
}
|
|
5699
7495
|
}
|
|
5700
7496
|
const scope = useMemo(() => ({ cwd: cwd, sessionId: sessionId }), [cwd, sessionId])
|
|
5701
|
-
|
|
7497
|
+
/* The dock actions need this tab's own identity, which only the native seat
|
|
7498
|
+
binds; the better-sidebar door has no sidebarRight tab id and therefore
|
|
7499
|
+
wires nothing (a panel hosted elsewhere must not float a foreign tab). */
|
|
7500
|
+
const dock = readDockInfo(props.useTabInfo, props.ctx)
|
|
7501
|
+
return E(LocaleLive, { ctx: props.ctx, scope: scope, t: props.t, visible: true, dock: dock })
|
|
5702
7502
|
}
|
|
5703
7503
|
|
|
5704
7504
|
function apply(ctx) {
|
|
@@ -5816,6 +7616,59 @@ window.__ModuleLoader__.load({
|
|
|
5816
7616
|
/* ctx.inject runs its callback straight away when the service is already
|
|
5817
7617
|
there, so only a host without better-sidebar reaches the native seats. */
|
|
5818
7618
|
if (!hostedByBetterSidebar) hostNatively()
|
|
7619
|
+
|
|
7620
|
+
/* ---- settings: the row Config is the rail's source of truth ---- */
|
|
7621
|
+
/* dsh >= 0.1.7 serves a plugin's row Config to the browser through
|
|
7622
|
+
ctx.configForms; from then on the settings card and the rail read that one
|
|
7623
|
+
document (and the legacy localStorage value is migrated once). A host
|
|
7624
|
+
without the service never fires this callback: railForm stays null and the
|
|
7625
|
+
localStorage home + in-panel editor keep working there. */
|
|
7626
|
+
ctx.inject(['configForms'], (formCtx) => {
|
|
7627
|
+
const forms = formCtx.get('configForms')
|
|
7628
|
+
if (forms === undefined || forms === null || typeof forms.get !== 'function') return
|
|
7629
|
+
let form
|
|
7630
|
+
try { form = forms.get(RAIL_NS) } catch (error) { void error }
|
|
7631
|
+
if (form === undefined || form === null || typeof form.getSnapshot !== 'function') return
|
|
7632
|
+
formCtx.effect(() => {
|
|
7633
|
+
railForm = form
|
|
7634
|
+
migrateRailConfig(form)
|
|
7635
|
+
/* The row Config is still 'loading' at bind time, so the migration is
|
|
7636
|
+
attempted here AND on every notification until it lands — it is
|
|
7637
|
+
idempotent (marker-gated, re-entrancy-guarded) and only stamps the
|
|
7638
|
+
marker once the Host accepted the write. */
|
|
7639
|
+
const unsubscribe = typeof form.subscribe === 'function' ? form.subscribe(() => { notifyRailConfig(); migrateRailConfig(form) }) : undefined
|
|
7640
|
+
notifyRailConfig()
|
|
7641
|
+
return () => {
|
|
7642
|
+
railForm = null
|
|
7643
|
+
if (typeof unsubscribe === 'function') unsubscribe()
|
|
7644
|
+
notifyRailConfig()
|
|
7645
|
+
}
|
|
7646
|
+
}, 'dsh-ide-git: row config')
|
|
7647
|
+
})
|
|
7648
|
+
|
|
7649
|
+
/* The settings card, on both official seats: the plugin's detail page
|
|
7650
|
+
(keyed by the PACKAGE name) and Settings → Plugins (keyed by the settings
|
|
7651
|
+
namespace = the loader row id). Both seats are optional — a host without
|
|
7652
|
+
the slots keeps whatever surface it has. */
|
|
7653
|
+
ctx.inject(['slots'], (slotCtx) => {
|
|
7654
|
+
const slots = slotCtx.get('slots')
|
|
7655
|
+
if (slots === undefined || slots === null || typeof slots.inject !== 'function') return
|
|
7656
|
+
slotCtx.effect(() => {
|
|
7657
|
+
const card = (cardProps) => E(RailSettingsCard, Object.assign({}, cardProps, { t: t }))
|
|
7658
|
+
const offBundle = slots.inject('plugins.bundle.config', () => slots.register(
|
|
7659
|
+
{ name: 'plugins.bundle.config', key: SETTINGS_BUNDLE, locale: LOCALE_NS },
|
|
7660
|
+
card,
|
|
7661
|
+
))
|
|
7662
|
+
const offItem = slots.inject('settings.plugin.item', () => slots.register(
|
|
7663
|
+
{ name: 'settings.plugin.item', key: RAIL_NS, locale: LOCALE_NS },
|
|
7664
|
+
card,
|
|
7665
|
+
))
|
|
7666
|
+
return () => {
|
|
7667
|
+
try { offBundle() } catch (error) { void error }
|
|
7668
|
+
try { offItem() } catch (error) { void error }
|
|
7669
|
+
}
|
|
7670
|
+
}, 'dsh-ide-git: settings card')
|
|
7671
|
+
})
|
|
5819
7672
|
}
|
|
5820
7673
|
|
|
5821
7674
|
return { name: 'dsh-ide-git', inject: [], apply }
|