@xosen/site-sdk 0.0.11 → 0.0.12
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/useDynamicLayout.d.ts +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +227 -149
- package/dist/types/config.d.ts +18 -0
- package/package.json +14 -14
- package/src/composables/useDynamicLayout.ts +146 -0
- package/src/index.ts +2 -0
- package/src/types/config.ts +22 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface GridBreakpoint {
|
|
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
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate responsive CSS for a layout config.
|
|
20
|
+
* Returns a CSS string with media queries for each breakpoint.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildLayoutCSS(layout: LayoutConfig, selector: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the current layout config from site data, route, and preview state.
|
|
25
|
+
*
|
|
26
|
+
* Returns:
|
|
27
|
+
* - `layoutName` — current layout name (e.g. 'default', 'sidebar')
|
|
28
|
+
* - `layoutConfig` — resolved LayoutConfig with grid + zones
|
|
29
|
+
* - `layoutCSS` — generated responsive CSS string
|
|
30
|
+
* - `layoutClass` — unique CSS class for this layout instance
|
|
31
|
+
*/
|
|
32
|
+
export declare function useDynamicLayout(): {
|
|
33
|
+
layoutName: import("vue").ComputedRef<any>;
|
|
34
|
+
layoutConfig: import("vue").ComputedRef<LayoutConfig>;
|
|
35
|
+
layoutCSS: import("vue").ComputedRef<string>;
|
|
36
|
+
layoutClass: import("vue").ComputedRef<string[]>;
|
|
37
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { usePageData } from './composables/usePageData.js';
|
|
|
7
7
|
export type { UsePageDataReturn } from './composables/usePageData.js';
|
|
8
8
|
export { useSiteComponents } from './composables/useSiteComponents.js';
|
|
9
9
|
export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
|
|
10
|
+
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
11
|
+
export type { LayoutConfig, LayoutZone, GridBreakpoint } from './composables/useDynamicLayout.js';
|
|
10
12
|
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
11
13
|
export { createSiteWorker } from './worker/index.js';
|
|
12
14
|
export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { ref as
|
|
1
|
+
import { ref as w, onMounted as T, watch as _, computed as u, onUnmounted as N, provide as I } from "vue";
|
|
2
2
|
import { useI18n as $ } from "vue-i18n";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { useRoute as P } from "vue-router";
|
|
4
|
+
function B(o, n) {
|
|
5
|
+
const t = w(null), { locale: e } = $(), a = window.__SITE_DATA__, s = a?.locale;
|
|
6
|
+
function i() {
|
|
7
|
+
return a && a[o] && e.value === s ? (t.value = a[o], !0) : !1;
|
|
7
8
|
}
|
|
8
|
-
async function
|
|
9
|
+
async function l() {
|
|
9
10
|
try {
|
|
10
11
|
const c = await fetch(`/api/content/${n}:${e.value}`);
|
|
11
12
|
c.ok && (t.value = await c.json());
|
|
@@ -13,35 +14,35 @@ function k(o, n) {
|
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
return T(() => {
|
|
16
|
-
|
|
17
|
+
i() || l();
|
|
17
18
|
}), _(e, () => {
|
|
18
|
-
|
|
19
|
+
l();
|
|
19
20
|
}), t;
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
-
const o = window.__SITE_DATA__, n = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => n.value?.navigation || []),
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
return typeof
|
|
22
|
+
function G() {
|
|
23
|
+
const o = window.__SITE_DATA__, n = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => n.value?.navigation || []), a = u(() => n.value?.locales || []), s = u(() => n.value?.defaultLocale || "en"), i = u(() => n.value?.branding || {}), l = u(() => n.value?.footer || {}), c = u(() => n.value?.features || {});
|
|
24
|
+
function f(d) {
|
|
25
|
+
const v = c.value[d];
|
|
26
|
+
return typeof v == "boolean" ? v : typeof v == "object";
|
|
26
27
|
}
|
|
27
28
|
return {
|
|
28
29
|
config: n,
|
|
29
30
|
components: t,
|
|
30
31
|
navigation: e,
|
|
31
|
-
locales:
|
|
32
|
-
defaultLocale:
|
|
33
|
-
branding:
|
|
34
|
-
footer:
|
|
32
|
+
locales: a,
|
|
33
|
+
defaultLocale: s,
|
|
34
|
+
branding: i,
|
|
35
|
+
footer: l,
|
|
35
36
|
features: c,
|
|
36
|
-
hasFeature:
|
|
37
|
+
hasFeature: f
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
|
-
function
|
|
40
|
+
function Y(o) {
|
|
40
41
|
function n(t) {
|
|
41
42
|
const e = document.documentElement;
|
|
42
43
|
if (t.colors)
|
|
43
|
-
for (const [
|
|
44
|
-
|
|
44
|
+
for (const [a, s] of Object.entries(t.colors))
|
|
45
|
+
s && e.style.setProperty(`--x-color-${x(a)}`, s);
|
|
45
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);
|
|
46
47
|
}
|
|
47
48
|
T(() => {
|
|
@@ -57,177 +58,252 @@ function U(o) {
|
|
|
57
58
|
});
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
+
function x(o) {
|
|
61
62
|
return o.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
62
63
|
}
|
|
63
|
-
function
|
|
64
|
+
function X() {
|
|
64
65
|
const n = window.__SITE_DATA__?.apiBase || "";
|
|
65
|
-
async function t(
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
68
|
-
throw new Error(`API error: ${
|
|
69
|
-
return
|
|
66
|
+
async function t(s) {
|
|
67
|
+
const i = await fetch(`${n}${s}`);
|
|
68
|
+
if (!i.ok)
|
|
69
|
+
throw new Error(`API error: ${i.status}`);
|
|
70
|
+
return i.json();
|
|
70
71
|
}
|
|
71
72
|
async function e() {
|
|
72
73
|
return t("/v1/billing/tariffs");
|
|
73
74
|
}
|
|
74
|
-
async function
|
|
75
|
+
async function a() {
|
|
75
76
|
return t("/v1/billing/services");
|
|
76
77
|
}
|
|
77
78
|
return {
|
|
78
79
|
get: t,
|
|
79
80
|
getTariffs: e,
|
|
80
|
-
getProducts:
|
|
81
|
+
getProducts: a
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
|
-
function
|
|
84
|
+
function b() {
|
|
84
85
|
try {
|
|
85
86
|
return window.self !== window.top;
|
|
86
87
|
} catch {
|
|
87
88
|
return !0;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
91
|
+
function Z(o) {
|
|
92
|
+
b() && o.beforeEach((n, t) => !t.name);
|
|
92
93
|
}
|
|
93
|
-
function
|
|
94
|
-
const o =
|
|
94
|
+
function C() {
|
|
95
|
+
const o = w(null), n = w(b());
|
|
95
96
|
function t(e) {
|
|
96
|
-
const
|
|
97
|
-
|
|
97
|
+
const a = e.data;
|
|
98
|
+
a?.type === "xosen-preview-update" && a.page && (o.value = a.page, n.value = !0);
|
|
98
99
|
}
|
|
99
100
|
return T(() => {
|
|
100
101
|
window.addEventListener("message", t);
|
|
101
|
-
}),
|
|
102
|
+
}), N(() => {
|
|
102
103
|
window.removeEventListener("message", t);
|
|
103
104
|
}), {
|
|
104
105
|
previewPage: o,
|
|
105
106
|
isPreviewMode: n
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
|
-
const
|
|
109
|
-
function
|
|
110
|
-
const { locale: n } = $(), { previewPage: t, isPreviewMode: e } =
|
|
111
|
-
const r = e.value ? t.value :
|
|
109
|
+
const D = /* @__PURE__ */ Symbol("pageContext");
|
|
110
|
+
function H(o) {
|
|
111
|
+
const { locale: n } = $(), { previewPage: t, isPreviewMode: e } = C(), a = window.__SITE_DATA__, s = w(null), i = w(!0), l = w(!1), c = u(() => typeof o == "string" ? o : o.value), f = u(() => `page:${c.value}`), d = u(() => {
|
|
112
|
+
const r = e.value ? t.value : s.value;
|
|
112
113
|
if (!r) return {};
|
|
113
|
-
const { blocks:
|
|
114
|
-
return
|
|
114
|
+
const { blocks: y, meta: p, slug: m, locale: z, ...L } = r;
|
|
115
|
+
return L;
|
|
115
116
|
});
|
|
116
|
-
|
|
117
|
-
const
|
|
117
|
+
I(D, d);
|
|
118
|
+
const v = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : s.value?.blocks || []);
|
|
118
119
|
function S() {
|
|
119
|
-
const r =
|
|
120
|
-
return r && n.value ===
|
|
120
|
+
const r = a?.[f.value];
|
|
121
|
+
return r && n.value === a?.locale ? (s.value = r, l.value = !1, h(), !0) : !1;
|
|
121
122
|
}
|
|
122
|
-
async function
|
|
123
|
-
|
|
123
|
+
async function g() {
|
|
124
|
+
i.value = !0, l.value = !1;
|
|
124
125
|
try {
|
|
125
|
-
const r = await fetch(`/api/content/${
|
|
126
|
-
r.ok ? (
|
|
126
|
+
const r = await fetch(`/api/content/${f.value}:${n.value}`);
|
|
127
|
+
r.ok ? (s.value = await r.json(), h()) : (s.value = null, l.value = !0);
|
|
127
128
|
} catch {
|
|
128
|
-
|
|
129
|
+
s.value = null, l.value = !0;
|
|
129
130
|
} finally {
|
|
130
|
-
|
|
131
|
+
i.value = !1;
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
|
-
function
|
|
134
|
-
if (!
|
|
135
|
-
if (
|
|
136
|
-
const
|
|
137
|
-
document.title = `${
|
|
134
|
+
function h() {
|
|
135
|
+
if (!s.value) return;
|
|
136
|
+
if (s.value.title) {
|
|
137
|
+
const y = a?.config?.branding, p = y?.siteName ? ` — ${y.siteName}` : "";
|
|
138
|
+
document.title = `${s.value.title}${p}`;
|
|
138
139
|
}
|
|
139
|
-
const r =
|
|
140
|
+
const r = s.value.meta?.description;
|
|
140
141
|
if (r) {
|
|
141
|
-
const
|
|
142
|
-
|
|
142
|
+
const y = document.querySelector('meta[name="description"]');
|
|
143
|
+
y && y.setAttribute("content", r);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
return _(t, (r) => {
|
|
146
|
-
r?.slug === c.value && (
|
|
147
|
-
...
|
|
147
|
+
r?.slug === c.value && (i.value = !1, l.value = !1, (r.title || r.blocks) && (s.value = {
|
|
148
|
+
...s.value || {},
|
|
148
149
|
...r.title ? { title: r.title } : {},
|
|
149
150
|
...r.blocks ? { blocks: r.blocks } : {}
|
|
150
|
-
},
|
|
151
|
+
}, h()));
|
|
151
152
|
}), T(() => {
|
|
152
|
-
S() ?
|
|
153
|
-
}), _(n, () =>
|
|
154
|
-
S() ?
|
|
153
|
+
S() ? i.value = !1 : g();
|
|
154
|
+
}), _(n, () => g()), _(c, () => {
|
|
155
|
+
S() ? i.value = !1 : g();
|
|
155
156
|
}), {
|
|
156
|
-
page:
|
|
157
|
-
blocks:
|
|
158
|
-
pageContext:
|
|
159
|
-
loading:
|
|
160
|
-
notFound:
|
|
157
|
+
page: s,
|
|
158
|
+
blocks: v,
|
|
159
|
+
pageContext: d,
|
|
160
|
+
loading: i,
|
|
161
|
+
notFound: l,
|
|
161
162
|
isPreviewMode: e,
|
|
162
|
-
reload:
|
|
163
|
+
reload: g
|
|
163
164
|
};
|
|
164
165
|
}
|
|
165
|
-
function
|
|
166
|
-
const { locale: o } = $(), n = window.__SITE_DATA__, t = n?.locale, e =
|
|
167
|
-
async function
|
|
168
|
-
if (
|
|
166
|
+
function Q() {
|
|
167
|
+
const { locale: o } = $(), n = window.__SITE_DATA__, t = n?.locale, e = w({ ...n?.components || {} });
|
|
168
|
+
async function a(i) {
|
|
169
|
+
if (i === t) {
|
|
169
170
|
e.value = { ...n?.components || {} };
|
|
170
171
|
return;
|
|
171
172
|
}
|
|
172
173
|
try {
|
|
173
|
-
const
|
|
174
|
-
|
|
174
|
+
const l = await fetch(`/api/content/components:${i}`);
|
|
175
|
+
l.ok && (e.value = await l.json());
|
|
175
176
|
} catch {
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
|
-
_(o, (
|
|
179
|
-
o.value !== t &&
|
|
179
|
+
_(o, (i) => a(i)), T(() => {
|
|
180
|
+
o.value !== t && a(o.value);
|
|
180
181
|
});
|
|
181
|
-
function
|
|
182
|
-
return u(() => e.value[
|
|
182
|
+
function s(i) {
|
|
183
|
+
return u(() => e.value[i] || null);
|
|
183
184
|
}
|
|
184
185
|
return {
|
|
185
186
|
/** All components (reactive, locale-aware) */
|
|
186
187
|
components: e,
|
|
187
188
|
/** Get a single component by key */
|
|
188
|
-
getComponent:
|
|
189
|
+
getComponent: s,
|
|
189
190
|
/** Force refetch for current locale */
|
|
190
|
-
reload: () =>
|
|
191
|
+
reload: () => a(o.value)
|
|
191
192
|
};
|
|
192
193
|
}
|
|
193
|
-
function
|
|
194
|
-
const { locale: o } = $(), n = window.__SITE_DATA__, t = u(() => n?.config?.locales || []), e = u(() => n?.config?.defaultLocale || "en"),
|
|
195
|
-
function
|
|
196
|
-
o.value =
|
|
194
|
+
function V() {
|
|
195
|
+
const { locale: o } = $(), n = window.__SITE_DATA__, t = u(() => n?.config?.locales || []), e = u(() => n?.config?.defaultLocale || "en"), a = u(() => t.value.length > 1);
|
|
196
|
+
function s(l) {
|
|
197
|
+
o.value = l, localStorage.setItem("x-site-locale", l);
|
|
197
198
|
}
|
|
198
|
-
function l
|
|
199
|
-
return typeof
|
|
199
|
+
function i(l) {
|
|
200
|
+
return typeof l == "string" ? l : l[o.value] || l[e.value] || Object.values(l)[0] || "";
|
|
200
201
|
}
|
|
201
202
|
return {
|
|
202
203
|
locale: o,
|
|
203
204
|
locales: t,
|
|
204
205
|
defaultLocale: e,
|
|
205
|
-
hasMultipleLocales:
|
|
206
|
-
switchLocale:
|
|
207
|
-
resolveText:
|
|
206
|
+
hasMultipleLocales: a,
|
|
207
|
+
switchLocale: s,
|
|
208
|
+
resolveText: i
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const j = {
|
|
212
|
+
sm: 600,
|
|
213
|
+
md: 960,
|
|
214
|
+
lg: 1280,
|
|
215
|
+
xl: 1920
|
|
216
|
+
}, O = {
|
|
217
|
+
grid: {
|
|
218
|
+
default: {
|
|
219
|
+
areas: ["header", "main", "footer"],
|
|
220
|
+
rows: "auto 1fr auto"
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
zones: {
|
|
224
|
+
header: { component: "nav-menu" },
|
|
225
|
+
main: { type: "slot" },
|
|
226
|
+
footer: { component: "footer" }
|
|
227
|
+
}
|
|
228
|
+
}, R = {
|
|
229
|
+
grid: {
|
|
230
|
+
default: {
|
|
231
|
+
areas: ["main"]
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
zones: {
|
|
235
|
+
main: { type: "slot" }
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function A(o, n) {
|
|
239
|
+
const t = [];
|
|
240
|
+
if (o.areas?.length) {
|
|
241
|
+
const e = o.areas.map((a) => `"${a}"`).join(" ");
|
|
242
|
+
t.push(`grid-template-areas: ${e}`);
|
|
243
|
+
}
|
|
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 ? `${n} { ${t.join("; ")}; }` : "";
|
|
245
|
+
}
|
|
246
|
+
function F(o, n) {
|
|
247
|
+
const t = [`${n} { display: grid; min-height: 100vh; }`], e = o.grid;
|
|
248
|
+
if (!e) return t.join(`
|
|
249
|
+
`);
|
|
250
|
+
if (e.default) {
|
|
251
|
+
const a = A(e.default, n);
|
|
252
|
+
a && t.push(a);
|
|
253
|
+
}
|
|
254
|
+
for (const a of ["sm", "md", "lg", "xl"]) {
|
|
255
|
+
const s = e[a];
|
|
256
|
+
if (!s) continue;
|
|
257
|
+
const i = A(s, n);
|
|
258
|
+
i && t.push(`@media (min-width: ${j[a]}px) { ${i} }`);
|
|
259
|
+
}
|
|
260
|
+
return t.join(`
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
263
|
+
function q() {
|
|
264
|
+
const o = P(), { previewPage: n, isPreviewMode: t } = C(), e = window.__SITE_DATA__, a = `dl-${Math.random().toString(36).slice(2, 8)}`, s = u(() => ["dynamic-layout", a]), i = u(() => {
|
|
265
|
+
if (o.path === "/_preview_")
|
|
266
|
+
return n.value?.layout || "landing";
|
|
267
|
+
if (t.value && n.value?.layout)
|
|
268
|
+
return n.value.layout;
|
|
269
|
+
const f = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
|
|
270
|
+
return e?.[f]?.layout || "default";
|
|
271
|
+
}), l = u(() => {
|
|
272
|
+
const f = e?.config?.layouts;
|
|
273
|
+
if (f) {
|
|
274
|
+
const d = f[i.value] || f.default;
|
|
275
|
+
if (d?.zones) return d;
|
|
276
|
+
}
|
|
277
|
+
return i.value === "landing" ? R : O;
|
|
278
|
+
}), c = u(() => F(l.value, `.${a}`));
|
|
279
|
+
return {
|
|
280
|
+
layoutName: i,
|
|
281
|
+
layoutConfig: l,
|
|
282
|
+
layoutCSS: c,
|
|
283
|
+
layoutClass: s
|
|
208
284
|
};
|
|
209
285
|
}
|
|
210
|
-
const
|
|
211
|
-
function
|
|
286
|
+
const M = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
|
|
287
|
+
function k(o, n) {
|
|
212
288
|
const e = new URL(o.url).searchParams.get("lang");
|
|
213
289
|
if (e && n.supportedLocales?.includes(e)) return e;
|
|
214
290
|
if (n.supportedLocales?.length) {
|
|
215
|
-
const
|
|
216
|
-
for (const
|
|
217
|
-
if (
|
|
291
|
+
const a = o.headers.get("Accept-Language") || "";
|
|
292
|
+
for (const s of n.supportedLocales)
|
|
293
|
+
if (a.includes(s)) return s;
|
|
218
294
|
}
|
|
219
295
|
return n.defaultLocale;
|
|
220
296
|
}
|
|
221
|
-
function
|
|
297
|
+
function U(o, n, t) {
|
|
222
298
|
if (!o.pathname.startsWith("/api/content/")) return null;
|
|
223
299
|
const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
|
|
224
300
|
return e ? (async () => {
|
|
225
|
-
let
|
|
226
|
-
if (!
|
|
227
|
-
const
|
|
228
|
-
|
|
301
|
+
let a = await n.SITE_CONTENT.get(e);
|
|
302
|
+
if (!a) {
|
|
303
|
+
const s = e.lastIndexOf(":");
|
|
304
|
+
s > 0 && e.slice(s + 1) !== t && (a = await n.SITE_CONTENT.get(e.slice(0, s + 1) + t));
|
|
229
305
|
}
|
|
230
|
-
return
|
|
306
|
+
return a ? new Response(a, {
|
|
231
307
|
headers: {
|
|
232
308
|
"Content-Type": "application/json",
|
|
233
309
|
"Cache-Control": "public, max-age=60",
|
|
@@ -236,53 +312,53 @@ function j(o, n, t) {
|
|
|
236
312
|
}) : Response.json({ error: "Not found" }, { status: 404 });
|
|
237
313
|
})() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
|
|
238
314
|
}
|
|
239
|
-
function
|
|
315
|
+
function E(o, n, t) {
|
|
240
316
|
let e = o;
|
|
241
317
|
if (n.title) {
|
|
242
|
-
const
|
|
243
|
-
e = e.replace(/<title>[^<]*<\/title>/, `<title>${n.title}${
|
|
318
|
+
const a = t ? ` — ${t}` : "";
|
|
319
|
+
e = e.replace(/<title>[^<]*<\/title>/, `<title>${n.title}${a}</title>`);
|
|
244
320
|
}
|
|
245
321
|
return n.meta?.description && (e = e.replace(/(<meta\s+name="description"\s+content=")[^"]*(")/, `$1${n.meta.description}$2`)), n.meta?.ogTitle && (e = e.replace(/(<meta\s+property="og:title"\s+content=")[^"]*(")/, `$1${n.meta.ogTitle}$2`)), n.html && (e = e.replace('<div id="app"></div>', `<div id="app">${n.html}</div>`)), e;
|
|
246
322
|
}
|
|
247
|
-
function
|
|
323
|
+
function ee(o) {
|
|
248
324
|
return {
|
|
249
325
|
async fetch(n, t) {
|
|
250
|
-
const e = new URL(n.url),
|
|
251
|
-
if (
|
|
252
|
-
if (
|
|
326
|
+
const e = new URL(n.url), a = U(e, t, o.defaultLocale);
|
|
327
|
+
if (a) return a;
|
|
328
|
+
if (M.test(e.pathname))
|
|
253
329
|
return t.ASSETS.fetch(n);
|
|
254
|
-
const
|
|
255
|
-
let c = await (await t.ASSETS.fetch(new Request(
|
|
256
|
-
const
|
|
330
|
+
const s = k(n, o), i = new URL("/index.html", n.url);
|
|
331
|
+
let c = await (await t.ASSETS.fetch(new Request(i))).text();
|
|
332
|
+
const f = o.defaultLocale, [d, v] = await Promise.all([
|
|
257
333
|
t.SITE_CONTENT.get("config"),
|
|
258
|
-
t.SITE_CONTENT.get(`content:${
|
|
259
|
-
]), S =
|
|
260
|
-
let
|
|
261
|
-
if (!
|
|
262
|
-
const
|
|
263
|
-
|
|
334
|
+
t.SITE_CONTENT.get(`content:${s}`)
|
|
335
|
+
]), S = d ? JSON.parse(d) : {};
|
|
336
|
+
let g = v ? JSON.parse(v) : {};
|
|
337
|
+
if (!v && s !== f) {
|
|
338
|
+
const p = await t.SITE_CONTENT.get(`content:${f}`);
|
|
339
|
+
p && (g = JSON.parse(p));
|
|
264
340
|
}
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
const
|
|
268
|
-
if (!p
|
|
269
|
-
let
|
|
270
|
-
!
|
|
341
|
+
const h = e.pathname.match(/^\/p\/(.+)$/);
|
|
342
|
+
if (h) {
|
|
343
|
+
const p = h[1];
|
|
344
|
+
if (!g[p]) {
|
|
345
|
+
let m = await t.SITE_CONTENT.get(`page:${p}:${s}`);
|
|
346
|
+
!m && s !== f && (m = await t.SITE_CONTENT.get(`page:${p}:${f}`)), m && (g[p] = JSON.parse(m));
|
|
271
347
|
}
|
|
272
348
|
}
|
|
273
349
|
const r = {
|
|
274
|
-
locale:
|
|
350
|
+
locale: s,
|
|
275
351
|
config: S
|
|
276
352
|
};
|
|
277
|
-
for (const [
|
|
278
|
-
r[`page:${
|
|
279
|
-
if (
|
|
280
|
-
const
|
|
281
|
-
|
|
353
|
+
for (const [p, m] of Object.entries(g))
|
|
354
|
+
r[`page:${p}`] = m;
|
|
355
|
+
if (h) {
|
|
356
|
+
const p = h[1], m = r[`page:${p}`];
|
|
357
|
+
m && (c = E(c, m, o.siteName));
|
|
282
358
|
}
|
|
283
|
-
e.pathname === "/" &&
|
|
284
|
-
const
|
|
285
|
-
return c = c.replace("</head>", `${
|
|
359
|
+
e.pathname === "/" && g.home && (c = E(c, g.home, o.siteName));
|
|
360
|
+
const y = `<script>window.__SITE_DATA__ = ${JSON.stringify(r)};<\/script>`;
|
|
361
|
+
return c = c.replace("</head>", `${y}
|
|
286
362
|
</head>`), new Response(c, {
|
|
287
363
|
headers: {
|
|
288
364
|
"Content-Type": "text/html;charset=utf-8",
|
|
@@ -293,16 +369,18 @@ function X(o) {
|
|
|
293
369
|
};
|
|
294
370
|
}
|
|
295
371
|
export {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
372
|
+
D as PAGE_CONTEXT_KEY,
|
|
373
|
+
F as buildLayoutCSS,
|
|
374
|
+
ee as createSiteWorker,
|
|
375
|
+
b as isInPreview,
|
|
376
|
+
Z as setupPreviewRouter,
|
|
377
|
+
q as useDynamicLayout,
|
|
378
|
+
C as useLivePreview,
|
|
379
|
+
V as useLocaleSwitcher,
|
|
380
|
+
H as usePageData,
|
|
381
|
+
X as useSiteApi,
|
|
382
|
+
Q as useSiteComponents,
|
|
383
|
+
G as useSiteConfig,
|
|
384
|
+
B as useSiteData,
|
|
385
|
+
Y as useSkin
|
|
308
386
|
};
|
package/dist/types/config.d.ts
CHANGED
|
@@ -33,6 +33,24 @@ export interface SiteConfig {
|
|
|
33
33
|
}>;
|
|
34
34
|
};
|
|
35
35
|
features?: Record<string, boolean | Record<string, any>>;
|
|
36
|
+
layouts?: Record<string, LayoutConfig>;
|
|
37
|
+
}
|
|
38
|
+
export interface LayoutZone {
|
|
39
|
+
type?: 'slot';
|
|
40
|
+
component?: string;
|
|
41
|
+
class?: string;
|
|
42
|
+
container?: boolean;
|
|
43
|
+
containerStyle?: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
export interface LayoutGridBreakpoint {
|
|
46
|
+
areas: string[];
|
|
47
|
+
columns?: string;
|
|
48
|
+
rows?: string;
|
|
49
|
+
gap?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface LayoutConfig {
|
|
52
|
+
grid: Record<string, LayoutGridBreakpoint>;
|
|
53
|
+
zones: Record<string, LayoutZone>;
|
|
36
54
|
}
|
|
37
55
|
export interface NavItem {
|
|
38
56
|
text: string | Record<string, string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xosen/site-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
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
|
-
},
|
|
32
27
|
"peerDependencies": {
|
|
33
28
|
"vue": "^3.5.0",
|
|
34
29
|
"vue-i18n": "^11.0.0",
|
|
35
30
|
"vue-router": "^5.0.0"
|
|
36
31
|
},
|
|
37
32
|
"devDependencies": {
|
|
38
|
-
"vue": "
|
|
39
|
-
"vue-i18n": "
|
|
40
|
-
"vue-router": "
|
|
41
|
-
"vue-tsc": "
|
|
42
|
-
"typescript": "
|
|
43
|
-
"vite": "
|
|
44
|
-
"@vitejs/plugin-vue": "
|
|
33
|
+
"vue": "^3.5.30",
|
|
34
|
+
"vue-i18n": "^11.3.0",
|
|
35
|
+
"vue-router": "^5.0.3",
|
|
36
|
+
"vue-tsc": "^3.2.5",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vite": "^7.3.1",
|
|
39
|
+
"@vitejs/plugin-vue": "^6.0.5"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
43
|
+
"dev": "vite build --watch",
|
|
44
|
+
"clean": "rm -rf dist"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { computed, type Ref } from 'vue';
|
|
2
|
+
import { useRoute } from 'vue-router';
|
|
3
|
+
|
|
4
|
+
import type { SiteData } from '../types/config.js';
|
|
5
|
+
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
|
+
}
|
|
26
|
+
|
|
27
|
+
const BREAKPOINTS: Record<string, number> = {
|
|
28
|
+
sm: 600,
|
|
29
|
+
md: 960,
|
|
30
|
+
lg: 1280,
|
|
31
|
+
xl: 1920,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const DEFAULT_LAYOUT: LayoutConfig = {
|
|
35
|
+
grid: {
|
|
36
|
+
default: {
|
|
37
|
+
areas: ['header', 'main', 'footer'],
|
|
38
|
+
rows: 'auto 1fr auto',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
zones: {
|
|
42
|
+
header: { component: 'nav-menu' },
|
|
43
|
+
main: { type: 'slot' },
|
|
44
|
+
footer: { component: 'footer' },
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const LANDING_LAYOUT: LayoutConfig = {
|
|
49
|
+
grid: {
|
|
50
|
+
default: {
|
|
51
|
+
areas: ['main'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
zones: {
|
|
55
|
+
main: { type: 'slot' },
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function buildGridCSS(bp: GridBreakpoint, selector: string): string {
|
|
60
|
+
const rules: string[] = [];
|
|
61
|
+
if (bp.areas?.length) {
|
|
62
|
+
const areas = bp.areas.map((row) => `"${row}"`).join(' ');
|
|
63
|
+
rules.push(`grid-template-areas: ${areas}`);
|
|
64
|
+
}
|
|
65
|
+
if (bp.columns) rules.push(`grid-template-columns: ${bp.columns}`);
|
|
66
|
+
if (bp.rows) rules.push(`grid-template-rows: ${bp.rows}`);
|
|
67
|
+
if (bp.gap) rules.push(`gap: ${bp.gap}`);
|
|
68
|
+
if (!rules.length) return '';
|
|
69
|
+
return `${selector} { ${rules.join('; ')}; }`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate responsive CSS for a layout config.
|
|
74
|
+
* Returns a CSS string with media queries for each breakpoint.
|
|
75
|
+
*/
|
|
76
|
+
export function buildLayoutCSS(layout: LayoutConfig, selector: string): string {
|
|
77
|
+
const parts: string[] = [`${selector} { display: grid; min-height: 100vh; }`];
|
|
78
|
+
const grid = layout.grid;
|
|
79
|
+
|
|
80
|
+
if (!grid) return parts.join('\n');
|
|
81
|
+
|
|
82
|
+
if (grid.default) {
|
|
83
|
+
const css = buildGridCSS(grid.default, selector);
|
|
84
|
+
if (css) parts.push(css);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const bp of ['sm', 'md', 'lg', 'xl']) {
|
|
88
|
+
const config = grid[bp];
|
|
89
|
+
if (!config) continue;
|
|
90
|
+
const css = buildGridCSS(config, selector);
|
|
91
|
+
if (css) {
|
|
92
|
+
parts.push(`@media (min-width: ${BREAKPOINTS[bp]}px) { ${css} }`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parts.join('\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the current layout config from site data, route, and preview state.
|
|
101
|
+
*
|
|
102
|
+
* Returns:
|
|
103
|
+
* - `layoutName` — current layout name (e.g. 'default', 'sidebar')
|
|
104
|
+
* - `layoutConfig` — resolved LayoutConfig with grid + zones
|
|
105
|
+
* - `layoutCSS` — generated responsive CSS string
|
|
106
|
+
* - `layoutClass` — unique CSS class for this layout instance
|
|
107
|
+
*/
|
|
108
|
+
export function useDynamicLayout() {
|
|
109
|
+
const route = useRoute();
|
|
110
|
+
const { previewPage, isPreviewMode } = useLivePreview();
|
|
111
|
+
const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
|
|
112
|
+
|
|
113
|
+
const layoutId = `dl-${Math.random().toString(36).slice(2, 8)}`;
|
|
114
|
+
const layoutClass = computed(() => ['dynamic-layout', layoutId]);
|
|
115
|
+
|
|
116
|
+
const layoutName = computed(() => {
|
|
117
|
+
if (route.path === '/_preview_') {
|
|
118
|
+
return previewPage.value?.layout || 'landing';
|
|
119
|
+
}
|
|
120
|
+
if (isPreviewMode.value && previewPage.value?.layout) {
|
|
121
|
+
return previewPage.value.layout;
|
|
122
|
+
}
|
|
123
|
+
const pageKey = route.path === '/' ? 'page:home' : `page:${route.params.slug || ''}`;
|
|
124
|
+
const pageData = siteData?.[pageKey];
|
|
125
|
+
return pageData?.layout || 'default';
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const layoutConfig = computed<LayoutConfig>(() => {
|
|
129
|
+
const layouts = siteData?.config?.layouts;
|
|
130
|
+
if (layouts) {
|
|
131
|
+
const config = layouts[layoutName.value] || layouts.default;
|
|
132
|
+
if (config?.zones) return config;
|
|
133
|
+
}
|
|
134
|
+
if (layoutName.value === 'landing') return LANDING_LAYOUT;
|
|
135
|
+
return DEFAULT_LAYOUT;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const layoutCSS = computed(() => buildLayoutCSS(layoutConfig.value, `.${layoutId}`));
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
layoutName,
|
|
142
|
+
layoutConfig,
|
|
143
|
+
layoutCSS,
|
|
144
|
+
layoutClass,
|
|
145
|
+
};
|
|
146
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { usePageData } from './composables/usePageData.js';
|
|
|
8
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
|
+
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
12
|
+
export type { LayoutConfig, LayoutZone, GridBreakpoint } from './composables/useDynamicLayout.js';
|
|
11
13
|
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
12
14
|
|
|
13
15
|
// Worker
|
package/src/types/config.ts
CHANGED
|
@@ -38,6 +38,28 @@ export interface SiteConfig {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
features?: Record<string, boolean | Record<string, any>>;
|
|
41
|
+
|
|
42
|
+
layouts?: Record<string, LayoutConfig>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LayoutZone {
|
|
46
|
+
type?: 'slot';
|
|
47
|
+
component?: string;
|
|
48
|
+
class?: string;
|
|
49
|
+
container?: boolean;
|
|
50
|
+
containerStyle?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LayoutGridBreakpoint {
|
|
54
|
+
areas: string[];
|
|
55
|
+
columns?: string;
|
|
56
|
+
rows?: string;
|
|
57
|
+
gap?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface LayoutConfig {
|
|
61
|
+
grid: Record<string, LayoutGridBreakpoint>;
|
|
62
|
+
zones: Record<string, LayoutZone>;
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
export interface NavItem {
|