dsh-better-workspace 0.16.0 → 0.17.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 +643 -27
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.17.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.17.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
|
@@ -142,6 +142,21 @@ window.__ModuleLoader__.load({
|
|
|
142
142
|
'group.move.title': '移动到分组',
|
|
143
143
|
'group.pick': '选择已有分组',
|
|
144
144
|
'group.new': '新建分组',
|
|
145
|
+
'group.new.title': "新建分组",
|
|
146
|
+
'group.new.sess': "会话分组",
|
|
147
|
+
'group.new.ws': "工作区分组",
|
|
148
|
+
'group.new.both': "新建会话或工作区分组",
|
|
149
|
+
'group.new.where': "选择创建位置",
|
|
150
|
+
'group.new.placeholder': "分组名称,可用 / 嵌套",
|
|
151
|
+
'group.new.nameHint': "名称中的 / 即嵌套层级,例如 前端/组件",
|
|
152
|
+
'group.new.empty': "请输入分组名称",
|
|
153
|
+
'group.new.root': "顶层",
|
|
154
|
+
'group.new.pickWorkspace': "选择一个工作区",
|
|
155
|
+
'group.new.noWorkspace': "还没有工作区,请先添加一个",
|
|
156
|
+
'group.new.pickFirst': "请先选择一个工作区",
|
|
157
|
+
'group.delete.title': "删除分组",
|
|
158
|
+
'group.delete.body': "「{name}」是空分组,删除后不可恢复。",
|
|
159
|
+
'menu.deleteGroup': "删除分组",
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
const en = {
|
|
@@ -255,6 +270,21 @@ window.__ModuleLoader__.load({
|
|
|
255
270
|
'group.move.title': 'Move to group',
|
|
256
271
|
'group.pick': 'Pick an existing group',
|
|
257
272
|
'group.new': 'New group',
|
|
273
|
+
'group.new.title': "New group",
|
|
274
|
+
'group.new.sess': "Session group",
|
|
275
|
+
'group.new.ws': "Workspace group",
|
|
276
|
+
'group.new.both': "New session or workspace group",
|
|
277
|
+
'group.new.where': "Choose where to create it",
|
|
278
|
+
'group.new.placeholder': "Group name, \"/\" nests",
|
|
279
|
+
'group.new.nameHint': "A \"/\" nests further, e.g. frontend/components",
|
|
280
|
+
'group.new.empty': "Enter a group name",
|
|
281
|
+
'group.new.root': "Top level",
|
|
282
|
+
'group.new.pickWorkspace': "Pick a workspace",
|
|
283
|
+
'group.new.noWorkspace': "No workspaces yet — add one first",
|
|
284
|
+
'group.new.pickFirst': "Pick a workspace first",
|
|
285
|
+
'group.delete.title': "Delete group",
|
|
286
|
+
'group.delete.body': "\"{name}\" is an empty group; deleting it cannot be undone.",
|
|
287
|
+
'menu.deleteGroup': "Delete group",
|
|
258
288
|
}
|
|
259
289
|
|
|
260
290
|
/* Third-language dictionaries, keyed by BCP 47 tag. Every entry must carry
|
|
@@ -376,6 +406,21 @@ window.__ModuleLoader__.load({
|
|
|
376
406
|
'group.move.title': 'نقل إلى مجموعة',
|
|
377
407
|
'group.pick': 'اختر مجموعة موجودة',
|
|
378
408
|
'group.new': 'مجموعة جديدة',
|
|
409
|
+
'group.new.title': "مجموعة جديدة",
|
|
410
|
+
'group.new.sess': "مجموعة جلسات",
|
|
411
|
+
'group.new.ws': "مجموعة مساحات عمل",
|
|
412
|
+
'group.new.both': "مجموعة جلسات أو مساحات عمل جديدة",
|
|
413
|
+
'group.new.where': "اختر مكان الإنشاء",
|
|
414
|
+
'group.new.placeholder': "اسم المجموعة، \"/\" للتداخل",
|
|
415
|
+
'group.new.nameHint': "الشرطة المائلة \"/\" تنشئ مستوى أعمق، مثل frontend/components",
|
|
416
|
+
'group.new.empty': "أدخل اسم المجموعة",
|
|
417
|
+
'group.new.root': "المستوى الأعلى",
|
|
418
|
+
'group.new.pickWorkspace': "اختر مساحة عمل",
|
|
419
|
+
'group.new.noWorkspace': "لا توجد مساحات عمل بعد — أضف واحدة أولاً",
|
|
420
|
+
'group.new.pickFirst': "اختر مساحة عمل أولاً",
|
|
421
|
+
'group.delete.title': "حذف المجموعة",
|
|
422
|
+
'group.delete.body': "\"{name}\" مجموعة فارغة؛ لا يمكن التراجع عن الحذف.",
|
|
423
|
+
'menu.deleteGroup': "حذف المجموعة",
|
|
379
424
|
},
|
|
380
425
|
/* locale: de */
|
|
381
426
|
'de': {
|
|
@@ -489,6 +534,21 @@ window.__ModuleLoader__.load({
|
|
|
489
534
|
'group.move.title': 'In Gruppe verschieben',
|
|
490
535
|
'group.pick': 'Vorhandene Gruppe wählen',
|
|
491
536
|
'group.new': 'Neue Gruppe',
|
|
537
|
+
'group.new.title': "Neue Gruppe",
|
|
538
|
+
'group.new.sess': "Sitzungsgruppe",
|
|
539
|
+
'group.new.ws': "Arbeitsbereichsgruppe",
|
|
540
|
+
'group.new.both': "Neue Sitzungs- oder Arbeitsbereichsgruppe",
|
|
541
|
+
'group.new.where': "Speicherort wählen",
|
|
542
|
+
'group.new.placeholder': "Gruppenname, „/\" verschachtelt",
|
|
543
|
+
'group.new.nameHint': "Ein „/\" verschachtelt weiter, z. B. frontend/components",
|
|
544
|
+
'group.new.empty': "Gruppennamen eingeben",
|
|
545
|
+
'group.new.root': "Oberste Ebene",
|
|
546
|
+
'group.new.pickWorkspace': "Arbeitsbereich wählen",
|
|
547
|
+
'group.new.noWorkspace': "Noch keine Arbeitsbereiche — zuerst einen hinzufügen",
|
|
548
|
+
'group.new.pickFirst': "Zuerst einen Arbeitsbereich wählen",
|
|
549
|
+
'group.delete.title': "Gruppe löschen",
|
|
550
|
+
'group.delete.body': "„{name}\" ist eine leere Gruppe; das Löschen kann nicht rückgängig gemacht werden.",
|
|
551
|
+
'menu.deleteGroup': "Gruppe löschen",
|
|
492
552
|
},
|
|
493
553
|
/* locale: fr */
|
|
494
554
|
'fr': {
|
|
@@ -602,6 +662,21 @@ window.__ModuleLoader__.load({
|
|
|
602
662
|
'group.move.title': 'Déplacer vers le groupe',
|
|
603
663
|
'group.pick': 'Choisir un groupe existant',
|
|
604
664
|
'group.new': 'Nouveau groupe',
|
|
665
|
+
'group.new.title': "Nouveau groupe",
|
|
666
|
+
'group.new.sess': "Groupe de sessions",
|
|
667
|
+
'group.new.ws': "Groupe d’espaces de travail",
|
|
668
|
+
'group.new.both': "Nouveau groupe de sessions ou d’espaces de travail",
|
|
669
|
+
'group.new.where': "Choisir l’emplacement",
|
|
670
|
+
'group.new.placeholder': "Nom du groupe, « / » imbrique",
|
|
671
|
+
'group.new.nameHint': "Un « / » imbrique davantage, par ex. frontend/components",
|
|
672
|
+
'group.new.empty': "Saisissez un nom de groupe",
|
|
673
|
+
'group.new.root': "Niveau supérieur",
|
|
674
|
+
'group.new.pickWorkspace': "Choisissez un espace de travail",
|
|
675
|
+
'group.new.noWorkspace': "Aucun espace de travail — ajoutez-en un d’abord",
|
|
676
|
+
'group.new.pickFirst': "Choisissez d’abord un espace de travail",
|
|
677
|
+
'group.delete.title': "Supprimer le groupe",
|
|
678
|
+
'group.delete.body': "« {name} » est un groupe vide ; la suppression est irréversible.",
|
|
679
|
+
'menu.deleteGroup': "Supprimer le groupe",
|
|
605
680
|
},
|
|
606
681
|
/* locale: hi */
|
|
607
682
|
'hi': {
|
|
@@ -715,6 +790,21 @@ window.__ModuleLoader__.load({
|
|
|
715
790
|
'group.move.title': 'समूह में ले जाएँ',
|
|
716
791
|
'group.pick': 'मौजूदा समूह चुनें',
|
|
717
792
|
'group.new': 'नया समूह',
|
|
793
|
+
'group.new.title': "नया समूह",
|
|
794
|
+
'group.new.sess': "सत्र समूह",
|
|
795
|
+
'group.new.ws': "कार्यस्थान समूह",
|
|
796
|
+
'group.new.both': "नया सत्र या कार्यस्थान समूह",
|
|
797
|
+
'group.new.where': "बनाने का स्थान चुनें",
|
|
798
|
+
'group.new.placeholder': "समूह का नाम, \"/\" से नेस्टिंग",
|
|
799
|
+
'group.new.nameHint': "\"/\" और गहरा स्तर बनाता है, जैसे frontend/components",
|
|
800
|
+
'group.new.empty': "समूह का नाम दर्ज करें",
|
|
801
|
+
'group.new.root': "शीर्ष स्तर",
|
|
802
|
+
'group.new.pickWorkspace': "एक कार्यस्थान चुनें",
|
|
803
|
+
'group.new.noWorkspace': "अभी कोई कार्यस्थान नहीं — पहले एक जोड़ें",
|
|
804
|
+
'group.new.pickFirst': "पहले एक कार्यस्थान चुनें",
|
|
805
|
+
'group.delete.title': "समूह हटाएँ",
|
|
806
|
+
'group.delete.body': "\"{name}\" एक खाली समूह है; हटाने पर इसे वापस नहीं लाया जा सकता।",
|
|
807
|
+
'menu.deleteGroup': "समूह हटाएँ",
|
|
718
808
|
},
|
|
719
809
|
/* locale: id */
|
|
720
810
|
'id': {
|
|
@@ -828,6 +918,21 @@ window.__ModuleLoader__.load({
|
|
|
828
918
|
'group.move.title': 'Pindahkan ke grup',
|
|
829
919
|
'group.pick': 'Pilih grup yang ada',
|
|
830
920
|
'group.new': 'Grup baru',
|
|
921
|
+
'group.new.title': "Grup baru",
|
|
922
|
+
'group.new.sess': "Grup sesi",
|
|
923
|
+
'group.new.ws': "Grup ruang kerja",
|
|
924
|
+
'group.new.both': "Grup sesi atau ruang kerja baru",
|
|
925
|
+
'group.new.where': "Pilih lokasi pembuatan",
|
|
926
|
+
'group.new.placeholder': "Nama grup, \"/\" untuk bersarang",
|
|
927
|
+
'group.new.nameHint': "\"/\" membuat tingkat lebih dalam, mis. frontend/components",
|
|
928
|
+
'group.new.empty': "Masukkan nama grup",
|
|
929
|
+
'group.new.root': "Tingkat teratas",
|
|
930
|
+
'group.new.pickWorkspace': "Pilih ruang kerja",
|
|
931
|
+
'group.new.noWorkspace': "Belum ada ruang kerja — tambahkan dulu",
|
|
932
|
+
'group.new.pickFirst': "Pilih ruang kerja dulu",
|
|
933
|
+
'group.delete.title': "Hapus grup",
|
|
934
|
+
'group.delete.body': "\"{name}\" adalah grup kosong; penghapusan tidak bisa dibatalkan.",
|
|
935
|
+
'menu.deleteGroup': "Hapus grup",
|
|
831
936
|
},
|
|
832
937
|
/* locale: it */
|
|
833
938
|
'it': {
|
|
@@ -941,6 +1046,21 @@ window.__ModuleLoader__.load({
|
|
|
941
1046
|
'group.move.title': 'Sposta nel gruppo',
|
|
942
1047
|
'group.pick': 'Scegli un gruppo esistente',
|
|
943
1048
|
'group.new': 'Nuovo gruppo',
|
|
1049
|
+
'group.new.title': "Nuovo gruppo",
|
|
1050
|
+
'group.new.sess': "Gruppo di sessioni",
|
|
1051
|
+
'group.new.ws': "Gruppo di aree di lavoro",
|
|
1052
|
+
'group.new.both': "Nuovo gruppo di sessioni o aree di lavoro",
|
|
1053
|
+
'group.new.where': "Scegli dove crearlo",
|
|
1054
|
+
'group.new.placeholder': "Nome del gruppo, «/» annida",
|
|
1055
|
+
'group.new.nameHint': "Una «/» annida ulteriormente, es. frontend/components",
|
|
1056
|
+
'group.new.empty': "Inserisci un nome di gruppo",
|
|
1057
|
+
'group.new.root': "Livello superiore",
|
|
1058
|
+
'group.new.pickWorkspace': "Scegli un’area di lavoro",
|
|
1059
|
+
'group.new.noWorkspace': "Nessuna area di lavoro — aggiungine una prima",
|
|
1060
|
+
'group.new.pickFirst': "Scegli prima un’area di lavoro",
|
|
1061
|
+
'group.delete.title': "Elimina gruppo",
|
|
1062
|
+
'group.delete.body': "«{name}» è un gruppo vuoto; l’eliminazione non è reversibile.",
|
|
1063
|
+
'menu.deleteGroup': "Elimina gruppo",
|
|
944
1064
|
},
|
|
945
1065
|
/* locale: ja */
|
|
946
1066
|
'ja': {
|
|
@@ -1054,6 +1174,21 @@ window.__ModuleLoader__.load({
|
|
|
1054
1174
|
'group.move.title': 'グループに移動',
|
|
1055
1175
|
'group.pick': '既存のグループを選択',
|
|
1056
1176
|
'group.new': '新しいグループ',
|
|
1177
|
+
'group.new.title': "新しいグループ",
|
|
1178
|
+
'group.new.sess': "セッショングループ",
|
|
1179
|
+
'group.new.ws': "ワークスペースグループ",
|
|
1180
|
+
'group.new.both': "新しいセッション/ワークスペースグループ",
|
|
1181
|
+
'group.new.where': "作成先を選択",
|
|
1182
|
+
'group.new.placeholder': "グループ名、/ で入れ子",
|
|
1183
|
+
'group.new.nameHint': "/ でさらに深い階層になります(例: frontend/components)",
|
|
1184
|
+
'group.new.empty': "グループ名を入力してください",
|
|
1185
|
+
'group.new.root': "最上位",
|
|
1186
|
+
'group.new.pickWorkspace': "ワークスペースを選択",
|
|
1187
|
+
'group.new.noWorkspace': "ワークスペースがありません。先に追加してください",
|
|
1188
|
+
'group.new.pickFirst': "先にワークスペースを選択してください",
|
|
1189
|
+
'group.delete.title': "グループを削除",
|
|
1190
|
+
'group.delete.body': "「{name}」は空のグループです。削除すると元に戻せません。",
|
|
1191
|
+
'menu.deleteGroup': "グループを削除",
|
|
1057
1192
|
},
|
|
1058
1193
|
/* locale: ko */
|
|
1059
1194
|
'ko': {
|
|
@@ -1167,6 +1302,21 @@ window.__ModuleLoader__.load({
|
|
|
1167
1302
|
'group.move.title': '그룹으로 이동',
|
|
1168
1303
|
'group.pick': '기존 그룹 선택',
|
|
1169
1304
|
'group.new': '새 그룹',
|
|
1305
|
+
'group.new.title': "새 그룹",
|
|
1306
|
+
'group.new.sess': "세션 그룹",
|
|
1307
|
+
'group.new.ws': "워크스페이스 그룹",
|
|
1308
|
+
'group.new.both': "새 세션 또는 워크스페이스 그룹",
|
|
1309
|
+
'group.new.where': "만들 위치 선택",
|
|
1310
|
+
'group.new.placeholder': "그룹 이름, \"/\"로 중첩",
|
|
1311
|
+
'group.new.nameHint': "\"/\"로 더 깊은 단계, 예: frontend/components",
|
|
1312
|
+
'group.new.empty': "그룹 이름을 입력하세요",
|
|
1313
|
+
'group.new.root': "최상위",
|
|
1314
|
+
'group.new.pickWorkspace': "워크스페이스 선택",
|
|
1315
|
+
'group.new.noWorkspace': "워크스페이스가 없습니다. 먼저 추가하세요",
|
|
1316
|
+
'group.new.pickFirst': "먼저 워크스페이스를 선택하세요",
|
|
1317
|
+
'group.delete.title': "그룹 삭제",
|
|
1318
|
+
'group.delete.body': "\"{name}\"은(는) 빈 그룹입니다. 삭제하면 되돌릴 수 없습니다.",
|
|
1319
|
+
'menu.deleteGroup': "그룹 삭제",
|
|
1170
1320
|
},
|
|
1171
1321
|
/* locale: nl */
|
|
1172
1322
|
'nl': {
|
|
@@ -1280,6 +1430,21 @@ window.__ModuleLoader__.load({
|
|
|
1280
1430
|
'group.move.title': 'Naar groep verplaatsen',
|
|
1281
1431
|
'group.pick': 'Bestaande groep kiezen',
|
|
1282
1432
|
'group.new': 'Nieuwe groep',
|
|
1433
|
+
'group.new.title': "Nieuwe groep",
|
|
1434
|
+
'group.new.sess': "Sessiegroep",
|
|
1435
|
+
'group.new.ws': "Werkruimtegroep",
|
|
1436
|
+
'group.new.both': "Nieuwe sessie- of werkruimtegroep",
|
|
1437
|
+
'group.new.where': "Kies waar je maakt",
|
|
1438
|
+
'group.new.placeholder': "Groepsnaam, \"/\" nest",
|
|
1439
|
+
'group.new.nameHint': "Een \"/\" nest dieper, bijv. frontend/components",
|
|
1440
|
+
'group.new.empty': "Voer een groepsnaam in",
|
|
1441
|
+
'group.new.root': "Bovenste niveau",
|
|
1442
|
+
'group.new.pickWorkspace': "Kies een werkruimte",
|
|
1443
|
+
'group.new.noWorkspace': "Nog geen werkruimten — voeg er eerst een toe",
|
|
1444
|
+
'group.new.pickFirst': "Kies eerst een werkruimte",
|
|
1445
|
+
'group.delete.title': "Groep verwijderen",
|
|
1446
|
+
'group.delete.body': "\"{name}\" is een lege groep; verwijderen kan niet ongedaan worden gemaakt.",
|
|
1447
|
+
'menu.deleteGroup': "Groep verwijderen",
|
|
1283
1448
|
},
|
|
1284
1449
|
/* locale: pl */
|
|
1285
1450
|
'pl': {
|
|
@@ -1393,6 +1558,21 @@ window.__ModuleLoader__.load({
|
|
|
1393
1558
|
'group.move.title': 'Przenieś do grupy',
|
|
1394
1559
|
'group.pick': 'Wybierz istniejącą grupę',
|
|
1395
1560
|
'group.new': 'Nowa grupa',
|
|
1561
|
+
'group.new.title': "Nowa grupa",
|
|
1562
|
+
'group.new.sess': "Grupa sesji",
|
|
1563
|
+
'group.new.ws': "Grupa obszarów roboczych",
|
|
1564
|
+
'group.new.both': "Nowa grupa sesji lub obszarów roboczych",
|
|
1565
|
+
'group.new.where': "Wybierz miejsce utworzenia",
|
|
1566
|
+
'group.new.placeholder': "Nazwa grupy, „/” zagnieżdża",
|
|
1567
|
+
'group.new.nameHint': "„/” zagnieżdża głębiej, np. frontend/components",
|
|
1568
|
+
'group.new.empty': "Podaj nazwę grupy",
|
|
1569
|
+
'group.new.root': "Najwyższy poziom",
|
|
1570
|
+
'group.new.pickWorkspace': "Wybierz obszar roboczy",
|
|
1571
|
+
'group.new.noWorkspace': "Brak obszarów roboczych — najpierw dodaj jeden",
|
|
1572
|
+
'group.new.pickFirst': "Najpierw wybierz obszar roboczy",
|
|
1573
|
+
'group.delete.title': "Usuń grupę",
|
|
1574
|
+
'group.delete.body': "„{name}” to pusta grupa; usunięcia nie można cofnąć.",
|
|
1575
|
+
'menu.deleteGroup': "Usuń grupę",
|
|
1396
1576
|
},
|
|
1397
1577
|
/* locale: pt */
|
|
1398
1578
|
'pt': {
|
|
@@ -1506,6 +1686,21 @@ window.__ModuleLoader__.load({
|
|
|
1506
1686
|
'group.move.title': 'Mover para o grupo',
|
|
1507
1687
|
'group.pick': 'Escolher um grupo existente',
|
|
1508
1688
|
'group.new': 'Novo grupo',
|
|
1689
|
+
'group.new.title': "Novo grupo",
|
|
1690
|
+
'group.new.sess': "Grupo de sessões",
|
|
1691
|
+
'group.new.ws': "Grupo de áreas de trabalho",
|
|
1692
|
+
'group.new.both': "Novo grupo de sessões ou áreas de trabalho",
|
|
1693
|
+
'group.new.where': "Escolha onde criar",
|
|
1694
|
+
'group.new.placeholder': "Nome do grupo, \"/\" aninha",
|
|
1695
|
+
'group.new.nameHint': "Uma \"/\" aninha mais, ex. frontend/components",
|
|
1696
|
+
'group.new.empty': "Digite um nome de grupo",
|
|
1697
|
+
'group.new.root': "Nível superior",
|
|
1698
|
+
'group.new.pickWorkspace': "Escolha uma área de trabalho",
|
|
1699
|
+
'group.new.noWorkspace': "Ainda não há áreas de trabalho — adicione uma primeiro",
|
|
1700
|
+
'group.new.pickFirst': "Escolha uma área de trabalho primeiro",
|
|
1701
|
+
'group.delete.title': "Excluir grupo",
|
|
1702
|
+
'group.delete.body': "\"{name}\" é um grupo vazio; a exclusão não pode ser desfeita.",
|
|
1703
|
+
'menu.deleteGroup': "Excluir grupo",
|
|
1509
1704
|
},
|
|
1510
1705
|
/* locale: ru */
|
|
1511
1706
|
'ru': {
|
|
@@ -1619,6 +1814,21 @@ window.__ModuleLoader__.load({
|
|
|
1619
1814
|
'group.move.title': 'Переместить в группу',
|
|
1620
1815
|
'group.pick': 'Выбрать существующую группу',
|
|
1621
1816
|
'group.new': 'Новая группа',
|
|
1817
|
+
'group.new.title': "Новая группа",
|
|
1818
|
+
'group.new.sess': "Группа сеансов",
|
|
1819
|
+
'group.new.ws': "Группа рабочих областей",
|
|
1820
|
+
'group.new.both': "Новая группа сеансов или рабочих областей",
|
|
1821
|
+
'group.new.where': "Выберите место создания",
|
|
1822
|
+
'group.new.placeholder': "Имя группы, «/» вкладывает",
|
|
1823
|
+
'group.new.nameHint': "«/» вкладывает глубже, напр. frontend/components",
|
|
1824
|
+
'group.new.empty': "Введите имя группы",
|
|
1825
|
+
'group.new.root': "Верхний уровень",
|
|
1826
|
+
'group.new.pickWorkspace': "Выберите рабочую область",
|
|
1827
|
+
'group.new.noWorkspace': "Рабочих областей пока нет — сначала добавьте одну",
|
|
1828
|
+
'group.new.pickFirst': "Сначала выберите рабочую область",
|
|
1829
|
+
'group.delete.title': "Удалить группу",
|
|
1830
|
+
'group.delete.body': "«{name}» — пустая группа; удаление необратимо.",
|
|
1831
|
+
'menu.deleteGroup': "Удалить группу",
|
|
1622
1832
|
},
|
|
1623
1833
|
/* locale: sv */
|
|
1624
1834
|
'sv': {
|
|
@@ -1732,6 +1942,21 @@ window.__ModuleLoader__.load({
|
|
|
1732
1942
|
'group.move.title': 'Flytta till grupp',
|
|
1733
1943
|
'group.pick': 'Välj en befintlig grupp',
|
|
1734
1944
|
'group.new': 'Ny grupp',
|
|
1945
|
+
'group.new.title': "Ny grupp",
|
|
1946
|
+
'group.new.sess': "Sessionsgrupp",
|
|
1947
|
+
'group.new.ws': "Arbetsytegrupp",
|
|
1948
|
+
'group.new.both': "Ny sessions- eller arbetsytegrupp",
|
|
1949
|
+
'group.new.where': "Välj var den skapas",
|
|
1950
|
+
'group.new.placeholder': "Gruppnamn, \"/\" nästlar",
|
|
1951
|
+
'group.new.nameHint': "Ett \"/\" nästlar djupare, t.ex. frontend/components",
|
|
1952
|
+
'group.new.empty': "Ange ett gruppnamn",
|
|
1953
|
+
'group.new.root': "Översta nivån",
|
|
1954
|
+
'group.new.pickWorkspace': "Välj en arbetsyta",
|
|
1955
|
+
'group.new.noWorkspace': "Inga arbetsytor än — lägg till en först",
|
|
1956
|
+
'group.new.pickFirst': "Välj en arbetsyta först",
|
|
1957
|
+
'group.delete.title': "Ta bort grupp",
|
|
1958
|
+
'group.delete.body': "\"{name}\" är en tom grupp; borttagningen kan inte ångras.",
|
|
1959
|
+
'menu.deleteGroup': "Ta bort grupp",
|
|
1735
1960
|
},
|
|
1736
1961
|
/* locale: th */
|
|
1737
1962
|
'th': {
|
|
@@ -1845,6 +2070,21 @@ window.__ModuleLoader__.load({
|
|
|
1845
2070
|
'group.move.title': 'ย้ายไปยังกลุ่ม',
|
|
1846
2071
|
'group.pick': 'เลือกกลุ่มที่มีอยู่',
|
|
1847
2072
|
'group.new': 'กลุ่มใหม่',
|
|
2073
|
+
'group.new.title': "กลุ่มใหม่",
|
|
2074
|
+
'group.new.sess': "กลุ่มเซสชัน",
|
|
2075
|
+
'group.new.ws': "กลุ่มเวิร์กสเปซ",
|
|
2076
|
+
'group.new.both': "กลุ่มเซสชันหรือเวิร์กสเปซใหม่",
|
|
2077
|
+
'group.new.where': "เลือกตำแหน่งที่สร้าง",
|
|
2078
|
+
'group.new.placeholder': "ชื่อกลุ่ม ใช้ / ซ้อนระดับ",
|
|
2079
|
+
'group.new.nameHint': "/ ใช้ซ้อนระดับลึกขึ้น เช่น frontend/components",
|
|
2080
|
+
'group.new.empty': "กรอกชื่อกลุ่ม",
|
|
2081
|
+
'group.new.root': "ระดับบนสุด",
|
|
2082
|
+
'group.new.pickWorkspace': "เลือกเวิร์กสเปซ",
|
|
2083
|
+
'group.new.noWorkspace': "ยังไม่มีเวิร์กสเปซ — เพิ่มก่อน",
|
|
2084
|
+
'group.new.pickFirst': "เลือกเวิร์กสเปซก่อน",
|
|
2085
|
+
'group.delete.title': "ลบกลุ่ม",
|
|
2086
|
+
'group.delete.body': "\"{name}\" เป็นกลุ่มว่าง ลบแล้วกู้คืนไม่ได้",
|
|
2087
|
+
'menu.deleteGroup': "ลบกลุ่ม",
|
|
1848
2088
|
},
|
|
1849
2089
|
/* locale: tr */
|
|
1850
2090
|
'tr': {
|
|
@@ -1958,6 +2198,21 @@ window.__ModuleLoader__.load({
|
|
|
1958
2198
|
'group.move.title': 'Gruba taşı',
|
|
1959
2199
|
'group.pick': 'Mevcut bir grup seç',
|
|
1960
2200
|
'group.new': 'Yeni grup',
|
|
2201
|
+
'group.new.title': "Yeni grup",
|
|
2202
|
+
'group.new.sess': "Oturum grubu",
|
|
2203
|
+
'group.new.ws': "Çalışma alanı grubu",
|
|
2204
|
+
'group.new.both': "Yeni oturum veya çalışma alanı grubu",
|
|
2205
|
+
'group.new.where': "Oluşturulacak yeri seçin",
|
|
2206
|
+
'group.new.placeholder': "Grup adı, \"/\" iç içe koyar",
|
|
2207
|
+
'group.new.nameHint': "\"/\" daha derin iç içe koyar, örn. frontend/components",
|
|
2208
|
+
'group.new.empty': "Bir grup adı girin",
|
|
2209
|
+
'group.new.root': "En üst düzey",
|
|
2210
|
+
'group.new.pickWorkspace': "Bir çalışma alanı seçin",
|
|
2211
|
+
'group.new.noWorkspace': "Henüz çalışma alanı yok — önce bir tane ekleyin",
|
|
2212
|
+
'group.new.pickFirst': "Önce bir çalışma alanı seçin",
|
|
2213
|
+
'group.delete.title': "Grubu sil",
|
|
2214
|
+
'group.delete.body': "\"{name}\" boş bir grup; silme geri alınamaz.",
|
|
2215
|
+
'menu.deleteGroup': "Grubu sil",
|
|
1961
2216
|
},
|
|
1962
2217
|
/* locale: vi */
|
|
1963
2218
|
'vi': {
|
|
@@ -2071,6 +2326,21 @@ window.__ModuleLoader__.load({
|
|
|
2071
2326
|
'group.move.title': 'Chuyển vào nhóm',
|
|
2072
2327
|
'group.pick': 'Chọn một nhóm có sẵn',
|
|
2073
2328
|
'group.new': 'Nhóm mới',
|
|
2329
|
+
'group.new.title': "Nhóm mới",
|
|
2330
|
+
'group.new.sess': "Nhóm phiên",
|
|
2331
|
+
'group.new.ws': "Nhóm không gian làm việc",
|
|
2332
|
+
'group.new.both': "Nhóm phiên hoặc không gian làm việc mới",
|
|
2333
|
+
'group.new.where': "Chọn nơi tạo",
|
|
2334
|
+
'group.new.placeholder': "Tên nhóm, \"/\" để lồng nhau",
|
|
2335
|
+
'group.new.nameHint': "\"/\" lồng sâu hơn, ví dụ frontend/components",
|
|
2336
|
+
'group.new.empty': "Nhập tên nhóm",
|
|
2337
|
+
'group.new.root': "Cấp cao nhất",
|
|
2338
|
+
'group.new.pickWorkspace': "Chọn một không gian làm việc",
|
|
2339
|
+
'group.new.noWorkspace': "Chưa có không gian làm việc — hãy thêm trước",
|
|
2340
|
+
'group.new.pickFirst': "Chọn không gian làm việc trước",
|
|
2341
|
+
'group.delete.title': "Xóa nhóm",
|
|
2342
|
+
'group.delete.body': "\"{name}\" là nhóm trống; không thể hoàn tác sau khi xóa.",
|
|
2343
|
+
'menu.deleteGroup': "Xóa nhóm",
|
|
2074
2344
|
},
|
|
2075
2345
|
/* locale: zh-HK */
|
|
2076
2346
|
'zh-HK': {
|
|
@@ -2184,6 +2454,21 @@ window.__ModuleLoader__.load({
|
|
|
2184
2454
|
'group.move.title': '移動到分組',
|
|
2185
2455
|
'group.pick': '選擇已有分組',
|
|
2186
2456
|
'group.new': '新增分組',
|
|
2457
|
+
'group.new.title': "新增分組",
|
|
2458
|
+
'group.new.sess': "工作階段分組",
|
|
2459
|
+
'group.new.ws': "工作區分組",
|
|
2460
|
+
'group.new.both': "新增工作階段或工作區分組",
|
|
2461
|
+
'group.new.where': "揀建立位置",
|
|
2462
|
+
'group.new.placeholder': "分組名,可用 / 嵌套",
|
|
2463
|
+
'group.new.nameHint': "名入面嘅 / 即嵌套層級,例如 前端/組件",
|
|
2464
|
+
'group.new.empty': "請輸入分組名",
|
|
2465
|
+
'group.new.root': "最頂層",
|
|
2466
|
+
'group.new.pickWorkspace': "揀一個工作區",
|
|
2467
|
+
'group.new.noWorkspace': "仲未有工作區,請先加一個",
|
|
2468
|
+
'group.new.pickFirst': "請先揀一個工作區",
|
|
2469
|
+
'group.delete.title': "刪除分組",
|
|
2470
|
+
'group.delete.body': "「{name}」係空分組,刪咗冇得返轉頭。",
|
|
2471
|
+
'menu.deleteGroup': "刪除分組",
|
|
2187
2472
|
},
|
|
2188
2473
|
/* locale: zh-MO */
|
|
2189
2474
|
'zh-MO': {
|
|
@@ -2297,6 +2582,21 @@ window.__ModuleLoader__.load({
|
|
|
2297
2582
|
'group.move.title': '移動到分組',
|
|
2298
2583
|
'group.pick': '選擇已有分組',
|
|
2299
2584
|
'group.new': '新增分組',
|
|
2585
|
+
'group.new.title': "新增分組",
|
|
2586
|
+
'group.new.sess': "工作階段分組",
|
|
2587
|
+
'group.new.ws': "工作區分組",
|
|
2588
|
+
'group.new.both': "新增工作階段或工作區分組",
|
|
2589
|
+
'group.new.where': "揀建立位置",
|
|
2590
|
+
'group.new.placeholder': "分組名,可用 / 嵌套",
|
|
2591
|
+
'group.new.nameHint': "名入面嘅 / 即嵌套層級,例如 前端/組件",
|
|
2592
|
+
'group.new.empty': "請輸入分組名",
|
|
2593
|
+
'group.new.root': "最頂層",
|
|
2594
|
+
'group.new.pickWorkspace': "揀一個工作區",
|
|
2595
|
+
'group.new.noWorkspace': "仲未有工作區,請先加一個",
|
|
2596
|
+
'group.new.pickFirst': "請先揀一個工作區",
|
|
2597
|
+
'group.delete.title': "刪除分組",
|
|
2598
|
+
'group.delete.body': "「{name}」係空分組,刪咗冇得返轉頭。",
|
|
2599
|
+
'menu.deleteGroup': "刪除分組",
|
|
2300
2600
|
},
|
|
2301
2601
|
/* locale: zh-TW */
|
|
2302
2602
|
'zh-TW': {
|
|
@@ -2410,6 +2710,21 @@ window.__ModuleLoader__.load({
|
|
|
2410
2710
|
'group.move.title': '移動到分組',
|
|
2411
2711
|
'group.pick': '選擇現有群組',
|
|
2412
2712
|
'group.new': '新增分組',
|
|
2713
|
+
'group.new.title': "新增分組",
|
|
2714
|
+
'group.new.sess': "工作階段分組",
|
|
2715
|
+
'group.new.ws': "工作區分組",
|
|
2716
|
+
'group.new.both': "新增工作階段或工作區分組",
|
|
2717
|
+
'group.new.where': "選擇建立位置",
|
|
2718
|
+
'group.new.placeholder': "分組名稱,可用 / 巢狀",
|
|
2719
|
+
'group.new.nameHint': "名稱中的 / 即巢狀層級,例如 前端/元件",
|
|
2720
|
+
'group.new.empty': "請輸入分組名稱",
|
|
2721
|
+
'group.new.root': "最頂層",
|
|
2722
|
+
'group.new.pickWorkspace': "選擇一個工作區",
|
|
2723
|
+
'group.new.noWorkspace': "還沒有工作區,請先新增一個",
|
|
2724
|
+
'group.new.pickFirst': "請先選擇一個工作區",
|
|
2725
|
+
'group.delete.title': "刪除分組",
|
|
2726
|
+
'group.delete.body': "「{name}」是空分組,刪除後無法復原。",
|
|
2727
|
+
'menu.deleteGroup': "刪除分組",
|
|
2413
2728
|
},
|
|
2414
2729
|
}
|
|
2415
2730
|
|
|
@@ -2633,7 +2948,7 @@ window.__ModuleLoader__.load({
|
|
|
2633
2948
|
* titles. Groups are virtual (projection of names). Rows keep the Host
|
|
2634
2949
|
* workspace.sessionIds (manual) order — drag-to-reorder must be visible.
|
|
2635
2950
|
*/
|
|
2636
|
-
function buildSessionTree(rows, groupBySlash) {
|
|
2951
|
+
function buildSessionTree(rows, groupBySlash, explicitSess, groupScope) {
|
|
2637
2952
|
const root = { path: '', name: '', groups: [], sessions: [] }
|
|
2638
2953
|
const byPath = new Map([['', root]])
|
|
2639
2954
|
// Slash grouping OFF (v0.13 view option): every row lands at the
|
|
@@ -2662,6 +2977,7 @@ window.__ModuleLoader__.load({
|
|
|
2662
2977
|
}
|
|
2663
2978
|
return node
|
|
2664
2979
|
}
|
|
2980
|
+
for (const segs of explicitSegsOf(explicitSess, groupScope)) ensure(segs)
|
|
2665
2981
|
for (const row of rows || []) {
|
|
2666
2982
|
const segs = splitTitleSegs(row.title)
|
|
2667
2983
|
const folderPath = segs.slice(0, -1).join('/')
|
|
@@ -2772,7 +3088,30 @@ window.__ModuleLoader__.load({
|
|
|
2772
3088
|
folderPath: '',
|
|
2773
3089
|
})
|
|
2774
3090
|
|
|
2775
|
-
|
|
3091
|
+
/**
|
|
3092
|
+
* Declared-but-empty groups, resolved per level. `explicit` is the store's
|
|
3093
|
+
* `folders.ws` bag (level idPrefix → path list); the empty path splits into
|
|
3094
|
+
* segments the same way a title prefix does, so a declared "a/b" produces
|
|
3095
|
+
* the same two-level chain a member would.
|
|
3096
|
+
*/
|
|
3097
|
+
const EMPTY_FOLDERS = Object.freeze({})
|
|
3098
|
+
/** True when this scope declares at least one group (empty groups included). */
|
|
3099
|
+
const hasDeclared = (explicit, scope) => {
|
|
3100
|
+
const raw = explicit && typeof explicit === 'object' ? explicit[String(scope)] : undefined
|
|
3101
|
+
return Array.isArray(raw) && raw.length > 0
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
const explicitSegsOf = (explicit, scope) => {
|
|
3105
|
+
const raw = explicit && typeof explicit === 'object' ? explicit[String(scope)] : undefined
|
|
3106
|
+
const out = []
|
|
3107
|
+
for (const path of Array.isArray(raw) ? raw : []) {
|
|
3108
|
+
const segs = String(path).split('/').filter((s) => s !== '')
|
|
3109
|
+
if (segs.length > 0) out.push(segs)
|
|
3110
|
+
}
|
|
3111
|
+
return out
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
function buildTree(items, groupBySlash, explicitWs) {
|
|
2776
3115
|
const list = items || []
|
|
2777
3116
|
const diskParentOf = diskParentMapOf(list)
|
|
2778
3117
|
const childrenOf = new Map()
|
|
@@ -2800,6 +3139,9 @@ window.__ModuleLoader__.load({
|
|
|
2800
3139
|
folders: [],
|
|
2801
3140
|
workspaces: [],
|
|
2802
3141
|
idPath: (idPrefix !== '' ? idPrefix + '//' : '') + key,
|
|
3142
|
+
// Declared-group scope of THIS level (v0.17.0): the exact key a
|
|
3143
|
+
// new child group must be filed under in folders.ws.
|
|
3144
|
+
scope: idPrefix,
|
|
2803
3145
|
}
|
|
2804
3146
|
byPath.set(key, next)
|
|
2805
3147
|
node.folders.push(next)
|
|
@@ -2808,11 +3150,19 @@ window.__ModuleLoader__.load({
|
|
|
2808
3150
|
}
|
|
2809
3151
|
return node
|
|
2810
3152
|
}
|
|
3153
|
+
// Declared empty groups land BEFORE the members: a declared path that
|
|
3154
|
+
// also carries members is the same node either way (ensure is
|
|
3155
|
+
// idempotent), and declaring first means an empty one still renders.
|
|
3156
|
+
if (groupBySlash !== false) {
|
|
3157
|
+
for (const segs of explicitSegsOf(explicitWs, idPrefix)) ensure(segs)
|
|
3158
|
+
}
|
|
2811
3159
|
for (const workspace of wsList) {
|
|
2812
3160
|
const segs = splitTitleSegs(workspace.title)
|
|
2813
3161
|
const folderPath = segs.slice(0, -1).join('/')
|
|
2814
3162
|
const leaf = segs.length > 0 ? segs[segs.length - 1] : (basename(workspace.path) || String(workspace.title || '') || String(workspace.workspaceId || ''))
|
|
2815
3163
|
const subList = childrenOf.get(workspace.workspaceId)
|
|
3164
|
+
// The disk level this workspace OWNS — its declared groups file here.
|
|
3165
|
+
const subPrefix = (idPrefix !== '' ? idPrefix + '//' : '') + workspace.workspaceId
|
|
2816
3166
|
const entry = {
|
|
2817
3167
|
workspaceId: workspace.workspaceId,
|
|
2818
3168
|
title: String(workspace.title || ''),
|
|
@@ -2820,7 +3170,8 @@ window.__ModuleLoader__.load({
|
|
|
2820
3170
|
sessionIds: Array.isArray(workspace.sessionIds) ? workspace.sessionIds : [],
|
|
2821
3171
|
leaf,
|
|
2822
3172
|
folderPath,
|
|
2823
|
-
|
|
3173
|
+
subPrefix,
|
|
3174
|
+
sub: subList ? buildLevel(subList, subPrefix) : null,
|
|
2824
3175
|
}
|
|
2825
3176
|
// Slash grouping OFF (v0.13 view option): skip the name layer —
|
|
2826
3177
|
// workspaces mount straight at their disk level; the DISK layer
|
|
@@ -2934,7 +3285,25 @@ window.__ModuleLoader__.load({
|
|
|
2934
3285
|
// rim + a tinted wash so "this drop joins a group" reads stronger than
|
|
2935
3286
|
// the reorder dashes above.
|
|
2936
3287
|
'.bw-drop-into-strong{outline:2px solid var(--dsw-alias-brand-primary,#5b8def);outline-offset:-2px;background:color-mix(in srgb, var(--dsw-alias-brand-primary,#5b8def) 16%, transparent)}',
|
|
2937
|
-
|
|
3288
|
+
// v0.17.0: the new-group entry is an ICON button now (same family as the
|
|
3289
|
+
// search / add buttons beside it); the wording moved into its tooltip.
|
|
3290
|
+
'.bw-newgroup-btn{flex:none}',
|
|
3291
|
+
'.bw-seg{display:flex;gap:0;width:fit-content;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));border-radius:7px;overflow:hidden}',
|
|
3292
|
+
'.bw-seg-btn{border:none;background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font:inherit;font-size:12.5px;padding:5px 14px;cursor:pointer}',
|
|
3293
|
+
'.bw-seg-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3294
|
+
'.bw-seg-btn-on{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.16));color:var(--dsw-alias-label-primary,#e6e6e6);font-weight:500}',
|
|
3295
|
+
'.bw-seg-btn+.bw-seg-btn{border-left:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25))}',
|
|
3296
|
+
'.bw-gn-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
|
|
3297
|
+
'.bw-gn-empty{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a);padding:10px 8px;text-align:center}',
|
|
3298
|
+
'.bw-gn-tree{max-height:250px;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))}',
|
|
3299
|
+
'.bw-gn-row{display:flex;align-items:center;gap:6px;min-height:26px;padding-right:8px;border-radius:6px;cursor:pointer;user-select:none}',
|
|
3300
|
+
'.bw-gn-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
|
|
3301
|
+
'.bw-gn-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)}',
|
|
3302
|
+
'.bw-gn-twist{flex:none;display:grid;place-items:center;width:14px;height:14px;padding:0;border:none;background:transparent;color:var(--dsw-alias-label-tertiary,#9a9a9a);cursor:pointer}',
|
|
3303
|
+
'.bw-gn-twist-open{transform:rotate(90deg)}',
|
|
3304
|
+
'.bw-gn-icon{flex:none;display:grid;place-items:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
3305
|
+
'.bw-gn-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3306
|
+
'.bw-gn-input{width:100%;box-sizing:border-box}',
|
|
2938
3307
|
'.bw-row:hover{background:var(--dsw-specific-sidebar-nav-item-hover,var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12)))}',
|
|
2939
3308
|
'.bw-row:hover{background:color-mix(in srgb,var(--dsw-specific-sidebar-nav-item-hover,rgba(127,127,127,.14)) 50%,transparent)}',
|
|
2940
3309
|
'.bw-row-current{background:var(--dsw-specific-sidebar-nav-item-active,rgba(91,141,239,.15))}',
|
|
@@ -3068,8 +3437,16 @@ window.__ModuleLoader__.load({
|
|
|
3068
3437
|
*/
|
|
3069
3438
|
const workspaceSlashOf = (value) => value === undefined ? true : value === true
|
|
3070
3439
|
|
|
3440
|
+
/**
|
|
3441
|
+
* Normalize a declared group path: split on "/", drop blank segments (so
|
|
3442
|
+
* " " and "a//b" cannot create nameless levels), trim each name. Returns
|
|
3443
|
+
* '' when nothing is left — the store actions treat that as "do nothing".
|
|
3444
|
+
*/
|
|
3445
|
+
const declaredPathOf = (raw) => String(raw == null ? '' : raw)
|
|
3446
|
+
.split('/').map((s) => s.trim()).filter((s) => s !== '').join('/')
|
|
3447
|
+
|
|
3071
3448
|
const createViewStore = () => storeKit.defineStore({
|
|
3072
|
-
init: () => ({ expanded: {}, sessionsExpanded: {}, sessionGroups: {}, sessionOrder: {}, prefs: { compactChains: true }, styling: {}, groupBy: 'workspace-tree', orderBy: 'manual', sessionTitleSlash: true, workspaceTitleSlash: false }),
|
|
3449
|
+
init: () => ({ expanded: {}, sessionsExpanded: {}, sessionGroups: {}, sessionOrder: {}, prefs: { compactChains: true }, styling: {}, groupBy: 'workspace-tree', orderBy: 'manual', sessionTitleSlash: true, workspaceTitleSlash: false, folders: { ws: {}, sess: {} } }),
|
|
3073
3450
|
// NOTE: hydration REPLACES the state with the persisted whole value —
|
|
3074
3451
|
// init defaults never merge. Every action must tolerate a missing key
|
|
3075
3452
|
// (states persisted by older plugin versions lack sessionGroups), and
|
|
@@ -3120,6 +3497,44 @@ window.__ModuleLoader__.load({
|
|
|
3120
3497
|
setOrderBy: (d, value) => { d.orderBy = value },
|
|
3121
3498
|
setSessionTitleSlash: (d, value) => { d.sessionTitleSlash = value !== false },
|
|
3122
3499
|
setWorkspaceTitleSlash: (d, value) => { d.workspaceTitleSlash = value !== false },
|
|
3500
|
+
// Explicit (possibly EMPTY) groups, v0.17.0. A group has always been a
|
|
3501
|
+
// projection of "/" in member titles, so a group with no members simply
|
|
3502
|
+
// did not exist and "create a group" had nothing to write. These two
|
|
3503
|
+
// actions are that missing write path: a declared path renders as a
|
|
3504
|
+
// real row even while it holds nothing, and disappears again when the
|
|
3505
|
+
// user deletes it. Scope keys follow the tree's own identities —
|
|
3506
|
+
// workspaces live in the DISK level they belong to (folders.ws keyed by
|
|
3507
|
+
// the level's idPrefix: '' for the top level, '<wsId>' one disk level
|
|
3508
|
+
// down, '<a>//<b>' deeper), sessions live in one workspace
|
|
3509
|
+
// (folders.sess keyed by workspaceId). Both bags are plain path lists.
|
|
3510
|
+
addFolder: (d, kind, scope, path) => {
|
|
3511
|
+
const clean = declaredPathOf(path)
|
|
3512
|
+
if (clean === '') return
|
|
3513
|
+
if (!d.folders || typeof d.folders !== 'object') d.folders = { ws: {}, sess: {} }
|
|
3514
|
+
const bagKey = kind === 'session' ? 'sess' : 'ws'
|
|
3515
|
+
if (!d.folders[bagKey] || typeof d.folders[bagKey] !== 'object') d.folders[bagKey] = {}
|
|
3516
|
+
const bag = d.folders[bagKey]
|
|
3517
|
+
const key = String(scope == null ? '' : scope)
|
|
3518
|
+
const cur = Array.isArray(bag[key]) ? bag[key].slice() : []
|
|
3519
|
+
if (cur.indexOf(clean) === -1) cur.push(clean)
|
|
3520
|
+
bag[key] = cur
|
|
3521
|
+
},
|
|
3522
|
+
// Deleting a declared group takes its declared DESCENDANTS with it —
|
|
3523
|
+
// "a", "a/b" and "a/b/c" are one branch, and leaving the children
|
|
3524
|
+
// behind would resurrect the parent as an implicit row.
|
|
3525
|
+
removeFolder: (d, kind, scope, path) => {
|
|
3526
|
+
const clean = declaredPathOf(path)
|
|
3527
|
+
if (clean === '') return
|
|
3528
|
+
if (!d.folders || typeof d.folders !== 'object') return
|
|
3529
|
+
const bag = d.folders[kind === 'session' ? 'sess' : 'ws']
|
|
3530
|
+
if (!bag || typeof bag !== 'object') return
|
|
3531
|
+
const key = String(scope == null ? '' : scope)
|
|
3532
|
+
const cur = Array.isArray(bag[key]) ? bag[key] : []
|
|
3533
|
+
const prefix = clean + '/'
|
|
3534
|
+
const next = cur.filter((p) => p !== clean && p.indexOf(prefix) !== 0)
|
|
3535
|
+
if (next.length === 0) delete bag[key]
|
|
3536
|
+
else bag[key] = next
|
|
3537
|
+
},
|
|
3123
3538
|
},
|
|
3124
3539
|
})
|
|
3125
3540
|
|
|
@@ -3396,6 +3811,112 @@ window.__ModuleLoader__.load({
|
|
|
3396
3811
|
* Degradation: callers keep a native control for hosts whose primitives
|
|
3397
3812
|
* have no Menu, so picking never becomes impossible.
|
|
3398
3813
|
*/
|
|
3814
|
+
|
|
3815
|
+
/**
|
|
3816
|
+
* "New group" dialog (v0.17.0). Three decisions, top to bottom:
|
|
3817
|
+
*
|
|
3818
|
+
* 1. WHICH LAYER — session groups, or workspace groups. A single segmented
|
|
3819
|
+
* pair of buttons; the workspace side only exists while "/" grouping is
|
|
3820
|
+
* on for workspaces, and a host where only one layer is live renders no
|
|
3821
|
+
* switch at all (the wording of the header button already promised it).
|
|
3822
|
+
* 2. WHERE — an expandable TREE of containers rather than a flat dropdown:
|
|
3823
|
+
* the parent is picked visually, so "under this workspace" and "inside
|
|
3824
|
+
* that group" read the same way they do in the sidebar.
|
|
3825
|
+
* 3. WHAT — the new group's name ("a/b" nests).
|
|
3826
|
+
*
|
|
3827
|
+
* Declared-empty groups did not use to exist (a group was a projection of
|
|
3828
|
+
* member titles, so a member-less one could not be represented); this dialog
|
|
3829
|
+
* writes the declaration itself, which is why it is not the move dialog.
|
|
3830
|
+
*/
|
|
3831
|
+
function GroupNewDialog(props) {
|
|
3832
|
+
const { kinds, initialKind, treeOf, onConfirm, onClose, t } = props
|
|
3833
|
+
const [kind, setKind] = React.useState(initialKind)
|
|
3834
|
+
const [picked, setPicked] = React.useState(null)
|
|
3835
|
+
const [openKeys, setOpenKeys] = React.useState({})
|
|
3836
|
+
const [name, setName] = React.useState('')
|
|
3837
|
+
const [error, setError] = React.useState('')
|
|
3838
|
+
const plan = treeOf(kind)
|
|
3839
|
+
const toggle = (id) => setOpenKeys((prev) => ({ ...prev, [id]: !prev[id] }))
|
|
3840
|
+
const pick = (node) => { setPicked(node); setError('') }
|
|
3841
|
+
const submit = () => {
|
|
3842
|
+
const clean = String(name).split('/').filter((s) => s !== '').join('/')
|
|
3843
|
+
if (clean === '') { setError(t('group.new.empty')); return }
|
|
3844
|
+
if (!picked) { setError(t('group.new.pickFirst')); return }
|
|
3845
|
+
onConfirm(kind, picked.scope, picked.path === '' ? clean : picked.path + '/' + clean)
|
|
3846
|
+
}
|
|
3847
|
+
const renderNodes = (list, depth) => {
|
|
3848
|
+
const out = []
|
|
3849
|
+
for (const node of list) {
|
|
3850
|
+
const hasKids = node.children.length > 0
|
|
3851
|
+
// The first level opens by default: the root row is a header ("pick a
|
|
3852
|
+
// workspace"), so a collapsed tree would show the user a header and
|
|
3853
|
+
// nothing to pick. Deeper levels stay closed until asked for.
|
|
3854
|
+
const expanded = openKeys[node.id] === true || (depth === 0 && openKeys[node.id] !== false)
|
|
3855
|
+
out.push(E('div', {
|
|
3856
|
+
key: 'n-' + node.id,
|
|
3857
|
+
className: cls('bw-gn-row', picked && picked.id === node.id && 'bw-gn-row-on'),
|
|
3858
|
+
style: { paddingLeft: 6 + depth * 14 },
|
|
3859
|
+
role: 'treeitem',
|
|
3860
|
+
'aria-selected': picked && picked.id === node.id ? 'true' : 'false',
|
|
3861
|
+
onClick: () => pick(node),
|
|
3862
|
+
},
|
|
3863
|
+
hasKids ? E('button', {
|
|
3864
|
+
type: 'button',
|
|
3865
|
+
className: cls('bw-gn-twist', expanded && 'bw-gn-twist-open'),
|
|
3866
|
+
tabIndex: -1,
|
|
3867
|
+
'aria-label': node.label,
|
|
3868
|
+
onClick: (e) => { e.stopPropagation(); toggle(node.id) },
|
|
3869
|
+
}, E('span', { className: 'bw-chevron' })) : E('span', { className: 'bw-gn-twist' }),
|
|
3870
|
+
E('span', { className: 'bw-gn-icon' }, node.kind === 'ws' ? icon('IconFolderOpen16', 15) : icon('IconFolderClose16', 15)),
|
|
3871
|
+
E('span', { className: 'bw-gn-label' }, node.label),
|
|
3872
|
+
))
|
|
3873
|
+
if (hasKids && expanded) out.push(...renderNodes(node.children, depth + 1))
|
|
3874
|
+
}
|
|
3875
|
+
return out
|
|
3876
|
+
}
|
|
3877
|
+
return E(ui.Modal, {
|
|
3878
|
+
open: true,
|
|
3879
|
+
onClose,
|
|
3880
|
+
closeLabel: t('close'),
|
|
3881
|
+
title: t('group.new.title'),
|
|
3882
|
+
footer: E('div', { className: 'bw-modal-actions' },
|
|
3883
|
+
E(BTN, { variant: 'outline', onClick: onClose }, t('cancel')),
|
|
3884
|
+
E(BTN, { variant: 'primary', onClick: submit }, t('confirm')),
|
|
3885
|
+
),
|
|
3886
|
+
},
|
|
3887
|
+
E('div', { className: 'bw-modal-body' },
|
|
3888
|
+
kinds.length > 1 ? E('div', { className: 'bw-seg', role: 'radiogroup', 'aria-label': t('group.new.title') },
|
|
3889
|
+
E('button', {
|
|
3890
|
+
type: 'button', role: 'radio', 'aria-checked': kind === 'session' ? 'true' : 'false',
|
|
3891
|
+
className: cls('bw-seg-btn', kind === 'session' && 'bw-seg-btn-on'),
|
|
3892
|
+
onClick: () => { setKind('session'); setPicked(null) },
|
|
3893
|
+
}, t('group.new.sess')),
|
|
3894
|
+
E('button', {
|
|
3895
|
+
type: 'button', role: 'radio', 'aria-checked': kind === 'workspace' ? 'true' : 'false',
|
|
3896
|
+
className: cls('bw-seg-btn', kind === 'workspace' && 'bw-seg-btn-on'),
|
|
3897
|
+
onClick: () => { setKind('workspace'); setPicked(null) },
|
|
3898
|
+
}, t('group.new.ws')),
|
|
3899
|
+
) : null,
|
|
3900
|
+
E('div', { className: 'bw-gn-hint' }, plan.hint || t('group.new.where')),
|
|
3901
|
+
E('div', { className: 'bw-gn-tree', role: 'tree', 'aria-label': t('group.new.where') },
|
|
3902
|
+
plan.nodes.length > 0 ? renderNodes(plan.nodes, 0) : E('div', { className: 'bw-gn-empty' }, t('group.new.noWorkspace'))),
|
|
3903
|
+
E('div', { className: 'bw-field' },
|
|
3904
|
+
E('input', {
|
|
3905
|
+
className: 'bw-input bw-gn-input',
|
|
3906
|
+
value: name,
|
|
3907
|
+
autoFocus: true,
|
|
3908
|
+
placeholder: t('group.new.placeholder'),
|
|
3909
|
+
'aria-label': t('group.new.title'),
|
|
3910
|
+
onChange: (e) => { setName(e.target.value); setError('') },
|
|
3911
|
+
onKeyDown: (e) => { if (e.key === 'Enter') submit() },
|
|
3912
|
+
}),
|
|
3913
|
+
),
|
|
3914
|
+
E('div', { className: 'bw-hint' }, t('group.new.nameHint')),
|
|
3915
|
+
error !== '' ? E('div', { className: 'bw-error-text', role: 'alert' }, error) : null,
|
|
3916
|
+
),
|
|
3917
|
+
)
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3399
3920
|
function MenuPicker({ options, value, onPick, placeholder, chevronOnly, title, t }) {
|
|
3400
3921
|
const [open, setOpen] = React.useState(false)
|
|
3401
3922
|
const list = Array.isArray(options) ? options : []
|
|
@@ -4368,6 +4889,12 @@ window.__ModuleLoader__.load({
|
|
|
4368
4889
|
const prefsMap = useStore ? (useStore(s => s.prefs) || {}) : {}
|
|
4369
4890
|
const stylingMap = useStore ? (useStore(s => s.styling) || {}) : {}
|
|
4370
4891
|
const sessionOrderMap = useStore ? (useStore(s => s.sessionOrder) || {}) : {}
|
|
4892
|
+
// Declared (possibly empty) groups, v0.17.0. Hydration replaces the whole
|
|
4893
|
+
// state, so a snapshot written by an older version has no `folders` key —
|
|
4894
|
+
// the fallback object keeps every read below total.
|
|
4895
|
+
const foldersMap = useStore ? (useStore(s => s.folders) || {}) : {}
|
|
4896
|
+
const wsFolders = foldersMap && typeof foldersMap.ws === 'object' && foldersMap.ws !== null ? foldersMap.ws : EMPTY_FOLDERS
|
|
4897
|
+
const sessFolders = foldersMap && typeof foldersMap.sess === 'object' && foldersMap.sess !== null ? foldersMap.sess : EMPTY_FOLDERS
|
|
4371
4898
|
// View options (v0.13). Selector reads take fallbacks: hydration
|
|
4372
4899
|
// replaces the whole persisted value, so states written by older
|
|
4373
4900
|
// plugin versions lack these keys (hard store discipline).
|
|
@@ -4634,10 +5161,10 @@ window.__ModuleLoader__.load({
|
|
|
4634
5161
|
// their drop targets live inside workspace rows, which compression merges.
|
|
4635
5162
|
const draggingWorkspace = drag !== null && drag.kind === 'workspace'
|
|
4636
5163
|
const tree = React.useMemo(() => {
|
|
4637
|
-
const built = buildTree(items, workspaceSlash)
|
|
5164
|
+
const built = buildTree(items, workspaceSlash, wsFolders)
|
|
4638
5165
|
if (!compactChains || draggingWorkspace) return built
|
|
4639
5166
|
return { ...built, folders: built.folders.map((f) => materializeChain(compressTree(f))), workspaces: built.workspaces }
|
|
4640
|
-
}, [items, compactChains, draggingWorkspace, workspaceSlash])
|
|
5167
|
+
}, [items, compactChains, draggingWorkspace, workspaceSlash, wsFolders])
|
|
4641
5168
|
// Disk-layer ancestry for drag semantics (same derivation as buildTree).
|
|
4642
5169
|
const diskParentMap = React.useMemo(() => diskParentMapOf(items), [items])
|
|
4643
5170
|
|
|
@@ -4744,7 +5271,7 @@ window.__ModuleLoader__.load({
|
|
|
4744
5271
|
// tree AND its disk-nested child levels.
|
|
4745
5272
|
const wsAllPulseOf = (workspace) => {
|
|
4746
5273
|
if (!statusPulse || searching) return null
|
|
4747
|
-
let best = nodePulseOf(buildSessionTree(sessionsOf(workspace), sessionSlash))
|
|
5274
|
+
let best = nodePulseOf(buildSessionTree(sessionsOf(workspace), sessionSlash, sessFolders, workspace.workspaceId))
|
|
4748
5275
|
if (workspace.sub) {
|
|
4749
5276
|
for (const folder of workspace.sub.folders) {
|
|
4750
5277
|
const s = folderPulseOf(folder)
|
|
@@ -4827,6 +5354,53 @@ window.__ModuleLoader__.load({
|
|
|
4827
5354
|
* button itself disappears entirely when neither is (see the header).
|
|
4828
5355
|
*/
|
|
4829
5356
|
const pickerKindOf = () => workspaceSlash && sessionSlash ? undefined : (workspaceSlash ? 'workspace' : 'session')
|
|
5357
|
+
// The header button opens "new group" (v0.17.0); its wording and its
|
|
5358
|
+
// default layer follow which name layers are live. With workspaces
|
|
5359
|
+
// disk-only there is no workspace-group layer to create into, so the
|
|
5360
|
+
// default is the session layer — and a single live layer means no switch.
|
|
5361
|
+
const newGroupLayers = () => (workspaceSlash && sessionSlash ? ['session', 'workspace'] : (workspaceSlash ? ['workspace'] : ['session']))
|
|
5362
|
+
const defaultNewGroupLayer = () => (workspaceSlash && !sessionSlash ? 'workspace' : 'session')
|
|
5363
|
+
const newGroupLabel = () => (workspaceSlash && sessionSlash ? t('group.new.both') : (workspaceSlash ? t('group.new.ws') : t('group.new.sess')))
|
|
5364
|
+
|
|
5365
|
+
// Container trees for the new-group dialog. Each node carries the exact
|
|
5366
|
+
// write target: `scope` is the store's bags key (the disk level's idPrefix
|
|
5367
|
+
// for workspaces, the workspaceId for sessions) and `path` is the parent
|
|
5368
|
+
// group's name path — the dialog appends the new name to it.
|
|
5369
|
+
const wsGroupNodesOf = (node) => {
|
|
5370
|
+
const out = []
|
|
5371
|
+
for (const f of node.folders || []) {
|
|
5372
|
+
out.push({ id: f.scope + '|' + f.path, scope: f.scope, path: f.path, label: f.name, kind: 'folder', children: wsGroupNodesOf(f) })
|
|
5373
|
+
}
|
|
5374
|
+
for (const w of node.workspaces || []) {
|
|
5375
|
+
out.push({
|
|
5376
|
+
id: w.subPrefix + '|', scope: w.subPrefix, path: '', label: w.leaf || w.title, kind: 'ws',
|
|
5377
|
+
children: w.sub ? wsGroupNodesOf(w.sub) : [],
|
|
5378
|
+
})
|
|
5379
|
+
}
|
|
5380
|
+
return out
|
|
5381
|
+
}
|
|
5382
|
+
const sessGroupNodesOf = (node, workspaceId) => (node.groups || []).map((g) => ({
|
|
5383
|
+
id: workspaceId + '|' + g.path, scope: workspaceId, path: g.path, label: g.name, kind: 'sgroup',
|
|
5384
|
+
children: sessGroupNodesOf(g, workspaceId),
|
|
5385
|
+
}))
|
|
5386
|
+
const newGroupTreeOf = (kind) => {
|
|
5387
|
+
if (kind === 'workspace') {
|
|
5388
|
+
// "Top level" IS a real target here (a group at the root of the
|
|
5389
|
+
// workspace tree), so it is a node like any other.
|
|
5390
|
+
return { hint: null, nodes: [{ id: '|', scope: '', path: '', label: t('group.new.root'), kind: 'folder', children: wsGroupNodesOf(tree) }] }
|
|
5391
|
+
}
|
|
5392
|
+
// A session group lives INSIDE one workspace (folders.sess is keyed by
|
|
5393
|
+
// workspaceId), so there is no "top level" to declare into: the
|
|
5394
|
+
// workspace list is the top level itself, introduced by a plain HINT
|
|
5395
|
+
// rather than wrapped in a fake root node (v0.17.1 — a header row that
|
|
5396
|
+
// looked like a tree node read as a container you could pick).
|
|
5397
|
+
const nodes = []
|
|
5398
|
+
for (const w of items) {
|
|
5399
|
+
const inner = sessGroupNodesOf(buildSessionTree(sessionsOf(w), sessionSlash, sessFolders, w.workspaceId), w.workspaceId)
|
|
5400
|
+
nodes.push({ id: w.workspaceId + '|', scope: w.workspaceId, path: '', label: w.leaf || w.title, kind: 'ws', children: inner })
|
|
5401
|
+
}
|
|
5402
|
+
return { hint: t('group.new.pickWorkspace'), nodes }
|
|
5403
|
+
}
|
|
4830
5404
|
/**
|
|
4831
5405
|
* Picker entries for the header button: every workspace, then its
|
|
4832
5406
|
* sessions — each list present only while its OWN name layer is on, so
|
|
@@ -4906,7 +5480,7 @@ window.__ModuleLoader__.load({
|
|
|
4906
5480
|
if (target.kind === 'sgroup') {
|
|
4907
5481
|
const workspace = (items || []).find((w) => w.workspaceId === target.workspaceId)
|
|
4908
5482
|
if (!workspace) { setDialog(null); return }
|
|
4909
|
-
const node = findSessionGroup(buildSessionTree(sessionsOf(workspace), sessionSlash), target.path)
|
|
5483
|
+
const node = findSessionGroup(buildSessionTree(sessionsOf(workspace), sessionSlash, sessFolders, workspace.workspaceId), target.path)
|
|
4910
5484
|
if (!node) { setDialog(null); return }
|
|
4911
5485
|
const affected = collectSessionRows(node)
|
|
4912
5486
|
Promise.resolve()
|
|
@@ -5245,7 +5819,7 @@ window.__ModuleLoader__.load({
|
|
|
5245
5819
|
if (name === target.name) { setDialog(null); return }
|
|
5246
5820
|
const workspace = (items || []).find(w => w.workspaceId === target.workspaceId)
|
|
5247
5821
|
if (!workspace) { setDialog(null); return }
|
|
5248
|
-
const node = findSessionGroup(buildSessionTree(sessionsOf(workspace), sessionSlash), target.path)
|
|
5822
|
+
const node = findSessionGroup(buildSessionTree(sessionsOf(workspace), sessionSlash, sessFolders, workspace.workspaceId), target.path)
|
|
5249
5823
|
if (!node) { setDialog(null); return }
|
|
5250
5824
|
const parentPath = target.path.includes('/') ? target.path.slice(0, target.path.lastIndexOf('/')) : ''
|
|
5251
5825
|
const nextPath = parentPath !== '' ? parentPath + '/' + name : name
|
|
@@ -5305,8 +5879,8 @@ window.__ModuleLoader__.load({
|
|
|
5305
5879
|
// row keeps its count badge); an open one shows the full session tree.
|
|
5306
5880
|
if (!searching && !sessionsOpenOf(workspace.workspaceId)) return []
|
|
5307
5881
|
const rows = sessionsOf(workspace)
|
|
5308
|
-
if (rows.length === 0) return []
|
|
5309
|
-
return renderSessionNode(buildSessionTree(rows, sessionSlash), workspace.workspaceId, depth)
|
|
5882
|
+
if (rows.length === 0 && !hasDeclared(sessFolders, workspace.workspaceId)) return []
|
|
5883
|
+
return renderSessionNode(buildSessionTree(rows, sessionSlash, sessFolders, workspace.workspaceId), workspace.workspaceId, depth)
|
|
5310
5884
|
}
|
|
5311
5885
|
const searchSessionNode = (node) => {
|
|
5312
5886
|
const groups = []
|
|
@@ -5333,7 +5907,7 @@ window.__ModuleLoader__.load({
|
|
|
5333
5907
|
pulse: (statusPulse && !open) ? nodePulseOf(group) : null,
|
|
5334
5908
|
count: countSessionTree(group),
|
|
5335
5909
|
onToggle: () => { if (!searching) actions.setSessionGroupExpanded(key, !open) },
|
|
5336
|
-
onContextMenu: (e) => openCtx('sgroup', { workspaceId, path: group.path, name: group.name }, e),
|
|
5910
|
+
onContextMenu: (e) => openCtx('sgroup', { workspaceId, path: group.path, name: group.name, empty: countSessionTree(group) === 0 }, e),
|
|
5337
5911
|
dropInto: sgroupDropInto(workspaceId, group.path),
|
|
5338
5912
|
dragEvents: sgroupDropEvents(workspaceId, group.path),
|
|
5339
5913
|
custStyle: rowStyleOf('sgroup:' + workspaceId + '|' + group.path),
|
|
@@ -5362,7 +5936,7 @@ window.__ModuleLoader__.load({
|
|
|
5362
5936
|
|
|
5363
5937
|
const renderWorkspaceEntry = (entry, depth, pulse) => {
|
|
5364
5938
|
const { workspace } = entry
|
|
5365
|
-
const count = countSessionTree(buildSessionTree(sessionsOf(workspace), sessionSlash))
|
|
5939
|
+
const count = countSessionTree(buildSessionTree(sessionsOf(workspace), sessionSlash, sessFolders, workspace.workspaceId))
|
|
5366
5940
|
const rows = [E(WorkspaceRow, {
|
|
5367
5941
|
key: 'ws-' + workspace.workspaceId,
|
|
5368
5942
|
workspace,
|
|
@@ -5412,7 +5986,7 @@ window.__ModuleLoader__.load({
|
|
|
5412
5986
|
depth,
|
|
5413
5987
|
expanded,
|
|
5414
5988
|
onToggle: () => { if (!searching) actions.setExpanded(idPath, !expanded) },
|
|
5415
|
-
onContextMenu: (e) => openCtx('folder', { path: node.path, name: node.name, idPath }, e),
|
|
5989
|
+
onContextMenu: (e) => openCtx('folder', { path: node.path, name: node.name, idPath, scope: node.scope, empty: countWorkspaces(node) === 0 }, e),
|
|
5416
5990
|
dropInto: wsDropInto(idPath),
|
|
5417
5991
|
dragEvents: folderDropEvents(idPath),
|
|
5418
5992
|
custStyle: rowStyleOf('folder:' + idPath),
|
|
@@ -5491,20 +6065,30 @@ window.__ModuleLoader__.load({
|
|
|
5491
6065
|
// and offering the item would promise a container the tree no longer
|
|
5492
6066
|
// renders. The two layers gate independently, so a session row keeps
|
|
5493
6067
|
// the entry even when workspaces have gone disk-only.
|
|
5494
|
-
|
|
6068
|
+
// IconFolderOutline16 is NOT in this host's icon set (retired early):
|
|
6069
|
+
// probing it silently yielded a glyph-less row. IconFolderOpenOutline16
|
|
6070
|
+
// is present and reads as "move into a group" just as well.
|
|
6071
|
+
const groupItem = { id: 'move-group', label: t('menu.moveToGroup'), icon: icon('IconFolderOpenOutline16', 16) }
|
|
5495
6072
|
const wsGroupItems = workspaceSlash ? [groupItem] : []
|
|
5496
6073
|
const sgroupItems = sessionSlash ? [groupItem] : []
|
|
6074
|
+
// "Delete group" only makes sense for a DECLARED group that holds
|
|
6075
|
+
// nothing (v0.17.0): a group with members is a projection of their
|
|
6076
|
+
// titles, so removing the declaration would change nothing on screen
|
|
6077
|
+
// and the row would stay — offering it would be a lie.
|
|
6078
|
+
const deleteFolderEntry = { id: 'delete-folder', label: t('menu.deleteGroup'), icon: icon('IconTrashOutline16', 16), danger: true }
|
|
6079
|
+
const deleteSgroupEntry = { id: 'delete-sgroup', label: t('menu.deleteGroup'), icon: icon('IconTrashOutline16', 16), danger: true }
|
|
5497
6080
|
if (ctx.kind === 'folder') return [
|
|
5498
6081
|
{ id: 'rename-folder', label: t('menu.renameFolder'), icon: icon('IconEditOutline16', 16) },
|
|
5499
|
-
...
|
|
6082
|
+
...(ctx.payload && ctx.payload.empty ? [deleteFolderEntry] : []),
|
|
5500
6083
|
{ type: 'separator', id: 'bw-sep' },
|
|
6084
|
+
...wsGroupItems,
|
|
5501
6085
|
customizeEntry,
|
|
5502
6086
|
]
|
|
5503
6087
|
if (ctx.kind === 'workspace') return [
|
|
5504
6088
|
{ id: 'rename', label: ot ? ot('rename') : t('menu.rename'), icon: icon('IconEditOutline16', 16) },
|
|
5505
6089
|
{ id: 'delete', label: ot ? ot('delete.workspace') : t('menu.delete'), icon: icon('IconTrashOutline16', 16), danger: true },
|
|
5506
|
-
...wsGroupItems,
|
|
5507
6090
|
{ type: 'separator', id: 'bw-sep' },
|
|
6091
|
+
...wsGroupItems,
|
|
5508
6092
|
customizeEntry,
|
|
5509
6093
|
]
|
|
5510
6094
|
if (ctx.kind === 'session') return [
|
|
@@ -5514,12 +6098,13 @@ window.__ModuleLoader__.load({
|
|
|
5514
6098
|
// registry-global archive set and never touches the session log —
|
|
5515
6099
|
// deliberately NOT styled destructive.
|
|
5516
6100
|
{ id: 'archive', label: ot ? ot('menu.archiveSession') : t('menu.archive'), icon: icon('IconArchiveOutline20', 16) },
|
|
5517
|
-
...sgroupItems,
|
|
5518
6101
|
{ type: 'separator', id: 'bw-sep' },
|
|
6102
|
+
...sgroupItems,
|
|
5519
6103
|
customizeEntry,
|
|
5520
6104
|
]
|
|
5521
6105
|
return [
|
|
5522
6106
|
{ id: 'rename-sgroup', label: t('menu.renameSgroup'), icon: icon('IconEditOutline16', 16) },
|
|
6107
|
+
...(ctx.payload && ctx.payload.empty ? [deleteSgroupEntry] : []),
|
|
5523
6108
|
...sgroupItems,
|
|
5524
6109
|
{ type: 'separator', id: 'bw-sep' },
|
|
5525
6110
|
customizeEntry,
|
|
@@ -5548,6 +6133,8 @@ window.__ModuleLoader__.load({
|
|
|
5548
6133
|
setDialog({ kind: 'group-move', target, initial: groupPrefixOf(target.title), groupPaths: collectGroupPaths(target.kind) })
|
|
5549
6134
|
}
|
|
5550
6135
|
else if (kind === 'folder' && id === 'rename-folder') setDialog({ kind: 'folder-rename', path: payload.path })
|
|
6136
|
+
else if (kind === 'folder' && id === 'delete-folder') setDialog({ kind: 'folder-delete', path: payload.path, scope: payload.scope, name: payload.name })
|
|
6137
|
+
else if (kind === 'sgroup' && id === 'delete-sgroup') setDialog({ kind: 'sgroup-delete', target: payload })
|
|
5551
6138
|
else if (kind === 'workspace' && id === 'rename') setDialog({ kind: 'ws-rename', workspace: payload })
|
|
5552
6139
|
else if (kind === 'workspace' && id === 'delete') setDialog({ kind: 'ws-delete', workspace: payload })
|
|
5553
6140
|
else if (kind === 'sgroup' && id === 'rename-sgroup') setDialog({ kind: 'sgroup-rename', target: payload })
|
|
@@ -5584,6 +6171,25 @@ window.__ModuleLoader__.load({
|
|
|
5584
6171
|
onClose: () => setDialog(null),
|
|
5585
6172
|
t,
|
|
5586
6173
|
})
|
|
6174
|
+
// Declared-empty groups are the only ones that can be deleted (the menu
|
|
6175
|
+
// only offers it there): removing the declaration of a group whose
|
|
6176
|
+
// members still carry the prefix would change nothing on screen.
|
|
6177
|
+
if (dialog.kind === 'folder-delete') return E(ConfirmDialog, {
|
|
6178
|
+
key: 'folder-delete',
|
|
6179
|
+
title: t('group.delete.title'),
|
|
6180
|
+
body: t('group.delete.body', { name: dialog.name }),
|
|
6181
|
+
onConfirm: () => { actions.removeFolder('workspace', dialog.scope, dialog.path); setDialog(null) },
|
|
6182
|
+
onClose: () => setDialog(null),
|
|
6183
|
+
t,
|
|
6184
|
+
})
|
|
6185
|
+
if (dialog.kind === 'sgroup-delete') return E(ConfirmDialog, {
|
|
6186
|
+
key: 'sgroup-delete',
|
|
6187
|
+
title: t('group.delete.title'),
|
|
6188
|
+
body: t('group.delete.body', { name: dialog.target.name }),
|
|
6189
|
+
onConfirm: () => { actions.removeFolder('session', dialog.target.workspaceId, dialog.target.path); setDialog(null) },
|
|
6190
|
+
onClose: () => setDialog(null),
|
|
6191
|
+
t,
|
|
6192
|
+
})
|
|
5587
6193
|
if (dialog.kind === 'ws-delete') return E(ConfirmDialog, {
|
|
5588
6194
|
key: 'ws-delete',
|
|
5589
6195
|
title: t('ws.delete.title'),
|
|
@@ -5618,6 +6224,15 @@ window.__ModuleLoader__.load({
|
|
|
5618
6224
|
onClose: () => setDialog(null),
|
|
5619
6225
|
t,
|
|
5620
6226
|
})
|
|
6227
|
+
if (dialog.kind === 'group-new') return E(GroupNewDialog, {
|
|
6228
|
+
key: 'group-new',
|
|
6229
|
+
kinds: newGroupLayers(),
|
|
6230
|
+
initialKind: dialog.layer === 'workspace' ? 'workspace' : 'session',
|
|
6231
|
+
treeOf: newGroupTreeOf,
|
|
6232
|
+
onConfirm: (kind, scope, path) => { actions.addFolder(kind, scope, path); setDialog(null) },
|
|
6233
|
+
onClose: () => setDialog(null),
|
|
6234
|
+
t,
|
|
6235
|
+
})
|
|
5621
6236
|
if (dialog.kind === 'group-move') return E(GroupDialog, {
|
|
5622
6237
|
key: 'group-move',
|
|
5623
6238
|
title: t('group.move.title'),
|
|
@@ -5689,17 +6304,18 @@ window.__ModuleLoader__.load({
|
|
|
5689
6304
|
StyleNode(),
|
|
5690
6305
|
E('div', { className: 'bw-header' },
|
|
5691
6306
|
E('div', { className: 'bw-header-title' }, t('title')),
|
|
5692
|
-
// Group discoverability (v0.
|
|
5693
|
-
// that doubles as the row-drop
|
|
5694
|
-
//
|
|
6307
|
+
// Group discoverability: an ICON entry point (v0.17.0 — it used to be
|
|
6308
|
+
// a text button, which crowded the header) that doubles as the row-drop
|
|
6309
|
+
// zone. Its tooltip names the layer(s) the button can actually create
|
|
6310
|
+
// into; the click opens the NEW-group dialog, not the move dialog.
|
|
5695
6311
|
(workspaceSlash || sessionSlash) ? E('button', {
|
|
5696
6312
|
type: 'button',
|
|
5697
|
-
className: cls('bw-newgroup-btn', groupDropTarget() && 'bw-drop-into-strong'),
|
|
5698
|
-
'aria-label':
|
|
5699
|
-
title:
|
|
5700
|
-
onClick: () => setDialog({ kind: 'group-
|
|
6313
|
+
className: cls('bw-icon-btn', 'bw-newgroup-btn', groupDropTarget() && 'bw-drop-into-strong'),
|
|
6314
|
+
'aria-label': newGroupLabel(),
|
|
6315
|
+
title: newGroupLabel(),
|
|
6316
|
+
onClick: () => setDialog({ kind: 'group-new', layer: defaultNewGroupLayer() }),
|
|
5701
6317
|
...groupDropEvents,
|
|
5702
|
-
},
|
|
6318
|
+
}, icon('IconFolderOpen16', 16)) : null,
|
|
5703
6319
|
(searchOpen || query !== '') ? E('input', {
|
|
5704
6320
|
className: 'bw-input',
|
|
5705
6321
|
style: { width: 130, flex: 'none' },
|