@xosen/site-sdk 0.0.10 → 0.0.11

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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Locale switcher composable.
3
+ * Manages locale state, available locales, and persistence.
4
+ */
5
+ export declare function useLocaleSwitcher(): {
6
+ locale: import("vue").WritableComputedRef<string, string>;
7
+ locales: import("vue").ComputedRef<string[]>;
8
+ defaultLocale: import("vue").ComputedRef<string>;
9
+ hasMultipleLocales: import("vue").ComputedRef<boolean>;
10
+ switchLocale: (loc: string) => void;
11
+ resolveText: (text: string | Record<string, string>) => string;
12
+ };
@@ -0,0 +1,15 @@
1
+ import { type Ref } from 'vue';
2
+ /**
3
+ * Locale-aware access to site components (navigation, footer, sidebar, etc.).
4
+ *
5
+ * On initial load, reads from preloaded __SITE_DATA__.components.
6
+ * When locale changes, fetches updated components from the API.
7
+ */
8
+ export declare function useSiteComponents(): {
9
+ /** All components (reactive, locale-aware) */
10
+ components: Ref<Record<string, any>, Record<string, any>>;
11
+ /** Get a single component by key */
12
+ getComponent: <T = any>(key: string) => Ref<T | null>;
13
+ /** Force refetch for current locale */
14
+ reload: () => Promise<void>;
15
+ };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export { useSiteApi } from './composables/useSiteApi.js';
5
5
  export { useLivePreview, setupPreviewRouter, isInPreview } from './composables/useLivePreview.js';
6
6
  export { usePageData } from './composables/usePageData.js';
7
7
  export type { UsePageDataReturn } from './composables/usePageData.js';
8
+ export { useSiteComponents } from './composables/useSiteComponents.js';
9
+ export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
8
10
  export type { PreviewMessage } from './composables/useLivePreview.js';
9
11
  export { createSiteWorker } from './worker/index.js';
10
12
  export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
