@websline/cms-view-utils 0.26.0 → 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/editScroll.global.js +1 -0
- package/dist/previewBridge.global.js +1 -0
- package/package.json +15 -9
- package/src/components/AddBlockPlaceholder.svelte +49 -0
- package/src/components/BlockWrapper.svelte +51 -0
- package/src/components/EditableBlock.svelte +284 -0
- package/src/components/Picture.svelte +87 -0
- package/src/components/skeleton/Grid.svelte +34 -0
- package/src/components/skeleton/Image.svelte +26 -0
- package/src/components/skeleton/ImageText.svelte +50 -0
- package/src/components/skeleton/Text.svelte +38 -0
- package/src/utils/blockValidation.js +9 -0
- package/src/utils/cmsActions.js +12 -0
- package/src/utils/editScroll.js +55 -0
- package/src/utils/editorToken.js +24 -0
- package/src/utils/errors.js +10 -0
- package/src/utils/fetchPage.js +51 -0
- package/src/utils/headers.js +9 -0
- package/src/utils/notify.js +27 -0
- package/src/utils/previewBridge.js +71 -0
- package/src/utils/urlResolver.js +13 -0
- package/src/components/Block.astro +0 -26
- package/src/components/PageRenderer.astro +0 -47
- package/src/data/index.js +0 -1
- package/src/data/pageData.js +0 -46
- package/src/index.js +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{(function(){var c;if(typeof window>"u")return;let u=150,a=60,t=document.currentScript||[...document.querySelectorAll("script[data-slug]")].pop(),n=(c=t==null?void 0:t.dataset)==null?void 0:c.slug;if(!n){console.warn("[editScroll] Missing data-slug");return}let s=`preview-scroll:${n}`,r=null,l=0,i=()=>{let e=window.scrollY;e!==l&&(l=e,sessionStorage.setItem(s,String(e)))},d=()=>{clearTimeout(r),r=setTimeout(i,u)},w=()=>{let e=sessionStorage.getItem(s);if(!e)return;let o=Number(e);Number.isNaN(o)||requestAnimationFrame(()=>{window.scrollTo(0,o),setTimeout(()=>{window.scrollTo(0,o)},a)})};window.addEventListener("scroll",d,{passive:!0}),window.addEventListener("beforeunload",i),window.addEventListener("load",w)})();})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{var e={BLOCK_ADD:"BLOCK_ADD",BLOCK_DELETE:"BLOCK_DELETE",BLOCK_EDIT:"BLOCK_EDIT",BLOCK_SHOW:"BLOCK_SHOW",BLOCK_HIDE:"BLOCK_HIDE",SLUG_CHANGED:"SLUG_CHANGED",SLUG_ON_LOAD:"SLUG_ON_LOAD",IFRAME_DOM_LOADED:"IFRAME_DOM_LOADED",CURRENT_BLOCK_EDIT:"CURRENT_BLOCK_EDIT",SCROLL_TO_BLOCK:"SCROLL_TO_BLOCK"},_=(r,L={})=>{r&&window.parent!==window&&window.parent.postMessage({type:r,payload:L},"*")};(function(){if(window.parent===window||window.__PREVIEW_BRIDGE_INITIALIZED__)return;window.__PREVIEW_BRIDGE_INITIALIZED__=!0;let r=t=>t.metaKey||t.ctrlKey||t.shiftKey,L=t=>{let n=t.target.closest("a[href]");if(!n||n.target==="_blank"||r(t))return;let o=new URL(n.href,window.location.origin);o.origin===window.location.origin&&(t.preventDefault(),t.stopPropagation(),_(e.SLUG_CHANGED,{slug:o.pathname}))};document.addEventListener("click",L,!0),_(e.SLUG_ON_LOAD,{slug:window.location.pathname}),window.addEventListener("load",()=>{_(e.IFRAME_DOM_LOADED)});let c=t=>{let n=`[data-cms-block-id="${t}"]`,o=0,i=10,a=setInterval(()=>{let E=document.querySelector(n);E&&(E.scrollIntoView({behavior:"smooth",block:"center"}),clearInterval(a)),++o>=i&&clearInterval(a)},100)};window.addEventListener("message",t=>{let{type:n,payload:o}=t.data||{};if(n&&n===e.SCROLL_TO_BLOCK){let{draftBlockUuid:i}=o||{};if(!i)return;c(i)}})})();})();
|
package/package.json
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@websline/cms-view-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"exports": {
|
|
6
|
-
".": "./src/index.js"
|
|
7
|
-
},
|
|
8
5
|
"files": [
|
|
9
|
-
"src"
|
|
6
|
+
"src",
|
|
7
|
+
"dist"
|
|
10
8
|
],
|
|
9
|
+
"exports": {
|
|
10
|
+
"./components/*": "./src/components/*",
|
|
11
|
+
"./utils/*": "./src/utils/*",
|
|
12
|
+
"./preview-bridge": "./dist/previewBridge.global.js",
|
|
13
|
+
"./edit-scroll": "./dist/editScroll.global.js",
|
|
14
|
+
"./dist/*": "./dist/*"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"jsonwebtoken": "^9.0.3"
|
|
18
|
+
},
|
|
11
19
|
"peerDependencies": {
|
|
12
|
-
"
|
|
13
|
-
"svelte": "^5.34.1"
|
|
20
|
+
"svelte": "^5.0.0"
|
|
14
21
|
},
|
|
15
22
|
"scripts": {
|
|
16
|
-
"
|
|
17
|
-
"release": "pnpm run bump:minor && pnpm publish --access public --no-git-checks"
|
|
23
|
+
"build": "tsup"
|
|
18
24
|
}
|
|
19
25
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { EVENTS, notifyCMS } from "../utils/notify.js";
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
class: className,
|
|
6
|
+
color = "#000fff",
|
|
7
|
+
textColor = "#F1F1F2",
|
|
8
|
+
label = "Ersten Block hinzufügen",
|
|
9
|
+
} = $props();
|
|
10
|
+
|
|
11
|
+
const handleClick = () => {
|
|
12
|
+
notifyCMS(EVENTS.BLOCK_ADD);
|
|
13
|
+
};
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<button
|
|
17
|
+
class={`group relative block min-h-32 w-full cursor-pointer rounded-lg transition-all duration-150 ${className}`}
|
|
18
|
+
style={`--block-color: ${color}; --block-text: ${textColor};`}
|
|
19
|
+
onclick={handleClick}>
|
|
20
|
+
<div
|
|
21
|
+
class="absolute inset-0 rounded-lg border-2 border-dashed transition-all duration-150 group-hover:opacity-60"
|
|
22
|
+
style="border-color: var(--block-color); opacity: 0.25;">
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
class="absolute inset-0 rounded-lg transition-all duration-150 group-hover:opacity-10"
|
|
27
|
+
style="background: var(--block-color); opacity: 0;">
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="relative flex h-full w-full flex-col items-center justify-center gap-2">
|
|
31
|
+
<div
|
|
32
|
+
class="flex h-10 w-10 items-center justify-center rounded-full border transition-all duration-150 group-hover:scale-110"
|
|
33
|
+
style="border-color: var(--block-color); color: var(--block-color);">
|
|
34
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
|
35
|
+
<path
|
|
36
|
+
d="M12 5V19M5 12H19"
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
stroke-width="2"
|
|
39
|
+
stroke-linecap="round" />
|
|
40
|
+
</svg>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<span
|
|
44
|
+
class="text-sm transition-opacity duration-150 group-hover:opacity-100"
|
|
45
|
+
style="color: var(--block-color);">
|
|
46
|
+
{label}
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
</button>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { block, children, spacingValues = {} } = $props();
|
|
3
|
+
|
|
4
|
+
const defaultSpacingValues = {
|
|
5
|
+
bottom: {
|
|
6
|
+
none: "pb-0",
|
|
7
|
+
s: "pb-4",
|
|
8
|
+
m: "pb-12",
|
|
9
|
+
l: "pb-24",
|
|
10
|
+
xl: "pb-30",
|
|
11
|
+
},
|
|
12
|
+
top: {
|
|
13
|
+
none: "pt-0",
|
|
14
|
+
s: "pt-4",
|
|
15
|
+
m: "pt-12",
|
|
16
|
+
l: "pt-24",
|
|
17
|
+
xl: "pt-30",
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const spacingMap = $derived({
|
|
22
|
+
bottom: {
|
|
23
|
+
...defaultSpacingValues.bottom,
|
|
24
|
+
...(spacingValues?.bottom ?? {}),
|
|
25
|
+
},
|
|
26
|
+
top: {
|
|
27
|
+
...defaultSpacingValues.top,
|
|
28
|
+
...(spacingValues?.top ?? {}),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let spacingBottom = $derived(block?.meta?.spacing?.bottom);
|
|
33
|
+
let spacingTop = $derived(block?.meta?.spacing?.top);
|
|
34
|
+
|
|
35
|
+
let spacingClass = $derived(
|
|
36
|
+
[
|
|
37
|
+
spacingMap.top[spacingTop] ?? spacingMap.top.m,
|
|
38
|
+
spacingMap.bottom[spacingBottom] ?? spacingMap.bottom.m,
|
|
39
|
+
].join(" "),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
let backgroundColor = $derived(block?.meta?.background);
|
|
43
|
+
|
|
44
|
+
let style = $derived(
|
|
45
|
+
backgroundColor ? `background-color: ${backgroundColor}` : undefined,
|
|
46
|
+
);
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<div data-cms-block-wrapper class={`${spacingClass}`} {style}>
|
|
50
|
+
{@render children?.()}
|
|
51
|
+
</div>
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { EVENTS, notifyCMS } from "../utils/notify.js";
|
|
4
|
+
import { CMS_ACTIONS } from "../utils/cmsActions.js";
|
|
5
|
+
import Text from "./skeleton/Text.svelte";
|
|
6
|
+
import Image from "./skeleton/Image.svelte";
|
|
7
|
+
import Grid from "./skeleton/Grid.svelte";
|
|
8
|
+
import ImageText from "./skeleton/ImageText.svelte";
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
block,
|
|
12
|
+
children,
|
|
13
|
+
color = "#000fff",
|
|
14
|
+
isValidBlock = true,
|
|
15
|
+
position,
|
|
16
|
+
textColor = "#F1F1F2",
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
const debounce = (fn, delay = 150) => {
|
|
20
|
+
let timer;
|
|
21
|
+
|
|
22
|
+
const debounced = (...args) => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
debounced.cancel = () => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return debounced;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleClick = (action) => {
|
|
35
|
+
switch (action) {
|
|
36
|
+
case CMS_ACTIONS.add:
|
|
37
|
+
notifyCMS(EVENTS.BLOCK_ADD, { position });
|
|
38
|
+
break;
|
|
39
|
+
case CMS_ACTIONS.delete:
|
|
40
|
+
notifyCMS(EVENTS.BLOCK_DELETE, {
|
|
41
|
+
draftBlockUuid: block.uuid,
|
|
42
|
+
});
|
|
43
|
+
break;
|
|
44
|
+
case CMS_ACTIONS.edit:
|
|
45
|
+
notifyCMS(EVENTS.BLOCK_EDIT, {
|
|
46
|
+
blockIdentifier: block.identifier,
|
|
47
|
+
draftBlockUuid: block.uuid,
|
|
48
|
+
});
|
|
49
|
+
break;
|
|
50
|
+
case CMS_ACTIONS.hideBlock:
|
|
51
|
+
notifyCMS(EVENTS.BLOCK_HIDE, {
|
|
52
|
+
draftBlockUuid: block.uuid,
|
|
53
|
+
});
|
|
54
|
+
break;
|
|
55
|
+
case CMS_ACTIONS.showBlock:
|
|
56
|
+
notifyCMS(EVENTS.BLOCK_SHOW, {
|
|
57
|
+
draftBlockUuid: block.uuid,
|
|
58
|
+
});
|
|
59
|
+
break;
|
|
60
|
+
default:
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let blockVisible = $derived(block?.meta?.visibility?.enabled);
|
|
66
|
+
|
|
67
|
+
let isHovered = $state(false);
|
|
68
|
+
let isActive = $state(false);
|
|
69
|
+
let skeleton = $derived(block?.meta?.emptyOptions?.skeleton || "text");
|
|
70
|
+
|
|
71
|
+
let toolbarVisible = $derived(isHovered || isActive);
|
|
72
|
+
|
|
73
|
+
const handleLeave = () => {
|
|
74
|
+
handleEnterDebounced.cancel();
|
|
75
|
+
isHovered = false;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleEnterDebounced = debounce(() => {
|
|
79
|
+
isHovered = true;
|
|
80
|
+
}, 50);
|
|
81
|
+
|
|
82
|
+
const handler = (event) => {
|
|
83
|
+
const { type, payload } = event.data || {};
|
|
84
|
+
if (type !== EVENTS.CURRENT_BLOCK_EDIT) return;
|
|
85
|
+
|
|
86
|
+
const { draftBlockUuid } = payload || {};
|
|
87
|
+
|
|
88
|
+
isActive = draftBlockUuid === block.uuid;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
onMount(() => {
|
|
92
|
+
window.addEventListener("message", handler);
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
window.removeEventListener("message", handler);
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<div
|
|
101
|
+
class="relative min-h-13"
|
|
102
|
+
style={`--block-color: ${color}; --block-text: ${textColor};`}
|
|
103
|
+
data-cms-block-id={block.uuid}
|
|
104
|
+
data-cms-editable-block
|
|
105
|
+
onmouseenter={handleEnterDebounced}
|
|
106
|
+
onmouseleave={handleLeave}
|
|
107
|
+
role="group">
|
|
108
|
+
<div
|
|
109
|
+
class="pointer-events-none absolute inset-0 z-20 border-6 transition-opacity duration-100"
|
|
110
|
+
style="border-color: var(--block-color)"
|
|
111
|
+
class:opacity-100={toolbarVisible}
|
|
112
|
+
class:opacity-0={!toolbarVisible}>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div
|
|
116
|
+
class="absolute top-1.5 left-1/2 z-10 flex h-9 -translate-x-1/2 items-center justify-center gap-1.5 rounded-b-lg px-3 pb-1 transition-opacity duration-100 before:absolute before:top-0 before:-left-4 before:h-4 before:w-4 before:rounded-full before:bg-transparent before:shadow-[6px_-6px_0_0_var(--block-color)] before:content-[''] after:absolute after:top-0 after:-right-4 after:h-4 after:w-4 after:rounded-full after:bg-transparent after:shadow-[-6px_-6px_0_0_var(--block-color)] after:content-['']"
|
|
117
|
+
style="background: var(--block-color); color: var(--block-text);"
|
|
118
|
+
class:opacity-100={toolbarVisible}
|
|
119
|
+
class:opacity-0={!toolbarVisible}>
|
|
120
|
+
<button
|
|
121
|
+
aria-label="add block"
|
|
122
|
+
class="cursor-pointer"
|
|
123
|
+
onclick={() => handleClick(CMS_ACTIONS.add)}>
|
|
124
|
+
<svg
|
|
125
|
+
width="24"
|
|
126
|
+
height="24"
|
|
127
|
+
viewBox="0 0 24 24"
|
|
128
|
+
fill="none"
|
|
129
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
130
|
+
<path
|
|
131
|
+
d="M12.0346 4L12.0137 20M4 12H20"
|
|
132
|
+
stroke="var(--block-text)"
|
|
133
|
+
stroke-width="1.5"
|
|
134
|
+
stroke-linecap="round"
|
|
135
|
+
stroke-linejoin="round" />
|
|
136
|
+
</svg>
|
|
137
|
+
</button>
|
|
138
|
+
|
|
139
|
+
<!-- {#if blockVisible}
|
|
140
|
+
<button
|
|
141
|
+
aria-label="hide block"
|
|
142
|
+
class="cursor-pointer"
|
|
143
|
+
onclick={() => handleClick(CMS_ACTIONS.hideBlock)}>
|
|
144
|
+
<svg
|
|
145
|
+
width="24"
|
|
146
|
+
height="24"
|
|
147
|
+
viewBox="0 0 16 16"
|
|
148
|
+
fill="none"
|
|
149
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
150
|
+
<path
|
|
151
|
+
d="M7.99992 12C11.6818 12 14.6666 8 14.6666 8C14.6666 8 11.6818 4 7.99992 4C4.31802 4 1.33325 8 1.33325 8C1.33325 8 4.31802 12 7.99992 12Z"
|
|
152
|
+
stroke="var(--block-text)"
|
|
153
|
+
stroke-width="1"
|
|
154
|
+
stroke-linejoin="round" />
|
|
155
|
+
<path
|
|
156
|
+
d="M7.99992 9.66667C8.92038 9.66667 9.66658 8.92047 9.66658 8C9.66658 7.07953 8.92038 6.33333 7.99992 6.33333C7.07945 6.33333 6.33325 7.07953 6.33325 8C6.33325 8.92047 7.07945 9.66667 7.99992 9.66667Z"
|
|
157
|
+
stroke="var(--block-text)"
|
|
158
|
+
stroke-width="1"
|
|
159
|
+
stroke-linejoin="round" />
|
|
160
|
+
</svg>
|
|
161
|
+
</button>
|
|
162
|
+
{/if}
|
|
163
|
+
|
|
164
|
+
{#if !blockVisible}
|
|
165
|
+
<button
|
|
166
|
+
aria-label="show block"
|
|
167
|
+
class="cursor-pointer"
|
|
168
|
+
onclick={() => handleClick(CMS_ACTIONS.showBlock)}>
|
|
169
|
+
<svg
|
|
170
|
+
width="24"
|
|
171
|
+
height="24"
|
|
172
|
+
viewBox="0 0 16 16"
|
|
173
|
+
fill="none"
|
|
174
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
175
|
+
<path
|
|
176
|
+
d="M7.99992 12C11.6818 12 14.6666 8 14.6666 8C14.6666 8 11.6818 4 7.99992 4C4.31802 4 1.33325 8 1.33325 8C1.33325 8 4.31802 12 7.99992 12Z"
|
|
177
|
+
stroke="var(--block-text)"
|
|
178
|
+
stroke-width="1"
|
|
179
|
+
stroke-linejoin="round" />
|
|
180
|
+
<path
|
|
181
|
+
d="M7.99992 9.66667C8.92038 9.66667 9.66658 8.92047 9.66658 8C9.66658 7.07953 8.92038 6.33333 7.99992 6.33333C7.07945 6.33333 6.33325 7.07953 6.33325 8C6.33325 8.92047 7.07945 9.66667 7.99992 9.66667Z"
|
|
182
|
+
stroke="var(--block-text)"
|
|
183
|
+
stroke-width="1"
|
|
184
|
+
stroke-linejoin="round" />
|
|
185
|
+
<path
|
|
186
|
+
d="M2.66675 13.3334L13.3334 2.66671"
|
|
187
|
+
stroke="var(--block-text)"
|
|
188
|
+
stroke-width="1"
|
|
189
|
+
stroke-linecap="round" />
|
|
190
|
+
</svg>
|
|
191
|
+
</button>
|
|
192
|
+
{/if} -->
|
|
193
|
+
|
|
194
|
+
<button
|
|
195
|
+
aria-label="copy block"
|
|
196
|
+
class="cursor-pointer"
|
|
197
|
+
onclick={() => handleClick(CMS_ACTIONS.copy)}>
|
|
198
|
+
<svg
|
|
199
|
+
width="24"
|
|
200
|
+
height="24"
|
|
201
|
+
viewBox="0 0 28 28"
|
|
202
|
+
fill="none"
|
|
203
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
204
|
+
<path
|
|
205
|
+
d="M0 4C0 1.79086 1.79086 0 4 0H24C26.2091 0 28 1.79086 28 4V24C28 26.2091 26.2091 28 24 28H4C1.79086 28 0 26.2091 0 24V4Z"
|
|
206
|
+
fill="var(--block-color)"></path>
|
|
207
|
+
<path
|
|
208
|
+
d="M10 4.5H22C22.2761 4.50011 22.5 4.72392 22.5 5V19C22.5 19.2761 22.2761 19.4999 22 19.5H18.5V22.999C18.5 23.2731 18.2763 23.4999 17.9932 23.5H6.00684C5.72308 23.5 5.5 23.2754 5.5 22.999L5.50293 9.00098C5.50298 8.72676 5.72665 8.5 6.00977 8.5H9.5V5C9.5 4.72386 9.72386 4.5 10 4.5ZM6.50195 10L6.5 22V22.5H17.5V9.5H6.50293L6.50195 10ZM10.5 8.5H18.5V18.5H21.5V5.5H10.5V8.5Z"
|
|
209
|
+
stroke="var(--block-text)"></path>
|
|
210
|
+
</svg>
|
|
211
|
+
</button>
|
|
212
|
+
|
|
213
|
+
<button
|
|
214
|
+
aria-label="position block"
|
|
215
|
+
class="cursor-pointer"
|
|
216
|
+
onclick={() => handleClick(CMS_ACTIONS.position)}>
|
|
217
|
+
<svg
|
|
218
|
+
width="24"
|
|
219
|
+
height="24"
|
|
220
|
+
viewBox="0 0 24 24"
|
|
221
|
+
fill="none"
|
|
222
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
223
|
+
<path
|
|
224
|
+
d="M12.5 7L8 2.5L3.5 7M7.99585 15.5V2.5M21 17L16.5 21.5L12 17M16.4958 8.5V21.5"
|
|
225
|
+
stroke="var(--block-text)"
|
|
226
|
+
stroke-width="1.5"
|
|
227
|
+
stroke-linecap="round"
|
|
228
|
+
stroke-linejoin="round" />
|
|
229
|
+
</svg>
|
|
230
|
+
</button>
|
|
231
|
+
|
|
232
|
+
<button
|
|
233
|
+
aria-label="delete block"
|
|
234
|
+
class="cursor-pointer"
|
|
235
|
+
onclick={() => handleClick(CMS_ACTIONS.delete)}>
|
|
236
|
+
<svg
|
|
237
|
+
width="24"
|
|
238
|
+
height="24"
|
|
239
|
+
viewBox="0 0 16 16"
|
|
240
|
+
fill="none"
|
|
241
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
242
|
+
<path
|
|
243
|
+
d="M6.66667 6.66659V11.3333M9.33333 6.66659V11.3333M2 3.33325H14M3.33333 3.33325V14.6666H12.6667V3.33325H3.33333ZM5.33333 3.33325L6.42967 1.33325H9.59237L10.6667 3.33325H5.33333Z"
|
|
244
|
+
stroke="var(--block-text)"
|
|
245
|
+
stroke-width="1"
|
|
246
|
+
stroke-linecap="round"
|
|
247
|
+
stroke-linejoin="round" />
|
|
248
|
+
</svg>
|
|
249
|
+
</button>
|
|
250
|
+
|
|
251
|
+
<button
|
|
252
|
+
aria-label="edit block"
|
|
253
|
+
class="cursor-pointer"
|
|
254
|
+
onclick={() => handleClick(CMS_ACTIONS.edit)}>
|
|
255
|
+
<svg
|
|
256
|
+
width="24"
|
|
257
|
+
height="24"
|
|
258
|
+
viewBox="0 0 24 24"
|
|
259
|
+
fill="none"
|
|
260
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
261
|
+
<path
|
|
262
|
+
d="M3 22H21M5 13.5111V17.5556H8.6586L19 6.06006L15.3476 2L5 13.5111Z"
|
|
263
|
+
stroke="var(--block-text)"
|
|
264
|
+
stroke-width="1.5"
|
|
265
|
+
stroke-linecap="round"
|
|
266
|
+
stroke-linejoin="round" />
|
|
267
|
+
</svg>
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div class:opacity-100={blockVisible} class:opacity-25={!blockVisible}>
|
|
272
|
+
{#if isValidBlock}
|
|
273
|
+
{@render children?.()}
|
|
274
|
+
{:else if skeleton === "text"}
|
|
275
|
+
<Text />
|
|
276
|
+
{:else if skeleton === "image"}
|
|
277
|
+
<Image />
|
|
278
|
+
{:else if skeleton === "grid"}
|
|
279
|
+
<Grid />
|
|
280
|
+
{:else if skeleton === "image-text"}
|
|
281
|
+
<ImageText />
|
|
282
|
+
{/if}
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let {
|
|
3
|
+
className = "",
|
|
4
|
+
decoding = "async",
|
|
5
|
+
image,
|
|
6
|
+
loading = "lazy",
|
|
7
|
+
sources = {},
|
|
8
|
+
} = $props();
|
|
9
|
+
|
|
10
|
+
const data = (() => {
|
|
11
|
+
if (!image || !image.formats) {
|
|
12
|
+
return {
|
|
13
|
+
resolved: [],
|
|
14
|
+
fallbackFormat: null,
|
|
15
|
+
fallbackSrc: "",
|
|
16
|
+
alt: "",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const formats = image.formats;
|
|
21
|
+
|
|
22
|
+
const formatMap = Object.create(null);
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < formats.length; i++) {
|
|
25
|
+
const f = formats[i];
|
|
26
|
+
formatMap[f.identifier] = f;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const normalized = [];
|
|
30
|
+
|
|
31
|
+
for (const id in sources) {
|
|
32
|
+
const min = sources[id];
|
|
33
|
+
normalized.push([id, min === true ? 0 : (min ?? 0)]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (normalized.length > 1) {
|
|
37
|
+
normalized.sort((a, b) => b[1] - a[1]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const resolved = [];
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
43
|
+
const [id, min] = normalized[i];
|
|
44
|
+
const format = formatMap[id];
|
|
45
|
+
|
|
46
|
+
if (format && format.sources && format.sources.length) {
|
|
47
|
+
resolved.push({ min, format });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let fallbackFormat = null;
|
|
52
|
+
let fallbackSrc = "";
|
|
53
|
+
|
|
54
|
+
if (resolved.length) {
|
|
55
|
+
const last = resolved[resolved.length - 1];
|
|
56
|
+
|
|
57
|
+
fallbackFormat = last.format;
|
|
58
|
+
fallbackSrc = fallbackFormat.sources[0]?.url ?? "";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const alt = image.alt ?? image.title ?? "";
|
|
62
|
+
|
|
63
|
+
return { resolved, fallbackFormat, fallbackSrc, alt };
|
|
64
|
+
})();
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
{#if data.resolved.length}
|
|
68
|
+
<picture>
|
|
69
|
+
{#each data.resolved as r (r.format.identifier + "-" + r.min)}
|
|
70
|
+
{#each r.format.sources as source (source.type + source.url)}
|
|
71
|
+
<source
|
|
72
|
+
srcset={source.url}
|
|
73
|
+
type={source.type}
|
|
74
|
+
media={r.min > 0 ? `(min-width: ${r.min}px)` : undefined} />
|
|
75
|
+
{/each}
|
|
76
|
+
{/each}
|
|
77
|
+
|
|
78
|
+
<img
|
|
79
|
+
src={data.fallbackSrc}
|
|
80
|
+
alt={data.alt}
|
|
81
|
+
width={data.fallbackFormat.width}
|
|
82
|
+
height={data.fallbackFormat.height}
|
|
83
|
+
{loading}
|
|
84
|
+
{decoding}
|
|
85
|
+
class={`h-full w-full object-cover ${className}`} />
|
|
86
|
+
</picture>
|
|
87
|
+
{/if}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
const { columns = 3, gap = 2, items = 6, ratio = "4/3" } = $props();
|
|
3
|
+
|
|
4
|
+
const gridClass = $derived(
|
|
5
|
+
columns === 2 ? "grid-cols-2" : columns === 4 ? "grid-cols-4" : "grid-cols-3",
|
|
6
|
+
);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div class={`mx-auto grid max-w-220 gap-${gap} ${gridClass}`}>
|
|
10
|
+
{#each Array.from({ length: items }, (_, idx) => idx) as idx (idx)}
|
|
11
|
+
<div
|
|
12
|
+
class="relative w-full overflow-hidden bg-slate-300/60"
|
|
13
|
+
style={`aspect-ratio: ${ratio};`}>
|
|
14
|
+
<div
|
|
15
|
+
class="animate-shimmer absolute inset-0 bg-linear-to-r from-transparent via-white/60 to-transparent">
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
{/each}
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<style>
|
|
22
|
+
@keyframes shimmer {
|
|
23
|
+
0% {
|
|
24
|
+
transform: translateX(-120%);
|
|
25
|
+
}
|
|
26
|
+
100% {
|
|
27
|
+
transform: translateX(120%);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.animate-shimmer {
|
|
32
|
+
animation: shimmer 1.4s infinite;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
const { ratio = "16/9" } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div
|
|
6
|
+
class="relative mx-auto w-full max-w-220 overflow-hidden bg-slate-300/60"
|
|
7
|
+
style={`aspect-ratio: ${ratio};`}>
|
|
8
|
+
<div
|
|
9
|
+
class="animate-shimmer absolute inset-0 bg-linear-to-r from-transparent via-white/60 to-transparent">
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<style>
|
|
14
|
+
@keyframes shimmer {
|
|
15
|
+
0% {
|
|
16
|
+
transform: translateX(-120%);
|
|
17
|
+
}
|
|
18
|
+
100% {
|
|
19
|
+
transform: translateX(120%);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.animate-shimmer {
|
|
24
|
+
animation: shimmer 1.4s infinite;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
const { ratio = "1/1", reverse = false } = $props();
|
|
3
|
+
|
|
4
|
+
const widths = ["w-full", "w-11/12", "w-10/12", "w-8/12"];
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<div
|
|
8
|
+
class={`mx-auto flex max-w-220 items-center gap-6 ${
|
|
9
|
+
reverse ? "flex-row-reverse" : "flex-row"
|
|
10
|
+
}`}>
|
|
11
|
+
<div
|
|
12
|
+
class="relative w-1/2 overflow-hidden bg-slate-300/60"
|
|
13
|
+
style={`aspect-ratio: ${ratio};`}>
|
|
14
|
+
<div
|
|
15
|
+
class="animate-shimmer absolute inset-0 bg-linear-to-r from-transparent via-white/60 to-transparent">
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="w-1/2 space-y-2.5">
|
|
20
|
+
{#each Array.from({ length: 6 }, (_, idx) => idx) as idx (idx)}
|
|
21
|
+
<div
|
|
22
|
+
class={`relative overflow-hidden rounded-md ${
|
|
23
|
+
idx === 0
|
|
24
|
+
? "h-5 w-2/3 bg-slate-300/70"
|
|
25
|
+
: `h-[0.7rem] bg-slate-300/60 ${
|
|
26
|
+
idx === 3 ? "w-2/5" : widths[idx % widths.length]
|
|
27
|
+
}`
|
|
28
|
+
}`}>
|
|
29
|
+
<div
|
|
30
|
+
class="animate-shimmer absolute inset-0 bg-linear-to-r from-transparent via-white/60 to-transparent">
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
{/each}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
@keyframes shimmer {
|
|
39
|
+
0% {
|
|
40
|
+
transform: translateX(-120%);
|
|
41
|
+
}
|
|
42
|
+
100% {
|
|
43
|
+
transform: translateX(120%);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.animate-shimmer {
|
|
48
|
+
animation: shimmer 1.4s infinite;
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
const { lines = 6 } = $props();
|
|
3
|
+
|
|
4
|
+
const widths = ["w-full", "w-11/12", "w-10/12", "w-8/12", "w-9/12"];
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<div class="mx-auto max-w-220 space-y-2.5">
|
|
8
|
+
{#each Array.from({ length: lines }) as value, idx (idx)}
|
|
9
|
+
<div
|
|
10
|
+
class={`relative overflow-hidden rounded-md ${
|
|
11
|
+
idx === 0
|
|
12
|
+
? "h-5 w-2/3 bg-slate-300/70"
|
|
13
|
+
: `h-[0.7rem] bg-slate-300/60 ${
|
|
14
|
+
idx === lines - 1 ? "w-2/5" : widths[idx % widths.length]
|
|
15
|
+
}`
|
|
16
|
+
}`}
|
|
17
|
+
data-ignore={value}>
|
|
18
|
+
<div
|
|
19
|
+
class="animate-shimmer absolute inset-0 bg-linear-to-r from-transparent via-white/60 to-transparent">
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
{/each}
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
@keyframes shimmer {
|
|
27
|
+
0% {
|
|
28
|
+
transform: translateX(-120%);
|
|
29
|
+
}
|
|
30
|
+
100% {
|
|
31
|
+
transform: translateX(120%);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.animate-shimmer {
|
|
36
|
+
animation: shimmer 1.4s infinite;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const CMS_ACTIONS = {
|
|
2
|
+
add: "add-block",
|
|
3
|
+
copy: "copy-block",
|
|
4
|
+
delete: "delete-block",
|
|
5
|
+
edit: "edit-block",
|
|
6
|
+
hideBlock: "hide-block",
|
|
7
|
+
position: "position-block",
|
|
8
|
+
showBlock: "show-block",
|
|
9
|
+
unlink: "unlink-block",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { CMS_ACTIONS };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
if (typeof window === "undefined") return;
|
|
3
|
+
|
|
4
|
+
const DEBOUNCE_MS = 150;
|
|
5
|
+
const RESTORE_DELAY = 60;
|
|
6
|
+
|
|
7
|
+
const script =
|
|
8
|
+
document.currentScript ||
|
|
9
|
+
[...document.querySelectorAll("script[data-slug]")].pop();
|
|
10
|
+
|
|
11
|
+
const slug = script?.dataset?.slug;
|
|
12
|
+
|
|
13
|
+
if (!slug) {
|
|
14
|
+
console.warn("[editScroll] Missing data-slug");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const storageKey = `preview-scroll:${slug}`;
|
|
19
|
+
|
|
20
|
+
let timeout = null;
|
|
21
|
+
let lastY = 0;
|
|
22
|
+
|
|
23
|
+
const saveScroll = () => {
|
|
24
|
+
const y = window.scrollY;
|
|
25
|
+
if (y === lastY) return;
|
|
26
|
+
|
|
27
|
+
lastY = y;
|
|
28
|
+
sessionStorage.setItem(storageKey, String(y));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const onScroll = () => {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
timeout = setTimeout(saveScroll, DEBOUNCE_MS);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const restoreScroll = () => {
|
|
37
|
+
const raw = sessionStorage.getItem(storageKey);
|
|
38
|
+
if (!raw) return;
|
|
39
|
+
|
|
40
|
+
const y = Number(raw);
|
|
41
|
+
if (Number.isNaN(y)) return;
|
|
42
|
+
|
|
43
|
+
requestAnimationFrame(() => {
|
|
44
|
+
window.scrollTo(0, y);
|
|
45
|
+
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
window.scrollTo(0, y);
|
|
48
|
+
}, RESTORE_DELAY);
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
53
|
+
window.addEventListener("beforeunload", saveScroll);
|
|
54
|
+
window.addEventListener("load", restoreScroll);
|
|
55
|
+
})();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { HttpError } from "./errors.js";
|
|
3
|
+
|
|
4
|
+
const resolveDraftUuidFromToken = (editorToken) => {
|
|
5
|
+
if (!editorToken) return undefined;
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const payload = jwt.verify(editorToken, import.meta.env.CMS_EDITOR_JWT_SECRET);
|
|
9
|
+
|
|
10
|
+
if (payload.type !== "editor") {
|
|
11
|
+
throw new HttpError(401, "Invalid token type");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!payload.draftUuid) {
|
|
15
|
+
throw new HttpError(401, "Draft UUID missing in token");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return payload.draftUuid;
|
|
19
|
+
} catch {
|
|
20
|
+
throw new HttpError(401, "Invalid or expired editor token");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { resolveDraftUuidFromToken };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { resolveDraftUuidFromToken } from "./editorToken.js";
|
|
2
|
+
import { buildCmsPageUrl } from "./urlResolver.js";
|
|
3
|
+
import { buildCmsHeaders } from "./headers.js";
|
|
4
|
+
import { HttpError } from "./errors.js";
|
|
5
|
+
|
|
6
|
+
const fetchPage = async (context) => {
|
|
7
|
+
let { slug = "" } = context.params;
|
|
8
|
+
const request = context.request;
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const isCMSPreviewRoute = url.pathname.startsWith("/preview_");
|
|
11
|
+
|
|
12
|
+
if (isCMSPreviewRoute) {
|
|
13
|
+
slug = slug.replace(/^preview_/, "");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const editorToken = url.searchParams.get("editorToken");
|
|
17
|
+
const draftUuid = resolveDraftUuidFromToken(editorToken);
|
|
18
|
+
const isCMSEditRoute = editorToken && draftUuid;
|
|
19
|
+
|
|
20
|
+
const cmsUrl = buildCmsPageUrl({ slug, draftUuid });
|
|
21
|
+
|
|
22
|
+
const response = await fetch(cmsUrl, {
|
|
23
|
+
method: "GET",
|
|
24
|
+
headers: buildCmsHeaders(request),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (response.status === 404) {
|
|
28
|
+
throw new HttpError(404, "Not Found");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new HttpError(response.status, "CMS Error");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
|
|
37
|
+
context.locals.content = data?.content ?? [];
|
|
38
|
+
context.locals.navigation = data?.navigation ?? {};
|
|
39
|
+
context.locals.page = data?.page ?? {};
|
|
40
|
+
context.locals.seo = data?.seo ?? {};
|
|
41
|
+
context.locals._cmsRaw = data;
|
|
42
|
+
|
|
43
|
+
if (isCMSPreviewRoute) {
|
|
44
|
+
context.locals._preview = true;
|
|
45
|
+
}
|
|
46
|
+
if (isCMSEditRoute) {
|
|
47
|
+
context.locals._edit = true;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { fetchPage };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const buildCmsHeaders = (request) => ({
|
|
2
|
+
"accept-language": request.headers.get("accept-language") ?? "",
|
|
3
|
+
"cms-tenant": import.meta.env.CMS_TENANT,
|
|
4
|
+
"cms-token": import.meta.env.CMS_TOKEN,
|
|
5
|
+
"user-agent": request.headers.get("user-agent") ?? "",
|
|
6
|
+
referer: request.headers.get("referer") ?? "",
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export { buildCmsHeaders };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const EVENTS = {
|
|
2
|
+
BLOCK_ADD: "BLOCK_ADD",
|
|
3
|
+
BLOCK_DELETE: "BLOCK_DELETE",
|
|
4
|
+
BLOCK_EDIT: "BLOCK_EDIT",
|
|
5
|
+
BLOCK_SHOW: "BLOCK_SHOW",
|
|
6
|
+
BLOCK_HIDE: "BLOCK_HIDE",
|
|
7
|
+
SLUG_CHANGED: "SLUG_CHANGED",
|
|
8
|
+
SLUG_ON_LOAD: "SLUG_ON_LOAD",
|
|
9
|
+
IFRAME_DOM_LOADED: "IFRAME_DOM_LOADED",
|
|
10
|
+
CURRENT_BLOCK_EDIT: "CURRENT_BLOCK_EDIT",
|
|
11
|
+
SCROLL_TO_BLOCK: "SCROLL_TO_BLOCK",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const notifyCMS = (type, payload = {}) => {
|
|
15
|
+
if (!type) return;
|
|
16
|
+
if (window.parent === window) return;
|
|
17
|
+
|
|
18
|
+
window.parent.postMessage(
|
|
19
|
+
{
|
|
20
|
+
type,
|
|
21
|
+
payload,
|
|
22
|
+
},
|
|
23
|
+
"*",
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { EVENTS, notifyCMS };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { EVENTS, notifyCMS } from "./notify.js";
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
if (window.parent === window) return;
|
|
5
|
+
if (window.__PREVIEW_BRIDGE_INITIALIZED__) return;
|
|
6
|
+
|
|
7
|
+
window.__PREVIEW_BRIDGE_INITIALIZED__ = true;
|
|
8
|
+
|
|
9
|
+
const isModifiedClick = (event) => event.metaKey || event.ctrlKey || event.shiftKey;
|
|
10
|
+
|
|
11
|
+
const handleClick = (event) => {
|
|
12
|
+
const link = event.target.closest("a[href]");
|
|
13
|
+
|
|
14
|
+
if (!link) return;
|
|
15
|
+
if (link.target === "_blank") return;
|
|
16
|
+
if (isModifiedClick(event)) return;
|
|
17
|
+
|
|
18
|
+
const url = new URL(link.href, window.location.origin);
|
|
19
|
+
if (url.origin !== window.location.origin) return;
|
|
20
|
+
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
event.stopPropagation();
|
|
23
|
+
|
|
24
|
+
notifyCMS(EVENTS.SLUG_CHANGED, {
|
|
25
|
+
slug: url.pathname,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
document.addEventListener("click", handleClick, true);
|
|
30
|
+
|
|
31
|
+
notifyCMS(EVENTS.SLUG_ON_LOAD, {
|
|
32
|
+
slug: window.location.pathname,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
window.addEventListener("load", () => {
|
|
36
|
+
notifyCMS(EVENTS.IFRAME_DOM_LOADED);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const scrollToId = (id) => {
|
|
40
|
+
const selector = `[data-cms-block-id="${id}"]`;
|
|
41
|
+
|
|
42
|
+
let tries = 0;
|
|
43
|
+
const max = 10;
|
|
44
|
+
|
|
45
|
+
const interval = setInterval(() => {
|
|
46
|
+
const el = document.querySelector(selector);
|
|
47
|
+
|
|
48
|
+
if (el) {
|
|
49
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
50
|
+
clearInterval(interval);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (++tries >= max) {
|
|
54
|
+
clearInterval(interval);
|
|
55
|
+
}
|
|
56
|
+
}, 100);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
window.addEventListener("message", (event) => {
|
|
60
|
+
const { type, payload } = event.data || {};
|
|
61
|
+
|
|
62
|
+
if (!type) return;
|
|
63
|
+
|
|
64
|
+
if (type === EVENTS.SCROLL_TO_BLOCK) {
|
|
65
|
+
const { draftBlockUuid } = payload || {};
|
|
66
|
+
if (!draftBlockUuid) return;
|
|
67
|
+
|
|
68
|
+
scrollToId(draftBlockUuid);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const buildCmsPageUrl = ({ slug, draftUuid }) => {
|
|
2
|
+
const base = import.meta.env.CMS_URL;
|
|
3
|
+
|
|
4
|
+
if (draftUuid) {
|
|
5
|
+
return `${base}/api/public/pages/preview/${draftUuid}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const normalizedSlug = slug.startsWith("/") ? slug : `/${slug}`;
|
|
9
|
+
|
|
10
|
+
return `${base}/api/public/pages${normalizedSlug}`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { buildCmsPageUrl };
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
const editToken = Astro.url.searchParams.get("edit_token") || null;
|
|
3
|
-
const isPreviewMode = editToken !== null && editToken !== "";
|
|
4
|
-
|
|
5
|
-
const { class: classes, style, ...rest } = Astro.props;
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
{
|
|
9
|
-
isPreviewMode ? (
|
|
10
|
-
<div class={classes} data-edit-block {style} {...rest}>
|
|
11
|
-
<div data-edit-block-menu>
|
|
12
|
-
<div data-edit-block-item data-edit-block-item-edit>
|
|
13
|
-
Bearbeiten...
|
|
14
|
-
</div>
|
|
15
|
-
<div data-edit-block-item data-edit-block-item-delete>
|
|
16
|
-
Löschen...
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
<slot />
|
|
20
|
-
</div>
|
|
21
|
-
) : (
|
|
22
|
-
<div class={classes} {style}>
|
|
23
|
-
<slot />
|
|
24
|
-
</div>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { fetchPageData } from "../data/pageData";
|
|
3
|
-
|
|
4
|
-
const { locale, slug } = Astro.params;
|
|
5
|
-
const editToken = Astro.url.searchParams.get("edit_token") || null;
|
|
6
|
-
const isPreviewMode = editToken !== null && editToken !== "";
|
|
7
|
-
|
|
8
|
-
await fetchPageData({ editToken, locals: Astro.locals, locale, slug });
|
|
9
|
-
|
|
10
|
-
const CMS_URL = import.meta.env.PUBLIC_CMS_URL?.replace(/\/+$/, "");
|
|
11
|
-
|
|
12
|
-
const styleURL = `${CMS_URL}/api/static/styles/drag-drop-blocks.css`;
|
|
13
|
-
const scriptDragHandlerURL = `${CMS_URL}/api/static/scripts/iframeDragHandler.js`;
|
|
14
|
-
const scriptDragDropUtilsURL = `${CMS_URL}/api/static/scripts/dragDropUtils.js`;
|
|
15
|
-
|
|
16
|
-
const previewScript = `
|
|
17
|
-
import IframeDragHandler from "${scriptDragHandlerURL}";
|
|
18
|
-
import {
|
|
19
|
-
disableLinks,
|
|
20
|
-
enableEditBlocks,
|
|
21
|
-
insertDropZones,
|
|
22
|
-
} from "${scriptDragDropUtilsURL}";
|
|
23
|
-
|
|
24
|
-
new IframeDragHandler();
|
|
25
|
-
insertDropZones();
|
|
26
|
-
disableLinks();
|
|
27
|
-
enableEditBlocks();
|
|
28
|
-
|
|
29
|
-
window.CMS_URL = "${CMS_URL}";
|
|
30
|
-
`;
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
<slot />
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
isPreviewMode && (
|
|
37
|
-
<>
|
|
38
|
-
<link
|
|
39
|
-
href="https://fonts.googleapis.com/css2?family=Ancizar+Sans:ital,wght@0,100..1000;1,100..1000&display=swap"
|
|
40
|
-
rel="stylesheet"
|
|
41
|
-
/>
|
|
42
|
-
<link rel="stylesheet" href={styleURL} />
|
|
43
|
-
</>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
{isPreviewMode && <script type="module" set:html={previewScript} />}
|
package/src/data/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./pageData";
|
package/src/data/pageData.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
const fetchPageData = async ({ editToken, locals, locale, slug }) => {
|
|
2
|
-
const CMS_URL = import.meta.env.PUBLIC_CMS_URL?.replace(/\/+$/, ""); // Remove trailing slashes
|
|
3
|
-
if (!CMS_URL) throw new Error("PUBLIC_CMS_URL ist nicht gesetzt.");
|
|
4
|
-
|
|
5
|
-
const API_URL = CMS_URL + "/api/public";
|
|
6
|
-
|
|
7
|
-
const key = `__pageData__${locale}__${slug}`;
|
|
8
|
-
|
|
9
|
-
if (!locals[key]) {
|
|
10
|
-
let url = `${API_URL}/${locale}/pages/${slug}`;
|
|
11
|
-
|
|
12
|
-
if (editToken) {
|
|
13
|
-
url += `?edit_token=${editToken}`;
|
|
14
|
-
}
|
|
15
|
-
const res = await fetch(url);
|
|
16
|
-
|
|
17
|
-
if (!res.ok) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
`Fehler beim Abrufen der Seite: ${res.status} ${res.statusText}`
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
locals[key] = await res.json();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
locals.__currentPageKey = key;
|
|
27
|
-
return locals[key];
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const getPageData = (locals) => {
|
|
31
|
-
const key = locals?.__currentPageKey;
|
|
32
|
-
|
|
33
|
-
if (!key) {
|
|
34
|
-
throw new Error("locals fehlt");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!key || !locals[key]) {
|
|
38
|
-
throw new Error(
|
|
39
|
-
"getPageData() wurde aufgerufen, bevor fetchPageData() ausgeführt wurde."
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return locals[key];
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export { fetchPageData, getPageData };
|
package/src/index.js
DELETED