@pantheon-systems/p1-media 0.4.2 → 0.4.4
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 +56 -0
- package/dist/index.d.mts +16 -6
- package/dist/index.d.ts +16 -6
- package/dist/index.js +136 -113
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +136 -113
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +13 -3
- package/dist/server.d.ts +13 -3
- package/dist/server.js +25 -6
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +25 -6
- package/dist/server.mjs.map +1 -1
- package/package.json +16 -11
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @pantheon-systems/p1-media
|
|
2
|
+
|
|
3
|
+
Media library plugin for the P1 Puck editor, backed by Cloudflare R2. Adds asset upload and
|
|
4
|
+
browsing to the editor, plus render helpers and a ready-made media figure block.
|
|
5
|
+
|
|
6
|
+
> Part of Pantheon's **P1** platform. It is published publicly so P1 applications can install
|
|
7
|
+
> it, but it requires the Pantheon-hosted media Worker and is not a general-purpose Puck
|
|
8
|
+
> plugin. Pre-1.0: minor versions may carry breaking changes.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @pantheon-systems/p1-media
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Peer dependencies:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @pantheon-systems/puck-css @puckeditor/core react
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Add the plugin to the editor:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { createMediaPlugin } from "@pantheon-systems/p1-media";
|
|
28
|
+
|
|
29
|
+
const mediaPlugin = createMediaPlugin({
|
|
30
|
+
workerUrl: process.env.NEXT_PUBLIC_MEDIA_WORKER_URL,
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`siteId` defaults to the ambient `P1PuckProvider` site context — pass it explicitly only when
|
|
35
|
+
rendering outside that provider, or to override it. `workerUrl` defaults to the production
|
|
36
|
+
media host.
|
|
37
|
+
|
|
38
|
+
Render uploaded media with the provided components, which handle R2 image transformation URLs:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { MediaImage, MediaFigure, buildImageUrl } from "@pantheon-systems/p1-media";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`createMediaFigureBlock` builds a drop-in Puck block for captioned media.
|
|
45
|
+
|
|
46
|
+
## Entry points
|
|
47
|
+
|
|
48
|
+
| Import | Contents |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `@pantheon-systems/p1-media` | Plugin, render components, media value helpers |
|
|
51
|
+
| `.../server` | Server-side helpers |
|
|
52
|
+
|
|
53
|
+
## Note on the package name
|
|
54
|
+
|
|
55
|
+
The source directory is `packages/p1-media-r2/`, but the package publishes as
|
|
56
|
+
`@pantheon-systems/p1-media`.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import {
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ImgHTMLAttributes, ReactElement, ReactNode } from 'react';
|
|
3
3
|
import { ComponentConfig } from '@puckeditor/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -116,8 +116,8 @@ declare function createMediaPlugin(options: MediaPluginOptions): {
|
|
|
116
116
|
name: string;
|
|
117
117
|
overrides: {
|
|
118
118
|
fieldTypes: {
|
|
119
|
-
text: (props: any) =>
|
|
120
|
-
"p1-media": (props: any) =>
|
|
119
|
+
text: (props: any) => react.JSX.Element;
|
|
120
|
+
"p1-media": (props: any) => react.JSX.Element;
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
123
|
fieldTransforms: {
|
|
@@ -188,7 +188,7 @@ declare function getMediaProps(value: MediaFieldValue | null | undefined, option
|
|
|
188
188
|
* (a broken foreign-origin image is never emitted). `alt` may be overridden;
|
|
189
189
|
* otherwise it comes from the value.
|
|
190
190
|
*/
|
|
191
|
-
declare function MediaImage({ image, mediaBaseUrl, transform, alt, ...rest }: {
|
|
191
|
+
declare function MediaImage({ image, mediaBaseUrl, transform, alt, loading, decoding, ...rest }: {
|
|
192
192
|
image: MediaFieldValue | null | undefined;
|
|
193
193
|
mediaBaseUrl?: string;
|
|
194
194
|
transform?: ImageTransformParams;
|
|
@@ -198,17 +198,21 @@ declare function MediaImage({ image, mediaBaseUrl, transform, alt, ...rest }: {
|
|
|
198
198
|
* ESCAPED text (React default escaping — never dangerouslySetInnerHTML, req.
|
|
199
199
|
* R6). Renders nothing when the src is rejected/empty.
|
|
200
200
|
*/
|
|
201
|
-
declare function MediaFigure({ image, schema, mediaBaseUrl, transform, className, captionClassName, }: {
|
|
201
|
+
declare function MediaFigure({ image, schema, mediaBaseUrl, transform, className, captionClassName, loading, decoding, }: {
|
|
202
202
|
image: MediaFieldValue | null | undefined;
|
|
203
203
|
schema?: MetadataFieldDef[];
|
|
204
204
|
mediaBaseUrl?: string;
|
|
205
205
|
transform?: ImageTransformParams;
|
|
206
206
|
className?: string;
|
|
207
207
|
captionClassName?: string;
|
|
208
|
+
/** Defaults to "lazy"; pass "eager" for an above-the-fold figure. */
|
|
209
|
+
loading?: ImgHTMLAttributes<HTMLImageElement>["loading"];
|
|
210
|
+
decoding?: ImgHTMLAttributes<HTMLImageElement>["decoding"];
|
|
208
211
|
}): ReactElement | null;
|
|
209
212
|
|
|
210
213
|
interface MediaFigureBlockProps {
|
|
211
214
|
photo: MediaFieldValue | null;
|
|
215
|
+
loading?: "lazy" | "eager";
|
|
212
216
|
}
|
|
213
217
|
interface MediaFigureBlockOptions {
|
|
214
218
|
/**
|
|
@@ -233,6 +237,12 @@ interface MediaFigureBlockOptions {
|
|
|
233
237
|
captionClassName?: string;
|
|
234
238
|
/** Rendered when no photo is chosen (or its URL fails origin validation). */
|
|
235
239
|
placeholder?: ReactNode;
|
|
240
|
+
/**
|
|
241
|
+
* Default for the block's "Loading" field. "lazy" (the default) keeps
|
|
242
|
+
* off-screen images off the wire on image-heavy pages; use "eager" when the
|
|
243
|
+
* block is reliably above the fold.
|
|
244
|
+
*/
|
|
245
|
+
defaultLoading?: "lazy" | "eager";
|
|
236
246
|
}
|
|
237
247
|
/**
|
|
238
248
|
* Builds a registerable Puck component around the rich `p1-media` field and
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import {
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ImgHTMLAttributes, ReactElement, ReactNode } from 'react';
|
|
3
3
|
import { ComponentConfig } from '@puckeditor/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -116,8 +116,8 @@ declare function createMediaPlugin(options: MediaPluginOptions): {
|
|
|
116
116
|
name: string;
|
|
117
117
|
overrides: {
|
|
118
118
|
fieldTypes: {
|
|
119
|
-
text: (props: any) =>
|
|
120
|
-
"p1-media": (props: any) =>
|
|
119
|
+
text: (props: any) => react.JSX.Element;
|
|
120
|
+
"p1-media": (props: any) => react.JSX.Element;
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
123
|
fieldTransforms: {
|
|
@@ -188,7 +188,7 @@ declare function getMediaProps(value: MediaFieldValue | null | undefined, option
|
|
|
188
188
|
* (a broken foreign-origin image is never emitted). `alt` may be overridden;
|
|
189
189
|
* otherwise it comes from the value.
|
|
190
190
|
*/
|
|
191
|
-
declare function MediaImage({ image, mediaBaseUrl, transform, alt, ...rest }: {
|
|
191
|
+
declare function MediaImage({ image, mediaBaseUrl, transform, alt, loading, decoding, ...rest }: {
|
|
192
192
|
image: MediaFieldValue | null | undefined;
|
|
193
193
|
mediaBaseUrl?: string;
|
|
194
194
|
transform?: ImageTransformParams;
|
|
@@ -198,17 +198,21 @@ declare function MediaImage({ image, mediaBaseUrl, transform, alt, ...rest }: {
|
|
|
198
198
|
* ESCAPED text (React default escaping — never dangerouslySetInnerHTML, req.
|
|
199
199
|
* R6). Renders nothing when the src is rejected/empty.
|
|
200
200
|
*/
|
|
201
|
-
declare function MediaFigure({ image, schema, mediaBaseUrl, transform, className, captionClassName, }: {
|
|
201
|
+
declare function MediaFigure({ image, schema, mediaBaseUrl, transform, className, captionClassName, loading, decoding, }: {
|
|
202
202
|
image: MediaFieldValue | null | undefined;
|
|
203
203
|
schema?: MetadataFieldDef[];
|
|
204
204
|
mediaBaseUrl?: string;
|
|
205
205
|
transform?: ImageTransformParams;
|
|
206
206
|
className?: string;
|
|
207
207
|
captionClassName?: string;
|
|
208
|
+
/** Defaults to "lazy"; pass "eager" for an above-the-fold figure. */
|
|
209
|
+
loading?: ImgHTMLAttributes<HTMLImageElement>["loading"];
|
|
210
|
+
decoding?: ImgHTMLAttributes<HTMLImageElement>["decoding"];
|
|
208
211
|
}): ReactElement | null;
|
|
209
212
|
|
|
210
213
|
interface MediaFigureBlockProps {
|
|
211
214
|
photo: MediaFieldValue | null;
|
|
215
|
+
loading?: "lazy" | "eager";
|
|
212
216
|
}
|
|
213
217
|
interface MediaFigureBlockOptions {
|
|
214
218
|
/**
|
|
@@ -233,6 +237,12 @@ interface MediaFigureBlockOptions {
|
|
|
233
237
|
captionClassName?: string;
|
|
234
238
|
/** Rendered when no photo is chosen (or its URL fails origin validation). */
|
|
235
239
|
placeholder?: ReactNode;
|
|
240
|
+
/**
|
|
241
|
+
* Default for the block's "Loading" field. "lazy" (the default) keeps
|
|
242
|
+
* off-screen images off the wire on image-heavy pages; use "eager" when the
|
|
243
|
+
* block is reliably above the fold.
|
|
244
|
+
*/
|
|
245
|
+
defaultLoading?: "lazy" | "eager";
|
|
236
246
|
}
|
|
237
247
|
/**
|
|
238
248
|
* Builds a registerable Puck component around the rich `p1-media` field and
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,46 @@ var DEFAULT_MEDIA_PATTERNS = [
|
|
|
59
59
|
// src/components/media-field.tsx
|
|
60
60
|
var import_react7 = require("react");
|
|
61
61
|
|
|
62
|
+
// src/crop.ts
|
|
63
|
+
function getBaseUrl(value) {
|
|
64
|
+
return value ? value.split("?")[0] : "";
|
|
65
|
+
}
|
|
66
|
+
function getParams(value) {
|
|
67
|
+
return new URLSearchParams(value.includes("?") ? value.split("?")[1] : "");
|
|
68
|
+
}
|
|
69
|
+
function getCropMode(value) {
|
|
70
|
+
if (!value) return "fit";
|
|
71
|
+
const params = getParams(value);
|
|
72
|
+
if (params.has("trim.left")) return "custom";
|
|
73
|
+
return params.get("fit") === "cover" ? "smart" : "fit";
|
|
74
|
+
}
|
|
75
|
+
function getTrimRect(value) {
|
|
76
|
+
if (!value) return null;
|
|
77
|
+
const params = getParams(value);
|
|
78
|
+
const read = (key) => {
|
|
79
|
+
const raw = params.get(key);
|
|
80
|
+
if (raw === null || raw === "") return null;
|
|
81
|
+
const n2 = Number(raw);
|
|
82
|
+
return Number.isFinite(n2) ? n2 : null;
|
|
83
|
+
};
|
|
84
|
+
const left = read("trim.left");
|
|
85
|
+
const top = read("trim.top");
|
|
86
|
+
const width = read("trim.width");
|
|
87
|
+
const height = read("trim.height");
|
|
88
|
+
if (left === null || top === null || width === null || height === null) return null;
|
|
89
|
+
return { left, top, width, height };
|
|
90
|
+
}
|
|
91
|
+
function buildValueWithCrop(baseUrl, crop) {
|
|
92
|
+
return crop === "smart" ? `${baseUrl}?fit=cover&gravity=auto` : `${baseUrl}?fit=scale-down`;
|
|
93
|
+
}
|
|
94
|
+
function buildValueWithTrim(baseUrl, rect) {
|
|
95
|
+
const left = Math.max(0, Math.round(rect.left));
|
|
96
|
+
const top = Math.max(0, Math.round(rect.top));
|
|
97
|
+
const width = Math.max(1, Math.round(rect.width));
|
|
98
|
+
const height = Math.max(1, Math.round(rect.height));
|
|
99
|
+
return `${baseUrl}?trim.left=${left}&trim.top=${top}&trim.width=${width}&trim.height=${height}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
62
102
|
// src/components/media-library.tsx
|
|
63
103
|
var import_react6 = require("react");
|
|
64
104
|
|
|
@@ -224,6 +264,9 @@ async function runUpload(target, file, metadata, assetId, progress, onStep) {
|
|
|
224
264
|
uploaded = true;
|
|
225
265
|
}
|
|
226
266
|
const nextProgress = { presigned, uploaded };
|
|
267
|
+
if (presigned === void 0) {
|
|
268
|
+
throw new UploadFlowError("Resume state is missing its presign result", nextProgress);
|
|
269
|
+
}
|
|
227
270
|
const presignedResult = presigned;
|
|
228
271
|
onStep("finalizing");
|
|
229
272
|
const finalizePath = assetId ? `/media/${encodeURIComponent(assetId)}/versions/finalize` : "/media/finalize";
|
|
@@ -1326,12 +1369,13 @@ function MediaLibrary({ isOpen, onClose, onSelect, onSelectItem }) {
|
|
|
1326
1369
|
onClose();
|
|
1327
1370
|
};
|
|
1328
1371
|
const deleteItem = (item) => {
|
|
1329
|
-
|
|
1372
|
+
const { assetId } = item;
|
|
1373
|
+
if (!assetId) return;
|
|
1330
1374
|
(async () => {
|
|
1331
1375
|
try {
|
|
1332
1376
|
const params = siteParams(config);
|
|
1333
1377
|
const response = await fetch(
|
|
1334
|
-
config.workerUrl + "/media/" + encodeURIComponent(
|
|
1378
|
+
config.workerUrl + "/media/" + encodeURIComponent(assetId) + "?" + params.toString(),
|
|
1335
1379
|
{
|
|
1336
1380
|
method: "DELETE",
|
|
1337
1381
|
headers: await getAuthHeaders()
|
|
@@ -1469,46 +1513,6 @@ function MediaLibrary({ isOpen, onClose, onSelect, onSelectItem }) {
|
|
|
1469
1513
|
);
|
|
1470
1514
|
}
|
|
1471
1515
|
|
|
1472
|
-
// src/crop.ts
|
|
1473
|
-
function getBaseUrl(value) {
|
|
1474
|
-
return value ? value.split("?")[0] : "";
|
|
1475
|
-
}
|
|
1476
|
-
function getParams(value) {
|
|
1477
|
-
return new URLSearchParams(value.includes("?") ? value.split("?")[1] : "");
|
|
1478
|
-
}
|
|
1479
|
-
function getCropMode(value) {
|
|
1480
|
-
if (!value) return "fit";
|
|
1481
|
-
const params = getParams(value);
|
|
1482
|
-
if (params.has("trim.left")) return "custom";
|
|
1483
|
-
return params.get("fit") === "cover" ? "smart" : "fit";
|
|
1484
|
-
}
|
|
1485
|
-
function getTrimRect(value) {
|
|
1486
|
-
if (!value) return null;
|
|
1487
|
-
const params = getParams(value);
|
|
1488
|
-
const read = (key) => {
|
|
1489
|
-
const raw = params.get(key);
|
|
1490
|
-
if (raw === null || raw === "") return null;
|
|
1491
|
-
const n2 = Number(raw);
|
|
1492
|
-
return Number.isFinite(n2) ? n2 : null;
|
|
1493
|
-
};
|
|
1494
|
-
const left = read("trim.left");
|
|
1495
|
-
const top = read("trim.top");
|
|
1496
|
-
const width = read("trim.width");
|
|
1497
|
-
const height = read("trim.height");
|
|
1498
|
-
if (left === null || top === null || width === null || height === null) return null;
|
|
1499
|
-
return { left, top, width, height };
|
|
1500
|
-
}
|
|
1501
|
-
function buildValueWithCrop(baseUrl, crop) {
|
|
1502
|
-
return crop === "smart" ? `${baseUrl}?fit=cover&gravity=auto` : `${baseUrl}?fit=scale-down`;
|
|
1503
|
-
}
|
|
1504
|
-
function buildValueWithTrim(baseUrl, rect) {
|
|
1505
|
-
const left = Math.max(0, Math.round(rect.left));
|
|
1506
|
-
const top = Math.max(0, Math.round(rect.top));
|
|
1507
|
-
const width = Math.max(1, Math.round(rect.width));
|
|
1508
|
-
const height = Math.max(1, Math.round(rect.height));
|
|
1509
|
-
return `${baseUrl}?trim.left=${left}&trim.top=${top}&trim.width=${width}&trim.height=${height}`;
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
1516
|
// src/components/media-field.tsx
|
|
1513
1517
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1514
1518
|
function MediaFieldRender(props) {
|
|
@@ -1663,10 +1667,73 @@ function MediaFieldRender(props) {
|
|
|
1663
1667
|
// src/components/media-object-field.tsx
|
|
1664
1668
|
var import_react10 = require("react");
|
|
1665
1669
|
|
|
1670
|
+
// src/media-value.ts
|
|
1671
|
+
var STRUCTURAL_MEDIA_KEYS = /* @__PURE__ */ new Set([
|
|
1672
|
+
"assetId",
|
|
1673
|
+
"versionId",
|
|
1674
|
+
"url",
|
|
1675
|
+
"metaSchemaVersion"
|
|
1676
|
+
]);
|
|
1677
|
+
function isMediaValue(value) {
|
|
1678
|
+
return typeof value === "object" && value !== null;
|
|
1679
|
+
}
|
|
1680
|
+
function makeMediaValue(input) {
|
|
1681
|
+
if (!input.assetId || !input.versionId) {
|
|
1682
|
+
return input.url;
|
|
1683
|
+
}
|
|
1684
|
+
const value = {
|
|
1685
|
+
assetId: input.assetId,
|
|
1686
|
+
versionId: input.versionId,
|
|
1687
|
+
url: input.url
|
|
1688
|
+
};
|
|
1689
|
+
if (typeof input.metaSchemaVersion === "number") {
|
|
1690
|
+
value.metaSchemaVersion = input.metaSchemaVersion;
|
|
1691
|
+
}
|
|
1692
|
+
if (input.metadata) {
|
|
1693
|
+
for (const [k, v] of Object.entries(input.metadata)) {
|
|
1694
|
+
if (v === void 0 || v === "") continue;
|
|
1695
|
+
if (STRUCTURAL_MEDIA_KEYS.has(k)) continue;
|
|
1696
|
+
value[k] = v;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return value;
|
|
1700
|
+
}
|
|
1701
|
+
function buildValueFromAsset(asset, crop) {
|
|
1702
|
+
const metadata = { ...asset.metadata ?? {} };
|
|
1703
|
+
if (typeof asset.width === "number") metadata.width = asset.width;
|
|
1704
|
+
if (typeof asset.height === "number") metadata.height = asset.height;
|
|
1705
|
+
return makeMediaValue({
|
|
1706
|
+
assetId: asset.assetId,
|
|
1707
|
+
versionId: asset.versionId,
|
|
1708
|
+
url: buildValueWithCrop(getBaseUrl(asset.url), crop),
|
|
1709
|
+
metaSchemaVersion: asset.metaSchemaVersion,
|
|
1710
|
+
metadata
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
function applyCropToValue(value, crop) {
|
|
1714
|
+
const currentUrl = isMediaValue(value) ? value.url : value;
|
|
1715
|
+
const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
|
|
1716
|
+
if (!baseUrl) return value;
|
|
1717
|
+
const url = buildValueWithCrop(baseUrl, crop);
|
|
1718
|
+
return isMediaValue(value) ? { ...value, url } : url;
|
|
1719
|
+
}
|
|
1720
|
+
function applyTrimToValue(value, rect) {
|
|
1721
|
+
const currentUrl = isMediaValue(value) ? value.url : value;
|
|
1722
|
+
const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
|
|
1723
|
+
if (!baseUrl) return value;
|
|
1724
|
+
const url = buildValueWithTrim(baseUrl, rect);
|
|
1725
|
+
return isMediaValue(value) ? { ...value, url } : url;
|
|
1726
|
+
}
|
|
1727
|
+
function setMetaOnValue(value, fieldName, fieldValue) {
|
|
1728
|
+
if (!isMediaValue(value)) return value;
|
|
1729
|
+
if (STRUCTURAL_MEDIA_KEYS.has(fieldName)) return value;
|
|
1730
|
+
return { ...value, [fieldName]: fieldValue };
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1666
1733
|
// src/components/crop-dialog.tsx
|
|
1667
1734
|
var import_react9 = require("react");
|
|
1668
1735
|
|
|
1669
|
-
// ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.
|
|
1736
|
+
// ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.7/node_modules/react-image-crop/dist/index.js
|
|
1670
1737
|
var import_react8 = __toESM(require("react"), 1);
|
|
1671
1738
|
var r = {
|
|
1672
1739
|
x: 0,
|
|
@@ -2095,7 +2162,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
2095
2162
|
}
|
|
2096
2163
|
}
|
|
2097
2164
|
|
|
2098
|
-
// ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.
|
|
2165
|
+
// ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.7/node_modules/react-image-crop/dist/ReactCrop.css
|
|
2099
2166
|
styleInject('@keyframes marching-ants {\n 0% {\n background-position:\n 0 0,\n 0 100%,\n 0 0,\n 100% 0;\n }\n to {\n background-position:\n 20px 0,\n -20px 100%,\n 0 -20px,\n 100% 20px;\n }\n}\n:root {\n --rc-drag-handle-size:12px;\n --rc-drag-handle-mobile-size:24px;\n --rc-drag-handle-bg-colour:#0003;\n --rc-drag-bar-size:6px;\n --rc-border-color:#ffffffb3;\n --rc-focus-color:#08f;\n}\n.ReactCrop {\n cursor: crosshair;\n max-width: 100%;\n display: inline-block;\n position: relative;\n}\n.ReactCrop *,\n.ReactCrop :before,\n.ReactCrop :after {\n box-sizing: border-box;\n}\n.ReactCrop--disabled,\n.ReactCrop--locked {\n cursor: inherit;\n}\n.ReactCrop__child-wrapper {\n max-height: inherit;\n overflow: hidden;\n}\n.ReactCrop__child-wrapper > img,\n.ReactCrop__child-wrapper > video {\n max-width: 100%;\n max-height: inherit;\n display: block;\n}\n.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper > img,\n.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper > video,\n.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__crop-selection {\n touch-action: none;\n}\n.ReactCrop__crop-mask {\n pointer-events: none;\n width: calc(100% + .5px);\n height: calc(100% + .5px);\n position: absolute;\n inset: 0;\n}\n.ReactCrop__crop-selection {\n cursor: move;\n position: absolute;\n top: 0;\n left: 0;\n transform: translate(0, 0);\n}\n.ReactCrop--disabled .ReactCrop__crop-selection {\n cursor: inherit;\n}\n.ReactCrop--circular-crop .ReactCrop__crop-selection {\n border-radius: 50%;\n}\n.ReactCrop--circular-crop .ReactCrop__crop-selection:after {\n pointer-events: none;\n content: "";\n border: 1px solid var(--rc-border-color);\n opacity: .3;\n position: absolute;\n inset: -1px;\n}\n.ReactCrop--no-animate .ReactCrop__crop-selection {\n outline: 1px dashed #fff;\n}\n.ReactCrop__crop-selection:not(.ReactCrop--no-animate .ReactCrop__crop-selection) {\n color: #fff;\n background-image:\n linear-gradient(\n 90deg,\n #fff 50%,\n #444 50%),\n linear-gradient(\n 90deg,\n #fff 50%,\n #444 50%),\n linear-gradient(#fff 50%, #444 50%),\n linear-gradient(#fff 50%, #444 50%);\n background-position:\n 0 0,\n 0 100%,\n 0 0,\n 100% 0;\n background-repeat:\n repeat-x,\n repeat-x,\n repeat-y,\n repeat-y;\n background-size:\n 10px 1px,\n 10px 1px,\n 1px 10px,\n 1px 10px;\n animation: 1s linear infinite marching-ants;\n}\n.ReactCrop__crop-selection:focus {\n outline: 2px solid var(--rc-focus-color);\n outline-offset: -1px;\n}\n.ReactCrop--invisible-crop .ReactCrop__crop-mask,\n.ReactCrop--invisible-crop .ReactCrop__crop-selection {\n display: none;\n}\n.ReactCrop__rule-of-thirds-vt:before,\n.ReactCrop__rule-of-thirds-vt:after,\n.ReactCrop__rule-of-thirds-hz:before,\n.ReactCrop__rule-of-thirds-hz:after {\n content: "";\n background-color: #fff6;\n display: block;\n position: absolute;\n}\n.ReactCrop__rule-of-thirds-vt:before,\n.ReactCrop__rule-of-thirds-vt:after {\n width: 1px;\n height: 100%;\n}\n.ReactCrop__rule-of-thirds-vt:before {\n left: 33.3333%;\n}\n.ReactCrop__rule-of-thirds-vt:after {\n left: 66.6667%;\n}\n.ReactCrop__rule-of-thirds-hz:before,\n.ReactCrop__rule-of-thirds-hz:after {\n width: 100%;\n height: 1px;\n}\n.ReactCrop__rule-of-thirds-hz:before {\n top: 33.3333%;\n}\n.ReactCrop__rule-of-thirds-hz:after {\n top: 66.6667%;\n}\n.ReactCrop__drag-handle {\n width: var(--rc-drag-handle-size);\n height: var(--rc-drag-handle-size);\n background-color: var(--rc-drag-handle-bg-colour);\n border: 1px solid var(--rc-border-color);\n position: absolute;\n}\n.ReactCrop__drag-handle:focus {\n background: var(--rc-focus-color);\n}\n.ReactCrop .ord-nw {\n cursor: nw-resize;\n top: 0;\n left: 0;\n transform: translate(-50%, -50%);\n}\n.ReactCrop .ord-n {\n cursor: n-resize;\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n}\n.ReactCrop .ord-ne {\n cursor: ne-resize;\n top: 0;\n right: 0;\n transform: translate(50%, -50%);\n}\n.ReactCrop .ord-e {\n cursor: e-resize;\n top: 50%;\n right: 0;\n transform: translate(50%, -50%);\n}\n.ReactCrop .ord-se {\n cursor: se-resize;\n bottom: 0;\n right: 0;\n transform: translate(50%, 50%);\n}\n.ReactCrop .ord-s {\n cursor: s-resize;\n bottom: 0;\n left: 50%;\n transform: translate(-50%, 50%);\n}\n.ReactCrop .ord-sw {\n cursor: sw-resize;\n bottom: 0;\n left: 0;\n transform: translate(-50%, 50%);\n}\n.ReactCrop .ord-w {\n cursor: w-resize;\n top: 50%;\n left: 0;\n transform: translate(-50%, -50%);\n}\n.ReactCrop__disabled .ReactCrop__drag-handle {\n cursor: inherit;\n}\n.ReactCrop__drag-bar {\n position: absolute;\n}\n.ReactCrop__drag-bar.ord-n {\n width: 100%;\n height: var(--rc-drag-bar-size);\n top: 0;\n left: 0;\n transform: translateY(-50%);\n}\n.ReactCrop__drag-bar.ord-e {\n width: var(--rc-drag-bar-size);\n height: 100%;\n top: 0;\n right: 0;\n transform: translate(50%);\n}\n.ReactCrop__drag-bar.ord-s {\n width: 100%;\n height: var(--rc-drag-bar-size);\n bottom: 0;\n left: 0;\n transform: translateY(50%);\n}\n.ReactCrop__drag-bar.ord-w {\n width: var(--rc-drag-bar-size);\n height: 100%;\n top: 0;\n left: 0;\n transform: translate(-50%);\n}\n.ReactCrop--new-crop .ReactCrop__drag-bar,\n.ReactCrop--new-crop .ReactCrop__drag-handle,\n.ReactCrop--fixed-aspect .ReactCrop__drag-bar,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-n,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-e,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-s,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-w {\n display: none;\n}\n@media (pointer: coarse) {\n .ReactCrop .ord-n,\n .ReactCrop .ord-e,\n .ReactCrop .ord-s,\n .ReactCrop .ord-w {\n display: none;\n }\n .ReactCrop__drag-handle {\n width: var(--rc-drag-handle-mobile-size);\n height: var(--rc-drag-handle-mobile-size);\n }\n}\n');
|
|
2100
2167
|
|
|
2101
2168
|
// src/components/crop-dialog.tsx
|
|
@@ -2342,69 +2409,6 @@ function CropDialog(props) {
|
|
|
2342
2409
|
);
|
|
2343
2410
|
}
|
|
2344
2411
|
|
|
2345
|
-
// src/media-value.ts
|
|
2346
|
-
var STRUCTURAL_MEDIA_KEYS = /* @__PURE__ */ new Set([
|
|
2347
|
-
"assetId",
|
|
2348
|
-
"versionId",
|
|
2349
|
-
"url",
|
|
2350
|
-
"metaSchemaVersion"
|
|
2351
|
-
]);
|
|
2352
|
-
function isMediaValue(value) {
|
|
2353
|
-
return typeof value === "object" && value !== null;
|
|
2354
|
-
}
|
|
2355
|
-
function makeMediaValue(input) {
|
|
2356
|
-
if (!input.assetId || !input.versionId) {
|
|
2357
|
-
return input.url;
|
|
2358
|
-
}
|
|
2359
|
-
const value = {
|
|
2360
|
-
assetId: input.assetId,
|
|
2361
|
-
versionId: input.versionId,
|
|
2362
|
-
url: input.url
|
|
2363
|
-
};
|
|
2364
|
-
if (typeof input.metaSchemaVersion === "number") {
|
|
2365
|
-
value.metaSchemaVersion = input.metaSchemaVersion;
|
|
2366
|
-
}
|
|
2367
|
-
if (input.metadata) {
|
|
2368
|
-
for (const [k, v] of Object.entries(input.metadata)) {
|
|
2369
|
-
if (v === void 0 || v === "") continue;
|
|
2370
|
-
if (STRUCTURAL_MEDIA_KEYS.has(k)) continue;
|
|
2371
|
-
value[k] = v;
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
return value;
|
|
2375
|
-
}
|
|
2376
|
-
function buildValueFromAsset(asset, crop) {
|
|
2377
|
-
const metadata = { ...asset.metadata ?? {} };
|
|
2378
|
-
if (typeof asset.width === "number") metadata.width = asset.width;
|
|
2379
|
-
if (typeof asset.height === "number") metadata.height = asset.height;
|
|
2380
|
-
return makeMediaValue({
|
|
2381
|
-
assetId: asset.assetId,
|
|
2382
|
-
versionId: asset.versionId,
|
|
2383
|
-
url: buildValueWithCrop(getBaseUrl(asset.url), crop),
|
|
2384
|
-
metaSchemaVersion: asset.metaSchemaVersion,
|
|
2385
|
-
metadata
|
|
2386
|
-
});
|
|
2387
|
-
}
|
|
2388
|
-
function applyCropToValue(value, crop) {
|
|
2389
|
-
const currentUrl = isMediaValue(value) ? value.url : value;
|
|
2390
|
-
const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
|
|
2391
|
-
if (!baseUrl) return value;
|
|
2392
|
-
const url = buildValueWithCrop(baseUrl, crop);
|
|
2393
|
-
return isMediaValue(value) ? { ...value, url } : url;
|
|
2394
|
-
}
|
|
2395
|
-
function applyTrimToValue(value, rect) {
|
|
2396
|
-
const currentUrl = isMediaValue(value) ? value.url : value;
|
|
2397
|
-
const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
|
|
2398
|
-
if (!baseUrl) return value;
|
|
2399
|
-
const url = buildValueWithTrim(baseUrl, rect);
|
|
2400
|
-
return isMediaValue(value) ? { ...value, url } : url;
|
|
2401
|
-
}
|
|
2402
|
-
function setMetaOnValue(value, fieldName, fieldValue) {
|
|
2403
|
-
if (!isMediaValue(value)) return value;
|
|
2404
|
-
if (STRUCTURAL_MEDIA_KEYS.has(fieldName)) return value;
|
|
2405
|
-
return { ...value, [fieldName]: fieldValue };
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
2412
|
// src/components/media-object-field.tsx
|
|
2409
2413
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2410
2414
|
function MediaObjectFieldRender(props) {
|
|
@@ -2811,6 +2815,8 @@ function MediaImage({
|
|
|
2811
2815
|
mediaBaseUrl,
|
|
2812
2816
|
transform,
|
|
2813
2817
|
alt,
|
|
2818
|
+
loading = "lazy",
|
|
2819
|
+
decoding = "async",
|
|
2814
2820
|
...rest
|
|
2815
2821
|
}) {
|
|
2816
2822
|
const props = getMediaProps(image, { mediaBaseUrl, transform });
|
|
@@ -2820,6 +2826,8 @@ function MediaImage({
|
|
|
2820
2826
|
{
|
|
2821
2827
|
src: props.src,
|
|
2822
2828
|
alt: alt ?? props.alt,
|
|
2829
|
+
loading,
|
|
2830
|
+
decoding,
|
|
2823
2831
|
...props.width !== void 0 ? { width: props.width } : {},
|
|
2824
2832
|
...props.height !== void 0 ? { height: props.height } : {},
|
|
2825
2833
|
...rest
|
|
@@ -2852,7 +2860,9 @@ function MediaFigure({
|
|
|
2852
2860
|
mediaBaseUrl,
|
|
2853
2861
|
transform,
|
|
2854
2862
|
className,
|
|
2855
|
-
captionClassName
|
|
2863
|
+
captionClassName,
|
|
2864
|
+
loading = "lazy",
|
|
2865
|
+
decoding = "async"
|
|
2856
2866
|
}) {
|
|
2857
2867
|
const props = getMediaProps(image, { mediaBaseUrl, transform });
|
|
2858
2868
|
if (!props.src) return null;
|
|
@@ -2863,6 +2873,8 @@ function MediaFigure({
|
|
|
2863
2873
|
{
|
|
2864
2874
|
src: props.src,
|
|
2865
2875
|
alt: props.alt,
|
|
2876
|
+
loading,
|
|
2877
|
+
decoding,
|
|
2866
2878
|
...props.width !== void 0 ? { width: props.width } : {},
|
|
2867
2879
|
...props.height !== void 0 ? { height: props.height } : {}
|
|
2868
2880
|
}
|
|
@@ -2894,19 +2906,29 @@ function createMediaFigureBlock(options) {
|
|
|
2894
2906
|
schema,
|
|
2895
2907
|
className,
|
|
2896
2908
|
captionClassName,
|
|
2897
|
-
placeholder = "Choose a photo from the media library"
|
|
2909
|
+
placeholder = "Choose a photo from the media library",
|
|
2910
|
+
defaultLoading = "lazy"
|
|
2898
2911
|
} = options;
|
|
2899
2912
|
return {
|
|
2900
2913
|
label,
|
|
2901
2914
|
fields: {
|
|
2902
2915
|
// `p1-media` is registered by createMediaPlugin via overrides.fieldTypes,
|
|
2903
2916
|
// so it is not part of Puck's built-in Field union.
|
|
2904
|
-
photo: { type: "p1-media", label: fieldLabel }
|
|
2917
|
+
photo: { type: "p1-media", label: fieldLabel },
|
|
2918
|
+
loading: {
|
|
2919
|
+
type: "radio",
|
|
2920
|
+
label: "Loading",
|
|
2921
|
+
options: [
|
|
2922
|
+
{ label: "Lazy", value: "lazy" },
|
|
2923
|
+
{ label: "Eager", value: "eager" }
|
|
2924
|
+
]
|
|
2925
|
+
}
|
|
2905
2926
|
},
|
|
2906
2927
|
defaultProps: {
|
|
2907
|
-
photo: null
|
|
2928
|
+
photo: null,
|
|
2929
|
+
loading: defaultLoading
|
|
2908
2930
|
},
|
|
2909
|
-
render: ({ photo }) => {
|
|
2931
|
+
render: ({ photo, loading }) => {
|
|
2910
2932
|
const { src } = getMediaProps(photo ?? null, { mediaBaseUrl });
|
|
2911
2933
|
if (!src) {
|
|
2912
2934
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: placeholderStyle, children: placeholder });
|
|
@@ -2919,7 +2941,8 @@ function createMediaFigureBlock(options) {
|
|
|
2919
2941
|
transform,
|
|
2920
2942
|
schema,
|
|
2921
2943
|
className,
|
|
2922
|
-
captionClassName
|
|
2944
|
+
captionClassName,
|
|
2945
|
+
loading: loading === "eager" ? "eager" : "lazy"
|
|
2923
2946
|
}
|
|
2924
2947
|
);
|
|
2925
2948
|
}
|