@leadertechie/personal-site-kit 0.1.0-alpha.8 → 0.1.0-alpha.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.
Files changed (93) hide show
  1. package/dist/api/content-utils.d.ts +27 -0
  2. package/dist/api/content-utils.d.ts.map +1 -0
  3. package/dist/api/handlers/content-api.d.ts +0 -1
  4. package/dist/api/handlers/content-api.d.ts.map +1 -1
  5. package/dist/api.js +2 -2
  6. package/dist/chunks/{index-CYd_Pe2U.js → index-CnSEOZse.js} +81 -121
  7. package/dist/chunks/{template-D1uGvdWZ.js → template-DWcsZW22.js} +1 -1
  8. package/dist/chunks/{website-api-FLejlWxJ.js → website-api-BEYGOsT3.js} +90 -131
  9. package/dist/index.js +3 -3
  10. package/dist/shared.js +1 -1
  11. package/dist/ui/admin/index.d.ts +8 -0
  12. package/dist/ui/admin/index.d.ts.map +1 -1
  13. package/dist/ui.js +1 -1
  14. package/package.json +4 -4
  15. package/src/api/__tests__/info.test.ts +0 -44
  16. package/src/api/__tests__/utils.test.ts +0 -78
  17. package/src/api/handlers/about-me.ts +0 -109
  18. package/src/api/handlers/auth-handler.ts +0 -204
  19. package/src/api/handlers/auth.ts +0 -157
  20. package/src/api/handlers/content-api.ts +0 -268
  21. package/src/api/handlers/content.ts +0 -139
  22. package/src/api/handlers/home.ts +0 -79
  23. package/src/api/handlers/info.ts +0 -12
  24. package/src/api/handlers/logo.ts +0 -55
  25. package/src/api/handlers/static-details.ts +0 -48
  26. package/src/api/index.ts +0 -9
  27. package/src/api/utils.ts +0 -16
  28. package/src/api/website-api.ts +0 -142
  29. package/src/index.ts +0 -4
  30. package/src/prerender/__tests__/page-content.test.ts +0 -44
  31. package/src/prerender/__tests__/template.test.ts +0 -54
  32. package/src/prerender/data-fetcher.ts +0 -93
  33. package/src/prerender/index.ts +0 -7
  34. package/src/prerender/page-content.ts +0 -266
  35. package/src/prerender/page-generators/about.ts +0 -38
  36. package/src/prerender/page-generators/base.ts +0 -77
  37. package/src/prerender/page-generators/blog-detail.ts +0 -35
  38. package/src/prerender/page-generators/blogs-list.ts +0 -43
  39. package/src/prerender/page-generators/home.ts +0 -54
  40. package/src/prerender/page-generators/index.ts +0 -8
  41. package/src/prerender/page-generators/not-found.ts +0 -36
  42. package/src/prerender/page-generators/stories-list.ts +0 -43
  43. package/src/prerender/page-generators/story-detail.ts +0 -35
  44. package/src/prerender/prerender.ts +0 -25
  45. package/src/prerender/template.ts +0 -65
  46. package/src/prerender/website-prerender.ts +0 -152
  47. package/src/shared/config/api.ts +0 -16
  48. package/src/shared/config/index.ts +0 -43
  49. package/src/shared/config/types.ts +0 -16
  50. package/src/shared/core/__tests__/theme-toggle.test.ts +0 -204
  51. package/src/shared/core/site-store.ts +0 -38
  52. package/src/shared/core/theme-toggle.ts +0 -118
  53. package/src/shared/index.ts +0 -17
  54. package/src/shared/interfaces/ifooter-link.ts +0 -4
  55. package/src/shared/interfaces/iroute.ts +0 -4
  56. package/src/shared/models/theme-variables.css +0 -25
  57. package/src/shared/page-content.ts +0 -210
  58. package/src/shared/router.ts +0 -250
  59. package/src/shared/runtime.ts +0 -11
  60. package/src/shared/template.ts +0 -35
  61. package/src/shared/website-ui.ts +0 -92
  62. package/src/styles/markdown.css +0 -129
  63. package/src/ui/about-me/api.ts +0 -12
  64. package/src/ui/about-me/index.ts +0 -121
  65. package/src/ui/about-me/styles.ts +0 -85
  66. package/src/ui/admin/api.ts +0 -93
  67. package/src/ui/admin/components/AboutMeSection.ts +0 -47
  68. package/src/ui/admin/components/AdminSection.ts +0 -134
  69. package/src/ui/admin/components/BlogsSection.ts +0 -62
  70. package/src/ui/admin/components/HomeSection.ts +0 -47
  71. package/src/ui/admin/components/ImagesSection.ts +0 -54
  72. package/src/ui/admin/components/LoginForm.ts +0 -116
  73. package/src/ui/admin/components/LogoSection.ts +0 -51
  74. package/src/ui/admin/components/ProfileSection.ts +0 -47
  75. package/src/ui/admin/components/StaticSection.ts +0 -67
  76. package/src/ui/admin/components/StoriesSection.ts +0 -62
  77. package/src/ui/admin/components/index.ts +0 -10
  78. package/src/ui/admin/index.ts +0 -413
  79. package/src/ui/admin/styles.ts +0 -270
  80. package/src/ui/admin/types.ts +0 -26
  81. package/src/ui/banner/index.ts +0 -38
  82. package/src/ui/banner/styles.ts +0 -95
  83. package/src/ui/blog-viewer/__tests__/blogviewer.test.ts +0 -7
  84. package/src/ui/blog-viewer/index.ts +0 -127
  85. package/src/ui/blog-viewer/styles.ts +0 -23
  86. package/src/ui/footer/index.ts +0 -37
  87. package/src/ui/footer/styles.ts +0 -50
  88. package/src/ui/index.ts +0 -13
  89. package/src/ui/story-viewer/__tests__/storyviewer.test.ts +0 -7
  90. package/src/ui/story-viewer/index.ts +0 -123
  91. package/src/ui/story-viewer/styles.ts +0 -54
  92. /package/{src/shared → dist}/styles/markdown.css +0 -0
  93. /package/{src → dist}/styles/theme.css +0 -0
