canopycms 0.0.57 → 0.0.58
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/ai/json-to-markdown.js +74 -3
- package/dist/ai/json-to-markdown.js.map +1 -1
- package/dist/api/__test__/mock-client.d.ts +9 -5
- package/dist/api/__test__/mock-client.d.ts.map +1 -1
- package/dist/api/__test__/mock-client.js +15 -7
- package/dist/api/__test__/mock-client.js.map +1 -1
- package/dist/api/assets.d.ts +100 -36
- package/dist/api/assets.d.ts.map +1 -1
- package/dist/api/assets.js +338 -51
- package/dist/api/assets.js.map +1 -1
- package/dist/api/client.d.ts +16 -5
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +24 -6
- package/dist/api/client.js.map +1 -1
- package/dist/api/entries.d.ts +4 -4
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/route-builder.d.ts +9 -0
- package/dist/api/route-builder.d.ts.map +1 -1
- package/dist/api/route-builder.js +1 -0
- package/dist/api/route-builder.js.map +1 -1
- package/dist/api/schema.d.ts +4 -4
- package/dist/api/types.d.ts +11 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/assets/asset-prefixes.d.ts +31 -0
- package/dist/assets/asset-prefixes.d.ts.map +1 -0
- package/dist/assets/asset-prefixes.js +20 -0
- package/dist/assets/asset-prefixes.js.map +1 -0
- package/dist/assets/asset-src.d.ts +23 -0
- package/dist/assets/asset-src.d.ts.map +1 -0
- package/dist/assets/asset-src.js +28 -0
- package/dist/assets/asset-src.js.map +1 -0
- package/dist/assets/asset-url.d.ts +35 -0
- package/dist/assets/asset-url.d.ts.map +1 -0
- package/dist/assets/asset-url.js +77 -0
- package/dist/assets/asset-url.js.map +1 -0
- package/dist/assets/factory.d.ts +18 -0
- package/dist/assets/factory.d.ts.map +1 -0
- package/dist/assets/factory.js +42 -0
- package/dist/assets/factory.js.map +1 -0
- package/dist/assets/finalize.d.ts +36 -0
- package/dist/assets/finalize.d.ts.map +1 -0
- package/dist/assets/finalize.js +72 -0
- package/dist/assets/finalize.js.map +1 -0
- package/dist/assets/index.d.ts +20 -0
- package/dist/assets/index.d.ts.map +1 -0
- package/dist/assets/index.js +19 -0
- package/dist/assets/index.js.map +1 -0
- package/dist/assets/keys.d.ts +56 -0
- package/dist/assets/keys.d.ts.map +1 -0
- package/dist/assets/keys.js +88 -0
- package/dist/assets/keys.js.map +1 -0
- package/dist/assets/pipeline.d.ts +50 -0
- package/dist/assets/pipeline.d.ts.map +1 -0
- package/dist/assets/pipeline.js +202 -0
- package/dist/assets/pipeline.js.map +1 -0
- package/dist/assets/store-local.d.ts +84 -0
- package/dist/assets/store-local.d.ts.map +1 -0
- package/dist/assets/store-local.js +254 -0
- package/dist/assets/store-local.js.map +1 -0
- package/dist/assets/store-s3.d.ts +71 -0
- package/dist/assets/store-s3.d.ts.map +1 -0
- package/dist/assets/store-s3.js +183 -0
- package/dist/assets/store-s3.js.map +1 -0
- package/dist/assets/svg-sanitizer.d.ts +37 -0
- package/dist/assets/svg-sanitizer.d.ts.map +1 -0
- package/dist/assets/svg-sanitizer.js +222 -0
- package/dist/assets/svg-sanitizer.js.map +1 -0
- package/dist/assets/transform-directives.d.ts +97 -0
- package/dist/assets/transform-directives.d.ts.map +1 -0
- package/dist/assets/transform-directives.js +267 -0
- package/dist/assets/transform-directives.js.map +1 -0
- package/dist/assets/transform.d.ts +51 -0
- package/dist/assets/transform.d.ts.map +1 -0
- package/dist/assets/transform.js +190 -0
- package/dist/assets/transform.js.map +1 -0
- package/dist/assets/types.d.ts +82 -0
- package/dist/assets/types.d.ts.map +1 -0
- package/dist/assets/types.js +10 -0
- package/dist/assets/types.js.map +1 -0
- package/dist/authorization/groups/schema.d.ts +2 -2
- package/dist/authorization/permissions/schema.d.ts +2 -2
- package/dist/cli/generate-ai-content.js +92 -7
- package/dist/cli/init.js +16 -4
- package/dist/config/helpers.d.ts.map +1 -1
- package/dist/config/helpers.js +2 -1
- package/dist/config/helpers.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schemas/config.d.ts +10 -0
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/field.d.ts +38 -3
- package/dist/config/schemas/field.d.ts.map +1 -1
- package/dist/config/schemas/field.js +18 -0
- package/dist/config/schemas/field.js.map +1 -1
- package/dist/config/schemas/media.d.ts +8 -0
- package/dist/config/schemas/media.d.ts.map +1 -1
- package/dist/config/schemas/media.js +9 -0
- package/dist/config/schemas/media.js.map +1 -1
- package/dist/config/types.d.ts +43 -3
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +4 -1
- package/dist/config/types.js.map +1 -1
- package/dist/editor/CanopyEditor.d.ts.map +1 -1
- package/dist/editor/CanopyEditor.js +1 -1
- package/dist/editor/CanopyEditor.js.map +1 -1
- package/dist/editor/Editor.d.ts +2 -0
- package/dist/editor/Editor.d.ts.map +1 -1
- package/dist/editor/Editor.js +82 -80
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FormRenderer.d.ts.map +1 -1
- package/dist/editor/FormRenderer.js +9 -0
- package/dist/editor/FormRenderer.js.map +1 -1
- package/dist/editor/components/EditorSidebar.d.ts +6 -1
- package/dist/editor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/editor/components/EditorSidebar.js +4 -3
- package/dist/editor/components/EditorSidebar.js.map +1 -1
- package/dist/editor/context/AssetContext.d.ts +25 -0
- package/dist/editor/context/AssetContext.d.ts.map +1 -0
- package/dist/editor/context/AssetContext.js +26 -0
- package/dist/editor/context/AssetContext.js.map +1 -0
- package/dist/editor/context/index.d.ts +1 -0
- package/dist/editor/context/index.d.ts.map +1 -1
- package/dist/editor/context/index.js +1 -0
- package/dist/editor/context/index.js.map +1 -1
- package/dist/editor/fields/ImageField.d.ts +22 -0
- package/dist/editor/fields/ImageField.d.ts.map +1 -0
- package/dist/editor/fields/ImageField.js +100 -0
- package/dist/editor/fields/ImageField.js.map +1 -0
- package/dist/editor/fields/MarkdownField.d.ts.map +1 -1
- package/dist/editor/fields/MarkdownField.js +30 -4
- package/dist/editor/fields/MarkdownField.js.map +1 -1
- package/dist/editor/fields/MdxImageDialog.d.ts +16 -0
- package/dist/editor/fields/MdxImageDialog.d.ts.map +1 -0
- package/dist/editor/fields/MdxImageDialog.js +73 -0
- package/dist/editor/fields/MdxImageDialog.js.map +1 -0
- package/dist/editor/media/AssetCard.d.ts +21 -0
- package/dist/editor/media/AssetCard.d.ts.map +1 -0
- package/dist/editor/media/AssetCard.js +41 -0
- package/dist/editor/media/AssetCard.js.map +1 -0
- package/dist/editor/media/CropStep.d.ts +26 -0
- package/dist/editor/media/CropStep.d.ts.map +1 -0
- package/dist/editor/media/CropStep.js +38 -0
- package/dist/editor/media/CropStep.js.map +1 -0
- package/dist/editor/media/MediaLibrary.d.ts +22 -0
- package/dist/editor/media/MediaLibrary.d.ts.map +1 -0
- package/dist/editor/media/MediaLibrary.js +17 -0
- package/dist/editor/media/MediaLibrary.js.map +1 -0
- package/dist/editor/media/MediaLibraryBody.d.ts +17 -0
- package/dist/editor/media/MediaLibraryBody.d.ts.map +1 -0
- package/dist/editor/media/MediaLibraryBody.js +133 -0
- package/dist/editor/media/MediaLibraryBody.js.map +1 -0
- package/dist/editor/media/crop-math.d.ts +37 -0
- package/dist/editor/media/crop-math.d.ts.map +1 -0
- package/dist/editor/media/crop-math.js +62 -0
- package/dist/editor/media/crop-math.js.map +1 -0
- package/dist/editor/media/upload-asset.d.ts +31 -0
- package/dist/editor/media/upload-asset.d.ts.map +1 -0
- package/dist/editor/media/upload-asset.js +56 -0
- package/dist/editor/media/upload-asset.js.map +1 -0
- package/dist/editor/media/upload-constants.d.ts +27 -0
- package/dist/editor/media/upload-constants.d.ts.map +1 -0
- package/dist/editor/media/upload-constants.js +40 -0
- package/dist/editor/media/upload-constants.js.map +1 -0
- package/dist/editor/media/useAssetUpload.d.ts +19 -0
- package/dist/editor/media/useAssetUpload.d.ts.map +1 -0
- package/dist/editor/media/useAssetUpload.js +53 -0
- package/dist/editor/media/useAssetUpload.js.map +1 -0
- package/dist/editor/media/xhr-upload.d.ts +20 -0
- package/dist/editor/media/xhr-upload.d.ts.map +1 -0
- package/dist/editor/media/xhr-upload.js +42 -0
- package/dist/editor/media/xhr-upload.js.map +1 -0
- package/dist/editor/theme.d.ts +2 -1
- package/dist/editor/theme.d.ts.map +1 -1
- package/dist/editor/theme.js +1 -0
- package/dist/editor/theme.js.map +1 -1
- package/dist/entry-schema.d.ts +19 -0
- package/dist/entry-schema.d.ts.map +1 -1
- package/dist/entry-schema.js.map +1 -1
- package/dist/http/handler.d.ts +5 -2
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/handler.js +13 -4
- package/dist/http/handler.js.map +1 -1
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +1 -1
- package/dist/http/index.js.map +1 -1
- package/dist/http/router.d.ts +14 -1
- package/dist/http/router.d.ts.map +1 -1
- package/dist/http/router.js +4 -1
- package/dist/http/router.js.map +1 -1
- package/dist/http/types.d.ts +40 -0
- package/dist/http/types.d.ts.map +1 -1
- package/dist/http/types.js +9 -0
- package/dist/http/types.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/schema/schema-store.d.ts +2 -2
- package/dist/server.d.ts +45 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +41 -0
- package/dist/server.js.map +1 -1
- package/dist/validation/entry-validator.d.ts.map +1 -1
- package/dist/validation/entry-validator.js +56 -1
- package/dist/validation/entry-validator.js.map +1 -1
- package/package.json +13 -1
- package/dist/asset-store.d.ts +0 -32
- package/dist/asset-store.d.ts.map +0 -1
- package/dist/asset-store.js +0 -85
- package/dist/asset-store.js.map +0 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Drawer, Modal, Text, Title } from '@mantine/core';
|
|
4
|
+
import { MediaLibraryBody } from './MediaLibraryBody.js';
|
|
5
|
+
/**
|
|
6
|
+
* One component, two presentations, sharing the same grid/upload/filter/
|
|
7
|
+
* pagination/delete core (MediaLibraryBody) - see
|
|
8
|
+
* .claude/future-tasks/assets-media-system.md "MediaManager design".
|
|
9
|
+
*/
|
|
10
|
+
export const MediaLibrary = ({ opened, onClose, mode, onSelect }) => {
|
|
11
|
+
if (mode === 'picker') {
|
|
12
|
+
return (_jsx(Modal, { opened: opened, onClose: onClose, title: "Media library", size: "lg", overlayProps: { blur: 2 }, "data-testid": "media-library-picker", children: _jsx(MediaLibraryBody, { opened: opened, mode: "picker", onSelect: onSelect }) }));
|
|
13
|
+
}
|
|
14
|
+
return (_jsx(Drawer, { opened: opened, onClose: onClose, position: "right", title: _jsxs("div", { children: [_jsx(Title, { order: 4, children: "Media library" }), _jsx(Text, { size: "xs", c: "dimmed", children: "Upload and manage images and PDFs" })] }), padding: "md", size: 700, overlayProps: { blur: 2 }, "data-testid": "media-library-manage", children: _jsx(MediaLibraryBody, { opened: opened, mode: "manage" }) }));
|
|
15
|
+
};
|
|
16
|
+
export default MediaLibrary;
|
|
17
|
+
//# sourceMappingURL=MediaLibrary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaLibrary.js","sourceRoot":"","sources":["../../../src/editor/media/MediaLibrary.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAIZ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAG1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAerD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAgC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC/F,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO,CACL,KAAC,KAAK,IACJ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,KAAK,EAAC,eAAe,EACrB,IAAI,EAAC,IAAI,EACT,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,iBACb,sBAAsB,YAElC,KAAC,gBAAgB,IAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,QAAQ,GAAI,GAChE,CACT,CAAA;IACH,CAAC;IAED,OAAO,CACL,KAAC,MAAM,IACL,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAC,OAAO,EAChB,KAAK,EACH,0BACE,KAAC,KAAK,IAAC,KAAK,EAAE,CAAC,8BAAuB,EACtC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,CAAC,EAAC,QAAQ,kDAEnB,IACH,EAER,OAAO,EAAC,IAAI,EACZ,IAAI,EAAE,GAAG,EACT,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,iBACb,sBAAsB,YAElC,KAAC,gBAAgB,IAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAC,QAAQ,GAAG,GAC3C,CACV,CAAA;AACH,CAAC,CAAA;AAED,eAAe,YAAY,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AssetRecord } from '../../api';
|
|
3
|
+
export interface MediaLibraryBodyProps {
|
|
4
|
+
/** Whether the enclosing Drawer/Modal is open - gates the (re)fetch of the first page on each open. */
|
|
5
|
+
opened: boolean;
|
|
6
|
+
mode: 'manage' | 'picker';
|
|
7
|
+
/** Present in picker mode - clicking a card (or completing an upload) selects the asset. */
|
|
8
|
+
onSelect?: (asset: AssetRecord) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Shared grid/upload/filter/pagination/delete core for both MediaLibrary
|
|
12
|
+
* presentations (Drawer "manage" mode, Modal "picker" mode) - see
|
|
13
|
+
* MediaLibrary.tsx for the two wrappers.
|
|
14
|
+
*/
|
|
15
|
+
export declare const MediaLibraryBody: React.FC<MediaLibraryBodyProps>;
|
|
16
|
+
export default MediaLibraryBody;
|
|
17
|
+
//# sourceMappingURL=MediaLibraryBody.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaLibraryBody.d.ts","sourceRoot":"","sources":["../../../src/editor/media/MediaLibraryBody.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA2C,MAAM,OAAO,CAAA;AAkB/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAiB5C,MAAM,WAAW,qBAAqB;IACpC,uGAAuG;IACvG,MAAM,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;CACxC;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA2M5D,CAAA;AAED,eAAe,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { Alert, Button, Group, Loader, Progress, ScrollArea, SimpleGrid, Stack, Text, TextInput, } from '@mantine/core';
|
|
5
|
+
import { Dropzone } from '@mantine/dropzone';
|
|
6
|
+
import { modals } from '@mantine/modals';
|
|
7
|
+
import { IconAlertCircle, IconUpload } from '@tabler/icons-react';
|
|
8
|
+
import { getErrorMessage } from '../../utils/error.js';
|
|
9
|
+
// Import directly from helpers (not the authorization barrel) to keep server-only
|
|
10
|
+
// authorization code out of this client bundle - matches BranchManager's convention.
|
|
11
|
+
import { isAdmin } from '../../authorization/helpers.js';
|
|
12
|
+
import { useApiClient, useAssetContext } from '../context/index.js';
|
|
13
|
+
import { useUserContext } from '../hooks/index.js';
|
|
14
|
+
import { AssetCard } from './AssetCard.js';
|
|
15
|
+
import { useAssetUpload } from './useAssetUpload.js';
|
|
16
|
+
import { ACCEPTED_ASSET_MIME_TYPES, ACCEPTED_IMAGE_MIME_TYPES, MAX_UPLOAD_BYTES, } from './upload-constants.js';
|
|
17
|
+
const PAGE_SIZE = 40;
|
|
18
|
+
/**
|
|
19
|
+
* Shared grid/upload/filter/pagination/delete core for both MediaLibrary
|
|
20
|
+
* presentations (Drawer "manage" mode, Modal "picker" mode) - see
|
|
21
|
+
* MediaLibrary.tsx for the two wrappers.
|
|
22
|
+
*/
|
|
23
|
+
export const MediaLibraryBody = ({ opened, mode, onSelect }) => {
|
|
24
|
+
const client = useApiClient();
|
|
25
|
+
const { baseUrl } = useAssetContext();
|
|
26
|
+
const { userContext } = useUserContext();
|
|
27
|
+
const canDelete = mode === 'manage' && isAdmin(userContext?.groups);
|
|
28
|
+
const upload = useAssetUpload();
|
|
29
|
+
// `picker` mode is used exclusively by ImageField and the MDX image dialog
|
|
30
|
+
// (see MediaLibraryProps.mode's doc comment) - both image-only contexts.
|
|
31
|
+
// `manage` mode (the Editor's Media Library drawer) keeps accepting PDFs.
|
|
32
|
+
const imageOnly = mode === 'picker';
|
|
33
|
+
const [assets, setAssets] = useState([]);
|
|
34
|
+
const [nextCursor, setNextCursor] = useState(undefined);
|
|
35
|
+
const [loading, setLoading] = useState(false);
|
|
36
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
37
|
+
const [listError, setListError] = useState(null);
|
|
38
|
+
const [filterText, setFilterText] = useState('');
|
|
39
|
+
const loadFirstPage = useCallback(async () => {
|
|
40
|
+
setLoading(true);
|
|
41
|
+
setListError(null);
|
|
42
|
+
try {
|
|
43
|
+
const result = await client.assets.list({ limit: String(PAGE_SIZE) });
|
|
44
|
+
if (!result.ok || !result.data) {
|
|
45
|
+
setListError(result.error || 'Failed to load assets');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
setAssets(result.data.assets);
|
|
49
|
+
setNextCursor(result.data.nextCursor);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
setListError(getErrorMessage(err));
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}, [client]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!opened)
|
|
60
|
+
return;
|
|
61
|
+
void loadFirstPage();
|
|
62
|
+
// Refetch only when the drawer/modal (re)opens, not on every render where
|
|
63
|
+
// loadFirstPage's identity happens to change.
|
|
64
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
|
+
}, [opened]);
|
|
66
|
+
const handleLoadMore = async () => {
|
|
67
|
+
if (!nextCursor)
|
|
68
|
+
return;
|
|
69
|
+
setLoadingMore(true);
|
|
70
|
+
setListError(null);
|
|
71
|
+
try {
|
|
72
|
+
const result = await client.assets.list({ cursor: nextCursor, limit: String(PAGE_SIZE) });
|
|
73
|
+
if (!result.ok || !result.data) {
|
|
74
|
+
setListError(result.error || 'Failed to load more assets');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const page = result.data;
|
|
78
|
+
setAssets((prev) => [...prev, ...page.assets]);
|
|
79
|
+
setNextCursor(page.nextCursor);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
setListError(getErrorMessage(err));
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setLoadingMore(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const handleDrop = async (files) => {
|
|
89
|
+
const file = files[0];
|
|
90
|
+
if (!file)
|
|
91
|
+
return;
|
|
92
|
+
const asset = await upload.upload(file);
|
|
93
|
+
if (asset) {
|
|
94
|
+
setAssets((prev) => [asset, ...prev]);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const handleReject = (rejections) => {
|
|
98
|
+
setListError(rejections[0]?.errors[0]?.message ?? 'File rejected');
|
|
99
|
+
};
|
|
100
|
+
const handleDelete = async (asset) => {
|
|
101
|
+
const result = await client.assets.delete({ key: asset.hash32 });
|
|
102
|
+
if (!result.ok) {
|
|
103
|
+
setListError(result.error || 'Failed to delete asset');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
setAssets((prev) => prev.filter((a) => a.hash32 !== asset.hash32));
|
|
107
|
+
};
|
|
108
|
+
const handleDeleteClick = (asset) => {
|
|
109
|
+
modals.openConfirmModal({
|
|
110
|
+
title: 'Remove from library',
|
|
111
|
+
children: (_jsxs(Text, { size: "sm", children: ["Remove \u201C", asset.filename, "\u201D from the media library? Existing content that already references this file keeps working - removing it here only hides it from the library; the file itself is not deleted."] })),
|
|
112
|
+
labels: { confirm: 'Remove', cancel: 'Cancel' },
|
|
113
|
+
confirmProps: { color: 'red' },
|
|
114
|
+
onConfirm: () => {
|
|
115
|
+
void handleDelete(asset);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
// Picker mode is image-only (see `imageOnly` above) - pdf assets are
|
|
120
|
+
// filtered out of the pickable grid entirely, not just the upload accept
|
|
121
|
+
// list, since an already-uploaded pdf could otherwise still be selected.
|
|
122
|
+
const kindFiltered = imageOnly ? assets.filter((asset) => asset.kind !== 'pdf') : assets;
|
|
123
|
+
const normalizedFilter = filterText.trim().toLowerCase();
|
|
124
|
+
const filteredAssets = normalizedFilter
|
|
125
|
+
? kindFiltered.filter((asset) => asset.filename.toLowerCase().includes(normalizedFilter))
|
|
126
|
+
: kindFiltered;
|
|
127
|
+
return (_jsxs(Stack, { gap: "sm", h: "100%", style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Dropzone, { onDrop: (files) => void handleDrop(files), onReject: handleReject, maxSize: MAX_UPLOAD_BYTES, accept: imageOnly ? ACCEPTED_IMAGE_MIME_TYPES : ACCEPTED_ASSET_MIME_TYPES, loading: upload.uploading, multiple: false, "data-testid": "media-library-dropzone", children: _jsxs(Group, { justify: "center", gap: "xs", py: "sm", style: { pointerEvents: 'none' }, children: [_jsx(IconUpload, { size: 20 }), _jsx(Text, { size: "sm", children: imageOnly
|
|
128
|
+
? 'Drop an image here, or click to browse'
|
|
129
|
+
: 'Drop an image or PDF here, or click to browse' })] }) }), upload.uploading &&
|
|
130
|
+
(upload.progress !== null ? (_jsx(Progress, { value: upload.progress * 100, size: "sm", "data-testid": "upload-progress" })) : (_jsx(Progress, { value: 100, size: "sm", animated: true, "data-testid": "upload-progress-indeterminate" }))), upload.error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", "data-testid": "upload-error", children: upload.error })), _jsx(TextInput, { placeholder: "Filter by filename...", value: filterText, onChange: (event) => setFilterText(event.currentTarget.value), "data-testid": "media-library-filter" }), listError && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", "data-testid": "media-library-error", children: _jsxs(Stack, { gap: 4, children: [_jsx(Text, { size: "sm", children: listError }), _jsx(Button, { size: "xs", variant: "light", onClick: () => void loadFirstPage(), children: "Retry" })] }) })), _jsxs(ScrollArea, { style: { flex: 1 }, children: [loading ? (_jsx(Group, { justify: "center", py: "xl", children: _jsx(Loader, { size: "sm", "data-testid": "media-library-loading" }) })) : filteredAssets.length === 0 ? (_jsx(Text, { size: "sm", c: "dimmed", py: "xl", ta: "center", children: "No assets found." })) : (_jsx(SimpleGrid, { cols: { base: 2, sm: 3, md: 4 }, spacing: "sm", children: filteredAssets.map((asset) => (_jsx(AssetCard, { asset: asset, baseUrl: baseUrl, onSelect: mode === 'picker' ? onSelect : undefined, onDelete: canDelete ? () => handleDeleteClick(asset) : undefined }, asset.hash32))) })), !loading && nextCursor && (_jsx(Group, { justify: "center", py: "sm", children: _jsx(Button, { variant: "light", size: "xs", loading: loadingMore, onClick: () => void handleLoadMore(), "data-testid": "media-library-load-more", children: "Load more" }) }))] })] }));
|
|
131
|
+
};
|
|
132
|
+
export default MediaLibraryBody;
|
|
133
|
+
//# sourceMappingURL=MediaLibraryBody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaLibraryBody.js","sourceRoot":"","sources":["../../../src/editor/media/MediaLibraryBody.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure math for the ImageField crop step: converting between react-easy-crop's
|
|
3
|
+
* percentage-based `Area` shape and CanopyCMS's normalized 0..1 `CropRect`
|
|
4
|
+
* (the same shape stored in `ImageFieldValue.crop` and accepted by the `c=`
|
|
5
|
+
* transform directive - see assets/transform-directives.ts). Kept dependency-free
|
|
6
|
+
* (no react-easy-crop import) so it's trivially unit-testable and reusable
|
|
7
|
+
* without pulling in the cropper UI library.
|
|
8
|
+
*/
|
|
9
|
+
import { type CropRect } from '../../assets/transform-directives';
|
|
10
|
+
/** react-easy-crop's `Area` shape, expressed in percentages (0..100). */
|
|
11
|
+
export interface CropAreaPercent {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Convert react-easy-crop's `onCropComplete(croppedArea, croppedAreaPixels)`
|
|
19
|
+
* percentage area into a normalized `CropRect`, rounded to 4 decimals and
|
|
20
|
+
* clamped to fit within bounds. Rounding can push `x+w`/`y+h` a hair over 1
|
|
21
|
+
* (e.g. x=0.6667, w=0.3334 -> 1.0001) - shrink the extent to the remaining
|
|
22
|
+
* space rather than reject a rect that was valid before rounding.
|
|
23
|
+
*
|
|
24
|
+
* Returns `null` if the input can't be coerced into a valid rect (e.g. a
|
|
25
|
+
* zero-area selection).
|
|
26
|
+
*/
|
|
27
|
+
export declare function cropAreaPercentToRect(area: CropAreaPercent): CropRect | null;
|
|
28
|
+
/**
|
|
29
|
+
* Inverse of `cropAreaPercentToRect` - used to seed react-easy-crop's
|
|
30
|
+
* `initialCroppedAreaPercentages` when re-opening the crop step for an
|
|
31
|
+
* already-cropped image, so the user sees their previous selection instead
|
|
32
|
+
* of starting over.
|
|
33
|
+
*/
|
|
34
|
+
export declare function cropRectToAreaPercent(rect: CropRect): CropAreaPercent;
|
|
35
|
+
/** "W:H" -> the numeric aspect ratio react-easy-crop's `aspect` prop expects. Mirrors config/schemas/field.ts's ASPECT_RATIO_RE; returns undefined for anything malformed rather than throwing (defensive - the field config was already validated at schema-parse time). */
|
|
36
|
+
export declare function parseAspectRatio(aspect: string | undefined): number | undefined;
|
|
37
|
+
//# sourceMappingURL=crop-math.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crop-math.d.ts","sourceRoot":"","sources":["../../../src/editor/media/crop-math.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,mCAAmC,CAAA;AAElF,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAeD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,GAAG,IAAI,CAW5E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,eAAe,CAErE;AAED,6QAA6Q;AAC7Q,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAK/E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure math for the ImageField crop step: converting between react-easy-crop's
|
|
3
|
+
* percentage-based `Area` shape and CanopyCMS's normalized 0..1 `CropRect`
|
|
4
|
+
* (the same shape stored in `ImageFieldValue.crop` and accepted by the `c=`
|
|
5
|
+
* transform directive - see assets/transform-directives.ts). Kept dependency-free
|
|
6
|
+
* (no react-easy-crop import) so it's trivially unit-testable and reusable
|
|
7
|
+
* without pulling in the cropper UI library.
|
|
8
|
+
*/
|
|
9
|
+
import { isValidCropRect } from '../../assets/transform-directives.js';
|
|
10
|
+
/** Matches transform-directives.ts's CROP_PRECISION so a crop rect round-trips through the URL directive unchanged. */
|
|
11
|
+
const CROP_PRECISION = 4;
|
|
12
|
+
function roundTo(value, decimals) {
|
|
13
|
+
const factor = 10 ** decimals;
|
|
14
|
+
return Math.round(value * factor) / factor;
|
|
15
|
+
}
|
|
16
|
+
function clamp01(value) {
|
|
17
|
+
if (!Number.isFinite(value))
|
|
18
|
+
return 0;
|
|
19
|
+
return Math.min(1, Math.max(0, value));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Convert react-easy-crop's `onCropComplete(croppedArea, croppedAreaPixels)`
|
|
23
|
+
* percentage area into a normalized `CropRect`, rounded to 4 decimals and
|
|
24
|
+
* clamped to fit within bounds. Rounding can push `x+w`/`y+h` a hair over 1
|
|
25
|
+
* (e.g. x=0.6667, w=0.3334 -> 1.0001) - shrink the extent to the remaining
|
|
26
|
+
* space rather than reject a rect that was valid before rounding.
|
|
27
|
+
*
|
|
28
|
+
* Returns `null` if the input can't be coerced into a valid rect (e.g. a
|
|
29
|
+
* zero-area selection).
|
|
30
|
+
*/
|
|
31
|
+
export function cropAreaPercentToRect(area) {
|
|
32
|
+
const x = roundTo(clamp01(area.x / 100), CROP_PRECISION);
|
|
33
|
+
const y = roundTo(clamp01(area.y / 100), CROP_PRECISION);
|
|
34
|
+
let w = roundTo(clamp01(area.width / 100), CROP_PRECISION);
|
|
35
|
+
let h = roundTo(clamp01(area.height / 100), CROP_PRECISION);
|
|
36
|
+
if (x + w > 1)
|
|
37
|
+
w = roundTo(1 - x, CROP_PRECISION);
|
|
38
|
+
if (y + h > 1)
|
|
39
|
+
h = roundTo(1 - y, CROP_PRECISION);
|
|
40
|
+
if (!isValidCropRect(x, y, w, h))
|
|
41
|
+
return null;
|
|
42
|
+
return { x, y, w, h };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Inverse of `cropAreaPercentToRect` - used to seed react-easy-crop's
|
|
46
|
+
* `initialCroppedAreaPercentages` when re-opening the crop step for an
|
|
47
|
+
* already-cropped image, so the user sees their previous selection instead
|
|
48
|
+
* of starting over.
|
|
49
|
+
*/
|
|
50
|
+
export function cropRectToAreaPercent(rect) {
|
|
51
|
+
return { x: rect.x * 100, y: rect.y * 100, width: rect.w * 100, height: rect.h * 100 };
|
|
52
|
+
}
|
|
53
|
+
/** "W:H" -> the numeric aspect ratio react-easy-crop's `aspect` prop expects. Mirrors config/schemas/field.ts's ASPECT_RATIO_RE; returns undefined for anything malformed rather than throwing (defensive - the field config was already validated at schema-parse time). */
|
|
54
|
+
export function parseAspectRatio(aspect) {
|
|
55
|
+
if (!aspect)
|
|
56
|
+
return undefined;
|
|
57
|
+
const match = /^([1-9][0-9]*):([1-9][0-9]*)$/.exec(aspect);
|
|
58
|
+
if (!match)
|
|
59
|
+
return undefined;
|
|
60
|
+
return Number(match[1]) / Number(match[2]);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=crop-math.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crop-math.js","sourceRoot":"","sources":["../../../src/editor/media/crop-math.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,eAAe,EAAiB,MAAM,mCAAmC,CAAA;AAUlF,uHAAuH;AACvH,MAAM,cAAc,GAAG,CAAC,CAAA;AAExB,SAAS,OAAO,CAAC,KAAa,EAAE,QAAgB;IAC9C,MAAM,MAAM,GAAG,EAAE,IAAI,QAAQ,CAAA;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,MAAM,CAAA;AAC5C,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACrC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAA;AACxC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAqB;IACzD,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,cAAc,CAAC,CAAA;IACxD,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,cAAc,CAAC,CAAA;IACxD,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,EAAE,cAAc,CAAC,CAAA;IAC1D,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,EAAE,cAAc,CAAC,CAAA;IAE3D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC,CAAA;IACjD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC,CAAA;IAEjD,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7C,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAc;IAClD,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,CAAA;AACxF,CAAC;AAED,6QAA6Q;AAC7Q,MAAM,UAAU,gBAAgB,CAAC,MAA0B;IACzD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,MAAM,KAAK,GAAG,+BAA+B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAC5C,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared upload state machine: presign -> transport -> finalize. Used by
|
|
3
|
+
* every asset-upload entry point in the editor (MediaLibrary's dropzone,
|
|
4
|
+
* ImageField's empty-state dropzone, the MDX image dialog's Upload tab, and
|
|
5
|
+
* MDXEditor's own drag/drop/paste `imageUploadHandler`) so the flow (and its
|
|
6
|
+
* 415/413 pipeline-rejection error surfacing) lives in exactly one place.
|
|
7
|
+
*
|
|
8
|
+
* Framework-agnostic (no React) so `MarkdownField`'s `imageUploadHandler`
|
|
9
|
+
* prop - a plain `(file: File) => Promise<string>` function required by
|
|
10
|
+
* MDXEditor's `imagePlugin`, called outside of any component render - can
|
|
11
|
+
* call it directly. `useAssetUpload.ts` wraps this with React state for
|
|
12
|
+
* callers that want progress/error UI.
|
|
13
|
+
*/
|
|
14
|
+
import type { ApiClient } from '../context';
|
|
15
|
+
import type { AssetRecord } from '../../api';
|
|
16
|
+
/**
|
|
17
|
+
* Upload `file` through the asset pipeline and return the finalized
|
|
18
|
+
* `AssetRecord`. `onProgress` receives a 0..1 fraction while the transport is
|
|
19
|
+
* underway for direct-upload stores (S3 presigned POST); it is never called
|
|
20
|
+
* for proxied stores (no XHR involved - `client.assets.uploadProxied` uses
|
|
21
|
+
* `fetch`, which exposes no upload progress) or during the (fast) finalize
|
|
22
|
+
* step, so callers should show an indeterminate indicator whenever they
|
|
23
|
+
* aren't actively receiving progress calls.
|
|
24
|
+
*
|
|
25
|
+
* Throws an `Error` (message = the server's `error` string when available)
|
|
26
|
+
* on any failure - a rejected presign (415 unsupported type, 413 too large),
|
|
27
|
+
* a failed transport, or a rejected finalize (e.g. a magic-byte sniff
|
|
28
|
+
* mismatch or SVG sanitization failure).
|
|
29
|
+
*/
|
|
30
|
+
export declare function uploadAsset(client: ApiClient, file: File, onProgress?: (fraction: number) => void): Promise<AssetRecord>;
|
|
31
|
+
//# sourceMappingURL=upload-asset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-asset.d.ts","sourceRoot":"","sources":["../../../src/editor/media/upload-asset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAG5C;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,IAAI,EACV,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GACtC,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared upload state machine: presign -> transport -> finalize. Used by
|
|
3
|
+
* every asset-upload entry point in the editor (MediaLibrary's dropzone,
|
|
4
|
+
* ImageField's empty-state dropzone, the MDX image dialog's Upload tab, and
|
|
5
|
+
* MDXEditor's own drag/drop/paste `imageUploadHandler`) so the flow (and its
|
|
6
|
+
* 415/413 pipeline-rejection error surfacing) lives in exactly one place.
|
|
7
|
+
*
|
|
8
|
+
* Framework-agnostic (no React) so `MarkdownField`'s `imageUploadHandler`
|
|
9
|
+
* prop - a plain `(file: File) => Promise<string>` function required by
|
|
10
|
+
* MDXEditor's `imagePlugin`, called outside of any component render - can
|
|
11
|
+
* call it directly. `useAssetUpload.ts` wraps this with React state for
|
|
12
|
+
* callers that want progress/error UI.
|
|
13
|
+
*/
|
|
14
|
+
import { uploadToPresignedPost } from './xhr-upload.js';
|
|
15
|
+
/**
|
|
16
|
+
* Upload `file` through the asset pipeline and return the finalized
|
|
17
|
+
* `AssetRecord`. `onProgress` receives a 0..1 fraction while the transport is
|
|
18
|
+
* underway for direct-upload stores (S3 presigned POST); it is never called
|
|
19
|
+
* for proxied stores (no XHR involved - `client.assets.uploadProxied` uses
|
|
20
|
+
* `fetch`, which exposes no upload progress) or during the (fast) finalize
|
|
21
|
+
* step, so callers should show an indeterminate indicator whenever they
|
|
22
|
+
* aren't actively receiving progress calls.
|
|
23
|
+
*
|
|
24
|
+
* Throws an `Error` (message = the server's `error` string when available)
|
|
25
|
+
* on any failure - a rejected presign (415 unsupported type, 413 too large),
|
|
26
|
+
* a failed transport, or a rejected finalize (e.g. a magic-byte sniff
|
|
27
|
+
* mismatch or SVG sanitization failure).
|
|
28
|
+
*/
|
|
29
|
+
export async function uploadAsset(client, file, onProgress) {
|
|
30
|
+
const presignResult = await client.assets.presign({
|
|
31
|
+
filename: file.name,
|
|
32
|
+
contentType: file.type || 'application/octet-stream',
|
|
33
|
+
size: file.size,
|
|
34
|
+
});
|
|
35
|
+
if (!presignResult.ok || !presignResult.data) {
|
|
36
|
+
throw new Error(presignResult.error || 'Failed to start upload');
|
|
37
|
+
}
|
|
38
|
+
const { upload } = presignResult.data;
|
|
39
|
+
if (upload.mode === 'direct') {
|
|
40
|
+
await uploadToPresignedPost(upload, file, onProgress);
|
|
41
|
+
const finalizeResult = await client.assets.finalize({
|
|
42
|
+
stagingKey: upload.stagingKey,
|
|
43
|
+
filename: file.name,
|
|
44
|
+
});
|
|
45
|
+
if (!finalizeResult.ok || !finalizeResult.data) {
|
|
46
|
+
throw new Error(finalizeResult.error || 'Failed to finalize upload');
|
|
47
|
+
}
|
|
48
|
+
return finalizeResult.data.asset;
|
|
49
|
+
}
|
|
50
|
+
const uploadResult = await client.assets.uploadProxied(file);
|
|
51
|
+
if (!uploadResult.ok || !uploadResult.data) {
|
|
52
|
+
throw new Error(uploadResult.error || 'Failed to upload');
|
|
53
|
+
}
|
|
54
|
+
return uploadResult.data.asset;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=upload-asset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-asset.js","sourceRoot":"","sources":["../../../src/editor/media/upload-asset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAiB,EACjB,IAAU,EACV,UAAuC;IAEvC,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QAChD,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,WAAW,EAAE,IAAI,CAAC,IAAI,IAAI,0BAA0B;QACpD,IAAI,EAAE,IAAI,CAAC,IAAI;KAChB,CAAC,CAAA;IACF,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,KAAK,IAAI,wBAAwB,CAAC,CAAA;IAClE,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAA;IAErC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,qBAAqB,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAA;QACrD,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;YAClD,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAA;QACF,IAAI,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,KAAK,IAAI,2BAA2B,CAAC,CAAA;QACtE,CAAC;QACD,OAAO,cAAc,CAAC,IAAI,CAAC,KAAK,CAAA;IAClC,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;IAC5D,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,KAAK,IAAI,kBAAkB,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAA;AAChC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side upload UX constants. Deliberately duplicated from
|
|
3
|
+
* assets/pipeline.ts's `ALLOWED_UPLOAD_CONTENT_TYPES`/size caps rather than
|
|
4
|
+
* imported from it: pipeline.ts is server-only (pulls in `file-type` and
|
|
5
|
+
* `image-size`) and must never reach a browser bundle. These are UX-only
|
|
6
|
+
* (Dropzone's `accept`/`maxSize` props reject bad files before an upload even
|
|
7
|
+
* starts) - the server-side finalize pipeline is the actual source of truth
|
|
8
|
+
* and re-validates independently.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Mirrors assets/pipeline.ts's RASTER_MIME_TYPES + SVG_MIME_TYPE + PDF_MIME_TYPE.
|
|
12
|
+
* A plain (not `readonly`/`as const`) `string[]` - `@mantine/dropzone`'s
|
|
13
|
+
* `accept` prop takes `string[] | Accept`, neither of which accepts a
|
|
14
|
+
* readonly tuple.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ACCEPTED_ASSET_MIME_TYPES: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Image-only subset of `ACCEPTED_ASSET_MIME_TYPES` (raster + svg, no PDF) -
|
|
19
|
+
* for contexts that only ever want an image out: the ImageField/MDX-image
|
|
20
|
+
* dropzones, and MediaLibraryBody's `picker` mode (used exclusively by those
|
|
21
|
+
* two - see MediaLibrary.tsx's doc comment). The general MediaLibrary
|
|
22
|
+
* (`manage` mode) keeps accepting the full set, PDF included.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ACCEPTED_IMAGE_MIME_TYPES: string[];
|
|
25
|
+
/** Mirrors store-local.ts/store-s3.ts's DEFAULT_MAX_UPLOAD_BYTES. The real limit is enforced by the presigned target's `maxBytes` (S3 content-length-range or the local store's own cap); this only avoids a doomed upload attempt. */
|
|
26
|
+
export declare const MAX_UPLOAD_BYTES: number;
|
|
27
|
+
//# sourceMappingURL=upload-constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-constants.d.ts","sourceRoot":"","sources":["../../../src/editor/media/upload-constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,EAO7C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,EAM7C,CAAA;AAED,uOAAuO;AACvO,eAAO,MAAM,gBAAgB,QAAmB,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side upload UX constants. Deliberately duplicated from
|
|
3
|
+
* assets/pipeline.ts's `ALLOWED_UPLOAD_CONTENT_TYPES`/size caps rather than
|
|
4
|
+
* imported from it: pipeline.ts is server-only (pulls in `file-type` and
|
|
5
|
+
* `image-size`) and must never reach a browser bundle. These are UX-only
|
|
6
|
+
* (Dropzone's `accept`/`maxSize` props reject bad files before an upload even
|
|
7
|
+
* starts) - the server-side finalize pipeline is the actual source of truth
|
|
8
|
+
* and re-validates independently.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Mirrors assets/pipeline.ts's RASTER_MIME_TYPES + SVG_MIME_TYPE + PDF_MIME_TYPE.
|
|
12
|
+
* A plain (not `readonly`/`as const`) `string[]` - `@mantine/dropzone`'s
|
|
13
|
+
* `accept` prop takes `string[] | Accept`, neither of which accepts a
|
|
14
|
+
* readonly tuple.
|
|
15
|
+
*/
|
|
16
|
+
export const ACCEPTED_ASSET_MIME_TYPES = [
|
|
17
|
+
'image/png',
|
|
18
|
+
'image/jpeg',
|
|
19
|
+
'image/webp',
|
|
20
|
+
'image/gif',
|
|
21
|
+
'image/svg+xml',
|
|
22
|
+
'application/pdf',
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Image-only subset of `ACCEPTED_ASSET_MIME_TYPES` (raster + svg, no PDF) -
|
|
26
|
+
* for contexts that only ever want an image out: the ImageField/MDX-image
|
|
27
|
+
* dropzones, and MediaLibraryBody's `picker` mode (used exclusively by those
|
|
28
|
+
* two - see MediaLibrary.tsx's doc comment). The general MediaLibrary
|
|
29
|
+
* (`manage` mode) keeps accepting the full set, PDF included.
|
|
30
|
+
*/
|
|
31
|
+
export const ACCEPTED_IMAGE_MIME_TYPES = [
|
|
32
|
+
'image/png',
|
|
33
|
+
'image/jpeg',
|
|
34
|
+
'image/webp',
|
|
35
|
+
'image/gif',
|
|
36
|
+
'image/svg+xml',
|
|
37
|
+
];
|
|
38
|
+
/** Mirrors store-local.ts/store-s3.ts's DEFAULT_MAX_UPLOAD_BYTES. The real limit is enforced by the presigned target's `maxBytes` (S3 content-length-range or the local store's own cap); this only avoids a doomed upload attempt. */
|
|
39
|
+
export const MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
40
|
+
//# sourceMappingURL=upload-constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-constants.js","sourceRoot":"","sources":["../../../src/editor/media/upload-constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAa;IACjD,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,eAAe;IACf,iBAAiB;CAClB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAa;IACjD,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,eAAe;CAChB,CAAA;AAED,uOAAuO;AACvO,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AssetRecord } from '../../api';
|
|
2
|
+
export interface UseAssetUploadResult {
|
|
3
|
+
uploading: boolean;
|
|
4
|
+
/** 0..1 fraction while a direct-upload transport is in flight; `null` when indeterminate (proxied stores, or the finalize step). */
|
|
5
|
+
progress: number | null;
|
|
6
|
+
error: string | null;
|
|
7
|
+
/** Upload `file`, returning the finalized AssetRecord, or `null` if the upload failed (see `error`). */
|
|
8
|
+
upload: (file: File) => Promise<AssetRecord | null>;
|
|
9
|
+
reset: () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* React state wrapper around `uploadAsset` for UI that shows upload
|
|
13
|
+
* progress/errors (MediaLibrary's dropzone, ImageField's empty state, the MDX
|
|
14
|
+
* image dialog's Upload tab). Uses `useApiClient()` (context DI) rather than
|
|
15
|
+
* `createApiClient()` so tests can inject a mock client via
|
|
16
|
+
* `ApiClientProvider`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useAssetUpload(): UseAssetUploadResult;
|
|
19
|
+
//# sourceMappingURL=useAssetUpload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAssetUpload.d.ts","sourceRoot":"","sources":["../../../src/editor/media/useAssetUpload.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAK5C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,OAAO,CAAA;IAClB,oIAAoI;IACpI,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,wGAAwG;IACxG,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACnD,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,oBAAoB,CAyCrD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useRef, useState } from 'react';
|
|
3
|
+
import { getErrorMessage } from '../../utils/error.js';
|
|
4
|
+
import { useApiClient } from '../context/index.js';
|
|
5
|
+
import { uploadAsset } from './upload-asset.js';
|
|
6
|
+
/**
|
|
7
|
+
* React state wrapper around `uploadAsset` for UI that shows upload
|
|
8
|
+
* progress/errors (MediaLibrary's dropzone, ImageField's empty state, the MDX
|
|
9
|
+
* image dialog's Upload tab). Uses `useApiClient()` (context DI) rather than
|
|
10
|
+
* `createApiClient()` so tests can inject a mock client via
|
|
11
|
+
* `ApiClientProvider`.
|
|
12
|
+
*/
|
|
13
|
+
export function useAssetUpload() {
|
|
14
|
+
const client = useApiClient();
|
|
15
|
+
const [uploading, setUploading] = useState(false);
|
|
16
|
+
const [progress, setProgress] = useState(null);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
// Guards against a progress/state update landing after the component that
|
|
19
|
+
// owns this hook has moved on to a different upload (or unmounted) -
|
|
20
|
+
// avoids a stale "uploading" flicker if a slow upload resolves after a
|
|
21
|
+
// newer one already started.
|
|
22
|
+
const uploadIdRef = useRef(0);
|
|
23
|
+
const upload = useCallback(async (file) => {
|
|
24
|
+
const uploadId = ++uploadIdRef.current;
|
|
25
|
+
setUploading(true);
|
|
26
|
+
setProgress(0);
|
|
27
|
+
setError(null);
|
|
28
|
+
try {
|
|
29
|
+
const asset = await uploadAsset(client, file, (fraction) => {
|
|
30
|
+
if (uploadIdRef.current === uploadId)
|
|
31
|
+
setProgress(fraction);
|
|
32
|
+
});
|
|
33
|
+
return asset;
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (uploadIdRef.current === uploadId)
|
|
37
|
+
setError(getErrorMessage(err));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
if (uploadIdRef.current === uploadId) {
|
|
42
|
+
setUploading(false);
|
|
43
|
+
setProgress(null);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}, [client]);
|
|
47
|
+
const reset = useCallback(() => {
|
|
48
|
+
setError(null);
|
|
49
|
+
setProgress(null);
|
|
50
|
+
}, []);
|
|
51
|
+
return { uploading, progress, error, upload, reset };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=useAssetUpload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAssetUpload.js","sourceRoot":"","sources":["../../../src/editor/media/useAssetUpload.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGrD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAY5C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAA;IAC7B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAC7D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,0EAA0E;IAC1E,qEAAqE;IACrE,uEAAuE;IACvE,6BAA6B;IAC7B,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAE7B,MAAM,MAAM,GAAG,WAAW,CACxB,KAAK,EAAE,IAAU,EAA+B,EAAE;QAChD,MAAM,QAAQ,GAAG,EAAE,WAAW,CAAC,OAAO,CAAA;QACtC,YAAY,CAAC,IAAI,CAAC,CAAA;QAClB,WAAW,CAAC,CAAC,CAAC,CAAA;QACd,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACzD,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ;oBAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;YAC7D,CAAC,CAAC,CAAA;YACF,OAAO,KAAK,CAAA;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ;gBAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAA;YACpE,OAAO,IAAI,CAAA;QACb,CAAC;gBAAS,CAAC;YACT,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACrC,YAAY,CAAC,KAAK,CAAC,CAAA;gBACnB,WAAW,CAAC,IAAI,CAAC,CAAA;YACnB,CAAC;QACH,CAAC;IACH,CAAC,EACD,CAAC,MAAM,CAAC,CACT,CAAA;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,WAAW,CAAC,IAAI,CAAC,CAAA;IACnB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AACtD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw XHR POST for S3 presigned-POST direct uploads. Deliberately bypasses
|
|
3
|
+
* the API client's `request()` (which always does `fetch` + `response.json()`)
|
|
4
|
+
* for two reasons: this posts to the presigned `upload.url` (not our own API),
|
|
5
|
+
* and XHR is the only fetch-adjacent browser API that exposes upload progress
|
|
6
|
+
* events.
|
|
7
|
+
*/
|
|
8
|
+
export interface PresignedPostTarget {
|
|
9
|
+
url: string;
|
|
10
|
+
fields: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* POST `file` to a presigned-POST target. S3 requires the `file` field to be
|
|
14
|
+
* the LAST field in the multipart body - fields from the presign response
|
|
15
|
+
* are appended first, `file` last. Resolves on any 2xx response; rejects with
|
|
16
|
+
* a descriptive Error otherwise (S3 error bodies are XML and not parsed here -
|
|
17
|
+
* the status code is enough to surface a "something went wrong" message).
|
|
18
|
+
*/
|
|
19
|
+
export declare function uploadToPresignedPost(target: PresignedPostTarget, file: File, onProgress?: (fraction: number) => void): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=xhr-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xhr-upload.d.ts","sourceRoot":"","sources":["../../../src/editor/media/xhr-upload.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,IAAI,EACV,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GACtC,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
|