@xosen/site-sdk 0.0.12 → 0.0.14
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/composables/pageDataStore.d.ts +9 -0
- package/dist/composables/useDynamicLayout.d.ts +1 -17
- package/dist/index.d.ts +2 -1
- package/dist/index.js +302 -188
- package/dist/types/blocks.d.ts +3 -0
- package/dist/types/layout.d.ts +48 -0
- package/package.json +1 -1
- package/src/composables/pageDataStore.ts +24 -0
- package/src/composables/useDynamicLayout.ts +5 -22
- package/src/composables/usePageData.ts +13 -5
- package/src/index.ts +2 -1
- package/src/types/blocks.ts +3 -0
- package/src/types/layout.ts +158 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared reactive store for page data loaded via usePageData.
|
|
3
|
+
* Allows useDynamicLayout to reactively read layout from fetched pages.
|
|
4
|
+
*/
|
|
5
|
+
export declare const pageDataStore: Record<string, any>;
|
|
6
|
+
/**
|
|
7
|
+
* Initialize store with preloaded data from __SITE_DATA__.
|
|
8
|
+
*/
|
|
9
|
+
export declare function initPageDataStore(): void;
|
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
areas?: string[];
|
|
3
|
-
columns?: string;
|
|
4
|
-
rows?: string;
|
|
5
|
-
gap?: string;
|
|
6
|
-
}
|
|
7
|
-
export interface LayoutZone {
|
|
8
|
-
type?: 'slot';
|
|
9
|
-
component?: string;
|
|
10
|
-
class?: string;
|
|
11
|
-
container?: boolean;
|
|
12
|
-
containerStyle?: Record<string, string>;
|
|
13
|
-
}
|
|
14
|
-
export interface LayoutConfig {
|
|
15
|
-
grid?: Record<string, GridBreakpoint>;
|
|
16
|
-
zones: Record<string, LayoutZone>;
|
|
17
|
-
}
|
|
1
|
+
import type { LayoutConfig } from '../types/layout.js';
|
|
18
2
|
/**
|
|
19
3
|
* Generate responsive CSS for a layout config.
|
|
20
4
|
* Returns a CSS string with media queries for each breakpoint.
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export type { UsePageDataReturn } from './composables/usePageData.js';
|
|
|
8
8
|
export { useSiteComponents } from './composables/useSiteComponents.js';
|
|
9
9
|
export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
|
|
10
10
|
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
11
|
-
export type { LayoutConfig, LayoutZone, GridBreakpoint } from './
|
|
11
|
+
export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
|
|
12
|
+
export { layoutSchema, layoutPresets } from './types/layout.js';
|
|
12
13
|
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
13
14
|
export { createSiteWorker } from './worker/index.js';
|
|
14
15
|
export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
import { ref as w, onMounted as T, watch as _, computed as u, onUnmounted as N, provide as
|
|
2
|
-
import { useI18n as
|
|
3
|
-
import { useRoute as
|
|
4
|
-
function
|
|
5
|
-
const t = w(null), { locale: e } =
|
|
6
|
-
function
|
|
7
|
-
return
|
|
1
|
+
import { ref as w, onMounted as T, watch as _, computed as u, onUnmounted as N, reactive as L, provide as P } from "vue";
|
|
2
|
+
import { useI18n as S } from "vue-i18n";
|
|
3
|
+
import { useRoute as I } from "vue-router";
|
|
4
|
+
function Z(o, a) {
|
|
5
|
+
const t = w(null), { locale: e } = S(), n = window.__SITE_DATA__, s = n?.locale;
|
|
6
|
+
function r() {
|
|
7
|
+
return n && n[o] && e.value === s ? (t.value = n[o], !0) : !1;
|
|
8
8
|
}
|
|
9
|
-
async function
|
|
9
|
+
async function i() {
|
|
10
10
|
try {
|
|
11
|
-
const c = await fetch(`/api/content/${
|
|
11
|
+
const c = await fetch(`/api/content/${a}:${e.value}`);
|
|
12
12
|
c.ok && (t.value = await c.json());
|
|
13
13
|
} catch {
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
return T(() => {
|
|
17
|
-
|
|
17
|
+
r() || i();
|
|
18
18
|
}), _(e, () => {
|
|
19
|
-
|
|
19
|
+
i();
|
|
20
20
|
}), t;
|
|
21
21
|
}
|
|
22
|
-
function
|
|
23
|
-
const o = window.__SITE_DATA__,
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
return typeof
|
|
22
|
+
function q() {
|
|
23
|
+
const o = window.__SITE_DATA__, a = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => a.value?.navigation || []), n = u(() => a.value?.locales || []), s = u(() => a.value?.defaultLocale || "en"), r = u(() => a.value?.branding || {}), i = u(() => a.value?.footer || {}), c = u(() => a.value?.features || {});
|
|
24
|
+
function p(m) {
|
|
25
|
+
const g = c.value[m];
|
|
26
|
+
return typeof g == "boolean" ? g : typeof g == "object";
|
|
27
27
|
}
|
|
28
28
|
return {
|
|
29
|
-
config:
|
|
29
|
+
config: a,
|
|
30
30
|
components: t,
|
|
31
31
|
navigation: e,
|
|
32
|
-
locales:
|
|
32
|
+
locales: n,
|
|
33
33
|
defaultLocale: s,
|
|
34
|
-
branding:
|
|
35
|
-
footer:
|
|
34
|
+
branding: r,
|
|
35
|
+
footer: i,
|
|
36
36
|
features: c,
|
|
37
|
-
hasFeature:
|
|
37
|
+
hasFeature: p
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
function
|
|
41
|
-
function
|
|
40
|
+
function H(o) {
|
|
41
|
+
function a(t) {
|
|
42
42
|
const e = document.documentElement;
|
|
43
43
|
if (t.colors)
|
|
44
|
-
for (const [
|
|
45
|
-
s && e.style.setProperty(`--x-color-${
|
|
44
|
+
for (const [n, s] of Object.entries(t.colors))
|
|
45
|
+
s && e.style.setProperty(`--x-color-${D(n)}`, s);
|
|
46
46
|
t.typography?.fontFamily && e.style.setProperty("--x-font-family", t.typography.fontFamily), t.typography?.headingFont && e.style.setProperty("--x-font-heading", t.typography.headingFont), t.typography?.baseFontSize && e.style.setProperty("--x-font-size", t.typography.baseFontSize), t.shape?.borderRadius && e.style.setProperty("--x-border-radius", t.shape.borderRadius), t.shape?.maxWidth && e.style.setProperty("--x-max-width", t.shape.maxWidth);
|
|
47
47
|
}
|
|
48
48
|
T(() => {
|
|
49
49
|
if (o) {
|
|
50
|
-
|
|
50
|
+
a(o);
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
const e = window.__SITE_DATA__?.skin;
|
|
54
|
-
e && (e.colors || e.typography || e.shape) &&
|
|
54
|
+
e && (e.colors || e.typography || e.shape) && a({
|
|
55
55
|
colors: e.colors || {},
|
|
56
56
|
typography: e.typography || {},
|
|
57
57
|
shape: e.shape || {}
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function D(o) {
|
|
62
62
|
return o.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
63
63
|
}
|
|
64
|
-
function
|
|
65
|
-
const
|
|
64
|
+
function Q() {
|
|
65
|
+
const a = window.__SITE_DATA__?.apiBase || "";
|
|
66
66
|
async function t(s) {
|
|
67
|
-
const
|
|
68
|
-
if (!
|
|
69
|
-
throw new Error(`API error: ${
|
|
70
|
-
return
|
|
67
|
+
const r = await fetch(`${a}${s}`);
|
|
68
|
+
if (!r.ok)
|
|
69
|
+
throw new Error(`API error: ${r.status}`);
|
|
70
|
+
return r.json();
|
|
71
71
|
}
|
|
72
72
|
async function e() {
|
|
73
73
|
return t("/v1/billing/tariffs");
|
|
74
74
|
}
|
|
75
|
-
async function
|
|
75
|
+
async function n() {
|
|
76
76
|
return t("/v1/billing/services");
|
|
77
77
|
}
|
|
78
78
|
return {
|
|
79
79
|
get: t,
|
|
80
80
|
getTariffs: e,
|
|
81
|
-
getProducts:
|
|
81
|
+
getProducts: n
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
-
function
|
|
84
|
+
function A() {
|
|
85
85
|
try {
|
|
86
86
|
return window.self !== window.top;
|
|
87
87
|
} catch {
|
|
88
88
|
return !0;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
91
|
+
function V(o) {
|
|
92
|
+
A() && o.beforeEach((a, t) => !t.name);
|
|
93
93
|
}
|
|
94
94
|
function C() {
|
|
95
|
-
const o = w(null),
|
|
95
|
+
const o = w(null), a = w(A());
|
|
96
96
|
function t(e) {
|
|
97
|
-
const
|
|
98
|
-
|
|
97
|
+
const n = e.data;
|
|
98
|
+
n?.type === "xosen-preview-update" && n.page && (o.value = n.page, a.value = !0);
|
|
99
99
|
}
|
|
100
100
|
return T(() => {
|
|
101
101
|
window.addEventListener("message", t);
|
|
@@ -103,84 +103,94 @@ function C() {
|
|
|
103
103
|
window.removeEventListener("message", t);
|
|
104
104
|
}), {
|
|
105
105
|
previewPage: o,
|
|
106
|
-
isPreviewMode:
|
|
106
|
+
isPreviewMode: a
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
|
-
const
|
|
110
|
-
function
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
const R = /* @__PURE__ */ Symbol("pageContext"), $ = L({});
|
|
110
|
+
function j() {
|
|
111
|
+
const o = window.__SITE_DATA__;
|
|
112
|
+
if (o)
|
|
113
|
+
for (const a of Object.keys(o))
|
|
114
|
+
a.startsWith("page:") && ($[a] = o[a]);
|
|
115
|
+
}
|
|
116
|
+
j();
|
|
117
|
+
function ee(o) {
|
|
118
|
+
const { locale: a } = S(), { previewPage: t, isPreviewMode: e } = C(), n = window.__SITE_DATA__, s = w(null), r = w(!0), i = w(!1), c = u(() => typeof o == "string" ? o : o.value), p = u(() => `page:${c.value}`), m = u(() => {
|
|
119
|
+
const l = e.value ? t.value : s.value;
|
|
120
|
+
if (!l) return {};
|
|
121
|
+
const { blocks: f, meta: d, slug: W, locale: J, ...E } = l;
|
|
122
|
+
return E;
|
|
116
123
|
});
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
P(R, m);
|
|
125
|
+
const g = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : s.value?.blocks || []);
|
|
126
|
+
function b(l) {
|
|
127
|
+
s.value = l, l && ($[p.value] = l);
|
|
128
|
+
}
|
|
129
|
+
function y() {
|
|
130
|
+
const l = n?.[p.value];
|
|
131
|
+
return l && a.value === n?.locale ? (b(l), i.value = !1, v(), !0) : !1;
|
|
122
132
|
}
|
|
123
|
-
async function
|
|
124
|
-
|
|
133
|
+
async function h() {
|
|
134
|
+
r.value = !0, i.value = !1;
|
|
125
135
|
try {
|
|
126
|
-
const
|
|
127
|
-
|
|
136
|
+
const l = await fetch(`/api/content/${p.value}:${a.value}`);
|
|
137
|
+
l.ok ? (b(await l.json()), v()) : (b(null), i.value = !0);
|
|
128
138
|
} catch {
|
|
129
|
-
|
|
139
|
+
b(null), i.value = !0;
|
|
130
140
|
} finally {
|
|
131
|
-
|
|
141
|
+
r.value = !1;
|
|
132
142
|
}
|
|
133
143
|
}
|
|
134
|
-
function
|
|
144
|
+
function v() {
|
|
135
145
|
if (!s.value) return;
|
|
136
146
|
if (s.value.title) {
|
|
137
|
-
const
|
|
138
|
-
document.title = `${s.value.title}${
|
|
147
|
+
const f = n?.config?.branding, d = f?.siteName ? ` — ${f.siteName}` : "";
|
|
148
|
+
document.title = `${s.value.title}${d}`;
|
|
139
149
|
}
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
142
|
-
const
|
|
143
|
-
|
|
150
|
+
const l = s.value.meta?.description;
|
|
151
|
+
if (l) {
|
|
152
|
+
const f = document.querySelector('meta[name="description"]');
|
|
153
|
+
f && f.setAttribute("content", l);
|
|
144
154
|
}
|
|
145
155
|
}
|
|
146
|
-
return _(t, (
|
|
147
|
-
|
|
156
|
+
return _(t, (l) => {
|
|
157
|
+
l?.slug === c.value && (r.value = !1, i.value = !1, (l.title || l.blocks) && (s.value = {
|
|
148
158
|
...s.value || {},
|
|
149
|
-
...
|
|
150
|
-
...
|
|
151
|
-
},
|
|
159
|
+
...l.title ? { title: l.title } : {},
|
|
160
|
+
...l.blocks ? { blocks: l.blocks } : {}
|
|
161
|
+
}, v()));
|
|
152
162
|
}), T(() => {
|
|
153
|
-
|
|
154
|
-
}), _(
|
|
155
|
-
|
|
163
|
+
y() ? r.value = !1 : h();
|
|
164
|
+
}), _(a, () => h()), _(c, () => {
|
|
165
|
+
y() ? r.value = !1 : h();
|
|
156
166
|
}), {
|
|
157
167
|
page: s,
|
|
158
|
-
blocks:
|
|
159
|
-
pageContext:
|
|
160
|
-
loading:
|
|
161
|
-
notFound:
|
|
168
|
+
blocks: g,
|
|
169
|
+
pageContext: m,
|
|
170
|
+
loading: r,
|
|
171
|
+
notFound: i,
|
|
162
172
|
isPreviewMode: e,
|
|
163
|
-
reload:
|
|
173
|
+
reload: h
|
|
164
174
|
};
|
|
165
175
|
}
|
|
166
|
-
function
|
|
167
|
-
const { locale: o } =
|
|
168
|
-
async function
|
|
169
|
-
if (
|
|
170
|
-
e.value = { ...
|
|
176
|
+
function te() {
|
|
177
|
+
const { locale: o } = S(), a = window.__SITE_DATA__, t = a?.locale, e = w({ ...a?.components || {} });
|
|
178
|
+
async function n(r) {
|
|
179
|
+
if (r === t) {
|
|
180
|
+
e.value = { ...a?.components || {} };
|
|
171
181
|
return;
|
|
172
182
|
}
|
|
173
183
|
try {
|
|
174
|
-
const
|
|
175
|
-
|
|
184
|
+
const i = await fetch(`/api/content/components:${r}`);
|
|
185
|
+
i.ok && (e.value = await i.json());
|
|
176
186
|
} catch {
|
|
177
187
|
}
|
|
178
188
|
}
|
|
179
|
-
_(o, (
|
|
180
|
-
o.value !== t &&
|
|
189
|
+
_(o, (r) => n(r)), T(() => {
|
|
190
|
+
o.value !== t && n(o.value);
|
|
181
191
|
});
|
|
182
|
-
function s(
|
|
183
|
-
return u(() => e.value[
|
|
192
|
+
function s(r) {
|
|
193
|
+
return u(() => e.value[r] || null);
|
|
184
194
|
}
|
|
185
195
|
return {
|
|
186
196
|
/** All components (reactive, locale-aware) */
|
|
@@ -188,32 +198,32 @@ function Q() {
|
|
|
188
198
|
/** Get a single component by key */
|
|
189
199
|
getComponent: s,
|
|
190
200
|
/** Force refetch for current locale */
|
|
191
|
-
reload: () =>
|
|
201
|
+
reload: () => n(o.value)
|
|
192
202
|
};
|
|
193
203
|
}
|
|
194
|
-
function
|
|
195
|
-
const { locale: o } =
|
|
196
|
-
function s(
|
|
197
|
-
o.value =
|
|
204
|
+
function ae() {
|
|
205
|
+
const { locale: o } = S(), a = window.__SITE_DATA__, t = u(() => a?.config?.locales || []), e = u(() => a?.config?.defaultLocale || "en"), n = u(() => t.value.length > 1);
|
|
206
|
+
function s(i) {
|
|
207
|
+
o.value = i, localStorage.setItem("x-site-locale", i);
|
|
198
208
|
}
|
|
199
|
-
function i
|
|
200
|
-
return typeof
|
|
209
|
+
function r(i) {
|
|
210
|
+
return typeof i == "string" ? i : i[o.value] || i[e.value] || Object.values(i)[0] || "";
|
|
201
211
|
}
|
|
202
212
|
return {
|
|
203
213
|
locale: o,
|
|
204
214
|
locales: t,
|
|
205
215
|
defaultLocale: e,
|
|
206
|
-
hasMultipleLocales:
|
|
216
|
+
hasMultipleLocales: n,
|
|
207
217
|
switchLocale: s,
|
|
208
|
-
resolveText:
|
|
218
|
+
resolveText: r
|
|
209
219
|
};
|
|
210
220
|
}
|
|
211
|
-
const
|
|
221
|
+
const O = {
|
|
212
222
|
sm: 600,
|
|
213
223
|
md: 960,
|
|
214
224
|
lg: 1280,
|
|
215
225
|
xl: 1920
|
|
216
|
-
},
|
|
226
|
+
}, z = {
|
|
217
227
|
grid: {
|
|
218
228
|
default: {
|
|
219
229
|
areas: ["header", "main", "footer"],
|
|
@@ -225,7 +235,7 @@ const j = {
|
|
|
225
235
|
main: { type: "slot" },
|
|
226
236
|
footer: { component: "footer" }
|
|
227
237
|
}
|
|
228
|
-
},
|
|
238
|
+
}, F = {
|
|
229
239
|
grid: {
|
|
230
240
|
default: {
|
|
231
241
|
areas: ["main"]
|
|
@@ -235,75 +245,177 @@ const j = {
|
|
|
235
245
|
main: { type: "slot" }
|
|
236
246
|
}
|
|
237
247
|
};
|
|
238
|
-
function
|
|
248
|
+
function k(o, a) {
|
|
239
249
|
const t = [];
|
|
240
250
|
if (o.areas?.length) {
|
|
241
|
-
const e = o.areas.map((
|
|
251
|
+
const e = o.areas.map((n) => `"${n}"`).join(" ");
|
|
242
252
|
t.push(`grid-template-areas: ${e}`);
|
|
243
253
|
}
|
|
244
|
-
return o.columns && t.push(`grid-template-columns: ${o.columns}`), o.rows && t.push(`grid-template-rows: ${o.rows}`), o.gap && t.push(`gap: ${o.gap}`), t.length ? `${
|
|
254
|
+
return o.columns && t.push(`grid-template-columns: ${o.columns}`), o.rows && t.push(`grid-template-rows: ${o.rows}`), o.gap && t.push(`gap: ${o.gap}`), t.length ? `${a} { ${t.join("; ")}; }` : "";
|
|
245
255
|
}
|
|
246
|
-
function
|
|
247
|
-
const t = [`${
|
|
256
|
+
function G(o, a) {
|
|
257
|
+
const t = [`${a} { display: grid; min-height: 100vh; }`], e = o.grid;
|
|
248
258
|
if (!e) return t.join(`
|
|
249
259
|
`);
|
|
250
260
|
if (e.default) {
|
|
251
|
-
const
|
|
252
|
-
|
|
261
|
+
const n = k(e.default, a);
|
|
262
|
+
n && t.push(n);
|
|
253
263
|
}
|
|
254
|
-
for (const
|
|
255
|
-
const s = e[
|
|
264
|
+
for (const n of ["sm", "md", "lg", "xl"]) {
|
|
265
|
+
const s = e[n];
|
|
256
266
|
if (!s) continue;
|
|
257
|
-
const
|
|
258
|
-
|
|
267
|
+
const r = k(s, a);
|
|
268
|
+
r && t.push(`@media (min-width: ${O[n]}px) { ${r} }`);
|
|
259
269
|
}
|
|
260
270
|
return t.join(`
|
|
261
271
|
`);
|
|
262
272
|
}
|
|
263
|
-
function
|
|
264
|
-
const o =
|
|
273
|
+
function oe() {
|
|
274
|
+
const o = I(), { previewPage: a, isPreviewMode: t } = C(), e = window.__SITE_DATA__, n = `dl-${Math.random().toString(36).slice(2, 8)}`, s = u(() => ["dynamic-layout", n]), r = u(() => {
|
|
265
275
|
if (o.path === "/_preview_")
|
|
266
|
-
return
|
|
267
|
-
if (t.value &&
|
|
268
|
-
return
|
|
269
|
-
const
|
|
270
|
-
return e?.[
|
|
271
|
-
}),
|
|
272
|
-
const
|
|
273
|
-
if (
|
|
274
|
-
const
|
|
275
|
-
if (
|
|
276
|
+
return a.value?.layout || "landing";
|
|
277
|
+
if (t.value && a.value?.layout)
|
|
278
|
+
return a.value.layout;
|
|
279
|
+
const p = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
|
|
280
|
+
return ($[p] || e?.[p])?.layout || "default";
|
|
281
|
+
}), i = u(() => {
|
|
282
|
+
const p = e?.config?.layouts;
|
|
283
|
+
if (p) {
|
|
284
|
+
const m = p[r.value] || p.default;
|
|
285
|
+
if (m?.zones) return m;
|
|
276
286
|
}
|
|
277
|
-
return
|
|
278
|
-
}), c = u(() =>
|
|
287
|
+
return r.value === "landing" ? F : z;
|
|
288
|
+
}), c = u(() => G(i.value, `.${n}`));
|
|
279
289
|
return {
|
|
280
|
-
layoutName:
|
|
281
|
-
layoutConfig:
|
|
290
|
+
layoutName: r,
|
|
291
|
+
layoutConfig: i,
|
|
282
292
|
layoutCSS: c,
|
|
283
293
|
layoutClass: s
|
|
284
294
|
};
|
|
285
295
|
}
|
|
286
|
-
const
|
|
287
|
-
|
|
296
|
+
const ne = {
|
|
297
|
+
type: "layout",
|
|
298
|
+
displayName: "Layout",
|
|
299
|
+
description: "CSS Grid layout with responsive breakpoints and component zones",
|
|
300
|
+
fields: [
|
|
301
|
+
// Mobile (default)
|
|
302
|
+
{ key: "grid.default.areas", type: "grid-areas", label: "Grid Areas (mobile)", breakpoint: "default", required: !0 },
|
|
303
|
+
{ key: "grid.default.columns", type: "text", label: "Columns (mobile)", breakpoint: "default" },
|
|
304
|
+
{ key: "grid.default.rows", type: "text", label: "Rows (mobile)", breakpoint: "default" },
|
|
305
|
+
{ key: "grid.default.gap", type: "text", label: "Gap (mobile)", breakpoint: "default" },
|
|
306
|
+
// Tablet (sm — 600px)
|
|
307
|
+
{ key: "grid.sm.areas", type: "grid-areas", label: "Grid Areas (sm 600px+)", breakpoint: "sm" },
|
|
308
|
+
{ key: "grid.sm.columns", type: "text", label: "Columns (sm)", breakpoint: "sm" },
|
|
309
|
+
{ key: "grid.sm.rows", type: "text", label: "Rows (sm)", breakpoint: "sm" },
|
|
310
|
+
{ key: "grid.sm.gap", type: "text", label: "Gap (sm)", breakpoint: "sm" },
|
|
311
|
+
// Desktop (md — 960px)
|
|
312
|
+
{ key: "grid.md.areas", type: "grid-areas", label: "Grid Areas (md 960px+)", breakpoint: "md" },
|
|
313
|
+
{ key: "grid.md.columns", type: "text", label: "Columns (md)", breakpoint: "md" },
|
|
314
|
+
{ key: "grid.md.rows", type: "text", label: "Rows (md)", breakpoint: "md" },
|
|
315
|
+
{ key: "grid.md.gap", type: "text", label: "Gap (md)", breakpoint: "md" },
|
|
316
|
+
// Large desktop (lg — 1280px)
|
|
317
|
+
{ key: "grid.lg.areas", type: "grid-areas", label: "Grid Areas (lg 1280px+)", breakpoint: "lg" },
|
|
318
|
+
{ key: "grid.lg.columns", type: "text", label: "Columns (lg)", breakpoint: "lg" },
|
|
319
|
+
{ key: "grid.lg.rows", type: "text", label: "Rows (lg)", breakpoint: "lg" },
|
|
320
|
+
{ key: "grid.lg.gap", type: "text", label: "Gap (lg)", breakpoint: "lg" },
|
|
321
|
+
// Zones
|
|
322
|
+
{ key: "zones", type: "zone-list", label: "Zones", required: !0 }
|
|
323
|
+
],
|
|
324
|
+
zoneFields: [
|
|
325
|
+
{ key: "type", type: "select", label: "Type", options: ["slot", "component"] },
|
|
326
|
+
{ key: "component", type: "text", label: "Component Key" },
|
|
327
|
+
{ key: "class", type: "text", label: "CSS Class" },
|
|
328
|
+
{ key: "container", type: "switch", label: "Wrap in Container" }
|
|
329
|
+
]
|
|
330
|
+
}, se = {
|
|
331
|
+
default: {
|
|
332
|
+
displayName: "Full Width",
|
|
333
|
+
config: {
|
|
334
|
+
grid: {
|
|
335
|
+
default: { areas: ["header", "main", "footer"], rows: "auto 1fr auto" }
|
|
336
|
+
},
|
|
337
|
+
zones: {
|
|
338
|
+
header: { component: "nav-menu" },
|
|
339
|
+
main: { type: "slot" },
|
|
340
|
+
footer: { component: "footer" }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
sidebar: {
|
|
345
|
+
displayName: "Sidebar",
|
|
346
|
+
config: {
|
|
347
|
+
grid: {
|
|
348
|
+
default: { areas: ["header", "main", "sidebar", "footer"], columns: "1fr", rows: "auto 1fr auto auto" },
|
|
349
|
+
md: { areas: ["header header", "sidebar main", "footer footer"], columns: "280px 1fr", rows: "auto 1fr auto" }
|
|
350
|
+
},
|
|
351
|
+
zones: {
|
|
352
|
+
header: { component: "nav-menu" },
|
|
353
|
+
sidebar: { component: "sidebar-menu" },
|
|
354
|
+
main: { type: "slot" },
|
|
355
|
+
footer: { component: "footer" }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
article: {
|
|
360
|
+
displayName: "Article",
|
|
361
|
+
config: {
|
|
362
|
+
grid: {
|
|
363
|
+
default: { areas: ["header", "main", "footer"], rows: "auto 1fr auto" }
|
|
364
|
+
},
|
|
365
|
+
zones: {
|
|
366
|
+
header: { component: "nav-menu" },
|
|
367
|
+
main: { type: "slot", container: !0, containerStyle: { "max-width": "900px" } },
|
|
368
|
+
footer: { component: "footer" }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
landing: {
|
|
373
|
+
displayName: "Landing (no nav)",
|
|
374
|
+
config: {
|
|
375
|
+
grid: {
|
|
376
|
+
default: { areas: ["main"] }
|
|
377
|
+
},
|
|
378
|
+
zones: {
|
|
379
|
+
main: { type: "slot" }
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
magazine: {
|
|
384
|
+
displayName: "Magazine",
|
|
385
|
+
config: {
|
|
386
|
+
grid: {
|
|
387
|
+
default: { areas: ["header", "main", "aside", "footer"], columns: "1fr", rows: "auto 1fr auto auto" },
|
|
388
|
+
md: { areas: ["header header header", "main main aside", "footer footer footer"], columns: "1fr 1fr 300px", rows: "auto 1fr auto", gap: "24px" }
|
|
389
|
+
},
|
|
390
|
+
zones: {
|
|
391
|
+
header: { component: "nav-menu" },
|
|
392
|
+
main: { type: "slot" },
|
|
393
|
+
aside: { component: "sidebar-menu" },
|
|
394
|
+
footer: { component: "footer" }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}, M = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
|
|
399
|
+
function U(o, a) {
|
|
288
400
|
const e = new URL(o.url).searchParams.get("lang");
|
|
289
|
-
if (e &&
|
|
290
|
-
if (
|
|
291
|
-
const
|
|
292
|
-
for (const s of
|
|
293
|
-
if (
|
|
401
|
+
if (e && a.supportedLocales?.includes(e)) return e;
|
|
402
|
+
if (a.supportedLocales?.length) {
|
|
403
|
+
const n = o.headers.get("Accept-Language") || "";
|
|
404
|
+
for (const s of a.supportedLocales)
|
|
405
|
+
if (n.includes(s)) return s;
|
|
294
406
|
}
|
|
295
|
-
return
|
|
407
|
+
return a.defaultLocale;
|
|
296
408
|
}
|
|
297
|
-
function
|
|
409
|
+
function K(o, a, t) {
|
|
298
410
|
if (!o.pathname.startsWith("/api/content/")) return null;
|
|
299
411
|
const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
|
|
300
412
|
return e ? (async () => {
|
|
301
|
-
let
|
|
302
|
-
if (!
|
|
413
|
+
let n = await a.SITE_CONTENT.get(e);
|
|
414
|
+
if (!n) {
|
|
303
415
|
const s = e.lastIndexOf(":");
|
|
304
|
-
s > 0 && e.slice(s + 1) !== t && (
|
|
416
|
+
s > 0 && e.slice(s + 1) !== t && (n = await a.SITE_CONTENT.get(e.slice(0, s + 1) + t));
|
|
305
417
|
}
|
|
306
|
-
return
|
|
418
|
+
return n ? new Response(n, {
|
|
307
419
|
headers: {
|
|
308
420
|
"Content-Type": "application/json",
|
|
309
421
|
"Cache-Control": "public, max-age=60",
|
|
@@ -312,53 +424,53 @@ function U(o, n, t) {
|
|
|
312
424
|
}) : Response.json({ error: "Not found" }, { status: 404 });
|
|
313
425
|
})() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
|
|
314
426
|
}
|
|
315
|
-
function
|
|
427
|
+
function x(o, a, t) {
|
|
316
428
|
let e = o;
|
|
317
|
-
if (
|
|
318
|
-
const
|
|
319
|
-
e = e.replace(/<title>[^<]*<\/title>/, `<title>${
|
|
429
|
+
if (a.title) {
|
|
430
|
+
const n = t ? ` — ${t}` : "";
|
|
431
|
+
e = e.replace(/<title>[^<]*<\/title>/, `<title>${a.title}${n}</title>`);
|
|
320
432
|
}
|
|
321
|
-
return
|
|
433
|
+
return a.meta?.description && (e = e.replace(/(<meta\s+name="description"\s+content=")[^"]*(")/, `$1${a.meta.description}$2`)), a.meta?.ogTitle && (e = e.replace(/(<meta\s+property="og:title"\s+content=")[^"]*(")/, `$1${a.meta.ogTitle}$2`)), a.html && (e = e.replace('<div id="app"></div>', `<div id="app">${a.html}</div>`)), e;
|
|
322
434
|
}
|
|
323
|
-
function
|
|
435
|
+
function re(o) {
|
|
324
436
|
return {
|
|
325
|
-
async fetch(
|
|
326
|
-
const e = new URL(
|
|
327
|
-
if (
|
|
437
|
+
async fetch(a, t) {
|
|
438
|
+
const e = new URL(a.url), n = K(e, t, o.defaultLocale);
|
|
439
|
+
if (n) return n;
|
|
328
440
|
if (M.test(e.pathname))
|
|
329
|
-
return t.ASSETS.fetch(
|
|
330
|
-
const s =
|
|
331
|
-
let c = await (await t.ASSETS.fetch(new Request(
|
|
332
|
-
const
|
|
441
|
+
return t.ASSETS.fetch(a);
|
|
442
|
+
const s = U(a, o), r = new URL("/index.html", a.url);
|
|
443
|
+
let c = await (await t.ASSETS.fetch(new Request(r))).text();
|
|
444
|
+
const p = o.defaultLocale, [m, g] = await Promise.all([
|
|
333
445
|
t.SITE_CONTENT.get("config"),
|
|
334
446
|
t.SITE_CONTENT.get(`content:${s}`)
|
|
335
|
-
]),
|
|
336
|
-
let
|
|
337
|
-
if (!
|
|
338
|
-
const
|
|
339
|
-
|
|
447
|
+
]), b = m ? JSON.parse(m) : {};
|
|
448
|
+
let y = g ? JSON.parse(g) : {};
|
|
449
|
+
if (!g && s !== p) {
|
|
450
|
+
const f = await t.SITE_CONTENT.get(`content:${p}`);
|
|
451
|
+
f && (y = JSON.parse(f));
|
|
340
452
|
}
|
|
341
453
|
const h = e.pathname.match(/^\/p\/(.+)$/);
|
|
342
454
|
if (h) {
|
|
343
|
-
const
|
|
344
|
-
if (!
|
|
345
|
-
let
|
|
346
|
-
!
|
|
455
|
+
const f = h[1];
|
|
456
|
+
if (!y[f]) {
|
|
457
|
+
let d = await t.SITE_CONTENT.get(`page:${f}:${s}`);
|
|
458
|
+
!d && s !== p && (d = await t.SITE_CONTENT.get(`page:${f}:${p}`)), d && (y[f] = JSON.parse(d));
|
|
347
459
|
}
|
|
348
460
|
}
|
|
349
|
-
const
|
|
461
|
+
const v = {
|
|
350
462
|
locale: s,
|
|
351
|
-
config:
|
|
463
|
+
config: b
|
|
352
464
|
};
|
|
353
|
-
for (const [
|
|
354
|
-
|
|
465
|
+
for (const [f, d] of Object.entries(y))
|
|
466
|
+
v[`page:${f}`] = d;
|
|
355
467
|
if (h) {
|
|
356
|
-
const
|
|
357
|
-
|
|
468
|
+
const f = h[1], d = v[`page:${f}`];
|
|
469
|
+
d && (c = x(c, d, o.siteName));
|
|
358
470
|
}
|
|
359
|
-
e.pathname === "/" &&
|
|
360
|
-
const
|
|
361
|
-
return c = c.replace("</head>", `${
|
|
471
|
+
e.pathname === "/" && y.home && (c = x(c, y.home, o.siteName));
|
|
472
|
+
const l = `<script>window.__SITE_DATA__ = ${JSON.stringify(v)};<\/script>`;
|
|
473
|
+
return c = c.replace("</head>", `${l}
|
|
362
474
|
</head>`), new Response(c, {
|
|
363
475
|
headers: {
|
|
364
476
|
"Content-Type": "text/html;charset=utf-8",
|
|
@@ -369,18 +481,20 @@ function ee(o) {
|
|
|
369
481
|
};
|
|
370
482
|
}
|
|
371
483
|
export {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
484
|
+
R as PAGE_CONTEXT_KEY,
|
|
485
|
+
G as buildLayoutCSS,
|
|
486
|
+
re as createSiteWorker,
|
|
487
|
+
A as isInPreview,
|
|
488
|
+
se as layoutPresets,
|
|
489
|
+
ne as layoutSchema,
|
|
490
|
+
V as setupPreviewRouter,
|
|
491
|
+
oe as useDynamicLayout,
|
|
378
492
|
C as useLivePreview,
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
493
|
+
ae as useLocaleSwitcher,
|
|
494
|
+
ee as usePageData,
|
|
495
|
+
Q as useSiteApi,
|
|
496
|
+
te as useSiteComponents,
|
|
497
|
+
q as useSiteConfig,
|
|
498
|
+
Z as useSiteData,
|
|
499
|
+
H as useSkin
|
|
386
500
|
};
|
package/dist/types/blocks.d.ts
CHANGED
|
@@ -12,9 +12,12 @@ export interface Block<T = any> {
|
|
|
12
12
|
settings?: BlockSettings & Record<string, any>;
|
|
13
13
|
}
|
|
14
14
|
export interface HeroBlockData {
|
|
15
|
+
variant?: 'default' | 'minimal' | 'video';
|
|
15
16
|
title: string;
|
|
16
17
|
subtitle?: string;
|
|
18
|
+
badge?: string;
|
|
17
19
|
image?: string;
|
|
20
|
+
backgroundImage?: string;
|
|
18
21
|
overlay?: boolean;
|
|
19
22
|
buttons?: Array<{
|
|
20
23
|
text: string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout schema definition for the editor.
|
|
3
|
+
* Describes the editable structure of a layout config.
|
|
4
|
+
*/
|
|
5
|
+
export interface GridBreakpoint {
|
|
6
|
+
areas?: string[];
|
|
7
|
+
columns?: string;
|
|
8
|
+
rows?: string;
|
|
9
|
+
gap?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface LayoutZone {
|
|
12
|
+
type?: 'slot';
|
|
13
|
+
component?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
container?: boolean;
|
|
16
|
+
containerStyle?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
export interface LayoutConfig {
|
|
19
|
+
grid?: Record<string, GridBreakpoint>;
|
|
20
|
+
zones: Record<string, LayoutZone>;
|
|
21
|
+
}
|
|
22
|
+
export interface LayoutField {
|
|
23
|
+
key: string;
|
|
24
|
+
type: 'text' | 'grid-areas' | 'select' | 'switch' | 'zone-list';
|
|
25
|
+
label: string;
|
|
26
|
+
breakpoint?: string;
|
|
27
|
+
options?: string[];
|
|
28
|
+
required?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface LayoutSchemaDefinition {
|
|
31
|
+
type: string;
|
|
32
|
+
displayName: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
fields: LayoutField[];
|
|
35
|
+
zoneFields: LayoutField[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Schema describing how to edit a layout in the editor.
|
|
39
|
+
* Used to render a form for creating/editing layout configs.
|
|
40
|
+
*/
|
|
41
|
+
export declare const layoutSchema: LayoutSchemaDefinition;
|
|
42
|
+
/**
|
|
43
|
+
* Built-in layout presets for quick layout creation.
|
|
44
|
+
*/
|
|
45
|
+
export declare const layoutPresets: Record<string, {
|
|
46
|
+
displayName: string;
|
|
47
|
+
config: LayoutConfig;
|
|
48
|
+
}>;
|
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { reactive } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared reactive store for page data loaded via usePageData.
|
|
5
|
+
* Allows useDynamicLayout to reactively read layout from fetched pages.
|
|
6
|
+
*/
|
|
7
|
+
export const pageDataStore = reactive<Record<string, any>>({});
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initialize store with preloaded data from __SITE_DATA__.
|
|
11
|
+
*/
|
|
12
|
+
export function initPageDataStore() {
|
|
13
|
+
const siteData = (window as any).__SITE_DATA__;
|
|
14
|
+
if (!siteData) return;
|
|
15
|
+
|
|
16
|
+
for (const key of Object.keys(siteData)) {
|
|
17
|
+
if (key.startsWith('page:')) {
|
|
18
|
+
pageDataStore[key] = siteData[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Auto-init on import
|
|
24
|
+
initPageDataStore();
|
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
import { computed
|
|
1
|
+
import { computed } from 'vue';
|
|
2
2
|
import { useRoute } from 'vue-router';
|
|
3
3
|
|
|
4
4
|
import type { SiteData } from '../types/config.js';
|
|
5
|
+
import type { GridBreakpoint, LayoutConfig } from '../types/layout.js';
|
|
5
6
|
import { useLivePreview } from './useLivePreview.js';
|
|
6
|
-
|
|
7
|
-
export interface GridBreakpoint {
|
|
8
|
-
areas?: string[];
|
|
9
|
-
columns?: string;
|
|
10
|
-
rows?: string;
|
|
11
|
-
gap?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface LayoutZone {
|
|
15
|
-
type?: 'slot';
|
|
16
|
-
component?: string;
|
|
17
|
-
class?: string;
|
|
18
|
-
container?: boolean;
|
|
19
|
-
containerStyle?: Record<string, string>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface LayoutConfig {
|
|
23
|
-
grid?: Record<string, GridBreakpoint>;
|
|
24
|
-
zones: Record<string, LayoutZone>;
|
|
25
|
-
}
|
|
7
|
+
import { pageDataStore } from './pageDataStore.js';
|
|
26
8
|
|
|
27
9
|
const BREAKPOINTS: Record<string, number> = {
|
|
28
10
|
sm: 600,
|
|
@@ -121,7 +103,8 @@ export function useDynamicLayout() {
|
|
|
121
103
|
return previewPage.value.layout;
|
|
122
104
|
}
|
|
123
105
|
const pageKey = route.path === '/' ? 'page:home' : `page:${route.params.slug || ''}`;
|
|
124
|
-
|
|
106
|
+
// Read from reactive store (updated by usePageData on fetch)
|
|
107
|
+
const pageData = pageDataStore[pageKey] || siteData?.[pageKey];
|
|
125
108
|
return pageData?.layout || 'default';
|
|
126
109
|
});
|
|
127
110
|
|
|
@@ -5,6 +5,7 @@ import type { Block } from '../types/blocks.js';
|
|
|
5
5
|
import type { PageConfig, PageContext, SiteData } from '../types/config.js';
|
|
6
6
|
import { PAGE_CONTEXT_KEY } from '../types/config.js';
|
|
7
7
|
import { useLivePreview } from './useLivePreview.js';
|
|
8
|
+
import { pageDataStore } from './pageDataStore.js';
|
|
8
9
|
|
|
9
10
|
export interface UsePageDataReturn {
|
|
10
11
|
/** Resolved page data (from preload, API, or preview) */
|
|
@@ -46,7 +47,7 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
|
|
|
46
47
|
const pageContext = computed<PageContext>(() => {
|
|
47
48
|
const p = isPreviewMode.value ? previewPage.value : page.value;
|
|
48
49
|
if (!p) return {};
|
|
49
|
-
const { blocks, meta, slug: _s, locale: _l, ...rest } = p as any;
|
|
50
|
+
const { blocks: _blocks, meta: _meta, slug: _s, locale: _l, ...rest } = p as any;
|
|
50
51
|
return rest;
|
|
51
52
|
});
|
|
52
53
|
|
|
@@ -61,10 +62,17 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
|
|
|
61
62
|
return page.value?.blocks || [];
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
function setPage(data: PageConfig | null) {
|
|
66
|
+
page.value = data;
|
|
67
|
+
if (data) {
|
|
68
|
+
pageDataStore[pageKey.value] = data;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
function loadFromPreloaded(): boolean {
|
|
65
73
|
const preloaded = siteData?.[pageKey.value];
|
|
66
74
|
if (preloaded && locale.value === siteData?.locale) {
|
|
67
|
-
|
|
75
|
+
setPage(preloaded as PageConfig);
|
|
68
76
|
notFound.value = false;
|
|
69
77
|
applyMeta();
|
|
70
78
|
return true;
|
|
@@ -78,14 +86,14 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
|
|
|
78
86
|
try {
|
|
79
87
|
const res = await fetch(`/api/content/${pageKey.value}:${locale.value}`);
|
|
80
88
|
if (res.ok) {
|
|
81
|
-
|
|
89
|
+
setPage(await res.json());
|
|
82
90
|
applyMeta();
|
|
83
91
|
} else {
|
|
84
|
-
|
|
92
|
+
setPage(null);
|
|
85
93
|
notFound.value = true;
|
|
86
94
|
}
|
|
87
95
|
} catch {
|
|
88
|
-
|
|
96
|
+
setPage(null);
|
|
89
97
|
notFound.value = true;
|
|
90
98
|
} finally {
|
|
91
99
|
loading.value = false;
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,8 @@ export type { UsePageDataReturn } from './composables/usePageData.js';
|
|
|
9
9
|
export { useSiteComponents } from './composables/useSiteComponents.js';
|
|
10
10
|
export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
|
|
11
11
|
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
12
|
-
export type { LayoutConfig, LayoutZone, GridBreakpoint } from './
|
|
12
|
+
export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
|
|
13
|
+
export { layoutSchema, layoutPresets } from './types/layout.js';
|
|
13
14
|
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
14
15
|
|
|
15
16
|
// Worker
|
package/src/types/blocks.ts
CHANGED
|
@@ -14,9 +14,12 @@ export interface Block<T = any> {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface HeroBlockData {
|
|
17
|
+
variant?: 'default' | 'minimal' | 'video';
|
|
17
18
|
title: string;
|
|
18
19
|
subtitle?: string;
|
|
20
|
+
badge?: string;
|
|
19
21
|
image?: string;
|
|
22
|
+
backgroundImage?: string;
|
|
20
23
|
overlay?: boolean;
|
|
21
24
|
buttons?: Array<{ text: string; url: string; variant?: 'primary' | 'outline' | 'white' }>;
|
|
22
25
|
stats?: Array<{ value: string; label: string }>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout schema definition for the editor.
|
|
3
|
+
* Describes the editable structure of a layout config.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface GridBreakpoint {
|
|
7
|
+
areas?: string[];
|
|
8
|
+
columns?: string;
|
|
9
|
+
rows?: string;
|
|
10
|
+
gap?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface LayoutZone {
|
|
14
|
+
type?: 'slot';
|
|
15
|
+
component?: string;
|
|
16
|
+
class?: string;
|
|
17
|
+
container?: boolean;
|
|
18
|
+
containerStyle?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LayoutConfig {
|
|
22
|
+
grid?: Record<string, GridBreakpoint>;
|
|
23
|
+
zones: Record<string, LayoutZone>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LayoutField {
|
|
27
|
+
key: string;
|
|
28
|
+
type: 'text' | 'grid-areas' | 'select' | 'switch' | 'zone-list';
|
|
29
|
+
label: string;
|
|
30
|
+
breakpoint?: string;
|
|
31
|
+
options?: string[];
|
|
32
|
+
required?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LayoutSchemaDefinition {
|
|
36
|
+
type: string;
|
|
37
|
+
displayName: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
fields: LayoutField[];
|
|
40
|
+
zoneFields: LayoutField[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Schema describing how to edit a layout in the editor.
|
|
45
|
+
* Used to render a form for creating/editing layout configs.
|
|
46
|
+
*/
|
|
47
|
+
export const layoutSchema: LayoutSchemaDefinition = {
|
|
48
|
+
type: 'layout',
|
|
49
|
+
displayName: 'Layout',
|
|
50
|
+
description: 'CSS Grid layout with responsive breakpoints and component zones',
|
|
51
|
+
fields: [
|
|
52
|
+
// Mobile (default)
|
|
53
|
+
{ key: 'grid.default.areas', type: 'grid-areas', label: 'Grid Areas (mobile)', breakpoint: 'default', required: true },
|
|
54
|
+
{ key: 'grid.default.columns', type: 'text', label: 'Columns (mobile)', breakpoint: 'default' },
|
|
55
|
+
{ key: 'grid.default.rows', type: 'text', label: 'Rows (mobile)', breakpoint: 'default' },
|
|
56
|
+
{ key: 'grid.default.gap', type: 'text', label: 'Gap (mobile)', breakpoint: 'default' },
|
|
57
|
+
|
|
58
|
+
// Tablet (sm — 600px)
|
|
59
|
+
{ key: 'grid.sm.areas', type: 'grid-areas', label: 'Grid Areas (sm 600px+)', breakpoint: 'sm' },
|
|
60
|
+
{ key: 'grid.sm.columns', type: 'text', label: 'Columns (sm)', breakpoint: 'sm' },
|
|
61
|
+
{ key: 'grid.sm.rows', type: 'text', label: 'Rows (sm)', breakpoint: 'sm' },
|
|
62
|
+
{ key: 'grid.sm.gap', type: 'text', label: 'Gap (sm)', breakpoint: 'sm' },
|
|
63
|
+
|
|
64
|
+
// Desktop (md — 960px)
|
|
65
|
+
{ key: 'grid.md.areas', type: 'grid-areas', label: 'Grid Areas (md 960px+)', breakpoint: 'md' },
|
|
66
|
+
{ key: 'grid.md.columns', type: 'text', label: 'Columns (md)', breakpoint: 'md' },
|
|
67
|
+
{ key: 'grid.md.rows', type: 'text', label: 'Rows (md)', breakpoint: 'md' },
|
|
68
|
+
{ key: 'grid.md.gap', type: 'text', label: 'Gap (md)', breakpoint: 'md' },
|
|
69
|
+
|
|
70
|
+
// Large desktop (lg — 1280px)
|
|
71
|
+
{ key: 'grid.lg.areas', type: 'grid-areas', label: 'Grid Areas (lg 1280px+)', breakpoint: 'lg' },
|
|
72
|
+
{ key: 'grid.lg.columns', type: 'text', label: 'Columns (lg)', breakpoint: 'lg' },
|
|
73
|
+
{ key: 'grid.lg.rows', type: 'text', label: 'Rows (lg)', breakpoint: 'lg' },
|
|
74
|
+
{ key: 'grid.lg.gap', type: 'text', label: 'Gap (lg)', breakpoint: 'lg' },
|
|
75
|
+
|
|
76
|
+
// Zones
|
|
77
|
+
{ key: 'zones', type: 'zone-list', label: 'Zones', required: true },
|
|
78
|
+
],
|
|
79
|
+
zoneFields: [
|
|
80
|
+
{ key: 'type', type: 'select', label: 'Type', options: ['slot', 'component'] },
|
|
81
|
+
{ key: 'component', type: 'text', label: 'Component Key' },
|
|
82
|
+
{ key: 'class', type: 'text', label: 'CSS Class' },
|
|
83
|
+
{ key: 'container', type: 'switch', label: 'Wrap in Container' },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Built-in layout presets for quick layout creation.
|
|
89
|
+
*/
|
|
90
|
+
export const layoutPresets: Record<string, { displayName: string; config: LayoutConfig }> = {
|
|
91
|
+
default: {
|
|
92
|
+
displayName: 'Full Width',
|
|
93
|
+
config: {
|
|
94
|
+
grid: {
|
|
95
|
+
default: { areas: ['header', 'main', 'footer'], rows: 'auto 1fr auto' },
|
|
96
|
+
},
|
|
97
|
+
zones: {
|
|
98
|
+
header: { component: 'nav-menu' },
|
|
99
|
+
main: { type: 'slot' },
|
|
100
|
+
footer: { component: 'footer' },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
sidebar: {
|
|
105
|
+
displayName: 'Sidebar',
|
|
106
|
+
config: {
|
|
107
|
+
grid: {
|
|
108
|
+
default: { areas: ['header', 'main', 'sidebar', 'footer'], columns: '1fr', rows: 'auto 1fr auto auto' },
|
|
109
|
+
md: { areas: ['header header', 'sidebar main', 'footer footer'], columns: '280px 1fr', rows: 'auto 1fr auto' },
|
|
110
|
+
},
|
|
111
|
+
zones: {
|
|
112
|
+
header: { component: 'nav-menu' },
|
|
113
|
+
sidebar: { component: 'sidebar-menu' },
|
|
114
|
+
main: { type: 'slot' },
|
|
115
|
+
footer: { component: 'footer' },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
article: {
|
|
120
|
+
displayName: 'Article',
|
|
121
|
+
config: {
|
|
122
|
+
grid: {
|
|
123
|
+
default: { areas: ['header', 'main', 'footer'], rows: 'auto 1fr auto' },
|
|
124
|
+
},
|
|
125
|
+
zones: {
|
|
126
|
+
header: { component: 'nav-menu' },
|
|
127
|
+
main: { type: 'slot', container: true, containerStyle: { 'max-width': '900px' } },
|
|
128
|
+
footer: { component: 'footer' },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
landing: {
|
|
133
|
+
displayName: 'Landing (no nav)',
|
|
134
|
+
config: {
|
|
135
|
+
grid: {
|
|
136
|
+
default: { areas: ['main'] },
|
|
137
|
+
},
|
|
138
|
+
zones: {
|
|
139
|
+
main: { type: 'slot' },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
magazine: {
|
|
144
|
+
displayName: 'Magazine',
|
|
145
|
+
config: {
|
|
146
|
+
grid: {
|
|
147
|
+
default: { areas: ['header', 'main', 'aside', 'footer'], columns: '1fr', rows: 'auto 1fr auto auto' },
|
|
148
|
+
md: { areas: ['header header header', 'main main aside', 'footer footer footer'], columns: '1fr 1fr 300px', rows: 'auto 1fr auto', gap: '24px' },
|
|
149
|
+
},
|
|
150
|
+
zones: {
|
|
151
|
+
header: { component: 'nav-menu' },
|
|
152
|
+
main: { type: 'slot' },
|
|
153
|
+
aside: { component: 'sidebar-menu' },
|
|
154
|
+
footer: { component: 'footer' },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|