@@ -1,77 +0,0 @@
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
- }
@@ -1,35 +0,0 @@
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
- }
@@ -1,43 +0,0 @@
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
- }
@@ -1,54 +0,0 @@
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
- }
@@ -1,8 +0,0 @@
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';
@@ -1,36 +0,0 @@
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
- }
@@ -1,43 +0,0 @@
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
- }
@@ -1,35 +0,0 @@
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
- }
@@ -1,25 +0,0 @@
1
- // Minimal prerender AST->HTML utility
2
- export function renderFromAst(ast: any): string {
3
- if (!ast) return '';
4
- if (ast.type === 'document' && Array.isArray(ast.children)) {
5
- return ast.children.map(renderFromAst).join('\n');
6
- }
7
- if (ast.type === 'heading') {
8
- const level = Math.max(1, Math.min(6, ast.depth || 1));
9
- const inner = Array.isArray(ast.children) ? ast.children.map(renderFromAst).join('') : '';
10
- return `<h${level}>${inner}</h${level}>`;
11
- }
12
- if (ast.type === 'paragraph') {
13
- const inner = Array.isArray(ast.children) ? ast.children.map(renderFromAst).join('') : '';
14
- return `<p>${inner}</p>`;
15
- }
16
- if (ast.type === 'text') {
17
- return ast.value || '';
18
- }
19
- if (ast.children) {
20
- return ast.children.map(renderFromAst).join('');
21
- }
22
- return '';
23
- }
24
-
25
- export default renderFromAst
@@ -1,65 +0,0 @@
1
- export interface TemplateProps {
2
- title: string;
3
- description: string;
4
- canonicalUrl: string;
5
- content: string;
6
- hydrationData?: string;
7
- baseSiteUrl?: string;
8
- }
9
-
10
- async function getAssetPaths(baseSiteUrl: string): Promise<{ js: string; css: string }> {
11
- const assetsUrl = `${baseSiteUrl}/cdn-assets.json`;
12
- try {
13
- const res = await fetch(assetsUrl);
14
- if (res.ok) {
15
- const data = await res.json();
16
- return { js: data.js, css: data.css };
17
- }
18
- } catch (e) {}
19
-
20
- try {
21
- const res = await fetch(`${baseSiteUrl}/?t=${Date.now()}`);
22
- const html = await res.text();
23
- const jsMatch = html.match(/src="(\/assets\/index-[^"]+\.js)"/);
24
- const cssMatch = html.match(/href="(\/assets\/index-[^"]+\.css)"/);
25
- return {
26
- js: jsMatch ? jsMatch[1] : "/assets/index.js",
27
- css: cssMatch ? cssMatch[1] : "/assets/index.css"
28
- };
29
- } catch (e) {}
30
- return { js: "/assets/index.js", css: "/assets/index.css" };
31
- }
32
-
33
- export const createHtmlTemplate = async ({
34
- title,
35
- description,
36
- canonicalUrl,
37
- content,
38
- hydrationData = "",
39
- baseSiteUrl = ""
40
- }: TemplateProps): Promise<string> => {
41
- const { js: jsAsset, css: cssAsset } = await getAssetPaths(baseSiteUrl);
42
-
43
- return `<!doctype html>
44
- <html lang="en" data-theme="light">
45
- <head>
46
- <meta charset="UTF-8" />
47
- <link rel="icon" type="image/svg+xml" href="/api/logo" />
48
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
49
- <title>${title}</title>
50
- <meta name="description" content="${description}" />
51
- <meta property="og:title" content="${title}" />
52
- <meta property="og:description" content="${description}" />
53
- <meta property="og:url" content="${canonicalUrl}" />
54
- <link rel="canonical" href="${canonicalUrl}" />
55
- <link rel="stylesheet" crossorigin href="${cssAsset}" />
56
- <script type="module" crossorigin src="${jsAsset}"></script>
57
- </head>
58
- <body>
59
- ${hydrationData}
60
- <div id="app">
61
- ${content}
62
- </div>
63
- </body>
64
- </html>`;
65
- };
@@ -1,152 +0,0 @@
1
- import { createHtmlTemplate, TemplateProps } from './template';
2
- import { IFooterLink, IRoute, generatePageContent } from './page-content';
3
-
4
- export interface PrerenderOptions {
5
- routes?: IRoute[];
6
- defaultFooterLinks?: IFooterLink[];
7
- siteTitle?: string;
8
- copyright?: string;
9
- templateRenderer?: (props: TemplateProps) => Promise<string> | string;
10
- }
11
-
12
- export class WebsitePrerender {
13
- private routes: IRoute[];
14
- private defaultFooterLinks: IFooterLink[];
15
- private footerLinks: IFooterLink[];
16
- private siteTitle: string;
17
- private copyright: string;
18
- private templateRenderer: (props: TemplateProps) => Promise<string> | string;
19
-
20
- constructor(options: PrerenderOptions = {}) {
21
- this.routes = options.routes || [
22
- { link: '/', text: 'Home' },
23
- { link: '/blogs', text: 'Blogs' },
24
- { link: '/stories', text: 'Stories' },
25
- { link: '/about-me', text: 'About Me' },
26
- ];
27
- this.defaultFooterLinks = options.defaultFooterLinks || [
28
- { text: 'LinkedIn', link: 'https://linkedin.com/in/yourname' },
29
- { text: 'GitHub', link: 'https://github.com/yourname' },
30
- { text: 'Email', link: 'mailto:yourname@domain.com' },
31
- ];
32
- this.footerLinks = [...this.defaultFooterLinks];
33
- this.siteTitle = options.siteTitle || 'My Personal Website';
34
- this.copyright = options.copyright || '2026 My Personal Website';
35
- this.templateRenderer = options.templateRenderer || createHtmlTemplate;
36
- }
37
-
38
- private async fetchStaticDetails(apiUrl: string) {
39
- try {
40
- const res = await fetch(`${apiUrl}/api/static`);
41
- if (res.ok) {
42
- const data = await res.json();
43
- this.siteTitle = data.siteTitle || this.siteTitle;
44
- this.copyright = data.copyright || this.copyright;
45
-
46
- const normalizeUrl = (url?: string) => {
47
- if (!url) return '';
48
- if (url.startsWith('http://') || url.startsWith('https://')) return url;
49
- if (url.startsWith('www.')) return `https://${url}`;
50
- return url;
51
- };
52
-
53
- this.footerLinks = [
54
- { text: 'LinkedIn', link: normalizeUrl(data.linkedin) || this.defaultFooterLinks[0].link },
55
- { text: 'GitHub', link: normalizeUrl(data.github) || this.defaultFooterLinks[1].link },
56
- { text: 'Email', link: data.email ? `mailto:${data.email}` : this.defaultFooterLinks[2].link },
57
- ];
58
- }
59
- } catch (e) {}
60
- }
61
-
62
- private async fetchAboutMeData(apiUrl: string): Promise<any> {
63
- try {
64
- const res = await fetch(`${apiUrl}/api/about-me`);
65
- if (res.ok) return await res.json();
66
- } catch (e) {}
67
- return null;
68
- }
69
-
70
- public async fetch(request: Request, env: any, ctx: any): Promise<Response> {
71
- const apiUrl = env?.API_URL || 'https://api.example.com';
72
- const baseSiteUrl = env?.BASE_SITE_URL || 'https://site.example.com';
73
-
74
- await this.fetchStaticDetails(apiUrl);
75
-
76
- const url = new URL(request.url);
77
-
78
- if (url.pathname.startsWith('/api/')) {
79
- return fetch(`${apiUrl}${url.pathname}${url.search}`);
80
- }
81
-
82
- if (url.pathname.startsWith('/images/')) {
83
- const imageKey = url.pathname.slice(1);
84
- try {
85
- const image = await env.CONTENT_BUCKET.get(imageKey);
86
- if (image) {
87
- return new Response(image.body, {
88
- headers: {
89
- 'content-type': image.httpMetadata?.contentType || 'image/jpeg',
90
- 'cache-control': 'public, max-age=86400',
91
- },
92
- });
93
- }
94
- } catch (e) {}
95
- return new Response('Not found', { status: 404 });
96
- }
97
-
98
- if (url.pathname.startsWith('/assets/') || url.pathname === '/logo.png' || url.pathname === '/favicon.ico') {
99
- const path = url.pathname;
100
- const ext = path.split('.').pop()?.toLowerCase();
101
- const contentTypes: Record<string, string> = {
102
- js: 'application/javascript',
103
- css: 'text/css',
104
- png: 'image/png',
105
- jpg: 'image/jpeg',
106
- jpeg: 'image/jpeg',
107
- gif: 'image/gif',
108
- svg: 'image/svg+xml',
109
- webp: 'image/webp',
110
- ico: 'image/x-icon',
111
- };
112
- const contentType = contentTypes[ext || ''] || 'application/octet-stream';
113
-
114
- const response = await fetch(`${baseSiteUrl}${path}`);
115
- if (response.ok) {
116
- return new Response(response.body, {
117
- headers: {
118
- 'content-type': contentType,
119
- 'cache-control': 'public, max-age=31536000',
120
- },
121
- });
122
- }
123
- return new Response('Not found', { status: 404 });
124
- }
125
-
126
- const PRERENDERED_DOMAINS = [
127
- url.hostname
128
- ];
129
-
130
- if (!PRERENDERED_DOMAINS.includes(url.hostname) && !url.hostname.includes('localhost')) {
131
- return fetch(request);
132
- }
133
-
134
- let hydrationScript = '';
135
- if (url.pathname === '/about-me' || url.pathname === '/about-me/') {
136
- const aboutMeData = await this.fetchAboutMeData(apiUrl);
137
- if (aboutMeData) {
138
- hydrationScript = `<script>window.__HYDRATION_DATA__ = ${JSON.stringify(aboutMeData)};</script>`;
139
- }
140
- }
141
-
142
- const pageContent = await generatePageContent(url.pathname, this.routes, this.footerLinks, { ...env, apiUrl, siteTitle: this.siteTitle, copyright: this.copyright });
143
- const html = await this.templateRenderer({ ...pageContent, hydrationData: hydrationScript });
144
-
145
- return new Response(html, {
146
- headers: {
147
- 'content-type': 'text/html',
148
- 'cache-control': 'public, max-age=60',
149
- },
150
- });
151
- }
152
- }
@@ -1,16 +0,0 @@
1
- // API Configuration
2
- export const API_CONFIG = {
3
- // Base URL for API calls
4
- BASE_URL: (typeof window !== 'undefined' && (window as any).__VITE_API_URL__) ||
5
- (typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}` : 'http://localhost:8787'),
6
-
7
- // API endpoints
8
- ENDPOINTS: {
9
- ABOUTME: '/aboutme'
10
- },
11
-
12
- // Build full URL for an endpoint
13
- getUrl(endpoint: keyof typeof API_CONFIG.ENDPOINTS) {
14
- return `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS[endpoint]}`;
15
- }
16
- };
@@ -1,43 +0,0 @@
1
- import { InfrastructureConfig, StaticDetails, WebsiteConfig } from './types';
2
-
3
- export * from './types';
4
-
5
- const DEFAULT_INFRA: InfrastructureConfig = {
6
- baseUrl: typeof window !== 'undefined' ? window.location.origin : 'http://localhost:5173',
7
- apiUrl: (typeof window !== 'undefined' && ((window as any).__VITE_API_URL__ || (import.meta as any).env?.VITE_API_URL)) || 'http://localhost:8788'
8
- };
9
-
10
- const DEFAULT_STATIC: StaticDetails = {
11
- siteTitle: 'My Personal Website',
12
- siteDescription: 'My Personal Website',
13
- copyright: '2026 My Personal Website',
14
- linkedin: 'https://linkedin.com/in/yourname',
15
- github: 'https://github.com/yourname',
16
- email: 'yourname@domain.com'
17
- };
18
-
19
- let activeConfig: WebsiteConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
20
-
21
- export async function initializeConfig(infra?: Partial<InfrastructureConfig>): Promise<WebsiteConfig> {
22
- if (infra) {
23
- // Only merge defined values
24
- if (infra.baseUrl) activeConfig.baseUrl = infra.baseUrl;
25
- if (infra.apiUrl) activeConfig.apiUrl = infra.apiUrl;
26
- }
27
-
28
- try {
29
- const res = await fetch(`${activeConfig.apiUrl}/api/static`);
30
- if (res.ok) {
31
- const remoteStatic = await res.json().catch(() => ({}));
32
- activeConfig = { ...activeConfig, ...remoteStatic };
33
- }
34
- } catch (e) {
35
- console.warn('Failed to load static details from R2, using defaults.');
36
- }
37
-
38
- return activeConfig;
39
- }
40
-
41
- export function getConfig(): WebsiteConfig {
42
- return activeConfig;
43
- }
@@ -1,16 +0,0 @@
1
- export interface InfrastructureConfig {
2
- baseUrl: string;
3
- apiUrl: string;
4
- }
5
-
6
- export interface StaticDetails {
7
- siteTitle: string;
8
- siteDescription: string;
9
- copyright: string;
10
- linkedin: string;
11
- github: string;
12
- email: string;
13
- twitter?: string;
14
- }
15
-
16
- export interface WebsiteConfig extends InfrastructureConfig, StaticDetails {}