@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
|
@@ -31,6 +31,8 @@ import { useLocale } from '../composables/useLocale';
|
|
|
31
31
|
import { browserProbeURL } from '../lib/externalReach';
|
|
32
32
|
import { fileIconTile } from '../lib/fileIcons';
|
|
33
33
|
import { actionIconSvg } from '../lib/actionIcons';
|
|
34
|
+
import { OFFICE_EXTS } from '../lib/serviceGate';
|
|
35
|
+
import { requestFailure, sayFailure } from '../lib/errorWords';
|
|
34
36
|
|
|
35
37
|
const props = defineProps<{
|
|
36
38
|
open: boolean;
|
|
@@ -40,6 +42,13 @@ const props = defineProps<{
|
|
|
40
42
|
downloadUrl: (path: string) => string;
|
|
41
43
|
onlyOfficeBase?: string | null;
|
|
42
44
|
onlyOfficeConfigEndpoint?: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Could this person set up a missing service (`capabilities.caller_admin`)?
|
|
47
|
+
* Only changes WHICH sentence a missing document server gets: an
|
|
48
|
+
* administrator is told where to set it up, everybody else that it is not
|
|
49
|
+
* available here and how to get the file anyway.
|
|
50
|
+
*/
|
|
51
|
+
canConfigure?: boolean;
|
|
43
52
|
saveTextEndpoint?: string | null;
|
|
44
53
|
/** Endpoint for the archive (zip/rar/7z/tar) member list. Forwarded to
|
|
45
54
|
* ArchiveViewer, which otherwise assumes a same-origin path. */
|
|
@@ -136,7 +145,10 @@ const CODE_LANGS: Record<string, string> = {
|
|
|
136
145
|
// CSV/TSV land in the rich viewer (`csv` kind below) instead of the
|
|
137
146
|
// legacy plain-text path so the user gets a proper table preview.
|
|
138
147
|
const TEXT_PLAIN = ['txt', 'log'];
|
|
139
|
-
|
|
148
|
+
/* The document server's formats — ONE list, shared with the explorer's menu
|
|
149
|
+
* (lib/serviceGate), so a type previewed as "office" is exactly a type whose
|
|
150
|
+
* Open needs ONLYOFFICE. */
|
|
151
|
+
const OFFICE = OFFICE_EXTS;
|
|
140
152
|
|
|
141
153
|
/**
|
|
142
154
|
* Lazy viewer map — extension → component loader. Each loader is a
|
|
@@ -223,7 +235,7 @@ async function loadViewerFor(extension: string): Promise<void> {
|
|
|
223
235
|
const mod = (await loader()) as { default?: Component };
|
|
224
236
|
viewerCmp.value = mod.default ?? (mod as unknown as Component);
|
|
225
237
|
} catch (err) {
|
|
226
|
-
viewerLoadError.value = err
|
|
238
|
+
viewerLoadError.value = said(err, 'err.viewer_failed').text;
|
|
227
239
|
if (extension === 'pdf') pdfFallbackToNative.value = true;
|
|
228
240
|
}
|
|
229
241
|
}
|
|
@@ -240,6 +252,9 @@ const viewerProps = computed(() => {
|
|
|
240
252
|
ext: e,
|
|
241
253
|
mime: props.file?.mime_type,
|
|
242
254
|
t,
|
|
255
|
+
/* The viewer's language as a code, for the one thing `t` cannot give it:
|
|
256
|
+
how a NUMBER is written (a size in the archive list). */
|
|
257
|
+
locale: props.locale,
|
|
243
258
|
authHeaders: props.authHeaders,
|
|
244
259
|
authCredentials: props.authCredentials,
|
|
245
260
|
};
|
|
@@ -256,6 +271,7 @@ const viewerProps = computed(() => {
|
|
|
256
271
|
}
|
|
257
272
|
if (e === 'drawio' || e === 'dio') {
|
|
258
273
|
base.drawioUrl = props.drawioUrl ?? undefined;
|
|
274
|
+
base.canConfigure = props.canConfigure === true;
|
|
259
275
|
base.saveUrl = props.saveTextEndpoint ?? undefined;
|
|
260
276
|
base.readOnly = props.openMode === 'view';
|
|
261
277
|
}
|
|
@@ -318,6 +334,26 @@ const canEditKind = computed<boolean>(() =>
|
|
|
318
334
|
!!props.file && EDITABLE_EXTS.has(ext(props.file)),
|
|
319
335
|
);
|
|
320
336
|
|
|
337
|
+
/**
|
|
338
|
+
* The Edit button of a document whose editor is an optional service that is
|
|
339
|
+
* not there — ONLYOFFICE for office files, draw.io for diagrams. It led to a
|
|
340
|
+
* tab that could only say so. The owner's rule (lib/serviceGate): greyed with
|
|
341
|
+
* where to set it up for someone who can, not offered to anybody else.
|
|
342
|
+
*/
|
|
343
|
+
const editNeeds = computed<'' | 'onlyoffice' | 'drawio'>(() => {
|
|
344
|
+
const e = ext(props.file);
|
|
345
|
+
if (OFFICE.includes(e) && !props.onlyOfficeBase) return 'onlyoffice';
|
|
346
|
+
if ((e === 'drawio' || e === 'dio') && !props.drawioUrl) return 'drawio';
|
|
347
|
+
return '';
|
|
348
|
+
});
|
|
349
|
+
const editTitle = computed(() =>
|
|
350
|
+
editNeeds.value === 'onlyoffice'
|
|
351
|
+
? t('ctx.needs_onlyoffice')
|
|
352
|
+
: editNeeds.value === 'drawio'
|
|
353
|
+
? t('ctx.needs_drawio')
|
|
354
|
+
: t('viewer.edit'),
|
|
355
|
+
);
|
|
356
|
+
|
|
321
357
|
// Keep adapter prefix so backend resolves the right storage — stripping
|
|
322
358
|
// it defaults to storages[0] and 404s on any non-default adapter.
|
|
323
359
|
const src = computed(() => (props.file ? props.previewUrl(props.file.path) : ''));
|
|
@@ -472,6 +508,24 @@ function onGroundClick(): void {
|
|
|
472
508
|
|
|
473
509
|
const loading = ref(false);
|
|
474
510
|
const fetchError = ref<string | null>(null);
|
|
511
|
+
/** An administrator's second line under `fetchError` (lib/errorWords). */
|
|
512
|
+
const fetchErrorDetail = ref<string | null>(null);
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* A failure, said (lib/errorWords): the sentence for everybody, the raw words
|
|
516
|
+
* as a second line only for a caller who can administer the instance.
|
|
517
|
+
*
|
|
518
|
+
* ⚠⚠ This modal used to print what it caught: "save failed: 500 {…}",
|
|
519
|
+
* "404 Not Found", "Monaco mount fail: …" — a status code, a JSON body and a
|
|
520
|
+
* library's message, to whoever was editing (QA, 2026-09-21).
|
|
521
|
+
*/
|
|
522
|
+
function said(err: unknown, fallbackKey: string) {
|
|
523
|
+
return sayFailure(err, t(fallbackKey), { callerAdmin: props.canConfigure === true });
|
|
524
|
+
}
|
|
525
|
+
function showFetchError(f: { text: string; detail?: string }): void {
|
|
526
|
+
fetchError.value = f.text;
|
|
527
|
+
fetchErrorDetail.value = f.detail ?? null;
|
|
528
|
+
}
|
|
475
529
|
const rawText = ref<string>('');
|
|
476
530
|
const MAX_TEXT_BYTES = 1_000_000;
|
|
477
531
|
const tooLarge = ref(false);
|
|
@@ -499,6 +553,7 @@ async function saveMarkdown() {
|
|
|
499
553
|
if (!props.saveTextEndpoint || !props.file || mdSaving.value) return;
|
|
500
554
|
mdSaving.value = true;
|
|
501
555
|
fetchError.value = null;
|
|
556
|
+
fetchErrorDetail.value = null;
|
|
502
557
|
try {
|
|
503
558
|
const headers = {
|
|
504
559
|
'Content-Type': 'application/json',
|
|
@@ -511,11 +566,11 @@ async function saveMarkdown() {
|
|
|
511
566
|
body: JSON.stringify({ path: stripAdapter(props.file.path), content: rawText.value }),
|
|
512
567
|
});
|
|
513
568
|
if (!res.ok) {
|
|
514
|
-
throw
|
|
569
|
+
throw requestFailure(res.status, await res.text().catch(() => ''), props.locale);
|
|
515
570
|
}
|
|
516
571
|
mdDirty.value = false;
|
|
517
572
|
} catch (err) {
|
|
518
|
-
|
|
573
|
+
showFetchError(said(err, 'err.save_failed'));
|
|
519
574
|
} finally {
|
|
520
575
|
mdSaving.value = false;
|
|
521
576
|
}
|
|
@@ -524,6 +579,7 @@ async function saveMarkdown() {
|
|
|
524
579
|
async function fetchText(url: string): Promise<void> {
|
|
525
580
|
loading.value = true;
|
|
526
581
|
fetchError.value = null;
|
|
582
|
+
fetchErrorDetail.value = null;
|
|
527
583
|
rawText.value = '';
|
|
528
584
|
tooLarge.value = false;
|
|
529
585
|
try {
|
|
@@ -534,7 +590,7 @@ async function fetchText(url: string): Promise<void> {
|
|
|
534
590
|
headers,
|
|
535
591
|
});
|
|
536
592
|
if (!res.ok) {
|
|
537
|
-
throw
|
|
593
|
+
throw requestFailure(res.status, await res.text().catch(() => ''), props.locale);
|
|
538
594
|
}
|
|
539
595
|
const len = Number(res.headers.get('content-length') || '0');
|
|
540
596
|
if (len > MAX_TEXT_BYTES) {
|
|
@@ -549,7 +605,7 @@ async function fetchText(url: string): Promise<void> {
|
|
|
549
605
|
rawText.value = text;
|
|
550
606
|
}
|
|
551
607
|
} catch (err) {
|
|
552
|
-
|
|
608
|
+
showFetchError(said(err, 'err.load_failed'));
|
|
553
609
|
} finally {
|
|
554
610
|
loading.value = false;
|
|
555
611
|
}
|
|
@@ -604,7 +660,7 @@ async function renderMarkdown(text: string): Promise<void> {
|
|
|
604
660
|
await enrichMarkdown();
|
|
605
661
|
} catch (err) {
|
|
606
662
|
markdownHtml.value = '';
|
|
607
|
-
|
|
663
|
+
showFetchError(said(err, 'err.load_failed'));
|
|
608
664
|
}
|
|
609
665
|
}
|
|
610
666
|
|
|
@@ -724,7 +780,7 @@ async function highlightCode(text: string, language: string): Promise<void> {
|
|
|
724
780
|
}
|
|
725
781
|
} catch (err) {
|
|
726
782
|
codeHtml.value = '';
|
|
727
|
-
|
|
783
|
+
showFetchError(said(err, 'err.load_failed'));
|
|
728
784
|
}
|
|
729
785
|
}
|
|
730
786
|
|
|
@@ -834,7 +890,7 @@ async function tryMountMonaco(text: string, extension: string): Promise<boolean>
|
|
|
834
890
|
monacoReady.value = true;
|
|
835
891
|
return true;
|
|
836
892
|
} catch (err) {
|
|
837
|
-
|
|
893
|
+
showFetchError(said(err, 'err.viewer_failed'));
|
|
838
894
|
return false;
|
|
839
895
|
}
|
|
840
896
|
}
|
|
@@ -860,15 +916,15 @@ async function saveCode(): Promise<void> {
|
|
|
860
916
|
}),
|
|
861
917
|
});
|
|
862
918
|
if (!res.ok) {
|
|
863
|
-
|
|
864
|
-
throw new Error(`${res.status} ${res.statusText}${txt ? ' — ' + txt.slice(0, 150) : ''}`);
|
|
919
|
+
throw requestFailure(res.status, await res.text().catch(() => ''), props.locale);
|
|
865
920
|
}
|
|
866
921
|
saveOk.value = true;
|
|
867
922
|
setTimeout(() => {
|
|
868
923
|
saveOk.value = false;
|
|
869
924
|
}, 2500);
|
|
870
925
|
} catch (err) {
|
|
871
|
-
|
|
926
|
+
const f = said(err, 'err.save_failed');
|
|
927
|
+
saveError.value = f.detail ? `${f.text}\n${f.detail}` : f.text;
|
|
872
928
|
} finally {
|
|
873
929
|
saving.value = false;
|
|
874
930
|
}
|
|
@@ -881,6 +937,7 @@ async function runOrchestration(open: boolean, url: string, k: string): Promise<
|
|
|
881
937
|
markdownHtml.value = '';
|
|
882
938
|
codeHtml.value = '';
|
|
883
939
|
fetchError.value = null;
|
|
940
|
+
fetchErrorDetail.value = null;
|
|
884
941
|
officeError.value = null;
|
|
885
942
|
viewerCmp.value = null;
|
|
886
943
|
viewerLoadError.value = null;
|
|
@@ -963,11 +1020,42 @@ const officeEl = ref<HTMLDivElement | null>(null);
|
|
|
963
1020
|
const officeError = ref<string | null>(null);
|
|
964
1021
|
let officeEditor: any = null;
|
|
965
1022
|
|
|
1023
|
+
/** "No document server here", in the sentence this person can act on. */
|
|
1024
|
+
function officeUnconfigured(): string {
|
|
1025
|
+
return t(props.canConfigure ? 'viewer.office_unconfigured_admin' : 'viewer.office_unconfigured');
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* ⚠⚠ Every failure below becomes a SENTENCE, never the HTTP layer. This
|
|
1030
|
+
* function used to throw `Config fetch ${status}: ${body}` and put it on
|
|
1031
|
+
* screen, which is how a person clicking "Open" on a .docx read
|
|
1032
|
+
* `Config fetch 503: {"error":"onlyoffice not configured"}` (owner,
|
|
1033
|
+
* 2026-09-21: "böyle hata vermek yerine … düzgün hata mesajı vermemiz
|
|
1034
|
+
* lazım"). The status still reaches the console for whoever is debugging it.
|
|
1035
|
+
*/
|
|
1036
|
+
async function officeConfigError(res: Response): Promise<string> {
|
|
1037
|
+
let body = '';
|
|
1038
|
+
try {
|
|
1039
|
+
body = (await res.text()).slice(0, 200);
|
|
1040
|
+
} catch {
|
|
1041
|
+
/* nothing to read */
|
|
1042
|
+
}
|
|
1043
|
+
console.warn('[filex] ONLYOFFICE config request failed', res.status, body);
|
|
1044
|
+
if (res.status === 503 && /not configured/i.test(body)) return officeUnconfigured();
|
|
1045
|
+
if (res.status === 403) return t('viewer.office_forbidden');
|
|
1046
|
+
return t('viewer.office_failed');
|
|
1047
|
+
}
|
|
1048
|
+
|
|
966
1049
|
async function mountOnlyOfficeEditor(): Promise<void> {
|
|
967
1050
|
officeError.value = null;
|
|
968
1051
|
if (!props.file || kind.value !== 'office') return;
|
|
969
|
-
|
|
970
|
-
|
|
1052
|
+
/* ⚠ BOTH halves. The standalone /files/edit route always handed over the
|
|
1053
|
+
* config ENDPOINT and left the base null when the capabilities probe said
|
|
1054
|
+
* the service was off — so this check, reading the endpoint alone, let the
|
|
1055
|
+
* request go out and put the server's 503 on screen. No base means no
|
|
1056
|
+
* document server, whatever else is configured. */
|
|
1057
|
+
if (!props.onlyOfficeConfigEndpoint || !props.onlyOfficeBase) {
|
|
1058
|
+
officeError.value = officeUnconfigured();
|
|
971
1059
|
return;
|
|
972
1060
|
}
|
|
973
1061
|
if (!officeEl.value) return;
|
|
@@ -989,7 +1077,8 @@ async function mountOnlyOfficeEditor(): Promise<void> {
|
|
|
989
1077
|
}),
|
|
990
1078
|
});
|
|
991
1079
|
if (!res.ok) {
|
|
992
|
-
|
|
1080
|
+
officeError.value = await officeConfigError(res);
|
|
1081
|
+
return;
|
|
993
1082
|
}
|
|
994
1083
|
const { config, documentServerUrl } = (await res.json()) as {
|
|
995
1084
|
config: any;
|
|
@@ -1014,7 +1103,11 @@ async function mountOnlyOfficeEditor(): Promise<void> {
|
|
|
1014
1103
|
}
|
|
1015
1104
|
officeEditor = new W.DocsAPI.DocEditor(mountId, config);
|
|
1016
1105
|
} catch (err) {
|
|
1017
|
-
|
|
1106
|
+
/* The document server's script did not load, or did not define its API:
|
|
1107
|
+
the server is configured but not answering. The detail is for the
|
|
1108
|
+
console, the sentence for the person. */
|
|
1109
|
+
console.warn('[filex] ONLYOFFICE did not start', err);
|
|
1110
|
+
officeError.value = t(props.canConfigure ? 'viewer.office_unreachable_admin' : 'viewer.office_unreachable');
|
|
1018
1111
|
}
|
|
1019
1112
|
}
|
|
1020
1113
|
|
|
@@ -1141,11 +1234,12 @@ function loadOnlyOfficeScript(base: string): Promise<void> {
|
|
|
1141
1234
|
@change="(v: boolean) => emit('starred', v)"
|
|
1142
1235
|
/>
|
|
1143
1236
|
<button
|
|
1144
|
-
v-if="openMode === 'view' && canEditKind && newTabEnabled !== false /* wiring:e2 */"
|
|
1237
|
+
v-if="openMode === 'view' && canEditKind && newTabEnabled !== false /* wiring:e2 */ && (!editNeeds || canConfigure)"
|
|
1145
1238
|
type="button"
|
|
1146
1239
|
class="fe-viewer__act"
|
|
1147
|
-
:
|
|
1148
|
-
:
|
|
1240
|
+
:disabled="!!editNeeds"
|
|
1241
|
+
:title="editTitle"
|
|
1242
|
+
:aria-label="editTitle"
|
|
1149
1243
|
@click="openEditInNewTab"
|
|
1150
1244
|
><span class="fe-icon" aria-hidden="true" v-html="actionIconSvg('rename')"></span></button>
|
|
1151
1245
|
<button
|
|
@@ -1239,15 +1333,19 @@ function loadOnlyOfficeScript(base: string): Promise<void> {
|
|
|
1239
1333
|
v-else
|
|
1240
1334
|
ref="markdownEl"
|
|
1241
1335
|
class="fe-preview__md-split-output fe-preview__md"
|
|
1336
|
+
dir="auto"
|
|
1242
1337
|
v-html="markdownHtml"
|
|
1243
1338
|
></div>
|
|
1244
1339
|
</div>
|
|
1245
1340
|
</div>
|
|
1246
1341
|
<div v-else-if="fetchError" class="fe-preview__fallback">
|
|
1247
1342
|
<p>{{ fetchError }}</p>
|
|
1343
|
+
<p v-if="fetchErrorDetail" class="fe-preview__error-detail" data-testid="preview-error-detail">{{ fetchErrorDetail }}</p>
|
|
1248
1344
|
<a :href="download" class="fe-btn fe-btn--primary">{{ t('viewer.download') }}</a>
|
|
1249
1345
|
</div>
|
|
1250
|
-
|
|
1346
|
+
<!-- ⚠ RTL: a document reads in ITS direction, not the interface's —
|
|
1347
|
+
`auto` lets an English README stay left to right in Arabic. -->
|
|
1348
|
+
<div v-else-if="markdownHtml" ref="markdownEl" class="fe-preview__md" dir="auto" v-html="markdownHtml"></div>
|
|
1251
1349
|
<pre v-else class="fe-preview__pre">{{ rawText }}</pre>
|
|
1252
1350
|
</template>
|
|
1253
1351
|
|
|
@@ -1287,6 +1385,7 @@ function loadOnlyOfficeScript(base: string): Promise<void> {
|
|
|
1287
1385
|
</div>
|
|
1288
1386
|
<div v-else-if="fetchError" class="fe-preview__fallback">
|
|
1289
1387
|
<p>{{ fetchError }}</p>
|
|
1388
|
+
<p v-if="fetchErrorDetail" class="fe-preview__error-detail" data-testid="preview-error-detail">{{ fetchErrorDetail }}</p>
|
|
1290
1389
|
<a :href="download" class="fe-btn fe-btn--primary">{{ t('viewer.download') }}</a>
|
|
1291
1390
|
</div>
|
|
1292
1391
|
<div v-else class="fe-preview__code-wrap">
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
import { ref, watch } from 'vue';
|
|
3
3
|
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
4
4
|
import { useLocale } from '../composables/useLocale';
|
|
5
|
+
import { isInternalName } from '../lib/internalPaths';
|
|
5
6
|
import Modal from './Modal.vue';
|
|
6
7
|
|
|
7
8
|
const props = defineProps<{
|
|
8
9
|
open: boolean;
|
|
9
10
|
locale: LocaleCode;
|
|
10
11
|
currentName: string;
|
|
12
|
+
/** Why the last attempt did not happen — the host's answer from the server
|
|
13
|
+
* (a taken name, a refusal, an outage). Shown under the field until the
|
|
14
|
+
* name is edited. */
|
|
15
|
+
error?: string | null;
|
|
11
16
|
}>();
|
|
12
17
|
|
|
13
18
|
const emit = defineEmits<{
|
|
@@ -17,20 +22,49 @@ const emit = defineEmits<{
|
|
|
17
22
|
|
|
18
23
|
const { t } = useLocale(() => props.locale);
|
|
19
24
|
const name = ref('');
|
|
25
|
+
// What the dialog is saying right now — ONE line, whoever said it: the host's
|
|
26
|
+
// error, or the dialog's own refusal of a name that is not one (a slash, "."
|
|
27
|
+
// or "..", a name filex keeps for itself). Either goes away once the person
|
|
28
|
+
// edits the name.
|
|
29
|
+
const shownError = ref<string | null>(null);
|
|
20
30
|
|
|
21
31
|
watch(
|
|
22
32
|
() => [props.open, props.currentName] as const,
|
|
23
33
|
([isOpen, cur]) => {
|
|
24
34
|
if (isOpen) {
|
|
25
35
|
name.value = cur;
|
|
36
|
+
shownError.value = props.error ?? null;
|
|
26
37
|
}
|
|
27
38
|
},
|
|
28
39
|
);
|
|
29
40
|
|
|
41
|
+
watch(
|
|
42
|
+
() => props.error,
|
|
43
|
+
(err) => {
|
|
44
|
+
shownError.value = err ?? null;
|
|
45
|
+
},
|
|
46
|
+
{ immediate: true },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
function onInput() {
|
|
50
|
+
shownError.value = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
function submit() {
|
|
31
54
|
const clean = name.value.trim();
|
|
32
55
|
if (!clean) return;
|
|
33
|
-
if (/[\\/]/.test(clean) || clean === '.' || clean === '..')
|
|
56
|
+
if (/[\\/]/.test(clean) || clean === '.' || clean === '..') {
|
|
57
|
+
// Refused here with a word, not by doing nothing: a Save button that
|
|
58
|
+
// silently ignores the click reads as a broken dialog.
|
|
59
|
+
shownError.value = t('modal.newfolder.invalid');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// A document renamed to `.versions` would vanish from every view the
|
|
63
|
+
// moment it was renamed; the server refuses it, this says why.
|
|
64
|
+
if (isInternalName(clean)) {
|
|
65
|
+
shownError.value = t('names.reserved', { name: clean });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
34
68
|
emit('submit', clean);
|
|
35
69
|
}
|
|
36
70
|
</script>
|
|
@@ -43,8 +77,10 @@ function submit() {
|
|
|
43
77
|
type="text"
|
|
44
78
|
class="fe-input"
|
|
45
79
|
autocomplete="off"
|
|
80
|
+
@input="onInput"
|
|
46
81
|
@keydown.enter.prevent="submit"
|
|
47
82
|
/>
|
|
83
|
+
<p v-if="shownError" class="fe-form__error" role="alert" data-testid="rename-error">{{ shownError }}</p>
|
|
48
84
|
</form>
|
|
49
85
|
<template #actions>
|
|
50
86
|
<button type="button" class="fe-btn" @click="emit('close')">
|