@brftech/filex-core 0.42.1 → 0.43.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 +14 -2
- package/dist/ArchiveViewer-BqU761Wk.js +149 -0
- package/dist/ArchiveViewer-BqU761Wk.js.map +1 -0
- package/dist/CsvViewer-ByHVK5ga.js +172 -0
- package/dist/CsvViewer-ByHVK5ga.js.map +1 -0
- package/dist/{DrawioViewer-BuhZOAx6.js → DrawioViewer-Bgrj8-zG.js} +45 -44
- package/dist/DrawioViewer-Bgrj8-zG.js.map +1 -0
- package/dist/{EpubViewer-DS3mb3k8.js → EpubViewer-DJwUKlHW.js} +50 -50
- package/dist/EpubViewer-DJwUKlHW.js.map +1 -0
- package/dist/IpynbViewer-Bz0eJ4NS.js +184 -0
- package/dist/IpynbViewer-Bz0eJ4NS.js.map +1 -0
- package/dist/MermaidViewer-CEDTD_pp.js +136 -0
- package/dist/MermaidViewer-CEDTD_pp.js.map +1 -0
- package/dist/PsdViewer-CZcxUpZO.js +121 -0
- package/dist/PsdViewer-CZcxUpZO.js.map +1 -0
- package/dist/TiffViewer-BwI62CbQ.js +141 -0
- package/dist/TiffViewer-BwI62CbQ.js.map +1 -0
- package/dist/{Viewer3D-CIrOn63I.js → Viewer3D-DoeZcuMm.js} +16 -16
- package/dist/Viewer3D-DoeZcuMm.js.map +1 -0
- package/dist/filex-core.js +475 -219
- package/dist/filex-core.umd.cjs +165 -108
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/fonts/caveat-latin-ext.woff2 +0 -0
- package/dist/fonts/caveat-latin.woff2 +0 -0
- package/dist/fonts/dancing-script-latin-ext.woff2 +0 -0
- package/dist/fonts/dancing-script-latin.woff2 +0 -0
- package/dist/fonts/homemade-apple-latin.woff2 +0 -0
- package/dist/fonts/source-serif-4-latin-ext.woff2 +0 -0
- package/dist/fonts/source-serif-4-latin.woff2 +0 -0
- package/dist/index-DuWnGuv5.js +35901 -0
- package/dist/index-DuWnGuv5.js.map +1 -0
- package/dist/index.d.ts +5241 -553
- package/dist/pdf-CaPYsOUo.js +21302 -0
- package/dist/pdf-CaPYsOUo.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +1207 -343
- package/src/assets/fonts/LICENSE-Apache-2.0.txt +202 -0
- package/src/assets/fonts/LICENSE-OFL-1.1.txt +93 -0
- package/src/assets/fonts/README.md +58 -0
- package/src/assets/fonts/caveat-latin-ext.woff2 +0 -0
- package/src/assets/fonts/caveat-latin.woff2 +0 -0
- package/src/assets/fonts/dancing-script-latin-ext.woff2 +0 -0
- package/src/assets/fonts/dancing-script-latin.woff2 +0 -0
- package/src/assets/fonts/homemade-apple-latin.woff2 +0 -0
- package/src/assets/fonts/source-serif-4-latin-ext.woff2 +0 -0
- package/src/assets/fonts/source-serif-4-latin.woff2 +0 -0
- package/src/components/AdvancedSearch.vue +49 -0
- package/src/components/Breadcrumb.vue +5 -5
- package/src/components/ChoiceButtons.vue +152 -0
- package/src/components/CommandPalette.vue +5 -5
- package/src/components/ConnectionGuideView.vue +1 -1
- package/src/components/ConnectionNotice.vue +59 -0
- package/src/components/ConnectionsPanel.vue +30 -568
- package/src/components/ContextMenu.vue +31 -5
- package/src/components/DataTable.vue +1331 -0
- package/src/components/FilePane.vue +3 -0
- package/src/components/FilterBar.vue +12 -5
- package/src/components/GalleryView.vue +50 -2
- package/src/components/GridView.vue +51 -3
- package/src/components/HomeView.vue +18 -6
- package/src/components/InspectorPanel.vue +134 -17
- package/src/components/ListView.vue +366 -900
- package/src/components/LogoMark.vue +57 -0
- package/src/components/NFSExportsPanel.vue +76 -28
- package/src/components/OnboardingTour.vue +19 -7
- package/src/components/OperationsCenter.vue +77 -1
- package/src/components/PendingOpsTray.vue +59 -5
- package/src/components/PresenceBar.vue +7 -7
- package/src/components/ProductVersion.vue +31 -0
- package/src/components/QuickLook.vue +10 -14
- package/src/components/RecentlyOpened.vue +47 -17
- package/src/components/RecoveryKeyModal.vue +1 -1
- package/src/components/RowActions.vue +134 -0
- package/src/components/S3KeysPanel.vue +98 -56
- package/src/components/SSHKeysPanel.vue +79 -26
- package/src/components/SideNav.vue +260 -96
- package/src/components/StarButton.vue +1 -0
- package/src/components/StorageFields.vue +74 -6
- package/src/components/StorageTags.vue +62 -0
- package/src/components/TabBar.vue +1 -1
- package/src/components/TagKindIcon.vue +56 -0
- package/src/components/TagPicker.vue +226 -40
- package/src/components/ThemePalette.vue +4 -4
- package/src/components/TimeZonePicker.vue +29 -10
- package/src/components/TokensPanel.vue +82 -27
- package/src/components/Toolbar.vue +28 -6
- package/src/components/plugin/PluginConfirmModal.vue +54 -0
- package/src/components/plugin/PluginInspectorSection.vue +149 -0
- package/src/components/plugin/PluginPageView.vue +339 -0
- package/src/components/plugin/PluginViewModal.vue +159 -0
- package/src/components/plugin/SurfaceConversation.vue +76 -0
- package/src/components/plugin/SurfaceFooterButtons.vue +73 -0
- package/src/components/plugin/SurfaceRenderer.vue +299 -0
- package/src/components/plugin/SurfaceSections.vue +91 -0
- package/src/components/plugin/nodes/PdfFieldEditor.vue +509 -0
- package/src/components/plugin/nodes/SurfaceFileChooser.vue +94 -0
- package/src/components/plugin/nodes/SurfaceForm.vue +82 -0
- package/src/components/plugin/nodes/SurfaceList.vue +127 -0
- package/src/components/plugin/nodes/SurfacePdfFields.vue +1285 -0
- package/src/components/plugin/nodes/SurfacePeoplePicker.vue +213 -0
- package/src/components/plugin/nodes/SurfacePinInput.vue +57 -0
- package/src/components/plugin/nodes/SurfacePreview.vue +119 -0
- package/src/components/plugin/nodes/SurfaceProgress.vue +48 -0
- package/src/components/plugin/nodes/SurfaceSignaturePad.vue +472 -0
- package/src/components/plugin/nodes/SurfaceSteps.vue +53 -0
- package/src/components/public/PublicLanguagePicker.vue +58 -0
- package/src/components/public/PublicLinkPage.vue +286 -0
- package/src/components/public/PublicLinkPreview.vue +85 -0
- package/src/components/public/PublicPinGate.vue +108 -0
- package/src/components/public/PublicRequestBody.vue +204 -0
- package/src/components/public/PublicShareBody.vue +188 -0
- package/src/components/public/PublicShell.vue +332 -0
- package/src/composables/useConnections.ts +8 -2
- package/src/composables/useFileApi.ts +183 -44
- package/src/composables/useLocale.ts +277 -41
- package/src/composables/useNFSExports.ts +8 -10
- package/src/composables/useOperations.ts +44 -3
- package/src/composables/usePendingOps.ts +59 -8
- package/src/composables/usePluginActions.ts +121 -0
- package/src/composables/usePluginSurface.ts +223 -0
- package/src/composables/usePublicBranding.ts +172 -0
- package/src/composables/usePublicLink.ts +573 -0
- package/src/composables/usePublicPage.ts +61 -0
- package/src/composables/useS3Keys.ts +17 -12
- package/src/composables/useSSHKeys.ts +8 -10
- package/src/composables/useTokens.ts +8 -12
- package/src/composables/useUploadChunked.ts +18 -8
- package/src/composables/useViewerFetch.ts +6 -3
- package/src/index.ts +404 -8
- package/src/lib/actionIcons.ts +46 -1
- package/src/lib/advSearch.ts +14 -7
- package/src/lib/appLock.ts +130 -0
- package/src/lib/connection.ts +110 -0
- package/src/lib/destinationTree.ts +19 -3
- package/src/lib/direction.ts +353 -0
- package/src/lib/dragGhost.ts +6 -0
- package/src/lib/dropLimits.ts +69 -0
- package/src/lib/errorWords.ts +394 -0
- package/src/lib/externalReach.ts +5 -1
- package/src/lib/fileFilters.ts +7 -2
- package/src/lib/fileIcons.ts +4 -0
- package/src/lib/internalPaths.ts +77 -0
- package/src/lib/listInput.ts +64 -0
- package/src/lib/listing.ts +58 -23
- package/src/lib/modalStack.ts +26 -0
- package/src/lib/nodeRow.ts +121 -0
- package/src/lib/pdfFieldRules.ts +126 -0
- package/src/lib/pdfFields.ts +589 -0
- package/src/lib/pdfFieldsGeom.ts +203 -0
- package/src/lib/pdfjsLoader.ts +51 -0
- package/src/lib/personName.ts +47 -0
- package/src/lib/pluginApplies.ts +123 -0
- package/src/lib/pluginLabel.ts +72 -0
- package/src/lib/pluginMenu.ts +253 -0
- package/src/lib/pluginPage.ts +58 -0
- package/src/lib/plural.ts +181 -0
- package/src/lib/popupLayer.ts +103 -0
- package/src/lib/prefs.ts +563 -0
- package/src/lib/productVersion.ts +35 -0
- package/src/lib/publicLayout.ts +39 -0
- package/src/lib/serviceGate.ts +104 -0
- package/src/lib/shareTtl.ts +25 -11
- package/src/lib/signFonts.ts +85 -0
- package/src/lib/signaturePad.ts +111 -0
- package/src/lib/sortOrder.ts +100 -38
- package/src/lib/star.ts +7 -1
- package/src/lib/surfaceCell.ts +41 -0
- package/src/lib/surfaceConditions.ts +156 -0
- package/src/lib/surfaceOpen.ts +106 -0
- package/src/lib/surfaceSteps.ts +68 -0
- package/src/lib/surfaceValues.ts +191 -0
- package/src/lib/symlink.ts +150 -0
- package/src/lib/tableColumns.ts +493 -0
- package/src/lib/tableEnv.ts +34 -0
- package/src/lib/tablePrefs.ts +84 -0
- package/src/lib/tags.ts +121 -12
- package/src/lib/themes.ts +286 -18
- package/src/lib/tour.ts +152 -0
- package/src/lib/trashEmpty.ts +111 -0
- package/src/lib/trashTimeLeft.ts +23 -0
- package/src/lib/uiLocales.ts +460 -0
- package/src/lib/viewPrefs.ts +696 -660
- package/src/lib/viewPrefsHttp.ts +78 -0
- package/src/locales/en.ts +522 -119
- package/src/locales/resolve.ts +17 -8
- package/src/locales/tr.ts +539 -155
- package/src/modals/ConvertModal.vue +62 -11
- package/src/modals/DestinationPickerModal.vue +59 -16
- package/src/modals/Modal.vue +113 -23
- package/src/modals/NewDocumentModal.vue +16 -6
- package/src/modals/NewFolderModal.vue +8 -0
- package/src/modals/PermissionsModal.vue +272 -163
- package/src/modals/PreviewModal.vue +119 -20
- package/src/modals/RenameModal.vue +37 -1
- package/src/styles/base.css +2895 -241
- package/src/styles/sign-fonts.css +118 -0
- package/src/styles/variables.css +60 -0
- package/src/types/Connections.ts +17 -0
- package/src/types/ExplorerConfig.ts +117 -1
- package/src/types/FileNode.ts +58 -0
- package/src/types/Plugins.ts +494 -0
- package/src/types/Public.ts +212 -0
- package/src/viewers/ArchiveViewer.vue +93 -48
- package/src/viewers/CsvViewer.vue +99 -53
- package/src/viewers/DrawioViewer.vue +35 -9
- package/src/viewers/EpubViewer.vue +7 -8
- package/src/viewers/IpynbViewer.vue +3 -1
- package/src/viewers/MermaidViewer.vue +4 -5
- package/src/viewers/PdfViewer.vue +8 -17
- package/src/viewers/PsdViewer.vue +2 -4
- package/src/viewers/TiffViewer.vue +4 -5
- package/src/viewers/Viewer3D.vue +1 -1
- package/LICENSE +0 -21
- package/dist/ArchiveViewer-CLNevfPE.js +0 -124
- package/dist/ArchiveViewer-CLNevfPE.js.map +0 -1
- package/dist/CsvViewer-K8XA3z7N.js +0 -141
- package/dist/CsvViewer-K8XA3z7N.js.map +0 -1
- package/dist/DrawioViewer-BuhZOAx6.js.map +0 -1
- package/dist/EpubViewer-DS3mb3k8.js.map +0 -1
- package/dist/IpynbViewer-BTk7p4Dn.js +0 -184
- package/dist/IpynbViewer-BTk7p4Dn.js.map +0 -1
- package/dist/MermaidViewer-DqucH_io.js +0 -137
- package/dist/MermaidViewer-DqucH_io.js.map +0 -1
- package/dist/PsdViewer-CwiVENmq.js +0 -122
- package/dist/PsdViewer-CwiVENmq.js.map +0 -1
- package/dist/TiffViewer-CpewSVmQ.js +0 -142
- package/dist/TiffViewer-CpewSVmQ.js.map +0 -1
- package/dist/Viewer3D-CIrOn63I.js.map +0 -1
- package/dist/index-Dx0IbBcI.js +0 -26831
- package/dist/index-Dx0IbBcI.js.map +0 -1
- package/dist/useViewerFetch-czqbd2Lj.js +0 -25
- package/dist/useViewerFetch-czqbd2Lj.js.map +0 -1
|
@@ -30,6 +30,8 @@ import type { ExplorerConfig, LocaleCode } from '../types/ExplorerConfig';
|
|
|
30
30
|
import type { ApiToken } from '../types/Tokens';
|
|
31
31
|
import { useLocale } from '../composables/useLocale';
|
|
32
32
|
import { useTokens } from '../composables/useTokens';
|
|
33
|
+
import DataTable, { type DataColumn } from './DataTable.vue';
|
|
34
|
+
import type { ContextAction } from './ContextMenu.vue';
|
|
33
35
|
import { resolveLocale } from '../locales/resolve';
|
|
34
36
|
|
|
35
37
|
const props = defineProps<{
|
|
@@ -89,6 +91,14 @@ const scopeState = ref<Record<string, boolean>>({
|
|
|
89
91
|
const rootPath = ref('');
|
|
90
92
|
const expiresInDays = ref<number | null>(null);
|
|
91
93
|
|
|
94
|
+
/**
|
|
95
|
+
* ⚠⚠ Nothing ticked is not a request (owner's decision, v0.43.0): no door
|
|
96
|
+
* mints a token without an explicit list, and this form used to paper over
|
|
97
|
+
* an empty one with `|| 'read'` — a silent default, the very thing the rule
|
|
98
|
+
* removes. The button waits for a tick and says why.
|
|
99
|
+
*/
|
|
100
|
+
const noScope = computed(() => !FULL_SCOPES.some((s) => scopeState.value[s]));
|
|
101
|
+
|
|
92
102
|
function buildScopes(): string {
|
|
93
103
|
const parts = FULL_SCOPES.filter((s) => scopeState.value[s]) as string[];
|
|
94
104
|
const root = rootPath.value.trim();
|
|
@@ -131,9 +141,8 @@ async function mint(): Promise<void> {
|
|
|
131
141
|
copied.value = false;
|
|
132
142
|
try {
|
|
133
143
|
if (props.full) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const scopes = buildScopes() || 'read';
|
|
144
|
+
if (noScope.value) return;
|
|
145
|
+
const scopes = buildScopes();
|
|
137
146
|
await create({
|
|
138
147
|
label: label.value.trim() || defaultLabel(),
|
|
139
148
|
scopes,
|
|
@@ -160,6 +169,50 @@ async function mint(): Promise<void> {
|
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
|
|
172
|
+
/** The list's columns — the product's one table (DataTable). */
|
|
173
|
+
const columns = computed<DataColumn<ApiToken>[]>(() => [
|
|
174
|
+
{
|
|
175
|
+
id: 'label',
|
|
176
|
+
label: t('conn.tokens.col.label'),
|
|
177
|
+
sortable: true,
|
|
178
|
+
width: 200,
|
|
179
|
+
format: (row) => row.label || '—',
|
|
180
|
+
},
|
|
181
|
+
{ id: 'scopes', label: t('conn.tokens.col.scopes'), sortable: true, width: 200 },
|
|
182
|
+
{
|
|
183
|
+
id: 'used',
|
|
184
|
+
label: t('conn.tokens.col.used'),
|
|
185
|
+
sortable: true,
|
|
186
|
+
sortDir: 'desc',
|
|
187
|
+
width: 140,
|
|
188
|
+
format: usedLabel,
|
|
189
|
+
sortValue: (row) => (row.last_used_at ? new Date(row.last_used_at).getTime() : null),
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
/** The row's one verb, behind its one `Actions` control. ⚠ It keeps the
|
|
194
|
+
* two-step confirmation it had as a loose button: the first pick arms it and
|
|
195
|
+
* the label becomes "Confirm", the second pick revokes the token — which
|
|
196
|
+
* also ends any session already open on it. */
|
|
197
|
+
function rowActions(row: ApiToken): ContextAction[] {
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
key: 'revoke',
|
|
201
|
+
label: confirming.value === row.id ? t('conn.tokens.confirm') : t('conn.tokens.revoke'),
|
|
202
|
+
icon: 'delete',
|
|
203
|
+
danger: true,
|
|
204
|
+
/* The whole control used to be `:disabled="busy"`; the table draws the
|
|
205
|
+
control, so the verb carries it — RowActions greys a button with
|
|
206
|
+
nothing usable behind it. */
|
|
207
|
+
disabled: busy.value,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function onRowAction(key: string, row: ApiToken) {
|
|
213
|
+
if (key === 'revoke') void revoke(row);
|
|
214
|
+
}
|
|
215
|
+
|
|
163
216
|
async function revoke(row: ApiToken): Promise<void> {
|
|
164
217
|
if (confirming.value !== row.id) {
|
|
165
218
|
confirming.value = row.id;
|
|
@@ -239,6 +292,9 @@ function usedLabel(row: ApiToken): string {
|
|
|
239
292
|
<input v-model="scopeState[s]" type="checkbox" :data-testid="`token-scope-${s}`" />
|
|
240
293
|
<span>{{ s }}</span>
|
|
241
294
|
</label>
|
|
295
|
+
<p v-if="noScope" class="fe-s3keys__hint fe-tokform__required" role="alert" data-testid="token-scopes-required">
|
|
296
|
+
{{ t('conn.tokens.scopesRequired') }}
|
|
297
|
+
</p>
|
|
242
298
|
</fieldset>
|
|
243
299
|
|
|
244
300
|
<label class="fe-tokform__field">
|
|
@@ -263,7 +319,7 @@ function usedLabel(row: ApiToken): string {
|
|
|
263
319
|
data-testid="token-expiry"
|
|
264
320
|
/>
|
|
265
321
|
</label>
|
|
266
|
-
<button class="fe-s3keys__btn" :disabled="busy" data-testid="token-mint" @click="mint">
|
|
322
|
+
<button class="fe-s3keys__btn" :disabled="busy || noScope" data-testid="token-mint" @click="mint">
|
|
267
323
|
{{ t('conn.tokens.mint') }}
|
|
268
324
|
</button>
|
|
269
325
|
</div>
|
|
@@ -287,30 +343,29 @@ function usedLabel(row: ApiToken): string {
|
|
|
287
343
|
</button>
|
|
288
344
|
</div>
|
|
289
345
|
|
|
346
|
+
<!-- ⚠ THE table (DataTable — the explorer's own), not a table of its own.
|
|
347
|
+
This panel is reachable from the admin panel's Connections menu and
|
|
348
|
+
from the explorer, so its list resizes, sorts, hides and moves columns
|
|
349
|
+
and remembers that on the account (`conn.tokens`), like every other
|
|
350
|
+
table. The raised ground is the panel's own (`.fe-s3keys` sets
|
|
351
|
+
`--tbl-bg` once). -->
|
|
290
352
|
<p v-if="loading" class="fe-s3keys__muted">…</p>
|
|
291
|
-
<
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
{{ confirming === row.id ? t('conn.tokens.confirm') : t('conn.tokens.revoke') }}
|
|
308
|
-
</button>
|
|
309
|
-
</td>
|
|
310
|
-
</tr>
|
|
311
|
-
</tbody>
|
|
312
|
-
</table>
|
|
313
|
-
<p v-else-if="canMint" class="fe-s3keys__muted">{{ t('conn.tokens.empty') }}</p>
|
|
353
|
+
<DataTable
|
|
354
|
+
v-else
|
|
355
|
+
table-id="conn.tokens"
|
|
356
|
+
:columns="columns"
|
|
357
|
+
:rows="tokens"
|
|
358
|
+
row-key="id"
|
|
359
|
+
:locale="locale"
|
|
360
|
+
:empty="t('conn.tokens.empty')"
|
|
361
|
+
:row-actions="rowActions"
|
|
362
|
+
:row-actions-test-id="(row: ApiToken) => `api-token-actions-${row.id}`"
|
|
363
|
+
@row-action="(key: string, row: ApiToken) => onRowAction(key, row)"
|
|
364
|
+
>
|
|
365
|
+
<template #cell-scopes="{ row }">
|
|
366
|
+
<code class="tbl-mono">{{ row.scopes }}</code>
|
|
367
|
+
</template>
|
|
368
|
+
</DataTable>
|
|
314
369
|
|
|
315
370
|
<!-- ⚠ Said next to the button rather than in a document nobody opens: a
|
|
316
371
|
revoked token stops a session that is already open, not only the next
|
|
@@ -20,6 +20,7 @@ import ContextMenu, { type ContextAction } from './ContextMenu.vue';
|
|
|
20
20
|
* `.fe-subhead__actions`), so the header and the crumb row can no longer end
|
|
21
21
|
* up offering different view modes — which is what two mount points cost. */
|
|
22
22
|
import { useLocale } from '../composables/useLocale';
|
|
23
|
+
import { inlineEndX } from '../lib/direction';
|
|
23
24
|
import { actionIconSvg } from '../lib/actionIcons';
|
|
24
25
|
import { eventMatchesShortcut, menuShortcutHint, shortcutHint } from '../composables/useKeyboardShortcuts';
|
|
25
26
|
|
|
@@ -253,7 +254,7 @@ function toggleDensity() {
|
|
|
253
254
|
emit('update:density', density.value);
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
const { t } = useLocale(() => props.locale);
|
|
257
|
+
const { t, dir } = useLocale(() => props.locale);
|
|
257
258
|
|
|
258
259
|
const searchEl = ref<HTMLInputElement | null>(null);
|
|
259
260
|
const localSearch = ref(props.searchQuery);
|
|
@@ -333,11 +334,30 @@ const hasSelection = computed(() => mode.value !== 'none');
|
|
|
333
334
|
const barSplit = computed(() => {
|
|
334
335
|
const icons: ContextAction[] = [];
|
|
335
336
|
const rest: ContextAction[] = [];
|
|
336
|
-
|
|
337
|
+
/* ⚠ Walks the parent's list WITH its dividers, not `toolbarItems`. The "⋯"
|
|
338
|
+
* is a dropdown drawn by the same ContextMenu as the right click, and the
|
|
339
|
+
* dividers are what group it: the owner asked for a line between every
|
|
340
|
+
* app's actions (lib/pluginMenu → one `sep-plugin:*` per app), and a "⋯"
|
|
341
|
+
* built from the divider-free list ran Open, Rename, Tags and two apps'
|
|
342
|
+
* verbs together as one undivided column while the right click beside it
|
|
343
|
+
* drew the lines. An icon never takes a divider; ContextMenu collapses the
|
|
344
|
+
* ones that end up leading, trailing or doubled once the icons are pulled
|
|
345
|
+
* out. `restCount` is the number of REAL entries — a "⋯" whose menu is
|
|
346
|
+
* nothing but dividers would open an empty box. */
|
|
347
|
+
let restCount = 0;
|
|
348
|
+
for (const a of props.actions) {
|
|
349
|
+
if (a.hidden) continue;
|
|
350
|
+
if (a.divider) {
|
|
351
|
+
rest.push(a);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
337
354
|
if (BAR_ICON_KEYS.has(a.key) && iconFor(a)) icons.push(a);
|
|
338
|
-
else
|
|
355
|
+
else {
|
|
356
|
+
rest.push(a);
|
|
357
|
+
restCount++;
|
|
358
|
+
}
|
|
339
359
|
}
|
|
340
|
-
return { icons, rest };
|
|
360
|
+
return { icons, rest: restCount ? rest : [], restCount };
|
|
341
361
|
});
|
|
342
362
|
|
|
343
363
|
/* ── folding (kept from ui-fix, moved onto the bar) ───────────────────
|
|
@@ -412,7 +432,9 @@ watch(
|
|
|
412
432
|
|
|
413
433
|
function openBarMore() {
|
|
414
434
|
const r = selMoreBtnEl.value?.getBoundingClientRect();
|
|
415
|
-
|
|
435
|
+
// ⚠ RTL: from the button's END edge (its left in RTL) — ContextMenu flips it
|
|
436
|
+
// back under the button from there, in either direction.
|
|
437
|
+
selMoreRef.value?.show({ clientX: r ? inlineEndX(r, dir.value) : 0, clientY: r ? r.bottom + 4 : 0 } as MouseEvent, []);
|
|
416
438
|
}
|
|
417
439
|
|
|
418
440
|
/* ── where the bar lands ──────────────────────────────────────────────── */
|
|
@@ -894,7 +916,7 @@ const menuActions = computed<ContextAction[]>(() => {
|
|
|
894
916
|
|
|
895
917
|
function openMore() {
|
|
896
918
|
const r = moreBtnEl.value?.getBoundingClientRect();
|
|
897
|
-
moreRef.value?.show({ clientX: r ? r.
|
|
919
|
+
moreRef.value?.show({ clientX: r ? inlineEndX(r, dir.value) : 0, clientY: r ? r.bottom + 4 : 0 }, []); // ⚠ RTL: END edge, see openBarMore
|
|
898
920
|
}
|
|
899
921
|
|
|
900
922
|
function onMoreSelect(a: ContextAction) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PluginConfirmModal — the question an action's manifest `confirm` text asks
|
|
4
|
+
* before the job is queued. The same small dialog every surface uses
|
|
5
|
+
* (`modals/Modal`), so a plugin's confirmation looks like the trash's.
|
|
6
|
+
*/
|
|
7
|
+
import type { LocaleCode, ThemeMode } from '../../types/ExplorerConfig';
|
|
8
|
+
import { useLocale } from '../../composables/useLocale';
|
|
9
|
+
import Modal from '../../modals/Modal.vue';
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
open: boolean;
|
|
13
|
+
locale: LocaleCode;
|
|
14
|
+
theme?: ThemeMode;
|
|
15
|
+
/** The action's label — the dialog's title. */
|
|
16
|
+
title: string;
|
|
17
|
+
/** The manifest's `confirm` text in the viewer's language. */
|
|
18
|
+
message: string;
|
|
19
|
+
danger?: boolean;
|
|
20
|
+
}>();
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits<{
|
|
23
|
+
(e: 'close'): void;
|
|
24
|
+
(e: 'confirm'): void;
|
|
25
|
+
}>();
|
|
26
|
+
|
|
27
|
+
const { t } = useLocale(() => props.locale);
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<Modal
|
|
32
|
+
:open="open"
|
|
33
|
+
:title="title || t('plugin.confirm.title')"
|
|
34
|
+
size="sm"
|
|
35
|
+
:theme="theme"
|
|
36
|
+
@close="emit('close')"
|
|
37
|
+
>
|
|
38
|
+
<p data-testid="plugin-confirm-message">{{ message }}</p>
|
|
39
|
+
<template #actions>
|
|
40
|
+
<button type="button" class="fe-btn" @click="emit('close')">
|
|
41
|
+
{{ t('plugin.confirm.cancel') }}
|
|
42
|
+
</button>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
class="fe-btn"
|
|
46
|
+
:class="danger ? 'fe-btn--danger' : 'fe-btn--primary'"
|
|
47
|
+
data-testid="plugin-confirm-run"
|
|
48
|
+
@click="emit('confirm')"
|
|
49
|
+
>
|
|
50
|
+
{{ t('plugin.confirm.run') }}
|
|
51
|
+
</button>
|
|
52
|
+
</template>
|
|
53
|
+
</Modal>
|
|
54
|
+
</template>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PluginInspectorSection — one `inspector` view of an app plugin, as a
|
|
4
|
+
* collapsible section of the details panel.
|
|
5
|
+
*
|
|
6
|
+
* Closed, it is a heading and nothing else: no request is made until the
|
|
7
|
+
* person opens it (a panel that loaded every plugin's view for every click
|
|
8
|
+
* would make N calls per selection). Opened, it asks
|
|
9
|
+
* `GET …/views/{plugin}/{view}?path=` once for the selected item and draws
|
|
10
|
+
* the surface inline; the footer buttons post events through the same
|
|
11
|
+
* conversation the modal holds (`usePluginSurface`), and a `{op}` answer
|
|
12
|
+
* goes up for the host to register in the tray, exactly as M1's modal does.
|
|
13
|
+
* A new selection resets the section to closed-and-unloaded.
|
|
14
|
+
*/
|
|
15
|
+
import { computed, ref, watch } from 'vue';
|
|
16
|
+
import type { LocaleCode, ThemeMode } from '../../types/ExplorerConfig';
|
|
17
|
+
import type { FileApi } from '../../composables/useFileApi';
|
|
18
|
+
import type { PluginViewRow, SurfaceOpenRequest } from '../../types/Plugins';
|
|
19
|
+
import { useLocale } from '../../composables/useLocale';
|
|
20
|
+
import { usePluginSurface } from '../../composables/usePluginSurface';
|
|
21
|
+
import { labelOf } from '../../lib/pluginLabel';
|
|
22
|
+
import SurfaceConversation from './SurfaceConversation.vue';
|
|
23
|
+
import SurfaceFooterButtons from './SurfaceFooterButtons.vue';
|
|
24
|
+
|
|
25
|
+
const props = defineProps<{
|
|
26
|
+
api: FileApi;
|
|
27
|
+
view: PluginViewRow;
|
|
28
|
+
/** Adapter-qualified path of the selected item. */
|
|
29
|
+
path: string;
|
|
30
|
+
locale: LocaleCode;
|
|
31
|
+
theme?: ThemeMode;
|
|
32
|
+
storages?: string[];
|
|
33
|
+
}>();
|
|
34
|
+
|
|
35
|
+
const emit = defineEmits<{
|
|
36
|
+
(e: 'op', op: Record<string, unknown>): void;
|
|
37
|
+
(e: 'toast', message: string): void;
|
|
38
|
+
/** v3 §3.0 — the answer says "go to this file"; the explorer navigates. */
|
|
39
|
+
(e: 'open', req: SurfaceOpenRequest): void;
|
|
40
|
+
}>();
|
|
41
|
+
|
|
42
|
+
const { t } = useLocale(() => props.locale);
|
|
43
|
+
|
|
44
|
+
const expanded = ref(false);
|
|
45
|
+
const loading = ref(false);
|
|
46
|
+
const loadError = ref('');
|
|
47
|
+
|
|
48
|
+
const conv = usePluginSurface(
|
|
49
|
+
{
|
|
50
|
+
api: props.api,
|
|
51
|
+
plugin: props.view.plugin,
|
|
52
|
+
view: props.view.id,
|
|
53
|
+
path: () => props.path,
|
|
54
|
+
locale: () => props.locale,
|
|
55
|
+
errorText: () => t('plugin.view.error'),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
onOp: (op) => emit('op', op),
|
|
59
|
+
onOpen: (req) => emit('open', req),
|
|
60
|
+
// The conversation ended (a job was queued, or the plugin said `done`):
|
|
61
|
+
// what the section shows next is the view's fresh state.
|
|
62
|
+
onDone: () => void load(),
|
|
63
|
+
onToast: (m) => emit('toast', m),
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const label = computed(() => labelOf(props.view.label, props.locale) || props.view.id);
|
|
68
|
+
const sectionId = computed(() => `fe-plugin-insp-${props.view.plugin}-${props.view.id}`);
|
|
69
|
+
|
|
70
|
+
async function load() {
|
|
71
|
+
loading.value = true;
|
|
72
|
+
loadError.value = '';
|
|
73
|
+
try {
|
|
74
|
+
const res = await props.api.pluginView(props.view.plugin, props.view.id, props.path);
|
|
75
|
+
conv.setSurface(res?.surface ?? null);
|
|
76
|
+
if (!res?.surface) loadError.value = t('plugin.view.error');
|
|
77
|
+
} catch (e) {
|
|
78
|
+
conv.setSurface(null);
|
|
79
|
+
// ⚠ An app's own error text never met the catalogue: isolate its
|
|
80
|
+
// machine runs for the reader's direction (lib/direction).
|
|
81
|
+
loadError.value = t.foreign?.(String((e as Error)?.message ?? e)) || t('plugin.view.error');
|
|
82
|
+
} finally {
|
|
83
|
+
loading.value = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function toggle() {
|
|
88
|
+
expanded.value = !expanded.value;
|
|
89
|
+
if (expanded.value && !conv.current.value && !loading.value) void load();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
watch(
|
|
93
|
+
() => props.path,
|
|
94
|
+
() => {
|
|
95
|
+
expanded.value = false;
|
|
96
|
+
loadError.value = '';
|
|
97
|
+
conv.setSurface(null);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<template>
|
|
103
|
+
<section class="fe-inspector__section fe-pinsp" :data-testid="`inspector-plugin-${view.plugin}-${view.id}`">
|
|
104
|
+
<h3 class="fe-inspector__heading fe-pinsp__heading">
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
class="fe-pinsp__toggle"
|
|
108
|
+
:aria-expanded="expanded"
|
|
109
|
+
:aria-controls="sectionId"
|
|
110
|
+
:data-testid="`inspector-plugin-toggle-${view.plugin}-${view.id}`"
|
|
111
|
+
@click="toggle"
|
|
112
|
+
>
|
|
113
|
+
<svg
|
|
114
|
+
class="fe-ficon fe-pinsp__chev"
|
|
115
|
+
:class="{ 'is-open': expanded }"
|
|
116
|
+
viewBox="0 0 24 24"
|
|
117
|
+
fill="none"
|
|
118
|
+
stroke="currentColor"
|
|
119
|
+
stroke-width="2"
|
|
120
|
+
stroke-linecap="round"
|
|
121
|
+
stroke-linejoin="round"
|
|
122
|
+
aria-hidden="true"
|
|
123
|
+
focusable="false"
|
|
124
|
+
>
|
|
125
|
+
<path d="M9 5l7 7-7 7" />
|
|
126
|
+
</svg>
|
|
127
|
+
<span>{{ label }}</span>
|
|
128
|
+
</button>
|
|
129
|
+
</h3>
|
|
130
|
+
<div v-if="expanded" :id="sectionId" class="fe-pinsp__body">
|
|
131
|
+
<p v-if="loading" class="fe-inspector__empty">{{ t('plugin.view.loading') }}</p>
|
|
132
|
+
<p v-else-if="loadError" class="fe-surface__error" role="alert">{{ loadError }}</p>
|
|
133
|
+
<template v-else-if="conv.current.value">
|
|
134
|
+
<SurfaceConversation
|
|
135
|
+
:conv="conv"
|
|
136
|
+
:locale="locale"
|
|
137
|
+
:theme="theme"
|
|
138
|
+
:api="api"
|
|
139
|
+
:plugin="view.plugin"
|
|
140
|
+
:storages="storages"
|
|
141
|
+
:start-at="path"
|
|
142
|
+
/>
|
|
143
|
+
<div v-if="conv.footer.value.length" class="fe-pinsp__actions">
|
|
144
|
+
<SurfaceFooterButtons :conv="conv" :locale="locale" testid-prefix="inspector-plugin" small />
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
|
147
|
+
</div>
|
|
148
|
+
</section>
|
|
149
|
+
</template>
|