@brightsideoy/kama 0.1.0-alpha.170 → 0.1.0-alpha.172

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.
@@ -30,7 +30,7 @@ async function f() {
30
30
  if (n(f)) l = (await import(t(f).href)).compileDefinitions;
31
31
  else if (n(p)) l = (await import(t(p).href)).compileDefinitions;
32
32
  else try {
33
- l = (await import("./dist-DkBY_SfG.js")).compileDefinitions;
33
+ l = (await import("./dist-CZH1oldV.js")).compileDefinitions;
34
34
  } catch (e) {
35
35
  console.error("❌ Could not resolve Kama Schema Engine:", e), process.exit(1);
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightsideoy/kama",
3
- "version": "0.1.0-alpha.170",
3
+ "version": "0.1.0-alpha.172",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,33 +1,73 @@
1
1
  ---
2
2
  // src/components/global/Footer.astro
3
- // Notice: No props needed anymore! It fetches its own data independently.
3
+
4
+ import BlockRenderer from "@/components/blocks/BlockRenderer.astro";
5
+ import { getChannelId } from "@/lib/cms";
6
+
7
+ // 🎯 Fetch data natively during the server request
8
+ const safeEnv = (Astro.locals as any).safeEnv || {};
9
+ const cmsWorker = safeEnv.CMS_WORKER;
10
+ const channelId = getChannelId() || "website";
11
+
12
+ const customFetch = (input: RequestInfo | URL, init?: RequestInit) => {
13
+ const reqInit = { ...init };
14
+ delete reqInit.cache;
15
+
16
+ let urlStr = input instanceof URL ? input.toString() : String(input);
17
+ if (urlStr.startsWith("/")) {
18
+ const baseUrl =
19
+ import.meta.env.PUBLIC_API_URL ||
20
+ safeEnv.PUBLIC_API_URL ||
21
+ "http://127.0.0.1:8787";
22
+ urlStr = `${baseUrl}${urlStr}`;
23
+ }
24
+
25
+ const req = new Request(urlStr, reqInit);
26
+ if (cmsWorker && typeof cmsWorker.fetch === "function") {
27
+ return cmsWorker.fetch(req);
28
+ }
29
+ return fetch(req);
30
+ };
31
+
32
+ // 🎯 Resolve the Home Page slug
33
+ const homeSlug = String(import.meta.env.PUBLIC_HOME_SLUG || "home").trim();
34
+
35
+ let footerBlocks: any[] = [];
36
+ let contentMap: Record<string, any[]> = {};
37
+
38
+ try {
39
+ const res = await customFetch(
40
+ `/api/v1/node/${homeSlug}?channelId=${channelId}`,
41
+ );
42
+ if (res.ok) {
43
+ const data = await res.json();
44
+ const allContent = data.content || {};
45
+ footerBlocks = allContent.footer || [];
46
+ contentMap = allContent;
47
+ }
48
+ } catch (e) {
49
+ console.error("[Footer] Failed to fetch home page blocks:", e);
50
+ }
4
51
  ---
5
52
 
6
53
  <footer class="site-footer">
7
- <div class="footer-container" id="footer-injection-target">
8
- <!-- Lightning fast SSR placeholder before the partial loads -->
54
+ <div class="footer-container">
55
+ {
56
+ footerBlocks && footerBlocks.length > 0 ? (
57
+ <BlockRenderer
58
+ blocks={footerBlocks}
59
+ contentMap={contentMap}
60
+ region="footer"
61
+ />
62
+ ) : (
63
+ <div class="footer-default">
64
+ <p>&copy; {new Date().getFullYear()} all rights reserved.</p>
65
+ </div>
66
+ )
67
+ }
9
68
  </div>
10
69
  </footer>
11
70
 
12
- <script>
13
- async function initFooter() {
14
- const target = document.getElementById("footer-injection-target");
15
- if (!target) return;
16
-
17
- try {
18
- const res = await fetch(`/partials/footer`);
19
- if (res.ok) {
20
- target.innerHTML = await res.text();
21
- }
22
- } catch (e) {
23
- console.error("Failed to load footer partial", e);
24
- }
25
- }
26
-
27
- // Run immediately
28
- initFooter();
29
- </script>
30
-
31
71
  <style>
32
72
  .site-footer {
33
73
  background: #ffffff;
@@ -41,8 +81,7 @@
41
81
  padding: 24px 20px;
42
82
  }
43
83
 
44
- /* 🎯 Use :global() so Astro doesn't strip the CSS for the injected partial */
45
- :global(.footer-default p) {
84
+ .footer-default p {
46
85
  margin: 0;
47
86
  font-size: 14px;
48
87
  color: #64748b;
@@ -1,5 +1,45 @@
1
1
  ---
2
2
  // src/components/global/NavigationMenu.astro
3
+ import type { TreeNode } from "@/lib/cms";
4
+
5
+ export interface Props {
6
+ tree?: TreeNode[];
7
+ }
8
+
9
+ const { tree = [] } = Astro.props;
10
+
11
+ // 🎯 Grab the current path directly from Astro's server request
12
+ const currentPath = Astro.url.pathname.replace(/\/$/, "") || "/";
13
+ const homeSlug = String(import.meta.env.PUBLIC_HOME_SLUG || "home").trim();
14
+
15
+ const homeNode = tree.find(
16
+ item =>
17
+ item.slug === homeSlug ||
18
+ item.slug === "" ||
19
+ item.path === "/" ||
20
+ item.path === "",
21
+ );
22
+
23
+ const topLevelNavItems =
24
+ homeNode?.children && homeNode.children.length > 0
25
+ ? homeNode.children
26
+ : tree.filter(
27
+ item =>
28
+ item.slug !== homeSlug &&
29
+ item.slug !== "" &&
30
+ item.path !== "/" &&
31
+ item.path !== "",
32
+ );
33
+
34
+ const isPathActive = (itemPath?: string) => {
35
+ const normItem = (itemPath || "").replace(/\/$/, "") || "/";
36
+ if (normItem === "/") return currentPath === "/";
37
+ return currentPath === normItem || currentPath.startsWith(`${normItem}/`);
38
+ };
39
+
40
+ const isExactActive = (itemPath?: string) => {
41
+ return currentPath === ((itemPath || "").replace(/\/$/, "") || "/");
42
+ };
3
43
  ---
4
44
 
5
45
  <!-- 2-LINE HAMBURGER TOGGLE BUTTON -->
@@ -16,30 +56,78 @@
16
56
 
17
57
  <!-- NAVIGATION BLOCK & FULLSCREEN CONTAINER -->
18
58
  <nav class="nav-block" id="nav-overlay">
19
- <!-- 🎯 The partial HTML will be injected into this div -->
20
- <div id="nav-injection-target"></div>
21
- </nav>
59
+ <ul class="nav-list">
60
+ {
61
+ topLevelNavItems.map(item => {
62
+ const hasChildren = item.children && item.children.length > 0;
63
+ const isParentActive = isPathActive(item.path);
22
64
 
23
- <script>
24
- async function initNav() {
25
- const target = document.getElementById("nav-injection-target");
26
- if (!target) return;
27
-
28
- try {
29
- // 🎯 Fetch the partial and pass the current page URL so it highlights correctly
30
- const currentPath = encodeURIComponent(window.location.pathname);
31
- const res = await fetch(`/partials/nav?path=${currentPath}`);
65
+ if (hasChildren) {
66
+ return (
67
+ <li class="nav-item">
68
+ <details class="nav-details">
69
+ <summary
70
+ class:list={[
71
+ "nav-button",
72
+ "dropdown-summary",
73
+ { active: isParentActive },
74
+ ]}
75
+ >
76
+ <span>{item.title}</span>
77
+ <span class="indicator">▼</span>
78
+ </summary>
79
+ <div class="submenu-container">
80
+ <ul class="dropdown-panel">
81
+ <li>
82
+ <a
83
+ href={item.path}
84
+ class:list={[
85
+ "dropdown-item",
86
+ "overview-item",
87
+ { active: isExactActive(item.path) },
88
+ ]}
89
+ >
90
+ {item.title} Overview
91
+ </a>
92
+ </li>
93
+ {item.children?.map(child => (
94
+ <li>
95
+ <a
96
+ href={child.path}
97
+ class:list={[
98
+ "dropdown-item",
99
+ { active: isExactActive(child.path) },
100
+ ]}
101
+ >
102
+ {child.title}
103
+ </a>
104
+ </li>
105
+ ))}
106
+ </ul>
107
+ </div>
108
+ </details>
109
+ </li>
110
+ );
111
+ }
32
112
 
33
- if (res.ok) {
34
- target.innerHTML = await res.text();
35
- bindNavEvents();
36
- }
37
- } catch (e) {
38
- console.error("Failed to load nav partial", e);
113
+ return (
114
+ <li class="nav-item">
115
+ <a
116
+ href={item.path}
117
+ class:list={["nav-button", { active: isExactActive(item.path) }]}
118
+ >
119
+ {item.title}
120
+ </a>
121
+ </li>
122
+ );
123
+ })
39
124
  }
40
- }
125
+ </ul>
126
+ </nav>
41
127
 
42
- function bindNavEvents() {
128
+ <script>
129
+ // 🎯 Clean, standard event listeners that run when the HTML is parsed
130
+ document.addEventListener("DOMContentLoaded", () => {
43
131
  const toggleBtn = document.getElementById("menu-toggle");
44
132
  const navOverlay = document.getElementById("nav-overlay");
45
133
 
@@ -84,10 +172,7 @@
84
172
  }
85
173
  });
86
174
  });
87
- }
88
-
89
- // Run on page load
90
- initNav();
175
+ });
91
176
  </script>