- import { ref as y, onMounted as S, watch as _, computed as f, onUnmounted as x, provide as C } from "vue";
2
- import { useI18n as b } from "vue-i18n";
1
+ import { ref as y, onMounted as T, watch as _, computed as u, onUnmounted as C, provide as L } from "vue";
2
+ import { useI18n as $ } from "vue-i18n";
3
3
  function k(o, n) {
4
- const t = y(null), { locale: e } = b(), s = window.__SITE_DATA__, a = s?.locale;
4
+ const t = y(null), { locale: e } = $(), s = window.__SITE_DATA__, a = s?.locale;
5
5
  function l() {
6
6
  return s && s[o] && e.value === a ? (t.value = s[o], !0) : !1;
7
7
  }
8
- async function u() {
8
+ async function i() {
9
9
  try {
10
- const i = await fetch(`/api/content/${n}:${e.value}`);
11
- i.ok && (t.value = await i.json());
10
+ const c = await fetch(`/api/content/${n}:${e.value}`);
11
+ c.ok && (t.value = await c.json());
12
12
  } catch {
13
13
  }
14
14
  }
15
- return S(() => {
16
- l() || u();
15
+ return T(() => {
16
+ l() || i();
17
17
  }), _(e, () => {
18
- u();
18
+ i();
19
19
  }), t;
20
20
  }
21
21
  function M() {
22
- const o = window.__SITE_DATA__, n = f(() => o?.config || null), t = f(() => o?.components || {}), e = f(() => n.value?.navigation || []), s = f(() => n.value?.locales || []), a = f(() => n.value?.defaultLocale || "en"), l = f(() => n.value?.branding || {}), u = f(() => n.value?.footer || {}), i = f(() => n.value?.features || {});
23
- function g(w) {
24
- const v = i.value[w];
25
- return typeof v == "boolean" ? v : typeof v == "object";
22
+ const o = window.__SITE_DATA__, n = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => n.value?.navigation || []), s = u(() => n.value?.locales || []), a = u(() => n.value?.defaultLocale || "en"), l = u(() => n.value?.branding || {}), i = u(() => n.value?.footer || {}), c = u(() => n.value?.features || {});
23
+ function v(w) {
24
+ const g = c.value[w];
25
+ return typeof g == "boolean" ? g : typeof g == "object";
26
26
  }
27
27
  return {
28
28
  config: n,
@@ -31,9 +31,9 @@ function M() {
31
31
  locales: s,
32
32
  defaultLocale: a,
33
33
  branding: l,
34
- footer: u,
35
- features: i,
36
- hasFeature: g
34
+ footer: i,
35
+ features: c,
36
+ hasFeature: v
37
37
  };
38
38
  }
39
39
  function U(o) {
@@ -44,7 +44,7 @@ function U(o) {
44
44
  a && e.style.setProperty(`--x-color-${N(s)}`, a);
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
- S(() => {
47
+ T(() => {
48
48
  if (o) {
49
49
  n(o);
50
50
  return;
@@ -96,73 +96,118 @@ function P() {
96
96
  const s = e.data;
97
97
  s?.type === "xosen-preview-update" && s.page && (o.value = s.page, n.value = !0);
98
98
  }
99
- return S(() => {
99
+ return T(() => {
100
100
  window.addEventListener("message", t);
101
- }), x(() => {
101
+ }), C(() => {
102
102
  window.removeEventListener("message", t);
103
103
  }), {
104
104
  previewPage: o,
105
105
  isPreviewMode: n
106
106
  };
107
107
  }
108
- const I = /* @__PURE__ */ Symbol("pageContext");
108
+ const x = /* @__PURE__ */ Symbol("pageContext");
109
109
  function K(o) {
110
- const { locale: n } = b(), { previewPage: t, isPreviewMode: e } = P(), s = window.__SITE_DATA__, a = y(null), l = y(!0), u = y(!1), i = f(() => typeof o == "string" ? o : o.value), g = f(() => `page:${i.value}`), w = f(() => {
110
+ const { locale: n } = $(), { previewPage: t, isPreviewMode: e } = P(), s = window.__SITE_DATA__, a = y(null), l = y(!0), i = y(!1), c = u(() => typeof o == "string" ? o : o.value), v = u(() => `page:${c.value}`), w = u(() => {
111
111
  const r = e.value ? t.value : a.value;
112
112
  if (!r) return {};
113
- const { blocks: m, meta: c, slug: d, locale: j, ...A } = r;
113
+ const { blocks: h, meta: f, slug: d, locale: O, ...A } = r;
114
114
  return A;
115
115
  });
116
- C(I, w);
117
- const v = f(() => e.value && t.value?.slug === i.value ? t.value.blocks || [] : a.value?.blocks || []);
118
- function T() {
119
- const r = s?.[g.value];
120
- return r && n.value === s?.locale ? (a.value = r, u.value = !1, h(), !0) : !1;
116
+ L(x, w);
117
+ const g = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : a.value?.blocks || []);
118
+ function S() {
119
+ const r = s?.[v.value];
120
+ return r && n.value === s?.locale ? (a.value = r, i.value = !1, m(), !0) : !1;
121
121
  }
122
122
  async function p() {
123
- l.value = !0, u.value = !1;
123
+ l.value = !0, i.value = !1;
124
124
  try {
125
- const r = await fetch(`/api/content/${g.value}:${n.value}`);
126
- r.ok ? (a.value = await r.json(), h()) : (a.value = null, u.value = !0);
125
+ const r = await fetch(`/api/content/${v.value}:${n.value}`);
126
+ r.ok ? (a.value = await r.json(), m()) : (a.value = null, i.value = !0);
127
127
  } catch {
128
- a.value = null, u.value = !0;
128
+ a.value = null, i.value = !0;
129
129
  } finally {
130
130
  l.value = !1;
131
131
  }
132
132
  }
133
- function h() {
133
+ function m() {
134
134
  if (!a.value) return;
135
135
  if (a.value.title) {
136
- const m = s?.config?.branding, c = m?.siteName ? ` — ${m.siteName}` : "";
137
- document.title = `${a.value.title}${c}`;
136
+ const h = s?.config?.branding, f = h?.siteName ? ` — ${h.siteName}` : "";
137
+ document.title = `${a.value.title}${f}`;
138
138
  }
139
139
  const r = a.value.meta?.description;
140
140
  if (r) {
141
- const m = document.querySelector('meta[name="description"]');
142
- m && m.setAttribute("content", r);
141
+ const h = document.querySelector('meta[name="description"]');
142
+ h && h.setAttribute("content", r);
143
143
  }
144
144
  }
145
145
  return _(t, (r) => {
146
- r?.slug === i.value && (l.value = !1, u.value = !1, (r.title || r.blocks) && (a.value = {
146
+ r?.slug === c.value && (l.value = !1, i.value = !1, (r.title || r.blocks) && (a.value = {
147
147
  ...a.value || {},
148
148
  ...r.title ? { title: r.title } : {},
149
149
  ...r.blocks ? { blocks: r.blocks } : {}
150
- }, h()));
151
- }), S(() => {
152
- T() ? l.value = !1 : p();
153
- }), _(n, () => p()), _(i, () => {
154
- T() ? l.value = !1 : p();
150
+ }, m()));
151
+ }), T(() => {
152
+ S() ? l.value = !1 : p();
153
+ }), _(n, () => p()), _(c, () => {
154
+ S() ? l.value = !1 : p();
155
155
  }), {
156
156
  page: a,
157
- blocks: v,
157
+ blocks: g,
158
158
  pageContext: w,
159
159
  loading: l,
160
- notFound: u,
160
+ notFound: i,
161
161
  isPreviewMode: e,
162
162
  reload: p
163
163
  };
164
164
  }
165
- const L = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
165
+ function W() {
166
+ const { locale: o } = $(), n = window.__SITE_DATA__, t = n?.locale, e = y({ ...n?.components || {} });
167
+ async function s(l) {
168
+ if (l === t) {
169
+ e.value = { ...n?.components || {} };
170
+ return;
171
+ }
172
+ try {
173
+ const i = await fetch(`/api/content/components:${l}`);
174
+ i.ok && (e.value = await i.json());
175
+ } catch {
176
+ }
177
+ }
178
+ _(o, (l) => s(l)), T(() => {
179
+ o.value !== t && s(o.value);
180
+ });
181
+ function a(l) {
182
+ return u(() => e.value[l] || null);
183
+ }
184
+ return {
185
+ /** All components (reactive, locale-aware) */
186
+ components: e,
187
+ /** Get a single component by key */
188
+ getComponent: a,
189
+ /** Force refetch for current locale */
190
+ reload: () => s(o.value)
191
+ };
192
+ }
193
+ function B() {
194
+ const { locale: o } = $(), n = window.__SITE_DATA__, t = u(() => n?.config?.locales || []), e = u(() => n?.config?.defaultLocale || "en"), s = u(() => t.value.length > 1);
195
+ function a(i) {
196
+ o.value = i, localStorage.setItem("x-site-locale", i);
197
+ }
198
+ function l(i) {
199
+ return typeof i == "string" ? i : i[o.value] || i[e.value] || Object.values(i)[0] || "";
200
+ }
201
+ return {
202
+ locale: o,
203
+ locales: t,
204
+ defaultLocale: e,
205
+ hasMultipleLocales: s,
206
+ switchLocale: a,
207
+ resolveText: l
208
+ };
209
+ }
210
+ const I = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
166
211
  function R(o, n) {
167
212
  const e = new URL(o.url).searchParams.get("lang");
168
213
  if (e && n.supportedLocales?.includes(e)) return e;
@@ -173,7 +218,7 @@ function R(o, n) {
173
218
  }
174
219
  return n.defaultLocale;
175
220
  }
176
- function O(o, n, t) {
221
+ function j(o, n, t) {
177
222
  if (!o.pathname.startsWith("/api/content/")) return null;
178
223
  const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
179
224
  return e ? (async () => {
@@ -191,7 +236,7 @@ function O(o, n, t) {
191
236
  }) : Response.json({ error: "Not found" }, { status: 404 });
192
237
  })() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
193
238
  }
194
- function $(o, n, t) {
239
+ function b(o, n, t) {
195
240
  let e = o;
196
241
  if (n.title) {
197
242
  const s = t ? ` — ${t}` : "";
@@ -199,46 +244,46 @@ function $(o, n, t) {
199
244
  }
200
245
  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;
201
246
  }
202
- function W(o) {
247
+ function X(o) {
203
248
  return {
204
249
  async fetch(n, t) {
205
- const e = new URL(n.url), s = O(e, t, o.defaultLocale);
250
+ const e = new URL(n.url), s = j(e, t, o.defaultLocale);
206
251
  if (s) return s;
207
- if (L.test(e.pathname))
252
+ if (I.test(e.pathname))
208
253
  return t.ASSETS.fetch(n);
209
254
  const a = R(n, o), l = new URL("/index.html", n.url);
210
- let i = await (await t.ASSETS.fetch(new Request(l))).text();
211
- const g = o.defaultLocale, [w, v] = await Promise.all([
255
+ let c = await (await t.ASSETS.fetch(new Request(l))).text();
256
+ const v = o.defaultLocale, [w, g] = await Promise.all([
212
257
  t.SITE_CONTENT.get("config"),
213
258
  t.SITE_CONTENT.get(`content:${a}`)
214
- ]), T = w ? JSON.parse(w) : {};
215
- let p = v ? JSON.parse(v) : {};
216
- if (!v && a !== g) {
217
- const c = await t.SITE_CONTENT.get(`content:${g}`);
218
- c && (p = JSON.parse(c));
259
+ ]), S = w ? JSON.parse(w) : {};
260
+ let p = g ? JSON.parse(g) : {};
261
+ if (!g && a !== v) {
262
+ const f = await t.SITE_CONTENT.get(`content:${v}`);
263
+ f && (p = JSON.parse(f));
219
264
  }
220
- const h = e.pathname.match(/^\/p\/(.+)$/);
221
- if (h) {
222
- const c = h[1];
223
- if (!p[c]) {
224
- let d = await t.SITE_CONTENT.get(`page:${c}:${a}`);
225
- !d && a !== g && (d = await t.SITE_CONTENT.get(`page:${c}:${g}`)), d && (p[c] = JSON.parse(d));
265
+ const m = e.pathname.match(/^\/p\/(.+)$/);
266
+ if (m) {
267
+ const f = m[1];
268
+ if (!p[f]) {
269
+ let d = await t.SITE_CONTENT.get(`page:${f}:${a}`);
270
+ !d && a !== v && (d = await t.SITE_CONTENT.get(`page:${f}:${v}`)), d && (p[f] = JSON.parse(d));
226
271
  }
227
272
  }
228
273
  const r = {
229
274
  locale: a,
230
- config: T
275
+ config: S
231
276
  };
232
- for (const [c, d] of Object.entries(p))
233
- r[`page:${c}`] = d;
234
- if (h) {
235
- const c = h[1], d = r[`page:${c}`];
236
- d && (i = $(i, d, o.siteName));
277
+ for (const [f, d] of Object.entries(p))
278
+ r[`page:${f}`] = d;
279
+ if (m) {
280
+ const f = m[1], d = r[`page:${f}`];
281
+ d && (c = b(c, d, o.siteName));
237
282
  }
238
- e.pathname === "/" && p.home && (i = $(i, p.home, o.siteName));
239
- const m = `<script>window.__SITE_DATA__ = ${JSON.stringify(r)};<\/script>`;
240
- return i = i.replace("</head>", `${m}
241
- </head>`), new Response(i, {
283
+ e.pathname === "/" && p.home && (c = b(c, p.home, o.siteName));
284
+ const h = `<script>window.__SITE_DATA__ = ${JSON.stringify(r)};<\/script>`;
285
+ return c = c.replace("</head>", `${h}
286
+ </head>`), new Response(c, {
242
287
  headers: {
243
288
  "Content-Type": "text/html;charset=utf-8",
244
289
  "Cache-Control": "public, max-age=60"
@@ -248,13 +293,15 @@ function W(o) {
248
293
  };
249
294
  }
250
295
  export {
251
- I as PAGE_CONTEXT_KEY,
252
- W as createSiteWorker,
296
+ x as PAGE_CONTEXT_KEY,
297
+ X as createSiteWorker,
253
298
  E as isInPreview,
254
299
  z as setupPreviewRouter,
255
300
  P as useLivePreview,
301
+ B as useLocaleSwitcher,
256
302
  K as usePageData,
257
303
  J as useSiteApi,
304
+ W as useSiteComponents,
258
305
  M as useSiteConfig,
259
306
  k as useSiteData,
260
307
  U as useSkin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xosen/site-sdk",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Shared Vue components and composables for Xosen site templates",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,40 @@
1
+ import { computed } from 'vue';
2
+ import { useI18n } from 'vue-i18n';
3
+
4
+ import type { SiteData } from '../types/config.js';
5
+
6
+ /**
7
+ * Locale switcher composable.
8
+ * Manages locale state, available locales, and persistence.
9
+ */
10
+ export function useLocaleSwitcher() {
11
+ const { locale } = useI18n();
12
+ const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
13
+
14
+ const locales = computed(() => siteData?.config?.locales || []);
15
+ const defaultLocale = computed(() => siteData?.config?.defaultLocale || 'en');
16
+ const hasMultipleLocales = computed(() => locales.value.length > 1);
17
+
18
+ function switchLocale(loc: string) {
19
+ locale.value = loc;
20
+ localStorage.setItem('x-site-locale', loc);
21
+ }
22
+
23
+ /**
24
+ * Resolve a text value that may be a string or a locale map.
25
+ * e.g. "Home" or { "en": "Home", "uk": "Головна" }
26
+ */
27
+ function resolveText(text: string | Record<string, string>): string {
28
+ if (typeof text === 'string') return text;
29
+ return text[locale.value] || text[defaultLocale.value] || Object.values(text)[0] || '';
30
+ }
31
+
32
+ return {
33
+ locale,
34
+ locales,
35
+ defaultLocale,
36
+ hasMultipleLocales,
37
+ switchLocale,
38
+ resolveText,
39
+ };
40
+ }
@@ -0,0 +1,59 @@
1
+ import { ref, computed, watch, onMounted, type Ref } from 'vue';
2
+ import { useI18n } from 'vue-i18n';
3
+
4
+ import type { SiteData } from '../types/config.js';
5
+
6
+ /**
7
+ * Locale-aware access to site components (navigation, footer, sidebar, etc.).
8
+ *
9
+ * On initial load, reads from preloaded __SITE_DATA__.components.
10
+ * When locale changes, fetches updated components from the API.
11
+ */
12
+ export function useSiteComponents() {
13
+ const { locale } = useI18n();
14
+ const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
15
+ const initialLocale = siteData?.locale;
16
+
17
+ const components = ref<Record<string, any>>({ ...(siteData as any)?.components || {} });
18
+
19
+ async function fetchComponents(loc: string) {
20
+ // If same as initial locale, use preloaded data
21
+ if (loc === initialLocale) {
22
+ components.value = { ...(siteData as any)?.components || {} };
23
+ return;
24
+ }
25
+ try {
26
+ const res = await fetch(`/api/content/components:${loc}`);
27
+ if (res.ok) {
28
+ components.value = await res.json();
29
+ }
30
+ } catch {
31
+ // Keep current
32
+ }
33
+ }
34
+
35
+ watch(locale, (newLocale) => fetchComponents(newLocale));
36
+
37
+ onMounted(() => {
38
+ if (locale.value !== initialLocale) {
39
+ fetchComponents(locale.value);
40
+ }
41
+ });
42
+
43
+ /**
44
+ * Get a specific component's data by key.
45
+ * Returns a computed ref that updates on locale change.
46
+ */
47
+ function getComponent<T = any>(key: string): Ref<T | null> {
48
+ return computed(() => components.value[key] || null) as Ref<T | null>;
49
+ }
50
+
51
+ return {
52
+ /** All components (reactive, locale-aware) */
53
+ components,
54
+ /** Get a single component by key */
55
+ getComponent,
56
+ /** Force refetch for current locale */
57
+ reload: () => fetchComponents(locale.value),
58
+ };
59
+ }
package/src/index.ts CHANGED
@@ -6,6 +6,8 @@ export { useSiteApi } from './composables/useSiteApi.js';
6
6
  export { useLivePreview, setupPreviewRouter, isInPreview } from './composables/useLivePreview.js';
7
7
  export { usePageData } from './composables/usePageData.js';
8
8
  export type { UsePageDataReturn } from './composables/usePageData.js';
9
+ export { useSiteComponents } from './composables/useSiteComponents.js';
10
+ export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
9
11
  export type { PreviewMessage } from './composables/useLivePreview.js';
10
12
 
11
13
  // Worker