@xosen/site-sdk 0.0.7 → 0.0.9
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 +54 -0
- package/dist/composables/useSiteApi.d.ts +9 -0
- package/dist/composables/useSiteConfig.d.ts +25 -0
- package/dist/composables/useSiteData.d.ts +8 -0
- package/dist/composables/useSkin.d.ts +6 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +65 -63
- package/dist/types/blocks.d.ts +103 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/skin.d.ts +41 -0
- package/dist/worker/create-site-worker.d.ts +14 -0
- package/dist/worker/index.d.ts +2 -0
- package/dist/worker/types.d.ts +41 -0
- package/package.json +5 -5
- package/src/composables/useLivePreview.ts +2 -0
- package/src/index.ts +2 -1
- package/src/types/blocks.ts +1 -1
- package/src/types/config.ts +15 -0
- package/src/worker/types.ts +6 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Router } from 'vue-router';
|
|
2
|
+
export interface PreviewMessage {
|
|
3
|
+
type: 'xosen-preview-update';
|
|
4
|
+
page: {
|
|
5
|
+
slug: string;
|
|
6
|
+
locale: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
blocks?: any[];
|
|
9
|
+
meta?: Record<string, any>;
|
|
10
|
+
layout?: string;
|
|
11
|
+
/** Page-level context fields (author, featuredImage, excerpt, etc.) */
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Check if running inside an iframe (preview mode) */
|
|
16
|
+
export declare function isInPreview(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Install a router guard that blocks all navigation in preview mode.
|
|
19
|
+
* Call this once during app setup (e.g. in App.vue or router config).
|
|
20
|
+
*/
|
|
21
|
+
export declare function setupPreviewRouter(router: Router): void;
|
|
22
|
+
/**
|
|
23
|
+
* Listen for live preview messages from the admin UI.
|
|
24
|
+
* When the admin sends page data via postMessage, this composable
|
|
25
|
+
* provides reactive access to the preview data.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useLivePreview(): {
|
|
28
|
+
previewPage: import("vue").Ref<{
|
|
29
|
+
[x: string]: any;
|
|
30
|
+
slug: string;
|
|
31
|
+
locale: string;
|
|
32
|
+
title?: string | undefined;
|
|
33
|
+
blocks?: any[] | undefined;
|
|
34
|
+
meta?: Record<string, any> | undefined;
|
|
35
|
+
layout?: string | undefined;
|
|
36
|
+
} | null, {
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
slug: string;
|
|
39
|
+
locale: string;
|
|
40
|
+
title?: string;
|
|
41
|
+
blocks?: any[];
|
|
42
|
+
meta?: Record<string, any>;
|
|
43
|
+
layout?: string;
|
|
44
|
+
} | {
|
|
45
|
+
[x: string]: any;
|
|
46
|
+
slug: string;
|
|
47
|
+
locale: string;
|
|
48
|
+
title?: string | undefined;
|
|
49
|
+
blocks?: any[] | undefined;
|
|
50
|
+
meta?: Record<string, any> | undefined;
|
|
51
|
+
layout?: string | undefined;
|
|
52
|
+
} | null>;
|
|
53
|
+
isPreviewMode: import("vue").Ref<boolean, boolean>;
|
|
54
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch data from the Xosen org API (for live data like tariffs, products).
|
|
3
|
+
* Uses the API base URL from site config.
|
|
4
|
+
*/
|
|
5
|
+
export declare function useSiteApi(): {
|
|
6
|
+
get: <T>(path: string) => Promise<T>;
|
|
7
|
+
getTariffs: () => Promise<any[]>;
|
|
8
|
+
getProducts: () => Promise<any[]>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SiteConfig } from '../types/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Access the site configuration injected by the CF Worker.
|
|
4
|
+
*/
|
|
5
|
+
export declare function useSiteConfig(): {
|
|
6
|
+
config: import("vue").ComputedRef<SiteConfig | null>;
|
|
7
|
+
components: import("vue").ComputedRef<Record<string, any>>;
|
|
8
|
+
navigation: import("vue").ComputedRef<import("../index.js").NavItem[]>;
|
|
9
|
+
locales: import("vue").ComputedRef<string[]>;
|
|
10
|
+
defaultLocale: import("vue").ComputedRef<string>;
|
|
11
|
+
branding: import("vue").ComputedRef<{
|
|
12
|
+
logo?: string;
|
|
13
|
+
favicon?: string;
|
|
14
|
+
siteName?: string;
|
|
15
|
+
}>;
|
|
16
|
+
footer: import("vue").ComputedRef<{
|
|
17
|
+
copyright?: string;
|
|
18
|
+
links?: Array<{
|
|
19
|
+
text: string;
|
|
20
|
+
url: string;
|
|
21
|
+
}>;
|
|
22
|
+
}>;
|
|
23
|
+
features: import("vue").ComputedRef<Record<string, boolean | Record<string, any>>>;
|
|
24
|
+
hasFeature: (key: string) => boolean;
|
|
25
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Read preloaded site data from window.__SITE_DATA__ or fetch from API.
|
|
4
|
+
*
|
|
5
|
+
* @param key - Key in __SITE_DATA__ object (e.g. 'tariffs', 'contacts:offices')
|
|
6
|
+
* @param kvKey - KV key suffix for API fallback (e.g. 'tariffs', 'contacts:offices')
|
|
7
|
+
*/
|
|
8
|
+
export declare function useSiteData<T>(key: string, kvKey: string): Ref<T | null>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { useSiteData } from './composables/useSiteData.js';
|
|
2
|
+
export { useSiteConfig } from './composables/useSiteConfig.js';
|
|
3
|
+
export { useSkin } from './composables/useSkin.js';
|
|
4
|
+
export { useSiteApi } from './composables/useSiteApi.js';
|
|
5
|
+
export { useLivePreview, setupPreviewRouter, isInPreview } from './composables/useLivePreview.js';
|
|
6
|
+
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
7
|
+
export { createSiteWorker } from './worker/index.js';
|
|
8
|
+
export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
|
|
9
|
+
export type { Block, BlockSettings, HeroBlockData, HtmlBlockData, CardsBlockData, ImageTextBlockData, PricingBlockData, PricingPlan, ContactsBlockData, Office, GalleryBlockData, MapBlockData, } from './types/blocks.js';
|
|
10
|
+
export type { SiteConfig, NavItem, PageConfig, SiteData, PageContext } from './types/config.js';
|
|
11
|
+
export { PAGE_CONTEXT_KEY } from './types/config.js';
|
|
12
|
+
export type { Skin } from './types/skin.js';
|
package/dist/index.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { ref as y, onMounted as T, watch as
|
|
2
|
-
import { useI18n as
|
|
3
|
-
function
|
|
4
|
-
const t = y(null), { locale: e } =
|
|
1
|
+
import { ref as y, onMounted as T, watch as $, computed as u, onUnmounted as b } from "vue";
|
|
2
|
+
import { useI18n as C } from "vue-i18n";
|
|
3
|
+
function R(n, o) {
|
|
4
|
+
const t = y(null), { locale: e } = C(), a = window.__SITE_DATA__, s = a?.locale;
|
|
5
5
|
function c() {
|
|
6
|
-
return a && a[
|
|
6
|
+
return a && a[n] && e.value === s ? (t.value = a[n], !0) : !1;
|
|
7
7
|
}
|
|
8
8
|
async function h() {
|
|
9
9
|
try {
|
|
10
|
-
const r = await fetch(`/api/content/${
|
|
10
|
+
const r = await fetch(`/api/content/${o}:${e.value}`);
|
|
11
11
|
r.ok && (t.value = await r.json());
|
|
12
12
|
} catch {
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
return T(() => {
|
|
16
16
|
c() || h();
|
|
17
|
-
}),
|
|
17
|
+
}), $(e, () => {
|
|
18
18
|
h();
|
|
19
19
|
}), t;
|
|
20
20
|
}
|
|
21
21
|
function O() {
|
|
22
|
-
const
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
return typeof
|
|
22
|
+
const n = window.__SITE_DATA__, o = u(() => n?.config || null), t = u(() => n?.components || {}), e = u(() => o.value?.navigation || []), a = u(() => o.value?.locales || []), s = u(() => o.value?.defaultLocale || "en"), c = u(() => o.value?.branding || {}), h = u(() => o.value?.footer || {}), r = u(() => o.value?.features || {});
|
|
23
|
+
function f(g) {
|
|
24
|
+
const p = r.value[g];
|
|
25
|
+
return typeof p == "boolean" ? p : typeof p == "object";
|
|
26
26
|
}
|
|
27
27
|
return {
|
|
28
|
-
config:
|
|
28
|
+
config: o,
|
|
29
29
|
components: t,
|
|
30
30
|
navigation: e,
|
|
31
31
|
locales: a,
|
|
@@ -33,37 +33,37 @@ function O() {
|
|
|
33
33
|
branding: c,
|
|
34
34
|
footer: h,
|
|
35
35
|
features: r,
|
|
36
|
-
hasFeature:
|
|
36
|
+
hasFeature: f
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
function j(
|
|
40
|
-
function
|
|
39
|
+
function j(n) {
|
|
40
|
+
function o(t) {
|
|
41
41
|
const e = document.documentElement;
|
|
42
42
|
if (t.colors)
|
|
43
43
|
for (const [a, s] of Object.entries(t.colors))
|
|
44
|
-
s && e.style.setProperty(`--x-color-${
|
|
44
|
+
s && e.style.setProperty(`--x-color-${x(a)}`, s);
|
|
45
45
|
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
46
|
}
|
|
47
47
|
T(() => {
|
|
48
|
-
if (
|
|
49
|
-
n
|
|
48
|
+
if (n) {
|
|
49
|
+
o(n);
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const e = window.__SITE_DATA__?.skin;
|
|
53
|
-
e && (e.colors || e.typography || e.shape) &&
|
|
53
|
+
e && (e.colors || e.typography || e.shape) && o({
|
|
54
54
|
colors: e.colors || {},
|
|
55
55
|
typography: e.typography || {},
|
|
56
56
|
shape: e.shape || {}
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
-
function
|
|
61
|
-
return
|
|
60
|
+
function x(n) {
|
|
61
|
+
return n.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
62
62
|
}
|
|
63
63
|
function F() {
|
|
64
|
-
const
|
|
64
|
+
const o = window.__SITE_DATA__?.apiBase || "";
|
|
65
65
|
async function t(s) {
|
|
66
|
-
const c = await fetch(`${
|
|
66
|
+
const c = await fetch(`${o}${s}`);
|
|
67
67
|
if (!c.ok)
|
|
68
68
|
throw new Error(`API error: ${c.status}`);
|
|
69
69
|
return c.json();
|
|
@@ -87,43 +87,43 @@ function S() {
|
|
|
87
87
|
return !0;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
function D(
|
|
91
|
-
S() &&
|
|
90
|
+
function D(n) {
|
|
91
|
+
S() && n.beforeEach((o, t) => !t.name);
|
|
92
92
|
}
|
|
93
93
|
function U() {
|
|
94
|
-
const
|
|
94
|
+
const n = y(null), o = y(S());
|
|
95
95
|
function t(e) {
|
|
96
96
|
const a = e.data;
|
|
97
|
-
a?.type === "xosen-preview-update" && a.page && (
|
|
97
|
+
a?.type === "xosen-preview-update" && a.page && (n.value = a.page, o.value = !0);
|
|
98
98
|
}
|
|
99
99
|
return T(() => {
|
|
100
100
|
window.addEventListener("message", t);
|
|
101
101
|
}), b(() => {
|
|
102
102
|
window.removeEventListener("message", t);
|
|
103
103
|
}), {
|
|
104
|
-
previewPage:
|
|
105
|
-
isPreviewMode:
|
|
104
|
+
previewPage: n,
|
|
105
|
+
isPreviewMode: o
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
const
|
|
109
|
-
function N(
|
|
110
|
-
const e = new URL(
|
|
111
|
-
if (e &&
|
|
112
|
-
if (
|
|
113
|
-
const a =
|
|
114
|
-
for (const s of
|
|
108
|
+
const A = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
|
|
109
|
+
function N(n, o) {
|
|
110
|
+
const e = new URL(n.url).searchParams.get("lang");
|
|
111
|
+
if (e && o.supportedLocales?.includes(e)) return e;
|
|
112
|
+
if (o.supportedLocales?.length) {
|
|
113
|
+
const a = n.headers.get("Accept-Language") || "";
|
|
114
|
+
for (const s of o.supportedLocales)
|
|
115
115
|
if (a.includes(s)) return s;
|
|
116
116
|
}
|
|
117
|
-
return
|
|
117
|
+
return o.defaultLocale;
|
|
118
118
|
}
|
|
119
|
-
function I(
|
|
120
|
-
if (!
|
|
121
|
-
const e = decodeURIComponent(
|
|
119
|
+
function I(n, o, t) {
|
|
120
|
+
if (!n.pathname.startsWith("/api/content/")) return null;
|
|
121
|
+
const e = decodeURIComponent(n.pathname.replace("/api/content/", ""));
|
|
122
122
|
return e ? (async () => {
|
|
123
|
-
let a = await
|
|
123
|
+
let a = await o.SITE_CONTENT.get(e);
|
|
124
124
|
if (!a) {
|
|
125
125
|
const s = e.lastIndexOf(":");
|
|
126
|
-
s > 0 && e.slice(s + 1) !== t && (a = await
|
|
126
|
+
s > 0 && e.slice(s + 1) !== t && (a = await o.SITE_CONTENT.get(e.slice(0, s + 1) + t));
|
|
127
127
|
}
|
|
128
128
|
return a ? new Response(a, {
|
|
129
129
|
headers: {
|
|
@@ -134,30 +134,30 @@ function I(o, n, t) {
|
|
|
134
134
|
}) : Response.json({ error: "Not found" }, { status: 404 });
|
|
135
135
|
})() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
|
|
136
136
|
}
|
|
137
|
-
function _(
|
|
138
|
-
let e =
|
|
139
|
-
if (
|
|
137
|
+
function _(n, o, t) {
|
|
138
|
+
let e = n;
|
|
139
|
+
if (o.title) {
|
|
140
140
|
const a = t ? ` — ${t}` : "";
|
|
141
|
-
e = e.replace(/<title>[^<]*<\/title>/, `<title>${
|
|
141
|
+
e = e.replace(/<title>[^<]*<\/title>/, `<title>${o.title}${a}</title>`);
|
|
142
142
|
}
|
|
143
|
-
return
|
|
143
|
+
return o.meta?.description && (e = e.replace(/(<meta\s+name="description"\s+content=")[^"]*(")/, `$1${o.meta.description}$2`)), o.meta?.ogTitle && (e = e.replace(/(<meta\s+property="og:title"\s+content=")[^"]*(")/, `$1${o.meta.ogTitle}$2`)), o.html && (e = e.replace('<div id="app"></div>', `<div id="app">${o.html}</div>`)), e;
|
|
144
144
|
}
|
|
145
|
-
function J(
|
|
145
|
+
function J(n) {
|
|
146
146
|
return {
|
|
147
|
-
async fetch(
|
|
148
|
-
const e = new URL(
|
|
147
|
+
async fetch(o, t) {
|
|
148
|
+
const e = new URL(o.url), a = I(e, t, n.defaultLocale);
|
|
149
149
|
if (a) return a;
|
|
150
|
-
if (
|
|
151
|
-
return t.ASSETS.fetch(
|
|
152
|
-
const s = N(
|
|
150
|
+
if (A.test(e.pathname))
|
|
151
|
+
return t.ASSETS.fetch(o);
|
|
152
|
+
const s = N(o, n), c = new URL("/index.html", o.url);
|
|
153
153
|
let r = await (await t.ASSETS.fetch(new Request(c))).text();
|
|
154
|
-
const
|
|
154
|
+
const f = n.defaultLocale, [g, p] = await Promise.all([
|
|
155
155
|
t.SITE_CONTENT.get("config"),
|
|
156
156
|
t.SITE_CONTENT.get(`content:${s}`)
|
|
157
157
|
]), v = g ? JSON.parse(g) : {};
|
|
158
|
-
let d =
|
|
159
|
-
if (!
|
|
160
|
-
const i = await t.SITE_CONTENT.get(`content:${
|
|
158
|
+
let d = p ? JSON.parse(p) : {};
|
|
159
|
+
if (!p && s !== f) {
|
|
160
|
+
const i = await t.SITE_CONTENT.get(`content:${f}`);
|
|
161
161
|
i && (d = JSON.parse(i));
|
|
162
162
|
}
|
|
163
163
|
const m = e.pathname.match(/^\/p\/(.+)$/);
|
|
@@ -165,7 +165,7 @@ function J(o) {
|
|
|
165
165
|
const i = m[1];
|
|
166
166
|
if (!d[i]) {
|
|
167
167
|
let l = await t.SITE_CONTENT.get(`page:${i}:${s}`);
|
|
168
|
-
!l && s !==
|
|
168
|
+
!l && s !== f && (l = await t.SITE_CONTENT.get(`page:${i}:${f}`)), l && (d[i] = JSON.parse(l));
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
const w = {
|
|
@@ -176,11 +176,11 @@ function J(o) {
|
|
|
176
176
|
w[`page:${i}`] = l;
|
|
177
177
|
if (m) {
|
|
178
178
|
const i = m[1], l = w[`page:${i}`];
|
|
179
|
-
l && (r = _(r, l,
|
|
179
|
+
l && (r = _(r, l, n.siteName));
|
|
180
180
|
}
|
|
181
|
-
e.pathname === "/" && d.home && (r = _(r, d.home,
|
|
182
|
-
const
|
|
183
|
-
return r = r.replace("</head>", `${
|
|
181
|
+
e.pathname === "/" && d.home && (r = _(r, d.home, n.siteName));
|
|
182
|
+
const E = `<script>window.__SITE_DATA__ = ${JSON.stringify(w)};<\/script>`;
|
|
183
|
+
return r = r.replace("</head>", `${E}
|
|
184
184
|
</head>`), new Response(r, {
|
|
185
185
|
headers: {
|
|
186
186
|
"Content-Type": "text/html;charset=utf-8",
|
|
@@ -190,13 +190,15 @@ function J(o) {
|
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
|
+
const M = /* @__PURE__ */ Symbol("pageContext");
|
|
193
194
|
export {
|
|
195
|
+
M as PAGE_CONTEXT_KEY,
|
|
194
196
|
J as createSiteWorker,
|
|
195
197
|
S as isInPreview,
|
|
196
198
|
D as setupPreviewRouter,
|
|
197
199
|
U as useLivePreview,
|
|
198
200
|
F as useSiteApi,
|
|
199
201
|
O as useSiteConfig,
|
|
200
|
-
|
|
202
|
+
R as useSiteData,
|
|
201
203
|
j as useSkin
|
|
202
204
|
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface BlockSettings {
|
|
2
|
+
id?: string;
|
|
3
|
+
visible?: boolean;
|
|
4
|
+
className?: string;
|
|
5
|
+
spacing?: 'none' | 'sm' | 'md' | 'lg';
|
|
6
|
+
}
|
|
7
|
+
export interface Block<T = any> {
|
|
8
|
+
type: string;
|
|
9
|
+
data?: T;
|
|
10
|
+
/** Alias for data — accepted for convenience */
|
|
11
|
+
props?: T;
|
|
12
|
+
settings?: BlockSettings & Record<string, any>;
|
|
13
|
+
}
|
|
14
|
+
export interface HeroBlockData {
|
|
15
|
+
title: string;
|
|
16
|
+
subtitle?: string;
|
|
17
|
+
image?: string;
|
|
18
|
+
overlay?: boolean;
|
|
19
|
+
buttons?: Array<{
|
|
20
|
+
text: string;
|
|
21
|
+
url: string;
|
|
22
|
+
variant?: 'primary' | 'outline' | 'white';
|
|
23
|
+
}>;
|
|
24
|
+
stats?: Array<{
|
|
25
|
+
value: string;
|
|
26
|
+
label: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export interface HtmlBlockData {
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
export interface CardsBlockData {
|
|
33
|
+
title?: string;
|
|
34
|
+
subtitle?: string;
|
|
35
|
+
cards: Array<{
|
|
36
|
+
title: string;
|
|
37
|
+
desc: string;
|
|
38
|
+
icon?: string;
|
|
39
|
+
image?: string;
|
|
40
|
+
link?: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
export interface ImageTextBlockData {
|
|
44
|
+
title?: string;
|
|
45
|
+
content?: string;
|
|
46
|
+
image?: string;
|
|
47
|
+
items?: Array<{
|
|
48
|
+
text: string;
|
|
49
|
+
icon?: string;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
export interface PricingPlan {
|
|
53
|
+
id?: string;
|
|
54
|
+
name: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
price: string | number;
|
|
57
|
+
period?: string;
|
|
58
|
+
features?: string[];
|
|
59
|
+
actionUrl?: string;
|
|
60
|
+
actionText?: string;
|
|
61
|
+
tariffGroupName?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface PricingBlockData {
|
|
64
|
+
title?: string;
|
|
65
|
+
subtitle?: string;
|
|
66
|
+
plans?: PricingPlan[];
|
|
67
|
+
}
|
|
68
|
+
export interface Office {
|
|
69
|
+
name: string;
|
|
70
|
+
address?: string;
|
|
71
|
+
phone?: string;
|
|
72
|
+
email?: string;
|
|
73
|
+
hours?: string;
|
|
74
|
+
isMain?: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface ContactsBlockData {
|
|
77
|
+
title?: string;
|
|
78
|
+
subtitle?: string;
|
|
79
|
+
showBankDetails?: boolean;
|
|
80
|
+
showForm?: boolean;
|
|
81
|
+
offices?: Office[];
|
|
82
|
+
}
|
|
83
|
+
export interface GalleryBlockData {
|
|
84
|
+
title?: string;
|
|
85
|
+
images: Array<{
|
|
86
|
+
url: string;
|
|
87
|
+
alt?: string;
|
|
88
|
+
caption?: string;
|
|
89
|
+
}>;
|
|
90
|
+
}
|
|
91
|
+
export interface MapBlockData {
|
|
92
|
+
title?: string;
|
|
93
|
+
center: {
|
|
94
|
+
lat: number;
|
|
95
|
+
lng: number;
|
|
96
|
+
};
|
|
97
|
+
zoom?: number;
|
|
98
|
+
markers?: Array<{
|
|
99
|
+
lat: number;
|
|
100
|
+
lng: number;
|
|
101
|
+
label?: string;
|
|
102
|
+
}>;
|
|
103
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue';
|
|
2
|
+
import type { Block } from './blocks.js';
|
|
3
|
+
export interface PageContext {
|
|
4
|
+
title?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
author?: string;
|
|
7
|
+
featuredImage?: string;
|
|
8
|
+
excerpt?: string;
|
|
9
|
+
isGated?: boolean;
|
|
10
|
+
termIds?: string[];
|
|
11
|
+
layout?: string;
|
|
12
|
+
publishedAt?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const PAGE_CONTEXT_KEY: InjectionKey<Ref<PageContext>>;
|
|
15
|
+
export interface SiteConfig {
|
|
16
|
+
template: string;
|
|
17
|
+
version: string;
|
|
18
|
+
skin: string;
|
|
19
|
+
autoUpdate?: boolean;
|
|
20
|
+
branding: {
|
|
21
|
+
logo?: string;
|
|
22
|
+
favicon?: string;
|
|
23
|
+
siteName?: string;
|
|
24
|
+
};
|
|
25
|
+
navigation: NavItem[];
|
|
26
|
+
locales: string[];
|
|
27
|
+
defaultLocale: string;
|
|
28
|
+
footer?: {
|
|
29
|
+
copyright?: string;
|
|
30
|
+
links?: Array<{
|
|
31
|
+
text: string;
|
|
32
|
+
url: string;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
features?: Record<string, boolean | Record<string, any>>;
|
|
36
|
+
}
|
|
37
|
+
export interface NavItem {
|
|
38
|
+
text: string | Record<string, string>;
|
|
39
|
+
url: string;
|
|
40
|
+
external?: boolean;
|
|
41
|
+
children?: NavItem[];
|
|
42
|
+
}
|
|
43
|
+
export interface PageConfig {
|
|
44
|
+
title: string;
|
|
45
|
+
blocks: Block[];
|
|
46
|
+
meta?: {
|
|
47
|
+
description?: string;
|
|
48
|
+
ogTitle?: string;
|
|
49
|
+
ogImage?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export interface SiteData {
|
|
53
|
+
config: SiteConfig;
|
|
54
|
+
locale: string;
|
|
55
|
+
[key: string]: any;
|
|
56
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface Skin {
|
|
2
|
+
name: string;
|
|
3
|
+
colors: {
|
|
4
|
+
primary: string;
|
|
5
|
+
primaryDark?: string;
|
|
6
|
+
primaryLight?: string;
|
|
7
|
+
secondary?: string;
|
|
8
|
+
accent?: string;
|
|
9
|
+
background: string;
|
|
10
|
+
backgroundAlt?: string;
|
|
11
|
+
surface: string;
|
|
12
|
+
text: string;
|
|
13
|
+
textLight?: string;
|
|
14
|
+
border?: string;
|
|
15
|
+
success?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
};
|
|
18
|
+
typography: {
|
|
19
|
+
fontFamily: string;
|
|
20
|
+
headingFont?: string;
|
|
21
|
+
baseFontSize?: string;
|
|
22
|
+
};
|
|
23
|
+
shape: {
|
|
24
|
+
borderRadius: string;
|
|
25
|
+
cardElevation?: number;
|
|
26
|
+
maxWidth?: string;
|
|
27
|
+
};
|
|
28
|
+
components?: {
|
|
29
|
+
navbar?: {
|
|
30
|
+
variant?: 'fixed' | 'static';
|
|
31
|
+
transparent?: boolean;
|
|
32
|
+
};
|
|
33
|
+
footer?: {
|
|
34
|
+
variant?: 'dark' | 'light';
|
|
35
|
+
};
|
|
36
|
+
hero?: {
|
|
37
|
+
variant?: 'fullscreen' | 'compact';
|
|
38
|
+
overlay?: boolean;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WorkerEnv, WorkerConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a Cloudflare Worker handler for an Xosen site template.
|
|
4
|
+
*
|
|
5
|
+
* The worker:
|
|
6
|
+
* - Serves static assets from ASSETS binding
|
|
7
|
+
* - Provides /api/content/:key endpoint for KV reads
|
|
8
|
+
* - Injects preloaded KV data as window.__SITE_DATA__ into HTML pages
|
|
9
|
+
* - Detects locale from query param or Accept-Language header
|
|
10
|
+
* - Preloads page-specific data for /p/:slug routes
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSiteWorker(config: WorkerConfig): {
|
|
13
|
+
fetch: (request: Request, env: WorkerEnv) => Promise<Response>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CF Workers type declarations to avoid requiring @cloudflare/workers-types.
|
|
3
|
+
* These match the Cloudflare Workers runtime API.
|
|
4
|
+
*/
|
|
5
|
+
export interface CfKVNamespace {
|
|
6
|
+
get(key: string, options?: any): Promise<string | null>;
|
|
7
|
+
put(key: string, value: string | ArrayBuffer | ReadableStream, options?: any): Promise<void>;
|
|
8
|
+
delete(key: string): Promise<void>;
|
|
9
|
+
list(options?: any): Promise<any>;
|
|
10
|
+
}
|
|
11
|
+
export interface CfFetcher {
|
|
12
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkerEnv {
|
|
15
|
+
SITE_CONTENT: CfKVNamespace;
|
|
16
|
+
ASSETS: CfFetcher;
|
|
17
|
+
}
|
|
18
|
+
export interface WorkerConfig {
|
|
19
|
+
/** Default locale for the site */
|
|
20
|
+
defaultLocale: string;
|
|
21
|
+
/** Supported locales for Accept-Language detection */
|
|
22
|
+
supportedLocales?: string[];
|
|
23
|
+
/** Site name for meta title suffix, e.g. 'SWI' */
|
|
24
|
+
siteName?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PageData {
|
|
27
|
+
title?: string;
|
|
28
|
+
blocks?: unknown[];
|
|
29
|
+
html?: string;
|
|
30
|
+
meta?: {
|
|
31
|
+
description?: string;
|
|
32
|
+
ogTitle?: string;
|
|
33
|
+
ogImage?: string;
|
|
34
|
+
};
|
|
35
|
+
type?: string;
|
|
36
|
+
author?: string;
|
|
37
|
+
featuredImage?: string;
|
|
38
|
+
excerpt?: string;
|
|
39
|
+
termIds?: string[];
|
|
40
|
+
layout?: string;
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xosen/site-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Shared Vue components and composables for Xosen site templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"build": "vue-tsc --declaration --emitDeclarationOnly --outDir dist
|
|
28
|
+
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
29
29
|
"dev": "vite build --watch",
|
|
30
30
|
"clean": "rm -rf dist"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"vue": "
|
|
34
|
-
"vue-i18n": "
|
|
35
|
-
"vue-router": "
|
|
33
|
+
"vue": "^3.5.0",
|
|
34
|
+
"vue-i18n": "^11.0.0",
|
|
35
|
+
"vue-router": "^5.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"vue": "catalog:",
|
package/src/index.ts
CHANGED
|
@@ -25,5 +25,6 @@ export type {
|
|
|
25
25
|
GalleryBlockData,
|
|
26
26
|
MapBlockData,
|
|
27
27
|
} from './types/blocks.js';
|
|
28
|
-
export type { SiteConfig, NavItem, PageConfig, SiteData } from './types/config.js';
|
|
28
|
+
export type { SiteConfig, NavItem, PageConfig, SiteData, PageContext } from './types/config.js';
|
|
29
|
+
export { PAGE_CONTEXT_KEY } from './types/config.js';
|
|
29
30
|
export type { Skin } from './types/skin.js';
|
package/src/types/blocks.ts
CHANGED
|
@@ -82,7 +82,7 @@ export interface ContactsBlockData {
|
|
|
82
82
|
|
|
83
83
|
export interface GalleryBlockData {
|
|
84
84
|
title?: string;
|
|
85
|
-
images: Array<{
|
|
85
|
+
images: Array<{ url: string; alt?: string; caption?: string }>;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export interface MapBlockData {
|
package/src/types/config.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue';
|
|
1
2
|
import type { Block } from './blocks.js';
|
|
2
3
|
|
|
4
|
+
export interface PageContext {
|
|
5
|
+
title?: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
author?: string;
|
|
8
|
+
featuredImage?: string;
|
|
9
|
+
excerpt?: string;
|
|
10
|
+
isGated?: boolean;
|
|
11
|
+
termIds?: string[];
|
|
12
|
+
layout?: string;
|
|
13
|
+
publishedAt?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PAGE_CONTEXT_KEY: InjectionKey<Ref<PageContext>> = Symbol('pageContext');
|
|
17
|
+
|
|
3
18
|
export interface SiteConfig {
|
|
4
19
|
template: string;
|
|
5
20
|
version: string;
|