@brftech/filex-core 0.42.2 → 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-CC6rJXeP.js → DrawioViewer-Bgrj8-zG.js} +45 -44
- package/dist/DrawioViewer-Bgrj8-zG.js.map +1 -0
- package/dist/{EpubViewer-CAoTSXTO.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-CdZeK4j4.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 +5227 -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 +1055 -253
- 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 +31 -15
- package/src/components/RecoveryKeyModal.vue +1 -1
- package/src/components/RowActions.vue +134 -0
- package/src/components/S3KeysPanel.vue +98 -62
- package/src/components/SSHKeysPanel.vue +79 -30
- 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 -31
- 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 +18 -3
- 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 +515 -117
- package/src/locales/resolve.ts +17 -8
- package/src/locales/tr.ts +536 -152
- 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 +2890 -260
- 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 +50 -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-BUQWLhwX.js +0 -124
- package/dist/ArchiveViewer-BUQWLhwX.js.map +0 -1
- package/dist/CsvViewer-Bg2_aqzc.js +0 -141
- package/dist/CsvViewer-Bg2_aqzc.js.map +0 -1
- package/dist/DrawioViewer-CC6rJXeP.js.map +0 -1
- package/dist/EpubViewer-CAoTSXTO.js.map +0 -1
- package/dist/IpynbViewer-D0GwGpnE.js +0 -184
- package/dist/IpynbViewer-D0GwGpnE.js.map +0 -1
- package/dist/MermaidViewer-DGwAvRZg.js +0 -137
- package/dist/MermaidViewer-DGwAvRZg.js.map +0 -1
- package/dist/PsdViewer-DhGweA0m.js +0 -122
- package/dist/PsdViewer-DhGweA0m.js.map +0 -1
- package/dist/TiffViewer-D0AurvAh.js +0 -142
- package/dist/TiffViewer-D0AurvAh.js.map +0 -1
- package/dist/Viewer3D-CdZeK4j4.js.map +0 -1
- package/dist/index-BArI-B1M.js +0 -26911
- package/dist/index-BArI-B1M.js.map +0 -1
- package/dist/useViewerFetch-czqbd2Lj.js +0 -25
- package/dist/useViewerFetch-czqbd2Lj.js.map +0 -1
- package/src/composables/useScrolledX.ts +0 -21
|
@@ -30,7 +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 {
|
|
33
|
+
import DataTable, { type DataColumn } from './DataTable.vue';
|
|
34
|
+
import type { ContextAction } from './ContextMenu.vue';
|
|
34
35
|
import { resolveLocale } from '../locales/resolve';
|
|
35
36
|
|
|
36
37
|
const props = defineProps<{
|
|
@@ -62,7 +63,6 @@ const emit = defineEmits<{
|
|
|
62
63
|
|
|
63
64
|
const locale = computed<LocaleCode>(() => resolveLocale(props.config.locale));
|
|
64
65
|
const { t, formatDate } = useLocale(locale);
|
|
65
|
-
const { scrolledX, onScroll } = useScrolledX();
|
|
66
66
|
|
|
67
67
|
const { tokens, loading, error, canMint, revealed, load, create, remove, dismiss } = useTokens(
|
|
68
68
|
props.config,
|
|
@@ -91,6 +91,14 @@ const scopeState = ref<Record<string, boolean>>({
|
|
|
91
91
|
const rootPath = ref('');
|
|
92
92
|
const expiresInDays = ref<number | null>(null);
|
|
93
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
|
+
|
|
94
102
|
function buildScopes(): string {
|
|
95
103
|
const parts = FULL_SCOPES.filter((s) => scopeState.value[s]) as string[];
|
|
96
104
|
const root = rootPath.value.trim();
|
|
@@ -133,9 +141,8 @@ async function mint(): Promise<void> {
|
|
|
133
141
|
copied.value = false;
|
|
134
142
|
try {
|
|
135
143
|
if (props.full) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const scopes = buildScopes() || 'read';
|
|
144
|
+
if (noScope.value) return;
|
|
145
|
+
const scopes = buildScopes();
|
|
139
146
|
await create({
|
|
140
147
|
label: label.value.trim() || defaultLabel(),
|
|
141
148
|
scopes,
|
|
@@ -162,6 +169,50 @@ async function mint(): Promise<void> {
|
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
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
|
+
|
|
165
216
|
async function revoke(row: ApiToken): Promise<void> {
|
|
166
217
|
if (confirming.value !== row.id) {
|
|
167
218
|
confirming.value = row.id;
|
|
@@ -241,6 +292,9 @@ function usedLabel(row: ApiToken): string {
|
|
|
241
292
|
<input v-model="scopeState[s]" type="checkbox" :data-testid="`token-scope-${s}`" />
|
|
242
293
|
<span>{{ s }}</span>
|
|
243
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>
|
|
244
298
|
</fieldset>
|
|
245
299
|
|
|
246
300
|
<label class="fe-tokform__field">
|
|
@@ -265,7 +319,7 @@ function usedLabel(row: ApiToken): string {
|
|
|
265
319
|
data-testid="token-expiry"
|
|
266
320
|
/>
|
|
267
321
|
</label>
|
|
268
|
-
<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">
|
|
269
323
|
{{ t('conn.tokens.mint') }}
|
|
270
324
|
</button>
|
|
271
325
|
</div>
|
|
@@ -289,32 +343,29 @@ function usedLabel(row: ApiToken): string {
|
|
|
289
343
|
</button>
|
|
290
344
|
</div>
|
|
291
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). -->
|
|
292
352
|
<p v-if="loading" class="fe-s3keys__muted">…</p>
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
<button class="fe-s3keys__link is-danger" :disabled="busy" @click="revoke(row)">
|
|
310
|
-
{{ confirming === row.id ? t('conn.tokens.confirm') : t('conn.tokens.revoke') }}
|
|
311
|
-
</button>
|
|
312
|
-
</td>
|
|
313
|
-
</tr>
|
|
314
|
-
</tbody>
|
|
315
|
-
</table>
|
|
316
|
-
</div>
|
|
317
|
-
<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>
|
|
318
369
|
|
|
319
370
|
<!-- ⚠ Said next to the button rather than in a document nobody opens: a
|
|
320
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>
|