@leadertechie/personal-site-kit 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
Files changed (169) hide show
  1. package/README.md +94 -17
  2. package/dist/api/content-utils.d.ts +27 -0
  3. package/dist/api/content-utils.d.ts.map +1 -0
  4. package/dist/api/handlers/about-me.d.ts.map +1 -1
  5. package/dist/api/handlers/auth-handler.d.ts +2 -0
  6. package/dist/api/handlers/auth-handler.d.ts.map +1 -0
  7. package/dist/api/handlers/auth.d.ts +23 -0
  8. package/dist/api/handlers/auth.d.ts.map +1 -0
  9. package/dist/api/handlers/content-api.d.ts +0 -1
  10. package/dist/api/handlers/content-api.d.ts.map +1 -1
  11. package/dist/api/handlers/content.d.ts.map +1 -1
  12. package/dist/api/handlers/home.d.ts.map +1 -1
  13. package/dist/api/handlers/static-details.d.ts +1 -1
  14. package/dist/api/handlers/static-details.d.ts.map +1 -1
  15. package/dist/api/index.d.ts +2 -0
  16. package/dist/api/index.d.ts.map +1 -1
  17. package/dist/api/website-api.d.ts +1 -1
  18. package/dist/api/website-api.d.ts.map +1 -1
  19. package/dist/api.js +17 -2
  20. package/dist/assets/logo-placeholder.svg +21 -0
  21. package/dist/chunks/index-C1krnvU3.js +211 -0
  22. package/dist/chunks/index-DrnbjP2Q.js +2715 -0
  23. package/dist/chunks/site-store-CGV9c2DI.js +89 -0
  24. package/dist/chunks/{template-gGTkeOcA.js → template-DVy2k_na.js} +128 -90
  25. package/dist/chunks/website-api-CFRUPu0X.js +958 -0
  26. package/dist/index.js +42 -14
  27. package/dist/prerender/data-fetcher.d.ts +19 -0
  28. package/dist/prerender/data-fetcher.d.ts.map +1 -0
  29. package/dist/prerender/page-content.d.ts.map +1 -1
  30. package/dist/prerender/page-generators/about.d.ts +16 -0
  31. package/dist/prerender/page-generators/about.d.ts.map +1 -0
  32. package/dist/prerender/page-generators/base.d.ts +25 -0
  33. package/dist/prerender/page-generators/base.d.ts.map +1 -0
  34. package/dist/prerender/page-generators/blog-detail.d.ts +15 -0
  35. package/dist/prerender/page-generators/blog-detail.d.ts.map +1 -0
  36. package/dist/prerender/page-generators/blogs-list.d.ts +17 -0
  37. package/dist/prerender/page-generators/blogs-list.d.ts.map +1 -0
  38. package/dist/prerender/page-generators/home.d.ts +19 -0
  39. package/dist/prerender/page-generators/home.d.ts.map +1 -0
  40. package/dist/prerender/page-generators/index.d.ts +9 -0
  41. package/dist/prerender/page-generators/index.d.ts.map +1 -0
  42. package/dist/prerender/page-generators/not-found.d.ts +14 -0
  43. package/dist/prerender/page-generators/not-found.d.ts.map +1 -0
  44. package/dist/prerender/page-generators/stories-list.d.ts +17 -0
  45. package/dist/prerender/page-generators/stories-list.d.ts.map +1 -0
  46. package/dist/prerender/page-generators/story-detail.d.ts +15 -0
  47. package/dist/prerender/page-generators/story-detail.d.ts.map +1 -0
  48. package/dist/prerender/template.d.ts +3 -1
  49. package/dist/prerender/template.d.ts.map +1 -1
  50. package/dist/prerender/website-prerender.d.ts +6 -0
  51. package/dist/prerender/website-prerender.d.ts.map +1 -1
  52. package/dist/prerender.js +291 -145
  53. package/dist/shared/config/index.d.ts +1 -0
  54. package/dist/shared/config/index.d.ts.map +1 -1
  55. package/dist/shared/core/site-store.d.ts +1 -0
  56. package/dist/shared/core/site-store.d.ts.map +1 -1
  57. package/dist/shared/core/theme-toggle.d.ts.map +1 -1
  58. package/dist/shared/page-content.d.ts.map +1 -1
  59. package/dist/shared/router.d.ts +9 -3
  60. package/dist/shared/router.d.ts.map +1 -1
  61. package/dist/shared/website-ui.d.ts +23 -0
  62. package/dist/shared/website-ui.d.ts.map +1 -1
  63. package/dist/shared.js +6 -4
  64. package/dist/ui/about-me/index.d.ts +2 -10
  65. package/dist/ui/about-me/index.d.ts.map +1 -1
  66. package/dist/ui/about-me/styles.d.ts.map +1 -1
  67. package/dist/ui/admin/api.d.ts +16 -0
  68. package/dist/ui/admin/api.d.ts.map +1 -0
  69. package/dist/ui/admin/components/AboutMeSection.d.ts +7 -0
  70. package/dist/ui/admin/components/AboutMeSection.d.ts.map +1 -0
  71. package/dist/ui/admin/components/AdminSection.d.ts +13 -0
  72. package/dist/ui/admin/components/AdminSection.d.ts.map +1 -0
  73. package/dist/ui/admin/components/BlogsSection.d.ts +7 -0
  74. package/dist/ui/admin/components/BlogsSection.d.ts.map +1 -0
  75. package/dist/ui/admin/components/HomeSection.d.ts +7 -0
  76. package/dist/ui/admin/components/HomeSection.d.ts.map +1 -0
  77. package/dist/ui/admin/components/ImagesSection.d.ts +7 -0
  78. package/dist/ui/admin/components/ImagesSection.d.ts.map +1 -0
  79. package/dist/ui/admin/components/LoginForm.d.ts +9 -0
  80. package/dist/ui/admin/components/LoginForm.d.ts.map +1 -0
  81. package/dist/ui/admin/components/LogoSection.d.ts +7 -0
  82. package/dist/ui/admin/components/LogoSection.d.ts.map +1 -0
  83. package/dist/ui/admin/components/ProfileSection.d.ts +7 -0
  84. package/dist/ui/admin/components/ProfileSection.d.ts.map +1 -0
  85. package/dist/ui/admin/components/StaticSection.d.ts +9 -0
  86. package/dist/ui/admin/components/StaticSection.d.ts.map +1 -0
  87. package/dist/ui/admin/components/StoriesSection.d.ts +7 -0
  88. package/dist/ui/admin/components/StoriesSection.d.ts.map +1 -0
  89. package/dist/ui/admin/components/index.d.ts +11 -0
  90. package/dist/ui/admin/components/index.d.ts.map +1 -0
  91. package/dist/ui/admin/index.d.ts +27 -26
  92. package/dist/ui/admin/index.d.ts.map +1 -1
  93. package/dist/ui/admin/styles.d.ts.map +1 -1
  94. package/dist/ui/admin/types.d.ts +24 -0
  95. package/dist/ui/admin/types.d.ts.map +1 -0
  96. package/dist/ui/banner/index.d.ts.map +1 -1
  97. package/dist/ui/banner/styles.d.ts.map +1 -1
  98. package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts +2 -0
  99. package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts.map +1 -0
  100. package/dist/ui/blog-viewer/index.d.ts +25 -0
  101. package/dist/ui/blog-viewer/index.d.ts.map +1 -0
  102. package/dist/ui/blog-viewer/styles.d.ts +2 -0
  103. package/dist/ui/blog-viewer/styles.d.ts.map +1 -0
  104. package/dist/ui/footer/index.d.ts.map +1 -1
  105. package/dist/ui/footer/styles.d.ts.map +1 -1
  106. package/dist/ui/index.d.ts +2 -0
  107. package/dist/ui/index.d.ts.map +1 -1
  108. package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts +2 -0
  109. package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts.map +1 -0
  110. package/dist/ui/story-viewer/index.d.ts +25 -0
  111. package/dist/ui/story-viewer/index.d.ts.map +1 -0
  112. package/dist/ui/story-viewer/styles.d.ts +2 -0
  113. package/dist/ui/story-viewer/styles.d.ts.map +1 -0
  114. package/dist/ui.js +15 -3
  115. package/package.json +37 -13
  116. package/public/assets/logo-placeholder.svg +21 -0
  117. package/dist/chunks/index-BqixlS-2.js +0 -1157
  118. package/dist/chunks/website-api-CVsi-OLc.js +0 -596
  119. package/dist/ui/about-me/renderer.d.ts +0 -5
  120. package/dist/ui/about-me/renderer.d.ts.map +0 -1
  121. package/src/api/__tests__/info.test.ts +0 -44
  122. package/src/api/__tests__/utils.test.ts +0 -78
  123. package/src/api/handlers/about-me.ts +0 -99
  124. package/src/api/handlers/content-api.ts +0 -268
  125. package/src/api/handlers/content.ts +0 -72
  126. package/src/api/handlers/home.ts +0 -79
  127. package/src/api/handlers/info.ts +0 -12
  128. package/src/api/handlers/logo.ts +0 -55
  129. package/src/api/handlers/static-details.ts +0 -48
  130. package/src/api/index.ts +0 -7
  131. package/src/api/utils.ts +0 -16
  132. package/src/api/website-api.ts +0 -124
  133. package/src/index.ts +0 -4
  134. package/src/prerender/__tests__/page-content.test.ts +0 -54
  135. package/src/prerender/__tests__/template.test.ts +0 -54
  136. package/src/prerender/index.ts +0 -7
  137. package/src/prerender/page-content.ts +0 -263
  138. package/src/prerender/prerender.ts +0 -25
  139. package/src/prerender/template.ts +0 -65
  140. package/src/prerender/website-prerender.ts +0 -152
  141. package/src/shared/config/api.ts +0 -16
  142. package/src/shared/config/index.ts +0 -41
  143. package/src/shared/config/types.ts +0 -16
  144. package/src/shared/core/__tests__/theme-toggle.test.ts +0 -204
  145. package/src/shared/core/site-store.ts +0 -38
  146. package/src/shared/core/theme-toggle.ts +0 -118
  147. package/src/shared/index.ts +0 -17
  148. package/src/shared/interfaces/ifooter-link.ts +0 -4
  149. package/src/shared/interfaces/iroute.ts +0 -4
  150. package/src/shared/models/theme-variables.css +0 -25
  151. package/src/shared/page-content.ts +0 -210
  152. package/src/shared/router.ts +0 -241
  153. package/src/shared/runtime.ts +0 -11
  154. package/src/shared/template.ts +0 -35
  155. package/src/shared/website-ui.ts +0 -92
  156. package/src/styles/markdown.css +0 -129
  157. package/src/ui/about-me/api.ts +0 -12
  158. package/src/ui/about-me/index.ts +0 -155
  159. package/src/ui/about-me/renderer.ts +0 -7
  160. package/src/ui/about-me/styles.ts +0 -10
  161. package/src/ui/admin/index.ts +0 -492
  162. package/src/ui/admin/styles.ts +0 -317
  163. package/src/ui/banner/index.ts +0 -38
  164. package/src/ui/banner/styles.ts +0 -10
  165. package/src/ui/footer/index.ts +0 -37
  166. package/src/ui/footer/styles.ts +0 -9
  167. package/src/ui/index.ts +0 -4
  168. /package/{src/shared → dist}/styles/markdown.css +0 -0
  169. /package/{src → dist}/styles/theme.css +0 -0
