@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.
Files changed (234) hide show
  1. package/README.md +14 -2
  2. package/dist/ArchiveViewer-BqU761Wk.js +149 -0
  3. package/dist/ArchiveViewer-BqU761Wk.js.map +1 -0
  4. package/dist/CsvViewer-ByHVK5ga.js +172 -0
  5. package/dist/CsvViewer-ByHVK5ga.js.map +1 -0
  6. package/dist/{DrawioViewer-CC6rJXeP.js → DrawioViewer-Bgrj8-zG.js} +45 -44
  7. package/dist/DrawioViewer-Bgrj8-zG.js.map +1 -0
  8. package/dist/{EpubViewer-CAoTSXTO.js → EpubViewer-DJwUKlHW.js} +50 -50
  9. package/dist/EpubViewer-DJwUKlHW.js.map +1 -0
  10. package/dist/IpynbViewer-Bz0eJ4NS.js +184 -0
  11. package/dist/IpynbViewer-Bz0eJ4NS.js.map +1 -0
  12. package/dist/MermaidViewer-CEDTD_pp.js +136 -0
  13. package/dist/MermaidViewer-CEDTD_pp.js.map +1 -0
  14. package/dist/PsdViewer-CZcxUpZO.js +121 -0
  15. package/dist/PsdViewer-CZcxUpZO.js.map +1 -0
  16. package/dist/TiffViewer-BwI62CbQ.js +141 -0
  17. package/dist/TiffViewer-BwI62CbQ.js.map +1 -0
  18. package/dist/{Viewer3D-CdZeK4j4.js → Viewer3D-DoeZcuMm.js} +16 -16
  19. package/dist/Viewer3D-DoeZcuMm.js.map +1 -0
  20. package/dist/filex-core.js +475 -219
  21. package/dist/filex-core.umd.cjs +165 -108
  22. package/dist/filex-core.umd.cjs.map +1 -1
  23. package/dist/fonts/caveat-latin-ext.woff2 +0 -0
  24. package/dist/fonts/caveat-latin.woff2 +0 -0
  25. package/dist/fonts/dancing-script-latin-ext.woff2 +0 -0
  26. package/dist/fonts/dancing-script-latin.woff2 +0 -0
  27. package/dist/fonts/homemade-apple-latin.woff2 +0 -0
  28. package/dist/fonts/source-serif-4-latin-ext.woff2 +0 -0
  29. package/dist/fonts/source-serif-4-latin.woff2 +0 -0
  30. package/dist/index-DuWnGuv5.js +35901 -0
  31. package/dist/index-DuWnGuv5.js.map +1 -0
  32. package/dist/index.d.ts +5227 -553
  33. package/dist/pdf-CaPYsOUo.js +21302 -0
  34. package/dist/pdf-CaPYsOUo.js.map +1 -0
  35. package/dist/style.css +1 -1
  36. package/package.json +1 -1
  37. package/src/FileExplorer.vue +1055 -253
  38. package/src/assets/fonts/LICENSE-Apache-2.0.txt +202 -0
  39. package/src/assets/fonts/LICENSE-OFL-1.1.txt +93 -0
  40. package/src/assets/fonts/README.md +58 -0
  41. package/src/assets/fonts/caveat-latin-ext.woff2 +0 -0
  42. package/src/assets/fonts/caveat-latin.woff2 +0 -0
  43. package/src/assets/fonts/dancing-script-latin-ext.woff2 +0 -0
  44. package/src/assets/fonts/dancing-script-latin.woff2 +0 -0
  45. package/src/assets/fonts/homemade-apple-latin.woff2 +0 -0
  46. package/src/assets/fonts/source-serif-4-latin-ext.woff2 +0 -0
  47. package/src/assets/fonts/source-serif-4-latin.woff2 +0 -0
  48. package/src/components/AdvancedSearch.vue +49 -0
  49. package/src/components/Breadcrumb.vue +5 -5
  50. package/src/components/ChoiceButtons.vue +152 -0
  51. package/src/components/CommandPalette.vue +5 -5
  52. package/src/components/ConnectionGuideView.vue +1 -1
  53. package/src/components/ConnectionNotice.vue +59 -0
  54. package/src/components/ConnectionsPanel.vue +30 -568
  55. package/src/components/ContextMenu.vue +31 -5
  56. package/src/components/DataTable.vue +1331 -0
  57. package/src/components/FilePane.vue +3 -0
  58. package/src/components/FilterBar.vue +12 -5
  59. package/src/components/GalleryView.vue +50 -2
  60. package/src/components/GridView.vue +51 -3
  61. package/src/components/HomeView.vue +18 -6
  62. package/src/components/InspectorPanel.vue +134 -17
  63. package/src/components/ListView.vue +366 -900
  64. package/src/components/LogoMark.vue +57 -0
  65. package/src/components/NFSExportsPanel.vue +76 -28
  66. package/src/components/OnboardingTour.vue +19 -7
  67. package/src/components/OperationsCenter.vue +77 -1
  68. package/src/components/PendingOpsTray.vue +59 -5
  69. package/src/components/PresenceBar.vue +7 -7
  70. package/src/components/ProductVersion.vue +31 -0
  71. package/src/components/QuickLook.vue +10 -14
  72. package/src/components/RecentlyOpened.vue +31 -15
  73. package/src/components/RecoveryKeyModal.vue +1 -1
  74. package/src/components/RowActions.vue +134 -0
  75. package/src/components/S3KeysPanel.vue +98 -62
  76. package/src/components/SSHKeysPanel.vue +79 -30
  77. package/src/components/SideNav.vue +260 -96
  78. package/src/components/StarButton.vue +1 -0
  79. package/src/components/StorageFields.vue +74 -6
  80. package/src/components/StorageTags.vue +62 -0
  81. package/src/components/TabBar.vue +1 -1
  82. package/src/components/TagKindIcon.vue +56 -0
  83. package/src/components/TagPicker.vue +226 -40
  84. package/src/components/ThemePalette.vue +4 -4
  85. package/src/components/TimeZonePicker.vue +29 -10
  86. package/src/components/TokensPanel.vue +82 -31
  87. package/src/components/Toolbar.vue +28 -6
  88. package/src/components/plugin/PluginConfirmModal.vue +54 -0
  89. package/src/components/plugin/PluginInspectorSection.vue +149 -0
  90. package/src/components/plugin/PluginPageView.vue +339 -0
  91. package/src/components/plugin/PluginViewModal.vue +159 -0
  92. package/src/components/plugin/SurfaceConversation.vue +76 -0
  93. package/src/components/plugin/SurfaceFooterButtons.vue +73 -0
  94. package/src/components/plugin/SurfaceRenderer.vue +299 -0
  95. package/src/components/plugin/SurfaceSections.vue +91 -0
  96. package/src/components/plugin/nodes/PdfFieldEditor.vue +509 -0
  97. package/src/components/plugin/nodes/SurfaceFileChooser.vue +94 -0
  98. package/src/components/plugin/nodes/SurfaceForm.vue +82 -0
  99. package/src/components/plugin/nodes/SurfaceList.vue +127 -0
  100. package/src/components/plugin/nodes/SurfacePdfFields.vue +1285 -0
  101. package/src/components/plugin/nodes/SurfacePeoplePicker.vue +213 -0
  102. package/src/components/plugin/nodes/SurfacePinInput.vue +57 -0
  103. package/src/components/plugin/nodes/SurfacePreview.vue +119 -0
  104. package/src/components/plugin/nodes/SurfaceProgress.vue +48 -0
  105. package/src/components/plugin/nodes/SurfaceSignaturePad.vue +472 -0
  106. package/src/components/plugin/nodes/SurfaceSteps.vue +53 -0
  107. package/src/components/public/PublicLanguagePicker.vue +58 -0
  108. package/src/components/public/PublicLinkPage.vue +286 -0
  109. package/src/components/public/PublicLinkPreview.vue +85 -0
  110. package/src/components/public/PublicPinGate.vue +108 -0
  111. package/src/components/public/PublicRequestBody.vue +204 -0
  112. package/src/components/public/PublicShareBody.vue +188 -0
  113. package/src/components/public/PublicShell.vue +332 -0
  114. package/src/composables/useConnections.ts +8 -2
  115. package/src/composables/useFileApi.ts +183 -44
  116. package/src/composables/useLocale.ts +277 -41
  117. package/src/composables/useNFSExports.ts +8 -10
  118. package/src/composables/useOperations.ts +44 -3
  119. package/src/composables/usePendingOps.ts +59 -8
  120. package/src/composables/usePluginActions.ts +121 -0
  121. package/src/composables/usePluginSurface.ts +223 -0
  122. package/src/composables/usePublicBranding.ts +172 -0
  123. package/src/composables/usePublicLink.ts +573 -0
  124. package/src/composables/usePublicPage.ts +61 -0
  125. package/src/composables/useS3Keys.ts +17 -12
  126. package/src/composables/useSSHKeys.ts +8 -10
  127. package/src/composables/useTokens.ts +8 -12
  128. package/src/composables/useUploadChunked.ts +18 -8
  129. package/src/composables/useViewerFetch.ts +6 -3
  130. package/src/index.ts +404 -8
  131. package/src/lib/actionIcons.ts +46 -1
  132. package/src/lib/advSearch.ts +14 -7
  133. package/src/lib/appLock.ts +130 -0
  134. package/src/lib/connection.ts +110 -0
  135. package/src/lib/destinationTree.ts +19 -3
  136. package/src/lib/direction.ts +353 -0
  137. package/src/lib/dragGhost.ts +6 -0
  138. package/src/lib/dropLimits.ts +69 -0
  139. package/src/lib/errorWords.ts +394 -0
  140. package/src/lib/externalReach.ts +5 -1
  141. package/src/lib/fileFilters.ts +7 -2
  142. package/src/lib/fileIcons.ts +4 -0
  143. package/src/lib/internalPaths.ts +77 -0
  144. package/src/lib/listInput.ts +64 -0
  145. package/src/lib/listing.ts +58 -23
  146. package/src/lib/modalStack.ts +26 -0
  147. package/src/lib/nodeRow.ts +18 -3
  148. package/src/lib/pdfFieldRules.ts +126 -0
  149. package/src/lib/pdfFields.ts +589 -0
  150. package/src/lib/pdfFieldsGeom.ts +203 -0
  151. package/src/lib/pdfjsLoader.ts +51 -0
  152. package/src/lib/personName.ts +47 -0
  153. package/src/lib/pluginApplies.ts +123 -0
  154. package/src/lib/pluginLabel.ts +72 -0
  155. package/src/lib/pluginMenu.ts +253 -0
  156. package/src/lib/pluginPage.ts +58 -0
  157. package/src/lib/plural.ts +181 -0
  158. package/src/lib/popupLayer.ts +103 -0
  159. package/src/lib/prefs.ts +563 -0
  160. package/src/lib/productVersion.ts +35 -0
  161. package/src/lib/publicLayout.ts +39 -0
  162. package/src/lib/serviceGate.ts +104 -0
  163. package/src/lib/shareTtl.ts +25 -11
  164. package/src/lib/signFonts.ts +85 -0
  165. package/src/lib/signaturePad.ts +111 -0
  166. package/src/lib/sortOrder.ts +100 -38
  167. package/src/lib/star.ts +7 -1
  168. package/src/lib/surfaceCell.ts +41 -0
  169. package/src/lib/surfaceConditions.ts +156 -0
  170. package/src/lib/surfaceOpen.ts +106 -0
  171. package/src/lib/surfaceSteps.ts +68 -0
  172. package/src/lib/surfaceValues.ts +191 -0
  173. package/src/lib/symlink.ts +150 -0
  174. package/src/lib/tableColumns.ts +493 -0
  175. package/src/lib/tableEnv.ts +34 -0
  176. package/src/lib/tablePrefs.ts +84 -0
  177. package/src/lib/tags.ts +121 -12
  178. package/src/lib/themes.ts +286 -18
  179. package/src/lib/tour.ts +152 -0
  180. package/src/lib/trashEmpty.ts +111 -0
  181. package/src/lib/trashTimeLeft.ts +23 -0
  182. package/src/lib/uiLocales.ts +460 -0
  183. package/src/lib/viewPrefs.ts +696 -660
  184. package/src/lib/viewPrefsHttp.ts +78 -0
  185. package/src/locales/en.ts +515 -117
  186. package/src/locales/resolve.ts +17 -8
  187. package/src/locales/tr.ts +536 -152
  188. package/src/modals/ConvertModal.vue +62 -11
  189. package/src/modals/DestinationPickerModal.vue +59 -16
  190. package/src/modals/Modal.vue +113 -23
  191. package/src/modals/NewDocumentModal.vue +16 -6
  192. package/src/modals/NewFolderModal.vue +8 -0
  193. package/src/modals/PermissionsModal.vue +272 -163
  194. package/src/modals/PreviewModal.vue +119 -20
  195. package/src/modals/RenameModal.vue +37 -1
  196. package/src/styles/base.css +2890 -260
  197. package/src/styles/sign-fonts.css +118 -0
  198. package/src/styles/variables.css +60 -0
  199. package/src/types/Connections.ts +17 -0
  200. package/src/types/ExplorerConfig.ts +117 -1
  201. package/src/types/FileNode.ts +50 -0
  202. package/src/types/Plugins.ts +494 -0
  203. package/src/types/Public.ts +212 -0
  204. package/src/viewers/ArchiveViewer.vue +93 -48
  205. package/src/viewers/CsvViewer.vue +99 -53
  206. package/src/viewers/DrawioViewer.vue +35 -9
  207. package/src/viewers/EpubViewer.vue +7 -8
  208. package/src/viewers/IpynbViewer.vue +3 -1
  209. package/src/viewers/MermaidViewer.vue +4 -5
  210. package/src/viewers/PdfViewer.vue +8 -17
  211. package/src/viewers/PsdViewer.vue +2 -4
  212. package/src/viewers/TiffViewer.vue +4 -5
  213. package/src/viewers/Viewer3D.vue +1 -1
  214. package/LICENSE +0 -21
  215. package/dist/ArchiveViewer-BUQWLhwX.js +0 -124
  216. package/dist/ArchiveViewer-BUQWLhwX.js.map +0 -1
  217. package/dist/CsvViewer-Bg2_aqzc.js +0 -141
  218. package/dist/CsvViewer-Bg2_aqzc.js.map +0 -1
  219. package/dist/DrawioViewer-CC6rJXeP.js.map +0 -1
  220. package/dist/EpubViewer-CAoTSXTO.js.map +0 -1
  221. package/dist/IpynbViewer-D0GwGpnE.js +0 -184
  222. package/dist/IpynbViewer-D0GwGpnE.js.map +0 -1
  223. package/dist/MermaidViewer-DGwAvRZg.js +0 -137
  224. package/dist/MermaidViewer-DGwAvRZg.js.map +0 -1
  225. package/dist/PsdViewer-DhGweA0m.js +0 -122
  226. package/dist/PsdViewer-DhGweA0m.js.map +0 -1
  227. package/dist/TiffViewer-D0AurvAh.js +0 -142
  228. package/dist/TiffViewer-D0AurvAh.js.map +0 -1
  229. package/dist/Viewer3D-CdZeK4j4.js.map +0 -1
  230. package/dist/index-BArI-B1M.js +0 -26911
  231. package/dist/index-BArI-B1M.js.map +0 -1
  232. package/dist/useViewerFetch-czqbd2Lj.js +0 -25
  233. package/dist/useViewerFetch-czqbd2Lj.js.map +0 -1
  234. package/src/composables/useScrolledX.ts +0 -21
