@brftech/filex-core 0.40.0 → 0.41.1

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.
Files changed (125) hide show
  1. package/README.md +32 -25
  2. package/dist/ArchiveViewer-DxxjhYZD.js +124 -0
  3. package/dist/ArchiveViewer-DxxjhYZD.js.map +1 -0
  4. package/dist/CsvViewer-CyuwENI_.js +141 -0
  5. package/dist/CsvViewer-CyuwENI_.js.map +1 -0
  6. package/dist/{DrawioViewer-BNALOB04.js → DrawioViewer-BIYsTX97.js} +48 -42
  7. package/dist/DrawioViewer-BIYsTX97.js.map +1 -0
  8. package/dist/EpubViewer-K9PjCMmS.js +163 -0
  9. package/dist/EpubViewer-K9PjCMmS.js.map +1 -0
  10. package/dist/IpynbViewer-DYU6Zgz2.js +184 -0
  11. package/dist/IpynbViewer-DYU6Zgz2.js.map +1 -0
  12. package/dist/MermaidViewer-Bp73aId8.js +137 -0
  13. package/dist/MermaidViewer-Bp73aId8.js.map +1 -0
  14. package/dist/PsdViewer-C2eLivw1.js +122 -0
  15. package/dist/PsdViewer-C2eLivw1.js.map +1 -0
  16. package/dist/TiffViewer-DsHg5gcz.js +142 -0
  17. package/dist/TiffViewer-DsHg5gcz.js.map +1 -0
  18. package/dist/Viewer3D-C_dc2HN9.js +75 -0
  19. package/dist/Viewer3D-C_dc2HN9.js.map +1 -0
  20. package/dist/filex-core.js +218 -20361
  21. package/dist/filex-core.js.map +1 -1
  22. package/dist/filex-core.umd.cjs +61 -62
  23. package/dist/filex-core.umd.cjs.map +1 -1
  24. package/dist/index-vxMZpYb-.js +26700 -0
  25. package/dist/index-vxMZpYb-.js.map +1 -0
  26. package/dist/index.d.ts +4212 -862
  27. package/dist/style.css +1 -1
  28. package/package.json +1 -1
  29. package/src/FileExplorer.vue +2154 -506
  30. package/src/components/AdvancedSearch.vue +870 -0
  31. package/src/components/Breadcrumb.vue +191 -19
  32. package/src/components/CommandPalette.vue +66 -22
  33. package/src/components/ConnectionsPanel.vue +6 -2
  34. package/src/components/ContextMenu.vue +39 -2
  35. package/src/components/EncryptedFolderModal.vue +31 -25
  36. package/src/components/FilePane.vue +951 -0
  37. package/src/components/FilterBar.vue +568 -72
  38. package/src/components/GalleryView.vue +172 -56
  39. package/src/components/GridView.vue +331 -82
  40. package/src/components/HomeView.vue +331 -0
  41. package/src/components/InspectorPanel.vue +296 -53
  42. package/src/components/ListView.vue +1173 -194
  43. package/src/components/NFSExportsPanel.vue +7 -4
  44. package/src/components/OnboardingTour.vue +156 -33
  45. package/src/components/QuickLook.vue +26 -1
  46. package/src/components/RecentlyOpened.vue +17 -2
  47. package/src/components/S3KeysPanel.vue +7 -4
  48. package/src/components/SSHKeysPanel.vue +7 -4
  49. package/src/components/ShortcutSettings.vue +6 -2
  50. package/src/components/ShortcutsHelp.vue +4 -1
  51. package/src/components/SideNav.vue +206 -140
  52. package/src/components/StorageFields.vue +19 -1
  53. package/src/components/TabBar.vue +87 -1
  54. package/src/components/TagPicker.vue +77 -6
  55. package/src/components/ThemeGallery.vue +13 -198
  56. package/src/components/ThemePalette.vue +244 -0
  57. package/src/components/TimeZoneDialog.vue +135 -0
  58. package/src/components/TimeZonePicker.vue +696 -0
  59. package/src/components/TokensPanel.vue +35 -14
  60. package/src/components/Toolbar.vue +953 -465
  61. package/src/components/ViewSwitcher.vue +9 -4
  62. package/src/composables/useConnections.ts +35 -4
  63. package/src/composables/useExplorerTimeZone.ts +89 -0
  64. package/src/composables/useFileApi.ts +53 -4
  65. package/src/composables/useLocale.ts +382 -26
  66. package/src/composables/useRowTouch.ts +95 -0
  67. package/src/index.ts +177 -3
  68. package/src/lib/actionIcons.ts +330 -0
  69. package/src/lib/advSearch.ts +162 -0
  70. package/src/lib/dateGroups.ts +197 -0
  71. package/src/lib/destinationTree.ts +235 -0
  72. package/src/lib/downloadSelection.ts +154 -0
  73. package/src/lib/fileFilters.ts +319 -12
  74. package/src/lib/fileIcons.ts +299 -5
  75. package/src/lib/filePreview.ts +605 -0
  76. package/src/lib/listing.ts +72 -0
  77. package/src/lib/shareTtl.ts +31 -2
  78. package/src/lib/sortOrder.ts +459 -0
  79. package/src/lib/themes.ts +151 -116
  80. package/src/lib/timezone.ts +501 -0
  81. package/src/lib/uiProfile.ts +82 -0
  82. package/src/lib/viewPrefs.ts +1036 -0
  83. package/src/locales/en.ts +416 -26
  84. package/src/locales/tr.ts +409 -26
  85. package/src/modals/ConvertModal.vue +23 -3
  86. package/src/modals/DestinationPickerModal.vue +341 -0
  87. package/src/modals/Modal.vue +18 -4
  88. package/src/modals/NewDocumentModal.vue +525 -0
  89. package/src/modals/NewFolderModal.vue +15 -9
  90. package/src/modals/PermissionsModal.vue +557 -476
  91. package/src/modals/PreviewModal.vue +516 -156
  92. package/src/styles/base.css +5409 -8
  93. package/src/styles/variables.css +139 -65
  94. package/src/types/Connections.ts +9 -0
  95. package/src/types/ExplorerConfig.ts +144 -39
  96. package/src/types/FileNode.ts +28 -0
  97. package/src/viewers/ArchiveViewer.vue +46 -8
  98. package/src/viewers/CsvViewer.vue +19 -2
  99. package/src/viewers/DrawioViewer.vue +12 -1
  100. package/src/viewers/EpubViewer.vue +50 -6
  101. package/src/viewers/IpynbViewer.vue +19 -2
  102. package/src/viewers/MermaidViewer.vue +20 -3
  103. package/src/viewers/PdfViewer.vue +24 -4
  104. package/src/viewers/PsdViewer.vue +28 -4
  105. package/src/viewers/TiffViewer.vue +19 -2
  106. package/src/viewers/Viewer3D.vue +36 -2
  107. package/dist/ArchiveViewer-Cdfv9LFC.js +0 -99
  108. package/dist/ArchiveViewer-Cdfv9LFC.js.map +0 -1
  109. package/dist/CsvViewer-CqWeV8VO.js +0 -131
  110. package/dist/CsvViewer-CqWeV8VO.js.map +0 -1
  111. package/dist/DrawioViewer-BNALOB04.js.map +0 -1
  112. package/dist/EpubViewer-BPmlImG7.js +0 -145
  113. package/dist/EpubViewer-BPmlImG7.js.map +0 -1
  114. package/dist/IpynbViewer-CDlQmuCV.js +0 -175
  115. package/dist/IpynbViewer-CDlQmuCV.js.map +0 -1
  116. package/dist/MermaidViewer-sfZWdR-d.js +0 -127
  117. package/dist/MermaidViewer-sfZWdR-d.js.map +0 -1
  118. package/dist/PsdViewer-DwuySZsb.js +0 -112
  119. package/dist/PsdViewer-DwuySZsb.js.map +0 -1
  120. package/dist/TiffViewer-BDQ3d3CQ.js +0 -132
  121. package/dist/TiffViewer-BDQ3d3CQ.js.map +0 -1
  122. package/dist/Viewer3D-CJJJgkuv.js +0 -60
  123. package/dist/Viewer3D-CJJJgkuv.js.map +0 -1
  124. package/src/components/SecondaryPane.vue +0 -447
  125. package/src/modals/ShareModal.vue +0 -139
