@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.
Files changed (233) 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-BuhZOAx6.js → DrawioViewer-Bgrj8-zG.js} +45 -44
  7. package/dist/DrawioViewer-Bgrj8-zG.js.map +1 -0
  8. package/dist/{EpubViewer-DS3mb3k8.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-CIrOn63I.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 +5241 -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 +1207 -343
  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 +47 -17
  73. package/src/components/RecoveryKeyModal.vue +1 -1
  74. package/src/components/RowActions.vue +134 -0
  75. package/src/components/S3KeysPanel.vue +98 -56
  76. package/src/components/SSHKeysPanel.vue +79 -26
  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 -27
  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 +121 -0
  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 +522 -119
  186. package/src/locales/resolve.ts +17 -8
  187. package/src/locales/tr.ts +539 -155
  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 +2895 -241
  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 +58 -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-CLNevfPE.js +0 -124
  216. package/dist/ArchiveViewer-CLNevfPE.js.map +0 -1
  217. package/dist/CsvViewer-K8XA3z7N.js +0 -141
  218. package/dist/CsvViewer-K8XA3z7N.js.map +0 -1
  219. package/dist/DrawioViewer-BuhZOAx6.js.map +0 -1
  220. package/dist/EpubViewer-DS3mb3k8.js.map +0 -1
  221. package/dist/IpynbViewer-BTk7p4Dn.js +0 -184
  222. package/dist/IpynbViewer-BTk7p4Dn.js.map +0 -1
  223. package/dist/MermaidViewer-DqucH_io.js +0 -137
  224. package/dist/MermaidViewer-DqucH_io.js.map +0 -1
  225. package/dist/PsdViewer-CwiVENmq.js +0 -122
  226. package/dist/PsdViewer-CwiVENmq.js.map +0 -1
  227. package/dist/TiffViewer-CpewSVmQ.js +0 -142
  228. package/dist/TiffViewer-CpewSVmQ.js.map +0 -1
  229. package/dist/Viewer3D-CIrOn63I.js.map +0 -1
  230. package/dist/index-Dx0IbBcI.js +0 -26831
  231. package/dist/index-Dx0IbBcI.js.map +0 -1
  232. package/dist/useViewerFetch-czqbd2Lj.js +0 -25
  233. package/dist/useViewerFetch-czqbd2Lj.js.map +0 -1
@@ -0,0 +1,203 @@
1
+ /**
2
+ * pdfFieldsGeom — where a `pdf-fields` box is, as pure arithmetic.
3
+ *
4
+ * A field's place is stored as FRACTIONS of the page's rendered viewport
5
+ * (0..1, origin top-left, CropBox and /Rotate already applied by pdf.js's
6
+ * viewport), so it survives any zoom and any screen. Three conversions are
7
+ * needed around that and they all live here, DOM-free:
8
+ *
9
+ * fraction ↔ pixel the drag on screen (a page drawn `size` CSS px wide)
10
+ * fraction ↔ PDF the rectangle in PDF user space (points, origin
11
+ * bottom-left of the UNROTATED page) that a stamping
12
+ * plugin needs, for /Rotate 0, 90, 180 and 270
13
+ *
14
+ * The rotation maps are the ones pdf.js's `PageViewport` applies: 90 is the
15
+ * page turned clockwise on screen, so the unrotated top-left corner lands at
16
+ * the rendered top-right; 270 turns it counter-clockwise.
17
+ */
18
+
19
+ /** A box as fractions of the rendered page (0..1, origin top-left). */
20
+ export interface FracRect {
21
+ x: number;
22
+ y: number;
23
+ w: number;
24
+ h: number;
25
+ }
26
+
27
+ /** A box in CSS pixels on a drawn page. */
28
+ export interface PixelRect {
29
+ left: number;
30
+ top: number;
31
+ width: number;
32
+ height: number;
33
+ }
34
+
35
+ /** A box in PDF user space: points, origin bottom-left of the unrotated page. */
36
+ export interface PdfRect {
37
+ x: number;
38
+ y: number;
39
+ w: number;
40
+ h: number;
41
+ }
42
+
43
+ /** The rendered viewport at scale 1: its size after rotation, and that rotation. */
44
+ export interface PageBox {
45
+ width: number;
46
+ height: number;
47
+ rotation: number;
48
+ }
49
+
50
+ export type Rotation = 0 | 90 | 180 | 270;
51
+
52
+ /** Any degree count as one of the four the PDF spec allows. */
53
+ export function normRotation(r: number | undefined | null): Rotation {
54
+ const n = Number(r);
55
+ if (!Number.isFinite(n)) return 0;
56
+ const m = ((Math.round(n / 90) * 90) % 360 + 360) % 360;
57
+ return m as Rotation;
58
+ }
59
+
60
+ /** The smallest box the editor lets a field shrink to (fractions). */
61
+ export const MIN_FIELD_FRAC = { w: 0.02, h: 0.01 };
62
+
63
+ const clamp01 = (v: number) => Math.min(1, Math.max(0, v));
64
+
65
+ /** Inside the page, at least the minimum size, no NaN. */
66
+ export function clampFrac(r: FracRect): FracRect {
67
+ const w = Math.min(1, Math.max(MIN_FIELD_FRAC.w, Number(r.w) || 0));
68
+ const h = Math.min(1, Math.max(MIN_FIELD_FRAC.h, Number(r.h) || 0));
69
+ const x = Math.min(1 - w, clamp01(Number(r.x) || 0));
70
+ const y = Math.min(1 - h, clamp01(Number(r.y) || 0));
71
+ return { x, y, w, h };
72
+ }
73
+
74
+ /** A box drawn between two points (any corner order), as fractions. */
75
+ export function rectFromPoints(a: { x: number; y: number }, b: { x: number; y: number }): FracRect {
76
+ return clampFrac({
77
+ x: Math.min(a.x, b.x),
78
+ y: Math.min(a.y, b.y),
79
+ w: Math.abs(a.x - b.x),
80
+ h: Math.abs(a.y - b.y),
81
+ });
82
+ }
83
+
84
+ export function fracToPixel(r: FracRect, size: { width: number; height: number }): PixelRect {
85
+ return {
86
+ left: r.x * size.width,
87
+ top: r.y * size.height,
88
+ width: r.w * size.width,
89
+ height: r.h * size.height,
90
+ };
91
+ }
92
+
93
+ export function pixelToFrac(px: PixelRect, size: { width: number; height: number }): FracRect {
94
+ const W = size.width || 1;
95
+ const H = size.height || 1;
96
+ return { x: px.left / W, y: px.top / H, w: px.width / W, h: px.height / H };
97
+ }
98
+
99
+ /** A pointer position on a drawn page (CSS px from its top-left) as fractions. */
100
+ export function pointToFrac(p: { x: number; y: number }, size: { width: number; height: number }): { x: number; y: number } {
101
+ return { x: clamp01(p.x / (size.width || 1)), y: clamp01(p.y / (size.height || 1)) };
102
+ }
103
+
104
+ /** The unrotated page's size in points, from the rendered viewport. */
105
+ export function unrotatedSize(page: PageBox): { width: number; height: number } {
106
+ const rot = normRotation(page.rotation);
107
+ return rot === 90 || rot === 270 ? { width: page.height, height: page.width } : { width: page.width, height: page.height };
108
+ }
109
+
110
+ /** A rendered point (px, origin top-left) → the unrotated page's top-left system (points). */
111
+ function renderedToPage(X: number, Y: number, page: PageBox): { x: number; y: number } {
112
+ const rot = normRotation(page.rotation);
113
+ const W = page.width;
114
+ const H = page.height;
115
+ switch (rot) {
116
+ case 90:
117
+ return { x: Y, y: W - X };
118
+ case 180:
119
+ return { x: W - X, y: H - Y };
120
+ case 270:
121
+ return { x: H - Y, y: X };
122
+ default:
123
+ return { x: X, y: Y };
124
+ }
125
+ }
126
+
127
+ /** Inverse of `renderedToPage`. */
128
+ function pageToRendered(x: number, y: number, page: PageBox): { X: number; Y: number } {
129
+ const rot = normRotation(page.rotation);
130
+ const W = page.width;
131
+ const H = page.height;
132
+ switch (rot) {
133
+ case 90:
134
+ return { X: W - y, Y: x };
135
+ case 180:
136
+ return { X: W - x, Y: H - y };
137
+ case 270:
138
+ return { X: y, Y: H - x };
139
+ default:
140
+ return { X: x, Y: y };
141
+ }
142
+ }
143
+
144
+ /** Fractions of the rendered page → PDF user-space rectangle (points, origin bottom-left, unrotated). */
145
+ export function fracToPdf(r: FracRect, page: PageBox): PdfRect {
146
+ const a = renderedToPage(r.x * page.width, r.y * page.height, page);
147
+ const b = renderedToPage((r.x + r.w) * page.width, (r.y + r.h) * page.height, page);
148
+ const { height: ph } = unrotatedSize(page);
149
+ const left = Math.min(a.x, b.x);
150
+ const right = Math.max(a.x, b.x);
151
+ const top = Math.min(a.y, b.y);
152
+ const bottom = Math.max(a.y, b.y);
153
+ return round({ x: left, y: ph - bottom, w: right - left, h: bottom - top });
154
+ }
155
+
156
+ /** PDF user-space rectangle → fractions of the rendered page. */
157
+ export function pdfToFrac(r: PdfRect, page: PageBox): FracRect {
158
+ const { height: ph } = unrotatedSize(page);
159
+ const topLeft = { x: r.x, y: ph - (r.y + r.h) };
160
+ const bottomRight = { x: r.x + r.w, y: ph - r.y };
161
+ const a = pageToRendered(topLeft.x, topLeft.y, page);
162
+ const b = pageToRendered(bottomRight.x, bottomRight.y, page);
163
+ const W = page.width || 1;
164
+ const H = page.height || 1;
165
+ return round({
166
+ x: Math.min(a.X, b.X) / W,
167
+ y: Math.min(a.Y, b.Y) / H,
168
+ w: Math.abs(a.X - b.X) / W,
169
+ h: Math.abs(a.Y - b.Y) / H,
170
+ });
171
+ }
172
+
173
+ function round<T extends Record<string, number>>(o: T): T {
174
+ const out: Record<string, number> = {};
175
+ for (const [k, v] of Object.entries(o)) out[k] = Math.round(v * 1e6) / 1e6;
176
+ return out as T;
177
+ }
178
+
179
+ /**
180
+ * v3 §3.2 — the width to draw a page at so the WHOLE page fits the box.
181
+ *
182
+ * ⚠⚠ The complaint that started the round: a signature page you had to
183
+ * scroll in two directions. The document was fitted to the column's WIDTH, so
184
+ * a portrait page in a landscape window ran off the bottom — the boxes were
185
+ * then found by hunting rather than by reading. Fitting means whichever of
186
+ * the two constraints binds.
187
+ *
188
+ * `gap` is the breathing room around the page (the scroll box's padding),
189
+ * taken off the HEIGHT: a page that touches the toolbar reads as clipped.
190
+ * A box with no height yet (before layout) falls back to the width, which is
191
+ * the old behaviour and is corrected on the first resize observation.
192
+ */
193
+ export function fitPageWidth(
194
+ box: { width: number; height: number },
195
+ page: { width: number; height: number },
196
+ gap = 16,
197
+ ): number {
198
+ const w = Math.max(0, box.width);
199
+ const h = Math.max(0, box.height) - gap;
200
+ if (!page.width || !page.height || h <= 0) return w;
201
+ const byHeight = (h * page.width) / page.height;
202
+ return Math.max(120, Math.min(w, byHeight));
203
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * pdfjsLoader — the ONE way this package brings pdf.js in.
3
+ *
4
+ * `pdfjs-dist` is ~600 KB and an optional peer, so it is imported lazily and
5
+ * its absence is an answer (`null`), not an exception. The worker script is
6
+ * a separate file the browser fetches: the host may point at its own copy
7
+ * (`pdfWorkerUrl` in the explorer config) and otherwise the pinned version
8
+ * is taken from jsDelivr. `viewers/PdfViewer.vue` and the sign track's
9
+ * `pdf-fields` node both open documents through here, so the legacy build,
10
+ * the worker rule and the fallback cannot drift between them.
11
+ */
12
+
13
+ /** The subset of the library the callers use; `any` on purpose — pdf.js ships no stable types for the legacy build. */
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ export type PdfjsLib = any;
16
+
17
+ let cached: Promise<PdfjsLib | null> | null = null;
18
+
19
+ /** The worker URL for a pdf.js version when the host names none. */
20
+ export function defaultPdfWorkerUrl(version: string): string {
21
+ return `https://cdn.jsdelivr.net/npm/pdfjs-dist@${version}/legacy/build/pdf.worker.min.mjs`;
22
+ }
23
+
24
+ /**
25
+ * The library, or `null` when it cannot be loaded (not installed, blocked).
26
+ * The first caller's `workerUrl` wins; pdf.js holds one global worker path.
27
+ */
28
+ export function loadPdfjs(workerUrl?: string | null): Promise<PdfjsLib | null> {
29
+ if (!cached) {
30
+ cached = (async () => {
31
+ try {
32
+ // The legacy build: broader browser support and a classic worker
33
+ // bootstrap (no module-worker requirement).
34
+ const mod = await import(/* @vite-ignore */ 'pdfjs-dist/legacy/build/pdf');
35
+ const lib = mod.default ?? mod;
36
+ if (lib.GlobalWorkerOptions && !lib.GlobalWorkerOptions.workerSrc) {
37
+ lib.GlobalWorkerOptions.workerSrc = workerUrl || defaultPdfWorkerUrl(lib.version || '5.7.284');
38
+ }
39
+ return lib;
40
+ } catch {
41
+ return null;
42
+ }
43
+ })();
44
+ }
45
+ return cached;
46
+ }
47
+
48
+ /** Tests only: forget the cached import so a mocked module is picked up. */
49
+ export function resetPdfjsLoader(): void {
50
+ cached = null;
51
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * personName — how filex names a person on screen. ONE rule, every surface.
3
+ *
4
+ * ⚠⚠ QA, 2026-09-21: one administrator was "admin2" in the explorer's Owner
5
+ * column, "admin@local" in notifications, the permissions panel and the admin
6
+ * tables, and a full name on signatures. Each place had its own chain —
7
+ * display name → username → e-mail (the Owner column, on the server), display
8
+ * name → e-mail (the share dialog, the details panel, the admin header),
9
+ * display name → e-mail local part (the presence strip). One chain now:
10
+ *
11
+ * the name the server already worked out (`name` — a row's `owner_name`,
12
+ * `user_name`, `creator_name`: model.PersonLabel on the server, the same
13
+ * rule) → the display name → the username → the e-mail address.
14
+ *
15
+ * The server's twin is `model.PersonLabel` (backend/internal/model/user.go);
16
+ * web/tests/lib/personName.test.ts and the Go test hold both to the same
17
+ * cases. docs/CONTRIBUTING.md → "A person is named one way". The e-mail is a
18
+ * second line or a tooltip where it helps — never the name while a name exists.
19
+ */
20
+ export interface PersonLike {
21
+ /** A label the server already resolved by the same rule. */
22
+ name?: string | null;
23
+ display_name?: string | null;
24
+ username?: string | null;
25
+ email?: string | null;
26
+ }
27
+
28
+ export function personName(p: PersonLike | null | undefined): string {
29
+ if (!p) return '';
30
+ for (const v of [p.name, p.display_name, p.username, p.email]) {
31
+ const s = typeof v === 'string' ? v.trim() : '';
32
+ if (s) return s;
33
+ }
34
+ return '';
35
+ }
36
+
37
+ /**
38
+ * The letter for a person's avatar circle: the first character of their name
39
+ * — a whole character (an emoji or an astral letter is a surrogate PAIR, and
40
+ * one half of it prints as �), upper-cased in the viewer's language (Turkish
41
+ * `i` → `İ`, never `I`). '' when there is no name; the caller draws a glyph.
42
+ */
43
+ export function personInitial(p: PersonLike | null | undefined, tag?: string): string {
44
+ const n = personName(p);
45
+ if (!n) return '';
46
+ return [...n][0].toLocaleUpperCase(tag);
47
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * pluginApplies — does an action's `applies` rule accept this selection?
3
+ *
4
+ * The client-side mirror of `wasmplugin.Matches` (backend/internal/wasmplugin/
5
+ * manifest.go). The server re-checks on every run; this copy only decides
6
+ * which rows the menu shows, so the two must agree or a row appears that the
7
+ * server then refuses with 422. Keep the rules in the same order as the Go:
8
+ *
9
+ * 1. an empty selection never matches;
10
+ * 2. more than one item needs `multi`;
11
+ * 3. `min` / `max` bound the count when > 0;
12
+ * 4. every item's kind must equal the rule's (`any` accepts both; an absent
13
+ * kind means `file`);
14
+ * 5. `state` / `no_state` (v2): every item must carry at least one of the
15
+ * rule's `state` keys and none of its `no_state` keys;
16
+ * 6. when the rule names extensions or mime types, every item must match
17
+ * at least one of EITHER list — an ext hit is enough, a mime hit is
18
+ * enough; with both lists empty anything of the right kind passes.
19
+ *
20
+ * ⚠⚠ The state keys a ROW carries are namespaced (`app_state: ["sign:pending"]`)
21
+ * while the keys a RULE names are bare (`state: ["pending"]`) — an action can
22
+ * only ever ask about its OWN plugin's keys. So the mirror needs to be told
23
+ * which plugin is asking (`appliesToNodes(rule, nodes, plugin)`), strips
24
+ * `"<plugin>:"` off the row's keys and compares what is left. Compare the two
25
+ * without stripping and every state rule silently never matches: the menu row
26
+ * simply is not there, which reads as "the app is broken", not as a bug here.
27
+ */
28
+ import type { PluginApplies } from '../types/Plugins';
29
+
30
+ /** What the rule sees of a selected row. */
31
+ export interface AppliesItem {
32
+ kind: 'file' | 'dir' | string;
33
+ /** Lower-case, no dot. */
34
+ ext?: string | null;
35
+ mime?: string | null;
36
+ /** BARE state keys the asking plugin keeps on this item (prefix stripped). */
37
+ state?: string[];
38
+ }
39
+
40
+ /** A listing row, as the explorer holds it — only the three fields matter. */
41
+ export interface AppliesNodeLike {
42
+ type?: string;
43
+ extension?: string | null;
44
+ mime_type?: string | null;
45
+ basename?: string;
46
+ /** `<plugin>:<key>` as the listing carries it. */
47
+ app_state?: string[];
48
+ }
49
+
50
+ /** The rule's own keys out of a row's namespaced ones. `''` asks about nobody. */
51
+ export function stateKeysOf(node: AppliesNodeLike, plugin: string | undefined): string[] {
52
+ if (!plugin || !Array.isArray(node.app_state)) return [];
53
+ const prefix = `${plugin}:`;
54
+ return node.app_state
55
+ .filter((k): k is string => typeof k === 'string' && k.startsWith(prefix))
56
+ .map((k) => k.slice(prefix.length));
57
+ }
58
+
59
+ /** Reduce a listing row to what the rule reads. */
60
+ export function appliesItemOf(node: AppliesNodeLike, plugin?: string): AppliesItem {
61
+ let ext = String(node.extension ?? '').toLowerCase();
62
+ if (!ext && node.basename) {
63
+ const dot = node.basename.lastIndexOf('.');
64
+ if (dot > 0) ext = node.basename.slice(dot + 1).toLowerCase();
65
+ }
66
+ return {
67
+ kind: node.type === 'dir' ? 'dir' : 'file',
68
+ ext,
69
+ mime: node.mime_type ?? '',
70
+ state: stateKeysOf(node, plugin),
71
+ };
72
+ }
73
+
74
+ function mimeMatches(pattern: string, mime: string): boolean {
75
+ if (pattern === mime) return true;
76
+ if (pattern.endsWith('/*')) return mime.startsWith(pattern.slice(0, -1));
77
+ return false;
78
+ }
79
+
80
+ /** The rule against a selection already reduced to items. */
81
+ export function appliesMatches(rule: PluginApplies | null | undefined, items: AppliesItem[]): boolean {
82
+ const a = rule ?? {};
83
+ const n = items.length;
84
+ if (n === 0) return false;
85
+ if (n > 1 && !a.multi) return false;
86
+ const min = a.min ?? 0;
87
+ const max = a.max ?? 0;
88
+ if (min > 0 && n < min) return false;
89
+ if (max > 0 && n > max) return false;
90
+ const kind = a.kind || 'file';
91
+ const exts = (a.ext ?? []).map((e) => e.toLowerCase());
92
+ const mimes = (a.mime ?? []).map((m) => m.toLowerCase());
93
+ const wantState = a.state ?? [];
94
+ const banState = a.no_state ?? [];
95
+ for (const it of items) {
96
+ if (kind !== 'any' && kind !== it.kind) return false;
97
+ const has = it.state ?? [];
98
+ if (wantState.length > 0 && !wantState.some((k) => has.includes(k))) return false;
99
+ if (banState.some((k) => has.includes(k))) return false;
100
+ if (exts.length === 0 && mimes.length === 0) continue;
101
+ const ext = String(it.ext ?? '').toLowerCase();
102
+ let ok = exts.includes(ext);
103
+ if (!ok) {
104
+ const mime = String(it.mime ?? '').toLowerCase();
105
+ ok = mimes.some((m) => mimeMatches(m, mime));
106
+ }
107
+ if (!ok) return false;
108
+ }
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * The rule against listing rows. `plugin` is the ASKING action's plugin —
114
+ * without it a `state` / `no_state` rule can never be satisfied, so pass it
115
+ * wherever the action is known.
116
+ */
117
+ export function appliesToNodes(
118
+ rule: PluginApplies | null | undefined,
119
+ nodes: AppliesNodeLike[],
120
+ plugin?: string,
121
+ ): boolean {
122
+ return appliesMatches(rule, nodes.map((n) => appliesItemOf(n, plugin)));
123
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * pluginLabel — a plugin's multilingual text, read in the viewer's language.
3
+ *
4
+ * Mirrors `wire.Text.Get` in backend/pkg/pluginkit/wire/wire.go: the
5
+ * requested language, then English, then whatever is there. One place, so an
6
+ * action row, a surface title and a footer button cannot fall back
7
+ * differently.
8
+ */
9
+ import type { PluginText } from '../types/Plugins';
10
+
11
+ /** The text for `locale`, falling back to `en`, then to any non-empty value. */
12
+ export function labelOf(text: PluginText | string | null | undefined, locale: string): string {
13
+ if (!text) return '';
14
+ if (typeof text === 'string') return text;
15
+ const lang = String(locale || 'en').toLowerCase().split('-')[0];
16
+ const direct = text[lang];
17
+ if (typeof direct === 'string' && direct !== '') return direct;
18
+ const en = text.en;
19
+ if (typeof en === 'string' && en !== '') return en;
20
+ for (const v of Object.values(text)) {
21
+ if (typeof v === 'string' && v !== '') return v;
22
+ }
23
+ return '';
24
+ }
25
+
26
+ /**
27
+ * The app's own words for THIS reader and nobody else: the requested
28
+ * language, or its base language (`pt` answers `pt-br`), and otherwise ''.
29
+ *
30
+ * Unlike `labelOf` it does NOT fall back to English — which is the whole
31
+ * point of `appTextOr` below.
32
+ */
33
+ export function labelIn(text: PluginText | string | null | undefined, locale: string): string {
34
+ if (!text) return '';
35
+ if (typeof text === 'string') return text;
36
+ const lang = String(locale || 'en').toLowerCase().split('-')[0];
37
+ const direct = text[lang];
38
+ return typeof direct === 'string' && direct !== '' ? direct : '';
39
+ }
40
+
41
+ /**
42
+ * An app's text where FILEX HAS WORDS OF ITS OWN for the same thing — a
43
+ * caption an app may override, an empty state, a progress line.
44
+ *
45
+ * ⚠⚠ THE ORDER IS THE WHOLE POINT. The obvious spelling,
46
+ * `labelOf(appText, locale) || t(key)`, reads as "the app first, filex if the
47
+ * app said nothing" — but `labelOf` falls back to ENGLISH, and English is
48
+ * truthy, so the host string is unreachable for every reader whose language
49
+ * the app does not speak. Measured in v0.43.0 on the signing app's date box:
50
+ * an app shipping en/tr/es/de/fr, a reader in Arabic, a language pack holding
51
+ * filex's own Arabic for those very captions — and three English words in the
52
+ * middle of an Arabic screen, because the app's English won.
53
+ *
54
+ * So: the app's words for this reader, then FILEX's words, and only then
55
+ * whatever other language the app has — the last resort, and labelled as one.
56
+ *
57
+ * ⚠ This is for a host string that TRANSLATES THE SAME THING. Where the host
58
+ * string is a generic stand-in for something the app NAMES (an action's
59
+ * label falling back to "Confirm"), the app's own name in another language
60
+ * says more than filex's placeholder, and `labelOf(…) || t(…)` is right.
61
+ */
62
+ export function appTextOr(
63
+ text: PluginText | string | null | undefined,
64
+ locale: string,
65
+ host: () => string,
66
+ ): string {
67
+ const mine = labelIn(text, locale);
68
+ if (mine) return mine;
69
+ const ours = host();
70
+ if (ours) return ours;
71
+ return labelOf(text, locale);
72
+ }