@xosen/site-sdk 0.0.14 → 0.0.16
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/useLivePreview.d.ts +15 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +180 -161
- package/dist/types/blocks.d.ts +2 -1
- package/dist/types/layout.d.ts +1 -0
- package/package.json +14 -14
- package/src/composables/useLivePreview.ts +12 -0
- package/src/composables/usePageData.ts +2 -2
- package/src/composables/useSiteComponents.ts +30 -9
- package/src/index.ts +1 -1
- package/src/types/blocks.ts +2 -1
- package/src/types/layout.ts +14 -2
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Router } from 'vue-router';
|
|
2
|
+
export interface PreviewSiteData {
|
|
3
|
+
config?: Record<string, any>;
|
|
4
|
+
skin?: Record<string, any>;
|
|
5
|
+
components?: Record<string, any>;
|
|
6
|
+
}
|
|
2
7
|
export interface PreviewMessage {
|
|
3
8
|
type: 'xosen-preview-update';
|
|
9
|
+
site?: PreviewSiteData;
|
|
4
10
|
page: {
|
|
5
11
|
slug: string;
|
|
6
12
|
locale: string;
|
|
@@ -50,5 +56,14 @@ export declare function useLivePreview(): {
|
|
|
50
56
|
meta?: Record<string, any> | undefined;
|
|
51
57
|
layout?: string | undefined;
|
|
52
58
|
} | null>;
|
|
59
|
+
previewSite: import("vue").Ref<{
|
|
60
|
+
config?: Record<string, any> | undefined;
|
|
61
|
+
skin?: Record<string, any> | undefined;
|
|
62
|
+
components?: Record<string, any> | undefined;
|
|
63
|
+
} | null, PreviewSiteData | {
|
|
64
|
+
config?: Record<string, any> | undefined;
|
|
65
|
+
skin?: Record<string, any> | undefined;
|
|
66
|
+
components?: Record<string, any> | undefined;
|
|
67
|
+
} | null>;
|
|
53
68
|
isPreviewMode: import("vue").Ref<boolean, boolean>;
|
|
54
69
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
|
|
|
10
10
|
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
11
11
|
export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
|
|
12
12
|
export { layoutSchema, layoutPresets } from './types/layout.js';
|
|
13
|
-
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
13
|
+
export type { PreviewMessage, PreviewSiteData } from './composables/useLivePreview.js';
|
|
14
14
|
export { createSiteWorker } from './worker/index.js';
|
|
15
15
|
export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
|
|
16
16
|
export type { Block, BlockSettings, HeroBlockData, HtmlBlockData, CardsBlockData, ImageTextBlockData, PricingBlockData, PricingPlan, ContactsBlockData, Office, GalleryBlockData, MapBlockData, } from './types/blocks.js';
|
package/dist/index.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { ref as
|
|
2
|
-
import { useI18n as
|
|
1
|
+
import { ref as v, onMounted as S, watch as _, computed as f, onUnmounted as N, reactive as L, provide as P } from "vue";
|
|
2
|
+
import { useI18n as T } from "vue-i18n";
|
|
3
3
|
import { useRoute as I } from "vue-router";
|
|
4
4
|
function Z(o, a) {
|
|
5
|
-
const t =
|
|
6
|
-
function
|
|
7
|
-
return
|
|
5
|
+
const t = v(null), { locale: e } = T(), s = window.__SITE_DATA__, n = s?.locale;
|
|
6
|
+
function i() {
|
|
7
|
+
return s && s[o] && e.value === n ? (t.value = s[o], !0) : !1;
|
|
8
8
|
}
|
|
9
|
-
async function
|
|
9
|
+
async function l() {
|
|
10
10
|
try {
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const r = await fetch(`/api/content/${a}:${e.value}`);
|
|
12
|
+
r.ok && (t.value = await r.json());
|
|
13
13
|
} catch {
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
return
|
|
17
|
-
|
|
16
|
+
return S(() => {
|
|
17
|
+
i() || l();
|
|
18
18
|
}), _(e, () => {
|
|
19
|
-
|
|
19
|
+
l();
|
|
20
20
|
}), t;
|
|
21
21
|
}
|
|
22
22
|
function q() {
|
|
23
|
-
const o = window.__SITE_DATA__, a =
|
|
24
|
-
function
|
|
25
|
-
const g =
|
|
23
|
+
const o = window.__SITE_DATA__, a = f(() => o?.config || null), t = f(() => o?.components || {}), e = f(() => a.value?.navigation || []), s = f(() => a.value?.locales || []), n = f(() => a.value?.defaultLocale || "en"), i = f(() => a.value?.branding || {}), l = f(() => a.value?.footer || {}), r = f(() => a.value?.features || {});
|
|
24
|
+
function u(m) {
|
|
25
|
+
const g = r.value[m];
|
|
26
26
|
return typeof g == "boolean" ? g : typeof g == "object";
|
|
27
27
|
}
|
|
28
28
|
return {
|
|
29
29
|
config: a,
|
|
30
30
|
components: t,
|
|
31
31
|
navigation: e,
|
|
32
|
-
locales:
|
|
33
|
-
defaultLocale:
|
|
34
|
-
branding:
|
|
35
|
-
footer:
|
|
36
|
-
features:
|
|
37
|
-
hasFeature:
|
|
32
|
+
locales: s,
|
|
33
|
+
defaultLocale: n,
|
|
34
|
+
branding: i,
|
|
35
|
+
footer: l,
|
|
36
|
+
features: r,
|
|
37
|
+
hasFeature: u
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
function H(o) {
|
|
41
41
|
function a(t) {
|
|
42
42
|
const e = document.documentElement;
|
|
43
43
|
if (t.colors)
|
|
44
|
-
for (const [
|
|
45
|
-
|
|
44
|
+
for (const [s, n] of Object.entries(t.colors))
|
|
45
|
+
n && e.style.setProperty(`--x-color-${D(s)}`, n);
|
|
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
|
+
S(() => {
|
|
49
49
|
if (o) {
|
|
50
50
|
a(o);
|
|
51
51
|
return;
|
|
@@ -63,25 +63,25 @@ function D(o) {
|
|
|
63
63
|
}
|
|
64
64
|
function Q() {
|
|
65
65
|
const a = window.__SITE_DATA__?.apiBase || "";
|
|
66
|
-
async function t(
|
|
67
|
-
const
|
|
68
|
-
if (!
|
|
69
|
-
throw new Error(`API error: ${
|
|
70
|
-
return
|
|
66
|
+
async function t(n) {
|
|
67
|
+
const i = await fetch(`${a}${n}`);
|
|
68
|
+
if (!i.ok)
|
|
69
|
+
throw new Error(`API error: ${i.status}`);
|
|
70
|
+
return i.json();
|
|
71
71
|
}
|
|
72
72
|
async function e() {
|
|
73
73
|
return t("/v1/billing/tariffs");
|
|
74
74
|
}
|
|
75
|
-
async function
|
|
75
|
+
async function s() {
|
|
76
76
|
return t("/v1/billing/services");
|
|
77
77
|
}
|
|
78
78
|
return {
|
|
79
79
|
get: t,
|
|
80
80
|
getTariffs: e,
|
|
81
|
-
getProducts:
|
|
81
|
+
getProducts: s
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
-
function
|
|
84
|
+
function $() {
|
|
85
85
|
try {
|
|
86
86
|
return window.self !== window.top;
|
|
87
87
|
} catch {
|
|
@@ -89,133 +89,141 @@ function A() {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
function V(o) {
|
|
92
|
-
|
|
92
|
+
$() && o.beforeEach((a, t) => !t.name);
|
|
93
93
|
}
|
|
94
94
|
function C() {
|
|
95
|
-
const o =
|
|
96
|
-
function
|
|
97
|
-
const n =
|
|
98
|
-
n?.type === "xosen-preview-update" && n.page && (o.value = n.page, a.value = !0);
|
|
95
|
+
const o = v(null), a = v(null), t = v($());
|
|
96
|
+
function e(s) {
|
|
97
|
+
const n = s.data;
|
|
98
|
+
n?.type === "xosen-preview-update" && n.page && (o.value = n.page, n.site && (a.value = n.site), t.value = !0);
|
|
99
99
|
}
|
|
100
|
-
return
|
|
101
|
-
window.addEventListener("message",
|
|
100
|
+
return S(() => {
|
|
101
|
+
window.addEventListener("message", e);
|
|
102
102
|
}), N(() => {
|
|
103
|
-
window.removeEventListener("message",
|
|
103
|
+
window.removeEventListener("message", e);
|
|
104
104
|
}), {
|
|
105
105
|
previewPage: o,
|
|
106
|
-
|
|
106
|
+
previewSite: a,
|
|
107
|
+
isPreviewMode: t
|
|
107
108
|
};
|
|
108
109
|
}
|
|
109
|
-
const R = /* @__PURE__ */ Symbol("pageContext"),
|
|
110
|
+
const R = /* @__PURE__ */ Symbol("pageContext"), k = L({});
|
|
110
111
|
function j() {
|
|
111
112
|
const o = window.__SITE_DATA__;
|
|
112
113
|
if (o)
|
|
113
114
|
for (const a of Object.keys(o))
|
|
114
|
-
a.startsWith("page:") && (
|
|
115
|
+
a.startsWith("page:") && (k[a] = o[a]);
|
|
115
116
|
}
|
|
116
117
|
j();
|
|
117
118
|
function ee(o) {
|
|
118
|
-
const { locale: a } =
|
|
119
|
-
const
|
|
120
|
-
if (!
|
|
121
|
-
const { blocks:
|
|
119
|
+
const { locale: a } = T(), { previewPage: t, isPreviewMode: e } = C(), s = window.__SITE_DATA__, n = v(null), i = v(!0), l = v(!1), r = f(() => typeof o == "string" ? o : o.value), u = f(() => `page:${r.value}`), m = f(() => {
|
|
120
|
+
const c = e.value ? t.value : n.value;
|
|
121
|
+
if (!c) return {};
|
|
122
|
+
const { blocks: p, meta: d, slug: W, locale: J, ...E } = c;
|
|
122
123
|
return E;
|
|
123
124
|
});
|
|
124
125
|
P(R, m);
|
|
125
|
-
const g =
|
|
126
|
-
function b(
|
|
127
|
-
|
|
126
|
+
const g = f(() => e.value && t.value?.slug === r.value ? t.value.blocks || [] : n.value?.blocks || []);
|
|
127
|
+
function b(c) {
|
|
128
|
+
n.value = c, c && (k[u.value] = c);
|
|
128
129
|
}
|
|
129
130
|
function y() {
|
|
130
|
-
const
|
|
131
|
-
return
|
|
131
|
+
const c = s?.[u.value];
|
|
132
|
+
return c && a.value === s?.locale ? (b(c), l.value = !1, w(), !0) : !1;
|
|
132
133
|
}
|
|
133
134
|
async function h() {
|
|
134
|
-
|
|
135
|
+
i.value = !0, l.value = !1;
|
|
135
136
|
try {
|
|
136
|
-
const
|
|
137
|
-
|
|
137
|
+
const c = await fetch(`/api/content/${u.value}:${a.value}`);
|
|
138
|
+
c.ok ? (b(await c.json()), w()) : (b(null), l.value = !0);
|
|
138
139
|
} catch {
|
|
139
|
-
b(null),
|
|
140
|
+
b(null), l.value = !0;
|
|
140
141
|
} finally {
|
|
141
|
-
|
|
142
|
+
i.value = !1;
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
|
-
function
|
|
145
|
-
if (!
|
|
146
|
-
if (
|
|
147
|
-
const
|
|
148
|
-
document.title = `${
|
|
145
|
+
function w() {
|
|
146
|
+
if (!n.value) return;
|
|
147
|
+
if (n.value.title) {
|
|
148
|
+
const p = s?.config?.branding, d = p?.siteName ? ` — ${p.siteName}` : "";
|
|
149
|
+
document.title = `${n.value.title}${d}`;
|
|
149
150
|
}
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
const
|
|
153
|
-
|
|
151
|
+
const c = n.value.meta?.description;
|
|
152
|
+
if (c) {
|
|
153
|
+
const p = document.querySelector('meta[name="description"]');
|
|
154
|
+
p && p.setAttribute("content", c);
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
|
-
return _(t, (
|
|
157
|
-
|
|
158
|
-
...
|
|
159
|
-
...
|
|
160
|
-
...
|
|
161
|
-
},
|
|
162
|
-
}),
|
|
163
|
-
y() ?
|
|
164
|
-
}), _(a, () => h()), _(
|
|
165
|
-
y() ?
|
|
157
|
+
return _(t, (c) => {
|
|
158
|
+
c?.slug === r.value && (i.value = !1, l.value = !1, (c.title || c.blocks) && (n.value = {
|
|
159
|
+
...n.value || {},
|
|
160
|
+
...c.title ? { title: c.title } : {},
|
|
161
|
+
...c.blocks ? { blocks: c.blocks } : {}
|
|
162
|
+
}, w()));
|
|
163
|
+
}), S(() => {
|
|
164
|
+
y() ? i.value = !1 : h();
|
|
165
|
+
}), _(a, () => h()), _(r, () => {
|
|
166
|
+
y() ? i.value = !1 : h();
|
|
166
167
|
}), {
|
|
167
|
-
page:
|
|
168
|
+
page: n,
|
|
168
169
|
blocks: g,
|
|
169
170
|
pageContext: m,
|
|
170
|
-
loading:
|
|
171
|
-
notFound:
|
|
171
|
+
loading: i,
|
|
172
|
+
notFound: l,
|
|
172
173
|
isPreviewMode: e,
|
|
173
174
|
reload: h
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
177
|
function te() {
|
|
177
|
-
const { locale: o } =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
178
|
+
const { locale: o } = T(), a = window.__SITE_DATA__, t = a?.locale, e = v({ ...a?.components || {} });
|
|
179
|
+
let s = t, n = null;
|
|
180
|
+
async function i(r) {
|
|
181
|
+
if (!$() && r !== s) {
|
|
182
|
+
if (r === t) {
|
|
183
|
+
e.value = { ...a?.components || {} }, s = r;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
n || (n = (async () => {
|
|
187
|
+
try {
|
|
188
|
+
const u = await fetch(`/api/content/components:${r}`);
|
|
189
|
+
u.ok && (e.value = await u.json(), s = r);
|
|
190
|
+
} catch {
|
|
191
|
+
} finally {
|
|
192
|
+
n = null;
|
|
193
|
+
}
|
|
194
|
+
})(), await n);
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
|
-
_(o, (r) =>
|
|
190
|
-
o.value !== t &&
|
|
197
|
+
_(o, (r) => i(r)), S(() => {
|
|
198
|
+
o.value !== t && i(o.value);
|
|
191
199
|
});
|
|
192
|
-
function
|
|
193
|
-
return
|
|
200
|
+
function l(r) {
|
|
201
|
+
return f(() => e.value[r] || null);
|
|
194
202
|
}
|
|
195
203
|
return {
|
|
196
204
|
/** All components (reactive, locale-aware) */
|
|
197
205
|
components: e,
|
|
198
206
|
/** Get a single component by key */
|
|
199
|
-
getComponent:
|
|
207
|
+
getComponent: l,
|
|
200
208
|
/** Force refetch for current locale */
|
|
201
|
-
reload: () =>
|
|
209
|
+
reload: () => i(o.value)
|
|
202
210
|
};
|
|
203
211
|
}
|
|
204
212
|
function ae() {
|
|
205
|
-
const { locale: o } =
|
|
206
|
-
function
|
|
207
|
-
o.value =
|
|
213
|
+
const { locale: o } = T(), a = window.__SITE_DATA__, t = f(() => a?.config?.locales || []), e = f(() => a?.config?.defaultLocale || "en"), s = f(() => t.value.length > 1);
|
|
214
|
+
function n(l) {
|
|
215
|
+
o.value = l, localStorage.setItem("x-site-locale", l);
|
|
208
216
|
}
|
|
209
|
-
function
|
|
210
|
-
return typeof
|
|
217
|
+
function i(l) {
|
|
218
|
+
return typeof l == "string" ? l : l[o.value] || l[e.value] || Object.values(l)[0] || "";
|
|
211
219
|
}
|
|
212
220
|
return {
|
|
213
221
|
locale: o,
|
|
214
222
|
locales: t,
|
|
215
223
|
defaultLocale: e,
|
|
216
|
-
hasMultipleLocales:
|
|
217
|
-
switchLocale:
|
|
218
|
-
resolveText:
|
|
224
|
+
hasMultipleLocales: s,
|
|
225
|
+
switchLocale: n,
|
|
226
|
+
resolveText: i
|
|
219
227
|
};
|
|
220
228
|
}
|
|
221
229
|
const O = {
|
|
@@ -245,10 +253,10 @@ const O = {
|
|
|
245
253
|
main: { type: "slot" }
|
|
246
254
|
}
|
|
247
255
|
};
|
|
248
|
-
function
|
|
256
|
+
function x(o, a) {
|
|
249
257
|
const t = [];
|
|
250
258
|
if (o.areas?.length) {
|
|
251
|
-
const e = o.areas.map((
|
|
259
|
+
const e = o.areas.map((s) => `"${s}"`).join(" ");
|
|
252
260
|
t.push(`grid-template-areas: ${e}`);
|
|
253
261
|
}
|
|
254
262
|
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("; ")}; }` : "";
|
|
@@ -258,39 +266,39 @@ function G(o, a) {
|
|
|
258
266
|
if (!e) return t.join(`
|
|
259
267
|
`);
|
|
260
268
|
if (e.default) {
|
|
261
|
-
const
|
|
262
|
-
|
|
269
|
+
const s = x(e.default, a);
|
|
270
|
+
s && t.push(s);
|
|
263
271
|
}
|
|
264
|
-
for (const
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
267
|
-
const
|
|
268
|
-
|
|
272
|
+
for (const s of ["sm", "md", "lg", "xl"]) {
|
|
273
|
+
const n = e[s];
|
|
274
|
+
if (!n) continue;
|
|
275
|
+
const i = x(n, a);
|
|
276
|
+
i && t.push(`@media (min-width: ${O[s]}px) { ${i} }`);
|
|
269
277
|
}
|
|
270
278
|
return t.join(`
|
|
271
279
|
`);
|
|
272
280
|
}
|
|
273
281
|
function oe() {
|
|
274
|
-
const o = I(), { previewPage: a, isPreviewMode: t } = C(), e = window.__SITE_DATA__,
|
|
282
|
+
const o = I(), { previewPage: a, isPreviewMode: t } = C(), e = window.__SITE_DATA__, s = `dl-${Math.random().toString(36).slice(2, 8)}`, n = f(() => ["dynamic-layout", s]), i = f(() => {
|
|
275
283
|
if (o.path === "/_preview_")
|
|
276
284
|
return a.value?.layout || "landing";
|
|
277
285
|
if (t.value && a.value?.layout)
|
|
278
286
|
return a.value.layout;
|
|
279
|
-
const
|
|
280
|
-
return (
|
|
281
|
-
}),
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
284
|
-
const m =
|
|
287
|
+
const u = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
|
|
288
|
+
return (k[u] || e?.[u])?.layout || "default";
|
|
289
|
+
}), l = f(() => {
|
|
290
|
+
const u = e?.config?.layouts;
|
|
291
|
+
if (u) {
|
|
292
|
+
const m = u[i.value] || u.default;
|
|
285
293
|
if (m?.zones) return m;
|
|
286
294
|
}
|
|
287
|
-
return
|
|
288
|
-
}),
|
|
295
|
+
return i.value === "landing" ? F : z;
|
|
296
|
+
}), r = f(() => G(l.value, `.${s}`));
|
|
289
297
|
return {
|
|
290
|
-
layoutName:
|
|
291
|
-
layoutConfig:
|
|
292
|
-
layoutCSS:
|
|
293
|
-
layoutClass:
|
|
298
|
+
layoutName: i,
|
|
299
|
+
layoutConfig: l,
|
|
300
|
+
layoutCSS: r,
|
|
301
|
+
layoutClass: n
|
|
294
302
|
};
|
|
295
303
|
}
|
|
296
304
|
const ne = {
|
|
@@ -299,7 +307,13 @@ const ne = {
|
|
|
299
307
|
description: "CSS Grid layout with responsive breakpoints and component zones",
|
|
300
308
|
fields: [
|
|
301
309
|
// Mobile (default)
|
|
302
|
-
{
|
|
310
|
+
{
|
|
311
|
+
key: "grid.default.areas",
|
|
312
|
+
type: "grid-areas",
|
|
313
|
+
label: "Grid Areas (mobile)",
|
|
314
|
+
breakpoint: "default",
|
|
315
|
+
required: !0
|
|
316
|
+
},
|
|
303
317
|
{ key: "grid.default.columns", type: "text", label: "Columns (mobile)", breakpoint: "default" },
|
|
304
318
|
{ key: "grid.default.rows", type: "text", label: "Rows (mobile)", breakpoint: "default" },
|
|
305
319
|
{ key: "grid.default.gap", type: "text", label: "Gap (mobile)", breakpoint: "default" },
|
|
@@ -385,7 +399,12 @@ const ne = {
|
|
|
385
399
|
config: {
|
|
386
400
|
grid: {
|
|
387
401
|
default: { areas: ["header", "main", "aside", "footer"], columns: "1fr", rows: "auto 1fr auto auto" },
|
|
388
|
-
md: {
|
|
402
|
+
md: {
|
|
403
|
+
areas: ["header header header", "main main aside", "footer footer footer"],
|
|
404
|
+
columns: "1fr 1fr 300px",
|
|
405
|
+
rows: "auto 1fr auto",
|
|
406
|
+
gap: "24px"
|
|
407
|
+
}
|
|
389
408
|
},
|
|
390
409
|
zones: {
|
|
391
410
|
header: { component: "nav-menu" },
|
|
@@ -400,9 +419,9 @@ function U(o, a) {
|
|
|
400
419
|
const e = new URL(o.url).searchParams.get("lang");
|
|
401
420
|
if (e && a.supportedLocales?.includes(e)) return e;
|
|
402
421
|
if (a.supportedLocales?.length) {
|
|
403
|
-
const
|
|
404
|
-
for (const
|
|
405
|
-
if (
|
|
422
|
+
const s = o.headers.get("Accept-Language") || "";
|
|
423
|
+
for (const n of a.supportedLocales)
|
|
424
|
+
if (s.includes(n)) return n;
|
|
406
425
|
}
|
|
407
426
|
return a.defaultLocale;
|
|
408
427
|
}
|
|
@@ -410,12 +429,12 @@ function K(o, a, t) {
|
|
|
410
429
|
if (!o.pathname.startsWith("/api/content/")) return null;
|
|
411
430
|
const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
|
|
412
431
|
return e ? (async () => {
|
|
413
|
-
let
|
|
414
|
-
if (!
|
|
415
|
-
const
|
|
416
|
-
|
|
432
|
+
let s = await a.SITE_CONTENT.get(e);
|
|
433
|
+
if (!s) {
|
|
434
|
+
const n = e.lastIndexOf(":");
|
|
435
|
+
n > 0 && e.slice(n + 1) !== t && (s = await a.SITE_CONTENT.get(e.slice(0, n + 1) + t));
|
|
417
436
|
}
|
|
418
|
-
return
|
|
437
|
+
return s ? new Response(s, {
|
|
419
438
|
headers: {
|
|
420
439
|
"Content-Type": "application/json",
|
|
421
440
|
"Cache-Control": "public, max-age=60",
|
|
@@ -424,54 +443,54 @@ function K(o, a, t) {
|
|
|
424
443
|
}) : Response.json({ error: "Not found" }, { status: 404 });
|
|
425
444
|
})() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
|
|
426
445
|
}
|
|
427
|
-
function
|
|
446
|
+
function A(o, a, t) {
|
|
428
447
|
let e = o;
|
|
429
448
|
if (a.title) {
|
|
430
|
-
const
|
|
431
|
-
e = e.replace(/<title>[^<]*<\/title>/, `<title>${a.title}${
|
|
449
|
+
const s = t ? ` — ${t}` : "";
|
|
450
|
+
e = e.replace(/<title>[^<]*<\/title>/, `<title>${a.title}${s}</title>`);
|
|
432
451
|
}
|
|
433
452
|
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;
|
|
434
453
|
}
|
|
435
454
|
function re(o) {
|
|
436
455
|
return {
|
|
437
456
|
async fetch(a, t) {
|
|
438
|
-
const e = new URL(a.url),
|
|
439
|
-
if (
|
|
457
|
+
const e = new URL(a.url), s = K(e, t, o.defaultLocale);
|
|
458
|
+
if (s) return s;
|
|
440
459
|
if (M.test(e.pathname))
|
|
441
460
|
return t.ASSETS.fetch(a);
|
|
442
|
-
const
|
|
443
|
-
let
|
|
444
|
-
const
|
|
461
|
+
const n = U(a, o), i = new URL("/index.html", a.url);
|
|
462
|
+
let r = await (await t.ASSETS.fetch(new Request(i))).text();
|
|
463
|
+
const u = o.defaultLocale, [m, g] = await Promise.all([
|
|
445
464
|
t.SITE_CONTENT.get("config"),
|
|
446
|
-
t.SITE_CONTENT.get(`content:${
|
|
465
|
+
t.SITE_CONTENT.get(`content:${n}`)
|
|
447
466
|
]), b = m ? JSON.parse(m) : {};
|
|
448
467
|
let y = g ? JSON.parse(g) : {};
|
|
449
|
-
if (!g &&
|
|
450
|
-
const
|
|
451
|
-
|
|
468
|
+
if (!g && n !== u) {
|
|
469
|
+
const p = await t.SITE_CONTENT.get(`content:${u}`);
|
|
470
|
+
p && (y = JSON.parse(p));
|
|
452
471
|
}
|
|
453
472
|
const h = e.pathname.match(/^\/p\/(.+)$/);
|
|
454
473
|
if (h) {
|
|
455
|
-
const
|
|
456
|
-
if (!y[
|
|
457
|
-
let d = await t.SITE_CONTENT.get(`page:${
|
|
458
|
-
!d &&
|
|
474
|
+
const p = h[1];
|
|
475
|
+
if (!y[p]) {
|
|
476
|
+
let d = await t.SITE_CONTENT.get(`page:${p}:${n}`);
|
|
477
|
+
!d && n !== u && (d = await t.SITE_CONTENT.get(`page:${p}:${u}`)), d && (y[p] = JSON.parse(d));
|
|
459
478
|
}
|
|
460
479
|
}
|
|
461
|
-
const
|
|
462
|
-
locale:
|
|
480
|
+
const w = {
|
|
481
|
+
locale: n,
|
|
463
482
|
config: b
|
|
464
483
|
};
|
|
465
|
-
for (const [
|
|
466
|
-
|
|
484
|
+
for (const [p, d] of Object.entries(y))
|
|
485
|
+
w[`page:${p}`] = d;
|
|
467
486
|
if (h) {
|
|
468
|
-
const
|
|
469
|
-
d && (
|
|
487
|
+
const p = h[1], d = w[`page:${p}`];
|
|
488
|
+
d && (r = A(r, d, o.siteName));
|
|
470
489
|
}
|
|
471
|
-
e.pathname === "/" && y.home && (
|
|
472
|
-
const
|
|
473
|
-
return
|
|
474
|
-
</head>`), new Response(
|
|
490
|
+
e.pathname === "/" && y.home && (r = A(r, y.home, o.siteName));
|
|
491
|
+
const c = `<script>window.__SITE_DATA__ = ${JSON.stringify(w)};<\/script>`;
|
|
492
|
+
return r = r.replace("</head>", `${c}
|
|
493
|
+
</head>`), new Response(r, {
|
|
475
494
|
headers: {
|
|
476
495
|
"Content-Type": "text/html;charset=utf-8",
|
|
477
496
|
"Cache-Control": "public, max-age=60"
|
|
@@ -484,7 +503,7 @@ export {
|
|
|
484
503
|
R as PAGE_CONTEXT_KEY,
|
|
485
504
|
G as buildLayoutCSS,
|
|
486
505
|
re as createSiteWorker,
|
|
487
|
-
|
|
506
|
+
$ as isInPreview,
|
|
488
507
|
se as layoutPresets,
|
|
489
508
|
ne as layoutSchema,
|
|
490
509
|
V as setupPreviewRouter,
|
package/dist/types/blocks.d.ts
CHANGED
|
@@ -18,7 +18,8 @@ export interface HeroBlockData {
|
|
|
18
18
|
badge?: string;
|
|
19
19
|
image?: string;
|
|
20
20
|
backgroundImage?: string;
|
|
21
|
-
|
|
21
|
+
/** Overlay: false to disable, number (0-1) for opacity, or CSS string for custom gradient */
|
|
22
|
+
overlay?: boolean | number | string;
|
|
22
23
|
buttons?: Array<{
|
|
23
24
|
text: string;
|
|
24
25
|
url: string;
|
package/dist/types/layout.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xosen/site-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Shared Vue components and composables for Xosen site templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,23 +24,23 @@
|
|
|
24
24
|
"dist",
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
29
|
+
"dev": "vite build --watch",
|
|
30
|
+
"clean": "rm -rf dist"
|
|
31
|
+
},
|
|
27
32
|
"peerDependencies": {
|
|
28
33
|
"vue": "^3.5.0",
|
|
29
34
|
"vue-i18n": "^11.0.0",
|
|
30
35
|
"vue-router": "^5.0.0"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
33
|
-
"vue": "
|
|
34
|
-
"vue-i18n": "
|
|
35
|
-
"vue-router": "
|
|
36
|
-
"vue-tsc": "
|
|
37
|
-
"typescript": "
|
|
38
|
-
"vite": "
|
|
39
|
-
"@vitejs/plugin-vue": "
|
|
40
|
-
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
43
|
-
"dev": "vite build --watch",
|
|
44
|
-
"clean": "rm -rf dist"
|
|
38
|
+
"vue": "catalog:",
|
|
39
|
+
"vue-i18n": "catalog:",
|
|
40
|
+
"vue-router": "catalog:",
|
|
41
|
+
"vue-tsc": "catalog:",
|
|
42
|
+
"typescript": "catalog:",
|
|
43
|
+
"vite": "catalog:",
|
|
44
|
+
"@vitejs/plugin-vue": "catalog:"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
2
2
|
import type { Router } from 'vue-router';
|
|
3
3
|
|
|
4
|
+
export interface PreviewSiteData {
|
|
5
|
+
config?: Record<string, any>;
|
|
6
|
+
skin?: Record<string, any>;
|
|
7
|
+
components?: Record<string, any>;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export interface PreviewMessage {
|
|
5
11
|
type: 'xosen-preview-update';
|
|
12
|
+
site?: PreviewSiteData;
|
|
6
13
|
page: {
|
|
7
14
|
slug: string;
|
|
8
15
|
locale: string;
|
|
@@ -45,12 +52,16 @@ export function setupPreviewRouter(router: Router) {
|
|
|
45
52
|
*/
|
|
46
53
|
export function useLivePreview() {
|
|
47
54
|
const previewPage = ref<PreviewMessage['page'] | null>(null);
|
|
55
|
+
const previewSite = ref<PreviewSiteData | null>(null);
|
|
48
56
|
const isPreviewMode = ref(isInPreview());
|
|
49
57
|
|
|
50
58
|
function handleMessage(event: MessageEvent) {
|
|
51
59
|
const data = event.data;
|
|
52
60
|
if (data?.type === 'xosen-preview-update' && data.page) {
|
|
53
61
|
previewPage.value = data.page;
|
|
62
|
+
if (data.site) {
|
|
63
|
+
previewSite.value = data.site;
|
|
64
|
+
}
|
|
54
65
|
isPreviewMode.value = true;
|
|
55
66
|
}
|
|
56
67
|
}
|
|
@@ -65,6 +76,7 @@ export function useLivePreview() {
|
|
|
65
76
|
|
|
66
77
|
return {
|
|
67
78
|
previewPage,
|
|
79
|
+
previewSite,
|
|
68
80
|
isPreviewMode,
|
|
69
81
|
};
|
|
70
82
|
}
|
|
@@ -40,7 +40,7 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
|
|
|
40
40
|
const loading = ref(true);
|
|
41
41
|
const notFound = ref(false);
|
|
42
42
|
|
|
43
|
-
const resolvedSlug = computed(() => typeof slug === 'string' ? slug : slug.value);
|
|
43
|
+
const resolvedSlug = computed(() => (typeof slug === 'string' ? slug : slug.value));
|
|
44
44
|
const pageKey = computed(() => `page:${resolvedSlug.value}`);
|
|
45
45
|
|
|
46
46
|
// Page context: page metadata without blocks (for injection)
|
|
@@ -121,7 +121,7 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
|
|
|
121
121
|
notFound.value = false;
|
|
122
122
|
if (p.title || p.blocks) {
|
|
123
123
|
page.value = {
|
|
124
|
-
...(page.value || {} as PageConfig),
|
|
124
|
+
...(page.value || ({} as PageConfig)),
|
|
125
125
|
...(p.title ? { title: p.title } : {}),
|
|
126
126
|
...(p.blocks ? { blocks: p.blocks as Block[] } : {}),
|
|
127
127
|
};
|
|
@@ -2,6 +2,7 @@ import { ref, computed, watch, onMounted, type Ref } from 'vue';
|
|
|
2
2
|
import { useI18n } from 'vue-i18n';
|
|
3
3
|
|
|
4
4
|
import type { SiteData } from '../types/config.js';
|
|
5
|
+
import { isInPreview } from './useLivePreview.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Locale-aware access to site components (navigation, footer, sidebar, etc.).
|
|
@@ -14,22 +15,42 @@ export function useSiteComponents() {
|
|
|
14
15
|
const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
|
|
15
16
|
const initialLocale = siteData?.locale;
|
|
16
17
|
|
|
17
|
-
const components = ref<Record<string, any>>({ ...(siteData as any)?.components || {} });
|
|
18
|
+
const components = ref<Record<string, any>>({ ...((siteData as any)?.components || {}) });
|
|
19
|
+
let lastFetchedLocale: string | undefined = initialLocale;
|
|
20
|
+
let fetchPromise: Promise<void> | null = null;
|
|
18
21
|
|
|
19
22
|
async function fetchComponents(loc: string) {
|
|
23
|
+
// Skip fetching in preview mode — components come via postMessage
|
|
24
|
+
if (isInPreview()) return;
|
|
25
|
+
|
|
26
|
+
// Skip if already fetched/loaded for this locale
|
|
27
|
+
if (loc === lastFetchedLocale) return;
|
|
28
|
+
|
|
20
29
|
// If same as initial locale, use preloaded data
|
|
21
30
|
if (loc === initialLocale) {
|
|
22
|
-
components.value = { ...(siteData as any)?.components || {} };
|
|
31
|
+
components.value = { ...((siteData as any)?.components || {}) };
|
|
32
|
+
lastFetchedLocale = loc;
|
|
23
33
|
return;
|
|
24
34
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
|
|
36
|
+
// Deduplicate concurrent fetches
|
|
37
|
+
if (fetchPromise) return;
|
|
38
|
+
|
|
39
|
+
fetchPromise = (async () => {
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(`/api/content/components:${loc}`);
|
|
42
|
+
if (res.ok) {
|
|
43
|
+
components.value = await res.json();
|
|
44
|
+
lastFetchedLocale = loc;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Keep current
|
|
48
|
+
} finally {
|
|
49
|
+
fetchPromise = null;
|
|
29
50
|
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
})();
|
|
52
|
+
|
|
53
|
+
await fetchPromise;
|
|
33
54
|
}
|
|
34
55
|
|
|
35
56
|
watch(locale, (newLocale) => fetchComponents(newLocale));
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
|
|
|
11
11
|
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
12
12
|
export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
|
|
13
13
|
export { layoutSchema, layoutPresets } from './types/layout.js';
|
|
14
|
-
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
14
|
+
export type { PreviewMessage, PreviewSiteData } from './composables/useLivePreview.js';
|
|
15
15
|
|
|
16
16
|
// Worker
|
|
17
17
|
export { createSiteWorker } from './worker/index.js';
|
package/src/types/blocks.ts
CHANGED
|
@@ -20,7 +20,8 @@ export interface HeroBlockData {
|
|
|
20
20
|
badge?: string;
|
|
21
21
|
image?: string;
|
|
22
22
|
backgroundImage?: string;
|
|
23
|
-
|
|
23
|
+
/** Overlay: false to disable, number (0-1) for opacity, or CSS string for custom gradient */
|
|
24
|
+
overlay?: boolean | number | string;
|
|
24
25
|
buttons?: Array<{ text: string; url: string; variant?: 'primary' | 'outline' | 'white' }>;
|
|
25
26
|
stats?: Array<{ value: string; label: string }>;
|
|
26
27
|
}
|
package/src/types/layout.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface LayoutZone {
|
|
|
14
14
|
type?: 'slot';
|
|
15
15
|
component?: string;
|
|
16
16
|
class?: string;
|
|
17
|
+
style?: Record<string, string>;
|
|
17
18
|
container?: boolean;
|
|
18
19
|
containerStyle?: Record<string, string>;
|
|
19
20
|
}
|
|
@@ -50,7 +51,13 @@ export const layoutSchema: LayoutSchemaDefinition = {
|
|
|
50
51
|
description: 'CSS Grid layout with responsive breakpoints and component zones',
|
|
51
52
|
fields: [
|
|
52
53
|
// Mobile (default)
|
|
53
|
-
{
|
|
54
|
+
{
|
|
55
|
+
key: 'grid.default.areas',
|
|
56
|
+
type: 'grid-areas',
|
|
57
|
+
label: 'Grid Areas (mobile)',
|
|
58
|
+
breakpoint: 'default',
|
|
59
|
+
required: true,
|
|
60
|
+
},
|
|
54
61
|
{ key: 'grid.default.columns', type: 'text', label: 'Columns (mobile)', breakpoint: 'default' },
|
|
55
62
|
{ key: 'grid.default.rows', type: 'text', label: 'Rows (mobile)', breakpoint: 'default' },
|
|
56
63
|
{ key: 'grid.default.gap', type: 'text', label: 'Gap (mobile)', breakpoint: 'default' },
|
|
@@ -145,7 +152,12 @@ export const layoutPresets: Record<string, { displayName: string; config: Layout
|
|
|
145
152
|
config: {
|
|
146
153
|
grid: {
|
|
147
154
|
default: { areas: ['header', 'main', 'aside', 'footer'], columns: '1fr', rows: 'auto 1fr auto auto' },
|
|
148
|
-
md: {
|
|
155
|
+
md: {
|
|
156
|
+
areas: ['header header header', 'main main aside', 'footer footer footer'],
|
|
157
|
+
columns: '1fr 1fr 300px',
|
|
158
|
+
rows: 'auto 1fr auto',
|
|
159
|
+
gap: '24px',
|
|
160
|
+
},
|
|
149
161
|
},
|
|
150
162
|
zones: {
|
|
151
163
|
header: { component: 'nav-menu' },
|