@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.
- package/dist/api/content-utils.d.ts +27 -0
- package/dist/api/content-utils.d.ts.map +1 -0
- package/dist/api/handlers/content-api.d.ts +0 -1
- package/dist/api/handlers/content-api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/chunks/{index-CYd_Pe2U.js → index-CnSEOZse.js} +81 -121
- package/dist/chunks/{template-D1uGvdWZ.js → template-DWcsZW22.js} +1 -1
- package/dist/chunks/{website-api-FLejlWxJ.js → website-api-BEYGOsT3.js} +90 -131
- package/dist/index.js +3 -3
- package/dist/shared.js +1 -1
- package/dist/ui/admin/index.d.ts +8 -0
- package/dist/ui/admin/index.d.ts.map +1 -1
- package/dist/ui.js +1 -1
- package/package.json +4 -4
- package/src/api/__tests__/info.test.ts +0 -44
- package/src/api/__tests__/utils.test.ts +0 -78
- package/src/api/handlers/about-me.ts +0 -109
- package/src/api/handlers/auth-handler.ts +0 -204
- package/src/api/handlers/auth.ts +0 -157
- package/src/api/handlers/content-api.ts +0 -268
- package/src/api/handlers/content.ts +0 -139
- package/src/api/handlers/home.ts +0 -79
- package/src/api/handlers/info.ts +0 -12
- package/src/api/handlers/logo.ts +0 -55
- package/src/api/handlers/static-details.ts +0 -48
- package/src/api/index.ts +0 -9
- package/src/api/utils.ts +0 -16
- package/src/api/website-api.ts +0 -142
- package/src/index.ts +0 -4
- package/src/prerender/__tests__/page-content.test.ts +0 -44
- package/src/prerender/__tests__/template.test.ts +0 -54
- package/src/prerender/data-fetcher.ts +0 -93
- package/src/prerender/index.ts +0 -7
- package/src/prerender/page-content.ts +0 -266
- package/src/prerender/page-generators/about.ts +0 -38
- package/src/prerender/page-generators/base.ts +0 -77
- package/src/prerender/page-generators/blog-detail.ts +0 -35
- package/src/prerender/page-generators/blogs-list.ts +0 -43
- package/src/prerender/page-generators/home.ts +0 -54
- package/src/prerender/page-generators/index.ts +0 -8
- package/src/prerender/page-generators/not-found.ts +0 -36
- package/src/prerender/page-generators/stories-list.ts +0 -43
- package/src/prerender/page-generators/story-detail.ts +0 -35
- package/src/prerender/prerender.ts +0 -25
- package/src/prerender/template.ts +0 -65
- package/src/prerender/website-prerender.ts +0 -152
- package/src/shared/config/api.ts +0 -16
- package/src/shared/config/index.ts +0 -43
- package/src/shared/config/types.ts +0 -16
- package/src/shared/core/__tests__/theme-toggle.test.ts +0 -204
- package/src/shared/core/site-store.ts +0 -38
- package/src/shared/core/theme-toggle.ts +0 -118
- package/src/shared/index.ts +0 -17
- package/src/shared/interfaces/ifooter-link.ts +0 -4
- package/src/shared/interfaces/iroute.ts +0 -4
- package/src/shared/models/theme-variables.css +0 -25
- package/src/shared/page-content.ts +0 -210
- package/src/shared/router.ts +0 -250
- package/src/shared/runtime.ts +0 -11
- package/src/shared/template.ts +0 -35
- package/src/shared/website-ui.ts +0 -92
- package/src/styles/markdown.css +0 -129
- package/src/ui/about-me/api.ts +0 -12
- package/src/ui/about-me/index.ts +0 -121
- package/src/ui/about-me/styles.ts +0 -85
- package/src/ui/admin/api.ts +0 -93
- package/src/ui/admin/components/AboutMeSection.ts +0 -47
- package/src/ui/admin/components/AdminSection.ts +0 -134
- package/src/ui/admin/components/BlogsSection.ts +0 -62
- package/src/ui/admin/components/HomeSection.ts +0 -47
- package/src/ui/admin/components/ImagesSection.ts +0 -54
- package/src/ui/admin/components/LoginForm.ts +0 -116
- package/src/ui/admin/components/LogoSection.ts +0 -51
- package/src/ui/admin/components/ProfileSection.ts +0 -47
- package/src/ui/admin/components/StaticSection.ts +0 -67
- package/src/ui/admin/components/StoriesSection.ts +0 -62
- package/src/ui/admin/components/index.ts +0 -10
- package/src/ui/admin/index.ts +0 -413
- package/src/ui/admin/styles.ts +0 -270
- package/src/ui/admin/types.ts +0 -26
- package/src/ui/banner/index.ts +0 -38
- package/src/ui/banner/styles.ts +0 -95
- package/src/ui/blog-viewer/__tests__/blogviewer.test.ts +0 -7
- package/src/ui/blog-viewer/index.ts +0 -127
- package/src/ui/blog-viewer/styles.ts +0 -23
- package/src/ui/footer/index.ts +0 -37
- package/src/ui/footer/styles.ts +0 -50
- package/src/ui/index.ts +0 -13
- package/src/ui/story-viewer/__tests__/storyviewer.test.ts +0 -7
- package/src/ui/story-viewer/index.ts +0 -123
- package/src/ui/story-viewer/styles.ts +0 -54
- /package/{src/shared → dist}/styles/markdown.css +0 -0
- /package/{src → dist}/styles/theme.css +0 -0
package/src/api/website-api.ts
DELETED
|
@@ -1,142 +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 { handleAuth } from './handlers/auth-handler';
|
|
7
|
-
import { handleBlogs, handleStories, handleSearch } from './handlers/content-api';
|
|
8
|
-
import { handleLogo } from './handlers/logo';
|
|
9
|
-
import { handleStaticDetails } from './handlers/static-details';
|
|
10
|
-
import { getAuthStore } from './handlers/auth';
|
|
11
|
-
|
|
12
|
-
export type APIHandler = (request: Request, env: any) => Promise<Response>;
|
|
13
|
-
|
|
14
|
-
export class WebsiteAPI {
|
|
15
|
-
private customHandlers = new Map<string, APIHandler>();
|
|
16
|
-
|
|
17
|
-
public registerHandler(route: string, handler: APIHandler) {
|
|
18
|
-
this.customHandlers.set(route, handler);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
private addCORSHeaders(response: Response): Response {
|
|
22
|
-
response.headers.set('Access-Control-Allow-Origin', '*' );
|
|
23
|
-
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS' );
|
|
24
|
-
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Session-Token');
|
|
25
|
-
return response;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
private addAdminCORSHeaders(response: Response, origin: string): Response {
|
|
29
|
-
const allowOrigin = origin && (origin.includes('localhost') || origin.includes('127.0.0.1'))
|
|
30
|
-
? origin
|
|
31
|
-
: 'same-origin';
|
|
32
|
-
response.headers.set('Access-Control-Allow-Origin', allowOrigin);
|
|
33
|
-
response.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
34
|
-
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
35
|
-
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Session-Token');
|
|
36
|
-
return response;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private handleCORS(origin: string): Response {
|
|
40
|
-
const allowOrigin = origin && (origin.includes('localhost') || origin.includes('127.0.0.1'))
|
|
41
|
-
? origin
|
|
42
|
-
: '*';
|
|
43
|
-
return new Response(null, {
|
|
44
|
-
status: 200,
|
|
45
|
-
headers: {
|
|
46
|
-
'Access-Control-Allow-Origin': allowOrigin ,
|
|
47
|
-
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' ,
|
|
48
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Session-Token',
|
|
49
|
-
'Access-Control-Allow-Credentials': 'true',
|
|
50
|
-
'Access-Control-Max-Age': '86400',
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public async fetch(request: Request, env: any): Promise<Response> {
|
|
56
|
-
const url = new URL(request.url);
|
|
57
|
-
const origin = request.headers.get('Origin') || url.origin;
|
|
58
|
-
|
|
59
|
-
if (request.method === 'OPTIONS') {
|
|
60
|
-
return this.handleCORS(origin);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const pathname = url.pathname;
|
|
64
|
-
const route = pathname
|
|
65
|
-
.replace(/^\/api\//, '')
|
|
66
|
-
.replace(/^\//, '')
|
|
67
|
-
.replace(/\/+$/, '');
|
|
68
|
-
|
|
69
|
-
// Check custom handlers first
|
|
70
|
-
if (this.customHandlers.has(route)) {
|
|
71
|
-
const handler = this.customHandlers.get(route)!;
|
|
72
|
-
return this.addCORSHeaders(await handler(request, env));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
// Check for content route first (content/*)
|
|
77
|
-
if (route === 'content' || route.startsWith('content/')) {
|
|
78
|
-
const subpath = route.replace(/^content\/?/, '');
|
|
79
|
-
return this.addAdminCORSHeaders(await handleContent(request, env, subpath), origin);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Check for auth route (auth/*)
|
|
83
|
-
if (route === 'auth' || route.startsWith('auth/')) {
|
|
84
|
-
const subpath = route.replace(/^auth\/?/, '');
|
|
85
|
-
return this.addAdminCORSHeaders(await handleAuth(request, env, subpath || '/'), origin);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
switch (route) {
|
|
89
|
-
case 'info':
|
|
90
|
-
return this.addCORSHeaders(await handleInfo());
|
|
91
|
-
case 'home':
|
|
92
|
-
return this.addCORSHeaders(await handleHome(env));
|
|
93
|
-
case 'cache-clear':
|
|
94
|
-
const cookieHeader = request.headers.get('Cookie');
|
|
95
|
-
const sessionToken = cookieHeader?.split(';')
|
|
96
|
-
.find(c => c.trim().startsWith('session='))
|
|
97
|
-
?.split('=')[1];
|
|
98
|
-
const session = sessionToken ? await env.KV.get(`session:${sessionToken}`, 'json') : null;
|
|
99
|
-
if (!session || session.expiresAt < Date.now()) {
|
|
100
|
-
return this.addAdminCORSHeaders(createErrorResponse('Unauthorized', 401), origin);
|
|
101
|
-
}
|
|
102
|
-
clearContentCache();
|
|
103
|
-
return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: 'Cache cleared' }), { status: 200 }), origin);
|
|
104
|
-
case 'aboutme':
|
|
105
|
-
return this.addCORSHeaders(await handleAboutMe(env));
|
|
106
|
-
case 'logo':
|
|
107
|
-
return this.addCORSHeaders(await handleLogo(env));
|
|
108
|
-
case 'static':
|
|
109
|
-
return this.addCORSHeaders(await handleStaticDetails(env));
|
|
110
|
-
case 'blogs':
|
|
111
|
-
return this.addCORSHeaders(await handleBlogs(env));
|
|
112
|
-
case 'blogs/latest':
|
|
113
|
-
const latestCount = url.searchParams.get('count');
|
|
114
|
-
return this.addCORSHeaders(await handleBlogs(env, undefined, latestCount ? parseInt(latestCount) : 5));
|
|
115
|
-
default:
|
|
116
|
-
if (route.startsWith('blogs/')) {
|
|
117
|
-
const slug = route.replace('blogs/', '');
|
|
118
|
-
return this.addCORSHeaders(await handleBlogs(env, slug));
|
|
119
|
-
}
|
|
120
|
-
if (route.startsWith('stories')) {
|
|
121
|
-
if (route === 'stories') {
|
|
122
|
-
return this.addCORSHeaders(await handleStories(env));
|
|
123
|
-
}
|
|
124
|
-
if (route === 'stories/latest') {
|
|
125
|
-
const latestCount = url.searchParams.get('count');
|
|
126
|
-
return this.addCORSHeaders(await handleStories(env, undefined, latestCount ? parseInt(latestCount) : 5));
|
|
127
|
-
}
|
|
128
|
-
const slug = route.replace('stories/', '');
|
|
129
|
-
return this.addCORSHeaders(await handleStories(env, slug));
|
|
130
|
-
}
|
|
131
|
-
if (route === 'search') {
|
|
132
|
-
const query = url.searchParams.get('q');
|
|
133
|
-
return this.addCORSHeaders(await handleSearch(env, query || undefined));
|
|
134
|
-
}
|
|
135
|
-
return this.addCORSHeaders(createErrorResponse('Route not found', 404));
|
|
136
|
-
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.error('API Error:', error);
|
|
139
|
-
return this.addCORSHeaders(createErrorResponse('Internal server error', 500));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,44 +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', link: '#' }
|
|
11
|
-
];
|
|
12
|
-
const mockEnv = {
|
|
13
|
-
apiUrl: 'https://api.example.com',
|
|
14
|
-
baseUrl: 'https://www.example.com'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
vi.clearAllMocks();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should generate home page content', async () => {
|
|
22
|
-
const result = await generatePageContent('/', mockRoutes, mockFooterLinks, mockEnv);
|
|
23
|
-
|
|
24
|
-
expect(result.title).toContain('User');
|
|
25
|
-
expect(result.canonicalUrl).toBe('https://www.example.com/');
|
|
26
|
-
expect(result.content).toContain('<my-banner');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should generate about-me page content', async () => {
|
|
30
|
-
const result = await generatePageContent('/about-me', mockRoutes, mockFooterLinks, mockEnv);
|
|
31
|
-
|
|
32
|
-
expect(result.title).toContain('About');
|
|
33
|
-
expect(result.canonicalUrl).toBe('https://www.example.com/about-me');
|
|
34
|
-
expect(result.content).toContain('<my-aboutme');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should generate 404 page content', async () => {
|
|
38
|
-
const result = await generatePageContent('/non-existent', mockRoutes, mockFooterLinks, mockEnv);
|
|
39
|
-
|
|
40
|
-
expect(result.title).toContain('Not Found');
|
|
41
|
-
expect(result.canonicalUrl).toBe('https://www.example.com/non-existent');
|
|
42
|
-
expect(result.content).toContain('Page Not Found');
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -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,93 +0,0 @@
|
|
|
1
|
-
import { R2ContentLoader } from "@leadertechie/r2tohtml";
|
|
2
|
-
|
|
3
|
-
export interface Profile {
|
|
4
|
-
name: string;
|
|
5
|
-
title: string;
|
|
6
|
-
experience: string;
|
|
7
|
-
profileImageUrl: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface BlogMeta {
|
|
11
|
-
slug: string;
|
|
12
|
-
title: string;
|
|
13
|
-
summary: string;
|
|
14
|
-
tags: string[];
|
|
15
|
-
date: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let loader: R2ContentLoader | null = null;
|
|
19
|
-
|
|
20
|
-
function getLoader(env: any): R2ContentLoader | null {
|
|
21
|
-
if (!loader) {
|
|
22
|
-
if (!env?.CONTENT_BUCKET) return null;
|
|
23
|
-
loader = new R2ContentLoader(
|
|
24
|
-
{ bucket: env.CONTENT_BUCKET, cacheTTL: 5 * 60 * 1000 },
|
|
25
|
-
{ md2html: { imagePathPrefix: "images/", styleOptions: { classPrefix: "md-", addHeadingIds: true } } }
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
return loader;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function fetchProfile(env: any): Promise<Profile | null> {
|
|
32
|
-
try {
|
|
33
|
-
const r2 = getLoader(env);
|
|
34
|
-
if (!r2) return null;
|
|
35
|
-
const obj = await r2.getObject("profile.json");
|
|
36
|
-
if (!obj) return null;
|
|
37
|
-
return await obj.json() as Profile;
|
|
38
|
-
} catch { return null; }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function fetchAboutMe(env: any): Promise<string> {
|
|
42
|
-
try {
|
|
43
|
-
const r2 = getLoader(env);
|
|
44
|
-
if (!r2) return "";
|
|
45
|
-
const result = await r2.getRendered("about-me.md");
|
|
46
|
-
return result?.content || "";
|
|
47
|
-
} catch { return ""; }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function fetchHome(env: any): Promise<string> {
|
|
51
|
-
try {
|
|
52
|
-
const r2 = getLoader(env);
|
|
53
|
-
if (!r2) return "";
|
|
54
|
-
const result = await r2.getRendered("pages/home.md");
|
|
55
|
-
return result?.content || "";
|
|
56
|
-
} catch { return ""; }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function fetchLatestBlogSummaries(env: any, count: number = 3): Promise<BlogMeta[]> {
|
|
60
|
-
try {
|
|
61
|
-
const r2 = getLoader(env);
|
|
62
|
-
if (!r2) return [];
|
|
63
|
-
const list = await r2.list("blogs/");
|
|
64
|
-
const metas: BlogMeta[] = [];
|
|
65
|
-
for (const obj of list.objects) {
|
|
66
|
-
if (obj.key.endsWith(".json")) {
|
|
67
|
-
try {
|
|
68
|
-
const metaObj = await r2.getObject(obj.key);
|
|
69
|
-
if (metaObj) metas.push(await metaObj.json() as BlogMeta);
|
|
70
|
-
} catch {}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return metas.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, count);
|
|
74
|
-
} catch { return []; }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function fetchLatestStorySummaries(env: any, count: number = 3): Promise<BlogMeta[]> {
|
|
78
|
-
try {
|
|
79
|
-
const r2 = getLoader(env);
|
|
80
|
-
if (!r2) return [];
|
|
81
|
-
const list = await r2.list("stories/");
|
|
82
|
-
const metas: BlogMeta[] = [];
|
|
83
|
-
for (const obj of list.objects) {
|
|
84
|
-
if (obj.key.endsWith(".json")) {
|
|
85
|
-
try {
|
|
86
|
-
const metaObj = await r2.getObject(obj.key);
|
|
87
|
-
if (metaObj) metas.push(await metaObj.json() as BlogMeta);
|
|
88
|
-
} catch {}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return metas.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, count);
|
|
92
|
-
} catch { return []; }
|
|
93
|
-
}
|
package/src/prerender/index.ts
DELETED
|
@@ -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,266 +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("pages/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?.API_URL || env?.apiUrl || "https://api.example.com";
|
|
119
|
-
const baseUrl = env?.BASE_URL || env?.baseUrl || "https://www.example.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
|
-
let profile: Profile | null = null;
|
|
135
|
-
let aboutMeContent = "";
|
|
136
|
-
let homeContent = "";
|
|
137
|
-
let latestBlogs: BlogMeta[] = [];
|
|
138
|
-
let latestStories: BlogMeta[] = [];
|
|
139
|
-
|
|
140
|
-
if (env?.CONTENT_BUCKET) {
|
|
141
|
-
[profile, aboutMeContent, homeContent, latestBlogs, latestStories] = await Promise.all([
|
|
142
|
-
fetchProfile(env), fetchAboutMe(env), fetchHome(env), fetchLatestBlogSummaries(env, 3), fetchLatestStorySummaries(env, 3)
|
|
143
|
-
]);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const name = profile?.name || "User";
|
|
147
|
-
const title = profile?.title || "Professional";
|
|
148
|
-
const canonicalUrl = new URL(pathname, baseUrl).toString();
|
|
149
|
-
|
|
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
|
-
};
|
|
248
|
-
|
|
249
|
-
if (pathname === "/" || pathname === "") {
|
|
250
|
-
return strategies.home();
|
|
251
|
-
} else if (pathname === "/about-me") {
|
|
252
|
-
return strategies.about();
|
|
253
|
-
} else if (pathname === "/blogs" || pathname === "/blogs/") {
|
|
254
|
-
return strategies.blogsList();
|
|
255
|
-
} else if (pathname === "/stories" || pathname === "/stories/") {
|
|
256
|
-
return strategies.storiesList();
|
|
257
|
-
} else if (pathname.startsWith("/blogs/")) {
|
|
258
|
-
const slug = pathname.replace("/blogs/", "").replace("/", "");
|
|
259
|
-
return strategies.blogDetail(slug);
|
|
260
|
-
} else if (pathname.startsWith("/stories/")) {
|
|
261
|
-
const slug = pathname.replace("/stories/", "").replace("/", "");
|
|
262
|
-
return strategies.storyDetail(slug);
|
|
263
|
-
} else {
|
|
264
|
-
return strategies.notFound();
|
|
265
|
-
}
|
|
266
|
-
};
|
|
@@ -1,38 +0,0 @@
|
|
|
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
|
-
}
|