@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
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePublicLink — a visitor with a link and no account, whatever the link is.
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ v3 §1: "one public surface, not three". A share (`/s/<token>`), a file
|
|
5
|
+
* request (`/d/<token>`) and an app plugin's page (a share carrying a
|
|
6
|
+
* `page_id`) used to be two rendering stacks and three layouts, so the PIN
|
|
7
|
+
* box of a signature request looked nothing like the PIN box of a download
|
|
8
|
+
* and neither carried the instance's own colours. They are the same walk:
|
|
9
|
+
*
|
|
10
|
+
* info → (PIN when required) → the body → the states it can end in
|
|
11
|
+
*
|
|
12
|
+
* Only the BODY differs, and only the caller decides that. Everything here —
|
|
13
|
+
* the fetch, the 401/404/410 reading, the PIN attempt and its lock, the
|
|
14
|
+
* plugin conversation when the link carries one — is shared, because every
|
|
15
|
+
* one of those is a place the three used to disagree.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ Nothing is stored on this side: no session, no token copy, no PIN. The
|
|
18
|
+
* unlock cookie is the server's and HttpOnly.
|
|
19
|
+
*
|
|
20
|
+
* ⚠ The endpoints are `/api/public/s/{token}` and `/api/public/d/{token}`;
|
|
21
|
+
* the app-page walk (`/api/p/{token}`) is the same shape at a different
|
|
22
|
+
* root, which is why the root is an argument. That is what lets `/p/*` be
|
|
23
|
+
* retired without a second implementation living on for the transition.
|
|
24
|
+
*/
|
|
25
|
+
import { ref, type Ref } from 'vue';
|
|
26
|
+
import type { PluginRunResult, PluginSurface, PluginViewEventBody } from '../types/Plugins';
|
|
27
|
+
import type { PublicFailure, PublicRequestInfo, PublicShareInfo } from '../types/Public';
|
|
28
|
+
import { usePluginSurface, type PluginSurfaceStore } from './usePluginSurface';
|
|
29
|
+
import { foreignText } from '../lib/direction';
|
|
30
|
+
import { checkDropFiles, type DropRefusal } from '../lib/dropLimits';
|
|
31
|
+
|
|
32
|
+
export type PublicStatus =
|
|
33
|
+
| 'loading'
|
|
34
|
+
| 'pin'
|
|
35
|
+
/** The body may be drawn (a file, a folder, an upload box, a surface). */
|
|
36
|
+
| 'ready'
|
|
37
|
+
/** A surface answered `202` — the job is queued and the visitor is done. */
|
|
38
|
+
| 'accepted'
|
|
39
|
+
| 'done'
|
|
40
|
+
| 'not_found'
|
|
41
|
+
| 'gone'
|
|
42
|
+
| 'error';
|
|
43
|
+
|
|
44
|
+
export type PinFailure = '' | 'wrong' | 'locked';
|
|
45
|
+
|
|
46
|
+
/** A refused answer: the HTTP status and the server's `error` code. */
|
|
47
|
+
export class PublicLinkError extends Error {
|
|
48
|
+
constructor(
|
|
49
|
+
public status: number,
|
|
50
|
+
public code: string,
|
|
51
|
+
message?: string,
|
|
52
|
+
) {
|
|
53
|
+
super(message || code || String(status));
|
|
54
|
+
this.name = 'PublicLinkError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Builds the addresses of one link's walk. */
|
|
59
|
+
export type PublicRoot = (tail: string) => string;
|
|
60
|
+
|
|
61
|
+
function root(base: string | undefined, prefix: string, token: string): PublicRoot {
|
|
62
|
+
const origin = (base ?? '').replace(/\/+$/, '');
|
|
63
|
+
return (tail = '') => `${origin}${prefix}${encodeURIComponent(token)}${tail}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `/api/public/s/{token}` — a share (download, folder, or an app's page). */
|
|
67
|
+
export function shareRoot(base: string | undefined, token: string): PublicRoot {
|
|
68
|
+
return root(base, '/api/public/s/', token);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** `/api/public/d/{token}` — a file request. */
|
|
72
|
+
export function requestRoot(base: string | undefined, token: string): PublicRoot {
|
|
73
|
+
return root(base, '/api/public/d/', token);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* `/api/p/{token}` — an app plugin's page.
|
|
78
|
+
*
|
|
79
|
+
* ⚠ Retired by v3 (§1: "`/p/*` — retired, 301 to the share"). Kept because
|
|
80
|
+
* links already in people's mailboxes point at it and a 301 only helps a
|
|
81
|
+
* browser that follows one; nothing new should be built on it.
|
|
82
|
+
*/
|
|
83
|
+
export function pageRoot(base: string | undefined, token: string): PublicRoot {
|
|
84
|
+
return root(base, '/api/p/', token);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PublicLinkOptions {
|
|
88
|
+
/** API origin; empty = same origin. */
|
|
89
|
+
base?: string;
|
|
90
|
+
/** The visitor's language, sent as `Accept-Language` so a plugin answers in it. */
|
|
91
|
+
locale: () => string;
|
|
92
|
+
/** The words for an answer that carries nothing to show. */
|
|
93
|
+
errorText: () => string;
|
|
94
|
+
fetchImpl?: typeof fetch;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function publicLinkClient<TInfo>(url: PublicRoot, opts: PublicLinkOptions) {
|
|
98
|
+
const doFetch = (...args: Parameters<typeof fetch>) => (opts.fetchImpl ?? fetch)(...args);
|
|
99
|
+
|
|
100
|
+
async function call<T>(tail: string, init: { method?: string; body?: unknown } = {}): Promise<{ status: number; body: T }> {
|
|
101
|
+
const headers: Record<string, string> = { Accept: 'application/json', 'Accept-Language': opts.locale() };
|
|
102
|
+
if (init.body !== undefined) headers['Content-Type'] = 'application/json';
|
|
103
|
+
const res = await doFetch(url(tail), {
|
|
104
|
+
method: init.method ?? 'GET',
|
|
105
|
+
headers,
|
|
106
|
+
credentials: 'same-origin',
|
|
107
|
+
cache: 'no-store',
|
|
108
|
+
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
109
|
+
});
|
|
110
|
+
let body: unknown = null;
|
|
111
|
+
try {
|
|
112
|
+
body = await res.json();
|
|
113
|
+
} catch {
|
|
114
|
+
body = null;
|
|
115
|
+
}
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
const b = (body ?? {}) as { error?: string; message?: string };
|
|
118
|
+
throw new PublicLinkError(res.status, b.error ?? '', b.message);
|
|
119
|
+
}
|
|
120
|
+
return { status: res.status, body: body as T };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
/** What the link is and what the visitor may see of it. */
|
|
125
|
+
info: (query = '') => call<TInfo>(query).then((r) => r.body),
|
|
126
|
+
pin: (pin: string) => call<TInfo>('/pin', { method: 'POST', body: { pin } }).then((r) => r.body),
|
|
127
|
+
/**
|
|
128
|
+
* The app's opening screen.
|
|
129
|
+
*
|
|
130
|
+
* ⚠ There is no `/view` route: an open is `POST /event
|
|
131
|
+
* {"event":"open"}`, because the opening surface and every later one are
|
|
132
|
+
* the same call to the app. One endpoint, one code path in the plugin.
|
|
133
|
+
*/
|
|
134
|
+
open: () =>
|
|
135
|
+
call<{ surface?: PluginSurface }>('/event', { method: 'POST', body: { event: 'open', state: {}, data: {} } }).then(
|
|
136
|
+
(r) => r.body,
|
|
137
|
+
),
|
|
138
|
+
/** `{surface}`, or `{op}` when the server queued a job (202). */
|
|
139
|
+
event: async (body: Omit<PluginViewEventBody, 'path' | 'storage_id'>): Promise<PluginRunResult> => {
|
|
140
|
+
const r = await call<{ surface?: PluginSurface; accepted?: boolean; job_id?: string }>('/event', {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
body,
|
|
143
|
+
});
|
|
144
|
+
if (r.status === 202 || r.body?.accepted) {
|
|
145
|
+
return { op: { accepted: true, job_id: r.body?.job_id }, job_id: r.body?.job_id };
|
|
146
|
+
}
|
|
147
|
+
return { surface: r.body.surface as PluginSurface };
|
|
148
|
+
},
|
|
149
|
+
/** Where the browser loads an exposed copy from (`pub:N`). */
|
|
150
|
+
fileUrl: (ref: string) => url(`/file/${encodeURIComponent(ref)}`),
|
|
151
|
+
/**
|
|
152
|
+
* The same copy, asked for as a DOWNLOAD — the Download button's link.
|
|
153
|
+
* ⚠ Kept apart from `fileUrl` because only this one counts as a download
|
|
154
|
+
* (handlers/public_api.go serveExposed): the page's own viewer loads the
|
|
155
|
+
* document to SHOW it, and the owner ruled on 2026-09-21 that looking at
|
|
156
|
+
* a signing link is not downloading it.
|
|
157
|
+
*/
|
|
158
|
+
fileDownloadUrl: (ref: string) => url(`/file/${encodeURIComponent(ref)}?download=1`),
|
|
159
|
+
url,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type PublicLinkClient<T> = ReturnType<typeof publicLinkClient<T>>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The walk itself.
|
|
167
|
+
*
|
|
168
|
+
* `TInfo` is the shape the body needs (`PublicShareInfo`,
|
|
169
|
+
* `PublicRequestInfo`); this composable only reads the four facts every
|
|
170
|
+
* public link has — does it want a PIN, is it unlocked, has it a surface,
|
|
171
|
+
* and is it still alive.
|
|
172
|
+
*/
|
|
173
|
+
/**
|
|
174
|
+
* What `usePublicLink` hands back.
|
|
175
|
+
*
|
|
176
|
+
* ⚠⚠ Written out rather than inferred (`ReturnType<typeof …>`). The returned
|
|
177
|
+
* object carries the surface conversation, whose own type names Vue's
|
|
178
|
+
* internal ref helpers, and an inferred declaration cannot name them from
|
|
179
|
+
* outside this package: `vue-tsc` failed the package build with TS2742 — "the
|
|
180
|
+
* inferred type cannot be named without a reference to @vue/shared" — and the
|
|
181
|
+
* published `.d.ts` was built anyway, with a type consumers could not use.
|
|
182
|
+
* A written type is also the contract: adding a field here is a decision.
|
|
183
|
+
*/
|
|
184
|
+
export interface PublicLinkStore<
|
|
185
|
+
TInfo extends { needs_pin?: boolean; unlocked?: boolean; expired?: boolean; revoked?: boolean; locked?: boolean },
|
|
186
|
+
> {
|
|
187
|
+
status: Ref<PublicStatus>;
|
|
188
|
+
info: Ref<TInfo | null>;
|
|
189
|
+
reason: Ref<PublicFailure>;
|
|
190
|
+
pinFailure: Ref<PinFailure>;
|
|
191
|
+
lockMessage: Ref<string>;
|
|
192
|
+
pinBusy: Ref<boolean>;
|
|
193
|
+
failure: Ref<string>;
|
|
194
|
+
toast: Ref<string>;
|
|
195
|
+
jobId: Ref<string>;
|
|
196
|
+
/** The plugin surface on this link, when it carries one. */
|
|
197
|
+
conv: PluginSurfaceStore;
|
|
198
|
+
/** (Re-)read the link. `query` is appended verbatim (`?path=…`). */
|
|
199
|
+
load: (query?: string) => Promise<void>;
|
|
200
|
+
submitPin: (pin: string) => Promise<void>;
|
|
201
|
+
/** Where the browser loads an exposed copy from (`pub:N`). */
|
|
202
|
+
fileUrl: (ref: string) => string;
|
|
203
|
+
/** The same copy as a download — the only fetch that counts as one. */
|
|
204
|
+
fileDownloadUrl: (ref: string) => string;
|
|
205
|
+
resolveFile: (ref: string) => { url: string; name?: string; mime?: string };
|
|
206
|
+
url: PublicRoot;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function usePublicLink<
|
|
210
|
+
TInfo extends { needs_pin?: boolean; unlocked?: boolean; expired?: boolean; revoked?: boolean; locked?: boolean },
|
|
211
|
+
>(
|
|
212
|
+
url: PublicRoot,
|
|
213
|
+
opts: PublicLinkOptions & {
|
|
214
|
+
/** True when this link's body is an app plugin's surface. */
|
|
215
|
+
hasSurface?: (info: TInfo) => boolean;
|
|
216
|
+
},
|
|
217
|
+
): PublicLinkStore<TInfo> {
|
|
218
|
+
const client = publicLinkClient<TInfo>(url, opts);
|
|
219
|
+
|
|
220
|
+
const status = ref<PublicStatus>('loading');
|
|
221
|
+
// ⚠ Annotated, not inferred: `ref<TInfo|null>` of a GENERIC unwraps to a
|
|
222
|
+
// type that cannot be written down outside this package (TS2742), and
|
|
223
|
+
// the store's declared shape has to match what callers actually get.
|
|
224
|
+
const info = ref(null) as Ref<TInfo | null>;
|
|
225
|
+
const pinFailure = ref<PinFailure>('');
|
|
226
|
+
/** The server's own words for a lock (`429 locked`), when it sends any. */
|
|
227
|
+
const lockMessage = ref('');
|
|
228
|
+
const jobId = ref('');
|
|
229
|
+
const toast = ref('');
|
|
230
|
+
/** A failure outside the conversation (network, 5xx). */
|
|
231
|
+
const failure = ref('');
|
|
232
|
+
/** Why there is nothing to show, for a caller that wants to say more. */
|
|
233
|
+
const reason = ref<PublicFailure>('');
|
|
234
|
+
|
|
235
|
+
const conv = usePluginSurface(
|
|
236
|
+
{
|
|
237
|
+
api: { pluginViewEvent: (_plugin, _view, body) => client.event(body) },
|
|
238
|
+
plugin: '',
|
|
239
|
+
view: '',
|
|
240
|
+
locale: opts.locale,
|
|
241
|
+
errorText: opts.errorText,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
onOp: (op) => {
|
|
245
|
+
jobId.value = String((op as { job_id?: string }).job_id ?? '');
|
|
246
|
+
status.value = 'accepted';
|
|
247
|
+
},
|
|
248
|
+
onDone: () => {
|
|
249
|
+
if (status.value !== 'accepted') status.value = 'done';
|
|
250
|
+
},
|
|
251
|
+
onToast: (m) => {
|
|
252
|
+
toast.value = m;
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* ⚠ 404 and 410 both land on "this link is not available", and the two
|
|
259
|
+
* states are kept apart only for the caller's own logging. A visitor is
|
|
260
|
+
* never told WHICH — "expired" confirms that the link existed, which is
|
|
261
|
+
* one bit more than somebody guessing tokens should get.
|
|
262
|
+
*/
|
|
263
|
+
function fail(e: unknown): void {
|
|
264
|
+
const err = e as PublicLinkError;
|
|
265
|
+
if (err?.status === 404) {
|
|
266
|
+
status.value = 'not_found';
|
|
267
|
+
reason.value = 'not_found';
|
|
268
|
+
} else if (err?.status === 410) {
|
|
269
|
+
status.value = 'gone';
|
|
270
|
+
reason.value = 'gone';
|
|
271
|
+
} else {
|
|
272
|
+
status.value = 'error';
|
|
273
|
+
reason.value = 'error';
|
|
274
|
+
// ⚠ An app's own error text never met the catalogue — isolate it.
|
|
275
|
+
failure.value = foreignText(opts.locale(), String((e as Error)?.message ?? e)) || opts.errorText();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function openSurface(): Promise<void> {
|
|
280
|
+
try {
|
|
281
|
+
const res = await client.open();
|
|
282
|
+
const s = res?.surface;
|
|
283
|
+
if (!s) {
|
|
284
|
+
status.value = 'error';
|
|
285
|
+
failure.value = opts.errorText();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
conv.setSurface(s);
|
|
289
|
+
status.value = s.done ? 'done' : 'ready';
|
|
290
|
+
} catch (e) {
|
|
291
|
+
if ((e as PublicLinkError)?.status === 401) {
|
|
292
|
+
status.value = 'pin';
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
fail(e);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Is this link dead?
|
|
301
|
+
*
|
|
302
|
+
* ⚠⚠ A link that ran out answers 200 with the ordinary shape and
|
|
303
|
+
* `expired` / `revoked` set — NOT a 410. That is deliberate on the server
|
|
304
|
+
* side (a visitor who was given a link is entitled to be told it is over),
|
|
305
|
+
* and it means a client that only checks the status code renders an empty
|
|
306
|
+
* download page instead of an explanation. Both flags mean the same thing
|
|
307
|
+
* to the person reading the page, which is why they collapse here and are
|
|
308
|
+
* kept apart in the payload.
|
|
309
|
+
*/
|
|
310
|
+
function dead(i: TInfo | null): boolean {
|
|
311
|
+
return i?.expired === true || i?.revoked === true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The first call: the facts, then the PIN form or the body. */
|
|
315
|
+
async function load(query = ''): Promise<void> {
|
|
316
|
+
status.value = 'loading';
|
|
317
|
+
failure.value = '';
|
|
318
|
+
reason.value = '';
|
|
319
|
+
try {
|
|
320
|
+
info.value = await client.info(query);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
fail(e);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (dead(info.value as TInfo)) {
|
|
326
|
+
status.value = 'gone';
|
|
327
|
+
reason.value = 'gone';
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (info.value?.needs_pin && !info.value?.unlocked) {
|
|
331
|
+
status.value = 'pin';
|
|
332
|
+
// A gate shut by too many wrong answers says so before the first try,
|
|
333
|
+
// rather than letting somebody spend an attempt finding out.
|
|
334
|
+
pinFailure.value = info.value?.locked === true ? 'locked' : '';
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (opts.hasSurface?.(info.value as TInfo)) {
|
|
338
|
+
await openSurface();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
status.value = 'ready';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const pinBusy = ref(false);
|
|
345
|
+
|
|
346
|
+
/** `401 pin_wrong` → `wrong`, `429 locked` → `locked`; on success straight to the body. */
|
|
347
|
+
async function submitPin(pin: string): Promise<void> {
|
|
348
|
+
if (pinBusy.value) return;
|
|
349
|
+
pinFailure.value = '';
|
|
350
|
+
lockMessage.value = '';
|
|
351
|
+
pinBusy.value = true;
|
|
352
|
+
try {
|
|
353
|
+
// ⚠ Trimmed. A PIN is usually pasted out of a message, and a trailing
|
|
354
|
+
// space is not a wrong code — refusing it spends one of the attempts
|
|
355
|
+
// before the lockout on something the person cannot even see.
|
|
356
|
+
info.value = await client.pin(pin.trim());
|
|
357
|
+
if (dead(info.value as TInfo)) {
|
|
358
|
+
status.value = 'gone';
|
|
359
|
+
reason.value = 'gone';
|
|
360
|
+
} else if (opts.hasSurface?.(info.value as TInfo)) {
|
|
361
|
+
await openSurface();
|
|
362
|
+
} else {
|
|
363
|
+
status.value = 'ready';
|
|
364
|
+
}
|
|
365
|
+
} catch (e) {
|
|
366
|
+
const err = e as PublicLinkError;
|
|
367
|
+
if (err?.status === 429) {
|
|
368
|
+
pinFailure.value = 'locked';
|
|
369
|
+
// ⚠ The server's own rate-limit sentence — isolate it.
|
|
370
|
+
lockMessage.value = foreignText(opts.locale(), err.message ?? '');
|
|
371
|
+
} else if (err?.status === 401 || err?.status === 403) {
|
|
372
|
+
pinFailure.value = 'wrong';
|
|
373
|
+
} else {
|
|
374
|
+
fail(e);
|
|
375
|
+
}
|
|
376
|
+
} finally {
|
|
377
|
+
pinBusy.value = false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
status,
|
|
383
|
+
info,
|
|
384
|
+
reason,
|
|
385
|
+
pinFailure,
|
|
386
|
+
lockMessage,
|
|
387
|
+
pinBusy,
|
|
388
|
+
failure,
|
|
389
|
+
toast,
|
|
390
|
+
jobId,
|
|
391
|
+
conv,
|
|
392
|
+
load,
|
|
393
|
+
submitPin,
|
|
394
|
+
fileUrl: client.fileUrl,
|
|
395
|
+
fileDownloadUrl: client.fileDownloadUrl,
|
|
396
|
+
/**
|
|
397
|
+
* A `FileRefResolver` for the surface renderer: the URL, plus the name
|
|
398
|
+
* and type the link's own `files[]` already told us.
|
|
399
|
+
*
|
|
400
|
+
* ⚠ The renderer needs the mime to decide how to draw the thing (a
|
|
401
|
+
* picture, an embedded PDF, a link). Handing it a bare URL makes every
|
|
402
|
+
* exposed copy render as "something to download", which is what a
|
|
403
|
+
* `preview` node is specifically not for.
|
|
404
|
+
*/
|
|
405
|
+
resolveFile: (ref: string) => {
|
|
406
|
+
const files = (info.value as { app?: { files?: Array<{ ref: string; name?: string; mime?: string }> } } | null)
|
|
407
|
+
?.app?.files;
|
|
408
|
+
const f = files?.find((x) => x.ref === ref);
|
|
409
|
+
return { url: client.fileUrl(ref), name: f?.name, mime: f?.mime };
|
|
410
|
+
},
|
|
411
|
+
url: client.url,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Where the BYTES of a share live.
|
|
419
|
+
*
|
|
420
|
+
* ⚠ Not under `/api/`. `/s/<token>` is the address that has always served
|
|
421
|
+
* the file, and it still does — the SPA shell is what a JavaScript browser
|
|
422
|
+
* gets when it NAVIGATES there (`handlers/public_shell.go`), while
|
|
423
|
+
* `?download=1` and `?zip=1` are among the escapes that mean "I am asking for
|
|
424
|
+
* the thing, not for a page". Building a second download route under `/api/`
|
|
425
|
+
* would be a second thing to keep working.
|
|
426
|
+
*/
|
|
427
|
+
export function shareDownloadUrl(base: string | undefined, token: string, opts: { zip?: boolean; path?: string } = {}): string {
|
|
428
|
+
const origin = (base ?? '').replace(/\/+$/, '');
|
|
429
|
+
const t = encodeURIComponent(token);
|
|
430
|
+
if (opts.path) return `${origin}/s/${t}/f/${opts.path.split('/').map(encodeURIComponent).join('/')}?download=1`;
|
|
431
|
+
return `${origin}/s/${t}?${opts.zip ? 'zip=1' : 'download=1'}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** The no-JS page, for whatever the shell cannot draw yet (a folder walk). */
|
|
435
|
+
export function shareNoJsUrl(base: string | undefined, token: string): string {
|
|
436
|
+
return `${(base ?? '').replace(/\/+$/, '')}/s/${encodeURIComponent(token)}?nojs=1`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** A share: a file, a folder, or an app plugin's page. */
|
|
440
|
+
export function usePublicShare(token: string, opts: PublicLinkOptions) {
|
|
441
|
+
const link = usePublicLink<PublicShareInfo>(shareRoot(opts.base, token), {
|
|
442
|
+
...opts,
|
|
443
|
+
hasSurface: (i) => i?.kind === 'app',
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
...link,
|
|
447
|
+
downloadUrl: (o: { zip?: boolean; path?: string } = {}) => shareDownloadUrl(opts.base, token, o),
|
|
448
|
+
noJsUrl: () => shareNoJsUrl(opts.base, token),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** One file on its way up, as the drop box shows it. */
|
|
453
|
+
export interface PublicUpload {
|
|
454
|
+
name: string;
|
|
455
|
+
size: number;
|
|
456
|
+
/** 0..100, or -1 while the browser has not said anything yet. */
|
|
457
|
+
percent: number;
|
|
458
|
+
/**
|
|
459
|
+
* `refused` — never sent: the link does not take it (its type, its size, one
|
|
460
|
+
* file too many). Said before the transfer, not after it.
|
|
461
|
+
*/
|
|
462
|
+
state: 'sending' | 'done' | 'failed' | 'refused';
|
|
463
|
+
error?: string;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** What the person on the drop page adds to the files. */
|
|
467
|
+
export interface PublicUploadOptions {
|
|
468
|
+
/** Their name, when the link asks for it (`limits.ask_name`). */
|
|
469
|
+
name?: string;
|
|
470
|
+
/**
|
|
471
|
+
* The words for a file this page will not send. Handed in by the page
|
|
472
|
+
* because the page has the catalogue; the rule that decides lives in
|
|
473
|
+
* `lib/dropLimits`.
|
|
474
|
+
*/
|
|
475
|
+
refusedText?: (reason: DropRefusal) => string;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* A file request: the drop box an outsider uploads into.
|
|
480
|
+
*
|
|
481
|
+
* The upload is XHR and not `fetch` for one reason: `fetch` has no upload
|
|
482
|
+
* progress. A stranger sending a 300 MB file to somebody else's server with
|
|
483
|
+
* no indication that anything is happening will reload the page, and the
|
|
484
|
+
* transfer starts over.
|
|
485
|
+
*
|
|
486
|
+
* ⚠⚠ ONE request per drop, carrying every file of it. The server makes ONE
|
|
487
|
+
* submission folder per request (`<date>_<name>`, drop.go) and counts
|
|
488
|
+
* `max_files` per request; sending the files one by one made three folders out
|
|
489
|
+
* of one drop of three files, and let a person past the per-submission cap by
|
|
490
|
+
* simply sending them separately. The no-JavaScript page always sent them
|
|
491
|
+
* together.
|
|
492
|
+
*/
|
|
493
|
+
export function usePublicRequest(token: string, opts: PublicLinkOptions) {
|
|
494
|
+
const link = usePublicLink<PublicRequestInfo>(requestRoot(opts.base, token), opts);
|
|
495
|
+
const uploads = ref<PublicUpload[]>([]);
|
|
496
|
+
|
|
497
|
+
function patchRange(from: number, to: number, p: Partial<PublicUpload>): void {
|
|
498
|
+
uploads.value = uploads.value.map((u, n) => (n >= from && n < to ? { ...u, ...p } : u));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function send(files: File[], from: number, name: string): Promise<void> {
|
|
502
|
+
const to = from + files.length;
|
|
503
|
+
return new Promise<void>((resolve) => {
|
|
504
|
+
let xhr: XMLHttpRequest;
|
|
505
|
+
try {
|
|
506
|
+
xhr = new XMLHttpRequest();
|
|
507
|
+
} catch {
|
|
508
|
+
patchRange(from, to, { state: 'failed', error: opts.errorText() });
|
|
509
|
+
resolve();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
xhr.open('POST', link.url('/upload'));
|
|
513
|
+
xhr.withCredentials = true;
|
|
514
|
+
xhr.setRequestHeader('Accept', 'application/json');
|
|
515
|
+
xhr.setRequestHeader('Accept-Language', opts.locale());
|
|
516
|
+
xhr.upload.onprogress = (e) => {
|
|
517
|
+
if (e.lengthComputable && e.total > 0) patchRange(from, to, { percent: Math.round((e.loaded / e.total) * 100) });
|
|
518
|
+
};
|
|
519
|
+
xhr.onload = () => {
|
|
520
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
521
|
+
patchRange(from, to, { state: 'done', percent: 100 });
|
|
522
|
+
} else {
|
|
523
|
+
// ⚠ The server says why, in the visitor's language (`message`,
|
|
524
|
+
// drop.go `refuse`). An older server sends only the code; the
|
|
525
|
+
// page's own "could not be sent" is then the honest fallback.
|
|
526
|
+
let msg = '';
|
|
527
|
+
try {
|
|
528
|
+
msg = String((JSON.parse(xhr.responseText) as { message?: string })?.message ?? '');
|
|
529
|
+
} catch {
|
|
530
|
+
msg = '';
|
|
531
|
+
}
|
|
532
|
+
patchRange(from, to, { state: 'failed', error: msg || opts.errorText() });
|
|
533
|
+
}
|
|
534
|
+
resolve();
|
|
535
|
+
};
|
|
536
|
+
xhr.onerror = () => {
|
|
537
|
+
patchRange(from, to, { state: 'failed', error: opts.errorText() });
|
|
538
|
+
resolve();
|
|
539
|
+
};
|
|
540
|
+
const form = new FormData();
|
|
541
|
+
for (const f of files) form.append('file', f, f.name);
|
|
542
|
+
if (name) form.append('uploader_name', name);
|
|
543
|
+
xhr.send(form);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Everything the person dropped: what the link takes goes up in one request, the rest is said. */
|
|
548
|
+
async function upload(files: File[] | FileList, o: PublicUploadOptions = {}): Promise<void> {
|
|
549
|
+
const info = link.info.value;
|
|
550
|
+
const { accepted, refused } = checkDropFiles(Array.from(files), info?.limits, info?.uploads_left ?? null);
|
|
551
|
+
const said = o.refusedText ?? (() => opts.errorText());
|
|
552
|
+
uploads.value = [
|
|
553
|
+
...uploads.value,
|
|
554
|
+
...refused.map(({ file, reason }) => ({
|
|
555
|
+
name: file.name,
|
|
556
|
+
size: file.size,
|
|
557
|
+
percent: -1,
|
|
558
|
+
state: 'refused' as const,
|
|
559
|
+
error: said(reason),
|
|
560
|
+
})),
|
|
561
|
+
];
|
|
562
|
+
if (!accepted.length) return;
|
|
563
|
+
const from = uploads.value.length;
|
|
564
|
+
uploads.value = [
|
|
565
|
+
...uploads.value,
|
|
566
|
+
...accepted.map((f) => ({ name: f.name, size: f.size, percent: -1, state: 'sending' as const })),
|
|
567
|
+
];
|
|
568
|
+
await send(accepted, from, (o.name ?? '').trim());
|
|
569
|
+
await link.load();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return { ...link, uploads, upload };
|
|
573
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePublicPage — the `/p/<token>` walk, kept as a NAME.
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ v3 §1 retired `/p/*`: an app plugin's public page is a real share now
|
|
5
|
+
* (`/s/<token>` carrying a `page_id`), so the administrator revokes it in
|
|
6
|
+
* **Shares** like every other link and there is one PIN implementation, one
|
|
7
|
+
* expiry policy and one visit counter instead of two.
|
|
8
|
+
*
|
|
9
|
+
* ⚠ So this file holds no implementation any more, and that is the point.
|
|
10
|
+
* The walk it used to own — info → PIN → view → events — turned out to be
|
|
11
|
+
* the SAME walk a share and a file request make, and `usePublicLink` is that
|
|
12
|
+
* one walk at whichever root the link lives under. Keeping a second copy
|
|
13
|
+
* here "for the transition" is exactly how two public pages came to disagree
|
|
14
|
+
* about their own PIN box in the first place.
|
|
15
|
+
*
|
|
16
|
+
* The exports survive because a host may be importing them from the package:
|
|
17
|
+
* they are the old names pointed at the shared machinery, and nothing new
|
|
18
|
+
* should reach for them.
|
|
19
|
+
*/
|
|
20
|
+
import type { PublicPageInfo } from '../types/Plugins';
|
|
21
|
+
import {
|
|
22
|
+
PublicLinkError,
|
|
23
|
+
pageRoot,
|
|
24
|
+
publicLinkClient,
|
|
25
|
+
usePublicLink,
|
|
26
|
+
type PublicLinkOptions,
|
|
27
|
+
type PublicStatus,
|
|
28
|
+
} from './usePublicLink';
|
|
29
|
+
|
|
30
|
+
export type { PinFailure } from './usePublicLink';
|
|
31
|
+
|
|
32
|
+
/** @deprecated The old name of {@link PublicLinkError}. */
|
|
33
|
+
export { PublicLinkError as PublicPageError };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated `/p/` is retired (v3 §1). The status names moved with the
|
|
37
|
+
* walk: what used to be `'surface'` is `'ready'`, because a share's body is a
|
|
38
|
+
* document or a folder at least as often as it is a plugin's screen.
|
|
39
|
+
*/
|
|
40
|
+
export type PublicPageStatus = PublicStatus;
|
|
41
|
+
|
|
42
|
+
export type PublicPageClientOptions = PublicLinkOptions;
|
|
43
|
+
|
|
44
|
+
/** `/api/p/{token}` and its children. */
|
|
45
|
+
export function publicPageUrl(base: string | undefined, token: string, tail = ''): string {
|
|
46
|
+
return pageRoot(base, token)(tail);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @deprecated Use `publicLinkClient(shareRoot(base, token), …)`. */
|
|
50
|
+
export function publicPageClient(token: string, opts: PublicPageClientOptions) {
|
|
51
|
+
return publicLinkClient<PublicPageInfo>(pageRoot(opts.base, token), opts);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type PublicPageClient = ReturnType<typeof publicPageClient>;
|
|
55
|
+
|
|
56
|
+
/** @deprecated Use `usePublicShare` — an app's public page IS a share (v3 §1.1). */
|
|
57
|
+
export function usePublicPage(token: string, opts: PublicPageClientOptions & { errorText: () => string }) {
|
|
58
|
+
return usePublicLink<PublicPageInfo>(pageRoot(opts.base, token), { ...opts, hasSurface: () => true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type PublicPageStore = ReturnType<typeof usePublicPage>;
|