@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/shared/router.ts
DELETED
|
@@ -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
|
-
}
|
package/src/shared/runtime.ts
DELETED
|
@@ -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';
|
package/src/shared/template.ts
DELETED
|
@@ -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
|
-
};
|
package/src/shared/website-ui.ts
DELETED
|
@@ -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();
|
package/src/styles/markdown.css
DELETED
|
@@ -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
|
-
}
|
package/src/ui/about-me/api.ts
DELETED
|
@@ -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
|
-
}
|
package/src/ui/about-me/index.ts
DELETED
|
@@ -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 -- <username> '<password>'</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
|
-
}
|