@@ -17,6 +17,7 @@ import type { ExplorerConfig } from '../types/ExplorerConfig';
17
17
  import type { S3AccessKey, S3Connection, S3KeyCreated, S3KeyRequest } from '../types/S3Keys';
18
18
  import { connectionsBase } from './useConnections';
19
19
  import { useFileApi } from './useFileApi';
20
+ import { serverWords } from '../lib/errorWords';
20
21
 
21
22
  export function useS3Keys(config: ExplorerConfig) {
22
23
  const api = useFileApi(config);
@@ -28,6 +29,9 @@ export function useS3Keys(config: ExplorerConfig) {
28
29
  const loading = ref(false);
29
30
  const loaded = ref(false);
30
31
  const error = ref<string | null>(null);
32
+ /** The fix, when the server told THIS caller one (`admin_hint`: only an
33
+ * administrator who can act on it is sent it). */
34
+ const errorHint = ref<string | null>(null);
31
35
  /**
32
36
  * null until the first load answers. False means the caller may not mint
33
37
  * keys (anonymous, or a token without the right to) — the guide is still
@@ -38,19 +42,15 @@ export function useS3Keys(config: ExplorerConfig) {
38
42
  /** The secret, held in memory for exactly as long as the user is looking. */
39
43
  const revealed = ref<S3KeyCreated | null>(null);
40
44
 
45
+ /** A refusal, said — lib/errorWords `serverWords`, the one rule every
46
+ * connection panel shares (it used to print the server's `error` field as
47
+ * it came: an environment variable, to a regular user). ⚠ The locale is
48
+ * passed because what comes back may be the SERVER's own sentence: it
49
+ * never met a translator, and an Arabic panel needs its machine runs
50
+ * isolated (lib/direction `foreignText`). */
41
51
  function messageOf(e: unknown): string {
42
- const err = e as { message?: string; detail?: string } | null;
43
- // The backend's own words beat a status line: "access keys are not
44
- // available on this install" tells an operator what to fix; "503" does not.
45
- if (err?.detail) {
46
- try {
47
- const parsed = JSON.parse(err.detail) as { error?: string };
48
- if (parsed?.error) return parsed.error;
49
- } catch {
50
- /* not JSON — fall through */
51
- }
52
- }
53
- return err?.message || String(e);
52
+ errorHint.value = (e as { hint?: string } | null)?.hint ?? null;
53
+ return serverWords(e, config.locale);
54
54
  }
55
55
 
56
56
  function statusOf(e: unknown): number | undefined {
@@ -60,6 +60,7 @@ export function useS3Keys(config: ExplorerConfig) {
60
60
  async function load(): Promise<void> {
61
61
  loading.value = true;
62
62
  error.value = null;
63
+ errorHint.value = null;
63
64
  try {
64
65
  const body = await api.jsonFetch<{ keys: S3AccessKey[] } & S3Connection>(
65
66
  url('/api/auth/s3-keys'),
@@ -93,6 +94,7 @@ export function useS3Keys(config: ExplorerConfig) {
93
94
  */
94
95
  async function create(req: S3KeyRequest): Promise<S3KeyCreated | null> {
95
96
  error.value = null;
97
+ errorHint.value = null;
96
98
  try {
97
99
  const body = await api.jsonFetch<S3KeyCreated>(url('/api/auth/s3-keys'), {
98
100
  method: 'POST',
@@ -117,6 +119,7 @@ export function useS3Keys(config: ExplorerConfig) {
117
119
 
118
120
  async function setDisabled(id: number, disabled: boolean): Promise<void> {
119
121
  error.value = null;
122
+ errorHint.value = null;
120
123
  try {
121
124
  await api.jsonFetch(url(`/api/auth/s3-keys/${id}/state`), {
122
125
  method: 'POST',
@@ -131,6 +134,7 @@ export function useS3Keys(config: ExplorerConfig) {
131
134
 
132
135
  async function remove(id: number): Promise<void> {
133
136
  error.value = null;
137
+ errorHint.value = null;
134
138
  try {
135
139
  await api.jsonFetch(url(`/api/auth/s3-keys/${id}`), { method: 'DELETE' });
136
140
  // A revealed secret belonging to the key just revoked must go with it.
@@ -163,6 +167,7 @@ export function useS3Keys(config: ExplorerConfig) {
163
167
  loading,
164
168
  loaded,
165
169
  error,
170
+ errorHint,
166
171
  canMint,
167
172
  revealed,
168
173
  guideKey,
@@ -16,6 +16,7 @@ import type { ExplorerConfig } from '../types/ExplorerConfig';
16
16
  import type { SSHConnection, SSHPublicKey } from '../types/SSHKeys';
17
17
  import { connectionsBase } from './useConnections';
18
18
  import { useFileApi } from './useFileApi';
19
+ import { serverWords } from '../lib/errorWords';
19
20
 
20
21
  export function useSSHKeys(config: ExplorerConfig) {
21
22
  const api = useFileApi(config);
@@ -29,17 +30,14 @@ export function useSSHKeys(config: ExplorerConfig) {
29
30
  const error = ref<string | null>(null);
30
31
  const canAdd = ref<boolean | null>(null);
31
32
 
33
+ /** A refusal, said — lib/errorWords `serverWords`, the one rule every
34
+ * connection panel shares (it used to print the server's `error` field as
35
+ * it came: an environment variable, to a regular user). ⚠ The locale is
36
+ * passed because what comes back may be the SERVER's own sentence: it
37
+ * never met a translator, and an Arabic panel needs its machine runs
38
+ * isolated (lib/direction `foreignText`). */
32
39
  function messageOf(e: unknown): string {
33
- const err = e as { message?: string; detail?: string } | null;
34
- if (err?.detail) {
35
- try {
36
- const parsed = JSON.parse(err.detail) as { error?: string };
37
- if (parsed?.error) return parsed.error;
38
- } catch {
39
- /* not JSON */
40
- }
41
- }
42
- return err?.message || String(e);
40
+ return serverWords(e, config.locale);
43
41
  }
44
42
 
45
43
  function statusOf(e: unknown): number | undefined {
@@ -20,6 +20,7 @@ import type { ExplorerConfig } from '../types/ExplorerConfig';
20
20
  import type { ApiToken, ApiTokenCreated, ApiTokenRequest } from '../types/Tokens';
21
21
  import { connectionsBase } from './useConnections';
22
22
  import { useFileApi } from './useFileApi';
23
+ import { serverWords } from '../lib/errorWords';
23
24
 
24
25
  export function useTokens(config: ExplorerConfig) {
25
26
  const api = useFileApi(config);
@@ -40,19 +41,14 @@ export function useTokens(config: ExplorerConfig) {
40
41
  /** The secret, held in memory for exactly as long as the user is looking. */
41
42
  const revealed = ref<ApiTokenCreated | null>(null);
42
43
 
44
+ /** A refusal, said — lib/errorWords `serverWords`, the one rule every
45
+ * connection panel shares (it used to print the server's `error` field as
46
+ * it came: an environment variable, to a regular user). ⚠ The locale is
47
+ * passed because what comes back may be the SERVER's own sentence: it
48
+ * never met a translator, and an Arabic panel needs its machine runs
49
+ * isolated (lib/direction `foreignText`). */
43
50
  function messageOf(e: unknown): string {
44
- const err = e as { message?: string; detail?: string } | null;
45
- // The backend's own words beat a status line: "scope 'admin' is not
46
- // available here" tells the user what to change; "403" does not.
47
- if (err?.detail) {
48
- try {
49
- const parsed = JSON.parse(err.detail) as { error?: string };
50
- if (parsed?.error) return parsed.error;
51
- } catch {
52
- /* not JSON — fall through */
53
- }
54
- }
55
- return err?.message || String(e);
51
+ return serverWords(e, config.locale);
56
52
  }
57
53
 
58
54
  function statusOf(e: unknown): number | undefined {
@@ -36,6 +36,12 @@
36
36
 
37
37
  import type { ExplorerConfig } from '../types/ExplorerConfig';
38
38
  import type { FileApi } from './useFileApi';
39
+ import { networkFailure, requestFailure, wordsIn } from '../lib/errorWords';
40
+ // ⚠ An upload is an ACTION: it is never folded into the shared "no
41
+ // connection" notice, because the person is waiting to be told whether their
42
+ // file went (lib/connection).
43
+ import { noteRequestFailed, noteRequestSucceeded } from '../lib/connection';
44
+ import { resolveLocale } from '../locales/resolve';
39
45
  import {
40
46
  clearResume,
41
47
  defaultResumeStorage,
@@ -367,7 +373,12 @@ export function useUploadChunked(
367
373
  }
368
374
  if (next === -2) continue; // offset moved under us; re-slice
369
375
  if (next < 0) throw lastErr ?? new Error('chunk upload failed');
370
- if (next <= acked) throw new Error(`upload stalled at byte ${acked}`);
376
+ // Said, not "upload stalled at byte 8388608": the row is read by a
377
+ // person, and the byte count is only for whoever is debugging.
378
+ if (next <= acked) {
379
+ console.warn('[filex] upload stalled at byte', acked);
380
+ throw new Error(wordsIn(resolveLocale(config.locale))('err.upload_stalled'));
381
+ }
371
382
  acked = next;
372
383
  report();
373
384
  saveResume(storage, key, {
@@ -455,6 +466,7 @@ export function useUploadChunked(
455
466
  };
456
467
  xhr.onload = () => {
457
468
  inFlight = null;
469
+ noteRequestSucceeded();
458
470
  if (xhr.status >= 200 && xhr.status < 300) {
459
471
  try {
460
472
  const body = JSON.parse(xhr.responseText) as StatusResponse;
@@ -466,16 +478,14 @@ export function useUploadChunked(
466
478
  }
467
479
  return;
468
480
  }
469
- const err = new Error(
470
- `chunk ${start}-${end - 1} → ${xhr.status}`,
471
- ) as Error & { status?: number; detail?: string };
472
- err.status = xhr.status;
473
- err.detail = xhr.responseText.slice(0, 200);
474
- reject(err);
481
+ // Said (lib/errorWords): the upload row used to read
482
+ // "chunk 0-8388607 → 500". Status and body still ride along.
483
+ reject(requestFailure(xhr.status, xhr.responseText || '', resolveLocale(config.locale)));
475
484
  };
476
485
  xhr.onerror = () => {
477
486
  inFlight = null;
478
- reject(new Error(`chunk ${start}-${end - 1}: network error`));
487
+ noteRequestFailed('action');
488
+ reject(networkFailure(resolveLocale(config.locale)));
479
489
  };
480
490
  xhr.onabort = () => {
481
491
  inFlight = null;
@@ -12,6 +12,8 @@
12
12
  * doesn't reject the request when the viewer fetches the file body
13
13
  * directly (instead of letting the browser do it via `<img src>` etc).
14
14
  */
15
+ import { requestFailure } from '../lib/errorWords';
16
+
15
17
  export interface ViewerFetchOptions {
16
18
  url: string;
17
19
  headers?: Record<string, string>;
@@ -26,10 +28,11 @@ async function fetchOk(opts: ViewerFetchOptions): Promise<Response> {
26
28
  signal: opts.signal,
27
29
  });
28
30
  if (!res.ok) {
31
+ // Said, not printed (lib/errorWords): the viewers used to show
32
+ // "404 Not Found — {…}" in the pane. The viewer re-says it in its own
33
+ // language through `sayFailure(err, …, { t })`.
29
34
  const text = await res.text().catch(() => '');
30
- throw new Error(
31
- `${res.status} ${res.statusText}${text ? ' — ' + text.slice(0, 200) : ''}`,
32
- );
35
+ throw requestFailure(res.status, text, undefined);
33
36
  }
34
37
  return res;
35
38
  }