@@ -0,0 +1,89 @@
1
+ const __vite_import_meta_env__ = {};
2
+ const DEFAULT_INFRA = {
3
+ baseUrl: typeof window !== "undefined" ? window.location.origin : "http://localhost:5173",
4
+ apiUrl: typeof window !== "undefined" && (window.__VITE_API_URL__ || __vite_import_meta_env__?.VITE_API_URL) || (typeof window !== "undefined" ? window.location.origin : "http://localhost:8787")
5
+ };
6
+ const DEFAULT_STATIC = {
7
+ siteTitle: "My Personal Website",
8
+ siteDescription: "My Personal Website",
9
+ copyright: "2026 My Personal Website",
10
+ linkedin: "https://linkedin.com/in/yourname",
11
+ github: "https://github.com/yourname",
12
+ email: "yourname@domain.com"
13
+ };
14
+ let activeConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
15
+ async function initializeConfig(infra) {
16
+ if (infra) {
17
+ if (infra.baseUrl) activeConfig.baseUrl = infra.baseUrl;
18
+ if (infra.apiUrl) activeConfig.apiUrl = infra.apiUrl;
19
+ }
20
+ try {
21
+ const res = await fetch(`${activeConfig.apiUrl}/api/static`);
22
+ if (res.ok) {
23
+ const remoteStatic = await res.json().catch(() => ({}));
24
+ const filteredStatic = Object.fromEntries(
25
+ Object.entries(remoteStatic).filter(([_, v]) => v != null && v !== "")
26
+ );
27
+ activeConfig = { ...activeConfig, ...filteredStatic };
28
+ }
29
+ } catch (e) {
30
+ console.warn("Failed to load static details from R2, using defaults.");
31
+ }
32
+ return activeConfig;
33
+ }
34
+ async function refreshConfig() {
35
+ try {
36
+ const res = await fetch(`${activeConfig.apiUrl}/api/static`);
37
+ if (res.ok) {
38
+ const remoteStatic = await res.json().catch(() => ({}));
39
+ activeConfig = { ...activeConfig, ...remoteStatic };
40
+ }
41
+ } catch (e) {
42
+ console.warn("Failed to refresh static details.");
43
+ }
44
+ return activeConfig;
45
+ }
46
+ function getConfig() {
47
+ return activeConfig;
48
+ }
49
+ class SiteStore {
50
+ constructor() {
51
+ this.config = null;
52
+ this.listeners = /* @__PURE__ */ new Set();
53
+ }
54
+ static getInstance() {
55
+ if (!SiteStore.instance) {
56
+ SiteStore.instance = new SiteStore();
57
+ }
58
+ return SiteStore.instance;
59
+ }
60
+ async init(infra) {
61
+ this.config = await initializeConfig(infra);
62
+ this.notify();
63
+ return this.config;
64
+ }
65
+ subscribe(listener) {
66
+ this.listeners.add(listener);
67
+ if (this.config) listener(this.config);
68
+ return () => this.listeners.delete(listener);
69
+ }
70
+ notify() {
71
+ if (this.config) {
72
+ this.listeners.forEach((l) => l(this.config));
73
+ }
74
+ }
75
+ async refresh() {
76
+ await refreshConfig();
77
+ this.config = getConfig();
78
+ this.notify();
79
+ }
80
+ getConfig() {
81
+ return this.config || getConfig();
82
+ }
83
+ }
84
+ export {
85
+ SiteStore as S,
86
+ getConfig as g,
87
+ initializeConfig as i,
88
+ refreshConfig as r
89
+ };
@@ -1,70 +1,23 @@
1
- import { MarkdownPipeline } from "@leadertechie/md2html";
2
- const DEFAULT_INFRA = {
3
- baseUrl: typeof window !== "undefined" ? window.location.origin : "http://localhost:5173",
4
- apiUrl: typeof window !== "undefined" && window.__VITE_API_URL__ || "http://localhost:8787"
5
- };
6
- const DEFAULT_STATIC = {
7
- siteTitle: "My Personal Website",
8
- siteDescription: "My Personal Website",
9
- copyright: "2026 My Personal Website",
10
- linkedin: "https://linkedin.com/in/yourname",
11
- github: "https://github.com/yourname",
12
- email: "yourname@domain.com"
13
- };
14
- let activeConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
15
- async function initializeConfig(infra) {
16
- if (infra) {
17
- activeConfig = { ...activeConfig, ...infra };
18
- }
19
- try {
20
- const res = await fetch(`${activeConfig.apiUrl}/api/static`);
21
- if (res.ok) {
22
- const remoteStatic = await res.json();
23
- activeConfig = { ...activeConfig, ...remoteStatic };
24
- }
25
- } catch (e) {
26
- console.warn("Failed to load static details from R2, using defaults.");
27
- }
28
- return activeConfig;
29
- }
30
- function getConfig() {
31
- return activeConfig;
32
- }
33
- class SiteStore {
34
- constructor() {
35
- this.config = null;
36
- this.listeners = /* @__PURE__ */ new Set();
37
- }
38
- static getInstance() {
39
- if (!SiteStore.instance) {
40
- SiteStore.instance = new SiteStore();
41
- }
42
- return SiteStore.instance;
43
- }
44
- async init(infra) {
45
- this.config = await initializeConfig(infra);
46
- this.notify();
47
- return this.config;
48
- }
49
- subscribe(listener) {
50
- this.listeners.add(listener);
51
- if (this.config) listener(this.config);
52
- return () => this.listeners.delete(listener);
53
- }
54
- notify() {
55
- if (this.config) {
56
- this.listeners.forEach((l) => l(this.config));
57
- }
58
- }
59
- getConfig() {
60
- return this.config || getConfig();
61
- }
62
- }
1
+ import { S as SiteStore } from "./site-store-CGV9c2DI.js";
2
+ import { MarkdownPipeline } from "@leadertechie/r2tohtml";
3
+ import { init, reinit } from "@leadertechie/md2interact";
63
4
  const pipeline = new MarkdownPipeline({
64
5
  imagePathPrefix: "images/",
6
+ preserveRawHTML: true,
7
+ errorRecovery: "warn",
8
+ maxRecursionDepth: 50,
65
9
  styleOptions: {
66
10
  classPrefix: "md-",
67
- addHeadingIds: true
11
+ addHeadingIds: true,
12
+ emitScopeAnchors: true
13
+ },
14
+ slotPattern: /\[\[(.*?)\]\]/g,
15
+ onSlot: (name) => {
16
+ const slotMap = {
17
+ "SITE_TITLE": document?.querySelector("title")?.textContent || "My Site",
18
+ "CURRENT_YEAR": (/* @__PURE__ */ new Date()).getFullYear().toString()
19
+ };
20
+ return slotMap[name] || `[[${name}]]`;
68
21
  }
69
22
  });
