@leadertechie/personal-site-kit 0.1.0-alpha.6 → 0.1.0-alpha.8

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 (109) hide show
  1. package/dist/api/handlers/about-me.d.ts.map +1 -1
  2. package/dist/api/handlers/auth-handler.d.ts.map +1 -1
  3. package/dist/api/website-api.d.ts.map +1 -1
  4. package/dist/api.js +2 -2
  5. package/dist/chunks/index-CGvOrVf8.js +213 -0
  6. package/dist/chunks/{index-C3wLSCKU.js → index-CYd_Pe2U.js} +1353 -482
  7. package/dist/chunks/{template-MawmknFQ.js → template-D1uGvdWZ.js} +10 -8
  8. package/dist/chunks/{website-api-DI3muo2s.js → website-api-FLejlWxJ.js} +78 -41
  9. package/dist/index.js +19 -9
  10. package/dist/prerender/data-fetcher.d.ts +19 -0
  11. package/dist/prerender/data-fetcher.d.ts.map +1 -0
  12. package/dist/prerender/page-content.d.ts.map +1 -1
  13. package/dist/prerender/page-generators/about.d.ts +16 -0
  14. package/dist/prerender/page-generators/about.d.ts.map +1 -0
  15. package/dist/prerender/page-generators/base.d.ts +26 -0
  16. package/dist/prerender/page-generators/base.d.ts.map +1 -0
  17. package/dist/prerender/page-generators/blog-detail.d.ts +15 -0
  18. package/dist/prerender/page-generators/blog-detail.d.ts.map +1 -0
  19. package/dist/prerender/page-generators/blogs-list.d.ts +17 -0
  20. package/dist/prerender/page-generators/blogs-list.d.ts.map +1 -0
  21. package/dist/prerender/page-generators/home.d.ts +19 -0
  22. package/dist/prerender/page-generators/home.d.ts.map +1 -0
  23. package/dist/prerender/page-generators/index.d.ts +9 -0
  24. package/dist/prerender/page-generators/index.d.ts.map +1 -0
  25. package/dist/prerender/page-generators/not-found.d.ts +14 -0
  26. package/dist/prerender/page-generators/not-found.d.ts.map +1 -0
  27. package/dist/prerender/page-generators/stories-list.d.ts +17 -0
  28. package/dist/prerender/page-generators/stories-list.d.ts.map +1 -0
  29. package/dist/prerender/page-generators/story-detail.d.ts +15 -0
  30. package/dist/prerender/page-generators/story-detail.d.ts.map +1 -0
  31. package/dist/prerender.js +109 -102
  32. package/dist/shared/config/index.d.ts.map +1 -1
  33. package/dist/shared.js +1 -1
  34. package/dist/ui/about-me/index.d.ts +2 -10
  35. package/dist/ui/about-me/index.d.ts.map +1 -1
  36. package/dist/ui/admin/api.d.ts +16 -0
  37. package/dist/ui/admin/api.d.ts.map +1 -0
  38. package/dist/ui/admin/components/AboutMeSection.d.ts +7 -0
  39. package/dist/ui/admin/components/AboutMeSection.d.ts.map +1 -0
  40. package/dist/ui/admin/components/AdminSection.d.ts +13 -0
  41. package/dist/ui/admin/components/AdminSection.d.ts.map +1 -0
  42. package/dist/ui/admin/components/BlogsSection.d.ts +7 -0
  43. package/dist/ui/admin/components/BlogsSection.d.ts.map +1 -0
  44. package/dist/ui/admin/components/HomeSection.d.ts +7 -0
  45. package/dist/ui/admin/components/HomeSection.d.ts.map +1 -0
  46. package/dist/ui/admin/components/ImagesSection.d.ts +7 -0
  47. package/dist/ui/admin/components/ImagesSection.d.ts.map +1 -0
  48. package/dist/ui/admin/components/LoginForm.d.ts +9 -0
  49. package/dist/ui/admin/components/LoginForm.d.ts.map +1 -0
  50. package/dist/ui/admin/components/LogoSection.d.ts +7 -0
  51. package/dist/ui/admin/components/LogoSection.d.ts.map +1 -0
  52. package/dist/ui/admin/components/ProfileSection.d.ts +7 -0
  53. package/dist/ui/admin/components/ProfileSection.d.ts.map +1 -0
  54. package/dist/ui/admin/components/StaticSection.d.ts +13 -0
  55. package/dist/ui/admin/components/StaticSection.d.ts.map +1 -0
  56. package/dist/ui/admin/components/StoriesSection.d.ts +7 -0
  57. package/dist/ui/admin/components/StoriesSection.d.ts.map +1 -0
  58. package/dist/ui/admin/components/index.d.ts +11 -0
  59. package/dist/ui/admin/components/index.d.ts.map +1 -0
  60. package/dist/ui/admin/index.d.ts +10 -26
  61. package/dist/ui/admin/index.d.ts.map +1 -1
  62. package/dist/ui/admin/types.d.ts +24 -0
  63. package/dist/ui/admin/types.d.ts.map +1 -0
  64. package/dist/ui/blog-viewer/index.d.ts.map +1 -1
  65. package/dist/ui/index.d.ts.map +1 -1
  66. package/dist/ui/story-viewer/index.d.ts.map +1 -1
  67. package/dist/ui.js +14 -4
  68. package/package.json +1 -1
  69. package/src/api/handlers/about-me.ts +19 -9
  70. package/src/api/handlers/auth-handler.ts +41 -18
  71. package/src/api/handlers/content.ts +1 -1
  72. package/src/api/handlers/home.ts +2 -2
  73. package/src/api/website-api.ts +25 -13
  74. package/src/prerender/__tests__/page-content.test.ts +1 -11
  75. package/src/prerender/data-fetcher.ts +93 -0
  76. package/src/prerender/page-content.ts +109 -106
  77. package/src/prerender/page-generators/about.ts +38 -0
  78. package/src/prerender/page-generators/base.ts +77 -0
  79. package/src/prerender/page-generators/blog-detail.ts +35 -0
  80. package/src/prerender/page-generators/blogs-list.ts +43 -0
  81. package/src/prerender/page-generators/home.ts +54 -0
  82. package/src/prerender/page-generators/index.ts +8 -0
  83. package/src/prerender/page-generators/not-found.ts +36 -0
  84. package/src/prerender/page-generators/stories-list.ts +43 -0
  85. package/src/prerender/page-generators/story-detail.ts +35 -0
  86. package/src/shared/config/index.ts +4 -2
  87. package/src/shared/page-content.ts +1 -1
  88. package/src/shared/router.ts +5 -5
  89. package/src/ui/about-me/index.ts +23 -57
  90. package/src/ui/admin/api.ts +93 -0
  91. package/src/ui/admin/components/AboutMeSection.ts +47 -0
  92. package/src/ui/admin/components/AdminSection.ts +134 -0
  93. package/src/ui/admin/components/BlogsSection.ts +62 -0
  94. package/src/ui/admin/components/HomeSection.ts +47 -0
  95. package/src/ui/admin/components/ImagesSection.ts +54 -0
  96. package/src/ui/admin/components/LoginForm.ts +116 -0
  97. package/src/ui/admin/components/LogoSection.ts +51 -0
  98. package/src/ui/admin/components/ProfileSection.ts +47 -0
  99. package/src/ui/admin/components/StaticSection.ts +67 -0
  100. package/src/ui/admin/components/StoriesSection.ts +62 -0
  101. package/src/ui/admin/components/index.ts +10 -0
  102. package/src/ui/admin/index.ts +192 -434
  103. package/src/ui/admin/types.ts +26 -0
  104. package/src/ui/blog-viewer/index.ts +4 -1
  105. package/src/ui/index.ts +7 -0
  106. package/src/ui/story-viewer/index.ts +4 -1
  107. package/dist/ui/about-me/renderer.d.ts +0 -5
  108. package/dist/ui/about-me/renderer.d.ts.map +0 -1
  109. package/src/ui/about-me/renderer.ts +0 -7