92
177
 
93
178
  <style>
@@ -147,8 +232,7 @@
147
232
  pointer-events: auto;
148
233
  }
149
234
 
150
- /* 🎯 We target the injected .nav-list using :global so Astro doesn't scope it away */
151
- :global(.nav-list) {
235
+ .nav-list {
152
236
  display: flex;
153
237
  flex-direction: column;
154
238
  gap: 8px;
@@ -157,11 +241,11 @@
157
241
  padding: 0;
158
242
  width: 100%;
159
243
  }
160
- :global(.nav-item) {
244
+ .nav-item {
161
245
  position: relative;
162
246
  width: 100%;
163
247
  }
164
- :global(.nav-button) {
248
+ .nav-button {
165
249
  display: flex;
166
250
  align-items: center;
167
251
  justify-content: space-between;
@@ -178,44 +262,44 @@
178
262
  cursor: pointer;
179
263
  box-sizing: border-box;
180
264
  }
181
- :global(.nav-button:hover) {
265
+ .nav-button:hover {
182
266
  color: #0f172a;
183
267
  background: #f8fafc;
184
268
  }
185
- :global(.nav-button.active) {
269
+ .nav-button.active {
186
270
  color: #0c4a6e;
187
271
  font-weight: 600;
188
272
  }
189
- :global(.dropdown-summary) {
273
+ .dropdown-summary {
190
274
  list-style: none;
191
275
  user-select: none;
192
276
  }
193
- :global(.dropdown-summary::-webkit-details-marker) {
277
+ .dropdown-summary::-webkit-details-marker {
194
278
  display: none;
195
279
  }
196
- :global(.indicator) {
280
+ .indicator {
197
281
  font-size: 11px;
198
282
  color: #64748b;
199
283
  transition: transform 0.2s ease;
200
284
  }
201
- :global(details[open] .indicator) {
285
+ details[open] .indicator {
202
286
  transform: rotate(180deg);
203
287
  }
204
- :global(details.nav-details) {
288
+ details.nav-details {
205
289
  border-radius: 6px;
206
290
  transition: background-color 0.2s ease;
207
291
  }
208
- :global(details[open].nav-details) {
292
+ details[open].nav-details {
209
293
  background: #f8fafc;
210
294
  padding-bottom: 8px;
211
295
  }
212
- :global(.submenu-container) {
296
+ .submenu-container {
213
297
  position: relative;
214
298
  margin-left: 24px;
215
299
  padding-left: 16px;
216
300
  border-left: 2px solid #cbd5e1;
217
301
  }
218
- :global(.dropdown-panel) {
302
+ .dropdown-panel {
219
303
  list-style: none;
220
304
  margin: 4px 0 0 0;
221
305
  padding: 0;
@@ -223,7 +307,7 @@
223
307
  flex-direction: column;
224
308
  gap: 4px;
225
309
  }
226
- :global(.dropdown-item) {
310
+ .dropdown-item {
227
311
  display: block;
228
312
  padding: 10px 12px;
229
313
  font-size: 16px;
@@ -235,20 +319,20 @@
235
319
  background 0.15s ease,
236
320
  color 0.15s ease;
237
321
  }
238
- :global(.overview-item) {
322
+ .overview-item {
239
323
  font-weight: 500;
240
324
  color: #334155;
241
325
  }
242
- :global(.dropdown-item:hover) {
326
+ .dropdown-item:hover {
243
327
  background: #e2e8f0;
244
328
  color: #0f172a;
245
329
  }
246
- :global(.dropdown-item.active) {
330
+ .dropdown-item.active {
247
331
  font-weight: 600;
248
332
  color: #0c4a6e;
249
333
  background: #e0f2fe;
250
334
  }
251
- :global(body.menu-lock-scroll) {
335
+ body.menu-lock-scroll {
252
336
  overflow: hidden !important;
253
337
  }
254
338
 
@@ -268,15 +352,15 @@
268
352
  overflow-y: visible;
269
353
  display: block;
270
354
  }
271
- :global(.nav-list) {
355
+ .nav-list {
272
356
  flex-direction: row;
273
357
  gap: 4px;
274
358
  justify-content: flex-end;
275
359
  }
276
- :global(.nav-item) {
360
+ .nav-item {
277
361
  width: auto;
278
362
  }
279
- :global(.nav-button) {
363
+ .nav-button {
280
364
  display: inline-flex;
281
365
  justify-content: flex-start;
282
366
  gap: 6px;
@@ -286,16 +370,16 @@
286
370
  border-radius: 4px;
287
371
  margin-top: 4px;
288
372
  }
289
- :global(details[open].nav-details) {
373
+ details[open].nav-details {
290
374
  background: transparent;
291
375
  padding-bottom: 0;
292
376
  }
293
- :global(.submenu-container) {
377
+ .submenu-container {
294
378
  margin-left: 0;
295
379
  padding-left: 0;
296
380
  border-left: none;
297
381
  }
298
- :global(.dropdown-panel) {
382
+ .dropdown-panel {
299
383
  position: absolute;
300
384
  top: 100%;
301
385
  left: 0;
@@ -308,7 +392,7 @@
308
392
  border-radius: 4px;
309
393
  padding: 4px;
310
394
  }
311
- :global(.dropdown-item) {
395
+ .dropdown-item {
312
396
  font-size: 14px;
313
397
  padding: 8px 16px;
314
398
  border-radius: 4px;
@@ -57,9 +57,7 @@ const homeSlug = String(envHomeSlug || workerHomeSlug || "home").trim();
57
57
 
58
58
  const envApiUrl = import.meta.env.PUBLIC_API_URL;
59
59
  const workerApiUrl = safeEnv.PUBLIC_API_URL;
60
- const baseApiUrl = String(
61
- envApiUrl || workerApiUrl || "https://admin.kamaengine.com",
62
- );
60
+ const baseApiUrl = String(envApiUrl || workerApiUrl || "http://localhost:8787");
63
61
 
64
62
  const cleanApiUrl = baseApiUrl.endsWith("/")
65
63
  ? baseApiUrl.slice(0, -1)
@@ -175,7 +173,6 @@ const isSkeleton = node?.nodeType === "page-skeleton";
175
173
  isHomePage={isHomePage}
176
174
  >
177
175
  <BlockRenderer blocks={mainBlocks} contentMap={content} region="main" />
178
- <KamaLivePreviewListener />
179
176
  </SkeletonLayout>
180
177
  ) : (
181
178
  <StandardLayout
@@ -235,8 +232,6 @@ const isSkeleton = node?.nodeType === "page-skeleton";
235
232
  </aside>
236
233
  )}
237
234
  </div>
238
-
239
- <KamaLivePreviewListener />
240
235
  </StandardLayout>
241
236
  )
242
237
  }