70
23
  const renderMarkdown = (content) => {
@@ -91,12 +44,12 @@ const generatePageContent = (pathname, routes, footerLinks, data) => {
91
44
  const footerTemplate = `
92
45
  <my-footer
93
46
  copyright="${copyright}"
94
- footerlinks='\${JSON.stringify(footerLinks)}'>
47
+ footer-links='${JSON.stringify(footerLinks)}'>
95
48
  </my-footer>`;
96
49
  const renderContentGists = (items = [], title, type) => {
97
50
  const listHtml = items.length > 0 ? items.map((item) => `
98
51
  <div class="gist-card">
99
- <a href="${type}/${item.slug}" data-route="${type}-${item.slug}"><h4>${item.title}</h4></a>
52
+ <a href="/${type}/${item.slug}" data-route="${type}-${item.slug}"><h4>${item.title}</h4></a>
100
53
  <p>${item.summary || item.description || ""}</p>
101
54
  <small>${new Date(item.date).toLocaleDateString()}</small>
102
55
  </div>
@@ -164,7 +117,7 @@ const generatePageContent = (pathname, routes, footerLinks, data) => {
164
117
  </aside>
165
118
  <div class="wide-main-column text-left">
166
119
  <div id="content-viewer">
167
- ${currentSlug ? isBlog ? `<my-blog-viewer slug="${currentSlug}"></my-blog-viewer>` : `<my-story-viewer slug="${currentSlug}"></my-story-viewer>` : items && items.length > 0 ? `<p>Select a ${type.slice(0, -1)} to read.</p>` : ""}
120
+ ${currentSlug ? isBlog ? `<my-blog-viewer slug="${currentSlug}" api-url="${data?.apiUrl || ""}"></my-blog-viewer>` : `<my-story-viewer slug="${currentSlug}" api-url="${data?.apiUrl || ""}"></my-story-viewer>` : items && items.length > 0 ? `<p>Select a ${type.slice(0, -1)} to read.</p>` : ""}
168
121
  </div>
169
122
  </div>
170
123
  </div>
@@ -220,20 +173,29 @@ class Router {
220
173
  { link: "/stories", text: "Stories" },
221
174
  { link: "/about-me", text: "About Me" }
222
175
  ];
223
- this.siteTitle = config.siteTitle;
224
- this.copyright = config.copyright;
225
176
  this.apiUrl = config.apiUrl;
177
+ }
178
+ get config() {
179
+ return this.ui.getStore().getConfig();
180
+ }
181
+ get siteTitle() {
182
+ return this.config.siteTitle;
183
+ }
184
+ get copyright() {
185
+ return this.config.copyright;
186
+ }
187
+ get footerLinks() {
226
188
  const normalizeUrl = (url) => {
227
189
  if (!url) return "";
228
190
  if (url.startsWith("http://") || url.startsWith("https://")) return url;
229
191
  if (url.startsWith("www.")) return `https://${url}`;
230
192
  return url;
231
193
  };
232
- this.footerLinks = [
233
- { text: "LinkedIn", link: normalizeUrl(config.linkedin) },
234
- { text: "GitHub", link: normalizeUrl(config.github) },
235
- { text: "Email", link: config.email ? `mailto:${config.email}` : "" }
236
- ].filter((link) => link.link !== "");
194
+ return [
195
+ { text: "LinkedIn", link: normalizeUrl(this.config.linkedin) || "https://linkedin.com" },
196
+ { text: "GitHub", link: normalizeUrl(this.config.github) || "https://github.com" },
197
+ { text: "Email", link: this.config.email ? `mailto:${this.config.email}` : "mailto:hello@example.com" }
198
+ ];
237
199
  }
238
200
  init(appElementId = "app") {
239
201
  this.appElement = document.getElementById(appElementId);
@@ -328,6 +290,13 @@ class Router {
328
290
  }
329
291
  canonicalLink.setAttribute("href", url);
330
292
  }
293
+ /**
294
+ * After rendering new content, reinitialize md2interact so it
295
+ * re-scans the DOM for interaction elements (poll, live-update, etc.)
296
+ */
297
+ afterRender() {
298
+ this.ui.reinitInteract();
299
+ }
331
300
  async renderHomePage() {
332
301
  let blogs = [];
333
302
  let stories = [];
@@ -338,9 +307,18 @@ class Router {
338
307
  fetch(`${this.apiUrl}/api/stories`),
339
308
  fetch(`${this.apiUrl}/api/home`)
340
309
  ]);
341
- if (blogsRes.ok) blogs = (await blogsRes.json()).slice(0, 3);
342
- if (storiesRes.ok) stories = (await storiesRes.json()).slice(0, 3);
343
- if (homeRes.ok) homeContent = (await homeRes.json()).content;
310
+ if (blogsRes.ok) {
311
+ const data = await blogsRes.json().catch(() => []);
312
+ blogs = Array.isArray(data) ? data.slice(0, 3) : [];
313
+ }
314
+ if (storiesRes.ok) {
315
+ const data = await storiesRes.json().catch(() => []);
316
+ stories = Array.isArray(data) ? data.slice(0, 3) : [];
317
+ }
318
+ if (homeRes.ok) {
319
+ const data = await homeRes.json().catch(() => ({}));
320
+ homeContent = data.content || "";
321
+ }
344
322
  } catch (e) {
345
323
  console.error("Failed to fetch home content", e);
346
324
  }
@@ -353,6 +331,7 @@ class Router {
353
331
  });
