dsh-better-workspace 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +104 -792
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);添加工作区由官方目录流承载。Workspace tree for the DSH sidebar workspace list, nested by official disk folders with optional \"/\"-name grouping.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);添加工作区走官方目录流。Workspace tree for the DSH sidebar: disk-path nesting (official workspace-tree semantics), with optional \"/\"-name grouping (off by default); add-workspace rides the official directory flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -125,20 +125,8 @@ window.__ModuleLoader__.load({
|
|
|
125
125
|
'sync.off': '当前环境不支持同步(需要宿主设置服务)',
|
|
126
126
|
'sync.loading': '正在连接宿主设置…',
|
|
127
127
|
'flow.creating': '正在创建…',
|
|
128
|
-
'
|
|
129
|
-
'
|
|
130
|
-
'browse.up': '上一层',
|
|
131
|
-
'browse.newFolder': '新建文件夹',
|
|
132
|
-
'browse.folderName': '文件夹名称',
|
|
133
|
-
'browse.empty': '此文件夹没有子文件夹',
|
|
134
|
-
'browse.loading': '加载中…',
|
|
135
|
-
'browse.truncated': '文件夹过多,仅显示开头部分。',
|
|
136
|
-
'browse.showHidden': '显示隐藏文件',
|
|
137
|
-
'browse.editPath': '编辑路径',
|
|
138
|
-
'browse.select': '选择此文件夹',
|
|
139
|
-
'browse.enter': '进入',
|
|
140
|
-
'browse.drives': '盘符',
|
|
141
|
-
'browse.selectNamed': '选择「{name}」',
|
|
128
|
+
'add.guide.title': "应用内添加工作区",
|
|
129
|
+
'add.guide': "此主机不提供桌面目录选择器。请到「新会话」页,用官方的「添加工作区」打开应用内浏览器。",
|
|
142
130
|
'error.title': '出错了',
|
|
143
131
|
'cancel': '取消',
|
|
144
132
|
'create': '创建',
|
|
@@ -250,20 +238,8 @@ window.__ModuleLoader__.load({
|
|
|
250
238
|
'sync.off': 'Sync unavailable here (requires the host settings service)',
|
|
251
239
|
'sync.loading': 'Connecting to host settings…',
|
|
252
240
|
'flow.creating': 'Creating…',
|
|
253
|
-
'
|
|
254
|
-
'
|
|
255
|
-
'browse.up': 'Up',
|
|
256
|
-
'browse.newFolder': 'New folder',
|
|
257
|
-
'browse.folderName': 'Folder name',
|
|
258
|
-
'browse.empty': 'No subfolders here',
|
|
259
|
-
'browse.loading': 'Loading…',
|
|
260
|
-
'browse.truncated': 'Too many folders to list; only the beginning is shown.',
|
|
261
|
-
'browse.showHidden': 'Show hidden files',
|
|
262
|
-
'browse.editPath': 'Edit path',
|
|
263
|
-
'browse.select': 'Use this folder',
|
|
264
|
-
'browse.enter': 'Open',
|
|
265
|
-
'browse.drives': 'Drives',
|
|
266
|
-
'browse.selectNamed': 'Use "{name}"',
|
|
241
|
+
'add.guide.title': "Add workspace in-app",
|
|
242
|
+
'add.guide': "This host has no desktop directory chooser. Use the official \"Add workspace\" entry on the New Session page.",
|
|
267
243
|
'error.title': 'Something went wrong',
|
|
268
244
|
'cancel': 'Cancel',
|
|
269
245
|
'create': 'Create',
|
|
@@ -383,20 +359,8 @@ window.__ModuleLoader__.load({
|
|
|
383
359
|
'sync.off': 'المزامنة غير متاحة هنا (تحتاج خدمة إعدادات المضيف)',
|
|
384
360
|
'sync.loading': 'الاتصال بإعدادات المضيف…',
|
|
385
361
|
'flow.creating': 'جارٍ الإنشاء…',
|
|
386
|
-
'
|
|
387
|
-
'
|
|
388
|
-
'browse.up': 'المستوى الأعلى',
|
|
389
|
-
'browse.newFolder': 'مجلد جديد',
|
|
390
|
-
'browse.folderName': 'اسم المجلد',
|
|
391
|
-
'browse.empty': 'لا توجد مجلدات فرعية هنا',
|
|
392
|
-
'browse.loading': 'جارٍ التحميل…',
|
|
393
|
-
'browse.truncated': 'المجلدات كثيرة جدًا؛ يُعرض الجزء الأول فقط.',
|
|
394
|
-
'browse.showHidden': 'إظهار الملفات المخفية',
|
|
395
|
-
'browse.editPath': 'تعديل المسار',
|
|
396
|
-
'browse.select': 'استخدام هذا المجلد',
|
|
397
|
-
'browse.enter': 'فتح',
|
|
398
|
-
'browse.drives': 'الأقراص',
|
|
399
|
-
'browse.selectNamed': 'استخدام "{name}"',
|
|
362
|
+
'add.guide.title': "إضافة مساحة عمل داخل التطبيق",
|
|
363
|
+
'add.guide': "لا يوفر هذا المضيف منتقي مجلدات سطح المكتب. استخدم إدخال \"إضافة مساحة عمل\" الرسمي في صفحة الجلسة الجديدة.",
|
|
400
364
|
'error.title': 'حدث خطأ ما',
|
|
401
365
|
'cancel': 'إلغاء',
|
|
402
366
|
'create': 'إنشاء',
|
|
@@ -508,20 +472,8 @@ window.__ModuleLoader__.load({
|
|
|
508
472
|
'sync.off': 'Hier nicht verfügbar (erfordert den Host-Einstellungsdienst)',
|
|
509
473
|
'sync.loading': 'Verbindung zum Host-Einstellungsspeicher…',
|
|
510
474
|
'flow.creating': 'Wird erstellt…',
|
|
511
|
-
'
|
|
512
|
-
'
|
|
513
|
-
'browse.up': 'Übergeordnet',
|
|
514
|
-
'browse.newFolder': 'Neuer Ordner',
|
|
515
|
-
'browse.folderName': 'Ordnername',
|
|
516
|
-
'browse.empty': 'Keine Unterordner vorhanden',
|
|
517
|
-
'browse.loading': 'Wird geladen…',
|
|
518
|
-
'browse.truncated': 'Zu viele Ordner; nur der Anfang wird angezeigt.',
|
|
519
|
-
'browse.showHidden': 'Versteckte Dateien anzeigen',
|
|
520
|
-
'browse.editPath': 'Pfad bearbeiten',
|
|
521
|
-
'browse.select': 'Diesen Ordner verwenden',
|
|
522
|
-
'browse.enter': 'Öffnen',
|
|
523
|
-
'browse.drives': 'Laufwerke',
|
|
524
|
-
'browse.selectNamed': '"{name}" verwenden',
|
|
475
|
+
'add.guide.title': "Arbeitsbereich in der App hinzufügen",
|
|
476
|
+
'add.guide': "Dieser Host bietet keinen Desktop-Ordnerdialog. Nutze den offiziellen Eintrag „Arbeitsbereich hinzufügen“ auf der Seite „Neue Sitzung“.",
|
|
525
477
|
'error.title': 'Etwas ist schiefgelaufen',
|
|
526
478
|
'cancel': 'Abbrechen',
|
|
527
479
|
'create': 'Erstellen',
|
|
@@ -633,20 +585,8 @@ window.__ModuleLoader__.load({
|
|
|
633
585
|
'sync.off': 'Synchronisation indisponible ici (service de réglages de l\'hôte requis)',
|
|
634
586
|
'sync.loading': 'Connexion aux réglages de l\'hôte…',
|
|
635
587
|
'flow.creating': 'Création…',
|
|
636
|
-
'
|
|
637
|
-
'
|
|
638
|
-
'browse.up': 'Niveau supérieur',
|
|
639
|
-
'browse.newFolder': 'Nouveau dossier',
|
|
640
|
-
'browse.folderName': 'Nom du dossier',
|
|
641
|
-
'browse.empty': 'Aucun sous-dossier ici',
|
|
642
|
-
'browse.loading': 'Chargement…',
|
|
643
|
-
'browse.truncated': 'Trop de dossiers ; seul le début est affiché.',
|
|
644
|
-
'browse.showHidden': 'Afficher les fichiers cachés',
|
|
645
|
-
'browse.editPath': 'Modifier le chemin',
|
|
646
|
-
'browse.select': 'Utiliser ce dossier',
|
|
647
|
-
'browse.enter': 'Ouvrir',
|
|
648
|
-
'browse.drives': 'Lecteurs',
|
|
649
|
-
'browse.selectNamed': 'Utiliser « {name} »',
|
|
588
|
+
'add.guide.title': "Ajouter un espace de travail dans l’application",
|
|
589
|
+
'add.guide': "Cet hôte n’a pas de sélecteur de dossier natif. Utilisez l’entrée officielle « Ajouter un espace de travail » sur la page Nouvelle session.",
|
|
650
590
|
'error.title': 'Une erreur est survenue',
|
|
651
591
|
'cancel': 'Annuler',
|
|
652
592
|
'create': 'Créer',
|
|
@@ -758,20 +698,8 @@ window.__ModuleLoader__.load({
|
|
|
758
698
|
'sync.off': 'यहाँ समन्वयन उपलब्ध नहीं (होस्ट सेटिंग सेवा चाहिए)',
|
|
759
699
|
'sync.loading': 'होस्ट सेटिंग से जुड़ रहे हैं…',
|
|
760
700
|
'flow.creating': 'बनाया जा रहा है…',
|
|
761
|
-
'
|
|
762
|
-
'
|
|
763
|
-
'browse.up': 'ऊपर',
|
|
764
|
-
'browse.newFolder': 'नया फ़ोल्डर',
|
|
765
|
-
'browse.folderName': 'फ़ोल्डर का नाम',
|
|
766
|
-
'browse.empty': 'यहाँ कोई उप-फ़ोल्डर नहीं',
|
|
767
|
-
'browse.loading': 'लोड हो रहा है…',
|
|
768
|
-
'browse.truncated': 'बहुत अधिक फ़ोल्डर; केवल शुरुआत दिखाई गई है।',
|
|
769
|
-
'browse.showHidden': 'छिपी फ़ाइलें दिखाएँ',
|
|
770
|
-
'browse.editPath': 'पथ संपादित करें',
|
|
771
|
-
'browse.select': 'यह फ़ोल्डर चुनें',
|
|
772
|
-
'browse.enter': 'खोलें',
|
|
773
|
-
'browse.drives': 'ड्राइव',
|
|
774
|
-
'browse.selectNamed': '"{name}" चुनें',
|
|
701
|
+
'add.guide.title': "ऐप में कार्यस्थान जोड़ें",
|
|
702
|
+
'add.guide': "इस होस्ट में डेस्कटॉप फ़ोल्डर चयनकर्ता नहीं है। नए सत्र पृष्ठ पर आधिकारिक \"कार्यस्थान जोड़ें\" प्रविष्टि का उपयोग करें।",
|
|
775
703
|
'error.title': 'कुछ गड़बड़ हो गई',
|
|
776
704
|
'cancel': 'रद्द करें',
|
|
777
705
|
'create': 'बनाएँ',
|
|
@@ -883,20 +811,8 @@ window.__ModuleLoader__.load({
|
|
|
883
811
|
'sync.off': 'Sinkronisasi tidak tersedia di sini (perlu layanan pengaturan host)',
|
|
884
812
|
'sync.loading': 'Menyambung ke pengaturan host…',
|
|
885
813
|
'flow.creating': 'Membuat…',
|
|
886
|
-
'
|
|
887
|
-
'
|
|
888
|
-
'browse.up': 'Naik',
|
|
889
|
-
'browse.newFolder': 'Folder baru',
|
|
890
|
-
'browse.folderName': 'Nama folder',
|
|
891
|
-
'browse.empty': 'Tidak ada subfolder di sini',
|
|
892
|
-
'browse.loading': 'Memuat…',
|
|
893
|
-
'browse.truncated': 'Terlalu banyak folder; hanya bagian awal yang ditampilkan.',
|
|
894
|
-
'browse.showHidden': 'Tampilkan file tersembunyi',
|
|
895
|
-
'browse.editPath': 'Edit jalur',
|
|
896
|
-
'browse.select': 'Gunakan folder ini',
|
|
897
|
-
'browse.enter': 'Buka',
|
|
898
|
-
'browse.drives': 'Drive',
|
|
899
|
-
'browse.selectNamed': 'Gunakan "{name}"',
|
|
814
|
+
'add.guide.title': "Tambah ruang kerja di aplikasi",
|
|
815
|
+
'add.guide': "Host ini tidak punya pemilih folder desktop. Gunakan entri resmi \"Tambah ruang kerja\" di halaman Sesi Baru.",
|
|
900
816
|
'error.title': 'Terjadi kesalahan',
|
|
901
817
|
'cancel': 'Batal',
|
|
902
818
|
'create': 'Buat',
|
|
@@ -1008,20 +924,8 @@ window.__ModuleLoader__.load({
|
|
|
1008
924
|
'sync.off': 'Sincronizzazione non disponibile qui (serve il servizio impostazioni dell\'host)',
|
|
1009
925
|
'sync.loading': 'Connessione alle impostazioni dell\'host…',
|
|
1010
926
|
'flow.creating': 'Creazione…',
|
|
1011
|
-
'
|
|
1012
|
-
'
|
|
1013
|
-
'browse.up': 'Livello superiore',
|
|
1014
|
-
'browse.newFolder': 'Nuova cartella',
|
|
1015
|
-
'browse.folderName': 'Nome cartella',
|
|
1016
|
-
'browse.empty': 'Nessuna sottocartella qui',
|
|
1017
|
-
'browse.loading': 'Caricamento…',
|
|
1018
|
-
'browse.truncated': 'Troppe cartelle; è mostrato solo l’inizio.',
|
|
1019
|
-
'browse.showHidden': 'Mostra file nascosti',
|
|
1020
|
-
'browse.editPath': 'Modifica percorso',
|
|
1021
|
-
'browse.select': 'Usa questa cartella',
|
|
1022
|
-
'browse.enter': 'Apri',
|
|
1023
|
-
'browse.drives': 'Unità',
|
|
1024
|
-
'browse.selectNamed': 'Usa "{name}"',
|
|
927
|
+
'add.guide.title': "Aggiungi area di lavoro nell’app",
|
|
928
|
+
'add.guide': "Questo host non ha un selettore di cartelle desktop. Usa la voce ufficiale «Aggiungi area di lavoro» nella pagina Nuova sessione.",
|
|
1025
929
|
'error.title': 'Si è verificato un errore',
|
|
1026
930
|
'cancel': 'Annulla',
|
|
1027
931
|
'create': 'Crea',
|
|
@@ -1133,20 +1037,8 @@ window.__ModuleLoader__.load({
|
|
|
1133
1037
|
'sync.off': 'この環境では同期できません(ホスト設定サービスが必要)',
|
|
1134
1038
|
'sync.loading': 'ホスト設定に接続中…',
|
|
1135
1039
|
'flow.creating': '作成中…',
|
|
1136
|
-
'
|
|
1137
|
-
'
|
|
1138
|
-
'browse.up': '上の階層',
|
|
1139
|
-
'browse.newFolder': '新しいフォルダー',
|
|
1140
|
-
'browse.folderName': 'フォルダー名',
|
|
1141
|
-
'browse.empty': 'ここにサブフォルダーはありません',
|
|
1142
|
-
'browse.loading': '読み込み中…',
|
|
1143
|
-
'browse.truncated': 'フォルダーが多すぎます。先頭のみ表示しています。',
|
|
1144
|
-
'browse.showHidden': '隠しファイルを表示',
|
|
1145
|
-
'browse.editPath': 'パスを編集',
|
|
1146
|
-
'browse.select': 'このフォルダーを選択',
|
|
1147
|
-
'browse.enter': '開く',
|
|
1148
|
-
'browse.drives': 'ドライブ',
|
|
1149
|
-
'browse.selectNamed': '「{name}」を選択',
|
|
1040
|
+
'add.guide.title': "アプリ内でワークスペースを追加",
|
|
1041
|
+
'add.guide': "このホストにはデスクトップのフォルダー選択画面がありません。「新規セッション」ページの公式の「ワークスペースを追加」から開いてください。",
|
|
1150
1042
|
'error.title': 'エラーが発生しました',
|
|
1151
1043
|
'cancel': 'キャンセル',
|
|
1152
1044
|
'create': '作成',
|
|
@@ -1258,20 +1150,8 @@ window.__ModuleLoader__.load({
|
|
|
1258
1150
|
'sync.off': '이 환경에서는 동기화할 수 없습니다(호스트 설정 서비스 필요)',
|
|
1259
1151
|
'sync.loading': '호스트 설정에 연결하는 중…',
|
|
1260
1152
|
'flow.creating': '만드는 중…',
|
|
1261
|
-
'
|
|
1262
|
-
'
|
|
1263
|
-
'browse.up': '상위',
|
|
1264
|
-
'browse.newFolder': '새 폴더',
|
|
1265
|
-
'browse.folderName': '폴더 이름',
|
|
1266
|
-
'browse.empty': '여기에 하위 폴더가 없습니다',
|
|
1267
|
-
'browse.loading': '불러오는 중…',
|
|
1268
|
-
'browse.truncated': '폴더가 너무 많습니다. 앞부분만 표시합니다.',
|
|
1269
|
-
'browse.showHidden': '숨김 파일 표시',
|
|
1270
|
-
'browse.editPath': '경로 편집',
|
|
1271
|
-
'browse.select': '이 폴더 선택',
|
|
1272
|
-
'browse.enter': '열기',
|
|
1273
|
-
'browse.drives': '드라이브',
|
|
1274
|
-
'browse.selectNamed': '"{name}" 선택',
|
|
1153
|
+
'add.guide.title': "앱에서 워크스페이스 추가",
|
|
1154
|
+
'add.guide': "이 호스트에는 데스크톱 폴더 선택기가 없습니다. 새 세션 페이지의 공식 \"워크스페이스 추가\" 항목을 사용하세요.",
|
|
1275
1155
|
'error.title': '문제가 발생했습니다',
|
|
1276
1156
|
'cancel': '취소',
|
|
1277
1157
|
'create': '만들기',
|
|
@@ -1383,20 +1263,8 @@ window.__ModuleLoader__.load({
|
|
|
1383
1263
|
'sync.off': 'Synchronisatie is hier niet beschikbaar (hostinstellingenservice nodig)',
|
|
1384
1264
|
'sync.loading': 'Verbinden met hostinstellingen…',
|
|
1385
1265
|
'flow.creating': 'Bezig met aanmaken…',
|
|
1386
|
-
'
|
|
1387
|
-
'
|
|
1388
|
-
'browse.up': 'Omhoog',
|
|
1389
|
-
'browse.newFolder': 'Nieuwe map',
|
|
1390
|
-
'browse.folderName': 'Mapnaam',
|
|
1391
|
-
'browse.empty': 'Geen submappen hier',
|
|
1392
|
-
'browse.loading': 'Laden…',
|
|
1393
|
-
'browse.truncated': 'Te veel mappen; alleen het begin wordt getoond.',
|
|
1394
|
-
'browse.showHidden': 'Verborgen bestanden tonen',
|
|
1395
|
-
'browse.editPath': 'Pad bewerken',
|
|
1396
|
-
'browse.select': 'Deze map gebruiken',
|
|
1397
|
-
'browse.enter': 'Openen',
|
|
1398
|
-
'browse.drives': 'Stations',
|
|
1399
|
-
'browse.selectNamed': '"{name}" gebruiken',
|
|
1266
|
+
'add.guide.title': "Werkruimte in de app toevoegen",
|
|
1267
|
+
'add.guide': "Deze host heeft geen systeemmapkiezer. Gebruik de officiële optie \"Werkruimte toevoegen\" op de pagina Nieuwe sessie.",
|
|
1400
1268
|
'error.title': 'Er is iets misgegaan',
|
|
1401
1269
|
'cancel': 'Annuleren',
|
|
1402
1270
|
'create': 'Aanmaken',
|
|
@@ -1508,20 +1376,8 @@ window.__ModuleLoader__.load({
|
|
|
1508
1376
|
'sync.off': 'Synchronizacja niedostępna (wymaga usługi ustawień hosta)',
|
|
1509
1377
|
'sync.loading': 'Łączenie z ustawieniami hosta…',
|
|
1510
1378
|
'flow.creating': 'Tworzenie…',
|
|
1511
|
-
'
|
|
1512
|
-
'
|
|
1513
|
-
'browse.up': 'W górę',
|
|
1514
|
-
'browse.newFolder': 'Nowy folder',
|
|
1515
|
-
'browse.folderName': 'Nazwa folderu',
|
|
1516
|
-
'browse.empty': 'Brak podfolderów',
|
|
1517
|
-
'browse.loading': 'Wczytywanie…',
|
|
1518
|
-
'browse.truncated': 'Zbyt wiele folderów; pokazano tylko początek.',
|
|
1519
|
-
'browse.showHidden': 'Pokaż ukryte pliki',
|
|
1520
|
-
'browse.editPath': 'Edytuj ścieżkę',
|
|
1521
|
-
'browse.select': 'Użyj tego folderu',
|
|
1522
|
-
'browse.enter': 'Otwórz',
|
|
1523
|
-
'browse.drives': 'Dyski',
|
|
1524
|
-
'browse.selectNamed': 'Użyj "{name}"',
|
|
1379
|
+
'add.guide.title': "Dodaj obszar roboczy w aplikacji",
|
|
1380
|
+
'add.guide': "Ten host nie ma systemowego wyboru folderów. Użyj oficjalnej pozycji „Dodaj obszar roboczy” na stronie Nowa sesja.",
|
|
1525
1381
|
'error.title': 'Coś poszło nie tak',
|
|
1526
1382
|
'cancel': 'Anuluj',
|
|
1527
1383
|
'create': 'Utwórz',
|
|
@@ -1633,20 +1489,8 @@ window.__ModuleLoader__.load({
|
|
|
1633
1489
|
'sync.off': 'Sincronização indisponível aqui (requer o serviço de configurações do host)',
|
|
1634
1490
|
'sync.loading': 'Conectando às configurações do host…',
|
|
1635
1491
|
'flow.creating': 'Criando…',
|
|
1636
|
-
'
|
|
1637
|
-
'
|
|
1638
|
-
'browse.up': 'Acima',
|
|
1639
|
-
'browse.newFolder': 'Nova pasta',
|
|
1640
|
-
'browse.folderName': 'Nome da pasta',
|
|
1641
|
-
'browse.empty': 'Nenhuma subpasta aqui',
|
|
1642
|
-
'browse.loading': 'Carregando…',
|
|
1643
|
-
'browse.truncated': 'Pastas demais; apenas o início é exibido.',
|
|
1644
|
-
'browse.showHidden': 'Mostrar arquivos ocultos',
|
|
1645
|
-
'browse.editPath': 'Editar caminho',
|
|
1646
|
-
'browse.select': 'Usar esta pasta',
|
|
1647
|
-
'browse.enter': 'Abrir',
|
|
1648
|
-
'browse.drives': 'Unidades',
|
|
1649
|
-
'browse.selectNamed': 'Usar "{name}"',
|
|
1492
|
+
'add.guide.title': "Adicionar área de trabalho no app",
|
|
1493
|
+
'add.guide': "Este host não tem seletor de pastas do sistema. Use a entrada oficial \"Adicionar área de trabalho\" na página Nova sessão.",
|
|
1650
1494
|
'error.title': 'Algo deu errado',
|
|
1651
1495
|
'cancel': 'Cancelar',
|
|
1652
1496
|
'create': 'Criar',
|
|
@@ -1758,20 +1602,8 @@ window.__ModuleLoader__.load({
|
|
|
1758
1602
|
'sync.off': 'Синхронизация недоступна (нужна служба настроек хоста)',
|
|
1759
1603
|
'sync.loading': 'Подключение к настройкам хоста…',
|
|
1760
1604
|
'flow.creating': 'Создание…',
|
|
1761
|
-
'
|
|
1762
|
-
'
|
|
1763
|
-
'browse.up': 'На уровень выше',
|
|
1764
|
-
'browse.newFolder': 'Новая папка',
|
|
1765
|
-
'browse.folderName': 'Имя папки',
|
|
1766
|
-
'browse.empty': 'Здесь нет вложенных папок',
|
|
1767
|
-
'browse.loading': 'Загрузка…',
|
|
1768
|
-
'browse.truncated': 'Слишком много папок; показано только начало.',
|
|
1769
|
-
'browse.showHidden': 'Показывать скрытые файлы',
|
|
1770
|
-
'browse.editPath': 'Изменить путь',
|
|
1771
|
-
'browse.select': 'Выбрать эту папку',
|
|
1772
|
-
'browse.enter': 'Открыть',
|
|
1773
|
-
'browse.drives': 'Диски',
|
|
1774
|
-
'browse.selectNamed': 'Выбрать «{name}»',
|
|
1605
|
+
'add.guide.title': "Добавить рабочую область в приложении",
|
|
1606
|
+
'add.guide': "На этом хосте нет системного выбора папок. Используйте официальный пункт «Добавить рабочую область» на странице «Новый сеанс».",
|
|
1775
1607
|
'error.title': 'Что-то пошло не так',
|
|
1776
1608
|
'cancel': 'Отмена',
|
|
1777
1609
|
'create': 'Создать',
|
|
@@ -1883,20 +1715,8 @@ window.__ModuleLoader__.load({
|
|
|
1883
1715
|
'sync.off': 'Synk är inte tillgänglig här (kräver värduppläggets inställningstjänst)',
|
|
1884
1716
|
'sync.loading': 'Ansluter till värduppläggets inställningar…',
|
|
1885
1717
|
'flow.creating': 'Skapar…',
|
|
1886
|
-
'
|
|
1887
|
-
'
|
|
1888
|
-
'browse.up': 'Upp',
|
|
1889
|
-
'browse.newFolder': 'Ny mapp',
|
|
1890
|
-
'browse.folderName': 'Mappnamn',
|
|
1891
|
-
'browse.empty': 'Inga undermappar här',
|
|
1892
|
-
'browse.loading': 'Laddar…',
|
|
1893
|
-
'browse.truncated': 'För många mappar; endast början visas.',
|
|
1894
|
-
'browse.showHidden': 'Visa dolda filer',
|
|
1895
|
-
'browse.editPath': 'Redigera sökväg',
|
|
1896
|
-
'browse.select': 'Använd den här mappen',
|
|
1897
|
-
'browse.enter': 'Öppna',
|
|
1898
|
-
'browse.drives': 'Enheter',
|
|
1899
|
-
'browse.selectNamed': 'Använd "{name}"',
|
|
1718
|
+
'add.guide.title': "Lägg till arbetsyta i appen",
|
|
1719
|
+
'add.guide': "Den här värden har ingen systemmappväljare. Använd den officiella posten \"Lägg till arbetsyta\" på sidan Ny session.",
|
|
1900
1720
|
'error.title': 'Något gick fel',
|
|
1901
1721
|
'cancel': 'Avbryt',
|
|
1902
1722
|
'create': 'Skapa',
|
|
@@ -2008,20 +1828,8 @@ window.__ModuleLoader__.load({
|
|
|
2008
1828
|
'sync.off': 'สภาพแวดล้อมนี้ไม่รองรับการซิงก์ (ต้องมีบริการการตั้งค่าของโฮสต์)',
|
|
2009
1829
|
'sync.loading': 'กำลังเชื่อมต่อการตั้งค่าโฮสต์…',
|
|
2010
1830
|
'flow.creating': 'กำลังสร้าง…',
|
|
2011
|
-
'
|
|
2012
|
-
'
|
|
2013
|
-
'browse.up': 'ขึ้นหนึ่งระดับ',
|
|
2014
|
-
'browse.newFolder': 'โฟลเดอร์ใหม่',
|
|
2015
|
-
'browse.folderName': 'ชื่อโฟลเดอร์',
|
|
2016
|
-
'browse.empty': 'ไม่มีโฟลเดอร์ย่อยที่นี่',
|
|
2017
|
-
'browse.loading': 'กำลังโหลด…',
|
|
2018
|
-
'browse.truncated': 'มีโฟลเดอร์มากเกินไป แสดงเฉพาะส่วนต้น',
|
|
2019
|
-
'browse.showHidden': 'แสดงไฟล์ที่ซ่อนอยู่',
|
|
2020
|
-
'browse.editPath': 'แก้ไขเส้นทาง',
|
|
2021
|
-
'browse.select': 'ใช้โฟลเดอร์นี้',
|
|
2022
|
-
'browse.enter': 'เปิด',
|
|
2023
|
-
'browse.drives': 'ไดรฟ์',
|
|
2024
|
-
'browse.selectNamed': 'ใช้ "{name}"',
|
|
1831
|
+
'add.guide.title': "เพิ่มเวิร์กสเปซในแอป",
|
|
1832
|
+
'add.guide': "โฮสต์นี้ไม่มีตัวเลือกโฟลเดอร์ของระบบ ใช้รายการ \"เพิ่มเวิร์กสเปซ\" อย่างเป็นทางการในหน้าเซสชันใหม่",
|
|
2025
1833
|
'error.title': 'เกิดข้อผิดพลาด',
|
|
2026
1834
|
'cancel': 'ยกเลิก',
|
|
2027
1835
|
'create': 'สร้าง',
|
|
@@ -2133,20 +1941,8 @@ window.__ModuleLoader__.load({
|
|
|
2133
1941
|
'sync.off': 'Bu ortamda eşitleme yok (ana makine ayar servisi gerekir)',
|
|
2134
1942
|
'sync.loading': 'Ana makine ayarlarına bağlanılıyor…',
|
|
2135
1943
|
'flow.creating': 'Oluşturuluyor…',
|
|
2136
|
-
'
|
|
2137
|
-
'
|
|
2138
|
-
'browse.up': 'Üst düzey',
|
|
2139
|
-
'browse.newFolder': 'Yeni klasör',
|
|
2140
|
-
'browse.folderName': 'Klasör adı',
|
|
2141
|
-
'browse.empty': 'Burada alt klasör yok',
|
|
2142
|
-
'browse.loading': 'Yükleniyor…',
|
|
2143
|
-
'browse.truncated': 'Çok fazla klasör var; yalnızca başlangıç gösteriliyor.',
|
|
2144
|
-
'browse.showHidden': 'Gizli dosyaları göster',
|
|
2145
|
-
'browse.editPath': 'Yolu düzenle',
|
|
2146
|
-
'browse.select': 'Bu klasörü kullan',
|
|
2147
|
-
'browse.enter': 'Aç',
|
|
2148
|
-
'browse.drives': 'Sürücüler',
|
|
2149
|
-
'browse.selectNamed': '"{name}" kullan',
|
|
1944
|
+
'add.guide.title': "Uygulama içinde çalışma alanı ekle",
|
|
1945
|
+
'add.guide': "Bu ana makinede masaüstü klasör seçici yok. Yeni Oturum sayfasındaki resmî \"Çalışma alanı ekle\" girişini kullanın.",
|
|
2150
1946
|
'error.title': 'Bir şeyler ters gitti',
|
|
2151
1947
|
'cancel': 'İptal',
|
|
2152
1948
|
'create': 'Oluştur',
|
|
@@ -2258,20 +2054,8 @@ window.__ModuleLoader__.load({
|
|
|
2258
2054
|
'sync.off': 'Môi trường này không hỗ trợ đồng bộ (cần dịch vụ cài đặt của máy chủ)',
|
|
2259
2055
|
'sync.loading': 'Đang kết nối cài đặt máy chủ…',
|
|
2260
2056
|
'flow.creating': 'Đang tạo…',
|
|
2261
|
-
'
|
|
2262
|
-
'
|
|
2263
|
-
'browse.up': 'Lên một cấp',
|
|
2264
|
-
'browse.newFolder': 'Thư mục mới',
|
|
2265
|
-
'browse.folderName': 'Tên thư mục',
|
|
2266
|
-
'browse.empty': 'Không có thư mục con ở đây',
|
|
2267
|
-
'browse.loading': 'Đang tải…',
|
|
2268
|
-
'browse.truncated': 'Quá nhiều thư mục; chỉ hiển thị phần đầu.',
|
|
2269
|
-
'browse.showHidden': 'Hiện tệp ẩn',
|
|
2270
|
-
'browse.editPath': 'Sửa đường dẫn',
|
|
2271
|
-
'browse.select': 'Dùng thư mục này',
|
|
2272
|
-
'browse.enter': 'Mở',
|
|
2273
|
-
'browse.drives': 'Ổ đĩa',
|
|
2274
|
-
'browse.selectNamed': 'Dùng "{name}"',
|
|
2057
|
+
'add.guide.title': "Thêm không gian làm việc trong ứng dụng",
|
|
2058
|
+
'add.guide': "Máy chủ này không có trình chọn thư mục trên màn hình. Dùng mục chính thức \"Thêm không gian làm việc\" ở trang Phiên mới.",
|
|
2275
2059
|
'error.title': 'Đã xảy ra lỗi',
|
|
2276
2060
|
'cancel': 'Hủy',
|
|
2277
2061
|
'create': 'Tạo',
|
|
@@ -2383,20 +2167,8 @@ window.__ModuleLoader__.load({
|
|
|
2383
2167
|
'sync.off': '呢個環境唔支援同步(需要宿主設定服務)',
|
|
2384
2168
|
'sync.loading': '連接緊宿主設定…',
|
|
2385
2169
|
'flow.creating': '建立緊…',
|
|
2386
|
-
'
|
|
2387
|
-
'
|
|
2388
|
-
'browse.up': '上一層',
|
|
2389
|
-
'browse.newFolder': '新增資料夾',
|
|
2390
|
-
'browse.folderName': '資料夾名稱',
|
|
2391
|
-
'browse.empty': '呢個資料夾冇子資料夾',
|
|
2392
|
-
'browse.loading': '載入緊…',
|
|
2393
|
-
'browse.truncated': '資料夾太多,只顯示開頭部分。',
|
|
2394
|
-
'browse.showHidden': '顯示隱藏檔案',
|
|
2395
|
-
'browse.editPath': '編輯路徑',
|
|
2396
|
-
'browse.select': '揀呢個資料夾',
|
|
2397
|
-
'browse.enter': '入去',
|
|
2398
|
-
'browse.drives': '磁碟',
|
|
2399
|
-
'browse.selectNamed': '揀「{name}」',
|
|
2170
|
+
'add.guide.title': "應用程式內新增工作區",
|
|
2171
|
+
'add.guide': "呢部主機冇桌面資料夾選擇器。請去「新工作階段」頁,用官方嘅「新增工作區」開應用程式內瀏覽器。",
|
|
2400
2172
|
'error.title': '出咗錯',
|
|
2401
2173
|
'cancel': '取消',
|
|
2402
2174
|
'create': '建立',
|
|
@@ -2508,20 +2280,8 @@ window.__ModuleLoader__.load({
|
|
|
2508
2280
|
'sync.off': '呢個環境唔支援同步(需要宿主設定服務)',
|
|
2509
2281
|
'sync.loading': '連接緊宿主設定…',
|
|
2510
2282
|
'flow.creating': '建立緊…',
|
|
2511
|
-
'
|
|
2512
|
-
'
|
|
2513
|
-
'browse.up': '上一層',
|
|
2514
|
-
'browse.newFolder': '新增資料夾',
|
|
2515
|
-
'browse.folderName': '資料夾名稱',
|
|
2516
|
-
'browse.empty': '呢個資料夾冇子資料夾',
|
|
2517
|
-
'browse.loading': '載入緊…',
|
|
2518
|
-
'browse.truncated': '資料夾太多,只顯示開頭部分。',
|
|
2519
|
-
'browse.showHidden': '顯示隱藏檔案',
|
|
2520
|
-
'browse.editPath': '編輯路徑',
|
|
2521
|
-
'browse.select': '揀呢個資料夾',
|
|
2522
|
-
'browse.enter': '入去',
|
|
2523
|
-
'browse.drives': '磁碟',
|
|
2524
|
-
'browse.selectNamed': '揀「{name}」',
|
|
2283
|
+
'add.guide.title': "應用程式內新增工作區",
|
|
2284
|
+
'add.guide': "呢部主機冇桌面資料夾選擇器。請去「新工作階段」頁,用官方嘅「新增工作區」開應用程式內瀏覽器。",
|
|
2525
2285
|
'error.title': '出咗錯',
|
|
2526
2286
|
'cancel': '取消',
|
|
2527
2287
|
'create': '建立',
|
|
@@ -2633,20 +2393,8 @@ window.__ModuleLoader__.load({
|
|
|
2633
2393
|
'sync.off': '目前環境不支援同步(需要宿主設定服務)',
|
|
2634
2394
|
'sync.loading': '正在連線宿主設定…',
|
|
2635
2395
|
'flow.creating': '正在建立…',
|
|
2636
|
-
'
|
|
2637
|
-
'
|
|
2638
|
-
'browse.up': '上一層',
|
|
2639
|
-
'browse.newFolder': '新增資料夾',
|
|
2640
|
-
'browse.folderName': '資料夾名稱',
|
|
2641
|
-
'browse.empty': '此資料夾沒有子資料夾',
|
|
2642
|
-
'browse.loading': '載入中…',
|
|
2643
|
-
'browse.truncated': '資料夾過多,僅顯示開頭部分。',
|
|
2644
|
-
'browse.showHidden': '顯示隱藏檔案',
|
|
2645
|
-
'browse.editPath': '編輯路徑',
|
|
2646
|
-
'browse.select': '選擇此資料夾',
|
|
2647
|
-
'browse.enter': '進入',
|
|
2648
|
-
'browse.drives': '磁碟',
|
|
2649
|
-
'browse.selectNamed': '選擇「{name}」',
|
|
2396
|
+
'add.guide.title': "應用程式內新增工作區",
|
|
2397
|
+
'add.guide': "此主機沒有桌面資料夾選擇器。請至「新工作階段」頁,用官方的「新增工作區」開啟應用程式內瀏覽器。",
|
|
2650
2398
|
'error.title': '發生錯誤',
|
|
2651
2399
|
'cancel': '取消',
|
|
2652
2400
|
'create': '建立',
|
|
@@ -3272,39 +3020,6 @@ window.__ModuleLoader__.load({
|
|
|
3272
3020
|
'.bw-btn-primary{background:var(--dsw-alias-brand-primary,#5b8def);border-color:transparent;color:var(--dsw-alias-brand-text,#fff)}',
|
|
3273
3021
|
'.bw-btn-primary:hover{background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-brand-primary,#5b8def))}',
|
|
3274
3022
|
'.bw-btn:disabled{opacity:.5;cursor:default}',
|
|
3275
|
-
'.bw-browse-body{min-width:360px;max-width:460px}',
|
|
3276
|
-
'.bw-browse-bar{display:flex;align-items:center;gap:4px}',
|
|
3277
|
-
'.bw-browse-crumbs{display:flex;align-items:center;gap:2px;flex:1;min-width:0;overflow-x:auto}',
|
|
3278
|
-
'.bw-browse-crumb{border:none;background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font:inherit;font-size:12px;padding:2px 5px;border-radius:4px;cursor:pointer;white-space:nowrap;max-width:150px;overflow:hidden;text-overflow:ellipsis;flex:none}',
|
|
3279
|
-
'.bw-browse-crumb:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3280
|
-
'.bw-browse-crumb:disabled{opacity:.4;cursor:default}',
|
|
3281
|
-
'.bw-browse-current{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:12px;padding:2px 5px;max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
3282
|
-
'.bw-browse-nav{font-size:13px;line-height:1}',
|
|
3283
|
-
'.bw-browse-edit{flex:none;display:grid;place-items:center;width:24px;height:24px;padding:0;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-label-tertiary,#9a9a9a);cursor:pointer}',
|
|
3284
|
-
'.bw-browse-edit:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3285
|
-
'.bw-browse-edit svg{width:14px;height:14px}',
|
|
3286
|
-
'.bw-browse-edit-row{display:flex;gap:6px;align-items:center}',
|
|
3287
|
-
'.bw-browse-list{display:flex;flex-direction:column;gap:1px;height:240px;overflow:auto;padding:4px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06))}',
|
|
3288
|
-
'.bw-browse-row{display:flex;align-items:center;gap:8px;width:100%;flex:none;text-align:left;border:none;background:transparent;color:var(--dsw-alias-label-primary,#e6e6e6);font:inherit;font-size:12.5px;padding:5px 8px;border-radius:6px;cursor:pointer;user-select:none}',
|
|
3289
|
-
'.bw-browse-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
|
|
3290
|
-
'.bw-browse-row:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8def);outline-offset:-2px}',
|
|
3291
|
-
'.bw-browse-row svg{width:15px;height:15px;flex:none;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
3292
|
-
'.bw-browse-row-on{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary,#5b8def)}',
|
|
3293
|
-
'.bw-browse-open{flex:none;width:22px;height:22px;padding:0;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-label-tertiary,#9a9a9a);font:inherit;font-size:15px;line-height:1;cursor:pointer}',
|
|
3294
|
-
'.bw-browse-open:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.16));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3295
|
-
'.bw-browse-drives{display:flex;align-items:center;gap:4px;margin-left:auto}',
|
|
3296
|
-
'.bw-browse-drive{min-width:30px;padding:3px 6px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font:inherit;font-size:11.5px;cursor:pointer}',
|
|
3297
|
-
'.bw-browse-drive:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3298
|
-
'.bw-browse-drive-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3299
|
-
'.bw-browse-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
3300
|
-
'.bw-browse-note{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a);padding:10px 8px;text-align:center}',
|
|
3301
|
-
'.bw-browse-new-row{display:flex;gap:6px;align-items:center}',
|
|
3302
|
-
'.bw-browse-tools{display:flex;align-items:center;gap:6px;flex-wrap:wrap}',
|
|
3303
|
-
'.bw-browse-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}',
|
|
3304
|
-
'.bw-browse-tool{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font:inherit;font-size:12px;padding:4px 10px;border-radius:6px;cursor:pointer;white-space:nowrap}',
|
|
3305
|
-
'.bw-browse-tool:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3306
|
-
'.bw-browse-tool:disabled{opacity:.45;cursor:default}',
|
|
3307
|
-
'.bw-browse-tool-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3308
3023
|
'.bw-error-text{font-size:12.5px;color:var(--dsw-alias-state-error-primary,#f85149);word-break:break-all;max-width:380px}',
|
|
3309
3024
|
'.bw-dialog-input-row{display:flex;gap:6px;align-items:center}',
|
|
3310
3025
|
'.bw-glow-row{display:flex;align-items:center;gap:10px}',
|
|
@@ -3558,10 +3273,17 @@ window.__ModuleLoader__.load({
|
|
|
3558
3273
|
// headless) gets `browse`, whose wire verbs are list/createDirectory ONLY
|
|
3559
3274
|
// and whose `pick` is refused BY DESIGN with `directory-picker/unavailable`
|
|
3560
3275
|
// ("... needs the native capability; the composed picker serves browse").
|
|
3561
|
-
//
|
|
3562
|
-
//
|
|
3563
|
-
//
|
|
3564
|
-
//
|
|
3276
|
+
// v0.16.0: the sidebar no longer draws its own in-app browser. The browse
|
|
3277
|
+
// backend SHIPS one (dsh-client-ui-directory-picker-browse, mounted as a
|
|
3278
|
+
// host+surface pair, its client half registering into both directory-flow
|
|
3279
|
+
// holes) — but it can only be rendered by the entry that DECLARED the
|
|
3280
|
+
// child hole (the shipped WorkspaceBrowser), and a child declaration is
|
|
3281
|
+
// exclusive, so shadowing that seat forfeits the authorization for good
|
|
3282
|
+
// (ui-renderer: "not declared by this entry's children"). A native Host
|
|
3283
|
+
// still gets the official OS chooser; a browse Host is pointed at the
|
|
3284
|
+
// official in-app flow in the conversation empty state instead of a
|
|
3285
|
+
// second-hand imitation. The probe stays: it decides which of the two
|
|
3286
|
+
// this page gets, and a refused pick still classifies the backend.
|
|
3565
3287
|
const pickerState = { kind: 'unknown', probe: null, api: null }
|
|
3566
3288
|
/**
|
|
3567
3289
|
* True for the refusal a Host serving the OTHER capability throws: the wire
|
|
@@ -3602,11 +3324,6 @@ window.__ModuleLoader__.load({
|
|
|
3602
3324
|
})
|
|
3603
3325
|
return pickerState.probe
|
|
3604
3326
|
}
|
|
3605
|
-
/** Commit a browse verdict the flow discovered the hard way (a refused pick). */
|
|
3606
|
-
const markPickerBrowse = () => {
|
|
3607
|
-
pickerState.kind = 'browse'
|
|
3608
|
-
pickerState.probe = Promise.resolve('browse')
|
|
3609
|
-
}
|
|
3610
3327
|
|
|
3611
3328
|
/* ============================ flow dialog ========================= */
|
|
3612
3329
|
|
|
@@ -3805,404 +3522,7 @@ window.__ModuleLoader__.load({
|
|
|
3805
3522
|
}
|
|
3806
3523
|
}
|
|
3807
3524
|
|
|
3808
|
-
/**
|
|
3809
|
-
* The in-app directory browser: the interaction a `browse`-composed Host can
|
|
3810
|
-
* actually serve. It appears when the probe (or a refused pick) proves the
|
|
3811
|
-
* OS chooser is not this Host's backend — a LAN/all-interfaces bind, an SSH
|
|
3812
|
-
* launch, a headless host — where `pick` is refused by design and
|
|
3813
|
-
* list/createDirectory are the only wire verbs. One level at a time: a
|
|
3814
|
-
* clickable Home-rooted breadcrumb chain, an editable path, an inline
|
|
3815
|
-
* new-folder row and the hidden-entry toggle; the listed level (or any
|
|
3816
|
-
* ancestor crumb) is the pick. Browse failures stay inside the dialog — the
|
|
3817
|
-
* owner's error surface belongs to the pick/create conversation, and a
|
|
3818
|
-
* denied listing must never close the flow.
|
|
3819
|
-
*/
|
|
3820
|
-
/**
|
|
3821
|
-
* Page-lifetime cache of the Windows drive letters this Host actually
|
|
3822
|
-
* serves. The browse API can only list a path, so the first open probes the
|
|
3823
|
-
* usual letters once (six cheap listings, a missing drive fails silently)
|
|
3824
|
-
* and every later open renders the chips straight from here.
|
|
3825
|
-
*/
|
|
3826
|
-
const browseDrives = { probed: false, probing: false, list: [] }
|
|
3827
|
-
|
|
3828
|
-
/**
|
|
3829
|
-
* Run the one wire verb that actually puts a folder on disk and hand back
|
|
3830
|
-
* the created path. This is deliberately a module-level seam the smoke suite
|
|
3831
|
-
* drives for real: v0.11.4 folded the call into the success handler and
|
|
3832
|
-
* dropped it, so the form closed on a resolved `undefined` — no request, no
|
|
3833
|
-
* error, no folder. A string-level assertion cannot see that; calling it can.
|
|
3834
|
-
*/
|
|
3835
|
-
const createFolderIn = (createDirectory, path, name) => Promise.resolve()
|
|
3836
|
-
.then(() => createDirectory(path, name))
|
|
3837
|
-
|
|
3838
|
-
function DirectoryBrowseDialog(props) {
|
|
3839
|
-
const { open, busy, listDirectory, createDirectory, onPick, onClose, t } = props
|
|
3840
|
-
const [listing, setListing] = React.useState(null)
|
|
3841
|
-
const [loading, setLoading] = React.useState(false)
|
|
3842
|
-
const [error, setError] = React.useState('')
|
|
3843
|
-
const [showHidden, setShowHidden] = React.useState(false)
|
|
3844
|
-
const [editing, setEditing] = React.useState(false)
|
|
3845
|
-
const [draft, setDraft] = React.useState('')
|
|
3846
|
-
const [creating, setCreating] = React.useState(false)
|
|
3847
|
-
const [createName, setCreateName] = React.useState('')
|
|
3848
|
-
const [createBusy, setCreateBusy] = React.useState(false)
|
|
3849
|
-
const [createError, setCreateError] = React.useState('')
|
|
3850
|
-
const [selected, setSelected] = React.useState('')
|
|
3851
|
-
const [drives, setDrives] = React.useState(browseDrives.list)
|
|
3852
|
-
// Guards a superseded scan: a slow level must not overwrite a newer one.
|
|
3853
|
-
const seqRef = React.useRef(0)
|
|
3854
|
-
const crumbsRef = React.useRef(null)
|
|
3855
|
-
|
|
3856
|
-
const go = (path, keepSelected) => {
|
|
3857
|
-
const seq = seqRef.current + 1
|
|
3858
|
-
seqRef.current = seq
|
|
3859
|
-
setLoading(true)
|
|
3860
|
-
setError('')
|
|
3861
|
-
if (keepSelected !== true) setSelected('')
|
|
3862
|
-
Promise.resolve()
|
|
3863
|
-
.then(() => listDirectory(path, undefined))
|
|
3864
|
-
.then((next) => {
|
|
3865
|
-
if (seqRef.current !== seq) return
|
|
3866
|
-
setListing(next || null)
|
|
3867
|
-
setLoading(false)
|
|
3868
|
-
})
|
|
3869
|
-
.catch((reason) => {
|
|
3870
|
-
if (seqRef.current !== seq) return
|
|
3871
|
-
setLoading(false)
|
|
3872
|
-
setError(messageOf(reason))
|
|
3873
|
-
})
|
|
3874
|
-
}
|
|
3875
|
-
|
|
3876
|
-
React.useEffect(() => {
|
|
3877
|
-
if (!open) return undefined
|
|
3878
|
-
seqRef.current += 1
|
|
3879
|
-
setListing(null)
|
|
3880
|
-
setError('')
|
|
3881
|
-
setEditing(false)
|
|
3882
|
-
setCreating(false)
|
|
3883
|
-
setCreateName('')
|
|
3884
|
-
setCreateBusy(false)
|
|
3885
|
-
setCreateError('')
|
|
3886
|
-
setShowHidden(false)
|
|
3887
|
-
setSelected('')
|
|
3888
|
-
go(undefined)
|
|
3889
|
-
return () => { seqRef.current += 1 }
|
|
3890
|
-
}, [open])
|
|
3891
|
-
|
|
3892
|
-
const currentPath = listing && typeof listing.path === 'string' ? listing.path : ''
|
|
3893
|
-
const homePath = listing && typeof listing.home === 'string' ? listing.home : ''
|
|
3894
|
-
const windowsHost = homePath.indexOf('\\') !== -1
|
|
3895
|
-
const hasListing = listing !== null
|
|
3896
|
-
|
|
3897
|
-
// Windows drives are separate filesystem roots and the browse API can only
|
|
3898
|
-
// list a path, so probe the usual letters once per page (cached above) and
|
|
3899
|
-
// offer the ones that answered. A drive nobody probed is still reachable
|
|
3900
|
-
// through the path editor, which is the only general answer anyway.
|
|
3901
|
-
React.useEffect(() => {
|
|
3902
|
-
if (!open || !hasListing || !windowsHost) return undefined
|
|
3903
|
-
if (browseDrives.probed || browseDrives.probing) {
|
|
3904
|
-
if (browseDrives.list.length > 0) setDrives(browseDrives.list)
|
|
3905
|
-
return undefined
|
|
3906
|
-
}
|
|
3907
|
-
browseDrives.probing = true
|
|
3908
|
-
let alive = true
|
|
3909
|
-
const letters = ['C:', 'D:', 'E:', 'F:', 'G:', 'H:']
|
|
3910
|
-
Promise.all(letters.map((letter) => Promise.resolve()
|
|
3911
|
-
.then(() => listDirectory(letter + '\\', undefined))
|
|
3912
|
-
.then(() => letter, () => null)))
|
|
3913
|
-
.then((found) => {
|
|
3914
|
-
browseDrives.list = found.filter(Boolean)
|
|
3915
|
-
browseDrives.probed = true
|
|
3916
|
-
browseDrives.probing = false
|
|
3917
|
-
if (alive) setDrives(browseDrives.list)
|
|
3918
|
-
}, () => { browseDrives.probing = false })
|
|
3919
|
-
return () => { alive = false }
|
|
3920
|
-
}, [open, hasListing, windowsHost])
|
|
3921
|
-
|
|
3922
|
-
// A deep path must show its TAIL — that is where you are — so the chain
|
|
3923
|
-
// keeps its right edge in view instead of cutting the last crumb off.
|
|
3924
|
-
React.useEffect(() => {
|
|
3925
|
-
const node = crumbsRef.current
|
|
3926
|
-
if (node) node.scrollLeft = node.scrollWidth
|
|
3927
|
-
}, [listing])
|
|
3928
|
-
|
|
3929
|
-
if (!open) return null
|
|
3930
|
-
|
|
3931
|
-
// The whole ancestry from the filesystem ROOT: rooting the chain at Home
|
|
3932
|
-
// (the official dialog's choice) hides where the listed level actually is,
|
|
3933
|
-
// and the drive crumb answers "which disk am I on" at a glance.
|
|
3934
|
-
const crumbs = listing && Array.isArray(listing.crumbs) ? listing.crumbs : []
|
|
3935
|
-
const parentPath = crumbs.length > 1 ? crumbs[crumbs.length - 2].path : ''
|
|
3936
|
-
const entries = (listing && Array.isArray(listing.entries) ? listing.entries : [])
|
|
3937
|
-
.filter((entry) => showHidden || !entry.hidden)
|
|
3938
|
-
const selectedEntry = entries.find((entry) => entry.path === selected) || null
|
|
3939
|
-
const targetPath = selectedEntry ? selectedEntry.path : currentPath
|
|
3940
|
-
|
|
3941
|
-
const submitDraft = () => {
|
|
3942
|
-
const path = draft.trim()
|
|
3943
|
-
setEditing(false)
|
|
3944
|
-
if (path === '') { go(undefined); return }
|
|
3945
|
-
go(path)
|
|
3946
|
-
}
|
|
3947
|
-
const submitCreate = () => {
|
|
3948
|
-
const name = createName.trim()
|
|
3949
|
-
if (name === '' || createBusy || currentPath === '') return
|
|
3950
|
-
setCreateBusy(true)
|
|
3951
|
-
setCreateError('')
|
|
3952
|
-
createFolderIn(createDirectory, currentPath, name)
|
|
3953
|
-
.then((created) => {
|
|
3954
|
-
setCreateBusy(false)
|
|
3955
|
-
setCreating(false)
|
|
3956
|
-
setCreateName('')
|
|
3957
|
-
// Land on the folder just made: it is almost always the one the
|
|
3958
|
-
// operator wants to adopt next.
|
|
3959
|
-
if (typeof created === 'string' && created !== '') setSelected(created)
|
|
3960
|
-
go(currentPath, true)
|
|
3961
|
-
})
|
|
3962
|
-
.catch((reason) => {
|
|
3963
|
-
setCreateBusy(false)
|
|
3964
|
-
setCreateError(messageOf(reason))
|
|
3965
|
-
})
|
|
3966
|
-
}
|
|
3967
|
-
|
|
3968
|
-
const head = editing
|
|
3969
|
-
? E('div', { className: 'bw-browse-edit-row' },
|
|
3970
|
-
E('input', {
|
|
3971
|
-
className: 'bw-input',
|
|
3972
|
-
value: draft,
|
|
3973
|
-
autoFocus: true,
|
|
3974
|
-
spellCheck: false,
|
|
3975
|
-
placeholder: currentPath,
|
|
3976
|
-
onChange: (e) => setDraft(e.target.value),
|
|
3977
|
-
onKeyDown: (e) => {
|
|
3978
|
-
if (e.key === 'Enter') submitDraft()
|
|
3979
|
-
else if (e.key === 'Escape') setEditing(false)
|
|
3980
|
-
},
|
|
3981
|
-
}),
|
|
3982
|
-
E(BTN, { variant: 'outline', onClick: () => setEditing(false) }, t('cancel')),
|
|
3983
|
-
)
|
|
3984
|
-
: E('div', { className: 'bw-browse-bar' },
|
|
3985
|
-
E('button', {
|
|
3986
|
-
type: 'button',
|
|
3987
|
-
className: 'bw-browse-crumb bw-browse-nav',
|
|
3988
|
-
title: t('browse.up'),
|
|
3989
|
-
'aria-label': t('browse.up'),
|
|
3990
|
-
disabled: parentPath === '',
|
|
3991
|
-
onClick: () => { if (parentPath !== '') go(parentPath) },
|
|
3992
|
-
}, '↑'),
|
|
3993
|
-
E('div', { className: 'bw-browse-crumbs', ref: crumbsRef },
|
|
3994
|
-
crumbs.map((crumb, index) => (index === crumbs.length - 1
|
|
3995
|
-
? E('span', { key: crumb.path, className: 'bw-browse-crumb bw-browse-current', title: crumb.path }, crumb.name)
|
|
3996
|
-
: E('button', {
|
|
3997
|
-
key: crumb.path,
|
|
3998
|
-
type: 'button',
|
|
3999
|
-
className: 'bw-browse-crumb',
|
|
4000
|
-
title: crumb.path,
|
|
4001
|
-
onClick: () => go(crumb.path),
|
|
4002
|
-
}, crumb.name))),
|
|
4003
|
-
),
|
|
4004
|
-
E('button', {
|
|
4005
|
-
type: 'button',
|
|
4006
|
-
className: 'bw-browse-edit',
|
|
4007
|
-
title: t('browse.editPath'),
|
|
4008
|
-
'aria-label': t('browse.editPath'),
|
|
4009
|
-
onClick: () => { setDraft(currentPath); setEditing(true) },
|
|
4010
|
-
}, icon('IconEditOutline16')),
|
|
4011
|
-
)
|
|
4012
|
-
|
|
4013
|
-
// One click SELECTS a row (the footer button then adopts it — no need to
|
|
4014
|
-
// step inside just to pick a folder), a double click or the row's chevron
|
|
4015
|
-
// enters it. Touch needs the chevron: a double tap is not a gesture.
|
|
4016
|
-
const rows = entries.map((entry) => E('div', {
|
|
4017
|
-
key: entry.path,
|
|
4018
|
-
className: cls('bw-browse-row', selected === entry.path && 'bw-browse-row-on'),
|
|
4019
|
-
title: entry.path,
|
|
4020
|
-
role: 'button',
|
|
4021
|
-
tabIndex: 0,
|
|
4022
|
-
onClick: () => setSelected((current) => (current === entry.path ? '' : entry.path)),
|
|
4023
|
-
onDoubleClick: () => go(entry.path),
|
|
4024
|
-
onKeyDown: (event) => {
|
|
4025
|
-
if (event.key === 'Enter') go(entry.path)
|
|
4026
|
-
else if (event.key === ' ') { event.preventDefault(); setSelected((current) => (current === entry.path ? '' : entry.path)) }
|
|
4027
|
-
},
|
|
4028
|
-
},
|
|
4029
|
-
icon('IconFolderClose16'),
|
|
4030
|
-
E('span', { className: 'bw-browse-name' }, entry.name),
|
|
4031
|
-
E('button', {
|
|
4032
|
-
type: 'button',
|
|
4033
|
-
className: 'bw-browse-open',
|
|
4034
|
-
title: t('browse.enter'),
|
|
4035
|
-
'aria-label': t('browse.enter'),
|
|
4036
|
-
onClick: (event) => { event.stopPropagation(); go(entry.path) },
|
|
4037
|
-
}, '›'),
|
|
4038
|
-
))
|
|
4039
|
-
|
|
4040
|
-
const body = loading
|
|
4041
|
-
? E('div', { className: 'bw-browse-note' }, t('browse.loading'))
|
|
4042
|
-
: rows.length > 0
|
|
4043
|
-
? rows
|
|
4044
|
-
: E('div', { className: 'bw-browse-note' }, t('browse.empty'))
|
|
4045
|
-
|
|
4046
|
-
const newRow = creating
|
|
4047
|
-
? E('div', { className: 'bw-browse-new-row' },
|
|
4048
|
-
E('input', {
|
|
4049
|
-
className: 'bw-input',
|
|
4050
|
-
value: createName,
|
|
4051
|
-
autoFocus: true,
|
|
4052
|
-
spellCheck: false,
|
|
4053
|
-
placeholder: t('browse.folderName'),
|
|
4054
|
-
disabled: createBusy,
|
|
4055
|
-
onChange: (e) => setCreateName(e.target.value),
|
|
4056
|
-
onKeyDown: (e) => {
|
|
4057
|
-
if (e.key === 'Enter') submitCreate()
|
|
4058
|
-
else if (e.key === 'Escape') { setCreating(false); setCreateError('') }
|
|
4059
|
-
},
|
|
4060
|
-
}),
|
|
4061
|
-
E(BTN, {
|
|
4062
|
-
variant: 'outline',
|
|
4063
|
-
disabled: createBusy,
|
|
4064
|
-
onClick: () => { setCreating(false); setCreateError('') },
|
|
4065
|
-
}, t('cancel')),
|
|
4066
|
-
E(BTN, { variant: 'primary', onClick: submitCreate, disabled: createBusy },
|
|
4067
|
-
createBusy ? t('flow.creating') : t('create')),
|
|
4068
|
-
)
|
|
4069
|
-
: null
|
|
4070
|
-
|
|
4071
|
-
// The dialog is deliberately narrower than the official browser, so the
|
|
4072
|
-
// two browsing tools sit on their own row above the list instead of
|
|
4073
|
-
// sharing the footer with the commit pair (they used to wrap there).
|
|
4074
|
-
const tools = E('div', { className: 'bw-browse-tools' },
|
|
4075
|
-
E('button', {
|
|
4076
|
-
type: 'button',
|
|
4077
|
-
className: cls('bw-browse-tool', currentPath !== '' && currentPath === homePath && 'bw-browse-tool-on'),
|
|
4078
|
-
disabled: homePath === '' || currentPath === homePath,
|
|
4079
|
-
onClick: () => { if (homePath !== '') go(homePath) },
|
|
4080
|
-
}, t('browse.home')),
|
|
4081
|
-
E('button', {
|
|
4082
|
-
type: 'button',
|
|
4083
|
-
className: cls('bw-browse-tool', showHidden && 'bw-browse-tool-on'),
|
|
4084
|
-
'aria-pressed': showHidden ? 'true' : 'false',
|
|
4085
|
-
onClick: () => setShowHidden((value) => !value),
|
|
4086
|
-
}, t('browse.showHidden')),
|
|
4087
|
-
E('button', {
|
|
4088
|
-
type: 'button',
|
|
4089
|
-
className: 'bw-browse-tool',
|
|
4090
|
-
disabled: creating || currentPath === '',
|
|
4091
|
-
onClick: () => { setCreating(true); setCreateName(''); setCreateError('') },
|
|
4092
|
-
}, t('browse.newFolder')),
|
|
4093
|
-
drives.length > 1
|
|
4094
|
-
? E('div', {
|
|
4095
|
-
className: 'bw-browse-drives',
|
|
4096
|
-
title: t('browse.drives'),
|
|
4097
|
-
role: 'group',
|
|
4098
|
-
'aria-label': t('browse.drives'),
|
|
4099
|
-
}, drives.map((drive) => E('button', {
|
|
4100
|
-
key: drive,
|
|
4101
|
-
type: 'button',
|
|
4102
|
-
className: cls('bw-browse-drive', currentPath.slice(0, 2).toUpperCase() === drive && 'bw-browse-drive-on'),
|
|
4103
|
-
title: drive + '\\',
|
|
4104
|
-
onClick: () => go(drive + '\\'),
|
|
4105
|
-
}, drive)))
|
|
4106
|
-
: null,
|
|
4107
|
-
)
|
|
4108
|
-
|
|
4109
|
-
const footer = E('div', { className: 'bw-browse-actions' },
|
|
4110
|
-
E(BTN, { variant: 'outline', onClick: onClose, disabled: busy === true }, t('cancel')),
|
|
4111
|
-
E(BTN, {
|
|
4112
|
-
variant: 'primary',
|
|
4113
|
-
disabled: busy === true || targetPath === '',
|
|
4114
|
-
onClick: () => { if (targetPath !== '') onPick(targetPath) },
|
|
4115
|
-
}, selectedEntry !== null ? t('browse.selectNamed', { name: selectedEntry.name }) : t('browse.select')),
|
|
4116
|
-
)
|
|
4117
|
-
|
|
4118
|
-
return E(ui.Modal, {
|
|
4119
|
-
open: true,
|
|
4120
|
-
onClose: () => { if (busy !== true) onClose() },
|
|
4121
|
-
closeLabel: t('close'),
|
|
4122
|
-
title: t('browse.title'),
|
|
4123
|
-
footer,
|
|
4124
|
-
},
|
|
4125
|
-
E('div', { className: 'bw-modal-body bw-browse-body' },
|
|
4126
|
-
head,
|
|
4127
|
-
createError !== '' ? E('div', { className: 'bw-error-text', role: 'alert' }, createError) : null,
|
|
4128
|
-
tools,
|
|
4129
|
-
newRow,
|
|
4130
|
-
E('div', { className: 'bw-browse-list', role: 'list' }, body),
|
|
4131
|
-
listing && listing.truncated === true ? E('div', { className: 'bw-hint' }, t('browse.truncated')) : null,
|
|
4132
|
-
error !== '' ? E('div', { className: 'bw-error-text', role: 'alert' }, error) : null,
|
|
4133
|
-
),
|
|
4134
|
-
StyleNode())
|
|
4135
|
-
}
|
|
4136
|
-
|
|
4137
|
-
/**
|
|
4138
|
-
* The add-workspace picking interaction as the sidebar hole's OCCUPANT
|
|
4139
|
-
* (v0.12.1): the official flow cannot be rendered here — a child-hole
|
|
4140
|
-
* declaration is exclusive and the shipped browser entry, though it
|
|
4141
|
-
* loses the render race, still owns 'sidebar.workspaces.directoryFlow's
|
|
4142
|
-
* declaration, so re-declaring it throws and renderSlot authorization
|
|
4143
|
-
* is runtime-enforced. This flow therefore probes the composed backend
|
|
4144
|
-
* (v0.11.3 discipline): a native Host gets its OS chooser through the
|
|
4145
|
-
* SAME official host service (uiWorkspace.pickDirectory), a browse Host
|
|
4146
|
-
* gets the in-app DirectoryBrowseDialog (the v0.11.4/0.11.5 interaction
|
|
4147
|
-
* this plugin polished). A picked path is adopted by the owner with
|
|
4148
|
-
* createWorkspace + startSession — no parent-group popup (groups derive
|
|
4149
|
-
* from names; rename or drag to group). The conversation hero flow stays
|
|
4150
|
-
* purely official: this plugin does NOT occupy that hole.
|
|
4151
|
-
*/
|
|
4152
|
-
function BetterFlow(props) {
|
|
4153
|
-
const { open, busy, onPicked, onCancel, onError, pickDirectory, listDirectory, createDirectory, t } = props
|
|
4154
|
-
const [phase, setPhase] = React.useState('idle') // idle | picking | browsing
|
|
4155
|
-
// All hooks run before any early return: the flow unmounts while
|
|
4156
|
-
// closed, but its hook sequence must stay stable.
|
|
4157
|
-
React.useEffect(() => {
|
|
4158
|
-
if (!open) {
|
|
4159
|
-
setPhase('idle')
|
|
4160
|
-
return
|
|
4161
|
-
}
|
|
4162
|
-
let alive = true
|
|
4163
|
-
setPhase('picking')
|
|
4164
|
-
// Which backend this Host composes decides the interaction: browse
|
|
4165
|
-
// renders the in-app browser directly (its pick is refused by
|
|
4166
|
-
// design, so trying the chooser first would only surface an error);
|
|
4167
|
-
// native opens the OS chooser; an unresolved verdict still tries the
|
|
4168
|
-
// chooser so a silent probe never changes the fallback.
|
|
4169
|
-
pickerCapabilityNow()
|
|
4170
|
-
.then((kind) => {
|
|
4171
|
-
if (!alive) return undefined
|
|
4172
|
-
if (kind === 'browse') { setPhase('browsing'); return undefined }
|
|
4173
|
-
return Promise.resolve()
|
|
4174
|
-
.then(() => pickDirectory())
|
|
4175
|
-
.then((path) => {
|
|
4176
|
-
if (!alive) return
|
|
4177
|
-
if (!path) { onCancel(); return }
|
|
4178
|
-
onPicked(String(path))
|
|
4179
|
-
})
|
|
4180
|
-
})
|
|
4181
|
-
.catch((reason) => {
|
|
4182
|
-
if (!alive) return
|
|
4183
|
-
// A refusal is a backend fact, not a failure: this Host serves
|
|
4184
|
-
// the browse verbs, so switch interactions instead of erroring.
|
|
4185
|
-
if (pickerRefusal(reason)) { markPickerBrowse(); setPhase('browsing'); return }
|
|
4186
|
-
setPhase('idle')
|
|
4187
|
-
onError(messageOf(reason))
|
|
4188
|
-
})
|
|
4189
|
-
return () => { alive = false }
|
|
4190
|
-
}, [open])
|
|
4191
3525
|
|
|
4192
|
-
if (!open) return null
|
|
4193
|
-
if (phase === 'browsing') {
|
|
4194
|
-
return E(DirectoryBrowseDialog, {
|
|
4195
|
-
open: true,
|
|
4196
|
-
busy: busy === true,
|
|
4197
|
-
listDirectory,
|
|
4198
|
-
createDirectory,
|
|
4199
|
-
onPick: (path) => onPicked(String(path)),
|
|
4200
|
-
onClose: onCancel,
|
|
4201
|
-
t,
|
|
4202
|
-
})
|
|
4203
|
-
}
|
|
4204
|
-
return null
|
|
4205
|
-
}
|
|
4206
3526
|
|
|
4207
3527
|
|
|
4208
3528
|
/* ============================== rows ============================== */
|
|
@@ -5139,15 +4459,12 @@ window.__ModuleLoader__.load({
|
|
|
5139
4459
|
|
|
5140
4460
|
const [query, setQuery] = React.useState('')
|
|
5141
4461
|
const [searchOpen, setSearchOpen] = React.useState(false)
|
|
5142
|
-
const [flowOpen, setFlowOpen] = React.useState(false)
|
|
5143
4462
|
const [flowBusy, setFlowBusy] = React.useState(false) // createWorkspace in flight after a pick
|
|
5144
|
-
// Occupancy of this surface's directory-flow hole
|
|
5145
|
-
//
|
|
5146
|
-
//
|
|
4463
|
+
// Occupancy of this surface's directory-flow hole. Since v0.16.0 the
|
|
4464
|
+
// occupant is the official backend's client half (native: a renderless
|
|
4465
|
+
// OS-chooser driver; browse: the shipped in-app browser), so this only
|
|
4466
|
+
// gates the add affordance: an uncomposed host has no way to pick at all.
|
|
5147
4467
|
const flowAvailable = typeof useDirectoryFlow === 'function' ? useDirectoryFlow(occupied => occupied) : false
|
|
5148
|
-
React.useEffect(() => {
|
|
5149
|
-
if (flowOpen && !flowAvailable) setFlowOpen(false)
|
|
5150
|
-
}, [flowOpen, flowAvailable])
|
|
5151
4468
|
const [dialog, setDialog] = React.useState(null) // { kind, ... }
|
|
5152
4469
|
const [ctx, setCtx] = React.useState(null) // context menu { kind, payload, x, y }
|
|
5153
4470
|
const [customize, setCustomize] = React.useState(null) // { kind, entryKey, name }
|
|
@@ -6249,7 +5566,7 @@ window.__ModuleLoader__.load({
|
|
|
6249
5566
|
return E('div', { className: 'bw-rail' },
|
|
6250
5567
|
StyleNode(),
|
|
6251
5568
|
E('button', { type: 'button', className: 'bw-rail-btn', 'aria-label': t('rail.search'), onClick: () => { expandSidebar(); setSearchOpen(true) } }, icon('IconSearchOutline16', 18)),
|
|
6252
|
-
flowAvailable ? E('button', { type: 'button', className: 'bw-rail-btn', 'aria-label': t('rail.add'), onClick: () => { expandSidebar();
|
|
5569
|
+
flowAvailable ? E('button', { type: 'button', className: 'bw-rail-btn', 'aria-label': t('rail.add'), onClick: () => { expandSidebar(); startAddFlow() }, disabled: flowBusy }, icon('IconProjectAddOutline16', 18)) : null,
|
|
6253
5570
|
)
|
|
6254
5571
|
}
|
|
6255
5572
|
|
|
@@ -6314,6 +5631,20 @@ window.__ModuleLoader__.load({
|
|
|
6314
5631
|
onClose: () => setDialog(null),
|
|
6315
5632
|
t,
|
|
6316
5633
|
})
|
|
5634
|
+
if (dialog.kind === 'add-guide') return E(ui.Modal, {
|
|
5635
|
+
key: 'add-guide',
|
|
5636
|
+
open: true,
|
|
5637
|
+
onClose: () => setDialog(null),
|
|
5638
|
+
closeLabel: t('close'),
|
|
5639
|
+
title: t('add.guide.title'),
|
|
5640
|
+
footer: E('div', { className: 'bw-modal-actions' },
|
|
5641
|
+
E(BTN, { variant: 'primary', onClick: () => setDialog(null) }, t('confirm')),
|
|
5642
|
+
),
|
|
5643
|
+
},
|
|
5644
|
+
E('div', { className: 'bw-modal-body' },
|
|
5645
|
+
E('div', { className: 'bw-hint' }, t('add.guide')),
|
|
5646
|
+
),
|
|
5647
|
+
)
|
|
6317
5648
|
return null
|
|
6318
5649
|
})()
|
|
6319
5650
|
|
|
@@ -6321,25 +5652,37 @@ window.__ModuleLoader__.load({
|
|
|
6321
5652
|
// (busy) until the Host answers, then startSession — exactly what the
|
|
6322
5653
|
// shipped browser does after the same pick. Groups never ride this
|
|
6323
5654
|
// flow (v0.12.0+): rename or drag to group instead.
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
5655
|
+
// Adopt keeps the busy flag up until the Host answers, then startSession —
|
|
5656
|
+
// exactly what the shipped browser does after the same pick. Groups never
|
|
5657
|
+
// ride this flow (v0.12.0+): rename or drag to group instead.
|
|
5658
|
+
const adoptDirectory = (path) => {
|
|
5659
|
+
setFlowBusy(true)
|
|
5660
|
+
return Promise.resolve()
|
|
5661
|
+
.then(() => createWorkspace({ path: String(path) }))
|
|
5662
|
+
.then((workspace) => {
|
|
5663
|
+
if (workspace && workspace.workspaceId && typeof startSession === 'function') startSession(workspace.workspaceId)
|
|
5664
|
+
})
|
|
5665
|
+
.catch((reason) => { fail(messageOf(reason)) })
|
|
5666
|
+
.then(() => setFlowBusy(false))
|
|
5667
|
+
}
|
|
5668
|
+
|
|
5669
|
+
// v0.16.0: the sidebar add affordance rides the OFFICIAL chooser only.
|
|
5670
|
+
// A `browse` host refuses that pick by design, so the click explains where
|
|
5671
|
+
// the official in-app flow lives instead of opening a plugin-built copy —
|
|
5672
|
+
// the shipped dialog cannot be rendered from here (see the capability block).
|
|
5673
|
+
const startAddFlow = () => {
|
|
5674
|
+
Promise.resolve(pickerCapabilityNow())
|
|
5675
|
+
.then((kind) => {
|
|
5676
|
+
if (kind === 'browse') { setDialog({ kind: 'add-guide' }); return undefined }
|
|
5677
|
+
return Promise.resolve()
|
|
5678
|
+
.then(() => pickDirectory())
|
|
5679
|
+
.then((path) => (path ? adoptDirectory(String(path)) : undefined))
|
|
5680
|
+
.catch((reason) => {
|
|
5681
|
+
if (pickerRefusal(reason)) { setDialog({ kind: 'add-guide' }); return }
|
|
5682
|
+
fail(messageOf(reason))
|
|
5683
|
+
})
|
|
5684
|
+
})
|
|
5685
|
+
.catch((reason) => { fail(messageOf(reason)) })
|
|
6343
5686
|
}
|
|
6344
5687
|
|
|
6345
5688
|
return E('div', { className: 'bw-root' },
|
|
@@ -6376,22 +5719,9 @@ window.__ModuleLoader__.load({
|
|
|
6376
5719
|
t,
|
|
6377
5720
|
}),
|
|
6378
5721
|
E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('search.placeholder'), onClick: () => setSearchOpen(v => !v) }, icon('IconSearchOutline16')),
|
|
6379
|
-
flowAvailable ? E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('add'), onClick:
|
|
5722
|
+
flowAvailable ? E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('add'), onClick: startAddFlow, disabled: flowBusy }, icon('IconProjectAddOutline16')) : null,
|
|
6380
5723
|
),
|
|
6381
5724
|
E('div', { ref: treeRef, className: 'bw-tree', role: 'tree', 'aria-label': t('title') }, bodyRows),
|
|
6382
|
-
// Our own occupant serves the sidebar hole (direct composition, the
|
|
6383
|
-
// same props conversation the official owner share defines).
|
|
6384
|
-
E(BetterFlow, {
|
|
6385
|
-
open: flowOpen,
|
|
6386
|
-
busy: flowBusy,
|
|
6387
|
-
onPicked: flowOwner.onPicked,
|
|
6388
|
-
onCancel: flowOwner.onCancel,
|
|
6389
|
-
onError: flowOwner.onError,
|
|
6390
|
-
pickDirectory,
|
|
6391
|
-
listDirectory,
|
|
6392
|
-
createDirectory,
|
|
6393
|
-
t,
|
|
6394
|
-
}),
|
|
6395
5725
|
dialogElement,
|
|
6396
5726
|
(ctx !== null && typeof ui.Menu === 'function') ? E(ui.Menu, {
|
|
6397
5727
|
// Right-click opens the SAME menu surface the official ⋯ button
|
|
@@ -6580,15 +5910,6 @@ window.__ModuleLoader__.load({
|
|
|
6580
5910
|
},
|
|
6581
5911
|
})
|
|
6582
5912
|
|
|
6583
|
-
// Occupant inject face for the sidebar hole (v0.12.1): the picking
|
|
6584
|
-
// primitives BetterFlow needs. The conversation hero hole is
|
|
6585
|
-
// deliberately NOT occupied — that flow stays purely official.
|
|
6586
|
-
const flowInjected = () => ({
|
|
6587
|
-
pickDirectory: () => uiWorkspace.pickDirectory(),
|
|
6588
|
-
listDirectory: (path, signal) => uiWorkspace.listDirectory(path, signal),
|
|
6589
|
-
createDirectory: (path, name) => uiWorkspace.createDirectory(path, name),
|
|
6590
|
-
hooks: { directoryFlow: flowSource(slots, 'sidebar.workspaces.directoryFlow') },
|
|
6591
|
-
})
|
|
6592
5913
|
|
|
6593
5914
|
// Registration helper: a thrown register (semantics drift, vanishing
|
|
6594
5915
|
// hole declaration mid-transition) degrades this one seat, never the
|
|
@@ -6634,15 +5955,6 @@ window.__ModuleLoader__.load({
|
|
|
6634
5955
|
prefsScopeRef = null
|
|
6635
5956
|
}
|
|
6636
5957
|
|
|
6637
|
-
// The sidebar hole occupation itself (v0.12.1): priority -1 shadows
|
|
6638
|
-
// the official composed picker inside the hole — the hero hole keeps
|
|
6639
|
-
// the official occupant, so the conversation flow stays stock.
|
|
6640
|
-
slots.inject('sidebar.workspaces.directoryFlow', guarded(
|
|
6641
|
-
'sidebar.workspaces.directoryFlow',
|
|
6642
|
-
{ name: 'sidebar.workspaces.directoryFlow', inject: flowInjected, locale: NS, priority: -1 },
|
|
6643
|
-
BetterFlow,
|
|
6644
|
-
))
|
|
6645
|
-
|
|
6646
5958
|
// Settings → Plugins card only (the tab dispatches the intersection of
|
|
6647
5959
|
// served namespaces — registered host-side — and settings.plugin.item
|
|
6648
5960
|
// cards). The old left-nav settings.section entry was removed: the
|