dsh-ide-git 0.8.0 → 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 +1121 -7
- 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'
|
|
@@ -155,6 +158,28 @@ window.__ModuleLoader__.load({
|
|
|
155
158
|
'toolbar.tree': '分支栏',
|
|
156
159
|
'rail.more': '更多操作',
|
|
157
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': '当前宿主把配置设为只读,开关不可修改。',
|
|
158
183
|
'rail.settingsHint': '拖动或用箭头调整顺序,眼睛图标控制显示',
|
|
159
184
|
'rail.reset': '恢复默认',
|
|
160
185
|
'rail.up': '上移',
|
|
@@ -316,6 +341,28 @@ window.__ModuleLoader__.load({
|
|
|
316
341
|
'toolbar.tree': 'Branch pane',
|
|
317
342
|
'rail.more': 'More actions',
|
|
318
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.',
|
|
319
366
|
'rail.settingsHint': 'Drag or use the arrows to reorder; the eye toggles visibility',
|
|
320
367
|
'rail.reset': 'Restore defaults',
|
|
321
368
|
'rail.up': 'Move up',
|
|
@@ -485,6 +532,28 @@ window.__ModuleLoader__.load({
|
|
|
485
532
|
'toolbar.tree': '分支面板',
|
|
486
533
|
'rail.more': '更多操作',
|
|
487
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': '目前宿主將設定設為唯讀,開關改唔到。',
|
|
488
557
|
'rail.settingsHint': '拖曳或用箭嘴排序;眼睛圖示切換顯示',
|
|
489
558
|
'rail.reset': '還原預設',
|
|
490
559
|
'rail.up': '上移',
|
|
@@ -646,6 +715,28 @@ window.__ModuleLoader__.load({
|
|
|
646
715
|
'toolbar.tree': '分支面板',
|
|
647
716
|
'rail.more': '更多操作',
|
|
648
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': '目前宿主將設定設為唯讀,開關無法修改。',
|
|
649
740
|
'rail.settingsHint': '拖曳或用箭頭排序;眼睛圖示切換顯示',
|
|
650
741
|
'rail.reset': '還原預設',
|
|
651
742
|
'rail.up': '上移',
|
|
@@ -807,6 +898,28 @@ window.__ModuleLoader__.load({
|
|
|
807
898
|
'toolbar.tree': '分支面板',
|
|
808
899
|
'rail.more': '更多操作',
|
|
809
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': '目前宿主將設定設為唯讀,開關無法修改。',
|
|
810
923
|
'rail.settingsHint': '拖曳或用箭嘴排序;眼睛圖示切換顯示',
|
|
811
924
|
'rail.reset': '還原預設',
|
|
812
925
|
'rail.up': '上移',
|
|
@@ -968,6 +1081,28 @@ window.__ModuleLoader__.load({
|
|
|
968
1081
|
'toolbar.tree': 'ブランチペイン',
|
|
969
1082
|
'rail.more': 'その他の操作',
|
|
970
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': 'このホストは設定を読み取り専用で提供しています。',
|
|
971
1106
|
'rail.settingsHint': 'ドラッグまたは矢印で並べ替え、目のアイコンで表示を切り替え',
|
|
972
1107
|
'rail.reset': '既定に戻す',
|
|
973
1108
|
'rail.up': '上へ',
|
|
@@ -1129,6 +1264,28 @@ window.__ModuleLoader__.load({
|
|
|
1129
1264
|
'toolbar.tree': '브랜치 창',
|
|
1130
1265
|
'rail.more': '추가 작업',
|
|
1131
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': '이 호스트는 설정을 읽기 전용으로 제공합니다.',
|
|
1132
1289
|
'rail.settingsHint': '끌어서 또는 화살표로 순서를 바꾸고, 눈 아이콘으로 표시를 전환합니다',
|
|
1133
1290
|
'rail.reset': '기본값 복원',
|
|
1134
1291
|
'rail.up': '위로',
|
|
@@ -1290,6 +1447,28 @@ window.__ModuleLoader__.load({
|
|
|
1290
1447
|
'toolbar.tree': 'Branch-Bereich',
|
|
1291
1448
|
'rail.more': 'Weitere Aktionen',
|
|
1292
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.',
|
|
1293
1472
|
'rail.settingsHint': 'Ziehen oder Pfeile zum Sortieren, das Auge schaltet die Sichtbarkeit um',
|
|
1294
1473
|
'rail.reset': 'Standard wiederherstellen',
|
|
1295
1474
|
'rail.up': 'Nach oben',
|
|
@@ -1451,6 +1630,28 @@ window.__ModuleLoader__.load({
|
|
|
1451
1630
|
'toolbar.tree': 'Volet des branches',
|
|
1452
1631
|
'rail.more': 'Plus d\'actions',
|
|
1453
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.',
|
|
1454
1655
|
'rail.settingsHint': 'Glisser ou utiliser les flèches pour réordonner ; l\'œil bascule la visibilité',
|
|
1455
1656
|
'rail.reset': 'Rétablir les valeurs par défaut',
|
|
1456
1657
|
'rail.up': 'Monter',
|
|
@@ -1612,6 +1813,28 @@ window.__ModuleLoader__.load({
|
|
|
1612
1813
|
'toolbar.tree': 'Панель веток',
|
|
1613
1814
|
'rail.more': 'Другие действия',
|
|
1614
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': 'Этот хост отдаёт конфигурацию только для чтения.',
|
|
1615
1838
|
'rail.settingsHint': 'Перетаскивайте или используйте стрелки для порядка; значок глаза переключает видимость',
|
|
1616
1839
|
'rail.reset': 'Вернуть значения по умолчанию',
|
|
1617
1840
|
'rail.up': 'Вверх',
|
|
@@ -1773,6 +1996,28 @@ window.__ModuleLoader__.load({
|
|
|
1773
1996
|
'toolbar.tree': 'Painel de branches',
|
|
1774
1997
|
'rail.more': 'Mais ações',
|
|
1775
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.',
|
|
1776
2021
|
'rail.settingsHint': 'Arraste ou use as setas para reordenar; o olho alterna a visibilidade',
|
|
1777
2022
|
'rail.reset': 'Restaurar padrões',
|
|
1778
2023
|
'rail.up': 'Mover para cima',
|
|
@@ -1934,6 +2179,28 @@ window.__ModuleLoader__.load({
|
|
|
1934
2179
|
'toolbar.tree': 'Riquadro dei branch',
|
|
1935
2180
|
'rail.more': 'Altre azioni',
|
|
1936
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.',
|
|
1937
2204
|
'rail.settingsHint': 'Trascina o usa le frecce per riordinare; l’occhio attiva o disattiva la visibilità',
|
|
1938
2205
|
'rail.reset': 'Ripristina i valori predefiniti',
|
|
1939
2206
|
'rail.up': 'Sposta su',
|
|
@@ -2095,6 +2362,28 @@ window.__ModuleLoader__.load({
|
|
|
2095
2362
|
'toolbar.tree': 'Branchpaneel',
|
|
2096
2363
|
'rail.more': 'Meer acties',
|
|
2097
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.',
|
|
2098
2387
|
'rail.settingsHint': 'Sleep of gebruik de pijlen om te sorteren; het oog schakelt de zichtbaarheid',
|
|
2099
2388
|
'rail.reset': 'Standaardwaarden herstellen',
|
|
2100
2389
|
'rail.up': 'Omhoog',
|
|
@@ -2256,6 +2545,28 @@ window.__ModuleLoader__.load({
|
|
|
2256
2545
|
'toolbar.tree': 'Panel gałęzi',
|
|
2257
2546
|
'rail.more': 'Więcej działań',
|
|
2258
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.',
|
|
2259
2570
|
'rail.settingsHint': 'Przeciągnij lub użyj strzałek, aby zmienić kolejność; oko przełącza widoczność',
|
|
2260
2571
|
'rail.reset': 'Przywróć domyślne',
|
|
2261
2572
|
'rail.up': 'W górę',
|
|
@@ -2417,6 +2728,28 @@ window.__ModuleLoader__.load({
|
|
|
2417
2728
|
'toolbar.tree': 'Grenpanel',
|
|
2418
2729
|
'rail.more': 'Fler åtgärder',
|
|
2419
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.',
|
|
2420
2753
|
'rail.settingsHint': 'Dra eller använd pilarna för att sortera; ögat växlar synlighet',
|
|
2421
2754
|
'rail.reset': 'Återställ standard',
|
|
2422
2755
|
'rail.up': 'Flytta upp',
|
|
@@ -2578,6 +2911,28 @@ window.__ModuleLoader__.load({
|
|
|
2578
2911
|
'toolbar.tree': 'Dal paneli',
|
|
2579
2912
|
'rail.more': 'Diğer eylemler',
|
|
2580
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.',
|
|
2581
2936
|
'rail.settingsHint': 'Sıralamak için sürükleyin veya okları kullanın; göz simgesi görünürlüğü değiştirir',
|
|
2582
2937
|
'rail.reset': 'Varsayılanlara dön',
|
|
2583
2938
|
'rail.up': 'Yukarı taşı',
|
|
@@ -2739,6 +3094,28 @@ window.__ModuleLoader__.load({
|
|
|
2739
3094
|
'toolbar.tree': 'Panel branch',
|
|
2740
3095
|
'rail.more': 'Tindakan lain',
|
|
2741
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.',
|
|
2742
3119
|
'rail.settingsHint': 'Seret atau pakai panah untuk mengurutkan; ikon mata mengalihkan visibilitas',
|
|
2743
3120
|
'rail.reset': 'Kembalikan default',
|
|
2744
3121
|
'rail.up': 'Naikkan',
|
|
@@ -2900,6 +3277,28 @@ window.__ModuleLoader__.load({
|
|
|
2900
3277
|
'toolbar.tree': 'Bảng nhánh',
|
|
2901
3278
|
'rail.more': 'Thao tác khác',
|
|
2902
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.',
|
|
2903
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ị',
|
|
2904
3303
|
'rail.reset': 'Khôi phục mặc định',
|
|
2905
3304
|
'rail.up': 'Di chuyển lên',
|
|
@@ -3061,6 +3460,28 @@ window.__ModuleLoader__.load({
|
|
|
3061
3460
|
'toolbar.tree': 'لوحة الفروع',
|
|
3062
3461
|
'rail.more': 'مزيد من الإجراءات',
|
|
3063
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': 'يوفّر هذا المضيف الإعدادات للقراءة فقط.',
|
|
3064
3485
|
'rail.settingsHint': 'اسحب أو استخدم الأسهم لإعادة الترتيب؛ وأيقونة العين تبدّل الظهور',
|
|
3065
3486
|
'rail.reset': 'استعادة الإعدادات الافتراضية',
|
|
3066
3487
|
'rail.up': 'تحريك لأعلى',
|
|
@@ -3222,6 +3643,28 @@ window.__ModuleLoader__.load({
|
|
|
3222
3643
|
'toolbar.tree': 'शाखा पैनल',
|
|
3223
3644
|
'rail.more': 'अधिक क्रियाएँ',
|
|
3224
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': 'यह होस्ट कॉन्फ़िगरेशन को केवल-पढ़ने के लिए देता है।',
|
|
3225
3668
|
'rail.settingsHint': 'क्रम बदलने के लिए खींचें या तीरों का उपयोग करें; आँख आइकन दृश्यता बदलता है',
|
|
3226
3669
|
'rail.reset': 'डिफ़ॉल्ट बहाल करें',
|
|
3227
3670
|
'rail.up': 'ऊपर ले जाएँ',
|
|
@@ -3383,6 +3826,28 @@ window.__ModuleLoader__.load({
|
|
|
3383
3826
|
'toolbar.tree': 'แผงแบรนช์',
|
|
3384
3827
|
'rail.more': 'การทำงานเพิ่มเติม',
|
|
3385
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': 'โฮสต์นี้ให้บริการการตั้งค่าแบบอ่านอย่างเดียว',
|
|
3386
3851
|
'rail.settingsHint': 'ลากหรือใช้ลูกศรเพื่อจัดลำดับ; ไอคอนตาใช้สลับการแสดง',
|
|
3387
3852
|
'rail.reset': 'คืนค่าเริ่มต้น',
|
|
3388
3853
|
'rail.up': 'เลื่อนขึ้น',
|
|
@@ -3639,7 +4104,78 @@ window.__ModuleLoader__.load({
|
|
|
3639
4104
|
return { order, hidden }
|
|
3640
4105
|
}
|
|
3641
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
|
+
|
|
3642
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.
|
|
3643
4179
|
try {
|
|
3644
4180
|
const raw = window.localStorage.getItem(RAIL_KEY)
|
|
3645
4181
|
if (raw === null || raw === '') return normalizeRail(null)
|
|
@@ -3648,10 +4184,151 @@ window.__ModuleLoader__.load({
|
|
|
3648
4184
|
return normalizeRail(null)
|
|
3649
4185
|
}
|
|
3650
4186
|
|
|
3651
|
-
|
|
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
|
|
3652
4218
|
try { window.localStorage.setItem(RAIL_KEY, JSON.stringify(config)) } catch (error) { void error }
|
|
3653
4219
|
}
|
|
3654
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
|
+
|
|
3655
4332
|
/* ============================== pane sizes ============================== */
|
|
3656
4333
|
|
|
3657
4334
|
/* ---- pane sizing core (pure: tests/smoke.mjs slices this block and drives
|
|
@@ -4157,7 +4834,11 @@ window.__ModuleLoader__.load({
|
|
|
4157
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'],
|
|
4158
4835
|
stash: ['M2.5 3.5h11V6h-11z', 'M3.5 6v6.5h9V6', 'M6.5 8.5h3'],
|
|
4159
4836
|
tag: ['M2.5 7.5 7.5 2.5h6v6l-5 5z', 'M10.5 5.5h.01'],
|
|
4160
|
-
|
|
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'],
|
|
4161
4842
|
more: ['M3.6 8h0.01', 'M8 8h0.01', 'M12.4 8h0.01'],
|
|
4162
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'],
|
|
4163
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'],
|
|
@@ -4263,6 +4944,264 @@ window.__ModuleLoader__.load({
|
|
|
4263
4944
|
list is drag-sortable and arrow-sortable (the arrows also work with a
|
|
4264
4945
|
keyboard); visibility is a per-action toggle, not a delete, so a hidden
|
|
4265
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
|
+
|
|
4266
5205
|
function RailSettings(props) {
|
|
4267
5206
|
const t = props.t
|
|
4268
5207
|
const [dragId, setDragId] = useState(null)
|
|
@@ -4691,6 +5630,12 @@ window.__ModuleLoader__.load({
|
|
|
4691
5630
|
const [message, setMessage] = useState('')
|
|
4692
5631
|
const [amend, setAmend] = useState(false)
|
|
4693
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)
|
|
4694
5639
|
// 'flat' keeps git's own grouping (staged / changes / untracked); 'dir'
|
|
4695
5640
|
// clusters each group's files under their folder, the way "Group by:
|
|
4696
5641
|
// Directory" does in an IDE. Folders are addressed by <group>|<dir>, so a
|
|
@@ -4704,6 +5649,32 @@ window.__ModuleLoader__.load({
|
|
|
4704
5649
|
const untracked = changes === null ? [] : changes.untracked
|
|
4705
5650
|
const ignored = changes === null || changes.ignored === undefined ? [] : changes.ignored
|
|
4706
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
|
+
}
|
|
4707
5678
|
const submit = (push) => {
|
|
4708
5679
|
if (message.trim() === '' || props.busy === true) return false
|
|
4709
5680
|
props.onCommit(message, amend, push === true)
|
|
@@ -4756,6 +5727,15 @@ window.__ModuleLoader__.load({
|
|
|
4756
5727
|
key === 'staged' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onUnstageAll() }, t('changes.unstageAll')) : null,
|
|
4757
5728
|
key === 'unstaged' || key === 'untracked' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onStageAll(entries) }, t('changes.stageAll')) : null),
|
|
4758
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
|
+
})
|
|
4759
5739
|
const composer = props.compact === true
|
|
4760
5740
|
? E('div', { className: 'dig-commit-box dig-commit-box-compact' },
|
|
4761
5741
|
E('input', {
|
|
@@ -4766,11 +5746,18 @@ window.__ModuleLoader__.load({
|
|
|
4766
5746
|
if (event.key === 'Enter') { event.preventDefault(); submit() }
|
|
4767
5747
|
},
|
|
4768
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')),
|
|
4769
5755
|
E('button', {
|
|
4770
5756
|
type: 'button', className: 'dig-btn dig-btn-primary dig-btn-small',
|
|
4771
5757
|
disabled: message.trim() === '' || props.busy === true,
|
|
4772
5758
|
onClick: () => { submit(false) },
|
|
4773
|
-
}, t('changes.commit'))
|
|
5759
|
+
}, t('changes.commit')),
|
|
5760
|
+
E('span', { className: 'dig-commit-ai-hint dig-commit-ai-hint-block' }, t('changes.aiQuota')))
|
|
4774
5761
|
: E('div', { className: 'dig-commit-box' },
|
|
4775
5762
|
E('textarea', {
|
|
4776
5763
|
className: 'dig-textarea', value: message, spellCheck: false,
|
|
@@ -4780,6 +5767,18 @@ window.__ModuleLoader__.load({
|
|
|
4780
5767
|
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); submit() }
|
|
4781
5768
|
},
|
|
4782
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),
|
|
4783
5782
|
E('div', { className: 'dig-commit-actions' },
|
|
4784
5783
|
E('label', { className: 'dig-check' },
|
|
4785
5784
|
E('input', { type: 'checkbox', checked: amend, onChange: (event) => setAmend(event.target.checked) }),
|
|
@@ -4829,6 +5828,7 @@ window.__ModuleLoader__.load({
|
|
|
4829
5828
|
onClick: () => props.onToggleIgnored(),
|
|
4830
5829
|
}, E(Icon, { name: props.showIgnored === true ? 'eye' : 'eyeOff', size: 13 })))
|
|
4831
5830
|
return E('div', { className: 'dig-changes' },
|
|
5831
|
+
aiConfirmDialog,
|
|
4832
5832
|
head,
|
|
4833
5833
|
E('div', { className: 'dig-changes-body' },
|
|
4834
5834
|
E('div', { className: 'dig-changes-list' },
|
|
@@ -5100,6 +6100,7 @@ window.__ModuleLoader__.load({
|
|
|
5100
6100
|
const [favTick, setFavTick] = useState(0)
|
|
5101
6101
|
const [pathFilter, setPathFilter] = useState('')
|
|
5102
6102
|
const [railConfig, setRailConfig] = useState(readRailConfig)
|
|
6103
|
+
const [railTick, setRailTick] = useState(0)
|
|
5103
6104
|
const [railSettings, setRailSettings] = useState(false)
|
|
5104
6105
|
const [railBox, setRailBox] = useState({ width: 0, height: 0 })
|
|
5105
6106
|
const [toasts, setToasts] = useState([])
|
|
@@ -5135,6 +6136,15 @@ window.__ModuleLoader__.load({
|
|
|
5135
6136
|
// its own — v0.1.3 already made that mistake with a height < 330 test (a
|
|
5136
6137
|
// 1500x300 workbench came out single-column) and v0.3.5 still let
|
|
5137
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
|
+
|
|
5138
6148
|
const compact = size.width > 0 && size.width < COMPACT_MAX_WIDTH
|
|
5139
6149
|
const columns = !compact && size.width >= 600 && size.width >= size.height * 1.15
|
|
5140
6150
|
|
|
@@ -5692,8 +6702,10 @@ window.__ModuleLoader__.load({
|
|
|
5692
6702
|
E('span', { className: 'dig-banner-text' }, note),
|
|
5693
6703
|
E('button', { type: 'button', className: 'dig-link', onClick: () => setNote(null) }, t('error.dismiss')))
|
|
5694
6704
|
|
|
6705
|
+
const commitSettings = readCommitSettings()
|
|
5695
6706
|
const changesPane = E(ChangesPanel, {
|
|
5696
6707
|
t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
|
|
6708
|
+
request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings,
|
|
5697
6709
|
showIgnored: showIgnored,
|
|
5698
6710
|
onToggleIgnored: () => setShowIgnored((value) => !value),
|
|
5699
6711
|
// Commit-and-push carries the confirm flag with it: the button already
|
|
@@ -5848,6 +6860,14 @@ window.__ModuleLoader__.load({
|
|
|
5848
6860
|
.filter((action) => action !== undefined && action.available !== false)
|
|
5849
6861
|
const railVisible = railOrdered.filter((action) => railConfig.hidden.indexOf(action.id) < 0)
|
|
5850
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
|
+
|
|
5851
6871
|
const applyRail = (next) => { setRailConfig(next); writeRailConfig(next) }
|
|
5852
6872
|
const moveRailAction = (id, delta) => {
|
|
5853
6873
|
const order = railConfig.order.slice()
|
|
@@ -5926,8 +6946,16 @@ window.__ModuleLoader__.load({
|
|
|
5926
6946
|
key: 'settings',
|
|
5927
6947
|
type: 'button',
|
|
5928
6948
|
className: 'dig-rail-btn dig-rail-settings',
|
|
5929
|
-
|
|
5930
|
-
|
|
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
|
+
},
|
|
5931
6959
|
}, E(Icon, { name: 'settings', size: 15 })))
|
|
5932
6960
|
return E('div', { className: vertical ? 'dig-rail' : 'dig-rail-row', ref: railHostRef }, children)
|
|
5933
6961
|
}
|
|
@@ -6189,6 +7217,30 @@ window.__ModuleLoader__.load({
|
|
|
6189
7217
|
// A panel dragged narrower than COMPACT_MAX_WIDTH (the compact chrome) is the
|
|
6190
7218
|
// case where 'nowrap + ellipsis' stops being readable: the overlays clamp
|
|
6191
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}',
|
|
6192
7244
|
'.dig-root-narrow .dig-menu-item{white-space:normal}',
|
|
6193
7245
|
'.dig-root-narrow .dig-toast-text{white-space:normal}',
|
|
6194
7246
|
'.dig-toasts{position:absolute;right:8px;bottom:8px;display:flex;flex-direction:column;gap:6px;z-index:80;max-width:min(340px,92%)}',
|
|
@@ -6334,7 +7386,7 @@ window.__ModuleLoader__.load({
|
|
|
6334
7386
|
'.dig-row:hover .dig-mini{opacity:1}',
|
|
6335
7387
|
'.dig-mini:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
6336
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))}',
|
|
6337
|
-
'.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}',
|
|
6338
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}',
|
|
6339
7391
|
'.dig-textarea::placeholder{color:var(--dsw-alias-label-tertiary)}',
|
|
6340
7392
|
'.dig-textarea:focus{border-color:var(--dsw-alias-state-business-primary,var(--dsw-alias-brand-primary))}',
|
|
@@ -6415,7 +7467,16 @@ window.__ModuleLoader__.load({
|
|
|
6415
7467
|
return typeof unsubscribe === 'function' ? unsubscribe : undefined
|
|
6416
7468
|
}, [])
|
|
6417
7469
|
void tick
|
|
6418
|
-
|
|
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 })
|
|
6419
7480
|
}
|
|
6420
7481
|
|
|
6421
7482
|
function NativePanel(props) {
|
|
@@ -6555,6 +7616,59 @@ window.__ModuleLoader__.load({
|
|
|
6555
7616
|
/* ctx.inject runs its callback straight away when the service is already
|
|
6556
7617
|
there, so only a host without better-sidebar reaches the native seats. */
|
|
6557
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
|
+
})
|
|
6558
7672
|
}
|
|
6559
7673
|
|
|
6560
7674
|
return { name: 'dsh-ide-git', inject: [], apply }
|