354
332
  if (this.appElement) this.appElement.innerHTML = pageContent.content;
355
333
  this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
334
+ this.afterRender();
356
335
  }
357
336
  renderAboutMePage() {
358
337
  const pageContent = generatePageContent("/about-me", this.routes, this.footerLinks, {
@@ -362,6 +341,7 @@ class Router {
362
341
  });
363
342
  if (this.appElement) this.appElement.innerHTML = pageContent.content;
364
343
  this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
344
+ this.afterRender();
365
345
  }
366
346
  async renderContentListPage(pathname) {
367
347
  const type = pathname === "/blogs" ? "blogs" : "stories";
@@ -372,7 +352,7 @@ class Router {
372
352
  } catch (e) {
373
353
  }
374
354
  const latestSlug = items.length > 0 ? items[0].slug : void 0;
375
- const data = type === "blogs" ? { blogs: items, slug: latestSlug } : { stories: items, slug: latestSlug };
355
+ const data = type === "blogs" ? { blogs: items, slug: latestSlug, apiUrl: this.apiUrl } : { stories: items, slug: latestSlug, apiUrl: this.apiUrl };
376
356
  const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
377
357
  ...data,
378
358
  siteTitle: this.siteTitle,
@@ -380,6 +360,7 @@ class Router {
380
360
  });
381
361
  if (this.appElement) this.appElement.innerHTML = pageContent.content;
382
362
  this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
363
+ this.afterRender();
383
364
  }