package/src/index.ts CHANGED
@@ -28,6 +28,47 @@ export { default as PreviewModal } from './modals/PreviewModal.vue';
28
28
  export { default as StarButton } from './components/StarButton.vue';
29
29
  export { default as TagPicker } from './components/TagPicker.vue';
30
30
  export { default as RecentlyOpened } from './components/RecentlyOpened.vue';
31
+ // belge:n1 — the "New document" picker. Exported because the entry belongs
32
+ // on every surface, not just the admin app: a host that draws its own
33
+ // "+ New" menu mounts this and gets the same dialog.
34
+ export { default as NewDocumentModal } from './modals/NewDocumentModal.vue';
35
+ /* tasi:m1 — the destination picker. Exported for the same reason and one
36
+ * more: it is the folder chooser the product did not have, and it is meant to
37
+ * be the ONLY one. "Move to", "Copy to" and (next) the new-document flow all
38
+ * mount this rather than growing a private browser each. Its rules are pure
39
+ * functions in lib/destinationTree so a host can reuse the decisions without
40
+ * the dialog. */
41
+ export { default as DestinationPickerModal } from './modals/DestinationPickerModal.vue';
42
+ export {
43
+ DRIVES,
44
+ blockedReason,
45
+ crumbsOfWire,
46
+ destinationRows,
47
+ driveRows,
48
+ initialLocation,
49
+ isAtOrInside,
50
+ joinWire,
51
+ labelOfWire,
52
+ parentOfWire,
53
+ permAllowsWrite,
54
+ splitWire,
55
+ } from './lib/destinationTree';
56
+ export type { DestinationRow } from './lib/destinationTree';
57
+
58
+ /* tasi:m1 — "download the selection as one archive". Exported so a host that
59
+ * draws its own selection bar gets the real two-step flow (authorized mint,
60
+ * then a navigation that streams) instead of reaching for window.open per
61
+ * file, which is what the explorer could not do and why Download used to
62
+ * disappear the moment a second row was selected. */
63
+ export {
64
+ DOWNLOAD_FRAME_TTL_MS,
65
+ absoluteTicketUrl,
66
+ archiveTicketUrl,
67
+ downloadArchive,
68
+ requestArchive,
69
+ triggerFileNavigation,
70
+ } from './lib/downloadSelection';
71
+ export type { ArchiveTicket } from './lib/downloadSelection';
31
72
 
32
73
  // ——— Types ———
