@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,250 +0,0 @@
1
- import { generatePageContent, ContentMetadata, IFooterLink } from './page-content';
2
- import { IRoute } from './interfaces/iroute';
3
- import { WebsiteUI } from './website-ui';
4
-
5
- export class Router {
6
- private routes: IRoute[];
7
- private siteTitle: string;
8
- private copyright: string;
9
- private footerLinks: IFooterLink[];
10
- private apiUrl: string;
11
- private logo: string = '/api/logo';
12
- private appElement: HTMLElement | null = null;
13
-
14
- constructor(private ui: WebsiteUI) {
15
- const store = ui.getStore();
16
- const config = store.getConfig();
17
-
18
- // Default routes if not provided in bootstrap
19
- this.routes = [
20
- { link: '/', text: 'Home' },
21
- { link: '/blogs', text: 'Blogs' },
22
- { link: '/stories', text: 'Stories' },
23
- { link: '/about-me', text: 'About Me' },
24
- ];
25
-
26
- this.siteTitle = config.siteTitle;
27
- this.copyright = config.copyright;
28
- this.apiUrl = config.apiUrl;
29
-
30
- const normalizeUrl = (url?: string) => {
31
- if (!url) return '';
32
- if (url.startsWith('http://') || url.startsWith('https://')) return url;
33
- if (url.startsWith('www.')) return `https://${url}`;
34
- return url;
35
- };
36
-
37
- this.footerLinks = [
38
- { text: 'LinkedIn', link: normalizeUrl(config.linkedin) || 'https://linkedin.com' },
39
- { text: 'GitHub', link: normalizeUrl(config.github) || 'https://github.com' },
40
- { text: 'Email', link: config.email ? `mailto:${config.email}` : 'mailto:hello@example.com' },
41
- ];
42
- }
43
-
44
- public init(appElementId: string = 'app') {
45
- this.appElement = document.getElementById(appElementId);
46
- if (!this.appElement) {
47
- console.error(`App element with id ${appElementId} not found`);
48
- return;
49
- }
50
-
51
- this.setupEventListeners();
52
- this.navigate(window.location.pathname);
53
- }
54
-
55
- private setupEventListeners() {
56
- document.body.addEventListener('click', (event) => {
57
- const target = (event.target as HTMLElement).closest('a[data-route]');
58
- if (target) {
59
- event.preventDefault();
60
- const route = target.getAttribute('href');
61
- if (route && route !== window.location.pathname) {
62
- window.history.pushState({}, '', route);
63
- this.navigate(route);
64
- }
65
- }
66
- });
67
-
68
- window.addEventListener('popstate', () => {
69
- this.navigate(window.location.pathname);
70
- });
71
-
72
- // Search input handler
73
- document.body.addEventListener('input', (event) => {
74
- const target = event.target as HTMLInputElement;
75
- if (target.id === 'content-search') {
76
- const query = target.value.toLowerCase().trim();
77
- const items = document.querySelectorAll('#content-list .list-item');
78
- items.forEach(item => {
79
- const searchText = item.getAttribute('data-search') || '';
80
- if (searchText.includes(query)) {
81
- item.classList.remove('hidden');
82
- } else {
83
- item.classList.add('hidden');
84
- }
85
- });
86
- }
87
- });
88
- }
89
-
90
- public async navigate(path: string) {
91
- if (path.startsWith('/blogs/') && path.length > 7) {
92
- await this.renderContentDetailPage(path);
93
- return;
94
- }
95
- if (path.startsWith('/stories/') && path.length > 9) {
96
- await this.renderContentDetailPage(path);
97
- return;
98
- }
99
-
100
- switch (path) {
101
- case '/':
102
- await this.renderHomePage();
103
- break;
104
- case '/about-me':
105
- this.renderAboutMePage();
106
- break;
107
- case '/blogs':
108
- case '/stories':
109
- await this.renderContentListPage(path);
110
- break;
111
- case '/admin':
112
- await this.renderAdminPage();
113
- break;
114
- default:
115
- await this.renderHomePage(); // Fallback to home for now
116
- }
117
- }
118
-
119
- private setPageMeta(title: string, description: string, url: string) {
120
- document.title = title;
121
- const metaTags = [
122
- { name: 'description', content: description },
123
- { property: 'og:title', content: title },
124
- { property: 'og:description', content: description },
125
- { property: 'og:url', content: url }
126
- ];
127
-
128
- metaTags.forEach(tag => {
129
- let element = tag.name
130
- ? document.querySelector(`meta[name="${tag.name}"]`)
131
- : document.querySelector(`meta[property="${tag.property}"]`);
132
-
133
- if (!element) {
134
- element = document.createElement('meta');
135
- if (tag.name) element.setAttribute('name', tag.name);
136
- if (tag.property) element.setAttribute('property', tag.property);
137
- document.head.appendChild(element);
138
- }
139
- element.setAttribute('content', tag.content);
140
- });
141
-
142
- let canonicalLink = document.querySelector('link[rel="canonical"]');
143
- if (!canonicalLink) {
144
- canonicalLink = document.createElement('link');
145
- canonicalLink.setAttribute('rel', 'canonical');
146
- document.head.appendChild(canonicalLink);
147
- }
148
- canonicalLink.setAttribute('href', url);
149
- }
150
-
151
- private async renderHomePage() {
152
- let blogs: ContentMetadata[] = [];
153
- let stories: ContentMetadata[] = [];
154
- let homeContent = '';
155
-
156
- try {
157
- const [blogsRes, storiesRes, homeRes] = await Promise.all([
158
- fetch(`${this.apiUrl}/api/blogs`),
159
- fetch(`${this.apiUrl}/api/stories`),
160
- fetch(`${this.apiUrl}/api/home`)
161
- ]);
162
- if (blogsRes.ok) {
163
- const data = await blogsRes.json().catch(() => []);
164
- blogs = Array.isArray(data) ? data.slice(0, 3) : [];
165
- }
166
- if (storiesRes.ok) {
167
- const data = await storiesRes.json().catch(() => []);
168
- stories = Array.isArray(data) ? data.slice(0, 3) : [];
169
- }
170
- if (homeRes.ok) {
171
- const data = await homeRes.json().catch(() => ({}));
172
- homeContent = data.content || '';
173
- }
174
- } catch (e) {
175
- console.error('Failed to fetch home content', e);
176
- }
177
-
178
- const pageContent = generatePageContent('/', this.routes, this.footerLinks, {
179
- blogs, stories, content: homeContent, siteTitle: this.siteTitle, copyright: this.copyright
180
- });
181
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
182
- this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
183
- }
184
-
185
- private renderAboutMePage() {
186
- const pageContent = generatePageContent('/about-me', this.routes, this.footerLinks, {
187
- siteTitle: this.siteTitle, copyright: this.copyright, apiUrl: this.apiUrl
188
- });
189
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
190
- this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
191
- }
192
-
193
- private async renderContentListPage(pathname: string) {
194
- const type = pathname === '/blogs' ? 'blogs' : 'stories';
195
- let items: ContentMetadata[] = [];
196
- try {
197
- const res = await fetch(`${this.apiUrl}/api/${type}`);
198
- if (res.ok) items = await res.json();
199
- } catch (e) {}
200
-
201
- const latestSlug = items.length > 0 ? items[0].slug : undefined;
202
- const data = type === 'blogs' ? { blogs: items, slug: latestSlug } : { stories: items, slug: latestSlug };
203
- const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
204
- ...data, siteTitle: this.siteTitle, copyright: this.copyright
205
- });
206
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
207
- this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
208
- }
209
-
210
- private async renderContentDetailPage(pathname: string) {
211
- const isBlog = pathname.startsWith('/blogs/');
212
- const type = isBlog ? 'blogs' : 'stories';
213
- const slug = pathname.replace(`/${type}/`, '').split('/')[0];
214
-
215
- let items: ContentMetadata[] = [];
216
- try {
217
- const res = await fetch(`${this.apiUrl}/api/${type}`);
218
- if (res.ok) items = await res.json();
219
- } catch (e) {}
220
-
221
- const data = type === 'blogs' ? { blogs: items, slug } : { stories: items, slug };
222
- const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
223
- ...data, siteTitle: this.siteTitle, copyright: this.copyright
224
- });
225
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
226
- this.setPageMeta(`${slug.replace(/-/g, ' ')} - ${this.siteTitle}`, 'Read more content', window.location.href);
227
- }
228
-
229
- private async renderAdminPage() {
230
- // Admin portal should be lazy loaded or integrated
231
- const pageContent = generatePageContent('/admin', this.routes, this.footerLinks, {
232
- siteTitle: this.siteTitle, copyright: this.copyright
233
- });
234
-
235
- if (this.appElement) {
236
- this.appElement.innerHTML = `
237
- <my-banner header="${this.siteTitle}" logo="${this.logo}">
238
- <theme-toggle slot="theme-switcher"></theme-toggle>
239
- <nav slot="nav-links">
240
- <a href="/" class="nav-link" data-route="home">Home</a>
241
- </nav>
242
- </my-banner>
243
- <main class="container container-medium">
244
- <admin-portal></admin-portal>
245
- </main>
246
- <my-footer copyright="${this.copyright}" footerLinks='[]'></my-footer>
247
- `;
248
- }
249
- }
250
- }
@@ -1,11 +0,0 @@
1
- // Runtime-only exports for Cloudflare Workers (no DOM/web components)
2
- export * from './interfaces/iroute';
3
- export * from './interfaces/ifooter-link';
4
- export * from './page-content';
5
-
6
- // Core utilities and types (DOM-free)
7
- export type { IRoute } from './interfaces/iroute';
8
- export type { IFooterLink } from './interfaces/ifooter-link';
9
-
10
- // Page content generation utilities
11
- export { generatePageContent } from './page-content';
@@ -1,35 +0,0 @@
1
- export interface TemplateProps {
2
- title: string;
3
- description: string;
4
- canonicalUrl: string;
5
- content: string;
6
- }
7
-
8
- export const createHtmlTemplate = ({
9
- title,
10
- description,
11
- canonicalUrl,
12
- content
13
- }: TemplateProps): string => {
14
- return `<!doctype html>
15
- <html lang="en" data-theme="light">
16
- <head>
17
- <meta charset="UTF-8" />
18
- <link rel="icon" type="image/svg+xml" href="/api/logo" />
19
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
20
- <title>${title}</title>
21
- <meta name="description" content="${description}" />
22
- <meta property="og:title" content="${title}" />
23
- <meta property="og:description" content="${description}" />
24
- <meta property="og:url" content="${canonicalUrl}" />
25
- <link rel="canonical" href="${canonicalUrl}" />
26
- <link rel="stylesheet" href="/src/styles.css" />
27
- <script type="module" src="/src/index.ts"></script>
28
- </head>
29
- <body>
30
- <div id="app">
31
- ${content}
32
- </div>
33
- </body>
34
- </html>`;
35
- };
@@ -1,92 +0,0 @@
1
- import { SiteStore } from './core/site-store';
2
- import { IRoute } from './interfaces/iroute';
3
- import { Router } from './router';
4
-
5
- export interface UIConfig {
6
- apiUrl?: string;
7
- baseUrl?: string;
8
- routes?: IRoute[];
9
- appElementId?: string;
10
- theme?: {
11
- primaryColor?: string;
12
- backgroundColor?: string;
13
- textColor?: string;
14
- customCss?: string;
15
- };
16
- onBootstrap?: (ui: WebsiteUI) => void | Promise<void>;
17
- }
18
-
19
- export class WebsiteUI {
20
- private static instance: WebsiteUI;
21
- private store: SiteStore;
22
- private config: UIConfig;
23
- private router: Router | null = null;
24
-
25
- private constructor(config: UIConfig = {}) {
26
- this.store = SiteStore.getInstance();
27
- this.config = config;
28
- }
29
-
30
- public static getInstance(config?: UIConfig): WebsiteUI {
31
- if (!WebsiteUI.instance) {
32
- WebsiteUI.instance = new WebsiteUI(config);
33
- } else if (config) {
34
- // Merge config if provided on existing instance
35
- WebsiteUI.instance.config = { ...WebsiteUI.instance.config, ...config };
36
- }
37
- return WebsiteUI.instance;
38
- }
39
-
40
- public async bootstrap() {
41
- // 1. Initialize SiteStore with infra config
42
- await this.store.init({
43
- apiUrl: this.config.apiUrl,
44
- baseUrl: this.config.baseUrl
45
- });
46
-
47
- // 2. Apply theme overrides if any
48
- this.applyTheme();
49
-
50
- // 3. Setup Router
51
- this.router = new Router(this);
52
- this.router.init(this.config.appElementId || 'app');
53
-
54
- // 4. Run bootstrap hook
55
- if (this.config.onBootstrap) {
56
- await this.config.onBootstrap(this);
57
- }
58
-
59
- console.log('WebsiteUI bootstrapped');
60
- }
61
-
62
- private applyTheme() {
63
- if (!this.config.theme) return;
64
-
65
- const { theme } = this.config;
66
- const root = document.documentElement;
67
-
68
- if (theme.primaryColor) root.style.setProperty('--link-color', theme.primaryColor);
69
- if (theme.backgroundColor) root.style.setProperty('--background-color', theme.backgroundColor);
70
- if (theme.textColor) root.style.setProperty('--text-color', theme.textColor);
71
-
72
- if (theme.customCss) {
73
- const style = document.createElement('style');
74
- style.textContent = theme.customCss;
75
- document.head.appendChild(style);
76
- }
77
- }
78
-
79
- public getStore() {
80
- return this.store;
81
- }
82
-
83
- public getConfig() {
84
- return this.config;
85
- }
86
-
87
- public getRouter() {
88
- return this.router;
89
- }
90
- }
91
-
92
- export const bootstrap = (config?: UIConfig) => WebsiteUI.getInstance(config).bootstrap();
@@ -1,129 +0,0 @@
1
- /* Markdown Content Styles */
2
-
3
- .md-heading {
4
- margin: 1.5rem 0 0.75rem 0;
5
- font-weight: 600;
6
- line-height: 1.3;
7
- color: var(--text-color, #213547);
8
- }
9
-
10
- .md-heading h1 { font-size: 2rem; }
11
- .md-heading h2 { font-size: 1.5rem; }
12
- .md-heading h3 { font-size: 1.25rem; }
13
- .md-heading h4 { font-size: 1.1rem; }
14
- .md-heading h5, .md-heading h6 { font-size: 1rem; }
15
-
16
- .md-paragraph {
17
- margin: 0 0 1rem 0;
18
- line-height: 1.7;
19
- color: var(--text-color, #213547);
20
- }
21
-
22
- .md-list {
23
- margin: 0 0 1rem 0;
24
- padding-left: 1.5rem;
25
- }
26
-
27
- .md-list-item {
28
- margin-bottom: 0.5rem;
29
- line-height: 1.6;
30
- }
31
-
32
- .md-blockquote {
33
- margin: 1rem 0;
34
- padding: 0.75rem 1rem;
35
- border-left: 4px solid var(--link-color, #646cff);
36
- background: var(--nav-link-hover-bg, #f8f9fa);
37
- border-radius: 0 8px 8px 0;
38
- color: var(--secondary-text, #666);
39
- }
40
-
41
- .md-blockquote .md-paragraph {
42
- margin: 0;
43
- }
44
-
45
- .md-code {
46
- font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
47
- font-size: 0.9em;
48
- background: var(--nav-link-hover-bg, #f5f5f5);
49
- padding: 0.2rem 0.4rem;
50
- border-radius: 4px;
51
- }
52
-
53
- .md-image {
54
- max-width: 100%;
55
- height: auto;
56
- border-radius: 8px;
57
- margin: 1rem 0;
58
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
59
- }
60
-
61
- .md-container {
62
- margin: 1rem 0;
63
- }
64
-
65
- .md-container hr {
66
- border: none;
67
- height: 2px;
68
- background: var(--border-color, #eee);
69
- margin: 2rem 0;
70
- border-radius: 1px;
71
- }
72
-
73
- .md-container[tag="hr"] {
74
- margin: 2rem 0;
75
- }
76
-
77
- /* Links within markdown content */
78
- .md-paragraph a,
79
- .md-list-item a {
80
- color: var(--link-color, #646cff);
81
- text-decoration: none;
82
- transition: color 0.2s ease;
83
- }
84
-
85
- .md-paragraph a:hover,
86
- .md-list-item a:hover {
87
- color: var(--link-hover-color, #535bf2);
88
- text-decoration: underline;
89
- }
90
-
91
- /* Strong and emphasis */
92
- .md-paragraph strong,
93
- .md-list-item strong {
94
- font-weight: 600;
95
- color: var(--text-color, #1a1a1a);
96
- }
97
-
98
- .md-paragraph em,
99
- .md-list-item em {
100
- font-style: italic;
101
- }
102
-
103
- /* Tables (if supported) */
104
- .md-container table {
105
- width: 100%;
106
- border-collapse: collapse;
107
- margin: 1rem 0;
108
- }
109
-
110
- .md-container th,
111
- .md-container td {
112
- padding: 0.75rem;
113
- border: 1px solid var(--border-color, #ddd);
114
- text-align: left;
115
- }
116
-
117
- .md-container th {
118
- background: var(--nav-link-hover-bg, #f5f5f5);
119
- font-weight: 600;
120
- }
121
-
122
- /* Dark theme adjustments */
123
- html[data-theme="dark"] .md-code {
124
- background: rgba(255, 255, 255, 0.1);
125
- }
126
-
127
- html[data-theme="dark"] .md-container th {
128
- background: rgba(255, 255, 255, 0.05);
129
- }
@@ -1,12 +0,0 @@
1
- export interface Profile {
2
- name: string;
3
- title: string;
4
- experience: string;
5
- profileImageUrl?: string;
6
- }
7
-
8
- export async function fetchAboutMe(url: string): Promise<{ profile: Profile; contentNodes: any[] }> {
9
- const res = await fetch(`${url}/api/aboutme`);
10
- if (!res.ok) throw new Error('Failed to fetch');
11
- return res.json();
12
- }
@@ -1,121 +0,0 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property, state } from 'lit/decorators.js';
3
-
4
- import { Profile } from './api';
5
- import { aboutmeStyles } from './styles';
6
- import { fetchAboutMe } from './api';
7
-
8
- @customElement('my-aboutme')
9
- export class MyAboutme extends LitElement {
10
- static styles = aboutmeStyles;
11
-
12
- @property({ type: String })
13
- accessor baseUrl = '';
14
-
15
- @state()
16
- accessor profile: Profile | null = null;
17
-
18
- @state()
19
- accessor htmlContent: string = '';
20
-
21
- @state()
22
- accessor loading = true;
23
-
24
- public fetcher = fetchAboutMe;
25
-
26
- constructor() {
27
- super();
28
- }
29
-
30
- private get apiBaseUrl(): string {
31
- return this.baseUrl || this.getAttribute('base-url') || '';
32
- }
33
-
34
- async connectedCallback() {
35
- super.connectedCallback();
36
-
37
- if (typeof window !== 'undefined' && (window as any).__HYDRATION_DATA__) {
38
- const hydrationData = (window as any).__HYDRATION_DATA__;
39
- this.profile = hydrationData.profile;
40
- this.htmlContent = hydrationData.processedMarkdown;
41
- this.loading = false;
42
- return;
43
- }
44
-
45
- this.loadContent();
46
- }
47
-
48
- updated(changedProperties: Map<string, any>) {
49
- super.updated(changedProperties);
50
-
51
- if (this.loading === false && this.profile && this.htmlContent) {
52
- return;
53
- }
54
-
55
- if (changedProperties.has('baseUrl') || changedProperties.has('base-url')) {
56
- this.loadContent();
57
- }
58
- }
59
-
60
- private async loadContent() {
61
- const url = this.apiBaseUrl;
62
- if (!url) {
63
- this.loading = false;
64
- this.setFallbackContent();
65
- return;
66
- }
67
-
68
- try {
69
- this.loading = true;
70
- const data = await this.fetcher(url);
71
-
72
- this.profile = data.profile;
73
- this.htmlContent = (data as any).processedMarkdown;
74
- this.loading = false;
75
- await this.updateComplete;
76
- } catch (error) {
77
- this.loading = false;
78
- this.setFallbackContent();
79
- await this.updateComplete;
80
- }
81
- }
82
-
83
- private setFallbackContent() {
84
- this.profile = null;
85
- this.htmlContent = `
86
- <h2>Profile Not Found</h2>
87
- <p>Your about-me profile has not been initialized yet. Please run the seed command to get started:</p>
88
- <pre>npm run seed -- &lt;username&gt; '&lt;password&gt;'</pre>
89
- `;
90
- }
91
-
92
- render() {
93
- if (this.loading) {
94
- return html`<div class="aboutme"><div class="loading">Loading...</div></div>`;
95
- }
96
-
97
- if (!this.profile) {
98
- return html`<div class="aboutme"><div class="content-section" .innerHTML="${this.htmlContent}"></div></div>`;
99
- }
100
-
101
- const profileImageUrl = this.profile.profileImageUrl
102
- ? (this.profile.profileImageUrl.startsWith('http') || this.profile.profileImageUrl.startsWith('/')
103
- ? this.profile.profileImageUrl
104
- : `images/${this.profile.profileImageUrl}`)
105
- : '';
106
-
107
- return html`
108
- <div class="aboutme">
109
- <div class="profile-section">
110
- ${profileImageUrl ? html`
111
- <img src="${profileImageUrl}" alt="${this.profile.name}"
112
- class="profile-picture">
113
- ` : ''}
114
- <h1>${this.profile.name}</h1>
115
- <p class="profile-title">${this.profile.title} • ${this.profile.experience}</p>
116
- </div>
117
- <div class="content-section" .innerHTML="${this.htmlContent}"></div>
118
- </div>
119
- `;
120
- }
121
- }