@strapi/upload 5.47.1 → 5.48.1
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/dist/admin/components/EditAssetDialog/EditAssetContent.js +12 -2
- package/dist/admin/components/EditAssetDialog/EditAssetContent.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs +12 -2
- package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js +1 -0
- package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs +1 -0
- package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs.map +1 -1
- package/dist/admin/future/components/Drawer.js +7 -8
- package/dist/admin/future/components/Drawer.js.map +1 -1
- package/dist/admin/future/components/Drawer.mjs +7 -8
- package/dist/admin/future/components/Drawer.mjs.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.js +33 -29
- package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.mjs +36 -32
- package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.js +2 -2
- package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.mjs +3 -3
- package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +626 -169
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +630 -175
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js +25 -5
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs +25 -5
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs.map +1 -1
- package/dist/admin/future/services/api.js +124 -200
- package/dist/admin/future/services/api.js.map +1 -1
- package/dist/admin/future/services/api.mjs +124 -200
- package/dist/admin/future/services/api.mjs.map +1 -1
- package/dist/admin/future/services/assets.js +57 -1
- package/dist/admin/future/services/assets.js.map +1 -1
- package/dist/admin/future/services/assets.mjs +56 -2
- package/dist/admin/future/services/assets.mjs.map +1 -1
- package/dist/admin/future/services/settings.js +18 -0
- package/dist/admin/future/services/settings.js.map +1 -0
- package/dist/admin/future/services/settings.mjs +16 -0
- package/dist/admin/future/services/settings.mjs.map +1 -0
- package/dist/admin/future/services/uploadFileViaXHR.js +92 -0
- package/dist/admin/future/services/uploadFileViaXHR.js.map +1 -0
- package/dist/admin/future/services/uploadFileViaXHR.mjs +88 -0
- package/dist/admin/future/services/uploadFileViaXHR.mjs.map +1 -0
- package/dist/admin/future/store/uploadProgress.js +32 -26
- package/dist/admin/future/store/uploadProgress.js.map +1 -1
- package/dist/admin/future/store/uploadProgress.mjs +32 -27
- package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
- package/dist/admin/future/utils/createRafBatcher.js +42 -0
- package/dist/admin/future/utils/createRafBatcher.js.map +1 -0
- package/dist/admin/future/utils/createRafBatcher.mjs +40 -0
- package/dist/admin/future/utils/createRafBatcher.mjs.map +1 -0
- package/dist/admin/future/utils/downloadFile.js +19 -0
- package/dist/admin/future/utils/downloadFile.js.map +1 -0
- package/dist/admin/future/utils/downloadFile.mjs +17 -0
- package/dist/admin/future/utils/downloadFile.mjs.map +1 -0
- package/dist/admin/hooks/useAssets.js +5 -3
- package/dist/admin/hooks/useAssets.js.map +1 -1
- package/dist/admin/hooks/useAssets.mjs +5 -3
- package/dist/admin/hooks/useAssets.mjs.map +1 -1
- package/dist/admin/src/components/EditAssetDialog/EditAssetContent.d.ts +2 -1
- package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +15 -1
- package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetPreview.d.ts +4 -1
- package/dist/admin/src/future/services/api.d.ts +9 -8
- package/dist/admin/src/future/services/assets.d.ts +6 -1
- package/dist/admin/src/future/services/uploadFileViaXHR.d.ts +34 -0
- package/dist/admin/src/future/store/uploadProgress.d.ts +17 -4
- package/dist/admin/src/future/utils/createRafBatcher.d.ts +23 -0
- package/dist/admin/src/future/utils/downloadFile.d.ts +6 -0
- package/dist/admin/translations/en.json.js +21 -0
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +21 -0
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/server/controllers/admin-upload.js +69 -118
- package/dist/server/controllers/admin-upload.js.map +1 -1
- package/dist/server/controllers/admin-upload.mjs +69 -118
- package/dist/server/controllers/admin-upload.mjs.map +1 -1
- package/dist/server/controllers/content-api.js +12 -0
- package/dist/server/controllers/content-api.js.map +1 -1
- package/dist/server/controllers/content-api.mjs +12 -0
- package/dist/server/controllers/content-api.mjs.map +1 -1
- package/dist/server/routes/admin.js +2 -2
- package/dist/server/routes/admin.js.map +1 -1
- package/dist/server/routes/admin.mjs +2 -2
- package/dist/server/routes/admin.mjs.map +1 -1
- package/dist/server/routes/content-api.js +15 -0
- package/dist/server/routes/content-api.js.map +1 -1
- package/dist/server/routes/content-api.mjs +15 -0
- package/dist/server/routes/content-api.mjs.map +1 -1
- package/dist/server/routes/validation/upload.js +28 -0
- package/dist/server/routes/validation/upload.js.map +1 -1
- package/dist/server/routes/validation/upload.mjs +28 -0
- package/dist/server/routes/validation/upload.mjs.map +1 -1
- package/dist/server/services/image-manipulation.js +16 -8
- package/dist/server/services/image-manipulation.js.map +1 -1
- package/dist/server/services/image-manipulation.mjs +16 -8
- package/dist/server/services/image-manipulation.mjs.map +1 -1
- package/dist/server/services/upload.js +90 -1
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +91 -2
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/src/controllers/admin-upload.d.ts +6 -8
- package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
- package/dist/server/src/controllers/content-api.d.ts +1 -0
- package/dist/server/src/controllers/content-api.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +2 -1
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +6 -1
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/routes/content-api.d.ts.map +1 -1
- package/dist/server/src/routes/validation/upload.d.ts +45 -0
- package/dist/server/src/routes/validation/upload.d.ts.map +1 -1
- package/dist/server/src/services/image-manipulation.d.ts +5 -0
- package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +4 -0
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts +5 -0
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/dist/server/src/types.d.ts +2 -2
- package/dist/server/src/types.d.ts.map +1 -1
- package/dist/shared/contracts/files.d.ts +19 -2
- package/dist/shared/contracts/files.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateFilesStream } from '../../../../shared/contracts/files';
|
|
1
|
+
import type { CreateFilesStream, File as UploadedFile } from '../../../../shared/contracts/files';
|
|
2
2
|
interface UploadFilesArgs {
|
|
3
3
|
formData: FormData;
|
|
4
4
|
totalFiles: number;
|
|
@@ -14,20 +14,21 @@ interface UploadFromUrlsArgs {
|
|
|
14
14
|
export declare const abortUpload: (uploadId: number) => void;
|
|
15
15
|
declare const uploadApi: import("@reduxjs/toolkit/query").Api<import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, import("@reduxjs/toolkit/dist/query/endpointDefinitions").UpdateDefinitions<{}, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", never> & {
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* Upload files sequentially, one request per file, to `/upload/unstable/upload-file`.
|
|
18
|
+
* Real per-file byte progress comes from `XHR.upload.onprogress`.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
uploadFiles: import("@reduxjs/toolkit/query").MutationDefinition<UploadFilesArgs, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", UploadedFile[], "adminApi">;
|
|
21
21
|
/**
|
|
22
22
|
* Retry uploading cancelled files.
|
|
23
|
-
*
|
|
23
|
+
* Maps cancelled rows back to their original entries and re-runs only those
|
|
24
|
+
* through the same sequential loop with a fresh AbortController.
|
|
24
25
|
*/
|
|
25
|
-
|
|
26
|
+
retryCancelledFiles: import("@reduxjs/toolkit/query").MutationDefinition<void, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", UploadedFile[], "adminApi">;
|
|
26
27
|
/**
|
|
27
28
|
* Upload files from URLs.
|
|
28
|
-
* Sends URLs to the server which fetches and uploads them.
|
|
29
|
+
* Sends URLs to the server which fetches and uploads them (SSE flow, unchanged).
|
|
29
30
|
*/
|
|
30
31
|
uploadFromUrls: import("@reduxjs/toolkit/query").MutationDefinition<UploadFromUrlsArgs, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", CreateFilesStream.Response, "adminApi">;
|
|
31
32
|
}, "adminApi", "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", typeof import("@reduxjs/toolkit/query").coreModuleName | typeof import("@reduxjs/toolkit/dist/query/react").reactHooksModuleName>;
|
|
32
|
-
export declare const
|
|
33
|
+
export declare const useUploadFilesMutation: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseMutation<import("@reduxjs/toolkit/query").MutationDefinition<UploadFilesArgs, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", UploadedFile[], "adminApi">>, useRetryCancelledFilesMutation: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseMutation<import("@reduxjs/toolkit/query").MutationDefinition<void, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", UploadedFile[], "adminApi">>, useUploadFromUrlsMutation: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseMutation<import("@reduxjs/toolkit/query").MutationDefinition<UploadFromUrlsArgs, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", CreateFilesStream.Response, "adminApi">>;
|
|
33
34
|
export { uploadApi };
|
|
@@ -13,5 +13,10 @@ interface UpdateAssetArgs {
|
|
|
13
13
|
id: number;
|
|
14
14
|
fileInfo: Partial<UploadFileInfo>;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
interface ReplaceAssetArgs {
|
|
17
|
+
id: number;
|
|
18
|
+
file: globalThis.File;
|
|
19
|
+
fileInfo?: Partial<UploadFileInfo>;
|
|
20
|
+
}
|
|
21
|
+
export declare const useGetAssetsQuery: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseQuery<import("@reduxjs/toolkit/query").QueryDefinition<void | GetAssetsParams, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", GetAssetsResponse, "adminApi">>, useGetAssetQuery: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseQuery<import("@reduxjs/toolkit/query").QueryDefinition<number, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", AssetWithPopulatedCreatedBy, "adminApi">>, useUpdateAssetMutation: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseMutation<import("@reduxjs/toolkit/query").MutationDefinition<UpdateAssetArgs, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", AssetWithPopulatedCreatedBy, "adminApi">>, useReplaceAssetMutation: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseMutation<import("@reduxjs/toolkit/query").MutationDefinition<ReplaceAssetArgs, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", AssetWithPopulatedCreatedBy, "adminApi">>, useDeleteAssetMutation: import("@reduxjs/toolkit/dist/query/react/buildHooks").UseMutation<import("@reduxjs/toolkit/query").MutationDefinition<number, import("@reduxjs/toolkit/query").BaseQueryFn<string | import("@strapi/admin/strapi-admin").QueryArguments, unknown, import("@strapi/admin/strapi-admin").BaseQueryError, {}, {}>, "GuidedTourMeta" | "HomepageKeyStatistics" | "AiUsage" | "AiFeatureConfig" | "Asset" | "Folder", unknown, "adminApi">>;
|
|
17
22
|
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { File } from '../../../../shared/contracts/files';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an upload is aborted via its `AbortSignal`.
|
|
4
|
+
* Distinct from {@link UploadFileError} so callers can tell cancellation apart
|
|
5
|
+
* from a genuine failure.
|
|
6
|
+
*/
|
|
7
|
+
export declare class UploadAbortedError extends Error {
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when an upload fails (non-2xx response, network error, or unparseable body).
|
|
12
|
+
*/
|
|
13
|
+
export declare class UploadFileError extends Error {
|
|
14
|
+
status?: number;
|
|
15
|
+
constructor(message: string, status?: number);
|
|
16
|
+
}
|
|
17
|
+
export type UploadProgressCallback = (bytes: number, total: number) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Uploads a single file via `XMLHttpRequest`, exposing real byte-level upload
|
|
20
|
+
* progress through {@link XMLHttpRequest.upload}'s `progress` event.
|
|
21
|
+
*
|
|
22
|
+
* This is the only place raw XHR lives. `fetch()` is intentionally avoided here
|
|
23
|
+
* because it does not surface upload progress.
|
|
24
|
+
*
|
|
25
|
+
* @param url - The full endpoint URL to POST to.
|
|
26
|
+
* @param token - Admin auth token; the `Authorization` header is only set when present.
|
|
27
|
+
* @param formData - Prebuilt multipart body containing the single file and its `fileInfo`.
|
|
28
|
+
* @param signal - Aborts the in-flight request when triggered.
|
|
29
|
+
* @param onProgress - Called with `(loaded, total)` bytes as the upload progresses.
|
|
30
|
+
* @returns The parsed, signed `File` on a 2xx response.
|
|
31
|
+
* @throws {UploadAbortedError} When the signal aborts.
|
|
32
|
+
* @throws {UploadFileError} On a non-2xx response or network error.
|
|
33
|
+
*/
|
|
34
|
+
export declare const uploadFileViaXHR: (url: string, token: string | null | undefined, formData: FormData, signal: AbortSignal, onProgress?: UploadProgressCallback) => Promise<File>;
|
|
@@ -9,13 +9,13 @@ export interface FileProgress {
|
|
|
9
9
|
index: number;
|
|
10
10
|
status: FileProgressStatus;
|
|
11
11
|
size: number;
|
|
12
|
+
uploadedBytes: number;
|
|
12
13
|
file?: File;
|
|
13
14
|
error?: string;
|
|
14
15
|
}
|
|
15
16
|
export interface UploadProgressState {
|
|
16
17
|
isVisible: boolean;
|
|
17
18
|
isMinimized: boolean;
|
|
18
|
-
progress: number;
|
|
19
19
|
totalFiles: number;
|
|
20
20
|
files: FileProgress[];
|
|
21
21
|
errors: FileUploadError[];
|
|
@@ -24,6 +24,17 @@ export interface UploadProgressState {
|
|
|
24
24
|
export interface RootState {
|
|
25
25
|
uploadProgress: UploadProgressState;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Byte-weighted aggregate progress across the whole batch: `sum(uploadedBytes) / sum(size)`.
|
|
29
|
+
*
|
|
30
|
+
* Falls back to count-based progress (settled files / total files) when all sizes are
|
|
31
|
+
* zero — e.g. URL-flow rows where the file size is unknown up front.
|
|
32
|
+
*/
|
|
33
|
+
export declare const selectAggregateProgress: ((state: RootState) => number) & import("reselect").OutputSelectorFields<(args_0: FileProgress[]) => number, {
|
|
34
|
+
clearCache: () => void;
|
|
35
|
+
}> & {
|
|
36
|
+
clearCache: () => void;
|
|
37
|
+
};
|
|
27
38
|
export declare const openUploadProgress: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
28
39
|
totalFiles: number;
|
|
29
40
|
fileNames: string[];
|
|
@@ -31,16 +42,18 @@ export declare const openUploadProgress: import("@reduxjs/toolkit").ActionCreato
|
|
|
31
42
|
}, "uploadProgress/openUploadProgress">, setFileUploading: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
32
43
|
name: string;
|
|
33
44
|
index: number;
|
|
34
|
-
total: number;
|
|
35
45
|
size: number;
|
|
36
|
-
}, "uploadProgress/setFileUploading">,
|
|
46
|
+
}, "uploadProgress/setFileUploading">, setFileProgress: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
47
|
+
index: number;
|
|
48
|
+
bytes: number;
|
|
49
|
+
}, "uploadProgress/setFileProgress">, setFileComplete: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
37
50
|
index: number;
|
|
38
51
|
file: File;
|
|
39
52
|
}, "uploadProgress/setFileComplete">, setFileError: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
40
53
|
index: number;
|
|
41
54
|
name: string;
|
|
42
55
|
message: string;
|
|
43
|
-
}, "uploadProgress/setFileError">,
|
|
56
|
+
}, "uploadProgress/setFileError">, addUploadErrors: import("@reduxjs/toolkit").ActionCreatorWithPayload<FileUploadError[], "uploadProgress/addUploadErrors">, closeUploadProgress: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"uploadProgress/closeUploadProgress">, toggleMinimize: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"uploadProgress/toggleMinimize">, cancelUpload: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"uploadProgress/cancelUpload">, setUploadFailed: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
44
57
|
message: string;
|
|
45
58
|
}, "uploadProgress/setUploadFailed">, retryCancelledFiles: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"uploadProgress/retryCancelledFiles">;
|
|
46
59
|
export declare const uploadProgressReducer: import("redux").Reducer<UploadProgressState>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalesces high-frequency values into at most one flush per animation frame.
|
|
3
|
+
*
|
|
4
|
+
* `XHR.upload.onprogress` can fire many times per second on a fast connection.
|
|
5
|
+
* Dispatching each one to Redux would flood the store, so the orchestrator routes
|
|
6
|
+
* progress through a batcher: many `schedule(value)` calls within a single frame
|
|
7
|
+
* collapse into one `flush(latestValue)` callback.
|
|
8
|
+
*
|
|
9
|
+
* @param flush - Invoked once per frame with the most recent scheduled value.
|
|
10
|
+
*/
|
|
11
|
+
export declare const createRafBatcher: <T>(flush: (value: T) => void) => {
|
|
12
|
+
/**
|
|
13
|
+
* Records the latest value and ensures a flush is scheduled for the next frame.
|
|
14
|
+
* Repeated calls within the same frame overwrite the value without scheduling
|
|
15
|
+
* additional frames.
|
|
16
|
+
*/
|
|
17
|
+
schedule(value: T): void;
|
|
18
|
+
/**
|
|
19
|
+
* Cancels any pending flush so a value scheduled this frame will not fire.
|
|
20
|
+
*/
|
|
21
|
+
cancel(): void;
|
|
22
|
+
};
|
|
23
|
+
export type RafBatcher<T> = ReturnType<typeof createRafBatcher<T>>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch a file at `url` and trigger a browser download with the given filename.
|
|
3
|
+
* Goes via a blob + temporary anchor so we honour the `download` attribute
|
|
4
|
+
* even when the file is served same-origin without `Content-Disposition`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const downloadFile: (url: string, fileName: string) => Promise<void>;
|
|
@@ -23,6 +23,27 @@ var en = {
|
|
|
23
23
|
"asset-details.update.success": "File updated",
|
|
24
24
|
"asset-details.update.error": "Failed to update the file.",
|
|
25
25
|
"asset-details.field.empty": "{label} is currently empty.",
|
|
26
|
+
"asset-details.location.root": "Media Library",
|
|
27
|
+
"asset-details.saveChanges": "Save changes",
|
|
28
|
+
"asset-details.delete.trigger": "Delete this file",
|
|
29
|
+
"asset-details.delete.title": "Delete this media file?",
|
|
30
|
+
"asset-details.delete.description": "This file cannot be recovered once deleted. If it is currently in use, linked content will break and image containers will be empty.",
|
|
31
|
+
"asset-details.delete.success": "1 element have been deleted from {folderName}",
|
|
32
|
+
"asset-details.delete.error": "Failed to delete the file.",
|
|
33
|
+
"asset-details.delete.loading": "Deleting the file…",
|
|
34
|
+
"asset-details.replace.trigger": "Replace this file",
|
|
35
|
+
"asset-details.replace.title": "Replace this media file?",
|
|
36
|
+
"asset-details.replace.description": "Current content will be permanently replaced.",
|
|
37
|
+
"asset-details.replace.description.ai": "AI will generate new metadata after upload.",
|
|
38
|
+
"asset-details.replace.continue": "Continue",
|
|
39
|
+
"asset-details.replace.success": "File replaced.",
|
|
40
|
+
"asset-details.replace.error": "Failed to replace the file.",
|
|
41
|
+
"asset-details.replace.loading": "Replacing the file…",
|
|
42
|
+
"asset-details.copy-link.trigger": "Copy link",
|
|
43
|
+
"asset-details.copy-link.success": "Link copied.",
|
|
44
|
+
"asset-details.copy-link.error": "Failed to copy the link.",
|
|
45
|
+
"asset-details.download.trigger": "Download",
|
|
46
|
+
"asset-details.download.error": "Failed to download the file.",
|
|
26
47
|
"asset-details.noPreview": "No preview available",
|
|
27
48
|
"upload.generic-error": "An error occurred while uploading the file.",
|
|
28
49
|
"upload.progress": "Upload progress",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -21,6 +21,27 @@ var en = {
|
|
|
21
21
|
"asset-details.update.success": "File updated",
|
|
22
22
|
"asset-details.update.error": "Failed to update the file.",
|
|
23
23
|
"asset-details.field.empty": "{label} is currently empty.",
|
|
24
|
+
"asset-details.location.root": "Media Library",
|
|
25
|
+
"asset-details.saveChanges": "Save changes",
|
|
26
|
+
"asset-details.delete.trigger": "Delete this file",
|
|
27
|
+
"asset-details.delete.title": "Delete this media file?",
|
|
28
|
+
"asset-details.delete.description": "This file cannot be recovered once deleted. If it is currently in use, linked content will break and image containers will be empty.",
|
|
29
|
+
"asset-details.delete.success": "1 element have been deleted from {folderName}",
|
|
30
|
+
"asset-details.delete.error": "Failed to delete the file.",
|
|
31
|
+
"asset-details.delete.loading": "Deleting the file…",
|
|
32
|
+
"asset-details.replace.trigger": "Replace this file",
|
|
33
|
+
"asset-details.replace.title": "Replace this media file?",
|
|
34
|
+
"asset-details.replace.description": "Current content will be permanently replaced.",
|
|
35
|
+
"asset-details.replace.description.ai": "AI will generate new metadata after upload.",
|
|
36
|
+
"asset-details.replace.continue": "Continue",
|
|
37
|
+
"asset-details.replace.success": "File replaced.",
|
|
38
|
+
"asset-details.replace.error": "Failed to replace the file.",
|
|
39
|
+
"asset-details.replace.loading": "Replacing the file…",
|
|
40
|
+
"asset-details.copy-link.trigger": "Copy link",
|
|
41
|
+
"asset-details.copy-link.success": "Link copied.",
|
|
42
|
+
"asset-details.copy-link.error": "Failed to copy the link.",
|
|
43
|
+
"asset-details.download.trigger": "Download",
|
|
44
|
+
"asset-details.download.error": "Failed to download the file.",
|
|
24
45
|
"asset-details.noPreview": "No preview available",
|
|
25
46
|
"upload.generic-error": "An error occurred while uploading the file.",
|
|
26
47
|
"upload.progress": "Upload progress",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -21,7 +21,9 @@ var adminUpload = {
|
|
|
21
21
|
const updated = await uploadService.updateFileInfo(id, fileInfo, {
|
|
22
22
|
user
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
// Sign file urls for private providers
|
|
25
|
+
const signedFile = await index.getService('file').signFileUrls(updated);
|
|
26
|
+
return pm.sanitizeOutput(signedFile, {
|
|
25
27
|
action: constants.ACTIONS.read
|
|
26
28
|
});
|
|
27
29
|
});
|
|
@@ -38,7 +40,9 @@ var adminUpload = {
|
|
|
38
40
|
const file = await uploadService.updateFileInfo(id, data.fileInfo, {
|
|
39
41
|
user
|
|
40
42
|
});
|
|
41
|
-
|
|
43
|
+
// Sign file urls for private providers
|
|
44
|
+
const signedFile = await index.getService('file').signFileUrls(file);
|
|
45
|
+
ctx.body = await pm.sanitizeOutput(signedFile, {
|
|
42
46
|
action: constants.ACTIONS.read
|
|
43
47
|
});
|
|
44
48
|
},
|
|
@@ -63,6 +67,25 @@ var adminUpload = {
|
|
|
63
67
|
}, {
|
|
64
68
|
user
|
|
65
69
|
});
|
|
70
|
+
// Regenerate AI metadata for image replacements so the alt text / caption
|
|
71
|
+
// reflect the new file content. Mirrors the post-upload hook in
|
|
72
|
+
// `uploadFiles`; failure is logged and swallowed to keep the replace flow
|
|
73
|
+
// resilient when the AI provider is unavailable.
|
|
74
|
+
const aiMetadataService = index.getService('aiMetadata');
|
|
75
|
+
if (replacedFile?.mime?.startsWith('image/') && await aiMetadataService.isEnabled()) {
|
|
76
|
+
try {
|
|
77
|
+
const metadataResults = await aiMetadataService.processFiles([
|
|
78
|
+
replacedFile
|
|
79
|
+
]);
|
|
80
|
+
await aiMetadataService.updateFilesWithAIMetadata([
|
|
81
|
+
replacedFile
|
|
82
|
+
], metadataResults, user);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
strapi.log.warn('AI metadata generation failed on replace, proceeding without it', {
|
|
85
|
+
error: error instanceof Error ? error.message : String(error)
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
66
89
|
// Sign file urls for private providers
|
|
67
90
|
const signedFile = await index.getService('file').signFileUrls(replacedFile);
|
|
68
91
|
ctx.body = await pm.sanitizeOutput(signedFile, {
|
|
@@ -126,15 +149,13 @@ var adminUpload = {
|
|
|
126
149
|
},
|
|
127
150
|
/**
|
|
128
151
|
* @experimental
|
|
129
|
-
*
|
|
152
|
+
* Upload a single file and return the created File.
|
|
130
153
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
|
|
136
|
-
*
|
|
137
|
-
*/ async unstable_uploadFilesStream (ctx) {
|
|
154
|
+
* Accepts one file per request (multipart `files` + `fileInfo`) and returns a
|
|
155
|
+
* single `File` object. Unlike `uploadFiles`, it does **not** run AI metadata
|
|
156
|
+
* generation inline — that responsibility is decoupled and will be handled by a
|
|
157
|
+
* background job. Auth and permission checks mirror `POST /upload`.
|
|
158
|
+
*/ async unstable_uploadFile (ctx) {
|
|
138
159
|
const { state: { userAbility, user }, request: { body, files: { files } = {} } } = ctx;
|
|
139
160
|
const uploadService = index.getService('upload');
|
|
140
161
|
const pm = strapi.service('admin::permission').createPermissionsManager({
|
|
@@ -148,124 +169,54 @@ var adminUpload = {
|
|
|
148
169
|
if (_.isEmpty(files) || !Array.isArray(files) && files.size === 0) {
|
|
149
170
|
throw new utils.errors.ApplicationError('Files are empty');
|
|
150
171
|
}
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
172
|
+
// Accept a single file per request; ignore any extras defensively.
|
|
173
|
+
const file = Array.isArray(files) ? files[0] : files;
|
|
174
|
+
const fileName = file.originalFilename || 'unknown';
|
|
175
|
+
// Parse the single fileInfo object from the multipart body.
|
|
176
|
+
let fileInfo = {
|
|
177
|
+
name: fileName,
|
|
178
|
+
caption: null,
|
|
179
|
+
alternativeText: null,
|
|
180
|
+
folder: null
|
|
161
181
|
};
|
|
162
|
-
// Normalize files to an array
|
|
163
|
-
const filesArray = Array.isArray(files) ? files : [
|
|
164
|
-
files
|
|
165
|
-
];
|
|
166
|
-
const total = filesArray.length;
|
|
167
|
-
// Parse fileInfo from body
|
|
168
|
-
// Multipart forms send fileInfo as either:
|
|
169
|
-
// - An array of JSON strings (one per file)
|
|
170
|
-
// - A single JSON string (one file, or a JSON-encoded array)
|
|
171
|
-
let parsedFileInfo = [];
|
|
172
182
|
if (body?.fileInfo) {
|
|
173
183
|
const raw = body.fileInfo;
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
} else if (
|
|
177
|
-
|
|
178
|
-
// Handle case where a single string contains a JSON array
|
|
179
|
-
parsedFileInfo = Array.isArray(parsed) ? parsed : [
|
|
180
|
-
parsed
|
|
181
|
-
];
|
|
184
|
+
if (typeof raw === 'string') {
|
|
185
|
+
fileInfo = JSON.parse(raw);
|
|
186
|
+
} else if (Array.isArray(raw)) {
|
|
187
|
+
fileInfo = typeof raw[0] === 'string' ? JSON.parse(raw[0]) : raw[0];
|
|
182
188
|
} else {
|
|
183
|
-
|
|
184
|
-
raw
|
|
185
|
-
];
|
|
189
|
+
fileInfo = raw;
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const fileInfo = parsedFileInfo[i] || {
|
|
195
|
-
name: fileName,
|
|
196
|
-
caption: null,
|
|
197
|
-
alternativeText: null,
|
|
198
|
-
folder: null
|
|
199
|
-
};
|
|
200
|
-
writeSSE('file:uploading', {
|
|
201
|
-
name: fileName,
|
|
202
|
-
index: i,
|
|
203
|
-
total,
|
|
204
|
-
size: file.size || 0
|
|
205
|
-
});
|
|
206
|
-
try {
|
|
207
|
-
// Validate this single file using security checks
|
|
208
|
-
const { validFiles, errors: validationErrors } = await mimeValidation.prepareUploadRequest(file, {
|
|
209
|
-
fileInfo: JSON.stringify(fileInfo)
|
|
210
|
-
}, strapi);
|
|
211
|
-
if (validFiles.length === 0) {
|
|
212
|
-
const errorMessage = validationErrors[0]?.message || 'Validation failed';
|
|
213
|
-
uploadErrors.push({
|
|
214
|
-
name: fileName,
|
|
215
|
-
message: errorMessage
|
|
216
|
-
});
|
|
217
|
-
writeSSE('file:error', {
|
|
218
|
-
name: fileName,
|
|
219
|
-
index: i,
|
|
220
|
-
message: errorMessage
|
|
221
|
-
});
|
|
222
|
-
} else {
|
|
223
|
-
// Validate using the already-parsed single fileInfo object directly
|
|
224
|
-
const data = await upload.validateUploadBody({
|
|
225
|
-
fileInfo
|
|
226
|
-
}, false);
|
|
227
|
-
const [uploadedFile] = await uploadService.upload({
|
|
228
|
-
data,
|
|
229
|
-
files: [
|
|
230
|
-
validFiles[0]
|
|
231
|
-
]
|
|
232
|
-
}, {
|
|
233
|
-
user
|
|
234
|
-
});
|
|
235
|
-
// Sign file url
|
|
236
|
-
const signedFile = await index.getService('file').signFileUrls(uploadedFile);
|
|
237
|
-
successfulFiles.push(signedFile);
|
|
238
|
-
writeSSE('file:complete', {
|
|
239
|
-
name: fileName,
|
|
240
|
-
index: i,
|
|
241
|
-
file: signedFile
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
} catch (error) {
|
|
245
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
246
|
-
uploadErrors.push({
|
|
247
|
-
name: fileName,
|
|
248
|
-
message: errorMessage
|
|
249
|
-
});
|
|
250
|
-
writeSSE('file:error', {
|
|
251
|
-
name: fileName,
|
|
252
|
-
index: i,
|
|
253
|
-
message: errorMessage
|
|
254
|
-
});
|
|
255
|
-
}
|
|
192
|
+
// Validate this single file using security checks.
|
|
193
|
+
const { validFiles, errors: validationErrors } = await mimeValidation.prepareUploadRequest(file, {
|
|
194
|
+
fileInfo: JSON.stringify(fileInfo)
|
|
195
|
+
}, strapi);
|
|
196
|
+
if (validFiles.length === 0) {
|
|
197
|
+
throw new utils.errors.ValidationError(validationErrors[0]?.message || 'Validation failed');
|
|
256
198
|
}
|
|
257
|
-
|
|
258
|
-
|
|
199
|
+
const data = await upload.validateUploadBody({
|
|
200
|
+
fileInfo
|
|
201
|
+
}, false);
|
|
202
|
+
const [uploadedFile] = await uploadService.upload({
|
|
203
|
+
data,
|
|
204
|
+
files: [
|
|
205
|
+
validFiles[0]
|
|
206
|
+
]
|
|
207
|
+
}, {
|
|
208
|
+
user
|
|
209
|
+
});
|
|
210
|
+
if (uploadedFile.mime?.startsWith('image/')) {
|
|
259
211
|
await index.getService('metrics').trackUsage('didUploadImage');
|
|
260
212
|
}
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
errors: uploadErrors
|
|
213
|
+
// No inline AI metadata generation — intentionally decoupled for the async job.
|
|
214
|
+
// Sign file url for private providers.
|
|
215
|
+
const signedFile = await index.getService('file').signFileUrls(uploadedFile);
|
|
216
|
+
ctx.body = await pm.sanitizeOutput(signedFile, {
|
|
217
|
+
action: constants.ACTIONS.read
|
|
267
218
|
});
|
|
268
|
-
|
|
219
|
+
ctx.status = 201;
|
|
269
220
|
},
|
|
270
221
|
/**
|
|
271
222
|
* @experimental
|