33
74
  export type {
@@ -44,6 +85,7 @@ export type {
44
85
  ShareInfo,
45
86
  UploadLimits,
46
87
  Capabilities,
88
+ NewDocType,
47
89
  ExternalServiceState,
48
90
  ExternalServiceStatus,
49
91
  UploadInitResponse,
@@ -74,6 +116,18 @@ export type {
74
116
  BrowserProbeDeps,
75
117
  } from './lib/externalReach';
76
118
 
119
+ /* gorunum:v4-hostmenu — the ACTION glyph vocabulary.
120
+ *
121
+ * Exported because a host draws rows that belong to this explorer: the web
122
+ * app's account menu now carries the explorer's own settings rows (see
123
+ * Toolbar.vue's `fe:header-menu` claim), and an "Restart the tour" row drawn
124
+ * with somebody else's icon set is the emoji problem lib/actionIcons.ts was
125
+ * written to end — a dozen sets, whatever weight and colour the OS shipped,
126
+ * four pixels from a column of stroked grey ones. One vocabulary, one voice.
127
+ * `actionIconSvg` returns '' for an unknown key, so a host can call it for
128
+ * every row and draw nothing where there is nothing. */
129
+ export { actionIconSvg, actionIconKeys } from './lib/actionIcons';
130
+
77
131
  export { snippetSegments, matchedInContent } from './lib/snippet';
78
132
  export type { SnippetSegment, SearchMatched } from './lib/snippet';
79
133
 
@@ -100,7 +154,8 @@ export type { ResumeRecord, ResumeStorage } from './lib/uploadResume';
100
154
  export { useSelection } from './composables/useSelection';
101
155
  export { useKeyboardShortcuts } from './composables/useKeyboardShortcuts';
102
156
  export type { ShortcutHandlers } from './composables/useKeyboardShortcuts';
103
- export { useLocale } from './composables/useLocale';
157
+ export { useLocale, localeTag, formatByteSize, formatInstant } from './composables/useLocale';
158
+ export type { ByteSizeOptions, ByteUnitKey } from './composables/useLocale';
104
159
  export { usePendingOps } from './composables/usePendingOps';
105
160
  export type { PendingOp, UsePendingOpsOptions } from './composables/usePendingOps';
106
161
  export {
@@ -126,9 +181,51 @@ export {
126
181
  applyThemeToEl,
127
182
  syncThemeStyle,
128
183
  generateThemeCss,
184
+ /* The light/dark MODE half — a different question from which palette paints
185
+ * (see themes.ts). Exported because the appearance controls now live in the
186
+ * host's own settings surface, and a host that can pick a palette but cannot
187
+ * un-pin the mode the explorer's old strip wrote would leave the user with a
188
+ * switch that visibly does nothing. */
189
+ THEME_MODE_LS_KEY,
190
+ useThemeModeState,
191
+ setThemeMode,
129
192
  } from './lib/themes';
130
- export type { ThemeDef, ThemeTokenMap } from './lib/themes';
193
+ export type { ThemeDef, ThemeTokenMap, ThemeModePref } from './lib/themes';
131
194
  export { default as ThemeGallery } from './components/ThemeGallery.vue';
195
+ /* The palette grid without the modal around it — for hosts that show
196
+ * appearance settings in a pane of their own (the filex admin app does). */
197
+ export { default as ThemePalette } from './components/ThemePalette.vue';
198
+
199
+ /* zaman:z1 / z3 — whose clock an instant is read on. ONE resolver ranks the
200
+ * tiers (`TIME_ZONE_TIERS`: the viewer's own pick in this browser, the host's
201
+ * `config.timeZone`, the account behind a PERSON's credential, the device) and
202
+ * every surface formats through it — the explorer, and a host's own dates
203
+ * outside it (the filex admin app's `lib/format` reads `activeTimeZone()`), so
204
+ * an admin page and an embed cannot disagree about one file's time again. */
205
+ export {
206
+ TIME_ZONE_TIERS,
207
+ resolveTimeZone,
208
+ activeTimeZone,
209
+ resolvedTimeZone,
210
+ timeZoneSources,
211
+ TIMEZONE_VIEWER_LS_KEY,
212
+ viewerTimeZone,
213
+ setViewerTimeZone,
214
+ setHostTimeZone,
215
+ TIMEZONE_ACCOUNT_LS_KEY,
216
+ setAccountTimeZone,
217
+ accountTimeZoneOf,
218
+ rememberedAccountTimeZone,
219
+ releaseTimeZoneOwner,
220
+ deviceTimeZone,
221
+ isValidTimeZone,
222
+ supportedTimeZones,
223
+ zonedDayNumber,
224
+ } from './lib/timezone';
225
+ export type { TimeZoneTier, TimeZoneSources, ResolvedTimeZone } from './lib/timezone';
226
+ /* The picker itself — a host with a settings surface of its own (the filex
227
+ * admin app) mounts this rather than writing a second one. */
228
+ export { default as TimeZonePicker } from './components/TimeZonePicker.vue';
132
229
  /* wiring:c2 — customizable shortcut registry + settings/quick-look UI */
133
230
  export {
134
231
  SHORTCUT_ACTIONS,
@@ -175,7 +272,7 @@ export { default as OperationsCenter } from './components/OperationsCenter.vue';
175
272
  export { useTabs } from './composables/useTabs';
176
273
  export type { TabState, TabSplit, TabsApi } from './composables/useTabs';
177
274
  export { default as TabBar } from './components/TabBar.vue';
178
- export { default as SecondaryPane } from './components/SecondaryPane.vue';
275
+ export { default as FilePane } from './components/FilePane.vue';
179
276
  /* /wiring:d1 */
180
277
  /* wiring:e2 — end-to-end encrypted folders (WebCrypto; docs/E2E-ENCRYPTION.md) */
181
278
  export {
@@ -288,3 +385,80 @@ export type {
288
385
  StorageWrite,
289
386
  } from './types/Connections';
290
387
  /* /connections */
388
+
389
+ /* tablo:t1 — per-folder view memory + the table configuration.
390
+ *
391
+ * Exported because the HOST owns two things this module cannot reach: the
392
+ * settings control that turns the memory on (`folderMemoryEnabled` /
393
+ * `setFolderMemoryEnabled`), and the transport that reads and writes the
394
+ * document (`attachViewPrefsStore` — the explorer wires its own, but a host
395
+ * embedding the views directly has to). The rest is exported so the gates in
396
+ * `web/tests/lib/viewPrefs.test.ts` drive the real module rather than a copy. */
397
+ export {
398
+ COLUMNS,
399
+ FOLDER_CAP,
400
+ NAME_AUTO,
401
+ NAME_MIN,
402
+ __flushViewPrefs,
403
+ __resetViewPrefs,
404
+ attachViewPrefsStore,
405
+ canMoveColumn,
406
+ columnHidden,
407
+ columnOrder,
408
+ columnWidth,
409
+ columnsCustomised,
410
+ folderIsRemembered,
411
+ folderKey,
412
+ folderMemoryEnabled,
413
+ folderPrefs,
414
+ forgetAllFolders,
415
+ forgetFolder,
416
+ freezeWidths,
417
+ moveColumn,
418
+ moveColumnBy,
419
+ rememberFolder,
420
+ rememberedCount,
421
+ resetColumns,
422
+ setColumnHidden,
423
+ setColumnWidth,
424
+ setFolderMemoryEnabled,
425
+ tableLayout,
426
+ touchFolder,
427
+ viewPrefsSlot,
428
+ widthsAreAuto,
429
+ } from './lib/viewPrefs';
430
+ export type {
431
+ ColumnId,
432
+ ColumnSpec,
433
+ FolderPrefs,
434
+ TableLayout,
435
+ ViewPrefsSlot,
436
+ ViewPrefsTransport,
437
+ } from './lib/viewPrefs';
438
+
439
+ /* gruplama — the date ladder every listing view draws its headings from.
440
+ *
441
+ * Exported so the gate in `web/tests/lib/dateGroups.test.ts` measures the real
442
+ * rungs rather than a copy of them, and so a host that mounts ListView /
443
+ * GridView / GalleryView itself can label a listing of its own with the same
444
+ * words the explorer uses. */
445
+ export { dateBucketFor, groupByDate, groupingActive } from './lib/dateGroups';
446
+ export type {
447
+ DateBucket,
448
+ DateGroupLabels,
449
+ DateGrouping,
450
+ DateRun,
451
+ } from './lib/dateGroups';
452
+
453
+ /* uiProfile — the two profiles, and the rule for everything that is not one of
454
+ * them. Exported because the web component resolves the `ui-profile` ATTRIBUTE
455
+ * with it: a string off the DOM needs the same answer the `config` object gets,
456
+ * and two copies of that answer is how the element and the SFC come to disagree
457
+ * about what an unknown value means. */
458
+ export {
459
+ DEFAULT_UI_PROFILE,
460
+ UI_PROFILES,
461
+ __resetUiProfileWarnings,
462
+ resolveUiProfile,
463
+ } from './lib/uiProfile';
464
+ export type { UiProfile } from './lib/uiProfile';
@@ -0,0 +1,330 @@
1
+ /**
2
+ * actionIcons — menu/toolbar ACTION key → inline SVG icon mapping.
3
+ *
4
+ * The sibling of `lib/fileIcons.ts`, for the other half of the problem: that
5
+ * module draws what a row IS (a folder, a sheet, an archive), this one draws
6
+ * what a row DOES (open, rename, move to trash).
7
+ *
8
+ * Why it exists: the action rows used to carry emoji (`↗ 👁 ⬇ 🗑 …`). Emoji are
9
+ * not one icon set — they are a dozen sets, rendered by whatever font the OS
10
+ * happens to ship, at whatever weight and colour that font's designer chose.
11
+ * Two rows of the same menu could come out flat-grey and full-colour, one
12
+ * hairline and one bold, and none of them matched the file glyphs drawn four
13
+ * pixels to the left. A stroked set on `currentColor` gives the menu one voice
14
+ * and lets the row's own colour reach the icon — which is how the destructive
15
+ * entry, and only the destructive entry, comes out red.
16
+ *
17
+ * Conventions are `fileIcons.ts`'s, deliberately: static strings (so `v-html`
18
+ * is injecting markup this file wrote, never user input), one cache, 24×24
19
+ * viewBox, 1.6 stroke, round caps/joins, `currentColor`.
20
+ *
21
+ * Adding a key: draw it here. Nothing else has to change — the renderers ask
22
+ * for the icon by the action's own key, so a new action gets its icon the
23
+ * moment its glyph lands in GLYPHS, and an action with no glyph renders
24
+ * nothing (the caller falls back to whatever `icon` string it was handed, so
25
+ * an embedder's custom emoji still shows).
26
+ */
27
+
28
+ /* Shared fragments — the arrow/tray pair reads as one family when download and
29
+ * upload sit two rows apart. */
30
+ const TRAY = '<path d="M4.5 15.5v3A2 2 0 0 0 6.5 20.5h11a2 2 0 0 0 2-2v-3"/>';
31
+ const EYE =
32
+ '<path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"/>' +
33
+ '<circle cx="12" cy="12" r="3"/>';
34
+ const STAR_PATH =
35
+ 'M12 3.6l2.6 5.3 5.85.85-4.23 4.12 1 5.83L12 16.97l-5.22 2.73 1-5.83L3.55 9.75l5.85-.85z';
36
+ /* The push-pin, shared by the two "keep on this device" states. */
37
+ const PIN =
38
+ '<path d="M8 3.5h8"/>' +
39
+ '<path d="M9.6 3.5v6.1L7.2 12.3v1.7h9.6v-1.7L14.4 9.6V3.5"/>' +
40
+ '<path d="M12 14v6.5"/>';
41
+ /* A window/panel with one edge split off — nav and inspector are the same
42
+ * shape mirrored, which is exactly what the two panels are. */
43
+ const PANEL = '<rect x="3.5" y="4.5" width="17" height="15" rx="2"/>';
44
+ /* The bin. `delete` moves a thing INTO it and `open-trash` opens it; those are
45
+ * two actions on one object, so they are one drawing (Drive and Finder draw
46
+ * them the same way too). */
47
+ const BIN =
48
+ '<path d="M4 6.5h16"/>' +
49
+ '<path d="M9.5 6.5V4.9a1.4 1.4 0 0 1 1.4-1.4h2.2a1.4 1.4 0 0 1 1.4 1.4v1.6"/>' +
50
+ '<path d="M6.6 6.5l.85 12.1a2 2 0 0 0 2 1.9h5.1a2 2 0 0 0 2-1.9l.85-12.1"/>' +
51
+ '<path d="M10.3 10.3v6.4"/><path d="M13.7 10.3v6.4"/>';
52
+ /* The ribbon a saved search hangs on — `bookmark` is the saved thing,
53
+ * `save-search` is the same ribbon with the "add" plus in it. */
54
+ const BOOKMARK =
55
+ '<path d="M6.8 3.5h10.4a1.6 1.6 0 0 1 1.6 1.6v15.4l-6.8-3.9-6.8 3.9V5.1A1.6 1.6 0 0 1 6.8 3.5z"/>';
56
+
57
+ const GLYPHS: Record<string, string> = {
58
+ /* ── opening ─────────────────────────────────────────────────────────── */
59
+ open:
60
+ '<path d="M14 3.5h6.5V10"/>' +
61
+ '<path d="M20.5 3.5L12 12"/>' +
62
+ '<path d="M18 13.5v5a2 2 0 0 1-2 2H5.5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5"/>',
63
+ 'open-tab':
64
+ '<rect x="3" y="4.5" width="18" height="15" rx="2"/>' +
65
+ '<path d="M3 9.5h18"/>' +
66
+ '<path d="M9.5 4.5v5"/>',
67
+ preview: EYE,
68
+ download: TRAY + '<path d="M12 4v11.5"/><path d="M7.5 11L12 15.5 16.5 11"/>',
69
+ upload:
70
+ '<path d="M4.5 15.5v3A2 2 0 0 0 6.5 20.5h11a2 2 0 0 0 2-2v-3"/>' +
71
+ '<path d="M12 15.5V4"/><path d="M7.5 8.5L12 4l4.5 4.5"/>',
72
+ convert:
73
+ '<path d="M4 8.5h13"/><path d="M13.5 5L17 8.5 13.5 12"/>' +
74
+ '<path d="M20 15.5H7"/><path d="M10.5 12L7 15.5 10.5 19"/>',
75
+ /* "Paylaş / İzinler" — the three-node share, not a chain link: this row
76
+ opens permissions as well as a link, and people are the subject. */
77
+ access:
78
+ '<circle cx="17.5" cy="6" r="2.5"/>' +
79
+ '<circle cx="6.5" cy="12" r="2.5"/>' +
80
+ '<circle cx="17.5" cy="18" r="2.5"/>' +
81
+ '<path d="M8.75 10.8l6.5-3.6"/><path d="M8.75 13.2l6.5 3.6"/>',
82
+ details:
83
+ '<circle cx="12" cy="12" r="8.5"/>' +
84
+ '<path d="M12 11.2v5.6"/>' +
85
+ '<circle cx="12" cy="7.7" r="0.9" fill="currentColor" stroke="none"/>',
86
+ /* ── identity / clipboard ────────────────────────────────────────────── */
87
+ 'copy-id':
88
+ '<rect x="2.5" y="5" width="19" height="14" rx="2.5"/>' +
89
+ '<circle cx="8.5" cy="10.8" r="2"/>' +
90
+ '<path d="M5.3 16.2c.5-1.5 1.75-2.4 3.2-2.4s2.7.9 3.2 2.4"/>' +
91
+ '<path d="M14.8 10h4.2"/><path d="M14.8 13.6h4.2"/>',
92
+ /* A clipboard carrying a path separator — this copies the location, not the
93
+ thing that lives at it. */
94
+ 'copy-path':
95
+ '<path d="M9 4.5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-12a2 2 0 0 0-2-2h-2"/>' +
96
+ '<rect x="9" y="2.8" width="6" height="3.6" rx="1.2"/>' +
97
+ '<path d="M10 11.2l2.4 2.4-2.4 2.4"/>',
98
+ rename: '<path d="M4 20l.9-4.1 11-11a2.05 2.05 0 0 1 2.9 2.9l-11 11z"/><path d="M14 7l3 3"/>',
99
+ cut:
100
+ '<circle cx="6.5" cy="17.5" r="2.5"/>' +
101
+ '<circle cx="6.5" cy="6.5" r="2.5"/>' +
102
+ '<path d="M8.6 8.3L19 19"/><path d="M8.6 15.7L19 5"/>',
103
+ copy:
104
+ '<rect x="9" y="9" width="11.5" height="11.5" rx="2"/>' +
105
+ '<path d="M15 6v-.5a2 2 0 0 0-2-2H5.5a2 2 0 0 0-2 2V13a2 2 0 0 0 2 2H6"/>',
106
+ paste:
107
+ '<path d="M9 4.5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-12a2 2 0 0 0-2-2h-2"/>' +
108
+ '<rect x="9" y="2.8" width="6" height="3.6" rx="1.2"/>',
109
+ /* ── metadata ────────────────────────────────────────────────────────── */
110
+ /* star = "not starred yet, do it" (outline); unstar = the state you are in
111
+ (filled). The renderer picks between them by name, see FileExplorer. */
112
+ star: `<path d="${STAR_PATH}"/>`,
113
+ unstar: `<path d="${STAR_PATH}" fill="currentColor"/>`,
114
+ tags:
115
+ '<path d="M3.5 11.4V5.2a1.7 1.7 0 0 1 1.7-1.7h6.2a1.7 1.7 0 0 1 1.2.5l7.2 7.2a1.7 1.7 0 0 1 0 2.4l-6.2 6.2a1.7 1.7 0 0 1-2.4 0L4 12.6a1.7 1.7 0 0 1-.5-1.2z"/>' +
116
+ '<circle cx="8" cy="8" r="1.3"/>',
117
+ /* ── destructive / recovery ──────────────────────────────────────────── */
118
+ delete: BIN,
119
+ 'open-trash': BIN,
120
+ /* The undo arrow, not a rotating one: "Geri Yükle" puts a thing back where
121
+ it was, and that is the gesture people already read as undo. */
122
+ restore: '<path d="M9 6L5 10l4 4"/><path d="M5 10h9a5 5 0 0 1 0 10h-3.5"/>',
123
+ /* ── folder-level ────────────────────────────────────────────────────── */
124
+ 'new-folder':
125
+ '<path d="M3 7.5a2 2 0 0 1 2-2h4.2a2 2 0 0 1 1.4.6l1.4 1.4H19a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>' +
126
+ '<path d="M12 11.3v5"/><path d="M9.5 13.8h5"/>',
127
+ /* belge:n1 — a page with a plus: "make me a new document of some type".
128
+ Keyed by the action's own key, so the menu row carries no icon of its
129
+ own and no emoji enters the markup. */
130
+ 'new-document':
131
+ '<path d="M13.5 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5"/><path d="M13.5 3l5 5"/>' +
132
+ '<path d="M13.5 3v5h5"/><path d="M17.5 14.5v6"/><path d="M14.5 17.5h6"/>',
133
+ refresh: '<path d="M20.5 12a8.5 8.5 0 1 1-2.49-6.01"/><path d="M18 1.5V6h-4.5"/>',
134
+ /* ── desktop sync ("keep on this device") ────────────────────────────── */
135
+ 'keep-local': PIN,
136
+ 'keep-online':
137
+ '<path d="M7 18.5h10a3.5 3.5 0 0 0 0-7 5 5 0 0 0-9.6-1.2A4.2 4.2 0 0 0 7 18.5z"/>',
138
+ /* Kept because a parent folder is kept — the pin, with the branch it came
139
+ down. The row is disabled; the glyph says why. */
140
+ 'keep-inherited': PIN + '<path d="M2.5 20.5h3.6a1.6 1.6 0 0 0 1.6-1.6V17"/>',
141
+ 'keep-reveal':
142
+ '<path d="M2.5 17.5V6.5a2 2 0 0 1 2-2h3.8a2 2 0 0 1 1.4.6l1.4 1.4h4.4a2 2 0 0 1 2 2v1.8"/>' +
143
+ '<path d="M4 19.5h13.4a2 2 0 0 0 1.9-1.37l2-6A1.4 1.4 0 0 0 20 10.3H9.4a2 2 0 0 0-1.9 1.37L5.5 18"/>',
144
+ /* ── view preferences ────────────────────────────────────────────────── */
145
+ 'toggle-hidden':
146
+ '<path d="M3.5 3.5l17 17"/>' +
147
+ '<path d="M9.9 6A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17.3 17.3 0 0 1-3.35 4.15"/>' +
148
+ '<path d="M6.55 7.7A17.4 17.4 0 0 0 2.5 12S6 18.5 12 18.5a9.7 9.7 0 0 0 3.95-.85"/>' +
149
+ '<path d="M10.2 10.3a2.9 2.9 0 0 0 3.9 4.1"/>',
150
+ density: '<path d="M8.5 8L12 4.5 15.5 8"/><path d="M8.5 16L12 19.5 15.5 16"/><path d="M12 4.5v15"/>',
151
+ 'view-list':
152
+ '<path d="M8.5 6.5h12"/><path d="M8.5 12h12"/><path d="M8.5 17.5h12"/>' +
153
+ '<path d="M4 6.5h.01"/><path d="M4 12h.01"/><path d="M4 17.5h.01"/>',
154
+ 'view-grid':
155
+ '<rect x="3.5" y="3.5" width="7" height="7" rx="1.5"/>' +
156
+ '<rect x="13.5" y="3.5" width="7" height="7" rx="1.5"/>' +
157
+ '<rect x="3.5" y="13.5" width="7" height="7" rx="1.5"/>' +
158
+ '<rect x="13.5" y="13.5" width="7" height="7" rx="1.5"/>',
159
+ 'view-gallery':
160
+ '<rect x="3" y="4.5" width="18" height="15" rx="2"/>' +
161
+ '<circle cx="8.3" cy="9.6" r="1.5"/>' +
162
+ '<path d="M4.3 17l4.7-4.7 3 3 3-3 5 4.7"/>',
163
+ nav: PANEL + '<path d="M9.5 4.5v15"/>',
164
+ inspector: PANEL + '<path d="M14.5 4.5v15"/>',
165
+ /* ── explorer settings ───────────────────────────────────────────────── */
166
+ 'shortcut-settings':
167
+ '<rect x="2.5" y="6" width="19" height="12" rx="2.5"/>' +
168
+ '<path d="M6.5 9.8h.01"/><path d="M10 9.8h.01"/><path d="M13.5 9.8h.01"/><path d="M17 9.8h.01"/>' +
169
+ '<path d="M6.5 12.9h.01"/><path d="M10 12.9h.01"/><path d="M13.5 12.9h.01"/><path d="M17 12.9h.01"/>' +
170
+ '<path d="M8.6 15.6h6.8"/>',
171
+ /* Light/dark as the thing it actually switches: one disc, half of it inked. */
172
+ theme:
173
+ '<circle cx="12" cy="12" r="8.5"/>' +
174
+ '<path d="M12 3.5a8.5 8.5 0 0 1 0 17z" fill="currentColor" stroke="none"/>',
175
+ /* zaman:z3 — a clock face, for the Time zone row. */
176
+ timezone: '<circle cx="12" cy="12" r="8.5"/><path d="M12 7.2V12l3.2 2"/>',
177
+ tour:
178
+ '<path d="M2.5 9L12 4.5 21.5 9 12 13.5z"/>' +
179
+ '<path d="M6.6 11.1v4.4c0 1.6 2.4 2.9 5.4 2.9s5.4-1.3 5.4-2.9v-4.4"/>',
180
+ /* "Dosya İste" — the public drop link. A chain, because what this makes is
181
+ a URL to hand somebody. */
182
+ 'request-files':
183
+ '<path d="M10.2 13.8a4.2 4.2 0 0 0 6.1 0l2.3-2.3a4.3 4.3 0 0 0-6.1-6.1l-1.35 1.35"/>' +
184
+ '<path d="M13.8 10.2a4.2 4.2 0 0 0-6.1 0l-2.3 2.3a4.3 4.3 0 0 0 6.1 6.1l1.35-1.35"/>',
185
+ /* ── toolbar chrome that is not a ContextAction ──────────────────────── */
186
+ 'go-up': '<path d="M12 20V5"/><path d="M5.5 11.5L12 5l6.5 6.5"/>',
187
+ /* === gorunum:v2-topbar — the breadcrumb row's own controls ============
188
+ The crumb trail gained the reference shell's three affordances: a home
189
+ crumb in place of the bare "/" root, a chevron on the last crumb that
190
+ lists what is inside it, and the path editor we already had (its glyph
191
+ moves here from Breadcrumb.vue so every mark in the row comes from one
192
+ file). */
193
+ home:
194
+ '<path d="M3.5 10.6L12 3.5l8.5 7.1"/>' +
195
+ '<path d="M5.6 9.4V19a1.5 1.5 0 0 0 1.5 1.5h9.8a1.5 1.5 0 0 0 1.5-1.5V9.4"/>' +
196
+ '<path d="M9.9 20.5v-5.2h4.2v5.2"/>',
197
+ subfolders: '<path d="M6.8 9.8L12 15l5.2-5.2"/>',
198
+ 'edit-path': '<path d="M4 20h4l10-10a2.1 2.1 0 0 0-3-3L5 17z"/><path d="M14.5 6.5l3 3"/>',
199
+ /* === gorunum:v2-topbar — the header's trailing cluster ================
200
+ The page chrome around the explorer is gone, so the account-level doors
201
+ are drawn by the header itself (the host fills them through the
202
+ `header-actions` slot). The glyphs live here so an embedder's cluster
203
+ comes out the same weight and colour as everything beside it. */
204
+ admin:
205
+ '<rect x="3" y="3.5" width="7.4" height="7.4" rx="1.6"/>' +
206
+ '<rect x="13.6" y="3.5" width="7.4" height="4.6" rx="1.6"/>' +
207
+ '<rect x="13.6" y="10.7" width="7.4" height="9.8" rx="1.6"/>' +
208
+ '<rect x="3" y="13.6" width="7.4" height="6.9" rx="1.6"/>',
209
+ /* ⚠ A PERSON, not a cog. The first draft drew the gear as a ringed disc with
210
+ eight spokes and at 16px it came out as a SUN — sitting in the very row
211
+ the light/dark toggle had just been removed from, which is the one thing
212
+ the mark must not be mistaken for (measured at 1440 and 390: it read as a
213
+ theme switch in both). The account bust is what the reference hangs this
214
+ menu on, and the tooltip says which settings. */
215
+ account: '<circle cx="12" cy="8.2" r="3.6"/><path d="M4.9 20.3a7.35 7.35 0 0 1 14.2 0"/>',
216
+ 'sign-out':
217
+ '<path d="M9.5 20.5H6a2 2 0 0 1-2-2v-13a2 2 0 0 1 2-2h3.5"/>' +
218
+ '<path d="M15.5 16.5L20 12l-4.5-4.5"/>' +
219
+ '<path d="M20 12H9"/>',
220
+ /* The AI assistant, drawn but not yet wired — a spark, the mark every
221
+ assistant in this class of product wears, so the day it does something
222
+ the icon does not have to change under people's fingers. */
223
+ ai:
224
+ '<path d="M12 3.2l1.75 4.3 4.3 1.75-4.3 1.75L12 15.3l-1.75-4.3L5.95 9.25l4.3-1.75z"/>' +
225
+ '<path d="M18.2 15.1l.8 1.95 1.95.8-1.95.8-.8 1.95-.8-1.95-1.95-.8 1.95-.8z"/>',
226
+ /* The notification bell, for the host's header cluster (web/src
227
+ NotificationBell.vue). ⚠ Drawn HERE, not taken from the app's icon
228
+ library: it sits in the same row as refresh, the spark and the avatar,
229
+ and a glyph at a foreign stroke weight in that row is the exact mismatch
230
+ this set exists to prevent. A dome on a flared rim, the clapper below. */
231
+ bell:
232
+ '<path d="M6.2 16.6V11a5.8 5.8 0 0 1 11.6 0v5.6l1.7 1.9H4.5z"/>' +
233
+ '<path d="M10.1 21a2.1 2.1 0 0 0 3.8 0"/>',
234
+ /* === /gorunum:v2-topbar === */
235
+ search: '<circle cx="10.8" cy="10.8" r="6.3"/><path d="M15.4 15.4l5.1 5.1"/>',
236
+ /* gorunum:v1-advsearch — sliders, not a funnel. A funnel says "throw rows
237
+ away"; this control opens a dialog where you SET things, and two of its
238
+ three rows are exactly a slider's job (a size range, a time window). */
239
+ filter:
240
+ '<path d="M4 7.5h4"/><path d="M13 7.5h7"/><circle cx="10.5" cy="7.5" r="2.4"/>' +
241
+ '<path d="M4 16.5h7"/><path d="M16 16.5h4"/><circle cx="13.5" cy="16.5" r="2.4"/>',
242
+ more:
243
+ '<circle cx="5.5" cy="12" r="1.2" fill="currentColor" stroke="none"/>' +
244
+ '<circle cx="12" cy="12" r="1.2" fill="currentColor" stroke="none"/>' +
245
+ '<circle cx="18.5" cy="12" r="1.2" fill="currentColor" stroke="none"/>',
246
+ close: '<path d="M5.5 5.5l13 13"/><path d="M18.5 5.5l-13 13"/>',
247
+ /* A share link. Two half-chains, which is what a link IS. The details
248
+ panel's "Not shared / Create link" row used to draw a chain EMOJI
249
+ there, i.e. whatever glyph the OS happened to ship, full-colour,
250
+ beside a column of stroked grey ones. */
251
+ link:
252
+ '<path d="M10 13.5a4.5 4.5 0 0 0 6.8.5l2.7-2.7a4.5 4.5 0 0 0-6.36-6.36L11.6 6.5"/>' +
253
+ '<path d="M14 10.5a4.5 4.5 0 0 0-6.8-.5l-2.7 2.7a4.5 4.5 0 0 0 6.36 6.36l1.53-1.53"/>',
254
+
255
+ /* === ikon:emoji — the command palette's own rows =======================
256
+ * The palette printed `{{ it.icon }}` as TEXT: `➜ 📁 ⬆ ▦ 🗑 ⟳ ↑ 🎨 ⌨ 🎓 ⧉ ◫
257
+ * 💾 🔖`. Thirteen marks from four different type families, in a list whose
258
+ * neighbours — the context menu it duplicates and the toolbar it launches —
259
+ * had already moved here. Most of the keys it needed were drawn already
260
+ * (`tour` is the mortarboard, `shortcut-settings` the keyboard, the three
261
+ * `view-*` the mode chips); these six are what was genuinely missing. */
262
+
263
+ /* "Go to path" — the arrow goes INTO the box, the mirror of `sign-out`.
264
+ A bare arrow would have said "next", which this row is not. */
265
+ goto:
266
+ '<path d="M13.5 4.5h4a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-4"/>' +
267
+ '<path d="M10 8.2L13.8 12 10 15.8"/>' +
268
+ '<path d="M4.5 12h9.3"/>',
269
+ /* A second pane opened beside the first — `nav`/`inspector`'s panel with the
270
+ divider down the middle, because that is literally what split view is. */
271
+ split: PANEL + '<path d="M12 4.5v15"/>',
272
+ /* A new tab: `open-tab`'s window, with the plus in the body rather than on
273
+ the tab strip (at 16px a plus up there merges into the tab). */
274
+ 'tab-new':
275
+ '<rect x="3" y="4.5" width="18" height="15" rx="2"/>' +
276
+ '<path d="M3 9.5h18"/>' +
277
+ '<path d="M9.5 4.5v5"/>' +
278
+ '<path d="M12 12.1v5"/><path d="M9.5 14.6h5"/>',
279
+ bookmark: BOOKMARK,
280
+ 'save-search': BOOKMARK + '<path d="M12 7.2v5.2"/><path d="M9.4 9.8h5.2"/>',
281
+ /* The padlock, for the rows and strips that talk ABOUT encryption ("create
282
+ an encrypted folder", "this one is unlocked"). The listing marker is a
283
+ different drawing on purpose — there the lock is cut out of the folder,
284
+ because the row still has to read as a folder. */
285
+ lock:
286
+ '<rect x="4.5" y="10.3" width="15" height="10.2" rx="2.2"/>' +
287
+ '<path d="M7.9 10.3V7.6a4.1 4.1 0 0 1 8.2 0v2.7"/>' +
288
+ '<circle cx="12" cy="15.4" r="1.5"/>',
289
+
290
+ /* === ikon:emoji — states the viewers and modals report ================
291
+ * `✓ ✗ ⚠️ ⏳` in the viewer chrome. The first two are typographic and came
292
+ * out at whatever weight the UI font had; the last two are colour emoji, so
293
+ * a fallback screen's 48px mark was the only full-colour thing on a grey
294
+ * page. `progress` is drawn as an open ring because it is SPUN by CSS
295
+ * (`@keyframes fe-spin`) — an hourglass cannot say "still going". */
296
+ check: '<path d="M4.8 12.6l4.7 4.7L19.2 6.9"/>',
297
+ alert:
298
+ '<path d="M10.7 4.6L2.9 18.2a1.5 1.5 0 0 0 1.3 2.25h15.6a1.5 1.5 0 0 0 1.3-2.25L13.3 4.6a1.5 1.5 0 0 0-2.6 0z"/>' +
299
+ '<path d="M12 9.9v4.3"/>' +
300
+ '<circle cx="12" cy="17.4" r="0.9" fill="currentColor" stroke="none"/>',
301
+ progress: '<path d="M20.5 12a8.5 8.5 0 1 1-8.5-8.5"/>',
302
+ };
303
+
304
+ const SVG_CACHE = new Map<string, string>();
305
+
306
+ /**
307
+ * Inline SVG markup for an action key, or `''` when nothing is drawn for it.
308
+ *
309
+ * The empty string is a contract, not a failure: the caller renders nothing
310
+ * (or falls back to the `icon` string an embedder supplied) rather than a
311
+ * placeholder box, so an unknown key costs a gap and never a broken glyph.
312
+ */
313
+ export function actionIconSvg(key: string): string {
314
+ if (!key) return '';
315
+ const cached = SVG_CACHE.get(key);
316
+ if (cached !== undefined) return cached;
317
+ const glyph = GLYPHS[key];
318
+ const svg = glyph
319
+ ? '<svg class="fe-aicon" viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
320
+ 'stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" ' +
321
+ `aria-hidden="true" focusable="false">${glyph}</svg>`
322
+ : '';
323
+ SVG_CACHE.set(key, svg);
324
+ return svg;
325
+ }
326
+
327
+ /** Every key this module draws — handy for tests and for a gallery page. */
328
+ export function actionIconKeys(): string[] {
329
+ return Object.keys(GLYPHS);
330
+ }