384
365
  async renderContentDetailPage(pathname) {
385
366
  const isBlog = pathname.startsWith("/blogs/");
@@ -391,7 +372,7 @@ class Router {
391
372
  if (res.ok) items = await res.json();
392
373
  } catch (e) {
393
374
  }
394
- const data = type === "blogs" ? { blogs: items, slug } : { stories: items, slug };
375
+ const data = type === "blogs" ? { blogs: items, slug, apiUrl: this.apiUrl } : { stories: items, slug, apiUrl: this.apiUrl };
395
376
  const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
396
377
  ...data,
397
378
  siteTitle: this.siteTitle,
@@ -399,12 +380,9 @@ class Router {
399
380
  });
400
381
  if (this.appElement) this.appElement.innerHTML = pageContent.content;
401
382
  this.setPageMeta(`${slug.replace(/-/g, " ")} - ${this.siteTitle}`, "Read more content", window.location.href);
383
+ this.afterRender();
402
384
  }
403
385
  async renderAdminPage() {
404
- generatePageContent("/admin", this.routes, this.footerLinks, {
405
- siteTitle: this.siteTitle,
406
- copyright: this.copyright
407
- });
408
386
  if (this.appElement) {
409
387
  this.appElement.innerHTML = `
410
388
  <my-banner header="${this.siteTitle}" logo="${this.logo}">
@@ -416,14 +394,16 @@ class Router {
416
394
  <main class="container container-medium">
417
395
  <admin-portal></admin-portal>
418
396
  </main>
419
- <my-footer copyright="${this.copyright}" footerlinks='[]'></my-footer>
397
+ <my-footer copyright="${this.copyright}" footerLinks='${JSON.stringify(this.footerLinks)}'></my-footer>
420
398
  `;
421
399
  }
400
+ this.afterRender();
422
401
  }
