camox 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/createApp.d.ts +88 -60
- package/dist/core/createBlock.d.ts +54 -232
- package/dist/core/createBlock.js +46 -38
- package/dist/core/lib/contentType.d.ts +42 -24
- package/dist/core/lib/contentType.js +55 -29
- package/dist/core/lib/fieldTypes.js +28 -16
- package/dist/core/lib/imageTransform.js +73 -0
- package/dist/features/content/CamoxContent.js +3 -3
- package/dist/features/content/components/AssetCard.js +30 -25
- package/dist/features/preview/components/AssetFieldEditor.js +9 -5
- package/dist/features/preview/components/AssetLightbox.js +194 -12
- package/dist/features/preview/components/ItemFieldsEditor.js +8 -9
- package/dist/features/preview/components/MultipleAssetFieldEditor.js +47 -42
- package/dist/features/preview/components/PageContentSheet.js +2 -3
- package/dist/features/preview/components/PageTree.js +66 -68
- package/dist/features/preview/components/useRepeatableItemActions.js +5 -5
- package/dist/studio.css +1 -1
- package/package.json +4 -4
- package/skills/camox-block/SKILL.md +38 -30
- package/skills/camox-cli/SKILL.md +40 -4
package/dist/core/createBlock.js
CHANGED
|
@@ -12,6 +12,7 @@ import { useFieldSelection } from "./hooks/useFieldSelection.js";
|
|
|
12
12
|
import { useIsEditable } from "./hooks/useIsEditable.js";
|
|
13
13
|
import { useOverlayMessage } from "./hooks/useOverlayMessage.js";
|
|
14
14
|
import { Type, resolveToMarkdown } from "./lib/contentType.js";
|
|
15
|
+
import { buildImageSrcSet, getDefaultImageSizes, getDefaultImageWidth, transformImageUrl } from "./lib/imageTransform.js";
|
|
15
16
|
import { markdownToReactNodes } from "./lib/lexicalReact.js";
|
|
16
17
|
import { c } from "react/compiler-runtime";
|
|
17
18
|
import { Input } from "@camox/ui/input";
|
|
@@ -58,8 +59,8 @@ let hasShownEmbedLockToast = false;
|
|
|
58
59
|
function buildInitialSeeds(properties, parentTempId, content, allSeeds, counter) {
|
|
59
60
|
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
60
61
|
if (fieldSchema.type !== "array" || !fieldSchema.items?.properties) continue;
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
62
|
+
const ft = fieldSchema.fieldType;
|
|
63
|
+
if (ft === "ImageList" || ft === "FileList") continue;
|
|
63
64
|
const defaultCount = fieldSchema.defaultItems ?? fieldSchema.minItems ?? 0;
|
|
64
65
|
if (defaultCount <= 0) continue;
|
|
65
66
|
const itemProperties = fieldSchema.items.properties;
|
|
@@ -97,7 +98,7 @@ function buildInitialSeeds(properties, parentTempId, content, allSeeds, counter)
|
|
|
97
98
|
function buildPeekItems(properties, blockId, parentItemId, content, allItems, counter) {
|
|
98
99
|
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
99
100
|
if (fieldSchema.type !== "array" || !fieldSchema.items?.properties) continue;
|
|
100
|
-
if (fieldSchema.fieldType === "
|
|
101
|
+
if (fieldSchema.fieldType === "ImageList" || fieldSchema.fieldType === "FileList") continue;
|
|
101
102
|
const defaultCount = fieldSchema.defaultItems ?? fieldSchema.minItems ?? 0;
|
|
102
103
|
if (defaultCount <= 0) continue;
|
|
103
104
|
const itemProperties = fieldSchema.items.properties;
|
|
@@ -158,8 +159,7 @@ function createBlock(options) {
|
|
|
158
159
|
for (const [key, prop] of Object.entries(typeboxSchema.properties)) if ("default" in prop) {
|
|
159
160
|
contentDefaults[key] = prop.default;
|
|
160
161
|
const ft = prop.fieldType;
|
|
161
|
-
|
|
162
|
-
if (ft === "Image" || ft === "File" || ft === "RepeatableItem" || ait === "Image" || ait === "File") continue;
|
|
162
|
+
if (ft === "Image" || ft === "File" || ft === "ImageList" || ft === "FileList" || ft === "Repeater") continue;
|
|
163
163
|
contentDefaultsForStorage[key] = prop.default;
|
|
164
164
|
}
|
|
165
165
|
const repeatableItemDefaults = {};
|
|
@@ -282,9 +282,9 @@ function createBlock(options) {
|
|
|
282
282
|
};
|
|
283
283
|
const Embed = (t0) => {
|
|
284
284
|
const $ = c(59);
|
|
285
|
-
if ($[0] !== "
|
|
285
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
286
286
|
for (let $i = 0; $i < 59; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
287
|
-
$[0] = "
|
|
287
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
288
288
|
}
|
|
289
289
|
const { name, children } = t0;
|
|
290
290
|
const blockContext = React.use(Context);
|
|
@@ -532,9 +532,9 @@ function createBlock(options) {
|
|
|
532
532
|
};
|
|
533
533
|
const Link = (t0) => {
|
|
534
534
|
const $ = c(38);
|
|
535
|
-
if ($[0] !== "
|
|
535
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
536
536
|
for (let $i = 0; $i < 38; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
537
|
-
$[0] = "
|
|
537
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
538
538
|
}
|
|
539
539
|
const { name, children } = t0;
|
|
540
540
|
const blockContext = React.use(Context);
|
|
@@ -773,9 +773,9 @@ function createBlock(options) {
|
|
|
773
773
|
};
|
|
774
774
|
const Image = (t0) => {
|
|
775
775
|
const $ = c(22);
|
|
776
|
-
if ($[0] !== "
|
|
776
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
777
777
|
for (let $i = 0; $i < 22; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
778
|
-
$[0] = "
|
|
778
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
779
779
|
}
|
|
780
780
|
const { name, children } = t0;
|
|
781
781
|
const blockContext = React.use(Context);
|
|
@@ -844,7 +844,12 @@ function createBlock(options) {
|
|
|
844
844
|
} else t4 = $[10];
|
|
845
845
|
const handleClick = t4;
|
|
846
846
|
const imageProps = {
|
|
847
|
-
src: fieldValue.url,
|
|
847
|
+
src: transformImageUrl(fieldValue.url, {
|
|
848
|
+
width: getDefaultImageWidth(),
|
|
849
|
+
mimeType: fieldValue.mimeType
|
|
850
|
+
}),
|
|
851
|
+
srcSet: buildImageSrcSet(fieldValue.url, fieldValue.mimeType),
|
|
852
|
+
sizes: getDefaultImageSizes(),
|
|
848
853
|
alt: fieldValue.alt
|
|
849
854
|
};
|
|
850
855
|
if (!isContentEditable) return /* @__PURE__ */ jsx(Fragment, { children: children(imageProps, fieldValue) });
|
|
@@ -891,9 +896,9 @@ function createBlock(options) {
|
|
|
891
896
|
};
|
|
892
897
|
const File = (t0) => {
|
|
893
898
|
const $ = c(9);
|
|
894
|
-
if ($[0] !== "
|
|
899
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
895
900
|
for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
896
|
-
$[0] = "
|
|
901
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
897
902
|
}
|
|
898
903
|
const { name, children } = t0;
|
|
899
904
|
const blockContext = React.use(Context);
|
|
@@ -926,16 +931,16 @@ function createBlock(options) {
|
|
|
926
931
|
} else t2 = $[8];
|
|
927
932
|
return t2;
|
|
928
933
|
};
|
|
929
|
-
const
|
|
930
|
-
const
|
|
934
|
+
const ASSET_LIST_SELF_KEY = "__camox_asset_self__";
|
|
935
|
+
const _AssetList = ({ name, children }) => {
|
|
931
936
|
const blockContext = React.use(Context);
|
|
932
|
-
if (!blockContext) throw new Error("
|
|
937
|
+
if (!blockContext) throw new Error("ImageList/FileList must be used within a Block Component");
|
|
933
938
|
const parentRepeaterContext = React.use(RepeaterItemContext);
|
|
934
939
|
const { filesMap } = useNormalizedData();
|
|
935
940
|
const fieldName = String(name);
|
|
936
941
|
const fieldSchema = parentRepeaterContext ? typeboxSchema.properties[parentRepeaterContext.arrayFieldName]?.items?.properties?.[fieldName] : typeboxSchema.properties[fieldName];
|
|
937
|
-
const
|
|
938
|
-
if (
|
|
942
|
+
const ft = fieldSchema?.fieldType;
|
|
943
|
+
if (ft !== "ImageList" && ft !== "FileList") throw new Error(`"${fieldName}" is not a Type.ImageList or Type.FileList field`);
|
|
939
944
|
const source = parentRepeaterContext ? parentRepeaterContext.itemContent[fieldName] : blockContext.content[fieldName];
|
|
940
945
|
let arr = Array.isArray(source) ? source : [];
|
|
941
946
|
if (arr.length === 0) {
|
|
@@ -944,7 +949,7 @@ function createBlock(options) {
|
|
|
944
949
|
if (defaultCount > 0 && itemDefault) arr = Array.from({ length: defaultCount }, () => itemDefault);
|
|
945
950
|
}
|
|
946
951
|
const resolved = arr.map((v) => isFileMarker(v) ? resolveFileMarker(v, filesMap) : v).filter((v) => v != null && typeof v === "object" && "url" in v);
|
|
947
|
-
const Single =
|
|
952
|
+
const Single = ft === "ImageList" ? Image : File;
|
|
948
953
|
return /* @__PURE__ */ jsx(RepeaterHoverProvider, {
|
|
949
954
|
blockId: blockContext.blockId,
|
|
950
955
|
fieldName,
|
|
@@ -952,24 +957,25 @@ function createBlock(options) {
|
|
|
952
957
|
value: {
|
|
953
958
|
arrayFieldName: fieldName,
|
|
954
959
|
itemIndex: index,
|
|
955
|
-
itemContent: { [
|
|
960
|
+
itemContent: { [ASSET_LIST_SELF_KEY]: value },
|
|
956
961
|
itemSettings: {},
|
|
957
962
|
itemId: void 0,
|
|
958
963
|
containerItemId: parentRepeaterContext?.itemId ?? parentRepeaterContext?.containerItemId
|
|
959
964
|
},
|
|
960
965
|
children: /* @__PURE__ */ jsx(Single, {
|
|
961
|
-
name:
|
|
966
|
+
name: ASSET_LIST_SELF_KEY,
|
|
962
967
|
children
|
|
963
968
|
})
|
|
964
969
|
}, index))
|
|
965
970
|
});
|
|
966
971
|
};
|
|
967
|
-
const
|
|
972
|
+
const ImageList = _AssetList;
|
|
973
|
+
const FileList = _AssetList;
|
|
968
974
|
const RepeaterItemWrapper = (t0) => {
|
|
969
975
|
const $ = c(9);
|
|
970
|
-
if ($[0] !== "
|
|
976
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
971
977
|
for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
972
|
-
$[0] = "
|
|
978
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
973
979
|
}
|
|
974
980
|
const { itemId, blockId, mode, children } = t0;
|
|
975
981
|
const isContentEditable = useIsEditable(mode);
|
|
@@ -1010,9 +1016,9 @@ function createBlock(options) {
|
|
|
1010
1016
|
};
|
|
1011
1017
|
const RepeaterHoverProvider = (t0) => {
|
|
1012
1018
|
const $ = c(7);
|
|
1013
|
-
if ($[0] !== "
|
|
1019
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
1014
1020
|
for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1015
|
-
$[0] = "
|
|
1021
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
1016
1022
|
}
|
|
1017
1023
|
const { blockId, fieldName, children } = t0;
|
|
1018
1024
|
const isContentEditable = useIsEditable("site");
|
|
@@ -1043,9 +1049,9 @@ function createBlock(options) {
|
|
|
1043
1049
|
};
|
|
1044
1050
|
const Repeater = (t0) => {
|
|
1045
1051
|
const $ = c(27);
|
|
1046
|
-
if ($[0] !== "
|
|
1052
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
1047
1053
|
for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1048
|
-
$[0] = "
|
|
1054
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
1049
1055
|
}
|
|
1050
1056
|
const { name, children } = t0;
|
|
1051
1057
|
const blockContext = React.use(Context);
|
|
@@ -1057,7 +1063,8 @@ function createBlock(options) {
|
|
|
1057
1063
|
const ItemLink = Link;
|
|
1058
1064
|
const ItemEmbed = Embed;
|
|
1059
1065
|
const ItemImage = Image;
|
|
1060
|
-
const
|
|
1066
|
+
const ItemImageList = _AssetList;
|
|
1067
|
+
const ItemFileList = _AssetList;
|
|
1061
1068
|
const ItemFile = File;
|
|
1062
1069
|
const ItemRepeater = Repeater;
|
|
1063
1070
|
const source = parentRepeaterContext ? parentRepeaterContext.itemContent[name] : content[name];
|
|
@@ -1103,7 +1110,8 @@ function createBlock(options) {
|
|
|
1103
1110
|
Embed: ItemEmbed,
|
|
1104
1111
|
Image: ItemImage,
|
|
1105
1112
|
File: ItemFile,
|
|
1106
|
-
|
|
1113
|
+
ImageList: ItemImageList,
|
|
1114
|
+
FileList: ItemFileList,
|
|
1107
1115
|
Repeater: ItemRepeater,
|
|
1108
1116
|
useSetting: useItemSetting
|
|
1109
1117
|
};
|
|
@@ -1168,9 +1176,9 @@ function createBlock(options) {
|
|
|
1168
1176
|
};
|
|
1169
1177
|
const BlockComponent = (t0) => {
|
|
1170
1178
|
const $ = c(70);
|
|
1171
|
-
if ($[0] !== "
|
|
1179
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
1172
1180
|
for (let $i = 0; $i < 70; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1173
|
-
$[0] = "
|
|
1181
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
1174
1182
|
}
|
|
1175
1183
|
const { blockData, mode, isFirstBlock, showAddBlockTop, showAddBlockBottom, addBlockAfterPosition } = t0;
|
|
1176
1184
|
const isContentEditable = useIsEditable(mode);
|
|
@@ -1470,9 +1478,9 @@ function createBlock(options) {
|
|
|
1470
1478
|
*/
|
|
1471
1479
|
const Detached = (t0) => {
|
|
1472
1480
|
const $ = c(31);
|
|
1473
|
-
if ($[0] !== "
|
|
1481
|
+
if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
|
|
1474
1482
|
for (let $i = 0; $i < 31; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1475
|
-
$[0] = "
|
|
1483
|
+
$[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
|
|
1476
1484
|
}
|
|
1477
1485
|
const { children } = t0;
|
|
1478
1486
|
const ctx = React.use(Context);
|
|
@@ -1600,7 +1608,8 @@ function createBlock(options) {
|
|
|
1600
1608
|
Link,
|
|
1601
1609
|
Image,
|
|
1602
1610
|
File,
|
|
1603
|
-
|
|
1611
|
+
ImageList,
|
|
1612
|
+
FileList,
|
|
1604
1613
|
Repeater,
|
|
1605
1614
|
useSetting,
|
|
1606
1615
|
_internal: {
|
|
@@ -1624,8 +1633,7 @@ function createBlock(options) {
|
|
|
1624
1633
|
const storageContent = {};
|
|
1625
1634
|
for (const [key, prop] of Object.entries(typeboxSchema.properties)) {
|
|
1626
1635
|
const ft = prop.fieldType;
|
|
1627
|
-
|
|
1628
|
-
if (ft === "Image" || ft === "File" || ft === "RepeatableItem" || ait === "Image" || ait === "File") continue;
|
|
1636
|
+
if (ft === "Image" || ft === "File" || ft === "ImageList" || ft === "FileList" || ft === "Repeater") continue;
|
|
1629
1637
|
if ("default" in prop) storageContent[key] = prop.default;
|
|
1630
1638
|
}
|
|
1631
1639
|
return {
|
|
@@ -72,26 +72,6 @@ type FileValue = {
|
|
|
72
72
|
} & {
|
|
73
73
|
readonly __brand: "FileValue";
|
|
74
74
|
};
|
|
75
|
-
declare function _imageType(options: {
|
|
76
|
-
title?: string;
|
|
77
|
-
multiple?: false;
|
|
78
|
-
}): TUnsafe<ImageValue>;
|
|
79
|
-
declare function _imageType(options: {
|
|
80
|
-
title?: string;
|
|
81
|
-
multiple: true;
|
|
82
|
-
defaultItems: number;
|
|
83
|
-
}): TArray<TUnsafe<ImageValue>>;
|
|
84
|
-
declare function _fileType(options: {
|
|
85
|
-
accept: string[];
|
|
86
|
-
title?: string;
|
|
87
|
-
multiple?: false;
|
|
88
|
-
}): TUnsafe<FileValue>;
|
|
89
|
-
declare function _fileType(options: {
|
|
90
|
-
accept: string[];
|
|
91
|
-
title?: string;
|
|
92
|
-
multiple: true;
|
|
93
|
-
defaultItems: number;
|
|
94
|
-
}): TArray<TUnsafe<FileValue>>;
|
|
95
75
|
/**
|
|
96
76
|
* Type builders for createBlock content schemas.
|
|
97
77
|
* All fields must have default values.
|
|
@@ -120,7 +100,7 @@ declare const Type$1: {
|
|
|
120
100
|
* similar to block-level settings.
|
|
121
101
|
*
|
|
122
102
|
* @example
|
|
123
|
-
* Type.
|
|
103
|
+
* Type.Repeater({
|
|
124
104
|
* content: {
|
|
125
105
|
* title: Type.String({ default: 'Item' }),
|
|
126
106
|
* description: Type.String({ default: 'Description' }),
|
|
@@ -134,7 +114,7 @@ declare const Type$1: {
|
|
|
134
114
|
* toMarkdown: (c) => [`### ${c.title}`, c.description],
|
|
135
115
|
* })
|
|
136
116
|
*/
|
|
137
|
-
|
|
117
|
+
Repeater: <T extends Record<string, TSchema>, S extends Record<string, TSchema> = Record<string, never>>(options: {
|
|
138
118
|
content: T;
|
|
139
119
|
settings?: S;
|
|
140
120
|
minItems: number;
|
|
@@ -197,8 +177,46 @@ declare const Type$1: {
|
|
|
197
177
|
};
|
|
198
178
|
title?: string;
|
|
199
179
|
}) => TUnsafe<LinkValue>;
|
|
200
|
-
|
|
201
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Creates an image asset field.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* Type.Image({ title: 'Hero' })
|
|
185
|
+
*/
|
|
186
|
+
Image: (options?: {
|
|
187
|
+
title?: string;
|
|
188
|
+
}) => TUnsafe<ImageValue>;
|
|
189
|
+
/**
|
|
190
|
+
* Creates a file asset field.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* Type.File({ accept: ['application/pdf'], title: 'Datasheet' })
|
|
194
|
+
*/
|
|
195
|
+
File: (options: {
|
|
196
|
+
accept: string[];
|
|
197
|
+
title?: string;
|
|
198
|
+
}) => TUnsafe<FileValue>;
|
|
199
|
+
/**
|
|
200
|
+
* Creates an array of image assets.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* Type.ImageList({ title: 'Gallery', defaultItems: 3 })
|
|
204
|
+
*/
|
|
205
|
+
ImageList: (options?: {
|
|
206
|
+
title?: string;
|
|
207
|
+
defaultItems?: number;
|
|
208
|
+
}) => TArray<TUnsafe<ImageValue>>;
|
|
209
|
+
/**
|
|
210
|
+
* Creates an array of file assets.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* Type.FileList({ accept: ['application/pdf'], title: 'Attachments' })
|
|
214
|
+
*/
|
|
215
|
+
FileList: (options: {
|
|
216
|
+
accept: string[];
|
|
217
|
+
title?: string;
|
|
218
|
+
defaultItems?: number;
|
|
219
|
+
}) => TArray<TUnsafe<FileValue>>;
|
|
202
220
|
};
|
|
203
221
|
//#endregion
|
|
204
222
|
export { EmbedURL, FileValue, ImageValue, ItemSettingsBrand, LinkValue, ToMarkdownBuilder, Type$1 as Type };
|
|
@@ -59,14 +59,8 @@ function resolveToMarkdown(builder, settingsShape, scope) {
|
|
|
59
59
|
else out.push(entry instanceof FieldToken ? entry.toString() : String(entry));
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
url: `https://placehold.co/1200x800/f4f4f5/a1a1aa.png?text=${options?.title || "image"}`,
|
|
65
|
-
alt: "",
|
|
66
|
-
filename: "placeholder.png",
|
|
67
|
-
mimeType: "image/png"
|
|
68
|
-
};
|
|
69
|
-
const singleSchema = Type.Unsafe({
|
|
62
|
+
function _imageSingle(options) {
|
|
63
|
+
return Type.Unsafe({
|
|
70
64
|
type: "object",
|
|
71
65
|
properties: {
|
|
72
66
|
url: { type: "string" },
|
|
@@ -75,24 +69,28 @@ function _imageType(options) {
|
|
|
75
69
|
mimeType: { type: "string" }
|
|
76
70
|
},
|
|
77
71
|
accept: ["image/*"],
|
|
78
|
-
default:
|
|
72
|
+
default: {
|
|
73
|
+
url: `https://placehold.co/1200x800/f4f4f5/a1a1aa.png?text=${options?.title || "image"}`,
|
|
74
|
+
alt: "",
|
|
75
|
+
filename: "placeholder.png",
|
|
76
|
+
mimeType: "image/png"
|
|
77
|
+
},
|
|
79
78
|
title: options.title,
|
|
80
79
|
fieldType: "Image"
|
|
81
80
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return Type.Array(
|
|
81
|
+
}
|
|
82
|
+
function _imageList(options) {
|
|
83
|
+
return Type.Array(_imageSingle({ title: options.title }), {
|
|
85
84
|
minItems: 0,
|
|
86
85
|
maxItems: 100,
|
|
87
86
|
default: [],
|
|
88
|
-
defaultItems,
|
|
87
|
+
defaultItems: options.defaultItems ?? 0,
|
|
89
88
|
title: options.title,
|
|
90
|
-
fieldType: "
|
|
91
|
-
arrayItemType: "Image"
|
|
89
|
+
fieldType: "ImageList"
|
|
92
90
|
});
|
|
93
91
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
92
|
+
function _fileSingle(options) {
|
|
93
|
+
return Type.Unsafe({
|
|
96
94
|
type: "object",
|
|
97
95
|
properties: {
|
|
98
96
|
url: { type: "string" },
|
|
@@ -110,16 +108,18 @@ function _fileType(options) {
|
|
|
110
108
|
title: options.title,
|
|
111
109
|
fieldType: "File"
|
|
112
110
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return Type.Array(
|
|
111
|
+
}
|
|
112
|
+
function _fileList(options) {
|
|
113
|
+
return Type.Array(_fileSingle({
|
|
114
|
+
accept: options.accept,
|
|
115
|
+
title: options.title
|
|
116
|
+
}), {
|
|
116
117
|
minItems: 0,
|
|
117
118
|
maxItems: 100,
|
|
118
119
|
default: [],
|
|
119
|
-
defaultItems,
|
|
120
|
+
defaultItems: options.defaultItems ?? 0,
|
|
120
121
|
title: options.title,
|
|
121
|
-
fieldType: "
|
|
122
|
-
arrayItemType: "File"
|
|
122
|
+
fieldType: "FileList"
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
@@ -151,7 +151,7 @@ const Type$1 = {
|
|
|
151
151
|
* similar to block-level settings.
|
|
152
152
|
*
|
|
153
153
|
* @example
|
|
154
|
-
* Type.
|
|
154
|
+
* Type.Repeater({
|
|
155
155
|
* content: {
|
|
156
156
|
* title: Type.String({ default: 'Item' }),
|
|
157
157
|
* description: Type.String({ default: 'Description' }),
|
|
@@ -165,8 +165,8 @@ const Type$1 = {
|
|
|
165
165
|
* toMarkdown: (c) => [`### ${c.title}`, c.description],
|
|
166
166
|
* })
|
|
167
167
|
*/
|
|
168
|
-
|
|
169
|
-
if (options.minItems < 1) throw new Error("
|
|
168
|
+
Repeater: (options) => {
|
|
169
|
+
if (options.minItems < 1) throw new Error("Repeater requires minItems to be at least 1");
|
|
170
170
|
const objectSchema = Type.Object(options.content);
|
|
171
171
|
const defaultItem = {};
|
|
172
172
|
for (const [key, prop] of Object.entries(objectSchema.properties)) if ("default" in prop) defaultItem[key] = prop.default;
|
|
@@ -186,7 +186,7 @@ const Type$1 = {
|
|
|
186
186
|
maxItems: options.maxItems,
|
|
187
187
|
default: defaultArray,
|
|
188
188
|
title: options.title,
|
|
189
|
-
fieldType: "
|
|
189
|
+
fieldType: "Repeater",
|
|
190
190
|
toMarkdown: resolveToMarkdown(options.toMarkdown, options.settings, "item"),
|
|
191
191
|
itemSettingsSchema,
|
|
192
192
|
defaultItemSettings: settingsTypeboxSchema ? defaultItemSettings : void 0
|
|
@@ -274,8 +274,34 @@ const Type$1 = {
|
|
|
274
274
|
fieldType: "Link"
|
|
275
275
|
});
|
|
276
276
|
},
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Creates an image asset field.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* Type.Image({ title: 'Hero' })
|
|
282
|
+
*/
|
|
283
|
+
Image: (options = {}) => _imageSingle(options),
|
|
284
|
+
/**
|
|
285
|
+
* Creates a file asset field.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* Type.File({ accept: ['application/pdf'], title: 'Datasheet' })
|
|
289
|
+
*/
|
|
290
|
+
File: (options) => _fileSingle(options),
|
|
291
|
+
/**
|
|
292
|
+
* Creates an array of image assets.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* Type.ImageList({ title: 'Gallery', defaultItems: 3 })
|
|
296
|
+
*/
|
|
297
|
+
ImageList: (options = {}) => _imageList(options),
|
|
298
|
+
/**
|
|
299
|
+
* Creates an array of file assets.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* Type.FileList({ accept: ['application/pdf'], title: 'Attachments' })
|
|
303
|
+
*/
|
|
304
|
+
FileList: (options) => _fileList(options)
|
|
279
305
|
};
|
|
280
306
|
|
|
281
307
|
//#endregion
|
|
@@ -27,16 +27,12 @@ const fieldTypesDictionary = {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
|
-
|
|
31
|
-
label: "
|
|
30
|
+
Repeater: {
|
|
31
|
+
label: "Repeater",
|
|
32
32
|
isScalar: false,
|
|
33
33
|
isContentEditable: false,
|
|
34
34
|
hasOwnView: false,
|
|
35
|
-
getIcon: (
|
|
36
|
-
if (arrayItemType === "Image") return (props) => /* @__PURE__ */ jsx(Images, { ...props });
|
|
37
|
-
if (arrayItemType === "File") return (props) => /* @__PURE__ */ jsx(FileIcon, { ...props });
|
|
38
|
-
return (props) => /* @__PURE__ */ jsx(List, { ...props });
|
|
39
|
-
},
|
|
35
|
+
getIcon: () => (props) => /* @__PURE__ */ jsx(List, { ...props }),
|
|
40
36
|
getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
|
|
41
37
|
onTreeDoubleClick: ({ blockId }) => {
|
|
42
38
|
previewStore.send({
|
|
@@ -183,23 +179,39 @@ const fieldTypesDictionary = {
|
|
|
183
179
|
});
|
|
184
180
|
}
|
|
185
181
|
},
|
|
186
|
-
|
|
187
|
-
label: "
|
|
182
|
+
ImageList: {
|
|
183
|
+
label: "Image list",
|
|
188
184
|
isScalar: false,
|
|
189
185
|
isContentEditable: false,
|
|
190
186
|
hasOwnView: true,
|
|
191
|
-
getIcon: (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
getIcon: () => (props) => /* @__PURE__ */ jsx(Images, { ...props }),
|
|
188
|
+
getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
|
|
189
|
+
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
190
|
+
previewStore.send({
|
|
191
|
+
type: "selectBlockField",
|
|
192
|
+
blockId,
|
|
193
|
+
fieldName,
|
|
194
|
+
fieldType: "Image"
|
|
195
|
+
});
|
|
196
|
+
previewStore.send({
|
|
197
|
+
type: "openBlockContentSheet",
|
|
198
|
+
blockId
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
FileList: {
|
|
203
|
+
label: "File list",
|
|
204
|
+
isScalar: false,
|
|
205
|
+
isContentEditable: false,
|
|
206
|
+
hasOwnView: true,
|
|
207
|
+
getIcon: () => (props) => /* @__PURE__ */ jsx(FileIcon, { ...props }),
|
|
195
208
|
getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
|
|
196
|
-
onTreeDoubleClick: ({ blockId, fieldName }
|
|
197
|
-
const fieldType = arrayItemType === "File" ? "File" : "Image";
|
|
209
|
+
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
198
210
|
previewStore.send({
|
|
199
211
|
type: "selectBlockField",
|
|
200
212
|
blockId,
|
|
201
213
|
fieldName,
|
|
202
|
-
fieldType
|
|
214
|
+
fieldType: "File"
|
|
203
215
|
});
|
|
204
216
|
previewStore.send({
|
|
205
217
|
type: "openBlockContentSheet",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/core/lib/imageTransform.ts
|
|
2
|
+
const DEFAULT_QUALITY = 85;
|
|
3
|
+
const DEFAULT_SRCSET_WIDTHS = [
|
|
4
|
+
320,
|
|
5
|
+
640,
|
|
6
|
+
960,
|
|
7
|
+
1280,
|
|
8
|
+
1920
|
|
9
|
+
];
|
|
10
|
+
const DEFAULT_SRC_WIDTH = 1280;
|
|
11
|
+
const DEFAULT_SIZES = "100vw";
|
|
12
|
+
function isNonProxiedHostname(hostname) {
|
|
13
|
+
if (hostname === "localhost") return true;
|
|
14
|
+
if (hostname === "127.0.0.1") return true;
|
|
15
|
+
if (hostname === "0.0.0.0") return true;
|
|
16
|
+
if (hostname === "::1") return true;
|
|
17
|
+
if (hostname.endsWith(".local")) return true;
|
|
18
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function parseUrl(url) {
|
|
22
|
+
try {
|
|
23
|
+
return new URL(url);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isTransformableUrl(parsed) {
|
|
29
|
+
if (isNonProxiedHostname(parsed.hostname)) return false;
|
|
30
|
+
if (parsed.pathname.startsWith("/cdn-cgi/image/")) return false;
|
|
31
|
+
if (!parsed.pathname.startsWith("/files/serve/")) return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function isNonTransformableMimeType(mimeType) {
|
|
35
|
+
if (!mimeType) return false;
|
|
36
|
+
return mimeType === "image/svg+xml";
|
|
37
|
+
}
|
|
38
|
+
function isRasterImage(mimeType) {
|
|
39
|
+
if (!mimeType) return false;
|
|
40
|
+
if (!mimeType.startsWith("image/")) return false;
|
|
41
|
+
return mimeType !== "image/svg+xml";
|
|
42
|
+
}
|
|
43
|
+
function transformImageUrl(url, options = {}) {
|
|
44
|
+
if (!url) return url;
|
|
45
|
+
if (isNonTransformableMimeType(options.mimeType)) return url;
|
|
46
|
+
const parsed = parseUrl(url);
|
|
47
|
+
if (!parsed) return url;
|
|
48
|
+
if (!isTransformableUrl(parsed)) return url;
|
|
49
|
+
const parts = [
|
|
50
|
+
`format=auto`,
|
|
51
|
+
`quality=${options.quality ?? DEFAULT_QUALITY}`,
|
|
52
|
+
`fit=scale-down`
|
|
53
|
+
];
|
|
54
|
+
if (options.width != null) parts.push(`width=${options.width}`);
|
|
55
|
+
return `${parsed.origin}/cdn-cgi/image/${parts.join(",")}${parsed.pathname}${parsed.search}`;
|
|
56
|
+
}
|
|
57
|
+
function buildImageSrcSet(url, mimeType) {
|
|
58
|
+
if (!url) return void 0;
|
|
59
|
+
if (isNonTransformableMimeType(mimeType)) return void 0;
|
|
60
|
+
const parsed = parseUrl(url);
|
|
61
|
+
if (!parsed) return void 0;
|
|
62
|
+
if (!isTransformableUrl(parsed)) return void 0;
|
|
63
|
+
return DEFAULT_SRCSET_WIDTHS.map((w) => `${transformImageUrl(url, { width: w })} ${w}w`).join(", ");
|
|
64
|
+
}
|
|
65
|
+
function getDefaultImageWidth() {
|
|
66
|
+
return DEFAULT_SRC_WIDTH;
|
|
67
|
+
}
|
|
68
|
+
function getDefaultImageSizes() {
|
|
69
|
+
return DEFAULT_SIZES;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { buildImageSrcSet, getDefaultImageSizes, getDefaultImageWidth, isRasterImage, transformImageUrl };
|
|
@@ -20,9 +20,9 @@ import { FloatingToolbar } from "@camox/ui/floating-toolbar";
|
|
|
20
20
|
//#region src/features/content/CamoxContent.tsx
|
|
21
21
|
const CamoxContent = () => {
|
|
22
22
|
const $ = c(53);
|
|
23
|
-
if ($[0] !== "
|
|
23
|
+
if ($[0] !== "8cb16b8f9bc49d4116b9897e9fc510ae1a05eaf98230f67c2f9664ca4f86e3bd") {
|
|
24
24
|
for (let $i = 0; $i < 53; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
25
|
-
$[0] = "
|
|
25
|
+
$[0] = "8cb16b8f9bc49d4116b9897e9fc510ae1a05eaf98230f67c2f9664ca4f86e3bd";
|
|
26
26
|
}
|
|
27
27
|
const projectSlug = useProjectSlug();
|
|
28
28
|
let t0;
|
|
@@ -268,7 +268,7 @@ const CamoxContent = () => {
|
|
|
268
268
|
let t22;
|
|
269
269
|
if ($[48] !== t18 || $[49] !== t19 || $[50] !== t20 || $[51] !== t21) {
|
|
270
270
|
t22 = /* @__PURE__ */ jsxs("div", {
|
|
271
|
-
className: "flex flex-1 flex-row",
|
|
271
|
+
className: "flex min-h-0 flex-1 flex-row",
|
|
272
272
|
children: [
|
|
273
273
|
t11,
|
|
274
274
|
t18,
|