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.
@@ -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 ait = fieldSchema.arrayItemType;
62
- if (ait === "Image" || ait === "File") continue;
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 === "MultipleAssets") continue;
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
- const ait = prop.arrayItemType;
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
285
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
286
286
  for (let $i = 0; $i < 59; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
287
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
535
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
536
536
  for (let $i = 0; $i < 38; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
537
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
776
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
777
777
  for (let $i = 0; $i < 22; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
778
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
900
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
895
901
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
896
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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 MULTIPLE_ASSETS_SELF_KEY = "__camox_asset_self__";
930
- const _MultipleAssets = ({ name, children }) => {
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("MultipleAssets must be used within a Block Component");
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 ait = fieldSchema?.arrayItemType;
938
- if (ait !== "Image" && ait !== "File") throw new Error(`MultipleAssets: "${fieldName}" is not Type.Image/File({ multiple: true })`);
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 = ait === "Image" ? Image : File;
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: { [MULTIPLE_ASSETS_SELF_KEY]: value },
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: MULTIPLE_ASSETS_SELF_KEY,
967
+ name: ASSET_LIST_SELF_KEY,
962
968
  children
963
969
  })
964
970
  }, index))
965
971
  });
966
972
  };
967
- const MultipleAssets = _MultipleAssets;
973
+ const ImageList = _AssetList;
974
+ const FileList = _AssetList;
968
975
  const RepeaterItemWrapper = (t0) => {
969
976
  const $ = c(9);
970
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
977
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
971
978
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
972
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1020
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1014
1021
  for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1015
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1053
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1047
1054
  for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1048
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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 ItemMultipleAssets = _MultipleAssets;
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
- MultipleAssets: ItemMultipleAssets,
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1180
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1172
1181
  for (let $i = 0; $i < 70; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1173
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1482
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1474
1483
  for (let $i = 0; $i < 31; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1475
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
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
- MultipleAssets,
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
- const ait = prop.arrayItemType;
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.RepeatableItem({
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
- RepeatableItem: <T extends Record<string, TSchema>, S extends Record<string, TSchema> = Record<string, never>>(options: {
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
- Image: typeof _imageType;
201
- File: typeof _fileType;
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 _imageType(options) {
63
- const imageDefault = {
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: imageDefault,
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
- if (!options.multiple) return singleSchema;
83
- const defaultItems = options.defaultItems ?? 0;
84
- return Type.Array(singleSchema, {
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: "MultipleAssets",
91
- arrayItemType: "Image"
89
+ fieldType: "ImageList"
92
90
  });
93
91
  }
94
- function _fileType(options) {
95
- const singleSchema = Type.Unsafe({
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
- if (!options.multiple) return singleSchema;
114
- const defaultItems = options.defaultItems ?? 0;
115
- return Type.Array(singleSchema, {
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: "MultipleAssets",
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.RepeatableItem({
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
- RepeatableItem: (options) => {
169
- if (options.minItems < 1) throw new Error("RepeatableItem requires minItems to be at least 1");
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: "RepeatableItem",
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
- Image: _imageType,
278
- File: _fileType
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
- RepeatableItem: {
31
- label: "Repeatable item",
30
+ Repeater: {
31
+ label: "Repeater",
32
32
  isScalar: false,
33
33
  isContentEditable: false,
34
34
  hasOwnView: false,
35
- getIcon: ({ arrayItemType }) => {
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
- MultipleAssets: {
187
- label: "Multiple assets",
182
+ ImageList: {
183
+ label: "Image list",
188
184
  isScalar: false,
189
185
  isContentEditable: false,
190
186
  hasOwnView: true,
191
- getIcon: ({ arrayItemType }) => {
192
- if (arrayItemType === "File") return (props) => /* @__PURE__ */ jsx(FileIcon, { ...props });
193
- return (props) => /* @__PURE__ */ jsx(Images, { ...props });
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 }, { arrayItemType }) => {
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] !== "41ba2e77784594f1ce07296c7a619951f1b5765164f78c588070f4799d74d906") {
23
+ if ($[0] !== "8cb16b8f9bc49d4116b9897e9fc510ae1a05eaf98230f67c2f9664ca4f86e3bd") {
24
24
  for (let $i = 0; $i < 53; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
25
- $[0] = "41ba2e77784594f1ce07296c7a619951f1b5765164f78c588070f4799d74d906";
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,