@brftech/filex-core 0.39.1 → 0.41.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/README.md +32 -25
- package/dist/ArchiveViewer-BNqukFg8.js +124 -0
- package/dist/ArchiveViewer-BNqukFg8.js.map +1 -0
- package/dist/CsvViewer-ii_-MgmG.js +141 -0
- package/dist/CsvViewer-ii_-MgmG.js.map +1 -0
- package/dist/{DrawioViewer-BNALOB04.js → DrawioViewer-B2tuu4rX.js} +48 -42
- package/dist/DrawioViewer-B2tuu4rX.js.map +1 -0
- package/dist/EpubViewer-p4B8iiWb.js +163 -0
- package/dist/EpubViewer-p4B8iiWb.js.map +1 -0
- package/dist/IpynbViewer-D_qYRJhu.js +184 -0
- package/dist/IpynbViewer-D_qYRJhu.js.map +1 -0
- package/dist/MermaidViewer-3ZkfNM8v.js +137 -0
- package/dist/MermaidViewer-3ZkfNM8v.js.map +1 -0
- package/dist/PsdViewer-CuV-OPSd.js +122 -0
- package/dist/PsdViewer-CuV-OPSd.js.map +1 -0
- package/dist/TiffViewer-BLeoF4Be.js +142 -0
- package/dist/TiffViewer-BLeoF4Be.js.map +1 -0
- package/dist/Viewer3D-DHhhU76E.js +75 -0
- package/dist/Viewer3D-DHhhU76E.js.map +1 -0
- package/dist/filex-core.js +217 -20077
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +61 -62
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-BraG7Cz4.js +26664 -0
- package/dist/index-BraG7Cz4.js.map +1 -0
- package/dist/index.d.ts +4276 -851
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +2170 -492
- package/src/components/AdvancedSearch.vue +870 -0
- package/src/components/Breadcrumb.vue +191 -19
- package/src/components/CommandPalette.vue +66 -22
- package/src/components/ConnectionsPanel.vue +6 -2
- package/src/components/ContextMenu.vue +57 -2
- package/src/components/EncryptedFolderModal.vue +31 -25
- package/src/components/FilePane.vue +939 -0
- package/src/components/FilterBar.vue +568 -72
- package/src/components/GalleryView.vue +162 -30
- package/src/components/GridView.vue +322 -58
- package/src/components/HomeView.vue +331 -0
- package/src/components/InspectorPanel.vue +296 -53
- package/src/components/ListView.vue +1158 -167
- package/src/components/NFSExportsPanel.vue +7 -4
- package/src/components/OnboardingTour.vue +156 -33
- package/src/components/QuickLook.vue +34 -5
- package/src/components/RecentlyOpened.vue +17 -2
- package/src/components/S3KeysPanel.vue +7 -4
- package/src/components/SSHKeysPanel.vue +7 -4
- package/src/components/ShortcutSettings.vue +43 -2
- package/src/components/ShortcutsHelp.vue +4 -1
- package/src/components/SideNav.vue +206 -140
- package/src/components/StorageFields.vue +19 -1
- package/src/components/TabBar.vue +87 -1
- package/src/components/TagPicker.vue +77 -6
- package/src/components/ThemeGallery.vue +13 -198
- package/src/components/ThemePalette.vue +244 -0
- package/src/components/TimeZoneDialog.vue +127 -0
- package/src/components/TimeZonePicker.vue +696 -0
- package/src/components/TokensPanel.vue +16 -4
- package/src/components/Toolbar.vue +965 -469
- package/src/components/ViewSwitcher.vue +9 -4
- package/src/composables/useConnections.ts +35 -4
- package/src/composables/useExplorerTimeZone.ts +85 -0
- package/src/composables/useFileApi.ts +53 -4
- package/src/composables/useKeyboardShortcuts.ts +122 -0
- package/src/composables/useLocale.ts +365 -25
- package/src/index.ts +182 -3
- package/src/lib/actionIcons.ts +330 -0
- package/src/lib/advSearch.ts +162 -0
- package/src/lib/dateGroups.ts +197 -0
- package/src/lib/destinationTree.ts +235 -0
- package/src/lib/downloadSelection.ts +154 -0
- package/src/lib/fileFilters.ts +319 -12
- package/src/lib/fileIcons.ts +299 -5
- package/src/lib/filePreview.ts +605 -0
- package/src/lib/listing.ts +72 -0
- package/src/lib/shareTtl.ts +11 -1
- package/src/lib/sortOrder.ts +459 -0
- package/src/lib/themes.ts +152 -23
- package/src/lib/timezone.ts +456 -0
- package/src/lib/uiProfile.ts +82 -0
- package/src/lib/viewPrefs.ts +1036 -0
- package/src/locales/en.ts +432 -25
- package/src/locales/tr.ts +425 -25
- package/src/modals/ConvertModal.vue +23 -3
- package/src/modals/DestinationPickerModal.vue +341 -0
- package/src/modals/Modal.vue +18 -4
- package/src/modals/NewDocumentModal.vue +525 -0
- package/src/modals/NewFolderModal.vue +15 -9
- package/src/modals/PermissionsModal.vue +543 -473
- package/src/modals/PreviewModal.vue +516 -156
- package/src/styles/base.css +5435 -8
- package/src/styles/variables.css +140 -60
- package/src/types/Connections.ts +9 -0
- package/src/types/ExplorerConfig.ts +144 -39
- package/src/types/FileNode.ts +28 -0
- package/src/viewers/ArchiveViewer.vue +46 -8
- package/src/viewers/CsvViewer.vue +19 -2
- package/src/viewers/DrawioViewer.vue +12 -1
- package/src/viewers/EpubViewer.vue +50 -6
- package/src/viewers/IpynbViewer.vue +19 -2
- package/src/viewers/MermaidViewer.vue +20 -3
- package/src/viewers/PdfViewer.vue +24 -4
- package/src/viewers/PsdViewer.vue +28 -4
- package/src/viewers/TiffViewer.vue +19 -2
- package/src/viewers/Viewer3D.vue +36 -2
- package/dist/ArchiveViewer-Cdfv9LFC.js +0 -99
- package/dist/ArchiveViewer-Cdfv9LFC.js.map +0 -1
- package/dist/CsvViewer-CqWeV8VO.js +0 -131
- package/dist/CsvViewer-CqWeV8VO.js.map +0 -1
- package/dist/DrawioViewer-BNALOB04.js.map +0 -1
- package/dist/EpubViewer-BPmlImG7.js +0 -145
- package/dist/EpubViewer-BPmlImG7.js.map +0 -1
- package/dist/IpynbViewer-CDlQmuCV.js +0 -175
- package/dist/IpynbViewer-CDlQmuCV.js.map +0 -1
- package/dist/MermaidViewer-sfZWdR-d.js +0 -127
- package/dist/MermaidViewer-sfZWdR-d.js.map +0 -1
- package/dist/PsdViewer-DwuySZsb.js +0 -112
- package/dist/PsdViewer-DwuySZsb.js.map +0 -1
- package/dist/TiffViewer-BDQ3d3CQ.js +0 -132
- package/dist/TiffViewer-BDQ3d3CQ.js.map +0 -1
- package/dist/Viewer3D-CJJJgkuv.js +0 -60
- package/dist/Viewer3D-CJJJgkuv.js.map +0 -1
- package/src/components/SecondaryPane.vue +0 -447
- package/src/modals/ShareModal.vue +0 -139
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gorunum:v1-advsearch — the model behind the Advanced search dialog.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the component so the two halves of a search can be read (and
|
|
5
|
+
* tested) side by side, because the honesty of the whole feature lives in the
|
|
6
|
+
* split:
|
|
7
|
+
*
|
|
8
|
+
* SERVER — `text`, `tags`, `excludeTags`, `scope`. These are the ONLY things
|
|
9
|
+
* filex's search endpoints actually read. `GET|POST /api/files/search` takes
|
|
10
|
+
* `q`/`query`, `storage_id`, `limit` and `scope` (name|content|all) and
|
|
11
|
+
* nothing else (handlers/search.go `searchRequest`); the query text may carry
|
|
12
|
+
* `tag:x` / `-tag:x`, which the backend parses out and resolves against the
|
|
13
|
+
* database (docs/SEARCH.md, "Filtering by tag"). The manager's
|
|
14
|
+
* `?action=search&filter=…` speaks the same query language.
|
|
15
|
+
*
|
|
16
|
+
* CLIENT — everything in `filters` (`lib/fileFilters.ts`). There is no date,
|
|
17
|
+
* size, mime or path parameter on either endpoint, so those four narrow the
|
|
18
|
+
* rows that came back. That is the same bargain the filter row already makes
|
|
19
|
+
* and is documented at the top of fileFilters.ts.
|
|
20
|
+
*
|
|
21
|
+
* ⚠ The one thing that is NOT the same bargain: over a folder listing the rows
|
|
22
|
+
* in hand ARE the folder, so a client-side filter is a complete answer. Over a
|
|
23
|
+
* SEARCH result they are the first N hits the ranker returned (50 by default on
|
|
24
|
+
* `/api/files/search`, 250 on the manager's search action). A size range over
|
|
25
|
+
* that is a filter over what came back — true of the rows shown, not a
|
|
26
|
+
* statement about the storage. The dialog prints that rather than implying a
|
|
27
|
+
* full scan; see `advSearchTruncated`.
|
|
28
|
+
*
|
|
29
|
+
* ⚠ Owner USED to be on this list, with the reason "there is no per-node owner
|
|
30
|
+
* on the wire at all". Migration 00038 made ownership a real fact on the row
|
|
31
|
+
* and the listing projection carries `owner_id` / `owner_name` / `owner_self`,
|
|
32
|
+
* so `filters.people` is now a client-side filter over a field the rows
|
|
33
|
+
* ALREADY carry — exactly the bargain the other four make. The rule did not
|
|
34
|
+
* bend; the data arrived.
|
|
35
|
+
*
|
|
36
|
+
* What is still NOT modelled here, because nothing could carry it:
|
|
37
|
+
* - A "paths" scope. `scope` accepts exactly name|content|all
|
|
38
|
+
* (search.ParseScope); paths are matched *within* the name scope and cannot
|
|
39
|
+
* be selected on their own. Measured against the reference build on
|
|
40
|
+
* 2026-09-13: its `scope:"path"` returns the byte-identical result set its
|
|
41
|
+
* `scope:"name"` returns, over every query tried.
|
|
42
|
+
* - "Match whole phrase". The query language has no phrase operator — the
|
|
43
|
+
* only quoting the parser knows keeps a `tag:"two words"` value together
|
|
44
|
+
* (search.splitQuery/unquote), and a quoted free-text token keeps its
|
|
45
|
+
* quotes and is matched with them.
|
|
46
|
+
*/
|
|
47
|
+
import type { DriveFilters, PeopleOption } from './fileFilters';
|
|
48
|
+
import { EMPTY_FILTERS } from './fileFilters';
|
|
49
|
+
|
|
50
|
+
/** Which fields the backend consults. Mirrors `search.ParseScope`. */
|
|
51
|
+
export type AdvScope = 'name' | 'content' | 'all';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* What one live-count run reports back.
|
|
55
|
+
*
|
|
56
|
+
* `capped` is the honest half: true when the server returned as many hits as
|
|
57
|
+
* it was allowed to, so the client-side filters narrowed a window rather than
|
|
58
|
+
* the whole result set and the number is "N of the first page", not "N in the
|
|
59
|
+
* storage". The dialog prints a different sentence for each.
|
|
60
|
+
*/
|
|
61
|
+
export interface AdvCountResult {
|
|
62
|
+
count: number;
|
|
63
|
+
capped: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The per-account People members the counted rows actually contained
|
|
66
|
+
* (`peopleOptions(rows)` minus the three fixed ones), so the Owner select
|
|
67
|
+
* can offer a name without asking for a user directory it must not have.
|
|
68
|
+
*
|
|
69
|
+
* ⚠ Optional. A caller that does not report them — the explorer does not
|
|
70
|
+
* yet — leaves the select with `Anyone` / `You` / `System`, which is three
|
|
71
|
+
* working members, not a broken menu. The field exists so the follow-up is
|
|
72
|
+
* one change in the shell that owns the API client, not a second change in
|
|
73
|
+
* this component.
|
|
74
|
+
*/
|
|
75
|
+
people?: PeopleOption[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AdvSearchRequest {
|
|
79
|
+
/** Free text. Never carries `tag:` tokens — those live in the two arrays. */
|
|
80
|
+
text: string;
|
|
81
|
+
/** ANDed include filters, emitted as `tag:x`. */
|
|
82
|
+
tags: string[];
|
|
83
|
+
/** Exclusions, emitted as `-tag:x`. Applied after the includes. */
|
|
84
|
+
excludeTags: string[];
|
|
85
|
+
scope: AdvScope;
|
|
86
|
+
/** The client-side half. Same model and same predicate as the filter row. */
|
|
87
|
+
filters: DriveFilters;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function emptyAdvSearch(pathBase = ''): AdvSearchRequest {
|
|
91
|
+
return {
|
|
92
|
+
text: '',
|
|
93
|
+
tags: [],
|
|
94
|
+
excludeTags: [],
|
|
95
|
+
scope: 'name',
|
|
96
|
+
filters: { ...EMPTY_FILTERS, pathBase },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A tag value only needs quoting when it contains whitespace — the parser
|
|
101
|
+
* splits on spaces unless a double quote is open (search.splitQuery). */
|
|
102
|
+
function tagToken(tag: string, negated: boolean): string {
|
|
103
|
+
const v = tag.trim();
|
|
104
|
+
if (!v) return '';
|
|
105
|
+
const body = /\s/.test(v) ? `"${v}"` : v;
|
|
106
|
+
return `${negated ? '-' : ''}tag:${body}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The string that actually goes on the wire, as `q` / `filter`.
|
|
111
|
+
*
|
|
112
|
+
* Text first so the free-text half reads the way the user typed it, then the
|
|
113
|
+
* tag filters. A bare `tag:x` with no text is legal and means "list the files
|
|
114
|
+
* carrying that tag, newest first" — the backend says so explicitly, so an
|
|
115
|
+
* empty `text` is not an empty search.
|
|
116
|
+
*/
|
|
117
|
+
export function advQueryString(req: AdvSearchRequest): string {
|
|
118
|
+
const parts: string[] = [];
|
|
119
|
+
const text = req.text.trim();
|
|
120
|
+
if (text) parts.push(text);
|
|
121
|
+
for (const t of req.tags) {
|
|
122
|
+
const tok = tagToken(t, false);
|
|
123
|
+
if (tok) parts.push(tok);
|
|
124
|
+
}
|
|
125
|
+
for (const t of req.excludeTags) {
|
|
126
|
+
const tok = tagToken(t, true);
|
|
127
|
+
if (tok) parts.push(tok);
|
|
128
|
+
}
|
|
129
|
+
return parts.join(' ');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Nothing for the server to answer — no text and no tag filter. */
|
|
133
|
+
export function advSearchEmpty(req: AdvSearchRequest): boolean {
|
|
134
|
+
return advQueryString(req) === '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Split a comma/newline separated tag box into values.
|
|
139
|
+
*
|
|
140
|
+
* Commas rather than spaces, because a tag may contain a space
|
|
141
|
+
* (`quarterly report`) and splitting on whitespace would turn one filter that
|
|
142
|
+
* matches into two that do not — and a tag that does not exist returns the
|
|
143
|
+
* empty set by design, so the mistake would look like "there is nothing here".
|
|
144
|
+
*/
|
|
145
|
+
export function parseTagList(raw: string): string[] {
|
|
146
|
+
return raw
|
|
147
|
+
.split(/[,\n]/)
|
|
148
|
+
.map((s) => s.trim())
|
|
149
|
+
.filter((s) => s !== '');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* True when the hit list came back at the cap, so the client-side filters ran
|
|
154
|
+
* over a window rather than over everything that matches.
|
|
155
|
+
*
|
|
156
|
+
* The dialog and the count line say this out loud. A count that silently
|
|
157
|
+
* describes "the first 50 hits, then filtered" as "N matching items" is the
|
|
158
|
+
* exact shape of a number that looks measured and is not.
|
|
159
|
+
*/
|
|
160
|
+
export function advSearchTruncated(returned: number, limit: number): boolean {
|
|
161
|
+
return returned >= limit;
|
|
162
|
+
}
|