423
402
  }
424
403
  class WebsiteUI {
425
404
  constructor(config = {}) {
426
405
  this.router = null;
406
+ this.interactInitialized = false;
427
407
  this.store = SiteStore.getInstance();
428
408
  this.config = config;
429
409
  }
@@ -440,14 +420,64 @@ class WebsiteUI {
440
420
  apiUrl: this.config.apiUrl,
441
421
  baseUrl: this.config.baseUrl
442
422
  });
423
+ this.store.subscribe((config) => {
424
+ this.applyTheme();
425
+ this.updateFavicon();
426
+ this.updateBanner(config);
427
+ if (this.router && window.location.pathname !== "/admin") {
428
+ this.router.navigate(window.location.pathname);
429
+ }
430
+ });
443
431
  this.applyTheme();
432
+ this.updateFavicon();
433
+ this.updateBanner(this.store.getConfig());
444
434
  this.router = new Router(this);
445
435
  this.router.init(this.config.appElementId || "app");
436
+ this.initInteract();
446
437
  if (this.config.onBootstrap) {
447
438
  await this.config.onBootstrap(this);
448
439
  }
449
440
  console.log("WebsiteUI bootstrapped");
450
441
  }
442
+ /**
443
+ * Initialize md2interact for client-side DOM interactions,
444
+ * CSS hydration, and event bus communication.
445
+ */
446
+ initInteract() {
447
+ if (this.interactInitialized) return;
448
+ const interactCfg = this.config.interactConfig || {};
449
+ init({
450
+ interactions: interactCfg.interactions || {
451
+ "poll": { selector: '[data-interact="poll"]' },
452
+ "live-update": { selector: '[data-interact="live-update"]' },
453
+ "click-toggle": { selector: '[data-interact="click-toggle"]' },
454
+ "infinite-scroll": { selector: '[data-interact="infinite-scroll"]' },
455
+ "form-live": { selector: '[data-interact="form-live"]' }
456
+ },
457
+ cssHydration: interactCfg.cssHydration || {
458
+ inlineCritical: true,
459
+ layerInjection: true,
460
+ themeToggle: true
461
+ }
462
+ });
463
+ this.interactInitialized = true;
464
+ }
465
+ /**
466
+ * Reinitialize md2interact after dynamic content changes (e.g., SPA navigation).
467
+ * This re-scans the DOM for new interaction elements.
468
+ */
469
+ reinitInteract() {
470
+ if (this.interactInitialized) {
471
+ reinit();
472
+ }
473
+ }
474
+ updateBanner(config) {
475
+ const banner = document.querySelector("my-banner");
476
+ if (banner) {
477
+ banner.setAttribute("header", config.siteTitle || "My Site");
478
+ banner.setAttribute("logo", `${config.apiUrl}/api/logo?t=${Date.now()}`);
479
+ }
480
+ }
451
481
  applyTheme() {
452
482
  if (!this.config.theme) return;
453
483
  const { theme } = this.config;
@@ -461,6 +491,18 @@ class WebsiteUI {
461
491
  document.head.appendChild(style);
462
492
  }
463
493
  }
