@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,210 +0,0 @@
1
- import { IFooterLink } from './interfaces/ifooter-link';
2
- import { IRoute } from './interfaces/iroute';
3
- export type { IFooterLink, IRoute };
4
- import { MarkdownPipeline } from '@leadertechie/md2html';
5
-
6
- export interface PageContent {
7
- title: string;
8
- description: string;
9
- canonicalUrl: string;
10
- content: string;
11
- }
12
-
13
- export interface ContentMetadata {
14
- slug: string;
15
- title: string;
16
- description?: string;
17
- summary?: string;
18
- date: string;
19
- imageUrl?: string;
20
- tags?: string[];
21
- author?: string;
22
- }
23
-
24
- const pipeline = new MarkdownPipeline({
25
- imagePathPrefix: 'images/',
26
- styleOptions: {
27
- classPrefix: 'md-',
28
- addHeadingIds: true
29
- }
30
- });
31
-
32
- export const renderMarkdown = (content: string): string => {
33
- if (!content) return '';
34
- return pipeline.renderMarkdown(content);
35
- };
36
-
37
- export const generatePageContent = (
38
- pathname: string,
39
- routes: IRoute[],
40
- footerLinks: IFooterLink[],
41
- data?: {
42
- blogs?: ContentMetadata[];
43
- stories?: ContentMetadata[];
44
- profile?: any;
45
- content?: string;
46
- title?: string;
47
- description?: string;
48
- date?: string;
49
- slug?: string;
50
- siteTitle?: string;
51
- siteDescription?: string;
52
- copyright?: string;
53
- apiUrl?: string;
54
- baseUrl?: string;
55
- }
56
- ): PageContent => {
57
- const logo = "/api/logo";
58
- const siteTitle = data?.siteTitle || "My Personal Website";
59
- const siteDescription = data?.siteDescription || "Welcome to my professional portfolio and blog.";
60
- const copyright = data?.copyright || "2026 All Rights Reserved";
61
- const baseUrl = data?.baseUrl || "";
62
- const canonicalUrl = baseUrl ? new URL(pathname, baseUrl).toString() : pathname;
63
-
64
- const navLinks = routes
65
- .map(
66
- (route) =>
67
- `<a href="${route.link}" class="nav-link" data-route="${route.link === "/" ? "home" : route.text.toLowerCase()}">${route.text}</a>`,
68
- )
69
- .join("");
70
-
71
- const bannerTemplate = `
72
- <my-banner header="${siteTitle}" logo="${logo}">
73
- <theme-toggle slot="theme-switcher"></theme-toggle>
74
- <nav slot="nav-links">
75
- ${navLinks}
76
- </nav>
77
- </my-banner>`;
78
-
79
- const footerTemplate = `
80
- <my-footer
81
- copyright="${copyright}"
82
- footerlinks='\${JSON.stringify(footerLinks)}'>
83
- </my-footer>`;
84
-
85
- const renderContentGists = (items: ContentMetadata[] = [], title: string, type: 'blogs' | 'stories') => {
86
- const listHtml = items.length > 0
87
- ? items.map(item => `
88
- <div class="gist-card">
89
- <a href="${type}/${item.slug}" data-route="${type}-${item.slug}"><h4>${item.title}</h4></a>
90
- <p>${item.summary || item.description || ''}</p>
91
- <small>${new Date(item.date).toLocaleDateString()}</small>
92
- </div>
93
- `).join('')
94
- : `<p>No ${type} available yet.</p>`;
95
-
96
- return `
97
- <div class="recent-content-section ${title.includes('Stories') ? 'mt-2' : ''}">
98
- <h3 class="border-bottom pb-05">${title}</h3>
99
- ${listHtml}
100
- </div>
101
- `;
102
- };
103
-
104
- if (pathname === "/" || pathname === "") {
105
- const homeHtml = data?.content || `<h1>Welcome to ${siteTitle}</h1><p>Start by configuring your content in the admin portal.</p>`;
106
-
107
- const mainContent = `
108
- ${bannerTemplate}
109
- <main class="container container-wide column-layout row-layout">
110
- <div class="home-main-content main-column text-left">
111
- ${homeHtml}
112
- </div>
113
- <aside class="home-side-content sidebar-column">
114
- ${renderContentGists(data?.blogs, 'Recent Blogs', 'blogs')}
115
- ${renderContentGists(data?.stories, 'Recent Stories', 'stories')}
116
- </aside>
117
- </main>
118
- ${footerTemplate}`;
119
-
120
- return {
121
- title: `${siteTitle} - Home`,
122
- description: siteDescription,
123
- canonicalUrl,
124
- content: mainContent
125
- };
126
- } else if (pathname === "/blogs" || pathname === "/stories" || pathname.startsWith("/blogs/") || pathname.startsWith("/stories/")) {
127
- const isBlog = pathname.includes("blogs");
128
- const type = isBlog ? "blogs" : "stories";
129
- const items = isBlog ? data?.blogs : data?.stories;
130
- const title = type.charAt(0).toUpperCase() + type.slice(1);
131
- const currentSlug = data?.slug;
132
-
133
- const mainContent = `
134
- ${bannerTemplate}
135
- <main class="container container-wide">
136
- <div class="mb-2">
137
- <input type="text" id="content-search" placeholder="Search ${type}..." class="search-input search-input-lg">
138
- </div>
139
- <div class="column-layout">
140
- <aside class="sidebar-nav sidebar-column">
141
- <div id="content-list">
142
- ${items?.map(item => {
143
- const searchTerms = [
144
- item.title,
145
- ...(item.tags || []),
146
- item.summary || item.description || '',
147
- item.slug
148
- ].join(' ').toLowerCase();
149
-
150
- return `
151
- <div class="list-item sidebar-item ${item.slug === currentSlug ? 'active' : ''}"
152
- data-search="${searchTerms}">
153
- <a href="/${type}/${item.slug}" data-route="${type}-${item.slug}" class="sidebar-item sidebar-item-link">
154
- <h4>${item.title}</h4>
155
- <small>${new Date(item.date).toLocaleDateString()}</small>
156
- </a>
157
- </div>
158
- `}).join('') || `<p>No ${type} available yet.</p>`}
159
- </div>
160
- </aside>
161
- <div class="wide-main-column text-left">
162
- <div id="content-viewer">
163
- ${currentSlug
164
- ? (isBlog ? `<my-blog-viewer slug="${currentSlug}"></my-blog-viewer>` : `<my-story-viewer slug="${currentSlug}"></my-story-viewer>`)
165
- : (items && items.length > 0 ? `<p>Select a ${type.slice(0, -1)} to read.</p>` : '')}
166
- </div>
167
- </div>
168
- </div>
169
- </main>
170
- ${footerTemplate}`;
171
-
172
- return {
173
- title: `${title} - ${siteTitle}`,
174
- description: `Read the latest ${type} from ${siteTitle}.`,
175
- canonicalUrl,
176
- content: mainContent
177
- };
178
- } else if (pathname === "/about-me") {
179
- const apiBaseUrl = data?.apiUrl || "";
180
- const mainContent = `
181
- ${bannerTemplate}
182
- <main class="container container-narrow">
183
- <my-aboutme base-url="${apiBaseUrl}"></my-aboutme>
184
- </main>
185
- ${footerTemplate}`;
186
-
187
- return {
188
- title: `About - ${siteTitle}`,
189
- description: `Learn more about ${siteTitle}.`,
190
- canonicalUrl,
191
- content: mainContent
192
- };
193
- } else {
194
- const mainContent = `
195
- ${bannerTemplate}
196
- <main class="container container-narrow text-center page-content">
197
- <h1 class="page-title">Page Not Found</h1>
198
- <p>The page you're looking for doesn't exist.</p>
199
- <p><a href="/">Return to home</a></p>
200
- </main>
201
- ${footerTemplate}`;
202
-
203
- return {
204
- title: `404 Not Found - ${siteTitle}`,
205
- description: "The page you requested could not be found.",
206
- canonicalUrl,
207
- content: mainContent
208
- };
209
- }
210
- };
@@ -1,241 +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) },
39
- { text: 'GitHub', link: normalizeUrl(config.github) },
40
- { text: 'Email', link: config.email ? `mailto:${config.email}` : '' },
41
- ].filter(link => link.link !== '');
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) blogs = (await blogsRes.json()).slice(0, 3);
163
- if (storiesRes.ok) stories = (await storiesRes.json()).slice(0, 3);
164
- if (homeRes.ok) homeContent = (await homeRes.json()).content;
165
- } catch (e) {
166
- console.error('Failed to fetch home content', e);
167
- }
168
-
169
- const pageContent = generatePageContent('/', this.routes, this.footerLinks, {
170
- blogs, stories, content: homeContent, siteTitle: this.siteTitle, copyright: this.copyright
171
- });
172
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
173
- this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
174
- }
175
-
176
- private renderAboutMePage() {
177
- const pageContent = generatePageContent('/about-me', this.routes, this.footerLinks, {
178
- siteTitle: this.siteTitle, copyright: this.copyright, apiUrl: this.apiUrl
179
- });
180
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
181
- this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
182
- }
183
-
184
- private async renderContentListPage(pathname: string) {
185
- const type = pathname === '/blogs' ? 'blogs' : 'stories';
186
- let items: ContentMetadata[] = [];
187
- try {
188
- const res = await fetch(`${this.apiUrl}/api/${type}`);
189
- if (res.ok) items = await res.json();
190
- } catch (e) {}
191
-
192
- const latestSlug = items.length > 0 ? items[0].slug : undefined;
193
- const data = type === 'blogs' ? { blogs: items, slug: latestSlug } : { stories: items, slug: latestSlug };
194
- const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
195
- ...data, siteTitle: this.siteTitle, copyright: this.copyright
196
- });
197
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
198
- this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
199
- }
200
-
201
- private async renderContentDetailPage(pathname: string) {
202
- const isBlog = pathname.startsWith('/blogs/');
203
- const type = isBlog ? 'blogs' : 'stories';
204
- const slug = pathname.replace(`/${type}/`, '').split('/')[0];
205
-
206
- let items: ContentMetadata[] = [];
207
- try {
208
- const res = await fetch(`${this.apiUrl}/api/${type}`);
209
- if (res.ok) items = await res.json();
210
- } catch (e) {}
211
-
212
- const data = type === 'blogs' ? { blogs: items, slug } : { stories: items, slug };
213
- const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
214
- ...data, siteTitle: this.siteTitle, copyright: this.copyright
215
- });
216
- if (this.appElement) this.appElement.innerHTML = pageContent.content;
217
- this.setPageMeta(`${slug.replace(/-/g, ' ')} - ${this.siteTitle}`, 'Read more content', window.location.href);
218
- }
219
-
220
- private async renderAdminPage() {
221
- // Admin portal should be lazy loaded or integrated
222
- const pageContent = generatePageContent('/admin', this.routes, this.footerLinks, {
223
- siteTitle: this.siteTitle, copyright: this.copyright
224
- });
225
-
226
- if (this.appElement) {
227
- this.appElement.innerHTML = `
228
- <my-banner header="${this.siteTitle}" logo="${this.logo}">
229
- <theme-toggle slot="theme-switcher"></theme-toggle>
230
- <nav slot="nav-links">
231
- <a href="/" class="nav-link" data-route="home">Home</a>
232
- </nav>
233
- </my-banner>
234
- <main class="container container-medium">
235
- <admin-portal></admin-portal>
236
- </main>
237
- <my-footer copyright="${this.copyright}" footerlinks='[]'></my-footer>
238
- `;
239
- }
240
- }
241
- }
@@ -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();