@@ -68,7 +68,7 @@ async function fetchHome(env: any): Promise<string> {
68
68
  try {
69
69
  const r2 = getLoader(env);
70
70
  if (!r2) return "";
71
- const result = await r2.getRendered("home.md");
71
+ const result = await r2.getRendered("pages/home.md");
72
72
  return result?.content || "";
73
73
  } catch { return ""; }
74
74
  }
@@ -115,8 +115,8 @@ export const generatePageContent = async (
115
115
  footerLinks: IFooterLink[],
116
116
  env?: any
117
117
  ): Promise<PageContent> => {
118
- const apiUrl = env?.apiUrl || "https://api.techieleader.com";
119
- const baseUrl = env?.baseUrl || "https://www.techieleader.com";
118
+ const apiUrl = env?.API_URL || env?.apiUrl || "https://api.example.com";
119
+ const baseUrl = env?.BASE_URL || env?.baseUrl || "https://www.example.com";
120
120
 
121
121
  let staticDetails = {
122
122
  siteTitle: "My Personal Website",
@@ -131,23 +131,6 @@ export const generatePageContent = async (
131
131
  if (res.ok) staticDetails = await res.json();
132
132
  } catch (e) {}
133
133
 
134
- const logo = "/api/logo";
135
- const navLinks = routes.map(r => `<a href="${r.link}" class="nav-link" data-route="${r.link === "/" ? "home" : r.text.toLowerCase()}">${r.text}</a>`).join("");
136
-
137
- const bannerTemplate = `
138
- <my-banner header="${staticDetails.siteTitle}" logo="${logo}">
139
- <theme-toggle slot="theme-switcher"></theme-toggle>
140
- <nav slot="nav-links">
141
- ${navLinks}
142
- </nav>
143
- </my-banner>`;
144
-
145
- const footerTemplate = `
146
- <my-footer
147
- copyright="${staticDetails.copyright}"
148
- footerlinks='${JSON.stringify(footerLinks)}'>
149
- </my-footer>`;
150
-
151
134
  let profile: Profile | null = null;
152
135
  let aboutMeContent = "";
153
136
  let homeContent = "";
@@ -162,102 +145,122 @@ export const generatePageContent = async (
162
145
 
163
146
  const name = profile?.name || "User";
164
147
  const title = profile?.title || "Professional";
165
- const experience = profile?.experience || "some";
166
148
  const canonicalUrl = new URL(pathname, baseUrl).toString();
167
149
 
168
- if (pathname === "/" || pathname === "") {
169
- const homeHtml = homeContent || `<h1>Welcome to ${name}</h1><p>Upload home.md to customize this page.</p>`;
170
- const blogGists = latestBlogs.map(b => `<div class="gist-card"><a href="/blogs/${b.slug}"><h4>${b.title}</h4></a><p>${b.summary}</p><small>${b.date}</small></div>`).join("");
171
- const storyGists = latestStories.map(s => `<div class="gist-card"><a href="/stories/${s.slug}"><h4>${s.title}</h4></a><p>${s.summary}</p><small>${s.date}</small></div>`).join("");
172
-
173
- const mainContent = `
174
- ${bannerTemplate}
175
- <main class="container container-wide column-layout">
176
- <div class="main-column">
177
- ${homeHtml}
178
- </div>
179
- <div class="sidebar-column">
180
- <h3>Recent Blogs</h3>
181
- ${blogGists || "<p>No blogs yet.</p>"}
182
- <h3 class="mt-2">Recent Stories</h3>
183
- ${storyGists || "<p>No stories yet.</p>"}
184
- </div>
185
- </main>
186
- ${footerTemplate}`;
150
+ // Strategy pattern: map pathname patterns to generators
151
+ const strategies = {
152
+ home: async () => {
153
+ const { HomePageGenerator } = await import('./page-generators');
154
+ const generator = new HomePageGenerator();
155
+ return generator.generate({
156
+ routes,
157
+ footerLinks,
158
+ staticDetails,
159
+ apiUrl,
160
+ baseUrl,
161
+ pathname,
162
+ profile,
163
+ homeContent,
164
+ latestBlogs,
165
+ latestStories
166
+ });
167
+ },
168
+ about: async () => {
169
+ const { AboutPageGenerator } = await import('./page-generators');
170
+ const generator = new AboutPageGenerator();
171
+ return generator.generate({
172
+ routes,
173
+ footerLinks,
174
+ staticDetails,
175
+ apiUrl,
176
+ baseUrl,
177
+ pathname,
178
+ profile
179
+ });
180
+ },
181
+ blogsList: async () => {
182
+ const { BlogsListPageGenerator } = await import('./page-generators');
183
+ const generator = new BlogsListPageGenerator();
184
+ return generator.generate({
185
+ routes,
186
+ footerLinks,
187
+ staticDetails,
188
+ apiUrl,
189
+ baseUrl,
190
+ pathname,
191
+ latestBlogs,
192
+ name
193
+ });
194
+ },
195
+ storiesList: async () => {
196
+ const { StoriesListPageGenerator } = await import('./page-generators');
197
+ const generator = new StoriesListPageGenerator();
198
+ return generator.generate({
199
+ routes,
200
+ footerLinks,
201
+ staticDetails,
202
+ apiUrl,
203
+ baseUrl,
204
+ pathname,
205
+ latestStories,
206
+ name
207
+ });
208
+ },
209
+ blogDetail: async (slug: string) => {
210
+ const { BlogDetailPageGenerator } = await import('./page-generators');
211
+ const generator = new BlogDetailPageGenerator();
212
+ return generator.generate({
213
+ routes,
214
+ footerLinks,
215
+ staticDetails,
216
+ apiUrl,
217
+ baseUrl,
218
+ pathname,
219
+ slug
220
+ });
221
+ },
222
+ storyDetail: async (slug: string) => {
223
+ const { StoryDetailPageGenerator } = await import('./page-generators');
224
+ const generator = new StoryDetailPageGenerator();
225
+ return generator.generate({
226
+ routes,
227
+ footerLinks,
228
+ staticDetails,
229
+ apiUrl,
230
+ baseUrl,
231
+ pathname,
232
+ slug
233
+ });
234
+ },
235
+ notFound: async () => {
236
+ const { NotFoundPageGenerator } = await import('./page-generators');
237
+ const generator = new NotFoundPageGenerator();
238
+ return generator.generate({
239
+ routes,
240
+ footerLinks,
241
+ staticDetails,
242
+ apiUrl,
243
+ baseUrl,
244
+ pathname
245
+ });
246
+ }
247
+ };
187
248
 
188
- return {
189
- title: `${name} – ${title}`,
190
- description: `Welcome to ${name}'s personal website. Professional portfolio and content.`,
191
- canonicalUrl,
192
- content: mainContent
193
- };
249
+ if (pathname === "/" || pathname === "") {
250
+ return strategies.home();
194
251
  } else if (pathname === "/about-me") {
195
- const mainContent = `
196
- ${bannerTemplate}
197
- <main class="container container-narrow">
198
- <my-aboutme base-url="${apiUrl}"></my-aboutme>
199
- </main>
200
- ${footerTemplate}`;
201
-
202
- return {
203
- title: `About - ${name}`,
204
- description: `Learn more about ${name}'s experience and skills.`,
205
- canonicalUrl,
206
- content: mainContent
207
- };
252
+ return strategies.about();
208
253
  } else if (pathname === "/blogs" || pathname === "/blogs/") {
209
- const blogGists = latestBlogs.map(b => `<div class="gist-card"><a href="/blogs/${b.slug}"><h4>${b.title}</h4></a><p>${b.summary}</p><small>${b.date}</small></div>`).join("");
210
- const mainContent = `
211
- ${bannerTemplate}
212
- <main class="container container-wide">
213
- <h1>Blogs</h1>
214
- <input type="text" placeholder="Search blogs..." class="search-input" />
215
- <div class="blog-list">
216
- ${blogGists || "<p>No blogs yet.</p>"}
217
- </div>
218
- </main>
219
- ${footerTemplate}`;
220
- return { title: `Blogs – ${name}`, description: "Read the latest blog posts.", canonicalUrl, content: mainContent };
254
+ return strategies.blogsList();
221
255
  } else if (pathname === "/stories" || pathname === "/stories/") {
222
- const storyGists = latestStories.map(s => `<div class="gist-card"><a href="/stories/${s.slug}"><h4>${s.title}</h4></a><p>${s.summary}</p><small>${s.date}</small></div>`).join("");
223
- const mainContent = `
224
- ${bannerTemplate}
225
- <main class="container container-wide">
226
- <h1>Stories</h1>
227
- <input type="text" placeholder="Search stories..." class="search-input" />
228
- <div class="story-list">
229
- ${storyGists || "<p>No stories yet.</p>"}
230
- </div>
231
- </main>
232
- ${footerTemplate}`;
233
- return { title: `Stories – ${name}`, description: "Read the latest stories.", canonicalUrl, content: mainContent };
256
+ return strategies.storiesList();
234
257
  } else if (pathname.startsWith("/blogs/")) {
235
258
  const slug = pathname.replace("/blogs/", "").replace("/", "");
236
- const mainContent = `
237
- ${bannerTemplate}
238
- <main class="container container-narrow">
239
- <my-blog-viewer slug="${slug}"></my-blog-viewer>
240
- </main>
241
- ${footerTemplate}`;
242
- return { title: `Blog: ${slug}`, description: "Blog post", canonicalUrl, content: mainContent };
259
+ return strategies.blogDetail(slug);
243
260
  } else if (pathname.startsWith("/stories/")) {
244
261
  const slug = pathname.replace("/stories/", "").replace("/", "");
245
- const mainContent = `
246
- ${bannerTemplate}
247
- <main class="container container-narrow">
248
- <my-story-viewer slug="${slug}"></my-story-viewer>
249
- </main>
250
- ${footerTemplate}`;
251
- return { title: `Story: ${slug}`, description: "Story post", canonicalUrl, content: mainContent };
262
+ return strategies.storyDetail(slug);
252
263
  } else {
253
- const mainContent = `
254
- ${bannerTemplate}
255
- <main class="container container-narrow text-center">
256
- <h1>Page Not Found</h1>
257
- <p>The page you're looking for doesn't exist.</p>
258
- <p><a href="/">Return to home</a></p>
259
- </main>
260
- ${footerTemplate}`;
261
- return { title: "404 Not Found", description: "The page you requested could not be found.", canonicalUrl, content: mainContent };
264
+ return strategies.notFound();
262
265
  }
263
266
  };
@@ -0,0 +1,38 @@
1
+ import { BasePageGenerator, StaticDetails } from './base';
2
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
3
+ import { Profile } from '../data-fetcher';
4
+
5
+ export interface AboutPageData {
6
+ routes: IRoute[];
7
+ footerLinks: IFooterLink[];
8
+ staticDetails: StaticDetails;
9
+ apiUrl: string;
10
+ baseUrl: string;
11
+ pathname: string;
12
+ profile: Profile | null;
13
+ }
14
+
15
+ export class AboutPageGenerator extends BasePageGenerator {
16
+ public generate(data: AboutPageData): PageContent {
17
+ const { profile, staticDetails, ...baseData } = data;
18
+
19
+ const name = profile?.name || "User";
20
+
21
+ const mainContent = `
22
+ <main class="container container-narrow">
23
+ <my-aboutme base-url="${baseData.apiUrl}"></my-aboutme>
24
+ </main>`;
25
+
26
+ return this.generatePage(
27
+ baseData.pathname,
28
+ baseData.routes,
29
+ baseData.footerLinks,
30
+ staticDetails,
31
+ baseData.apiUrl,
32
+ baseData.baseUrl,
33
+ mainContent,
34
+ `About - ${name}`,
35
+ `Learn more about ${name}'s experience and skills.`
36
+ );
37
+ }
38
+ }
@@ -0,0 +1,77 @@
1
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
2
+
3
+ export interface StaticDetails {
4
+ siteTitle?: string;
5
+ copyright?: string;
6
+ linkedin?: string;
7
+ github?: string;
8
+ email?: string;
9
+ }
10
+
11
+ export interface BasePageData {
12
+ routes: IRoute[];
13
+ footerLinks: IFooterLink[];
14
+ staticDetails: StaticDetails;
15
+ apiUrl: string;
16
+ baseUrl: string;
17
+ pathname: string;
18
+ name?: string;
19
+ title?: string;
20
+ }
21
+
22
+ export class BasePageGenerator {
23
+ protected generateBanner(routes: IRoute[], siteTitle: string, logo: string): string {
24
+ const navLinks = routes
25
+ .map(r => `<a href="${r.link}" class="nav-link" data-route="${r.link === "/" ? "home" : r.text.toLowerCase()}">${r.text}</a>`)
26
+ .join("");
27
+
28
+ return `
29
+ <my-banner header="${siteTitle}" logo="${logo}">
30
+ <theme-toggle slot="theme-switcher"></theme-toggle>
31
+ <nav slot="nav-links">
32
+ ${navLinks}
33
+ </nav>
34
+ </my-banner>`;
35
+ }
36
+
37
+ protected generateFooter(footerLinks: IFooterLink[], copyright: string): string {
38
+ return `
39
+ <my-footer
40
+ copyright="${copyright}"
41
+ footerlinks='${JSON.stringify(footerLinks)}'>
42
+ </my-footer>`;
43
+ }
44
+
45
+ protected generateMeta(title: string, description: string, canonicalUrl: string): void {
46
+ // This would be handled in the main function
47
+ }
48
+
49
+ protected wrapContent(banner: string, mainContent: string, footer: string): string {
50
+ return `${banner}${mainContent}${footer}`;
51
+ }
52
+
53
+ public generatePage(
54
+ pathname: string,
55
+ routes: IRoute[],
56
+ footerLinks: IFooterLink[],
57
+ staticDetails: StaticDetails,
58
+ apiUrl: string,
59
+ baseUrl: string,
60
+ mainContent: string,
61
+ title: string,
62
+ description: string
63
+ ): PageContent {
64
+ const logo = "/api/logo";
65
+ const banner = this.generateBanner(routes, staticDetails.siteTitle || "My Personal Website", logo);
66
+ const footer = this.generateFooter(footerLinks, staticDetails.copyright || "2026 My Personal Website");
67
+ const canonicalUrl = new URL(pathname, baseUrl).toString();
68
+ const content = this.wrapContent(banner, mainContent, footer);
69
+
70
+ return {
71
+ title,
72
+ description,
73
+ canonicalUrl,
74
+ content
75
+ };
76
+ }
77
+ }
@@ -0,0 +1,35 @@
1
+ import { BasePageGenerator, StaticDetails } from './base';
2
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
3
+
4
+ export interface BlogDetailPageData {
5
+ routes: IRoute[];
6
+ footerLinks: IFooterLink[];
7
+ staticDetails: StaticDetails;
8
+ apiUrl: string;
9
+ baseUrl: string;
10
+ pathname: string;
11
+ slug: string;
12
+ }
13
+
14
+ export class BlogDetailPageGenerator extends BasePageGenerator {
15
+ public generate(data: BlogDetailPageData): PageContent {
16
+ const { slug, staticDetails, ...baseData } = data;
17
+
18
+ const mainContent = `
19
+ <main class="container container-narrow">
20
+ <my-blog-viewer slug="${slug}"></my-blog-viewer>
21
+ </main>`;
22
+
23
+ return this.generatePage(
24
+ baseData.pathname,
25
+ baseData.routes,
26
+ baseData.footerLinks,
27
+ staticDetails,
28
+ baseData.apiUrl,
29
+ baseData.baseUrl,
30
+ mainContent,
31
+ `Blog: ${slug}`,
32
+ "Blog post"
33
+ );
34
+ }
35
+ }
@@ -0,0 +1,43 @@
1
+ import { BasePageGenerator, StaticDetails } from './base';
2
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
3
+ import { BlogMeta } from '../data-fetcher';
4
+
5
+ export interface BlogsListPageData {
6
+ routes: IRoute[];
7
+ footerLinks: IFooterLink[];
8
+ staticDetails: StaticDetails;
9
+ apiUrl: string;
10
+ baseUrl: string;
11
+ pathname: string;
12
+ latestBlogs: BlogMeta[];
13
+ name: string;
14
+ }
15
+
16
+ export class BlogsListPageGenerator extends BasePageGenerator {
17
+ public generate(data: BlogsListPageData): PageContent {
18
+ const { latestBlogs, name, staticDetails, ...baseData } = data;
19
+
20
+ const blogGists = latestBlogs.map(b => `<div class="gist-card"><a href="/blogs/${b.slug}"><h4>${b.title}</h4></a><p>${b.summary}</p><small>${b.date}</small></div>`).join("");
21
+
22
+ const mainContent = `
23
+ <main class="container container-wide">
24
+ <h1>Blogs</h1>
25
+ <input type="text" placeholder="Search blogs..." class="search-input" />
26
+ <div class="blog-list">
27
+ ${blogGists || "<p>No blogs yet.</p>"}
28
+ </div>
29
+ </main>`;
30
+
31
+ return this.generatePage(
32
+ baseData.pathname,
33
+ baseData.routes,
34
+ baseData.footerLinks,
35
+ staticDetails,
36
+ baseData.apiUrl,
37
+ baseData.baseUrl,
38
+ mainContent,
39
+ `Blogs – ${name}`,
40
+ "Read the latest blog posts."
41
+ );
42
+ }
43
+ }
@@ -0,0 +1,54 @@
1
+ import { BasePageGenerator, StaticDetails } from './base';
2
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
3
+ import { Profile, BlogMeta } from '../data-fetcher';
4
+
5
+ export interface HomePageData {
6
+ routes: IRoute[];
7
+ footerLinks: IFooterLink[];
8
+ staticDetails: StaticDetails;
9
+ apiUrl: string;
10
+ baseUrl: string;
11
+ pathname: string;
12
+ profile: Profile | null;
13
+ homeContent: string;
14
+ latestBlogs: BlogMeta[];
15
+ latestStories: BlogMeta[];
16
+ }
17
+
18
+ export class HomePageGenerator extends BasePageGenerator {
19
+ public generate(data: HomePageData): PageContent {
20
+ const { profile, homeContent, latestBlogs, latestStories, staticDetails, ...baseData } = data;
21
+
22
+ const name = profile?.name || "User";
23
+ const title = profile?.title || "Professional";
24
+
25
+ const homeHtml = homeContent || `<h1>Welcome to ${name}</h1><p>Upload home.md to customize this page.</p>`;
26
+ const blogGists = latestBlogs.map(b => `<div class="gist-card"><a href="/blogs/${b.slug}"><h4>${b.title}</h4></a><p>${b.summary}</p><small>${b.date}</small></div>`).join("");
27
+ const storyGists = latestStories.map(s => `<div class="gist-card"><a href="/stories/${s.slug}"><h4>${s.title}</h4></a><p>${s.summary}</p><small>${s.date}</small></div>`).join("");
28
+
29
+ const mainContent = `
30
+ <main class="container container-wide column-layout">
31
+ <div class="main-column">
32
+ ${homeHtml}
33
+ </div>
34
+ <div class="sidebar-column">
35
+ <h3>Recent Blogs</h3>
36
+ ${blogGists || "<p>No blogs yet.</p>"}
37
+ <h3 class="mt-2">Recent Stories</h3>
38
+ ${storyGists || "<p>No stories yet.</p>"}
39
+ </div>
40
+ </main>`;
41
+
42
+ return this.generatePage(
43
+ baseData.pathname,
44
+ baseData.routes,
45
+ baseData.footerLinks,
46
+ staticDetails,
47
+ baseData.apiUrl,
48
+ baseData.baseUrl,
49
+ mainContent,
50
+ `${name} – ${title}`,
51
+ `Welcome to ${name}'s personal website. Professional portfolio and content.`
52
+ );
53
+ }
54
+ }
@@ -0,0 +1,8 @@
1
+ export * from './base';
2
+ export * from './home';
3
+ export * from './about';
4
+ export * from './blogs-list';
5
+ export * from './stories-list';
6
+ export * from './blog-detail';
7
+ export * from './story-detail';
8
+ export * from './not-found';
@@ -0,0 +1,36 @@
1
+ import { BasePageGenerator, StaticDetails } from './base';
2
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
3
+
4
+ export interface NotFoundPageData {
5
+ routes: IRoute[];
6
+ footerLinks: IFooterLink[];
7
+ staticDetails: StaticDetails;
8
+ apiUrl: string;
9
+ baseUrl: string;
10
+ pathname: string;
11
+ }
12
+
13
+ export class NotFoundPageGenerator extends BasePageGenerator {
14
+ public generate(data: NotFoundPageData): PageContent {
15
+ const { staticDetails, ...baseData } = data;
16
+
17
+ const mainContent = `
18
+ <main class="container container-narrow text-center">
19
+ <h1>Page Not Found</h1>
20
+ <p>The page you're looking for doesn't exist.</p>
21
+ <p><a href="/">Return to home</a></p>
22
+ </main>`;
23
+
24
+ return this.generatePage(
25
+ baseData.pathname,
26
+ baseData.routes,
27
+ baseData.footerLinks,
28
+ staticDetails,
29
+ baseData.apiUrl,
30
+ baseData.baseUrl,
31
+ mainContent,
32
+ "404 Not Found",
33
+ "The page you requested could not be found."
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,43 @@
1
+ import { BasePageGenerator, StaticDetails } from './base';
2
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
3
+ import { BlogMeta } from '../data-fetcher';
4
+
5
+ export interface StoriesListPageData {
6
+ routes: IRoute[];
7
+ footerLinks: IFooterLink[];
8
+ staticDetails: StaticDetails;
9
+ apiUrl: string;
10
+ baseUrl: string;
11
+ pathname: string;
12
+ latestStories: BlogMeta[];
13
+ name: string;
14
+ }
15
+
16
+ export class StoriesListPageGenerator extends BasePageGenerator {
17
+ public generate(data: StoriesListPageData): PageContent {
18
+ const { latestStories, name, staticDetails, ...baseData } = data;
19
+
20
+ const storyGists = latestStories.map(s => `<div class="gist-card"><a href="/stories/${s.slug}"><h4>${s.title}</h4></a><p>${s.summary}</p><small>${s.date}</small></div>`).join("");
21
+
22
+ const mainContent = `
23
+ <main class="container container-wide">
24
+ <h1>Stories</h1>
25
+ <input type="text" placeholder="Search stories..." class="search-input" />
26
+ <div class="story-list">
27
+ ${storyGists || "<p>No stories yet.</p>"}
28
+ </div>
29
+ </main>`;
30
+
31
+ return this.generatePage(
32
+ baseData.pathname,
33
+ baseData.routes,
34
+ baseData.footerLinks,
35
+ staticDetails,
36
+ baseData.apiUrl,
37
+ baseData.baseUrl,
38
+ mainContent,
39
+ `Stories – ${name}`,
40
+ "Read the latest stories."
41
+ );
42
+ }
43
+ }
@@ -0,0 +1,35 @@
1
+ import { BasePageGenerator, StaticDetails } from './base';
2
+ import { IRoute, IFooterLink, PageContent } from '../page-content';
3
+
4
+ export interface StoryDetailPageData {
5
+ routes: IRoute[];
6
+ footerLinks: IFooterLink[];
7
+ staticDetails: StaticDetails;
8
+ apiUrl: string;
9
+ baseUrl: string;
10
+ pathname: string;
11
+ slug: string;
12
+ }
13
+
14
+ export class StoryDetailPageGenerator extends BasePageGenerator {
15
+ public generate(data: StoryDetailPageData): PageContent {
16
+ const { slug, staticDetails, ...baseData } = data;
17
+
18
+ const mainContent = `
19
+ <main class="container container-narrow">
20
+ <my-story-viewer slug="${slug}"></my-story-viewer>
21
+ </main>`;
22
+
23
+ return this.generatePage(
24
+ baseData.pathname,
25
+ baseData.routes,
26
+ baseData.footerLinks,
27
+ staticDetails,
28
+ baseData.apiUrl,
29
+ baseData.baseUrl,
30
+ mainContent,
31
+ `Story: ${slug}`,
32
+ "Story post"
33
+ );
34
+ }
35
+ }
@@ -4,7 +4,7 @@ export * from './types';
4
4
 
5
5
  const DEFAULT_INFRA: InfrastructureConfig = {
6
6
  baseUrl: typeof window !== 'undefined' ? window.location.origin : 'http://localhost:5173',
7
- apiUrl: (typeof window !== 'undefined' && (window as any).__VITE_API_URL__) || 'http://localhost:8787'
7
+ apiUrl: (typeof window !== 'undefined' && ((window as any).__VITE_API_URL__ || (import.meta as any).env?.VITE_API_URL)) || 'http://localhost:8788'
8
8
  };
9
9
 
10
10
  const DEFAULT_STATIC: StaticDetails = {
@@ -20,7 +20,9 @@ let activeConfig: WebsiteConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
20
20
 
21
21
  export async function initializeConfig(infra?: Partial<InfrastructureConfig>): Promise<WebsiteConfig> {
22
22
  if (infra) {
23
- activeConfig = { ...activeConfig, ...infra };
23
+ // Only merge defined values
24
+ if (infra.baseUrl) activeConfig.baseUrl = infra.baseUrl;
25
+ if (infra.apiUrl) activeConfig.apiUrl = infra.apiUrl;
24
26
  }
25
27
 
26
28
  try {
@@ -79,7 +79,7 @@ export const generatePageContent = (
79
79
  const footerTemplate = `
80
80
  <my-footer
81
81
  copyright="${copyright}"
82
- footerlinks='\${JSON.stringify(footerLinks)}'>
82
+ footerLinks='${JSON.stringify(footerLinks)}'>
83
83
  </my-footer>`;
84
84
 
85
85
  const renderContentGists = (items: ContentMetadata[] = [], title: string, type: 'blogs' | 'stories') => {