494
+ updateFavicon() {
495
+ const favicon = document.querySelector('link[rel="icon"]');
496
+ const logoUrl = `/api/logo?t=${Date.now()}`;
497
+ if (favicon) {
498
+ favicon.setAttribute("href", logoUrl);
499
+ } else {
500
+ const newFavicon = document.createElement("link");
501
+ newFavicon.rel = "icon";
502
+ newFavicon.href = logoUrl;
503
+ document.head.appendChild(newFavicon);
504
+ }
505
+ }
464
506
  getStore() {
465
507
  return this.store;
466
508
  }
@@ -506,13 +548,12 @@ class ThemeToggle extends HTMLElement {
506
548
  cursor: pointer;
507
549
  font-size: 1.5rem;
508
550
  padding: 0.5rem;
509
- color: var(--text-color, #213547); /* Default for light mode */
551
+ color: var(--text-color, #213547);
510
552
  transition: color 0.3s ease;
511
553
  }
512
554
  button:hover {
513
- color: var(--primary-color, #747bff); /* Hover color */
555
+ color: var(--primary-color, #747bff);
514
556
  }
515
- /* Dark mode specific styles for the button */
516
557
  html[data-theme='dark'] button {
517
558
  color: var(--dark-mode-text-color, rgba(255, 255, 255, 0.87));
518
559
  }
@@ -521,7 +562,7 @@ class ThemeToggle extends HTMLElement {
521
562
  }
522
563
  </style>
523
564
  <button id="theme-toggle" aria-label="Toggle theme">
524
- <span class="icon">${this.getSunIcon()}</span> <!-- Default to sun for light mode -->
565
+ <span class="icon">${this.getSunIcon()}</span>
525
566
  </button>
526
567
  `;
527
568
  }
@@ -610,13 +651,10 @@ const createHtmlTemplate = ({
610
651
  };
611
652
  export {
612
653
  Router as R,
613
- SiteStore as S,
614
654
  ThemeToggle as T,
615
655
  WebsiteUI as W,
616
- getConfig as a,
617
656
  bootstrap as b,
618
657
  createHtmlTemplate as c,
619
658
  generatePageContent as g,
620
- initializeConfig as i,
621
659
  renderMarkdown as r
622
660
  };