camox 0.22.0 → 0.24.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 +96 -60
- package/dist/core/createBlock.d.ts +58 -232
- package/dist/core/createBlock.js +47 -38
- package/dist/core/lib/contentType.d.ts +44 -24
- package/dist/core/lib/contentType.js +55 -29
- package/dist/core/lib/fieldTypes.js +28 -16
- package/dist/core/lib/imageTransform.js +76 -0
- package/dist/features/content/CamoxContent.js +3 -3
- package/dist/features/content/components/AssetCard.js +124 -77
- package/dist/features/preview/CamoxPreview.d.ts +2 -0
- package/dist/features/preview/components/AssetFieldEditor.js +10 -5
- package/dist/features/preview/components/AssetLightbox.js +197 -13
- package/dist/features/preview/components/EditPageModal.js +244 -74
- package/dist/features/preview/components/ItemFieldsEditor.js +8 -9
- package/dist/features/preview/components/MultipleAssetFieldEditor.js +49 -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/features/routes/pageRoute.d.ts +2 -0
- package/dist/features/routes/pageRoute.js +5 -4
- package/dist/lib/normalized-data.js +6 -4
- package/dist/lib/queries.js +33 -2
- 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] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
286
286
|
for (let $i = 0; $i < 59; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
287
|
-
$[0] = "
|
|
287
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
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] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
536
536
|
for (let $i = 0; $i < 38; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
537
|
-
$[0] = "
|
|
537
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
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] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
777
777
|
for (let $i = 0; $i < 22; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
778
|
-
$[0] = "
|
|
778
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
779
779
|
}
|
|
780
780
|
const { name, children } = t0;
|
|
781
781
|
const blockContext = React.use(Context);
|
|
@@ -844,7 +844,13 @@ 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
|
+
size: fieldValue.size
|
|
851
|
+
}),
|
|
852
|
+
srcSet: buildImageSrcSet(fieldValue.url, fieldValue.mimeType, fieldValue.size),
|
|
853
|
+
sizes: getDefaultImageSizes(),
|
|
848
854
|
alt: fieldValue.alt
|
|
849
855
|
};
|
|
850
856
|
if (!isContentEditable) return /* @__PURE__ */ jsx(Fragment, { children: children(imageProps, fieldValue) });
|
|
@@ -891,9 +897,9 @@ function createBlock(options) {
|
|
|
891
897
|
};
|
|
892
898
|
const File = (t0) => {
|
|
893
899
|
const $ = c(9);
|
|
894
|
-
if ($[0] !== "
|
|
900
|
+
if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
895
901
|
for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
896
|
-
$[0] = "
|
|
902
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
897
903
|
}
|
|
898
904
|
const { name, children } = t0;
|
|
899
905
|
const blockContext = React.use(Context);
|
|
@@ -926,16 +932,16 @@ function createBlock(options) {
|
|
|
926
932
|
} else t2 = $[8];
|
|
927
933
|
return t2;
|
|
928
934
|
};
|
|
929
|
-
const
|
|
930
|
-
const
|
|
935
|
+
const ASSET_LIST_SELF_KEY = "__camox_asset_self__";
|
|
936
|
+
const _AssetList = ({ name, children }) => {
|
|
931
937
|
const blockContext = React.use(Context);
|
|
932
|
-
if (!blockContext) throw new Error("
|
|
938
|
+
if (!blockContext) throw new Error("ImageList/FileList must be used within a Block Component");
|
|
933
939
|
const parentRepeaterContext = React.use(RepeaterItemContext);
|
|
934
940
|
const { filesMap } = useNormalizedData();
|
|
935
941
|
const fieldName = String(name);
|
|
936
942
|
const fieldSchema = parentRepeaterContext ? typeboxSchema.properties[parentRepeaterContext.arrayFieldName]?.items?.properties?.[fieldName] : typeboxSchema.properties[fieldName];
|
|
937
|
-
const
|
|
938
|
-
if (
|
|
943
|
+
const ft = fieldSchema?.fieldType;
|
|
944
|
+
if (ft !== "ImageList" && ft !== "FileList") throw new Error(`"${fieldName}" is not a Type.ImageList or Type.FileList field`);
|
|
939
945
|
const source = parentRepeaterContext ? parentRepeaterContext.itemContent[fieldName] : blockContext.content[fieldName];
|
|
940
946
|
let arr = Array.isArray(source) ? source : [];
|
|
941
947
|
if (arr.length === 0) {
|
|
@@ -944,7 +950,7 @@ function createBlock(options) {
|
|
|
944
950
|
if (defaultCount > 0 && itemDefault) arr = Array.from({ length: defaultCount }, () => itemDefault);
|
|
945
951
|
}
|
|
946
952
|
const resolved = arr.map((v) => isFileMarker(v) ? resolveFileMarker(v, filesMap) : v).filter((v) => v != null && typeof v === "object" && "url" in v);
|
|
947
|
-
const Single =
|
|
953
|
+
const Single = ft === "ImageList" ? Image : File;
|
|
948
954
|
return /* @__PURE__ */ jsx(RepeaterHoverProvider, {
|
|
949
955
|
blockId: blockContext.blockId,
|
|
950
956
|
fieldName,
|
|
@@ -952,24 +958,25 @@ function createBlock(options) {
|
|
|
952
958
|
value: {
|
|
953
959
|
arrayFieldName: fieldName,
|
|
954
960
|
itemIndex: index,
|
|
955
|
-
itemContent: { [
|
|
961
|
+
itemContent: { [ASSET_LIST_SELF_KEY]: value },
|
|
956
962
|
itemSettings: {},
|
|
957
963
|
itemId: void 0,
|
|
958
964
|
containerItemId: parentRepeaterContext?.itemId ?? parentRepeaterContext?.containerItemId
|
|
959
965
|
},
|
|
960
966
|
children: /* @__PURE__ */ jsx(Single, {
|
|
961
|
-
name:
|
|
967
|
+
name: ASSET_LIST_SELF_KEY,
|
|
962
968
|
children
|
|
963
969
|
})
|
|
964
970
|
}, index))
|
|
965
971
|
});
|
|
966
972
|
};
|
|
967
|
-
const
|
|
973
|
+
const ImageList = _AssetList;
|
|
974
|
+
const FileList = _AssetList;
|
|
968
975
|
const RepeaterItemWrapper = (t0) => {
|
|
969
976
|
const $ = c(9);
|
|
970
|
-
if ($[0] !== "
|
|
977
|
+
if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
971
978
|
for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
972
|
-
$[0] = "
|
|
979
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
973
980
|
}
|
|
974
981
|
const { itemId, blockId, mode, children } = t0;
|
|
975
982
|
const isContentEditable = useIsEditable(mode);
|
|
@@ -1010,9 +1017,9 @@ function createBlock(options) {
|
|
|
1010
1017
|
};
|
|
1011
1018
|
const RepeaterHoverProvider = (t0) => {
|
|
1012
1019
|
const $ = c(7);
|
|
1013
|
-
if ($[0] !== "
|
|
1020
|
+
if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
1014
1021
|
for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1015
|
-
$[0] = "
|
|
1022
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
1016
1023
|
}
|
|
1017
1024
|
const { blockId, fieldName, children } = t0;
|
|
1018
1025
|
const isContentEditable = useIsEditable("site");
|
|
@@ -1043,9 +1050,9 @@ function createBlock(options) {
|
|
|
1043
1050
|
};
|
|
1044
1051
|
const Repeater = (t0) => {
|
|
1045
1052
|
const $ = c(27);
|
|
1046
|
-
if ($[0] !== "
|
|
1053
|
+
if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
1047
1054
|
for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1048
|
-
$[0] = "
|
|
1055
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
1049
1056
|
}
|
|
1050
1057
|
const { name, children } = t0;
|
|
1051
1058
|
const blockContext = React.use(Context);
|
|
@@ -1057,7 +1064,8 @@ function createBlock(options) {
|
|
|
1057
1064
|
const ItemLink = Link;
|
|
1058
1065
|
const ItemEmbed = Embed;
|
|
1059
1066
|
const ItemImage = Image;
|
|
1060
|
-
const
|
|
1067
|
+
const ItemImageList = _AssetList;
|
|
1068
|
+
const ItemFileList = _AssetList;
|
|
1061
1069
|
const ItemFile = File;
|
|
1062
1070
|
const ItemRepeater = Repeater;
|
|
1063
1071
|
const source = parentRepeaterContext ? parentRepeaterContext.itemContent[name] : content[name];
|
|
@@ -1103,7 +1111,8 @@ function createBlock(options) {
|
|
|
1103
1111
|
Embed: ItemEmbed,
|
|
1104
1112
|
Image: ItemImage,
|
|
1105
1113
|
File: ItemFile,
|
|
1106
|
-
|
|
1114
|
+
ImageList: ItemImageList,
|
|
1115
|
+
FileList: ItemFileList,
|
|
1107
1116
|
Repeater: ItemRepeater,
|
|
1108
1117
|
useSetting: useItemSetting
|
|
1109
1118
|
};
|
|
@@ -1168,9 +1177,9 @@ function createBlock(options) {
|
|
|
1168
1177
|
};
|
|
1169
1178
|
const BlockComponent = (t0) => {
|
|
1170
1179
|
const $ = c(70);
|
|
1171
|
-
if ($[0] !== "
|
|
1180
|
+
if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
1172
1181
|
for (let $i = 0; $i < 70; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1173
|
-
$[0] = "
|
|
1182
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
1174
1183
|
}
|
|
1175
1184
|
const { blockData, mode, isFirstBlock, showAddBlockTop, showAddBlockBottom, addBlockAfterPosition } = t0;
|
|
1176
1185
|
const isContentEditable = useIsEditable(mode);
|
|
@@ -1470,9 +1479,9 @@ function createBlock(options) {
|
|
|
1470
1479
|
*/
|
|
1471
1480
|
const Detached = (t0) => {
|
|
1472
1481
|
const $ = c(31);
|
|
1473
|
-
if ($[0] !== "
|
|
1482
|
+
if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
|
|
1474
1483
|
for (let $i = 0; $i < 31; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
1475
|
-
$[0] = "
|
|
1484
|
+
$[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
|
|
1476
1485
|
}
|
|
1477
1486
|
const { children } = t0;
|
|
1478
1487
|
const ctx = React.use(Context);
|
|
@@ -1600,7 +1609,8 @@ function createBlock(options) {
|
|
|
1600
1609
|
Link,
|
|
1601
1610
|
Image,
|
|
1602
1611
|
File,
|
|
1603
|
-
|
|
1612
|
+
ImageList,
|
|
1613
|
+
FileList,
|
|
1604
1614
|
Repeater,
|
|
1605
1615
|
useSetting,
|
|
1606
1616
|
_internal: {
|
|
@@ -1624,8 +1634,7 @@ function createBlock(options) {
|
|
|
1624
1634
|
const storageContent = {};
|
|
1625
1635
|
for (const [key, prop] of Object.entries(typeboxSchema.properties)) {
|
|
1626
1636
|
const ft = prop.fieldType;
|
|
1627
|
-
|
|
1628
|
-
if (ft === "Image" || ft === "File" || ft === "RepeatableItem" || ait === "Image" || ait === "File") continue;
|
|
1637
|
+
if (ft === "Image" || ft === "File" || ft === "ImageList" || ft === "FileList" || ft === "Repeater") continue;
|
|
1629
1638
|
if ("default" in prop) storageContent[key] = prop.default;
|
|
1630
1639
|
}
|
|
1631
1640
|
return {
|
|
@@ -59,6 +59,7 @@ type ImageValue = {
|
|
|
59
59
|
alt: string;
|
|
60
60
|
filename: string;
|
|
61
61
|
mimeType: string;
|
|
62
|
+
size?: number;
|
|
62
63
|
_fileId?: string;
|
|
63
64
|
} & {
|
|
64
65
|
readonly __brand: "ImageValue";
|
|
@@ -68,30 +69,11 @@ type FileValue = {
|
|
|
68
69
|
alt: string;
|
|
69
70
|
filename: string;
|
|
70
71
|
mimeType: string;
|
|
72
|
+
size?: number;
|
|
71
73
|
_fileId?: string;
|
|
72
74
|
} & {
|
|
73
75
|
readonly __brand: "FileValue";
|
|
74
76
|
};
|
|
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
77
|
/**
|
|
96
78
|
* Type builders for createBlock content schemas.
|
|
97
79
|
* All fields must have default values.
|
|
@@ -120,7 +102,7 @@ declare const Type$1: {
|
|
|
120
102
|
* similar to block-level settings.
|
|
121
103
|
*
|
|
122
104
|
* @example
|
|
123
|
-
* Type.
|
|
105
|
+
* Type.Repeater({
|
|
124
106
|
* content: {
|
|
125
107
|
* title: Type.String({ default: 'Item' }),
|
|
126
108
|
* description: Type.String({ default: 'Description' }),
|
|
@@ -134,7 +116,7 @@ declare const Type$1: {
|
|
|
134
116
|
* toMarkdown: (c) => [`### ${c.title}`, c.description],
|
|
135
117
|
* })
|
|
136
118
|
*/
|
|
137
|
-
|
|
119
|
+
Repeater: <T extends Record<string, TSchema>, S extends Record<string, TSchema> = Record<string, never>>(options: {
|
|
138
120
|
content: T;
|
|
139
121
|
settings?: S;
|
|
140
122
|
minItems: number;
|
|
@@ -197,8 +179,46 @@ declare const Type$1: {
|
|
|
197
179
|
};
|
|
198
180
|
title?: string;
|
|
199
181
|
}) => TUnsafe<LinkValue>;
|
|
200
|
-
|
|
201
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Creates an image asset field.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* Type.Image({ title: 'Hero' })
|
|
187
|
+
*/
|
|
188
|
+
Image: (options?: {
|
|
189
|
+
title?: string;
|
|
190
|
+
}) => TUnsafe<ImageValue>;
|
|
191
|
+
/**
|
|
192
|
+
* Creates a file asset field.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* Type.File({ accept: ['application/pdf'], title: 'Datasheet' })
|
|
196
|
+
*/
|
|
197
|
+
File: (options: {
|
|
198
|
+
accept: string[];
|
|
199
|
+
title?: string;
|
|
200
|
+
}) => TUnsafe<FileValue>;
|
|
201
|
+
/**
|
|
202
|
+
* Creates an array of image assets.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* Type.ImageList({ title: 'Gallery', defaultItems: 3 })
|
|
206
|
+
*/
|
|
207
|
+
ImageList: (options?: {
|
|
208
|
+
title?: string;
|
|
209
|
+
defaultItems?: number;
|
|
210
|
+
}) => TArray<TUnsafe<ImageValue>>;
|
|
211
|
+
/**
|
|
212
|
+
* Creates an array of file assets.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* Type.FileList({ accept: ['application/pdf'], title: 'Attachments' })
|
|
216
|
+
*/
|
|
217
|
+
FileList: (options: {
|
|
218
|
+
accept: string[];
|
|
219
|
+
title?: string;
|
|
220
|
+
defaultItems?: number;
|
|
221
|
+
}) => TArray<TUnsafe<FileValue>>;
|
|
202
222
|
};
|
|
203
223
|
//#endregion
|
|
204
224
|
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,76 @@
|
|
|
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
|
+
const MIN_OPTIMIZE_SIZE = 50 * 1024;
|
|
13
|
+
function isNonProxiedHostname(hostname) {
|
|
14
|
+
if (hostname === "localhost") return true;
|
|
15
|
+
if (hostname === "127.0.0.1") return true;
|
|
16
|
+
if (hostname === "0.0.0.0") return true;
|
|
17
|
+
if (hostname === "::1") return true;
|
|
18
|
+
if (hostname.endsWith(".local")) return true;
|
|
19
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function parseUrl(url) {
|
|
23
|
+
try {
|
|
24
|
+
return new URL(url);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isTransformableUrl(parsed) {
|
|
30
|
+
if (isNonProxiedHostname(parsed.hostname)) return false;
|
|
31
|
+
if (parsed.pathname.startsWith("/cdn-cgi/image/")) return false;
|
|
32
|
+
if (!parsed.pathname.startsWith("/files/serve/")) return false;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function isNonTransformableMimeType(mimeType) {
|
|
36
|
+
if (!mimeType) return false;
|
|
37
|
+
return mimeType === "image/svg+xml";
|
|
38
|
+
}
|
|
39
|
+
function isRasterImage(mimeType) {
|
|
40
|
+
if (!mimeType) return false;
|
|
41
|
+
if (!mimeType.startsWith("image/")) return false;
|
|
42
|
+
return mimeType !== "image/svg+xml";
|
|
43
|
+
}
|
|
44
|
+
function transformImageUrl(url, options = {}) {
|
|
45
|
+
if (!url) return url;
|
|
46
|
+
if (isNonTransformableMimeType(options.mimeType)) return url;
|
|
47
|
+
if (options.size != null && options.size < MIN_OPTIMIZE_SIZE) return url;
|
|
48
|
+
const parsed = parseUrl(url);
|
|
49
|
+
if (!parsed) return url;
|
|
50
|
+
if (!isTransformableUrl(parsed)) return url;
|
|
51
|
+
const parts = [
|
|
52
|
+
`format=auto`,
|
|
53
|
+
`quality=${options.quality ?? DEFAULT_QUALITY}`,
|
|
54
|
+
`fit=scale-down`
|
|
55
|
+
];
|
|
56
|
+
if (options.width != null) parts.push(`width=${options.width}`);
|
|
57
|
+
return `${parsed.origin}/cdn-cgi/image/${parts.join(",")}${parsed.pathname}${parsed.search}`;
|
|
58
|
+
}
|
|
59
|
+
function buildImageSrcSet(url, mimeType, size) {
|
|
60
|
+
if (!url) return void 0;
|
|
61
|
+
if (isNonTransformableMimeType(mimeType)) return void 0;
|
|
62
|
+
if (size != null && size < MIN_OPTIMIZE_SIZE) return void 0;
|
|
63
|
+
const parsed = parseUrl(url);
|
|
64
|
+
if (!parsed) return void 0;
|
|
65
|
+
if (!isTransformableUrl(parsed)) return void 0;
|
|
66
|
+
return DEFAULT_SRCSET_WIDTHS.map((w) => `${transformImageUrl(url, { width: w })} ${w}w`).join(", ");
|
|
67
|
+
}
|
|
68
|
+
function getDefaultImageWidth() {
|
|
69
|
+
return DEFAULT_SRC_WIDTH;
|
|
70
|
+
}
|
|
71
|
+
function getDefaultImageSizes() {
|
|
72
|
+
return DEFAULT_SIZES;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
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,
|