camox 0.36.0 → 0.36.1
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/features/preview/CamoxPreview.js +100 -85
- package/dist/features/preview/components/PreviewToolbar.js +15 -11
- package/dist/features/routes/pageRuntime.js +240 -2
- package/dist/features/runtime/runtime.d.ts +30 -1
- package/dist/features/runtime/runtime.js +343 -4
- package/dist/lib/telemetry.js +24 -1
- package/package.json +4 -4
|
@@ -51,9 +51,9 @@ function pageStructureQueryFn(path, projectSlug, source) {
|
|
|
51
51
|
}
|
|
52
52
|
function usePreviewedPage() {
|
|
53
53
|
const $ = c(26);
|
|
54
|
-
if ($[0] !== "
|
|
54
|
+
if ($[0] !== "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a") {
|
|
55
55
|
for (let $i = 0; $i < 26; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
56
|
-
$[0] = "
|
|
56
|
+
$[0] = "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a";
|
|
57
57
|
}
|
|
58
58
|
const { pathname } = useLocation();
|
|
59
59
|
const queryClient = useQueryClient();
|
|
@@ -150,9 +150,9 @@ function _temp(state) {
|
|
|
150
150
|
}
|
|
151
151
|
const BlockRenderer = (t0) => {
|
|
152
152
|
const $ = c(23);
|
|
153
|
-
if ($[0] !== "
|
|
153
|
+
if ($[0] !== "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a") {
|
|
154
154
|
for (let $i = 0; $i < 23; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
155
|
-
$[0] = "
|
|
155
|
+
$[0] = "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a";
|
|
156
156
|
}
|
|
157
157
|
const { blockId, mode, showAddBlockTop, showAddBlockBottom } = t0;
|
|
158
158
|
const previewSource = useSelector(previewStore, _temp3);
|
|
@@ -295,9 +295,9 @@ const PageContent = () => {
|
|
|
295
295
|
};
|
|
296
296
|
function useHydrateDraftCache() {
|
|
297
297
|
const $ = c(7);
|
|
298
|
-
if ($[0] !== "
|
|
298
|
+
if ($[0] !== "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a") {
|
|
299
299
|
for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
300
|
-
$[0] = "
|
|
300
|
+
$[0] = "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a";
|
|
301
301
|
}
|
|
302
302
|
const isAuthenticated = useIsAuthenticated();
|
|
303
303
|
const queryClient = useQueryClient();
|
|
@@ -347,16 +347,17 @@ function useHydrateDraftCache() {
|
|
|
347
347
|
}
|
|
348
348
|
function _temp4() {}
|
|
349
349
|
const CamoxPreview = (t0) => {
|
|
350
|
-
const $ = c(
|
|
351
|
-
if ($[0] !== "
|
|
352
|
-
for (let $i = 0; $i <
|
|
353
|
-
$[0] = "
|
|
350
|
+
const $ = c(40);
|
|
351
|
+
if ($[0] !== "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a") {
|
|
352
|
+
for (let $i = 0; $i < 40; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
353
|
+
$[0] = "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a";
|
|
354
354
|
}
|
|
355
355
|
const { children } = t0;
|
|
356
356
|
const isAuthenticated = useIsAuthenticated();
|
|
357
357
|
const isEditMode = useSelector(previewStore, _temp5);
|
|
358
358
|
const isToolbarHidden = useSelector(previewStore, _temp6);
|
|
359
|
-
const
|
|
359
|
+
const isAddBlockSidebarOpen = useSelector(previewStore, _temp7);
|
|
360
|
+
const previewSource = useSelector(previewStore, _temp8);
|
|
360
361
|
const pageData = usePreviewedPage();
|
|
361
362
|
useHydrateDraftCache();
|
|
362
363
|
const hasLiveCheckpoint = pageData.page.livePublishedCheckpointId != null;
|
|
@@ -376,7 +377,7 @@ const CamoxPreview = (t0) => {
|
|
|
376
377
|
],
|
|
377
378
|
groupLabel: "Preview",
|
|
378
379
|
checkIfAvailable: () => isAuthenticated && isEditMode,
|
|
379
|
-
execute:
|
|
380
|
+
execute: _temp9,
|
|
380
381
|
shortcut: {
|
|
381
382
|
key: "Enter",
|
|
382
383
|
withMeta: true
|
|
@@ -392,7 +393,7 @@ const CamoxPreview = (t0) => {
|
|
|
392
393
|
],
|
|
393
394
|
groupLabel: "Preview",
|
|
394
395
|
checkIfAvailable: () => isAuthenticated && !isEditMode,
|
|
395
|
-
execute:
|
|
396
|
+
execute: _temp0,
|
|
396
397
|
shortcut: {
|
|
397
398
|
key: "Enter",
|
|
398
399
|
withMeta: true
|
|
@@ -408,7 +409,7 @@ const CamoxPreview = (t0) => {
|
|
|
408
409
|
],
|
|
409
410
|
groupLabel: "Preview",
|
|
410
411
|
checkIfAvailable: () => isAuthenticated && previewSource === "draft" && hasLiveCheckpoint,
|
|
411
|
-
execute:
|
|
412
|
+
execute: _temp1,
|
|
412
413
|
shortcut: {
|
|
413
414
|
key: "d",
|
|
414
415
|
withAlt: true
|
|
@@ -424,7 +425,7 @@ const CamoxPreview = (t0) => {
|
|
|
424
425
|
],
|
|
425
426
|
groupLabel: "Preview",
|
|
426
427
|
checkIfAvailable: () => isAuthenticated && previewSource === "live",
|
|
427
|
-
execute:
|
|
428
|
+
execute: _temp10,
|
|
428
429
|
shortcut: {
|
|
429
430
|
key: "d",
|
|
430
431
|
withAlt: true
|
|
@@ -435,8 +436,8 @@ const CamoxPreview = (t0) => {
|
|
|
435
436
|
label: "Clear selection",
|
|
436
437
|
aliases: ["Deselect", "Unselect"],
|
|
437
438
|
groupLabel: "Preview",
|
|
438
|
-
checkIfAvailable:
|
|
439
|
-
execute:
|
|
439
|
+
checkIfAvailable: _temp11,
|
|
440
|
+
execute: _temp12,
|
|
440
441
|
shortcut: { key: "Escape" }
|
|
441
442
|
}
|
|
442
443
|
];
|
|
@@ -447,7 +448,7 @@ const CamoxPreview = (t0) => {
|
|
|
447
448
|
return () => {
|
|
448
449
|
actionsStore.send({
|
|
449
450
|
type: "unregisterManyActions",
|
|
450
|
-
ids: actions.map(
|
|
451
|
+
ids: actions.map(_temp13)
|
|
451
452
|
});
|
|
452
453
|
};
|
|
453
454
|
};
|
|
@@ -485,57 +486,65 @@ const CamoxPreview = (t0) => {
|
|
|
485
486
|
$[10] = t4;
|
|
486
487
|
} else t4 = $[10];
|
|
487
488
|
let t5;
|
|
488
|
-
if ($[11] !== isToolbarHidden) {
|
|
489
|
-
t5 = !isToolbarHidden && /* @__PURE__ */
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
489
|
+
if ($[11] !== isAddBlockSidebarOpen || $[12] !== isToolbarHidden) {
|
|
490
|
+
t5 = !isToolbarHidden && /* @__PURE__ */ jsxs("div", {
|
|
491
|
+
className: "relative",
|
|
492
|
+
children: [/* @__PURE__ */ jsx(Navbar, {}), isAddBlockSidebarOpen && /* @__PURE__ */ jsx("div", {
|
|
493
|
+
className: "absolute inset-0 z-20",
|
|
494
|
+
style: { background: "rgba(0, 0, 0, 0.66)" },
|
|
495
|
+
onClick: _temp14
|
|
496
|
+
})]
|
|
497
|
+
});
|
|
498
|
+
$[11] = isAddBlockSidebarOpen;
|
|
499
|
+
$[12] = isToolbarHidden;
|
|
500
|
+
$[13] = t5;
|
|
501
|
+
} else t5 = $[13];
|
|
493
502
|
let t6;
|
|
494
|
-
if ($[
|
|
503
|
+
if ($[14] !== isEditMode || $[15] !== pageData.page) {
|
|
495
504
|
t6 = isEditMode && /* @__PURE__ */ jsx(LeftSidebar, { page: pageData.page });
|
|
496
|
-
$[
|
|
497
|
-
$[
|
|
498
|
-
$[
|
|
499
|
-
} else t6 = $[
|
|
505
|
+
$[14] = isEditMode;
|
|
506
|
+
$[15] = pageData.page;
|
|
507
|
+
$[16] = t6;
|
|
508
|
+
} else t6 = $[16];
|
|
500
509
|
let t7;
|
|
501
|
-
if ($[
|
|
510
|
+
if ($[17] !== hasLiveCheckpoint || $[18] !== pageData.page.status) {
|
|
502
511
|
t7 = {
|
|
503
512
|
pageStatus: pageData.page.status,
|
|
504
513
|
hasLiveVersion: hasLiveCheckpoint
|
|
505
514
|
};
|
|
506
|
-
$[
|
|
507
|
-
$[
|
|
508
|
-
$[
|
|
509
|
-
} else t7 = $[
|
|
515
|
+
$[17] = hasLiveCheckpoint;
|
|
516
|
+
$[18] = pageData.page.status;
|
|
517
|
+
$[19] = t7;
|
|
518
|
+
} else t7 = $[19];
|
|
510
519
|
let t8;
|
|
511
|
-
if ($[
|
|
520
|
+
if ($[20] !== isEditMode) {
|
|
512
521
|
t8 = isEditMode && /* @__PURE__ */ jsx("div", { style: {
|
|
513
522
|
height: "80px",
|
|
514
523
|
background: "transparent"
|
|
515
524
|
} });
|
|
516
|
-
$[
|
|
517
|
-
$[
|
|
518
|
-
} else t8 = $[
|
|
525
|
+
$[20] = isEditMode;
|
|
526
|
+
$[21] = t8;
|
|
527
|
+
} else t8 = $[21];
|
|
519
528
|
let t9;
|
|
520
|
-
if ($[
|
|
529
|
+
if ($[22] !== children || $[23] !== t7 || $[24] !== t8) {
|
|
521
530
|
t9 = /* @__PURE__ */ jsxs(PreviewPanel, {
|
|
522
531
|
toolbarProps: t7,
|
|
523
532
|
children: [children, t8]
|
|
524
533
|
});
|
|
525
|
-
$[
|
|
526
|
-
$[
|
|
527
|
-
$[
|
|
528
|
-
$[
|
|
529
|
-
} else t9 = $[
|
|
534
|
+
$[22] = children;
|
|
535
|
+
$[23] = t7;
|
|
536
|
+
$[24] = t8;
|
|
537
|
+
$[25] = t9;
|
|
538
|
+
} else t9 = $[25];
|
|
530
539
|
let t10;
|
|
531
|
-
if ($[
|
|
540
|
+
if ($[26] !== isEditMode || $[27] !== pageData.page.id) {
|
|
532
541
|
t10 = isEditMode && /* @__PURE__ */ jsx(RightSidebar, { pageId: pageData.page.id });
|
|
533
|
-
$[
|
|
534
|
-
$[
|
|
535
|
-
$[
|
|
536
|
-
} else t10 = $[
|
|
542
|
+
$[26] = isEditMode;
|
|
543
|
+
$[27] = pageData.page.id;
|
|
544
|
+
$[28] = t10;
|
|
545
|
+
} else t10 = $[28];
|
|
537
546
|
let t11;
|
|
538
|
-
if ($[
|
|
547
|
+
if ($[29] !== t10 || $[30] !== t6 || $[31] !== t9) {
|
|
539
548
|
t11 = /* @__PURE__ */ jsxs("div", {
|
|
540
549
|
className: "flex h-full flex-row items-stretch",
|
|
541
550
|
children: [
|
|
@@ -544,19 +553,19 @@ const CamoxPreview = (t0) => {
|
|
|
544
553
|
t10
|
|
545
554
|
]
|
|
546
555
|
});
|
|
547
|
-
$[
|
|
548
|
-
$[
|
|
549
|
-
$[
|
|
550
|
-
$[
|
|
551
|
-
} else t11 = $[
|
|
556
|
+
$[29] = t10;
|
|
557
|
+
$[30] = t6;
|
|
558
|
+
$[31] = t9;
|
|
559
|
+
$[32] = t11;
|
|
560
|
+
} else t11 = $[32];
|
|
552
561
|
let t12;
|
|
553
|
-
if ($[
|
|
562
|
+
if ($[33] !== isEditMode) {
|
|
554
563
|
t12 = isEditMode && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CreatePageModal, {}), /* @__PURE__ */ jsx(DraftSwitchDialog, {})] });
|
|
555
|
-
$[
|
|
556
|
-
$[
|
|
557
|
-
} else t12 = $[
|
|
564
|
+
$[33] = isEditMode;
|
|
565
|
+
$[34] = t12;
|
|
566
|
+
} else t12 = $[34];
|
|
558
567
|
let t13;
|
|
559
|
-
if ($[
|
|
568
|
+
if ($[35] !== t11 || $[36] !== t12 || $[37] !== t4 || $[38] !== t5) {
|
|
560
569
|
t13 = /* @__PURE__ */ jsxs("div", {
|
|
561
570
|
className: t4,
|
|
562
571
|
children: [
|
|
@@ -565,19 +574,19 @@ const CamoxPreview = (t0) => {
|
|
|
565
574
|
t12
|
|
566
575
|
]
|
|
567
576
|
});
|
|
568
|
-
$[
|
|
569
|
-
$[
|
|
570
|
-
$[
|
|
571
|
-
$[
|
|
572
|
-
$[
|
|
573
|
-
} else t13 = $[
|
|
577
|
+
$[35] = t11;
|
|
578
|
+
$[36] = t12;
|
|
579
|
+
$[37] = t4;
|
|
580
|
+
$[38] = t5;
|
|
581
|
+
$[39] = t13;
|
|
582
|
+
} else t13 = $[39];
|
|
574
583
|
return t13;
|
|
575
584
|
};
|
|
576
585
|
function usePreviewPagesActions() {
|
|
577
586
|
const $ = c(13);
|
|
578
|
-
if ($[0] !== "
|
|
587
|
+
if ($[0] !== "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a") {
|
|
579
588
|
for (let $i = 0; $i < 13; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
580
|
-
$[0] = "
|
|
589
|
+
$[0] = "08efc24964e6f71a0b75d26676e04d8253640e7adc88fa9c6e2293e86440c27a";
|
|
581
590
|
}
|
|
582
591
|
const navigate = useNavigate();
|
|
583
592
|
const { pathname } = useLocation();
|
|
@@ -618,8 +627,8 @@ function usePreviewPagesActions() {
|
|
|
618
627
|
label: "Create page",
|
|
619
628
|
aliases: ["New page", "Add page"],
|
|
620
629
|
groupLabel: "Preview",
|
|
621
|
-
checkIfAvailable:
|
|
622
|
-
execute:
|
|
630
|
+
checkIfAvailable: _temp15,
|
|
631
|
+
execute: _temp16
|
|
623
632
|
},
|
|
624
633
|
{
|
|
625
634
|
id: "edit-current-page",
|
|
@@ -650,14 +659,14 @@ function usePreviewPagesActions() {
|
|
|
650
659
|
groupLabel: "Preview",
|
|
651
660
|
checkIfAvailable: () => !!pages,
|
|
652
661
|
hasChildren: true,
|
|
653
|
-
execute:
|
|
662
|
+
execute: _temp17
|
|
654
663
|
},
|
|
655
664
|
...pages ? pages.map((page) => ({
|
|
656
665
|
id: `go-to-page-${page.id}`,
|
|
657
666
|
parentActionId: "go-to-page",
|
|
658
667
|
label: `Go to "${page.nickname}"`,
|
|
659
668
|
groupLabel: "Preview",
|
|
660
|
-
checkIfAvailable:
|
|
669
|
+
checkIfAvailable: _temp18,
|
|
661
670
|
execute: () => navigate({ to: page.fullPath })
|
|
662
671
|
})) : []
|
|
663
672
|
];
|
|
@@ -668,7 +677,7 @@ function usePreviewPagesActions() {
|
|
|
668
677
|
return () => {
|
|
669
678
|
actionsStore.send({
|
|
670
679
|
type: "unregisterManyActions",
|
|
671
|
-
ids: actions.map(
|
|
680
|
+
ids: actions.map(_temp19)
|
|
672
681
|
});
|
|
673
682
|
};
|
|
674
683
|
};
|
|
@@ -688,17 +697,17 @@ function usePreviewPagesActions() {
|
|
|
688
697
|
}
|
|
689
698
|
React.useEffect(t4, t5);
|
|
690
699
|
}
|
|
691
|
-
function
|
|
700
|
+
function _temp19(a) {
|
|
692
701
|
return a.id;
|
|
693
702
|
}
|
|
694
|
-
function
|
|
703
|
+
function _temp18() {
|
|
695
704
|
return true;
|
|
696
705
|
}
|
|
697
|
-
function
|
|
698
|
-
function
|
|
706
|
+
function _temp17() {}
|
|
707
|
+
function _temp16() {
|
|
699
708
|
return previewStore.send({ type: "openCreatePageModal" });
|
|
700
709
|
}
|
|
701
|
-
function
|
|
710
|
+
function _temp15() {
|
|
702
711
|
return true;
|
|
703
712
|
}
|
|
704
713
|
function _temp3(state) {
|
|
@@ -711,35 +720,41 @@ function _temp6(state_0) {
|
|
|
711
720
|
return state_0.context.isToolbarHidden;
|
|
712
721
|
}
|
|
713
722
|
function _temp7(state_1) {
|
|
714
|
-
return state_1.context.
|
|
723
|
+
return state_1.context.isAddBlockSidebarOpen;
|
|
715
724
|
}
|
|
716
|
-
function _temp8() {
|
|
717
|
-
return
|
|
725
|
+
function _temp8(state_2) {
|
|
726
|
+
return state_2.context.previewSource;
|
|
718
727
|
}
|
|
719
728
|
function _temp9() {
|
|
720
|
-
return previewStore.send({ type: "
|
|
729
|
+
return previewStore.send({ type: "exitEditMode" });
|
|
721
730
|
}
|
|
722
731
|
function _temp0() {
|
|
732
|
+
return previewStore.send({ type: "enterEditMode" });
|
|
733
|
+
}
|
|
734
|
+
function _temp1() {
|
|
723
735
|
return previewStore.send({
|
|
724
736
|
type: "setPreviewSource",
|
|
725
737
|
source: "live"
|
|
726
738
|
});
|
|
727
739
|
}
|
|
728
|
-
function
|
|
740
|
+
function _temp10() {
|
|
729
741
|
return previewStore.send({
|
|
730
742
|
type: "setPreviewSource",
|
|
731
743
|
source: "draft"
|
|
732
744
|
});
|
|
733
745
|
}
|
|
734
|
-
function
|
|
746
|
+
function _temp11() {
|
|
735
747
|
return true;
|
|
736
748
|
}
|
|
737
|
-
function
|
|
749
|
+
function _temp12() {
|
|
738
750
|
previewStore.send({ type: "clearSelection" });
|
|
739
751
|
}
|
|
740
|
-
function
|
|
752
|
+
function _temp13(a) {
|
|
741
753
|
return a.id;
|
|
742
754
|
}
|
|
755
|
+
function _temp14() {
|
|
756
|
+
return previewStore.send({ type: "closeAddBlockSidebar" });
|
|
757
|
+
}
|
|
743
758
|
|
|
744
759
|
//#endregion
|
|
745
760
|
export { CamoxPreview, PageContent, usePreviewPagesActions, usePreviewedPage };
|
|
@@ -16,17 +16,18 @@ import { Toggle } from "@camox/ui/toggle";
|
|
|
16
16
|
//#region src/features/preview/components/PreviewToolbar.tsx
|
|
17
17
|
const PreviewToolbar = (t0) => {
|
|
18
18
|
const $ = c(74);
|
|
19
|
-
if ($[0] !== "
|
|
19
|
+
if ($[0] !== "88648f3760cc744b4acae6ce6f9f000de00b69d7e7f65268463e98389ba90d90") {
|
|
20
20
|
for (let $i = 0; $i < 74; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
21
|
-
$[0] = "
|
|
21
|
+
$[0] = "88648f3760cc744b4acae6ce6f9f000de00b69d7e7f65268463e98389ba90d90";
|
|
22
22
|
}
|
|
23
23
|
const { onEditModeChange, pageStatus, hasLiveVersion } = t0;
|
|
24
24
|
const isEditMode = useSelector(previewStore, _temp);
|
|
25
25
|
const isToolbarHidden = useSelector(previewStore, _temp2);
|
|
26
|
-
const
|
|
27
|
-
const
|
|
26
|
+
const peekedBlock = useSelector(previewStore, _temp3);
|
|
27
|
+
const actions = useSelector(actionsStore, _temp4);
|
|
28
|
+
const viewportMode = useSelector(previewStore, _temp5);
|
|
28
29
|
const editModeActionId = isEditMode ? "exit-edit-mode" : "enter-edit-mode";
|
|
29
|
-
if (isToolbarHidden) return null;
|
|
30
|
+
if (isToolbarHidden || peekedBlock) return null;
|
|
30
31
|
let t1;
|
|
31
32
|
if ($[1] !== onEditModeChange) {
|
|
32
33
|
t1 = (checked) => {
|
|
@@ -251,7 +252,7 @@ const PreviewToolbar = (t0) => {
|
|
|
251
252
|
type: "button",
|
|
252
253
|
variant: "outline",
|
|
253
254
|
disabled: pageStatus === "published" || !hasLiveVersion,
|
|
254
|
-
onClick:
|
|
255
|
+
onClick: _temp6,
|
|
255
256
|
children: "View live site"
|
|
256
257
|
});
|
|
257
258
|
$[54] = hasLiveVersion;
|
|
@@ -265,7 +266,7 @@ const PreviewToolbar = (t0) => {
|
|
|
265
266
|
variant: "ghost",
|
|
266
267
|
size: "icon",
|
|
267
268
|
disabled: isEditMode,
|
|
268
|
-
onClick:
|
|
269
|
+
onClick: _temp7,
|
|
269
270
|
"aria-label": "Hide toolbar",
|
|
270
271
|
className: "text-muted-foreground"
|
|
271
272
|
});
|
|
@@ -336,15 +337,18 @@ function _temp2(state_0) {
|
|
|
336
337
|
return state_0.context.isToolbarHidden;
|
|
337
338
|
}
|
|
338
339
|
function _temp3(state_1) {
|
|
339
|
-
return state_1.context.
|
|
340
|
+
return state_1.context.peekedBlock;
|
|
340
341
|
}
|
|
341
342
|
function _temp4(state_2) {
|
|
342
|
-
return state_2.context.
|
|
343
|
+
return state_2.context.actions;
|
|
343
344
|
}
|
|
344
|
-
function _temp5() {
|
|
345
|
-
return
|
|
345
|
+
function _temp5(state_3) {
|
|
346
|
+
return state_3.context.viewportMode;
|
|
346
347
|
}
|
|
347
348
|
function _temp6() {
|
|
349
|
+
return previewStore.send({ type: "viewLivePage" });
|
|
350
|
+
}
|
|
351
|
+
function _temp7() {
|
|
348
352
|
return previewStore.send({ type: "hideToolbar" });
|
|
349
353
|
}
|
|
350
354
|
|
|
@@ -1,6 +1,244 @@
|
|
|
1
|
-
import "../../lib/telemetry.js";
|
|
1
|
+
import { trackEvent } from "../../lib/telemetry.js";
|
|
2
2
|
import { queryKeys } from "@camox/api-contract/query-keys";
|
|
3
3
|
import { createORPCClient } from "@orpc/client";
|
|
4
4
|
import { RPCLink } from "@orpc/client/fetch";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
//#region src/features/routes/pageRuntime.ts
|
|
7
|
+
function parseQuality(part) {
|
|
8
|
+
const match = part.match(/;\s*q=([0-9.]+)/);
|
|
9
|
+
return match ? parseFloat(match[1]) : 1;
|
|
10
|
+
}
|
|
11
|
+
function prefersMarkdown(accept) {
|
|
12
|
+
let markdownQ = -1;
|
|
13
|
+
let htmlQ = -1;
|
|
14
|
+
for (const part of accept.split(",")) {
|
|
15
|
+
const trimmed = part.trim();
|
|
16
|
+
if (trimmed.startsWith("text/markdown")) markdownQ = parseQuality(trimmed);
|
|
17
|
+
else if (trimmed.startsWith("text/html")) htmlQ = parseQuality(trimmed);
|
|
18
|
+
}
|
|
19
|
+
return markdownQ > 0 && markdownQ >= htmlQ;
|
|
20
|
+
}
|
|
21
|
+
function createServerApiClient(apiUrl, environmentName, options) {
|
|
22
|
+
const headers = {};
|
|
23
|
+
if (environmentName) headers["x-environment-name"] = environmentName;
|
|
24
|
+
return createORPCClient(new RPCLink({
|
|
25
|
+
url: `${apiUrl}/rpc`,
|
|
26
|
+
headers,
|
|
27
|
+
fetch: (request, init) => {
|
|
28
|
+
if (options?.authCookieHeader && request instanceof Request) request.headers.set("Better-Auth-Cookie", options.authCookieHeader);
|
|
29
|
+
return fetch(request, init);
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
function getErrorDetails(error) {
|
|
34
|
+
if (!error || typeof error !== "object") return { message: String(error) };
|
|
35
|
+
const errorRecord = error;
|
|
36
|
+
return {
|
|
37
|
+
code: typeof errorRecord.code === "string" ? errorRecord.code : void 0,
|
|
38
|
+
status: typeof errorRecord.status === "number" ? errorRecord.status : void 0,
|
|
39
|
+
message: error instanceof Error ? error.message : typeof errorRecord.message === "string" ? errorRecord.message : ""
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function isAuthSessionError(error) {
|
|
43
|
+
const { code, status, message } = getErrorDetails(error);
|
|
44
|
+
if (code === "UNAUTHORIZED" || code === "FORBIDDEN") return true;
|
|
45
|
+
if (status === 401 || status === 403) return true;
|
|
46
|
+
const lowerMessage = message.toLowerCase();
|
|
47
|
+
return lowerMessage.includes("unauthorized") || lowerMessage.includes("forbidden") || lowerMessage.includes("invalid session") || lowerMessage.includes("session expired") || lowerMessage.includes("expired session");
|
|
48
|
+
}
|
|
49
|
+
function isNotFoundError(error) {
|
|
50
|
+
const { code, status } = getErrorDetails(error);
|
|
51
|
+
return code === "NOT_FOUND" || status === 404;
|
|
52
|
+
}
|
|
53
|
+
function visitForFileIds(value, ids) {
|
|
54
|
+
if (value == null || typeof value !== "object") return;
|
|
55
|
+
if ("_fileId" in value && typeof value._fileId === "number") {
|
|
56
|
+
ids.add(value._fileId);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
for (const item of value) visitForFileIds(item, ids);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const child of Object.values(value)) visitForFileIds(child, ids);
|
|
64
|
+
}
|
|
65
|
+
function collectFileIdsFromContent(content, ids) {
|
|
66
|
+
for (const value of Object.values(content)) visitForFileIds(value, ids);
|
|
67
|
+
}
|
|
68
|
+
function seedBlockCaches(queryClient, pageData, source = "draft") {
|
|
69
|
+
const filesById = new Map(pageData.files.map((f) => [f.id, f]));
|
|
70
|
+
for (const block of pageData.blocks) {
|
|
71
|
+
const blockItems = pageData.repeatableItems.filter((i) => i.blockId === block.id);
|
|
72
|
+
const fileIds = /* @__PURE__ */ new Set();
|
|
73
|
+
collectFileIdsFromContent(block.content, fileIds);
|
|
74
|
+
for (const item of blockItems) collectFileIdsFromContent(item.content, fileIds);
|
|
75
|
+
const blockFiles = [...fileIds].map((id) => filesById.get(id)).filter((f) => f != null);
|
|
76
|
+
queryClient.setQueryData(queryKeys.blocks.get(block.id, source), {
|
|
77
|
+
block,
|
|
78
|
+
repeatableItems: blockItems,
|
|
79
|
+
files: blockFiles
|
|
80
|
+
});
|
|
81
|
+
for (const file of blockFiles) queryClient.setQueryData(queryKeys.files.get(file.id), file);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function loadCamoxPage({ apiUrl, authCookieHeader, environmentName, pathname, projectSlug, queryClient, source }) {
|
|
85
|
+
const serverApi = createServerApiClient(apiUrl, environmentName, { authCookieHeader });
|
|
86
|
+
return queryClient.ensureQueryData({
|
|
87
|
+
queryKey: queryKeys.pages.getByPath(pathname, source),
|
|
88
|
+
queryFn: async () => {
|
|
89
|
+
const [data, pagesList] = await Promise.all([serverApi.pages.getByPath({
|
|
90
|
+
path: pathname,
|
|
91
|
+
projectSlug,
|
|
92
|
+
source
|
|
93
|
+
}), serverApi.pages.listBySlug({ projectSlug }).catch(() => null)]);
|
|
94
|
+
seedBlockCaches(queryClient, data, source);
|
|
95
|
+
queryClient.setQueryData(queryKeys.pages.getByPath(pathname, source), {
|
|
96
|
+
page: data.page,
|
|
97
|
+
layout: data.layout,
|
|
98
|
+
projectName: data.projectName,
|
|
99
|
+
project: data.project
|
|
100
|
+
});
|
|
101
|
+
if (source === "live") {
|
|
102
|
+
seedBlockCaches(queryClient, data, "draft");
|
|
103
|
+
queryClient.setQueryData(queryKeys.pages.getByPath(pathname, "draft"), {
|
|
104
|
+
page: data.page,
|
|
105
|
+
layout: data.layout,
|
|
106
|
+
projectName: data.projectName,
|
|
107
|
+
project: data.project
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (pagesList) queryClient.setQueryData(queryKeys.pages.list, pagesList);
|
|
111
|
+
return {
|
|
112
|
+
page: data.page,
|
|
113
|
+
layout: data.layout,
|
|
114
|
+
projectName: data.projectName,
|
|
115
|
+
project: data.project
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
staleTime: Infinity
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function loadLivePage(options) {
|
|
122
|
+
return loadCamoxPage({
|
|
123
|
+
...options,
|
|
124
|
+
source: "live"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function buildCamoxPageLoaderData(apiUrl, page, origin) {
|
|
128
|
+
return {
|
|
129
|
+
page,
|
|
130
|
+
origin,
|
|
131
|
+
faviconUrl: `${apiUrl}/favicons/${page.project.id}?v=${page.project.updatedAt}`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function loadCamoxPageForRequest({ apiUrl, authCookieHeader, environmentName, origin, pathname, projectSlug, queryClient }) {
|
|
135
|
+
const loadOptions = {
|
|
136
|
+
apiUrl,
|
|
137
|
+
environmentName,
|
|
138
|
+
pathname,
|
|
139
|
+
projectSlug,
|
|
140
|
+
queryClient
|
|
141
|
+
};
|
|
142
|
+
if (!authCookieHeader) return {
|
|
143
|
+
data: buildCamoxPageLoaderData(apiUrl, await loadLivePage(loadOptions), origin),
|
|
144
|
+
shouldClearAuthCookie: false,
|
|
145
|
+
source: "live"
|
|
146
|
+
};
|
|
147
|
+
try {
|
|
148
|
+
return {
|
|
149
|
+
data: buildCamoxPageLoaderData(apiUrl, await loadCamoxPage({
|
|
150
|
+
...loadOptions,
|
|
151
|
+
authCookieHeader,
|
|
152
|
+
source: "draft"
|
|
153
|
+
}), origin),
|
|
154
|
+
shouldClearAuthCookie: false,
|
|
155
|
+
source: "draft"
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (!isAuthSessionError(error)) throw error;
|
|
159
|
+
console.warn("[camox] Ignoring stale camox_auth_cookie and retrying published page load.");
|
|
160
|
+
return {
|
|
161
|
+
data: buildCamoxPageLoaderData(apiUrl, await loadLivePage(loadOptions), origin),
|
|
162
|
+
shouldClearAuthCookie: true,
|
|
163
|
+
source: "live"
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function createMarkdownResponse({ apiUrl, environmentName, pathname, projectSlug, request }) {
|
|
168
|
+
if (!prefersMarkdown(request.headers.get("Accept") ?? "")) return null;
|
|
169
|
+
const api = createServerApiClient(apiUrl, environmentName);
|
|
170
|
+
const requestPathname = pathname ?? new URL(request.url).pathname;
|
|
171
|
+
try {
|
|
172
|
+
const page = await api.pages.getByPath({
|
|
173
|
+
path: requestPathname,
|
|
174
|
+
projectSlug,
|
|
175
|
+
source: "live"
|
|
176
|
+
});
|
|
177
|
+
const { markdown } = await api.blocks.getPageMarkdown({ pageId: page.page.id });
|
|
178
|
+
if (!markdown) return null;
|
|
179
|
+
trackEvent("markdown_served", {
|
|
180
|
+
pathname: requestPathname,
|
|
181
|
+
projectId: page.page.projectId,
|
|
182
|
+
projectName: page.projectName
|
|
183
|
+
});
|
|
184
|
+
return new Response(markdown, { headers: { "Content-Type": "text/markdown; charset=utf-8" } });
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function buildCamoxPageHead(camoxApp, loaderData) {
|
|
190
|
+
if (!loaderData) return {};
|
|
191
|
+
const { page, origin, faviconUrl } = loaderData;
|
|
192
|
+
const pageMetaTitle = page.page.metaTitle ?? page.page.pathSegment;
|
|
193
|
+
const meta = [];
|
|
194
|
+
let title = pageMetaTitle;
|
|
195
|
+
if (page.layout) {
|
|
196
|
+
const layout = camoxApp.getLayoutById(page.layout.layoutId);
|
|
197
|
+
if (layout) {
|
|
198
|
+
title = layout._internal.buildMetaTitle({
|
|
199
|
+
pageMetaTitle,
|
|
200
|
+
projectName: page.projectName,
|
|
201
|
+
pageFullPath: page.page.fullPath
|
|
202
|
+
});
|
|
203
|
+
meta.push({ title });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (page.page.metaDescription) meta.push({
|
|
207
|
+
name: "description",
|
|
208
|
+
content: page.page.metaDescription
|
|
209
|
+
});
|
|
210
|
+
const ogImageParams = new URLSearchParams({
|
|
211
|
+
...page.layout && { layoutId: page.layout.layoutId },
|
|
212
|
+
title: pageMetaTitle,
|
|
213
|
+
...page.page.metaDescription && { description: page.page.metaDescription },
|
|
214
|
+
...page.projectName && { projectName: page.projectName }
|
|
215
|
+
});
|
|
216
|
+
const ogImageUrl = page.page.customOgImageUrl ?? `${origin}/og?${ogImageParams.toString()}`;
|
|
217
|
+
meta.push({
|
|
218
|
+
property: "og:title",
|
|
219
|
+
content: title
|
|
220
|
+
}, {
|
|
221
|
+
property: "og:image",
|
|
222
|
+
content: ogImageUrl
|
|
223
|
+
}, {
|
|
224
|
+
property: "og:image:width",
|
|
225
|
+
content: "1200"
|
|
226
|
+
}, {
|
|
227
|
+
property: "og:image:height",
|
|
228
|
+
content: "630"
|
|
229
|
+
});
|
|
230
|
+
if (page.page.metaDescription) meta.push({
|
|
231
|
+
property: "og:description",
|
|
232
|
+
content: page.page.metaDescription
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
meta,
|
|
236
|
+
links: [{
|
|
237
|
+
rel: "icon",
|
|
238
|
+
href: faviconUrl
|
|
239
|
+
}]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
export { buildCamoxPageHead, createMarkdownResponse, createServerApiClient, isNotFoundError, loadCamoxPageForRequest };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { CamoxApp } from "../../core/createApp.js";
|
|
2
|
+
import { CamoxDocument } from "../../core/defineDocument.js";
|
|
3
|
+
import { StudioRenderInput } from "./studioApp.js";
|
|
1
4
|
import { UseHeadInput } from "unhead/types";
|
|
2
5
|
|
|
3
6
|
//#region src/features/runtime/runtime.d.ts
|
|
7
|
+
type RuntimeRouteKind = "data" | "health" | "og" | "page" | "registry" | "sitemap" | "studio" | "studio-content" | "studio-nested";
|
|
8
|
+
interface RuntimeRouteMatch {
|
|
9
|
+
kind: RuntimeRouteKind;
|
|
10
|
+
pathname: string;
|
|
11
|
+
}
|
|
4
12
|
interface LayoutIdentity {
|
|
5
13
|
blockIds: number[];
|
|
6
14
|
id: number;
|
|
@@ -21,5 +29,26 @@ interface PageRenderInput {
|
|
|
21
29
|
runtimeBasePath: string;
|
|
22
30
|
source: "live" | "draft";
|
|
23
31
|
}
|
|
32
|
+
interface RuntimeOptions {
|
|
33
|
+
apiUrl?: string;
|
|
34
|
+
authenticationUrl?: string;
|
|
35
|
+
environmentName?: string;
|
|
36
|
+
pageClientEntryUrl?: string;
|
|
37
|
+
studioClientEntryUrl?: string;
|
|
38
|
+
getCamoxApp?: () => Promise<CamoxApp>;
|
|
39
|
+
getDocument?: () => Promise<CamoxDocument>;
|
|
40
|
+
projectSlug?: string;
|
|
41
|
+
renderPage?: (input: PageRenderInput) => Promise<string>;
|
|
42
|
+
renderStudio?: (input: StudioRenderInput) => Promise<string>;
|
|
43
|
+
runtimeBasePath?: string;
|
|
44
|
+
stylesheetUrl?: string;
|
|
45
|
+
}
|
|
46
|
+
declare function getRuntimePathname(url: string, runtimeBasePath?: string): string | null;
|
|
47
|
+
declare function matchRuntimeRoute(pathname: string): RuntimeRouteMatch;
|
|
48
|
+
declare function createPageHeadInput(head: {
|
|
49
|
+
meta?: Array<Record<string, string>>;
|
|
50
|
+
links?: Array<Record<string, string>>;
|
|
51
|
+
}): UseHeadInput;
|
|
52
|
+
declare function handleCamoxRequest(request: Request, options?: RuntimeOptions): Promise<Response | null>;
|
|
24
53
|
//#endregion
|
|
25
|
-
export { PageRenderInput };
|
|
54
|
+
export { LayoutIdentity, PageRenderInput, RuntimeOptions, RuntimeRouteKind, RuntimeRouteMatch, createPageHeadInput, getRuntimePathname, handleCamoxRequest, matchRuntimeRoute };
|
|
@@ -1,8 +1,142 @@
|
|
|
1
|
-
import "../routes/pageRuntime.js";
|
|
2
|
-
import { QueryClient } from "@tanstack/react-query";
|
|
3
|
-
import "@unhead/react/server";
|
|
1
|
+
import { buildCamoxPageHead, createMarkdownResponse, createServerApiClient, isNotFoundError, loadCamoxPageForRequest } from "../routes/pageRuntime.js";
|
|
2
|
+
import { QueryClient, dehydrate } from "@tanstack/react-query";
|
|
3
|
+
import { createHead, renderSSRHead } from "@unhead/react/server";
|
|
4
4
|
|
|
5
5
|
//#region src/features/runtime/runtime.ts
|
|
6
|
+
const DEFAULT_RUNTIME_BASE_PATH = "";
|
|
7
|
+
const RUNTIME_HEALTH_PATH = "/_camox/health";
|
|
8
|
+
const RUNTIME_REGISTRY_PATH = "/_camox/registry";
|
|
9
|
+
const SERVER_AUTH_COOKIE_NAME = "camox_auth_cookie";
|
|
10
|
+
function buildClearServerAuthCookieHeader() {
|
|
11
|
+
return `${SERVER_AUTH_COOKIE_NAME}=; Path=/; SameSite=Lax; Max-Age=0`;
|
|
12
|
+
}
|
|
13
|
+
function getServerAuthCookieHeader(headers) {
|
|
14
|
+
const authCookie = (headers.get("Cookie") ?? headers.get("cookie") ?? "").split(";").map((part) => part.trim()).find((part) => part.startsWith(`${SERVER_AUTH_COOKIE_NAME}=`));
|
|
15
|
+
if (!authCookie) return "";
|
|
16
|
+
const value = authCookie.slice(18);
|
|
17
|
+
try {
|
|
18
|
+
return decodeURIComponent(value);
|
|
19
|
+
} catch {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function normalizeRuntimeBasePath(basePath) {
|
|
24
|
+
if (basePath === void 0) return DEFAULT_RUNTIME_BASE_PATH;
|
|
25
|
+
if (!basePath || basePath === "/") return "";
|
|
26
|
+
return basePath.startsWith("/") ? basePath.replace(/\/+$/, "") : `/${basePath.replace(/\/+$/, "")}`;
|
|
27
|
+
}
|
|
28
|
+
function getRuntimePathname(url, runtimeBasePath = DEFAULT_RUNTIME_BASE_PATH) {
|
|
29
|
+
const { pathname } = new URL(url);
|
|
30
|
+
const basePath = normalizeRuntimeBasePath(runtimeBasePath);
|
|
31
|
+
if (!basePath) return pathname || "/";
|
|
32
|
+
if (pathname === basePath) return "/";
|
|
33
|
+
if (!pathname.startsWith(`${basePath}/`)) return null;
|
|
34
|
+
return pathname.slice(basePath.length) || "/";
|
|
35
|
+
}
|
|
36
|
+
function escapeXml(value) {
|
|
37
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
38
|
+
}
|
|
39
|
+
function normalizePagePath(path) {
|
|
40
|
+
if (!path) return "/";
|
|
41
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
42
|
+
}
|
|
43
|
+
function getLayoutIdentity(data, source) {
|
|
44
|
+
const layout = data.page.layout;
|
|
45
|
+
if (!layout) return null;
|
|
46
|
+
const version = source === "live" ? String(layout.livePublishedCheckpointId ?? "none") : String(layout.contentUpdatedAt ?? layout.updatedAt);
|
|
47
|
+
return {
|
|
48
|
+
blockIds: [...layout.beforeBlockIds, ...layout.afterBlockIds],
|
|
49
|
+
id: layout.id,
|
|
50
|
+
layoutId: layout.layoutId,
|
|
51
|
+
version
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function matchRuntimeRoute(pathname) {
|
|
55
|
+
if (pathname === RUNTIME_HEALTH_PATH) return {
|
|
56
|
+
kind: "health",
|
|
57
|
+
pathname
|
|
58
|
+
};
|
|
59
|
+
if (pathname === RUNTIME_REGISTRY_PATH) return {
|
|
60
|
+
kind: "registry",
|
|
61
|
+
pathname
|
|
62
|
+
};
|
|
63
|
+
if (pathname === "/_camox/data") return {
|
|
64
|
+
kind: "data",
|
|
65
|
+
pathname
|
|
66
|
+
};
|
|
67
|
+
if (pathname === "/camox") return {
|
|
68
|
+
kind: "studio",
|
|
69
|
+
pathname
|
|
70
|
+
};
|
|
71
|
+
if (pathname === "/camox/content") return {
|
|
72
|
+
kind: "studio-content",
|
|
73
|
+
pathname
|
|
74
|
+
};
|
|
75
|
+
if (pathname.startsWith("/camox/")) return {
|
|
76
|
+
kind: "studio-nested",
|
|
77
|
+
pathname
|
|
78
|
+
};
|
|
79
|
+
if (pathname === "/og") return {
|
|
80
|
+
kind: "og",
|
|
81
|
+
pathname
|
|
82
|
+
};
|
|
83
|
+
if (pathname === "/sitemap.xml") return {
|
|
84
|
+
kind: "sitemap",
|
|
85
|
+
pathname
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
kind: "page",
|
|
89
|
+
pathname
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function withRuntimeBasePath(pathname, runtimeBasePath) {
|
|
93
|
+
const basePath = normalizeRuntimeBasePath(runtimeBasePath);
|
|
94
|
+
if (!basePath) return pathname;
|
|
95
|
+
if (pathname === "/") return `${basePath}/`;
|
|
96
|
+
return `${basePath}${pathname}`;
|
|
97
|
+
}
|
|
98
|
+
async function createSitemapResponse(request, options) {
|
|
99
|
+
if (!options.apiUrl || !options.projectSlug) return Response.json({ message: "Camox sitemap is not configured." }, { status: 500 });
|
|
100
|
+
const api = createServerApiClient(options.apiUrl, options.environmentName);
|
|
101
|
+
const origin = new URL(request.url).origin;
|
|
102
|
+
const entries = (await api.pages.listBySlug({ projectSlug: options.projectSlug })).map((page) => ` <url>
|
|
103
|
+
<loc>${escapeXml(`${origin}${withRuntimeBasePath(page.fullPath, options.runtimeBasePath)}`)}</loc>
|
|
104
|
+
<lastmod>${escapeXml(new Date(page.updatedAt).toISOString())}</lastmod>
|
|
105
|
+
</url>`).join("\n");
|
|
106
|
+
return new Response(`<?xml version="1.0" encoding="UTF-8"?>
|
|
107
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
108
|
+
${entries}
|
|
109
|
+
</urlset>`, { headers: { "Content-Type": "application/xml; charset=utf-8" } });
|
|
110
|
+
}
|
|
111
|
+
async function createOgResponse(request, options) {
|
|
112
|
+
const camoxApp = await options.getCamoxApp?.();
|
|
113
|
+
if (!camoxApp) return Response.json({ message: "Camox app registry is not available." }, { status: 500 });
|
|
114
|
+
const url = new URL(request.url);
|
|
115
|
+
const layoutId = url.searchParams.get("layoutId") || "";
|
|
116
|
+
const title = url.searchParams.get("title") || "";
|
|
117
|
+
const description = url.searchParams.get("description") || "";
|
|
118
|
+
const projectName = url.searchParams.get("projectName") || "";
|
|
119
|
+
const layout = camoxApp.getLayoutById(layoutId);
|
|
120
|
+
if (!layout?._internal.buildOgImage) return new Response("Not found", { status: 404 });
|
|
121
|
+
return layout._internal.buildOgImage({
|
|
122
|
+
title,
|
|
123
|
+
description,
|
|
124
|
+
projectName
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function createDefaultHeadInput(stylesheetUrl) {
|
|
128
|
+
return {
|
|
129
|
+
htmlAttrs: { lang: "en" },
|
|
130
|
+
meta: [{ charset: "utf-8" }, {
|
|
131
|
+
name: "viewport",
|
|
132
|
+
content: "width=device-width, initial-scale=1"
|
|
133
|
+
}],
|
|
134
|
+
link: stylesheetUrl ? [{
|
|
135
|
+
rel: "stylesheet",
|
|
136
|
+
href: stylesheetUrl
|
|
137
|
+
}] : []
|
|
138
|
+
};
|
|
139
|
+
}
|
|
6
140
|
function createPageHeadInput(head) {
|
|
7
141
|
const meta = head.meta ?? [];
|
|
8
142
|
const links = head.links ?? [];
|
|
@@ -39,6 +173,211 @@ function createPageHeadInput(head) {
|
|
|
39
173
|
link: pageLinks
|
|
40
174
|
};
|
|
41
175
|
}
|
|
176
|
+
function renderHead(document, pageHead, stylesheetUrl) {
|
|
177
|
+
const head = createHead();
|
|
178
|
+
head.push(createDefaultHeadInput(stylesheetUrl));
|
|
179
|
+
head.push(document);
|
|
180
|
+
head.push(pageHead);
|
|
181
|
+
return renderSSRHead(head);
|
|
182
|
+
}
|
|
183
|
+
function serializeJsonForHtml(value) {
|
|
184
|
+
return JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
185
|
+
}
|
|
186
|
+
function renderClientEntryScript(src) {
|
|
187
|
+
if (!src) return "";
|
|
188
|
+
return `<script type="module" src="${escapeXml(src)}"><\/script>`;
|
|
189
|
+
}
|
|
190
|
+
async function createPageHtmlResponse({ options, pathname, request }) {
|
|
191
|
+
if (!options.apiUrl || !options.authenticationUrl || !options.projectSlug || !options.renderPage) return createPageDataResponse({
|
|
192
|
+
options,
|
|
193
|
+
pathname,
|
|
194
|
+
request
|
|
195
|
+
});
|
|
196
|
+
const queryClient = new QueryClient();
|
|
197
|
+
try {
|
|
198
|
+
const result = await loadCamoxPageForRequest({
|
|
199
|
+
apiUrl: options.apiUrl,
|
|
200
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
201
|
+
environmentName: options.environmentName,
|
|
202
|
+
origin: new URL(request.url).origin,
|
|
203
|
+
pathname,
|
|
204
|
+
projectSlug: options.projectSlug,
|
|
205
|
+
queryClient
|
|
206
|
+
});
|
|
207
|
+
const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" });
|
|
208
|
+
if (result.shouldClearAuthCookie) headers.append("Set-Cookie", buildClearServerAuthCookieHeader());
|
|
209
|
+
const camoxApp = await options.getCamoxApp?.();
|
|
210
|
+
const document = await options.getDocument?.() ?? {};
|
|
211
|
+
const head = camoxApp ? buildCamoxPageHead(camoxApp, result.data) : {};
|
|
212
|
+
const dehydratedState = dehydrate(queryClient);
|
|
213
|
+
const pageRenderInput = {
|
|
214
|
+
apiUrl: options.apiUrl,
|
|
215
|
+
authenticationUrl: options.authenticationUrl,
|
|
216
|
+
dehydratedState,
|
|
217
|
+
environmentName: options.environmentName,
|
|
218
|
+
head,
|
|
219
|
+
href: new URL(request.url).href,
|
|
220
|
+
layoutIdentity: getLayoutIdentity(result.data, result.source),
|
|
221
|
+
loaderData: result.data,
|
|
222
|
+
pathname,
|
|
223
|
+
projectSlug: options.projectSlug,
|
|
224
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
225
|
+
source: result.source
|
|
226
|
+
};
|
|
227
|
+
const appHtml = await options.renderPage(pageRenderInput);
|
|
228
|
+
const renderedHead = renderHead(document, createPageHeadInput(head), options.stylesheetUrl);
|
|
229
|
+
return new Response(`<!doctype html>
|
|
230
|
+
<html${renderedHead.htmlAttrs}>
|
|
231
|
+
<head>
|
|
232
|
+
${renderedHead.headTags}
|
|
233
|
+
<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(pageRenderInput)}<\/script>
|
|
234
|
+
</head>
|
|
235
|
+
<body${renderedHead.bodyAttrs}>
|
|
236
|
+
${renderedHead.bodyTagsOpen}
|
|
237
|
+
<div id="root">${appHtml}</div>
|
|
238
|
+
${renderedHead.bodyTags}
|
|
239
|
+
${renderClientEntryScript(options.pageClientEntryUrl)}
|
|
240
|
+
</body>
|
|
241
|
+
</html>`, { headers });
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (isNotFoundError(error)) return new Response("No Camox page on this path", { status: 404 });
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function createStudioHtmlResponse({ match, options, request }) {
|
|
248
|
+
if (!options.apiUrl || !options.authenticationUrl || !options.projectSlug || !options.renderStudio) return Response.json({
|
|
249
|
+
message: "Camox Studio renderer is not configured.",
|
|
250
|
+
...match
|
|
251
|
+
}, { status: 500 });
|
|
252
|
+
const queryClient = new QueryClient();
|
|
253
|
+
const document = await options.getDocument?.() ?? {};
|
|
254
|
+
const studioRenderInput = {
|
|
255
|
+
apiUrl: options.apiUrl,
|
|
256
|
+
authenticationUrl: options.authenticationUrl,
|
|
257
|
+
dehydratedState: dehydrate(queryClient),
|
|
258
|
+
environmentName: options.environmentName,
|
|
259
|
+
href: new URL(request.url).href,
|
|
260
|
+
pathname: match.pathname,
|
|
261
|
+
projectSlug: options.projectSlug,
|
|
262
|
+
routeKind: match.kind,
|
|
263
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath)
|
|
264
|
+
};
|
|
265
|
+
const appHtml = await options.renderStudio(studioRenderInput);
|
|
266
|
+
const renderedHead = renderHead(document, {}, options.stylesheetUrl);
|
|
267
|
+
return new Response(`<!doctype html>
|
|
268
|
+
<html${renderedHead.htmlAttrs}>
|
|
269
|
+
<head>
|
|
270
|
+
${renderedHead.headTags}
|
|
271
|
+
<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(studioRenderInput)}<\/script>
|
|
272
|
+
</head>
|
|
273
|
+
<body${renderedHead.bodyAttrs}>
|
|
274
|
+
${renderedHead.bodyTagsOpen}
|
|
275
|
+
<div id="root">${appHtml}</div>
|
|
276
|
+
${renderedHead.bodyTags}
|
|
277
|
+
${renderClientEntryScript(options.studioClientEntryUrl)}
|
|
278
|
+
</body>
|
|
279
|
+
</html>`, { headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
280
|
+
}
|
|
281
|
+
async function createPageDataResponse({ options, pathname, request }) {
|
|
282
|
+
if (!options.apiUrl || !options.projectSlug) return Response.json({ message: "Camox page loader is not configured." }, { status: 500 });
|
|
283
|
+
const queryClient = new QueryClient();
|
|
284
|
+
try {
|
|
285
|
+
const result = await loadCamoxPageForRequest({
|
|
286
|
+
apiUrl: options.apiUrl,
|
|
287
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
288
|
+
environmentName: options.environmentName,
|
|
289
|
+
origin: new URL(request.url).origin,
|
|
290
|
+
pathname,
|
|
291
|
+
projectSlug: options.projectSlug,
|
|
292
|
+
queryClient
|
|
293
|
+
});
|
|
294
|
+
const headers = new Headers({ "Content-Type": "application/json; charset=utf-8" });
|
|
295
|
+
if (result.shouldClearAuthCookie) headers.append("Set-Cookie", buildClearServerAuthCookieHeader());
|
|
296
|
+
const camoxApp = await options.getCamoxApp?.();
|
|
297
|
+
const head = camoxApp ? buildCamoxPageHead(camoxApp, result.data) : null;
|
|
298
|
+
const dehydratedState = dehydrate(queryClient);
|
|
299
|
+
const { page, layout, projectName, project } = result.data.page;
|
|
300
|
+
return new Response(`${JSON.stringify({
|
|
301
|
+
kind: "page",
|
|
302
|
+
pathname,
|
|
303
|
+
page: {
|
|
304
|
+
id: page.id,
|
|
305
|
+
fullPath: page.fullPath,
|
|
306
|
+
metaTitle: page.metaTitle,
|
|
307
|
+
metaDescription: page.metaDescription
|
|
308
|
+
},
|
|
309
|
+
layout: layout ? {
|
|
310
|
+
id: layout.id,
|
|
311
|
+
layoutId: layout.layoutId
|
|
312
|
+
} : null,
|
|
313
|
+
layoutIdentity: getLayoutIdentity(result.data, result.source),
|
|
314
|
+
project: {
|
|
315
|
+
id: project.id,
|
|
316
|
+
name: projectName
|
|
317
|
+
},
|
|
318
|
+
loaderData: {
|
|
319
|
+
faviconUrl: result.data.faviconUrl,
|
|
320
|
+
origin: result.data.origin
|
|
321
|
+
},
|
|
322
|
+
head,
|
|
323
|
+
dehydratedState
|
|
324
|
+
}, null, 2)}\n`, { headers });
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (isNotFoundError(error)) return Response.json({
|
|
327
|
+
kind: "page",
|
|
328
|
+
message: "No Camox page on this path",
|
|
329
|
+
pathname
|
|
330
|
+
}, { status: 404 });
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function handleCamoxRequest(request, options = {}) {
|
|
335
|
+
const pathname = getRuntimePathname(request.url, options.runtimeBasePath);
|
|
336
|
+
if (!pathname) return null;
|
|
337
|
+
const match = matchRuntimeRoute(pathname);
|
|
338
|
+
if (match.kind === "health") return new Response("ok\n", { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
339
|
+
if (match.kind === "registry") {
|
|
340
|
+
const camoxApp = await options.getCamoxApp?.();
|
|
341
|
+
if (!camoxApp) return Response.json({ message: "Camox app registry is not available." }, { status: 500 });
|
|
342
|
+
return Response.json({
|
|
343
|
+
blocks: camoxApp.getBlocks().map((block) => block._internal.id),
|
|
344
|
+
layouts: camoxApp.getLayouts().map((layout) => layout._internal.id)
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
if (match.kind === "og") return createOgResponse(request, options);
|
|
348
|
+
if (match.kind === "sitemap") return createSitemapResponse(request, options);
|
|
349
|
+
if (match.kind === "data") return createPageDataResponse({
|
|
350
|
+
options,
|
|
351
|
+
pathname: normalizePagePath(new URL(request.url).searchParams.get("path")),
|
|
352
|
+
request
|
|
353
|
+
});
|
|
354
|
+
if (match.kind === "studio" || match.kind === "studio-content" || match.kind === "studio-nested") return createStudioHtmlResponse({
|
|
355
|
+
match,
|
|
356
|
+
options,
|
|
357
|
+
request
|
|
358
|
+
});
|
|
359
|
+
if (match.kind === "page") {
|
|
360
|
+
if (options.apiUrl && options.projectSlug) {
|
|
361
|
+
const markdownResponse = await createMarkdownResponse({
|
|
362
|
+
apiUrl: options.apiUrl,
|
|
363
|
+
environmentName: options.environmentName,
|
|
364
|
+
pathname: match.pathname,
|
|
365
|
+
projectSlug: options.projectSlug,
|
|
366
|
+
request
|
|
367
|
+
});
|
|
368
|
+
if (markdownResponse) return markdownResponse;
|
|
369
|
+
}
|
|
370
|
+
return createPageHtmlResponse({
|
|
371
|
+
options,
|
|
372
|
+
pathname: match.pathname,
|
|
373
|
+
request
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return Response.json({
|
|
377
|
+
message: "Camox runtime route matched.",
|
|
378
|
+
...match
|
|
379
|
+
}, { status: 501 });
|
|
380
|
+
}
|
|
42
381
|
|
|
43
382
|
//#endregion
|
|
44
|
-
export { createPageHeadInput };
|
|
383
|
+
export { createPageHeadInput, getRuntimePathname, handleCamoxRequest, matchRuntimeRoute };
|
package/dist/lib/telemetry.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
1
|
import { POSTHOG_PUBLIC_KEY } from "@camox/api-contract";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
//#region src/lib/telemetry.ts
|
|
4
|
+
let client = null;
|
|
5
|
+
async function getClient() {
|
|
6
|
+
if (__CAMOX_TELEMETRY_DISABLED__) return null;
|
|
7
|
+
if (!client) {
|
|
8
|
+
const { PostHog } = await import("posthog-node");
|
|
9
|
+
client = new PostHog(POSTHOG_PUBLIC_KEY, { host: "https://us.i.posthog.com" });
|
|
10
|
+
}
|
|
11
|
+
return client;
|
|
12
|
+
}
|
|
13
|
+
/** Server-side tracking for page views (used in pageRoute.tsx) */
|
|
14
|
+
async function trackEvent(event, properties) {
|
|
15
|
+
const posthog = await getClient();
|
|
16
|
+
if (!posthog) return;
|
|
17
|
+
const distinctId = properties.projectId ?? "unknown";
|
|
18
|
+
posthog.capture({
|
|
19
|
+
distinctId,
|
|
20
|
+
event,
|
|
21
|
+
properties
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { trackEvent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "camox",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"camox": "./bin/camox.mjs"
|
|
@@ -157,9 +157,9 @@
|
|
|
157
157
|
"streamdown": "^2.5.0",
|
|
158
158
|
"unhead": "^3.1.7",
|
|
159
159
|
"zod": "^4.4.3",
|
|
160
|
-
"@camox/
|
|
161
|
-
"@camox/
|
|
162
|
-
"@camox/ui": "0.36.
|
|
160
|
+
"@camox/cli": "0.36.1",
|
|
161
|
+
"@camox/api-contract": "0.36.1",
|
|
162
|
+
"@camox/ui": "0.36.1"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@babel/core": "^7.29.0",
|