camox 0.23.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.
@@ -190,6 +190,7 @@ declare function createApp({
190
190
  alt: string;
191
191
  filename: string;
192
192
  mimeType: string;
193
+ size?: number;
193
194
  _fileId?: string;
194
195
  } & {
195
196
  readonly __brand: "ImageValue";
@@ -207,6 +208,7 @@ declare function createApp({
207
208
  alt: string;
208
209
  filename: string;
209
210
  mimeType: string;
211
+ size?: number;
210
212
  _fileId?: string;
211
213
  } & {
212
214
  readonly __brand: "FileValue";
@@ -222,6 +224,7 @@ declare function createApp({
222
224
  alt: string;
223
225
  filename: string;
224
226
  mimeType: string;
227
+ size?: number;
225
228
  _fileId?: string;
226
229
  } & {
227
230
  readonly __brand: "ImageValue";
@@ -239,6 +242,7 @@ declare function createApp({
239
242
  alt: string;
240
243
  filename: string;
241
244
  mimeType: string;
245
+ size?: number;
242
246
  _fileId?: string;
243
247
  } & {
244
248
  readonly __brand: "FileValue";
@@ -584,6 +588,7 @@ declare function createApp({
584
588
  alt: string;
585
589
  filename: string;
586
590
  mimeType: string;
591
+ size?: number;
587
592
  _fileId?: string;
588
593
  } & {
589
594
  readonly __brand: "ImageValue";
@@ -601,6 +606,7 @@ declare function createApp({
601
606
  alt: string;
602
607
  filename: string;
603
608
  mimeType: string;
609
+ size?: number;
604
610
  _fileId?: string;
605
611
  } & {
606
612
  readonly __brand: "FileValue";
@@ -616,6 +622,7 @@ declare function createApp({
616
622
  alt: string;
617
623
  filename: string;
618
624
  mimeType: string;
625
+ size?: number;
619
626
  _fileId?: string;
620
627
  } & {
621
628
  readonly __brand: "ImageValue";
@@ -633,6 +640,7 @@ declare function createApp({
633
640
  alt: string;
634
641
  filename: string;
635
642
  mimeType: string;
643
+ size?: number;
636
644
  _fileId?: string;
637
645
  } & {
638
646
  readonly __brand: "FileValue";
@@ -811,6 +811,7 @@ declare function createBlock<TSchemaShape extends Record<string, TSchema>, TSett
811
811
  alt: string;
812
812
  filename: string;
813
813
  mimeType: string;
814
+ size?: number;
814
815
  _fileId?: string;
815
816
  } & {
816
817
  readonly __brand: "ImageValue";
@@ -948,6 +949,7 @@ declare function createBlock<TSchemaShape extends Record<string, TSchema>, TSett
948
949
  alt: string;
949
950
  filename: string;
950
951
  mimeType: string;
952
+ size?: number;
951
953
  _fileId?: string;
952
954
  } & {
953
955
  readonly __brand: "FileValue";
@@ -1035,6 +1037,7 @@ declare function createBlock<TSchemaShape extends Record<string, TSchema>, TSett
1035
1037
  alt: string;
1036
1038
  filename: string;
1037
1039
  mimeType: string;
1040
+ size?: number;
1038
1041
  _fileId?: string;
1039
1042
  } & {
1040
1043
  readonly __brand: "ImageValue";
@@ -1124,6 +1127,7 @@ declare function createBlock<TSchemaShape extends Record<string, TSchema>, TSett
1124
1127
  alt: string;
1125
1128
  filename: string;
1126
1129
  mimeType: string;
1130
+ size?: number;
1127
1131
  _fileId?: string;
1128
1132
  } & {
1129
1133
  readonly __brand: "FileValue";
@@ -282,9 +282,9 @@ function createBlock(options) {
282
282
  };
283
283
  const Embed = (t0) => {
284
284
  const $ = c(59);
285
- if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
285
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
286
286
  for (let $i = 0; $i < 59; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
287
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
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] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
535
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
536
536
  for (let $i = 0; $i < 38; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
537
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
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] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
776
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
777
777
  for (let $i = 0; $i < 22; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
778
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
778
+ $[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
779
779
  }
780
780
  const { name, children } = t0;
781
781
  const blockContext = React.use(Context);
@@ -846,9 +846,10 @@ function createBlock(options) {
846
846
  const imageProps = {
847
847
  src: transformImageUrl(fieldValue.url, {
848
848
  width: getDefaultImageWidth(),
849
- mimeType: fieldValue.mimeType
849
+ mimeType: fieldValue.mimeType,
850
+ size: fieldValue.size
850
851
  }),
851
- srcSet: buildImageSrcSet(fieldValue.url, fieldValue.mimeType),
852
+ srcSet: buildImageSrcSet(fieldValue.url, fieldValue.mimeType, fieldValue.size),
852
853
  sizes: getDefaultImageSizes(),
853
854
  alt: fieldValue.alt
854
855
  };
@@ -896,9 +897,9 @@ function createBlock(options) {
896
897
  };
897
898
  const File = (t0) => {
898
899
  const $ = c(9);
899
- if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
900
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
900
901
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
901
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
902
+ $[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
902
903
  }
903
904
  const { name, children } = t0;
904
905
  const blockContext = React.use(Context);
@@ -973,9 +974,9 @@ function createBlock(options) {
973
974
  const FileList = _AssetList;
974
975
  const RepeaterItemWrapper = (t0) => {
975
976
  const $ = c(9);
976
- if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
977
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
977
978
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
978
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
979
+ $[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
979
980
  }
980
981
  const { itemId, blockId, mode, children } = t0;
981
982
  const isContentEditable = useIsEditable(mode);
@@ -1016,9 +1017,9 @@ function createBlock(options) {
1016
1017
  };
1017
1018
  const RepeaterHoverProvider = (t0) => {
1018
1019
  const $ = c(7);
1019
- if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1020
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1020
1021
  for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1021
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1022
+ $[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
1022
1023
  }
1023
1024
  const { blockId, fieldName, children } = t0;
1024
1025
  const isContentEditable = useIsEditable("site");
@@ -1049,9 +1050,9 @@ function createBlock(options) {
1049
1050
  };
1050
1051
  const Repeater = (t0) => {
1051
1052
  const $ = c(27);
1052
- if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1053
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1053
1054
  for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1054
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1055
+ $[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
1055
1056
  }
1056
1057
  const { name, children } = t0;
1057
1058
  const blockContext = React.use(Context);
@@ -1176,9 +1177,9 @@ function createBlock(options) {
1176
1177
  };
1177
1178
  const BlockComponent = (t0) => {
1178
1179
  const $ = c(70);
1179
- if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1180
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1180
1181
  for (let $i = 0; $i < 70; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1181
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1182
+ $[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
1182
1183
  }
1183
1184
  const { blockData, mode, isFirstBlock, showAddBlockTop, showAddBlockBottom, addBlockAfterPosition } = t0;
1184
1185
  const isContentEditable = useIsEditable(mode);
@@ -1478,9 +1479,9 @@ function createBlock(options) {
1478
1479
  */
1479
1480
  const Detached = (t0) => {
1480
1481
  const $ = c(31);
1481
- if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1482
+ if ($[0] !== "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a") {
1482
1483
  for (let $i = 0; $i < 31; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1483
- $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1484
+ $[0] = "7449f3ea9be4e14dfde818341dfd9ef5acd44b63f0cff7104a5c200feae79d4a";
1484
1485
  }
1485
1486
  const { children } = t0;
1486
1487
  const ctx = React.use(Context);
@@ -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,6 +69,7 @@ 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";
@@ -9,6 +9,7 @@ const DEFAULT_SRCSET_WIDTHS = [
9
9
  ];
10
10
  const DEFAULT_SRC_WIDTH = 1280;
11
11
  const DEFAULT_SIZES = "100vw";
12
+ const MIN_OPTIMIZE_SIZE = 50 * 1024;
12
13
  function isNonProxiedHostname(hostname) {
13
14
  if (hostname === "localhost") return true;
14
15
  if (hostname === "127.0.0.1") return true;
@@ -43,6 +44,7 @@ function isRasterImage(mimeType) {
43
44
  function transformImageUrl(url, options = {}) {
44
45
  if (!url) return url;
45
46
  if (isNonTransformableMimeType(options.mimeType)) return url;
47
+ if (options.size != null && options.size < MIN_OPTIMIZE_SIZE) return url;
46
48
  const parsed = parseUrl(url);
47
49
  if (!parsed) return url;
48
50
  if (!isTransformableUrl(parsed)) return url;
@@ -54,9 +56,10 @@ function transformImageUrl(url, options = {}) {
54
56
  if (options.width != null) parts.push(`width=${options.width}`);
55
57
  return `${parsed.origin}/cdn-cgi/image/${parts.join(",")}${parsed.pathname}${parsed.search}`;
56
58
  }
57
- function buildImageSrcSet(url, mimeType) {
59
+ function buildImageSrcSet(url, mimeType, size) {
58
60
  if (!url) return void 0;
59
61
  if (isNonTransformableMimeType(mimeType)) return void 0;
62
+ if (size != null && size < MIN_OPTIMIZE_SIZE) return void 0;
60
63
  const parsed = parseUrl(url);
61
64
  if (!parsed) return void 0;
62
65
  if (!isTransformableUrl(parsed)) return void 0;
@@ -5,108 +5,150 @@ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { FileIcon } from "lucide-react";
6
6
 
7
7
  //#region src/features/content/components/AssetCard.tsx
8
+ const OPAQUE_IMAGE_MIME_TYPES = new Set(["image/jpeg", "image/jpg"]);
8
9
  const AssetCard = (t0) => {
9
- const $ = c(27);
10
- if ($[0] !== "9ecd8015db2f9718029c6f2952f71322cd0d4bdec949a22375db105de6ada5db") {
11
- for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
12
- $[0] = "9ecd8015db2f9718029c6f2952f71322cd0d4bdec949a22375db105de6ada5db";
10
+ const $ = c(46);
11
+ if ($[0] !== "1047e431261024f9506ec8f4989ecb00b2808af70364054688c5f3d5eb9e1f28") {
12
+ for (let $i = 0; $i < 46; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
13
+ $[0] = "1047e431261024f9506ec8f4989ecb00b2808af70364054688c5f3d5eb9e1f28";
13
14
  }
14
15
  const { file, selected, onSelect, onOpen } = t0;
16
+ let extension;
17
+ let isImage;
18
+ let isOpaqueImage;
15
19
  let t1;
16
- if ($[1] !== file.mimeType) {
17
- t1 = file.mimeType?.startsWith("image/");
18
- $[1] = file.mimeType;
19
- $[2] = t1;
20
- } else t1 = $[2];
21
- const isImage = t1;
22
20
  let t2;
23
- if ($[3] !== file.filename) {
24
- t2 = file.filename?.split(".").pop()?.toUpperCase() ?? "";
25
- $[3] = file.filename;
26
- $[4] = t2;
27
- } else t2 = $[4];
28
- const extension = t2;
29
- const t3 = file.id;
30
- const t4 = selected ? "bg-primary/20 border-2 border-primary" : "hover:bg-accent/75";
21
+ let t3;
22
+ let t4;
31
23
  let t5;
32
- if ($[5] !== t4) {
33
- t5 = cn("group flex flex-col gap-1.5 rounded-lg p-2 text-left border-2 border-transparent", t4);
34
- $[5] = t4;
35
- $[6] = t5;
36
- } else t5 = $[6];
37
24
  let t6;
38
- if ($[7] !== onSelect) {
39
- t6 = (e) => {
40
- e.stopPropagation();
41
- onSelect();
42
- };
43
- $[7] = onSelect;
44
- $[8] = t6;
45
- } else t6 = $[8];
25
+ if ($[1] !== file.filename || $[2] !== file.id || $[3] !== file.mimeType || $[4] !== onOpen || $[5] !== onSelect || $[6] !== selected) {
26
+ isImage = file.mimeType?.startsWith("image/");
27
+ isOpaqueImage = isImage && OPAQUE_IMAGE_MIME_TYPES.has(file.mimeType ?? "");
28
+ let t7;
29
+ if ($[16] !== file.filename) {
30
+ t7 = file.filename?.split(".").pop()?.toUpperCase() ?? "";
31
+ $[16] = file.filename;
32
+ $[17] = t7;
33
+ } else t7 = $[17];
34
+ extension = t7;
35
+ t2 = "button";
36
+ t3 = file.id;
37
+ const t8 = selected ? "bg-primary/20 border-2 border-primary" : "hover:bg-accent/75";
38
+ if ($[18] !== t8) {
39
+ t4 = cn("group flex flex-col gap-1.5 rounded-lg p-2 text-left border-2 border-transparent", t8);
40
+ $[18] = t8;
41
+ $[19] = t4;
42
+ } else t4 = $[19];
43
+ if ($[20] !== onSelect) {
44
+ t5 = (e) => {
45
+ e.stopPropagation();
46
+ onSelect();
47
+ };
48
+ $[20] = onSelect;
49
+ $[21] = t5;
50
+ } else t5 = $[21];
51
+ if ($[22] !== onOpen) {
52
+ t6 = (e_0) => {
53
+ e_0.stopPropagation();
54
+ onOpen();
55
+ };
56
+ $[22] = onOpen;
57
+ $[23] = t6;
58
+ } else t6 = $[23];
59
+ t1 = cn("flex aspect-4/3 w-full items-center justify-center overflow-hidden rounded-sm", isOpaqueImage && "bg-muted", !isImage && "bg-muted", isImage && !isOpaqueImage && "checkered p-1.5");
60
+ $[1] = file.filename;
61
+ $[2] = file.id;
62
+ $[3] = file.mimeType;
63
+ $[4] = onOpen;
64
+ $[5] = onSelect;
65
+ $[6] = selected;
66
+ $[7] = extension;
67
+ $[8] = isImage;
68
+ $[9] = isOpaqueImage;
69
+ $[10] = t1;
70
+ $[11] = t2;
71
+ $[12] = t3;
72
+ $[13] = t4;
73
+ $[14] = t5;
74
+ $[15] = t6;
75
+ } else {
76
+ extension = $[7];
77
+ isImage = $[8];
78
+ isOpaqueImage = $[9];
79
+ t1 = $[10];
80
+ t2 = $[11];
81
+ t3 = $[12];
82
+ t4 = $[13];
83
+ t5 = $[14];
84
+ t6 = $[15];
85
+ }
46
86
  let t7;
47
- if ($[9] !== onOpen) {
48
- t7 = (e_0) => {
49
- e_0.stopPropagation();
50
- onOpen();
51
- };
52
- $[9] = onOpen;
53
- $[10] = t7;
54
- } else t7 = $[10];
87
+ if ($[24] !== extension || $[25] !== file.alt || $[26] !== file.filename || $[27] !== file.mimeType || $[28] !== file.size || $[29] !== file.url || $[30] !== isImage || $[31] !== isOpaqueImage) {
88
+ t7 = isImage ? /* @__PURE__ */ jsx("img", {
89
+ src: transformImageUrl(file.url, {
90
+ width: 480,
91
+ mimeType: file.mimeType,
92
+ size: file.size
93
+ }),
94
+ alt: file.alt || file.filename,
95
+ draggable: false,
96
+ className: cn("pointer-events-none h-full w-full", isOpaqueImage ? "object-cover" : "object-contain")
97
+ }) : /* @__PURE__ */ jsxs("div", {
98
+ className: "text-muted-foreground flex flex-col items-center gap-1",
99
+ children: [/* @__PURE__ */ jsx(FileIcon, { className: "h-8 w-8" }), extension && /* @__PURE__ */ jsx("span", {
100
+ className: "text-sm font-medium",
101
+ children: extension
102
+ })]
103
+ });
104
+ $[24] = extension;
105
+ $[25] = file.alt;
106
+ $[26] = file.filename;
107
+ $[27] = file.mimeType;
108
+ $[28] = file.size;
109
+ $[29] = file.url;
110
+ $[30] = isImage;
111
+ $[31] = isOpaqueImage;
112
+ $[32] = t7;
113
+ } else t7 = $[32];
55
114
  let t8;
56
- if ($[11] !== extension || $[12] !== file.alt || $[13] !== file.filename || $[14] !== file.mimeType || $[15] !== file.url || $[16] !== isImage) {
115
+ if ($[33] !== t1 || $[34] !== t7) {
57
116
  t8 = /* @__PURE__ */ jsx("div", {
58
- className: "checkered flex aspect-4/3 w-full items-center justify-center overflow-hidden rounded-sm p-1.5",
59
- children: isImage ? /* @__PURE__ */ jsx("img", {
60
- src: transformImageUrl(file.url, {
61
- width: 480,
62
- mimeType: file.mimeType
63
- }),
64
- alt: file.alt || file.filename,
65
- draggable: false,
66
- className: "pointer-events-none h-full w-full object-contain"
67
- }) : /* @__PURE__ */ jsxs("div", {
68
- className: "text-muted-foreground flex flex-col items-center gap-1",
69
- children: [/* @__PURE__ */ jsx(FileIcon, { className: "h-8 w-8" }), extension && /* @__PURE__ */ jsx("span", {
70
- className: "text-sm font-medium",
71
- children: extension
72
- })]
73
- })
117
+ className: t1,
118
+ children: t7
74
119
  });
75
- $[11] = extension;
76
- $[12] = file.alt;
77
- $[13] = file.filename;
78
- $[14] = file.mimeType;
79
- $[15] = file.url;
80
- $[16] = isImage;
81
- $[17] = t8;
82
- } else t8 = $[17];
120
+ $[33] = t1;
121
+ $[34] = t7;
122
+ $[35] = t8;
123
+ } else t8 = $[35];
83
124
  let t9;
84
- if ($[18] !== file.filename) {
125
+ if ($[36] !== file.filename) {
85
126
  t9 = /* @__PURE__ */ jsx("p", {
86
127
  className: "line-clamp-2 px-0.5 text-xs break-all",
87
128
  children: file.filename
88
129
  });
89
- $[18] = file.filename;
90
- $[19] = t9;
91
- } else t9 = $[19];
130
+ $[36] = file.filename;
131
+ $[37] = t9;
132
+ } else t9 = $[37];
92
133
  let t10;
93
- if ($[20] !== file.id || $[21] !== t5 || $[22] !== t6 || $[23] !== t7 || $[24] !== t8 || $[25] !== t9) {
134
+ if ($[38] !== t2 || $[39] !== t3 || $[40] !== t4 || $[41] !== t5 || $[42] !== t6 || $[43] !== t8 || $[44] !== t9) {
94
135
  t10 = /* @__PURE__ */ jsxs("button", {
95
- type: "button",
136
+ type: t2,
96
137
  "data-asset-id": t3,
97
- className: t5,
98
- onClick: t6,
99
- onDoubleClick: t7,
138
+ className: t4,
139
+ onClick: t5,
140
+ onDoubleClick: t6,
100
141
  children: [t8, t9]
101
142
  });
102
- $[20] = file.id;
103
- $[21] = t5;
104
- $[22] = t6;
105
- $[23] = t7;
106
- $[24] = t8;
107
- $[25] = t9;
108
- $[26] = t10;
109
- } else t10 = $[26];
143
+ $[38] = t2;
144
+ $[39] = t3;
145
+ $[40] = t4;
146
+ $[41] = t5;
147
+ $[42] = t6;
148
+ $[43] = t8;
149
+ $[44] = t9;
150
+ $[45] = t10;
151
+ } else t10 = $[45];
110
152
  return t10;
111
153
  };
112
154
 
@@ -15,6 +15,8 @@ declare function usePreviewedPage(): {
15
15
  metaTitle: string | null;
16
16
  metaDescription: string | null;
17
17
  aiSeoEnabled: boolean | null;
18
+ customOgImageBlobId: string | null;
19
+ customOgImageUrl: string | null;
18
20
  createdAt: number;
19
21
  updatedAt: number;
20
22
  };
@@ -21,9 +21,9 @@ function assetLabel(isImage, multiple) {
21
21
  }
22
22
  const AssetActionButtons = (t0) => {
23
23
  const $ = c(27);
24
- if ($[0] !== "44259d77bb7c7c167ba0def21aa6d5d7b7d1115e4b30d38a3c3d829d0a76df91") {
24
+ if ($[0] !== "578c810b03b0bcf84595cb011652bdf58260b853f4d229ef94d55109880eacf0") {
25
25
  for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
26
- $[0] = "44259d77bb7c7c167ba0def21aa6d5d7b7d1115e4b30d38a3c3d829d0a76df91";
26
+ $[0] = "578c810b03b0bcf84595cb011652bdf58260b853f4d229ef94d55109880eacf0";
27
27
  }
28
28
  const { isImage, multiple, fileInputRef, onPickerOpen, onFilesSelected, uploads } = t0;
29
29
  let t1;
@@ -126,9 +126,9 @@ const AssetActionButtons = (t0) => {
126
126
  };
127
127
  const SingleAssetFieldEditor = (t0) => {
128
128
  const $ = c(29);
129
- if ($[0] !== "44259d77bb7c7c167ba0def21aa6d5d7b7d1115e4b30d38a3c3d829d0a76df91") {
129
+ if ($[0] !== "578c810b03b0bcf84595cb011652bdf58260b853f4d229ef94d55109880eacf0") {
130
130
  for (let $i = 0; $i < 29; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
131
- $[0] = "44259d77bb7c7c167ba0def21aa6d5d7b7d1115e4b30d38a3c3d829d0a76df91";
131
+ $[0] = "578c810b03b0bcf84595cb011652bdf58260b853f4d229ef94d55109880eacf0";
132
132
  }
133
133
  const { fieldName, assetType, currentData, onFieldChange } = t0;
134
134
  const asset = currentData[fieldName];
@@ -210,7 +210,8 @@ const SingleAssetFieldEditor = (t0) => {
210
210
  children: /* @__PURE__ */ jsx("img", {
211
211
  src: transformImageUrl(asset.url, {
212
212
  width: 128,
213
- mimeType: asset.mimeType
213
+ mimeType: asset.mimeType,
214
+ size: asset.size
214
215
  }),
215
216
  alt: asset.alt || asset.filename,
216
217
  className: "h-full w-full object-cover"
@@ -3,8 +3,8 @@ import { getAuthCookieHeader } from "../../../lib/auth.js";
3
3
  import { getApiUrl, getEnvironmentName } from "../../../lib/api-client.js";
4
4
  import { fileMutations, fileQueries } from "../../../lib/queries.js";
5
5
  import { isRasterImage, transformImageUrl } from "../../../core/lib/imageTransform.js";
6
- import { DebouncedFieldEditor } from "./DebouncedFieldEditor.js";
7
6
  import { UploadDropZone } from "../../content/components/UploadDropZone.js";
7
+ import { DebouncedFieldEditor } from "./DebouncedFieldEditor.js";
8
8
  import { c } from "react/compiler-runtime";
9
9
  import { Label } from "@camox/ui/label";
10
10
  import { toast } from "@camox/ui/toaster";
@@ -21,9 +21,9 @@ import { ButtonGroup } from "@camox/ui/button-group";
21
21
  //#region src/features/preview/components/AssetLightbox.tsx
22
22
  function MetadataRow(t0) {
23
23
  const $ = c(9);
24
- if ($[0] !== "50fbf2f7ee8a6e8b4452e53c7c776c76cf2aa2926c13218fcc4664012d02ad9a") {
24
+ if ($[0] !== "5a5b1edf9c8dcefe67d31c9a59c95831dcc6dfede24df286efbdba62d1fd4e8d") {
25
25
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
26
- $[0] = "50fbf2f7ee8a6e8b4452e53c7c776c76cf2aa2926c13218fcc4664012d02ad9a";
26
+ $[0] = "5a5b1edf9c8dcefe67d31c9a59c95831dcc6dfede24df286efbdba62d1fd4e8d";
27
27
  }
28
28
  const { label, children } = t0;
29
29
  let t1;
@@ -67,9 +67,9 @@ function MetadataRow(t0) {
67
67
  }
68
68
  function DeliveredSize(t0) {
69
69
  const $ = c(16);
70
- if ($[0] !== "50fbf2f7ee8a6e8b4452e53c7c776c76cf2aa2926c13218fcc4664012d02ad9a") {
70
+ if ($[0] !== "5a5b1edf9c8dcefe67d31c9a59c95831dcc6dfede24df286efbdba62d1fd4e8d") {
71
71
  for (let $i = 0; $i < 16; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
72
- $[0] = "50fbf2f7ee8a6e8b4452e53c7c776c76cf2aa2926c13218fcc4664012d02ad9a";
72
+ $[0] = "5a5b1edf9c8dcefe67d31c9a59c95831dcc6dfede24df286efbdba62d1fd4e8d";
73
73
  }
74
74
  const { bytes, raw } = t0;
75
75
  if (bytes == null) {
@@ -138,9 +138,9 @@ function DeliveredSize(t0) {
138
138
  }
139
139
  function DeliveredLabel(t0) {
140
140
  const $ = c(4);
141
- if ($[0] !== "50fbf2f7ee8a6e8b4452e53c7c776c76cf2aa2926c13218fcc4664012d02ad9a") {
141
+ if ($[0] !== "5a5b1edf9c8dcefe67d31c9a59c95831dcc6dfede24df286efbdba62d1fd4e8d") {
142
142
  for (let $i = 0; $i < 4; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
143
- $[0] = "50fbf2f7ee8a6e8b4452e53c7c776c76cf2aa2926c13218fcc4664012d02ad9a";
143
+ $[0] = "5a5b1edf9c8dcefe67d31c9a59c95831dcc6dfede24df286efbdba62d1fd4e8d";
144
144
  }
145
145
  const { children } = t0;
146
146
  let t1;
@@ -243,11 +243,13 @@ const AssetLightbox = ({ open, onOpenChange, fileId }) => {
243
243
  }
244
244
  const phoneUrl = transformImageUrl(fileUrl, {
245
245
  width: DELIVERED_PHONE_WIDTH,
246
- mimeType: fileMimeType
246
+ mimeType: fileMimeType,
247
+ size: file?.size
247
248
  });
248
249
  const laptopUrl = transformImageUrl(fileUrl, {
249
250
  width: DELIVERED_LAPTOP_WIDTH,
250
- mimeType: fileMimeType
251
+ mimeType: fileMimeType,
252
+ size: file?.size
251
253
  });
252
254
  if (phoneUrl === fileUrl && laptopUrl === fileUrl) {
253
255
  setDeliveredSizes(null);
@@ -5,19 +5,20 @@ import { blockQueries, layoutQueries, pageMutations, pageQueries, projectQueries
5
5
  import { formatPathSegment } from "../../../lib/utils.js";
6
6
  import { useCamoxApp } from "../../provider/components/CamoxAppContext.js";
7
7
  import { PageLocationFieldset } from "./PageLocationFieldset.js";
8
+ import { UploadDropZone } from "../../content/components/UploadDropZone.js";
8
9
  import { DebouncedFieldEditor } from "./DebouncedFieldEditor.js";
9
10
  import { ShikiMarkdown } from "./ShikiMarkdown.js";
10
11
  import { c } from "react/compiler-runtime";
11
12
  import { Label } from "@camox/ui/label";
12
13
  import { toast } from "@camox/ui/toaster";
13
- import { useMutation, useQuery } from "@tanstack/react-query";
14
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
14
15
  import { useNavigate } from "@tanstack/react-router";
15
16
  import { useSelector } from "@xstate/store-react";
16
17
  import * as React from "react";
17
18
  import { jsx, jsxs } from "react/jsx-runtime";
18
19
  import { Button } from "@camox/ui/button";
19
20
  import { Tooltip, TooltipContent, TooltipTrigger } from "@camox/ui/tooltip";
20
- import { Globe, Info } from "lucide-react";
21
+ import { Globe, Info, Trash2, Upload } from "lucide-react";
21
22
  import { Spinner } from "@camox/ui/spinner";
22
23
  import { Alert, AlertDescription, AlertTitle } from "@camox/ui/alert";
23
24
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@camox/ui/dialog";
@@ -28,9 +29,9 @@ import { Switch } from "@camox/ui/switch";
28
29
  //#region src/features/preview/components/EditPageModal.tsx
29
30
  const EditPageModal = () => {
30
31
  const $ = c(3);
31
- if ($[0] !== "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2") {
32
+ if ($[0] !== "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c") {
32
33
  for (let $i = 0; $i < 3; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
33
- $[0] = "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2";
34
+ $[0] = "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c";
34
35
  }
35
36
  const editingPageId = useSelector(previewStore, _temp);
36
37
  let t0;
@@ -302,9 +303,9 @@ function truncateText(text, maxLen) {
302
303
  }
303
304
  const SearchEnginePreview = (t0) => {
304
305
  const $ = c(17);
305
- if ($[0] !== "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2") {
306
+ if ($[0] !== "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c") {
306
307
  for (let $i = 0; $i < 17; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
307
- $[0] = "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2";
308
+ $[0] = "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c";
308
309
  }
309
310
  const { page, metaTitle, metaDescription } = t0;
310
311
  const url = `${typeof window !== "undefined" ? window.location.origin : ""}${page.fullPath}`;
@@ -386,10 +387,10 @@ const SearchEnginePreview = (t0) => {
386
387
  return t10;
387
388
  };
388
389
  const SocialPreviewSection = (t0) => {
389
- const $ = c(23);
390
- if ($[0] !== "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2") {
391
- for (let $i = 0; $i < 23; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
392
- $[0] = "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2";
390
+ const $ = c(60);
391
+ if ($[0] !== "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c") {
392
+ for (let $i = 0; $i < 60; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
393
+ $[0] = "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c";
393
394
  }
394
395
  const { page, metaTitle, metaDescription, layoutId, projectName } = t0;
395
396
  const pageMetaTitle = page.metaTitle ?? page.pathSegment;
@@ -407,102 +408,265 @@ const SocialPreviewSection = (t0) => {
407
408
  $[4] = projectName;
408
409
  $[5] = t1;
409
410
  } else t1 = $[5];
410
- const ogImage = `/og?${t1}`;
411
+ const generatedOgImage = `/og?${t1}`;
412
+ const ogImage = page.customOgImageUrl ?? generatedOgImage;
411
413
  const url = `${typeof window !== "undefined" ? window.location.origin : ""}${page.fullPath}`;
414
+ const fileInputRef = React.useRef(null);
415
+ const queryClient = useQueryClient();
412
416
  let t2;
413
417
  if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
414
- t2 = /* @__PURE__ */ jsx(Label, { children: "Social preview" });
418
+ t2 = pageMutations.uploadCustomOgImage();
415
419
  $[6] = t2;
416
420
  } else t2 = $[6];
417
421
  let t3;
418
- if ($[7] !== ogImage) {
419
- t3 = ogImage ? /* @__PURE__ */ jsx("img", {
422
+ if ($[7] !== page.id || $[8] !== queryClient) {
423
+ t3 = {
424
+ ...t2,
425
+ onSuccess: () => {
426
+ queryClient.invalidateQueries({ queryKey: pageQueries.getById(page.id).queryKey });
427
+ trackClientEvent("page_custom_og_image_uploaded", { pageId: page.id });
428
+ },
429
+ onError: _temp5
430
+ };
431
+ $[7] = page.id;
432
+ $[8] = queryClient;
433
+ $[9] = t3;
434
+ } else t3 = $[9];
435
+ const uploadCustomOgImage = useMutation(t3);
436
+ let t4;
437
+ if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
438
+ t4 = pageMutations.deleteCustomOgImage();
439
+ $[10] = t4;
440
+ } else t4 = $[10];
441
+ let t5;
442
+ if ($[11] !== page.id || $[12] !== queryClient) {
443
+ t5 = {
444
+ ...t4,
445
+ onSuccess: () => {
446
+ queryClient.invalidateQueries({ queryKey: pageQueries.getById(page.id).queryKey });
447
+ trackClientEvent("page_custom_og_image_removed", { pageId: page.id });
448
+ },
449
+ onError: _temp6
450
+ };
451
+ $[11] = page.id;
452
+ $[12] = queryClient;
453
+ $[13] = t5;
454
+ } else t5 = $[13];
455
+ const deleteCustomOgImage = useMutation(t5);
456
+ const hasCustomImage = !!page.customOgImageUrl;
457
+ const isBusy = uploadCustomOgImage.isPending || deleteCustomOgImage.isPending;
458
+ let t6;
459
+ if ($[14] !== page.id || $[15] !== uploadCustomOgImage) {
460
+ t6 = (files) => {
461
+ const file = files[0];
462
+ if (file) uploadCustomOgImage.mutate({
463
+ pageId: page.id,
464
+ file
465
+ });
466
+ };
467
+ $[14] = page.id;
468
+ $[15] = uploadCustomOgImage;
469
+ $[16] = t6;
470
+ } else t6 = $[16];
471
+ const handleFiles = t6;
472
+ let t7;
473
+ if ($[17] === Symbol.for("react.memo_cache_sentinel")) {
474
+ t7 = /* @__PURE__ */ jsx(Label, { children: "Social preview" });
475
+ $[17] = t7;
476
+ } else t7 = $[17];
477
+ let t8;
478
+ if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
479
+ t8 = () => fileInputRef.current?.click();
480
+ $[18] = t8;
481
+ } else t8 = $[18];
482
+ let t9;
483
+ if ($[19] !== uploadCustomOgImage.isPending) {
484
+ t9 = uploadCustomOgImage.isPending ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(Upload, { className: "size-3.5" });
485
+ $[19] = uploadCustomOgImage.isPending;
486
+ $[20] = t9;
487
+ } else t9 = $[20];
488
+ const t10 = hasCustomImage ? "Replace custom image" : "Upload custom image";
489
+ let t11;
490
+ if ($[21] !== isBusy || $[22] !== t10 || $[23] !== t9) {
491
+ t11 = /* @__PURE__ */ jsxs(Button, {
492
+ type: "button",
493
+ variant: "secondary",
494
+ size: "sm",
495
+ disabled: isBusy,
496
+ onClick: t8,
497
+ children: [t9, t10]
498
+ });
499
+ $[21] = isBusy;
500
+ $[22] = t10;
501
+ $[23] = t9;
502
+ $[24] = t11;
503
+ } else t11 = $[24];
504
+ let t12;
505
+ if ($[25] !== deleteCustomOgImage || $[26] !== hasCustomImage || $[27] !== isBusy || $[28] !== page.id) {
506
+ t12 = hasCustomImage && /* @__PURE__ */ jsxs(Button, {
507
+ type: "button",
508
+ variant: "ghost",
509
+ disabled: isBusy,
510
+ onClick: () => deleteCustomOgImage.mutate({ pageId: page.id }),
511
+ "aria-label": "Remove custom image",
512
+ children: [
513
+ deleteCustomOgImage.isPending ? /* @__PURE__ */ jsx(Spinner, { className: "text-muted-foreground" }) : /* @__PURE__ */ jsx(Trash2, { className: "text-muted-foreground" }),
514
+ " ",
515
+ "Clear"
516
+ ]
517
+ });
518
+ $[25] = deleteCustomOgImage;
519
+ $[26] = hasCustomImage;
520
+ $[27] = isBusy;
521
+ $[28] = page.id;
522
+ $[29] = t12;
523
+ } else t12 = $[29];
524
+ let t13;
525
+ if ($[30] !== handleFiles) {
526
+ t13 = /* @__PURE__ */ jsx("input", {
527
+ type: "file",
528
+ ref: fileInputRef,
529
+ className: "hidden",
530
+ accept: "image/jpeg,image/png,image/gif,image/webp",
531
+ onChange: (e) => {
532
+ if (e.target.files) handleFiles(e.target.files);
533
+ e.target.value = "";
534
+ }
535
+ });
536
+ $[30] = handleFiles;
537
+ $[31] = t13;
538
+ } else t13 = $[31];
539
+ let t14;
540
+ if ($[32] !== t11 || $[33] !== t12 || $[34] !== t13) {
541
+ t14 = /* @__PURE__ */ jsxs("div", {
542
+ className: "flex items-center gap-2",
543
+ children: [
544
+ t11,
545
+ t12,
546
+ t13
547
+ ]
548
+ });
549
+ $[32] = t11;
550
+ $[33] = t12;
551
+ $[34] = t13;
552
+ $[35] = t14;
553
+ } else t14 = $[35];
554
+ const t15 = hasCustomImage ? "Drop image to replace" : "Drop image to upload";
555
+ let t16;
556
+ if ($[36] === Symbol.for("react.memo_cache_sentinel")) {
557
+ t16 = { aspectRatio: "1200 / 630" };
558
+ $[36] = t16;
559
+ } else t16 = $[36];
560
+ let t17;
561
+ if ($[37] !== ogImage) {
562
+ t17 = /* @__PURE__ */ jsx("img", {
420
563
  src: ogImage,
421
564
  alt: "",
422
565
  className: "w-full object-cover",
423
- style: { aspectRatio: "1200 / 630" }
424
- }) : /* @__PURE__ */ jsx("div", {
425
- className: "bg-muted w-full",
426
- style: { aspectRatio: "1200 / 630" }
566
+ style: t16
427
567
  });
428
- $[7] = ogImage;
429
- $[8] = t3;
430
- } else t3 = $[8];
431
- const t4 = metaTitle || "Untitled";
432
- let t5;
433
- if ($[9] !== t4) {
434
- t5 = /* @__PURE__ */ jsx("p", {
568
+ $[37] = ogImage;
569
+ $[38] = t17;
570
+ } else t17 = $[38];
571
+ let t18;
572
+ if ($[39] !== handleFiles || $[40] !== t15 || $[41] !== t17) {
573
+ t18 = /* @__PURE__ */ jsx(UploadDropZone, {
574
+ onDrop: handleFiles,
575
+ label: t15,
576
+ className: "block",
577
+ children: t17
578
+ });
579
+ $[39] = handleFiles;
580
+ $[40] = t15;
581
+ $[41] = t17;
582
+ $[42] = t18;
583
+ } else t18 = $[42];
584
+ const t19 = metaTitle || "Untitled";
585
+ let t20;
586
+ if ($[43] !== t19) {
587
+ t20 = /* @__PURE__ */ jsx("p", {
435
588
  className: "text-foreground truncate text-sm font-semibold",
436
- children: t4
589
+ children: t19
437
590
  });
438
- $[9] = t4;
439
- $[10] = t5;
440
- } else t5 = $[10];
441
- const t6 = metaDescription || "No description";
442
- let t7;
443
- if ($[11] !== t6) {
444
- t7 = /* @__PURE__ */ jsx("p", {
591
+ $[43] = t19;
592
+ $[44] = t20;
593
+ } else t20 = $[44];
594
+ const t21 = metaDescription || "No description";
595
+ let t22;
596
+ if ($[45] !== t21) {
597
+ t22 = /* @__PURE__ */ jsx("p", {
445
598
  className: "text-muted-foreground line-clamp-2 text-xs",
446
- children: t6
599
+ children: t21
447
600
  });
448
- $[11] = t6;
449
- $[12] = t7;
450
- } else t7 = $[12];
451
- let t8;
452
- if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
453
- t8 = /* @__PURE__ */ jsx(Globe, { className: "size-3 shrink-0" });
454
- $[13] = t8;
455
- } else t8 = $[13];
456
- let t9;
457
- if ($[14] !== url) {
458
- t9 = /* @__PURE__ */ jsx("div", {
601
+ $[45] = t21;
602
+ $[46] = t22;
603
+ } else t22 = $[46];
604
+ let t23;
605
+ if ($[47] === Symbol.for("react.memo_cache_sentinel")) {
606
+ t23 = /* @__PURE__ */ jsx(Globe, { className: "size-3 shrink-0" });
607
+ $[47] = t23;
608
+ } else t23 = $[47];
609
+ let t24;
610
+ if ($[48] !== url) {
611
+ t24 = /* @__PURE__ */ jsx("div", {
459
612
  className: "pt-1.5",
460
613
  children: /* @__PURE__ */ jsxs("p", {
461
614
  className: "text-muted-foreground flex items-center gap-1 text-xs",
462
- children: [t8, /* @__PURE__ */ jsx("span", {
615
+ children: [t23, /* @__PURE__ */ jsx("span", {
463
616
  className: "truncate",
464
617
  children: url
465
618
  })]
466
619
  })
467
620
  });
468
- $[14] = url;
469
- $[15] = t9;
470
- } else t9 = $[15];
471
- let t10;
472
- if ($[16] !== t5 || $[17] !== t7 || $[18] !== t9) {
473
- t10 = /* @__PURE__ */ jsxs("div", {
621
+ $[48] = url;
622
+ $[49] = t24;
623
+ } else t24 = $[49];
624
+ let t25;
625
+ if ($[50] !== t20 || $[51] !== t22 || $[52] !== t24) {
626
+ t25 = /* @__PURE__ */ jsxs("div", {
474
627
  className: "space-y-1.5 border-t px-3 py-2.5",
475
628
  children: [
476
- t5,
477
- t7,
478
- t9
629
+ t20,
630
+ t22,
631
+ t24
479
632
  ]
480
633
  });
481
- $[16] = t5;
482
- $[17] = t7;
483
- $[18] = t9;
484
- $[19] = t10;
485
- } else t10 = $[19];
486
- let t11;
487
- if ($[20] !== t10 || $[21] !== t3) {
488
- t11 = /* @__PURE__ */ jsxs("div", {
634
+ $[50] = t20;
635
+ $[51] = t22;
636
+ $[52] = t24;
637
+ $[53] = t25;
638
+ } else t25 = $[53];
639
+ let t26;
640
+ if ($[54] !== t18 || $[55] !== t25) {
641
+ t26 = /* @__PURE__ */ jsxs("div", {
642
+ className: "border-border max-w-xl overflow-hidden rounded-lg border",
643
+ children: [t18, t25]
644
+ });
645
+ $[54] = t18;
646
+ $[55] = t25;
647
+ $[56] = t26;
648
+ } else t26 = $[56];
649
+ let t27;
650
+ if ($[57] !== t14 || $[58] !== t26) {
651
+ t27 = /* @__PURE__ */ jsxs("div", {
489
652
  className: "space-y-2 pt-2",
490
- children: [t2, /* @__PURE__ */ jsxs("div", {
491
- className: "border-border max-w-xl overflow-hidden rounded-lg border",
492
- children: [t3, t10]
493
- })]
653
+ children: [
654
+ t7,
655
+ t14,
656
+ t26
657
+ ]
494
658
  });
495
- $[20] = t10;
496
- $[21] = t3;
497
- $[22] = t11;
498
- } else t11 = $[22];
499
- return t11;
659
+ $[57] = t14;
660
+ $[58] = t26;
661
+ $[59] = t27;
662
+ } else t27 = $[59];
663
+ return t27;
500
664
  };
501
665
  const PageMarkdownPreview = (t0) => {
502
666
  const $ = c(9);
503
- if ($[0] !== "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2") {
667
+ if ($[0] !== "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c") {
504
668
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
505
- $[0] = "1a44c3b62f2d464ddc4fb38012b10897c6c1b331adf4c50075c0f78c681dcec2";
669
+ $[0] = "4cfb98531a98e3bc48da0686012d88a786d8b22d52089109158bebc149d3d37c";
506
670
  }
507
671
  const { pageId, metaTitle, metaDescription } = t0;
508
672
  let t1;
@@ -549,6 +713,12 @@ const PageMarkdownPreview = (t0) => {
549
713
  function _temp(state) {
550
714
  return state.context.editingPageId;
551
715
  }
716
+ function _temp5(error) {
717
+ toast.error(error.message || "Could not upload image");
718
+ }
719
+ function _temp6() {
720
+ toast.error("Could not remove image");
721
+ }
552
722
 
553
723
  //#endregion
554
724
  export { EditPageModal };
@@ -21,10 +21,10 @@ import { CSS } from "@dnd-kit/utilities";
21
21
 
22
22
  //#region src/features/preview/components/MultipleAssetFieldEditor.tsx
23
23
  const SortableAssetItem = (t0) => {
24
- const $ = c(44);
25
- if ($[0] !== "2cded293bac7099a2f0d21d69f3d54cce48c76ba242f02366ecaa807b6b79b7d") {
26
- for (let $i = 0; $i < 44; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
27
- $[0] = "2cded293bac7099a2f0d21d69f3d54cce48c76ba242f02366ecaa807b6b79b7d";
24
+ const $ = c(45);
25
+ if ($[0] !== "3cf038d410d44a905bb67a7755e19d8146b75274d1d91e2ad5f7a0d2580bec80") {
26
+ for (let $i = 0; $i < 45; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
27
+ $[0] = "3cf038d410d44a905bb67a7755e19d8146b75274d1d91e2ad5f7a0d2580bec80";
28
28
  }
29
29
  const { asset, assetType, onRemove, onAssetOpen } = t0;
30
30
  const t1 = String(asset._fileId);
@@ -90,13 +90,14 @@ const SortableAssetItem = (t0) => {
90
90
  $[17] = t10;
91
91
  } else t10 = $[17];
92
92
  let t11;
93
- if ($[18] !== asset.alt || $[19] !== asset.filename || $[20] !== asset.mimeType || $[21] !== asset.url || $[22] !== assetType) {
93
+ if ($[18] !== asset.alt || $[19] !== asset.filename || $[20] !== asset.mimeType || $[21] !== asset.size || $[22] !== asset.url || $[23] !== assetType) {
94
94
  t11 = assetType === "Image" ? /* @__PURE__ */ jsx("div", {
95
95
  className: "border-border h-12 w-12 shrink-0 overflow-hidden rounded border",
96
96
  children: /* @__PURE__ */ jsx("img", {
97
97
  src: transformImageUrl(asset.url, {
98
98
  width: 128,
99
- mimeType: asset.mimeType
99
+ mimeType: asset.mimeType,
100
+ size: asset.size
100
101
  }),
101
102
  alt: asset.alt || asset.filename,
102
103
  className: "h-full w-full object-cover"
@@ -108,55 +109,56 @@ const SortableAssetItem = (t0) => {
108
109
  $[18] = asset.alt;
109
110
  $[19] = asset.filename;
110
111
  $[20] = asset.mimeType;
111
- $[21] = asset.url;
112
- $[22] = assetType;
113
- $[23] = t11;
114
- } else t11 = $[23];
112
+ $[21] = asset.size;
113
+ $[22] = asset.url;
114
+ $[23] = assetType;
115
+ $[24] = t11;
116
+ } else t11 = $[24];
115
117
  const t12 = asset.filename || "Untitled";
116
118
  let t13;
117
- if ($[24] !== asset.filename || $[25] !== t12) {
119
+ if ($[25] !== asset.filename || $[26] !== t12) {
118
120
  t13 = /* @__PURE__ */ jsx("p", {
119
121
  className: "flex-1 truncate text-left text-sm",
120
122
  title: asset.filename,
121
123
  children: t12
122
124
  });
123
- $[24] = asset.filename;
124
- $[25] = t12;
125
- $[26] = t13;
126
- } else t13 = $[26];
125
+ $[25] = asset.filename;
126
+ $[26] = t12;
127
+ $[27] = t13;
128
+ } else t13 = $[27];
127
129
  let t14;
128
- if ($[27] !== t10 || $[28] !== t11 || $[29] !== t13) {
130
+ if ($[28] !== t10 || $[29] !== t11 || $[30] !== t13) {
129
131
  t14 = /* @__PURE__ */ jsxs("button", {
130
132
  type: "button",
131
133
  className: "flex min-w-0 flex-1 cursor-zoom-in items-center gap-2",
132
134
  onClick: t10,
133
135
  children: [t11, t13]
134
136
  });
135
- $[27] = t10;
136
- $[28] = t11;
137
- $[29] = t13;
138
- $[30] = t14;
139
- } else t14 = $[30];
137
+ $[28] = t10;
138
+ $[29] = t11;
139
+ $[30] = t13;
140
+ $[31] = t14;
141
+ } else t14 = $[31];
140
142
  let t15;
141
- if ($[31] !== asset._fileId || $[32] !== onRemove) {
143
+ if ($[32] !== asset._fileId || $[33] !== onRemove) {
142
144
  t15 = () => onRemove(asset._fileId);
143
- $[31] = asset._fileId;
144
- $[32] = onRemove;
145
- $[33] = t15;
146
- } else t15 = $[33];
145
+ $[32] = asset._fileId;
146
+ $[33] = onRemove;
147
+ $[34] = t15;
148
+ } else t15 = $[34];
147
149
  let t16;
148
- if ($[34] !== asset._fileId || $[35] !== t15) {
150
+ if ($[35] !== asset._fileId || $[36] !== t15) {
149
151
  t16 = /* @__PURE__ */ jsx(UnlinkAssetButton, {
150
152
  fileId: asset._fileId,
151
153
  onUnlink: t15,
152
154
  className: "hidden group-focus-within:flex group-hover:flex"
153
155
  });
154
- $[34] = asset._fileId;
155
- $[35] = t15;
156
- $[36] = t16;
157
- } else t16 = $[36];
156
+ $[35] = asset._fileId;
157
+ $[36] = t15;
158
+ $[37] = t16;
159
+ } else t16 = $[37];
158
160
  let t17;
159
- if ($[37] !== setNodeRef || $[38] !== style || $[39] !== t14 || $[40] !== t16 || $[41] !== t7 || $[42] !== t9) {
161
+ if ($[38] !== setNodeRef || $[39] !== style || $[40] !== t14 || $[41] !== t16 || $[42] !== t7 || $[43] !== t9) {
160
162
  t17 = /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("div", {
161
163
  ref: setNodeRef,
162
164
  style,
@@ -167,21 +169,21 @@ const SortableAssetItem = (t0) => {
167
169
  t16
168
170
  ]
169
171
  }) });
170
- $[37] = setNodeRef;
171
- $[38] = style;
172
- $[39] = t14;
173
- $[40] = t16;
174
- $[41] = t7;
175
- $[42] = t9;
176
- $[43] = t17;
177
- } else t17 = $[43];
172
+ $[38] = setNodeRef;
173
+ $[39] = style;
174
+ $[40] = t14;
175
+ $[41] = t16;
176
+ $[42] = t7;
177
+ $[43] = t9;
178
+ $[44] = t17;
179
+ } else t17 = $[44];
178
180
  return t17;
179
181
  };
180
182
  const MultipleAssetFieldEditor = (t0) => {
181
183
  const $ = c(12);
182
- if ($[0] !== "2cded293bac7099a2f0d21d69f3d54cce48c76ba242f02366ecaa807b6b79b7d") {
184
+ if ($[0] !== "3cf038d410d44a905bb67a7755e19d8146b75274d1d91e2ad5f7a0d2580bec80") {
183
185
  for (let $i = 0; $i < 12; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
184
- $[0] = "2cded293bac7099a2f0d21d69f3d54cce48c76ba242f02366ecaa807b6b79b7d";
186
+ $[0] = "3cf038d410d44a905bb67a7755e19d8146b75274d1d91e2ad5f7a0d2580bec80";
185
187
  }
186
188
  const { fieldName, assetType, currentData, onFieldChange } = t0;
187
189
  const isImage = assetType === "Image";
@@ -33,6 +33,8 @@ declare function createPageLoader(apiUrl: string, projectSlug: string, environme
33
33
  metaTitle: string | null;
34
34
  metaDescription: string | null;
35
35
  aiSeoEnabled: boolean | null;
36
+ customOgImageBlobId: string | null;
37
+ customOgImageUrl: string | null;
36
38
  createdAt: number;
37
39
  updatedAt: number;
38
40
  };
@@ -117,12 +117,13 @@ function createPageHead(camoxApp) {
117
117
  name: "description",
118
118
  content: page.page.metaDescription
119
119
  });
120
- const ogImageUrl = `${origin}/og?${new URLSearchParams({
120
+ const ogImageParams = new URLSearchParams({
121
121
  ...page.layout && { layoutId: page.layout.layoutId },
122
122
  title: pageMetaTitle,
123
123
  ...page.page.metaDescription && { description: page.page.metaDescription },
124
124
  ...page.projectName && { projectName: page.projectName }
125
- }).toString()}`;
125
+ });
126
+ const ogImageUrl = page.page.customOgImageUrl ?? `${origin}/og?${ogImageParams.toString()}`;
126
127
  meta.push({
127
128
  property: "og:title",
128
129
  content: title
@@ -151,9 +152,9 @@ function createPageHead(camoxApp) {
151
152
  }
152
153
  const PageRouteComponent = () => {
153
154
  const $ = c(2);
154
- if ($[0] !== "4272e30e312eefa840c2b2498844aebeea94a415ce1efe0a8248fab111737756") {
155
+ if ($[0] !== "858ec55c5e5a82d2aa9c37c6b5b7f1c0a2d1cff69ec415b2b53199f5bf1faa69") {
155
156
  for (let $i = 0; $i < 2; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
156
- $[0] = "4272e30e312eefa840c2b2498844aebeea94a415ce1efe0a8248fab111737756";
157
+ $[0] = "858ec55c5e5a82d2aa9c37c6b5b7f1c0a2d1cff69ec415b2b53199f5bf1faa69";
157
158
  }
158
159
  let t0;
159
160
  if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
@@ -11,9 +11,9 @@ const NormalizedDataContext = React.createContext({
11
11
  });
12
12
  const NormalizedDataProvider = (t0) => {
13
13
  const $ = c(13);
14
- if ($[0] !== "a0f4a795dddd4ca9e7a329b1dc44ac458e5aff0b7cf376e05306b8ccf0cac2a8") {
14
+ if ($[0] !== "c508cbfbcda1ea1312463b6cbebcd66332f3346f51c02c09c35687c430a19469") {
15
15
  for (let $i = 0; $i < 13; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
16
- $[0] = "a0f4a795dddd4ca9e7a329b1dc44ac458e5aff0b7cf376e05306b8ccf0cac2a8";
16
+ $[0] = "c508cbfbcda1ea1312463b6cbebcd66332f3346f51c02c09c35687c430a19469";
17
17
  }
18
18
  const { files, repeatableItems, children } = t0;
19
19
  let t1;
@@ -74,9 +74,9 @@ function useStableArray(next) {
74
74
  }
75
75
  function usePageBlocks(pageStructure) {
76
76
  const $ = c(36);
77
- if ($[0] !== "a0f4a795dddd4ca9e7a329b1dc44ac458e5aff0b7cf376e05306b8ccf0cac2a8") {
77
+ if ($[0] !== "c508cbfbcda1ea1312463b6cbebcd66332f3346f51c02c09c35687c430a19469") {
78
78
  for (let $i = 0; $i < 36; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
79
- $[0] = "a0f4a795dddd4ca9e7a329b1dc44ac458e5aff0b7cf376e05306b8ccf0cac2a8";
79
+ $[0] = "c508cbfbcda1ea1312463b6cbebcd66332f3346f51c02c09c35687c430a19469";
80
80
  }
81
81
  const blockIds = pageStructure.page.blockIds;
82
82
  const beforeIds = pageStructure.layout?.beforeBlockIds ?? EMPTY_IDS;
@@ -239,6 +239,7 @@ function resolveFileMarker(marker, filesMap) {
239
239
  alt: file.alt,
240
240
  filename: file.filename,
241
241
  mimeType: file.mimeType,
242
+ size: file.size,
242
243
  _fileId: marker._fileId
243
244
  };
244
245
  return {
@@ -246,6 +247,7 @@ function resolveFileMarker(marker, filesMap) {
246
247
  alt: "",
247
248
  filename: "",
248
249
  mimeType: "",
250
+ size: 0,
249
251
  _fileId: marker._fileId
250
252
  };
251
253
  }
@@ -1,4 +1,5 @@
1
- import { getOrpc } from "./api-client.js";
1
+ import { getAuthCookieHeader } from "./auth.js";
2
+ import { getApiUrl, getEnvironmentName, getOrpc } from "./api-client.js";
2
3
  import { queryKeys } from "@camox/api-contract/query-keys";
3
4
 
4
5
  //#region src/lib/queries.ts
@@ -124,6 +125,12 @@ const repeatableItemMutations = {
124
125
  updateSettings: () => getOrpc().repeatableItems.updateSettings.mutationOptions(),
125
126
  updatePosition: () => getOrpc().repeatableItems.updatePosition.mutationOptions()
126
127
  };
128
+ function ogImageHeaders() {
129
+ const headers = { "Better-Auth-Cookie": getAuthCookieHeader() };
130
+ const envName = getEnvironmentName();
131
+ if (envName) headers["x-environment-name"] = envName;
132
+ return headers;
133
+ }
127
134
  const pageMutations = {
128
135
  create: () => getOrpc().pages.create.mutationOptions(),
129
136
  delete: () => getOrpc().pages.delete.mutationOptions(),
@@ -131,7 +138,31 @@ const pageMutations = {
131
138
  setLayout: () => getOrpc().pages.setLayout.mutationOptions(),
132
139
  setAiSeo: () => getOrpc().pages.setAiSeo.mutationOptions(),
133
140
  setMetaTitle: () => getOrpc().pages.setMetaTitle.mutationOptions(),
134
- setMetaDescription: () => getOrpc().pages.setMetaDescription.mutationOptions()
141
+ setMetaDescription: () => getOrpc().pages.setMetaDescription.mutationOptions(),
142
+ uploadCustomOgImage: () => ({ mutationFn: async ({ pageId, file }) => {
143
+ const formData = new FormData();
144
+ formData.append("file", file);
145
+ const res = await fetch(`${getApiUrl()}/pages/${pageId}/og-image`, {
146
+ method: "POST",
147
+ body: formData,
148
+ headers: ogImageHeaders(),
149
+ credentials: "omit"
150
+ });
151
+ if (!res.ok) {
152
+ const body = await res.json().catch(() => null);
153
+ throw new Error(body?.error ?? `Upload failed: ${res.status}`);
154
+ }
155
+ return res.json();
156
+ } }),
157
+ deleteCustomOgImage: () => ({ mutationFn: async ({ pageId }) => {
158
+ const res = await fetch(`${getApiUrl()}/pages/${pageId}/og-image`, {
159
+ method: "DELETE",
160
+ headers: ogImageHeaders(),
161
+ credentials: "omit"
162
+ });
163
+ if (!res.ok) throw new Error(`Delete failed: ${res.status}`);
164
+ return res.json();
165
+ } })
135
166
  };
136
167
  const fileMutations = {
137
168
  delete: () => getOrpc().files.delete.mutationOptions(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camox",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "bin": {
5
5
  "camox": "./bin/camox.mjs"
6
6
  },
@@ -126,9 +126,9 @@
126
126
  "react-og-preview": "^0.2.0",
127
127
  "shiki": "^4.0.2",
128
128
  "zod": "^4.3.6",
129
- "@camox/api-contract": "0.23.0",
130
- "@camox/cli": "0.23.0",
131
- "@camox/ui": "0.23.0"
129
+ "@camox/api-contract": "0.24.0",
130
+ "@camox/cli": "0.24.0",
131
+ "@camox/ui": "0.24.0"
132
132
  },
133
133
  "devDependencies": {
134
134
  "@babel/core": "^7.29.0",