@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
@@ -1,48 +0,0 @@
1
- const DEFAULT_STATIC_DETAILS = {
2
- siteTitle: "My Personal Website",
3
- copyright: "2026 My Personal Website",
4
- linkedin: "https://linkedin.com/in/yourname",
5
- github: "https://github.com/yourname",
6
- email: "yourname@domain.com"
7
- };
8
-
9
- export async function handleStaticDetails(env?: any, method?: string, body?: any): Promise<Response> {
10
- try {
11
- if (!env?.CONTENT_BUCKET) {
12
- return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
13
- headers: { 'Content-Type': 'application/json' },
14
- });
15
- }
16
-
17
- // GET - return static details
18
- if (!method || method === 'GET') {
19
- const staticDetails = await env.CONTENT_BUCKET.get('staticdetails.json');
20
- if (staticDetails) {
21
- const data = await staticDetails.json();
22
- return new Response(JSON.stringify({ ...DEFAULT_STATIC_DETAILS, ...data }), {
23
- headers: {
24
- 'Content-Type': 'application/json',
25
- 'Access-Control-Allow-Origin': '*'
26
- },
27
- });
28
- }
29
- return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
30
- headers: {
31
- 'Content-Type': 'application/json',
32
- 'Access-Control-Allow-Origin': '*'
33
- },
34
- });
35
- }
36
-
37
- // PUT - save static details (handled in content handler)
38
- return new Response(JSON.stringify({ error: 'Use content endpoint for PUT' }), {
39
- status: 400,
40
- headers: { 'Content-Type': 'application/json' },
41
- });
42
- } catch (error) {
43
- console.error('Error serving static details:', error);
44
- return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
45
- headers: { 'Content-Type': 'application/json' },
46
- });
47
- }
48
- }
package/src/api/index.ts DELETED
@@ -1,7 +0,0 @@
1
- import { WebsiteAPI } from './website-api';
2
- export { WebsiteAPI };
3
- export type { APIHandler } from './website-api';
4
-
5
- // Default worker export using WebsiteAPI
6
- const defaultAPI = new WebsiteAPI();
7
- export default defaultAPI;
package/src/api/utils.ts DELETED
@@ -1,16 +0,0 @@
1
- export interface APIResponse {
2
- data?: any;
3
- error?: string;
4
- status: number;
5
- }
6
-
7
- export function createJSONResponse(data: any, status: number = 200): Response {
8
- return new Response(JSON.stringify(data), {
9
- status,
10
- headers: { 'Content-Type': 'application/json' },
11
- });
12
- }
13
-
14
- export function createErrorResponse(message: string, status: number = 500): Response {
15
- return createJSONResponse({ error: message }, status);
16
- }
@@ -1,124 +0,0 @@
1
- import { createErrorResponse } from './utils';
2
- import { handleAboutMe, clearContentCache } from './handlers/about-me';
3
- import { handleHome } from './handlers/home';
4
- import { handleInfo } from './handlers/info';
5
- import { handleContent } from './handlers/content';
6
- import { handleBlogs, handleStories, handleSearch } from './handlers/content-api';
7
- import { handleLogo } from './handlers/logo';
8
- import { handleStaticDetails } from './handlers/static-details';
9
-
10
- export type APIHandler = (request: Request, env: any) => Promise<Response>;
11
-
12
- export class WebsiteAPI {
13
- private customHandlers = new Map<string, APIHandler>();
14
-
15
- public registerHandler(route: string, handler: APIHandler) {
16
- this.customHandlers.set(route, handler);
17
- }
18
-
19
- private addCORSHeaders(response: Response): Response {
20
- response.headers.set('Access-Control-Allow-Origin', '*' );
21
- response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS' );
22
- response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
23
- return response;
24
- }
25
-
26
- private handleCORS(): Response {
27
- return new Response(null, {
28
- status: 200,
29
- headers: {
30
- 'Access-Control-Allow-Origin': '*' ,
31
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' ,
32
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
33
- 'Access-Control-Max-Age': '86400',
34
- },
35
- });
36
- }
37
-
38
- private requireAuth(request: Request, env?: any): Response | null {
39
- const authHeader = request.headers.get('Authorization');
40
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
41
- return createErrorResponse('Unauthorized', 401);
42
- }
43
- const token = authHeader.slice(7);
44
- if (token !== env?.ADMIN_API_KEY) {
45
- return createErrorResponse('Unauthorized', 401);
46
- }
47
- return null;
48
- }
49
-
50
- public async fetch(request: Request, env: any): Promise<Response> {
51
- const url = new URL(request.url);
52
-
53
- if (request.method === 'OPTIONS') {
54
- return this.handleCORS();
55
- }
56
-
57
- const pathname = url.pathname;
58
- const route = pathname
59
- .replace(/^\/api\//, '')
60
- .replace(/^\//, '')
61
- .replace(/\/+$/, '');
62
-
63
- // Check custom handlers first
64
- if (this.customHandlers.has(route)) {
65
- const handler = this.customHandlers.get(route)!;
66
- return this.addCORSHeaders(await handler(request, env));
67
- }
68
-
69
- try {
70
- // Check for content route first (content/*)
71
- if (route === 'content' || route.startsWith('content/')) {
72
- const subpath = route.replace(/^content\/?/, '');
73
- return this.addCORSHeaders(await handleContent(request, env, subpath));
74
- }
75
-
76
- switch (route) {
77
- case 'info':
78
- return this.addCORSHeaders(await handleInfo());
79
- case 'home':
80
- return this.addCORSHeaders(await handleHome(env));
81
- case 'cache-clear':
82
- const authError = this.requireAuth(request, env);
83
- if (authError) return this.addCORSHeaders(authError);
84
- clearContentCache();
85
- return this.addCORSHeaders(new Response(JSON.stringify({ success: true, message: 'Cache cleared' }), { status: 200 }));
86
- case 'aboutme':
87
- return this.addCORSHeaders(await handleAboutMe(env));
88
- case 'logo':
89
- return this.addCORSHeaders(await handleLogo(env));
90
- case 'static':
91
- return this.addCORSHeaders(await handleStaticDetails(env));
92
- case 'blogs':
93
- return this.addCORSHeaders(await handleBlogs(env));
94
- case 'blogs/latest':
95
- const latestCount = url.searchParams.get('count');
96
- return this.addCORSHeaders(await handleBlogs(env, undefined, latestCount ? parseInt(latestCount) : 5));
97
- default:
98
- if (route.startsWith('blogs/')) {
99
- const slug = route.replace('blogs/', '');
100
- return this.addCORSHeaders(await handleBlogs(env, slug));
101
- }
102
- if (route.startsWith('stories')) {
103
- if (route === 'stories') {
104
- return this.addCORSHeaders(await handleStories(env));
105
- }
106
- if (route === 'stories/latest') {
107
- const latestCount = url.searchParams.get('count');
108
- return this.addCORSHeaders(await handleStories(env, undefined, latestCount ? parseInt(latestCount) : 5));
109
- }
110
- const slug = route.replace('stories/', '');
111
- return this.addCORSHeaders(await handleStories(env, slug));
112
- }
113
- if (route === 'search') {
114
- const query = url.searchParams.get('q');
115
- return this.addCORSHeaders(await handleSearch(env, query || undefined));
116
- }
117
- return this.addCORSHeaders(createErrorResponse('Route not found', 404));
118
- }
119
- } catch (error) {
120
- console.error('API Error:', error);
121
- return this.addCORSHeaders(createErrorResponse('Internal server error', 500));
122
- }
123
- }
124
- }
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './api';
2
- export * from './prerender';
3
- export * from './ui';
4
- export * from './shared';
@@ -1,54 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { generatePageContent } from '../page-content';
3
-
4
- describe('generatePageContent', () => {
5
- const mockRoutes = [
6
- { link: '/', text: 'Home' },
7
- { link: '/about-me', text: 'About' }
8
- ];
9
- const mockFooterLinks = [
10
- { text: 'Link', href: '#' }
11
- ];
12
- const mockEnv = {
13
- CONTENT_BUCKET: {
14
- get: vi.fn().mockResolvedValue({
15
- json: () => Promise.resolve({ name: 'User', title: 'Professional', experience: 'some' })
16
- }),
17
- list: vi.fn().mockResolvedValue({ objects: [] })
18
- },
19
- apiUrl: 'https://api.example.com',
20
- baseUrl: 'https://www.example.com'
21
- };
22
-
23
- beforeEach(() => {
24
- vi.clearAllMocks();
25
- global.fetch = vi.fn().mockResolvedValue({
26
- ok: true,
27
- json: () => Promise.resolve({ siteTitle: 'My Site', copyright: '2026' })
28
- });
29
- });
30
-
31
- it('should generate home page content', async () => {
32
- const result = await generatePageContent('/', mockRoutes, mockFooterLinks, mockEnv);
33
-
34
- expect(result.title).toContain('User');
35
- expect(result.canonicalUrl).toBe('https://www.example.com/');
36
- expect(result.content).toContain('<my-banner');
37
- });
38
-
39
- it('should generate about-me page content', async () => {
40
- const result = await generatePageContent('/about-me', mockRoutes, mockFooterLinks, mockEnv);
41
-
42
- expect(result.title).toContain('About');
43
- expect(result.canonicalUrl).toBe('https://www.example.com/about-me');
44
- expect(result.content).toContain('<my-aboutme');
45
- });
46
-
47
- it('should generate 404 page content', async () => {
48
- const result = await generatePageContent('/non-existent', mockRoutes, mockFooterLinks, mockEnv);
49
-
50
- expect(result.title).toContain('Not Found');
51
- expect(result.canonicalUrl).toBe('https://www.example.com/non-existent');
52
- expect(result.content).toContain('Page Not Found');
53
- });
54
- });
@@ -1,54 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createHtmlTemplate } from '../template';
3
-
4
- describe('createHtmlTemplate', () => {
5
- it('should generate complete HTML template', async () => {
6
- const props = {
7
- title: 'Test Title',
8
- description: 'Test Description',
9
- canonicalUrl: 'https://example.com/test',
10
- content: '<main>Test Content</main>'
11
- };
12
-
13
- const html = await createHtmlTemplate(props);
14
-
15
- expect(html).toContain('<!doctype html>');
16
- expect(html).toContain('<title>Test Title</title>');
17
- expect(html).toContain('<meta name="description" content="Test Description" />');
18
- expect(html).toContain('<meta property="og:title" content="Test Title" />');
19
- expect(html).toContain('<link rel="canonical" href="https://example.com/test" />');
20
- expect(html).toContain('<main>Test Content</main>');
21
- });
22
-
23
- it('should escape HTML entities properly', async () => {
24
- const props = {
25
- title: 'Test & Title',
26
- description: 'Test "Description"',
27
- canonicalUrl: 'https://example.com/test',
28
- content: '<main>Test Content</main>'
29
- };
30
-
31
- const html = await createHtmlTemplate(props);
32
-
33
- expect(html).toContain('<title>Test & Title</title>');
34
- expect(html).toContain('<meta name="description" content="Test "Description"" />');
35
- });
36
-
37
- it('should have proper HTML structure', async () => {
38
- const props = {
39
- title: 'Test',
40
- description: 'Test',
41
- canonicalUrl: 'https://example.com',
42
- content: '<div>Content</div>'
43
- };
44
-
45
- const html = await createHtmlTemplate(props);
46
-
47
- expect(html).toMatch(/^<!doctype html>/);
48
- expect(html).toContain('<html lang="en" data-theme="light">');
49
- expect(html).toContain('<head>');
50
- expect(html).toContain('<body>');
51
- expect(html).toContain('<div id="app">');
52
- expect(html).toContain('</html>');
53
- });
54
- });
@@ -1,7 +0,0 @@
1
- import { WebsitePrerender } from './website-prerender';
2
- export { WebsitePrerender };
3
- export type { PrerenderOptions } from './website-prerender';
4
-
5
- // Default worker export using WebsitePrerender
6
- const defaultPrerender = new WebsitePrerender();
7
- export default defaultPrerender;
@@ -1,263 +0,0 @@
1
- import { R2ContentLoader } from "@leadertechie/r2tohtml";
2
-
3
- interface Profile {
4
- name: string;
5
- title: string;
6
- experience: string;
7
- profileImageUrl: string;
8
- }
9
-
10
- export interface IRoute {
11
- text: string;
12
- link: string;
13
- }
14
-
15
- export interface IFooterLink {
16
- text: string;
17
- link: string;
18
- }
19
-
20
- export interface PageContent {
21
- title: string;
22
- description: string;
23
- canonicalUrl: string;
24
- content: string;
25
- }
26
-
27
- interface BlogMeta {
28
- slug: string;
29
- title: string;
30
- summary: string;
31
- tags: string[];
32
- date: string;
33
- }
34
-
35
- let loader: R2ContentLoader | null = null;
36
-
37
- function getLoader(env: any): R2ContentLoader | null {
38
- if (!loader) {
39
- if (!env?.CONTENT_BUCKET) return null;
40
- loader = new R2ContentLoader(
41
- { bucket: env.CONTENT_BUCKET, cacheTTL: 5 * 60 * 1000 },
42
- { md2html: { imagePathPrefix: "images/", styleOptions: { classPrefix: "md-", addHeadingIds: true } } }
43
- );
44
- }
45
- return loader;
46
- }
47
-
48
- async function fetchProfile(env: any): Promise<Profile | null> {
49
- try {
50
- const r2 = getLoader(env);
51
- if (!r2) return null;
52
- const obj = await r2.getObject("profile.json");
53
- if (!obj) return null;
54
- return await obj.json() as Profile;
55
- } catch { return null; }
56
- }
57
-
58
- async function fetchAboutMe(env: any): Promise<string> {
59
- try {
60
- const r2 = getLoader(env);
61
- if (!r2) return "";
62
- const result = await r2.getRendered("about-me.md");
63
- return result?.content || "";
64
- } catch { return ""; }
65
- }
66
-
67
- async function fetchHome(env: any): Promise<string> {
68
- try {
69
- const r2 = getLoader(env);
70
- if (!r2) return "";
71
- const result = await r2.getRendered("home.md");
72
- return result?.content || "";
73
- } catch { return ""; }
74
- }
75
-
76
- async function fetchLatestBlogSummaries(env: any, count: number = 3): Promise<BlogMeta[]> {
77
- try {
78
- const r2 = getLoader(env);
79
- if (!r2) return [];
80
- const list = await r2.list("blogs/");
81
- const metas: BlogMeta[] = [];
82
- for (const obj of list.objects) {
83
- if (obj.key.endsWith(".json")) {
84
- try {
85
- const metaObj = await r2.getObject(obj.key);
86
- if (metaObj) metas.push(await metaObj.json() as BlogMeta);
87
- } catch {}
88
- }
89
- }
90
- return metas.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, count);
91
- } catch { return []; }
92
- }
93
-
94
- async function fetchLatestStorySummaries(env: any, count: number = 3): Promise<BlogMeta[]> {
95
- try {
96
- const r2 = getLoader(env);
97
- if (!r2) return [];
98
- const list = await r2.list("stories/");
99
- const metas: BlogMeta[] = [];
100
- for (const obj of list.objects) {
101
- if (obj.key.endsWith(".json")) {
102
- try {
103
- const metaObj = await r2.getObject(obj.key);
104
- if (metaObj) metas.push(await metaObj.json() as BlogMeta);
105
- } catch {}
106
- }
107
- }
108
- return metas.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, count);
109
- } catch { return []; }
110
- }
111
-
112
- export const generatePageContent = async (
113
- pathname: string,
114
- routes: IRoute[],
115
- footerLinks: IFooterLink[],
116
- env?: any
117
- ): Promise<PageContent> => {
118
- const apiUrl = env?.apiUrl || "https://api.techieleader.com";
119
- const baseUrl = env?.baseUrl || "https://www.techieleader.com";
120
-
121
- let staticDetails = {
122
- siteTitle: "My Personal Website",
123
- copyright: "2026 My Personal Website",
124
- linkedin: "https://linkedin.com/in/yourname",
125
- github: "https://github.com/yourname",
126
- email: "yourname@domain.com"
127
- };
128
-
129
- try {
130
- const res = await fetch(`${apiUrl}/api/static`);
131
- if (res.ok) staticDetails = await res.json();
132
- } catch (e) {}
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
- let profile: Profile | null = null;
152
- let aboutMeContent = "";
153
- let homeContent = "";
154
- let latestBlogs: BlogMeta[] = [];
155
- let latestStories: BlogMeta[] = [];
156
-
157
- if (env?.CONTENT_BUCKET) {
158
- [profile, aboutMeContent, homeContent, latestBlogs, latestStories] = await Promise.all([
159
- fetchProfile(env), fetchAboutMe(env), fetchHome(env), fetchLatestBlogSummaries(env, 3), fetchLatestStorySummaries(env, 3)
160
- ]);
161
- }
162
-
163
- const name = profile?.name || "User";
164
- const title = profile?.title || "Professional";
165
- const experience = profile?.experience || "some";
166
- const canonicalUrl = new URL(pathname, baseUrl).toString();
167
-
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}`;
187
-
188
- return {
189
- title: `${name} – ${title}`,
190
- description: `Welcome to ${name}'s personal website. Professional portfolio and content.`,
191
- canonicalUrl,
192
- content: mainContent
193
- };
194
- } 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
- };
208
- } 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 };
221
- } 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 };
234
- } else if (pathname.startsWith("/blogs/")) {
235
- 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 };
243
- } else if (pathname.startsWith("/stories/")) {
244
- 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 };
252
- } 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 };
262
- }
263
- };
@@ -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
- };