@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
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @leadertechie/personal-site-kit
2
2
 
3
- A high-performance, modular engine for building personal websites and professional portfolios. Powered by Cloudflare Workers, R2 Storage, and Lit Web Components.
3
+ A high-performance, modular engine for building personal websites and professional portfolios. Powered by Cloudflare Workers, R2 Storage, Lit Web Components, and the **@leadertechie** ecosystem.
4
4
 
5
5
  ## Features
6
6
 
@@ -8,18 +8,21 @@ A high-performance, modular engine for building personal websites and profession
8
8
  - **🛠️ 4-Section Engine**: Fully integrated logic for **API**, **UI**, **Prerender**, and **Admin**.
9
9
  - **🎨 Modern Layout**: Semantic grid system with responsive `main-column` and `sidebar-column` components.
10
10
  - **🌓 Theme Engine**: Built-in light/dark mode support with persistent user preferences.
11
- - **📝 Content Driven**: Dynamic Markdown rendering with frontmatter support via `@leadertechie/md2html`.
11
+ - **📝 Content Driven**: Dynamic Markdown rendering with frontmatter support via `@leadertechie/md2html` (v2).
12
12
  - **⚙️ Remote Config**: Manage site settings (title, copyright, social links) directly from R2 without re-deploying.
13
13
  - **🔍 SEO Optimized**: Server-side prerendering ensures search engines see your content perfectly.
14
+ - **🔄 SWR Caching**: Stale-while-revalidate caching via `@leadertechie/r2tohtml` for optimal performance.
15
+ - **⚡ Client-Side Interactivity**: DOM interaction patterns (poll, live-update, click-toggle, infinite-scroll, form-live) via `@leadertechie/md2interact`.
16
+ - **🎯 CSS Hydration**: Inline critical CSS, layer injection, and theme toggle support.
14
17
 
15
18
  ## Architecture
16
19
 
17
20
  The kit is divided into four logical subpaths:
18
21
 
19
- 1. **/shared**: Common interfaces, types, and the reactive `SiteStore`.
20
- 2. **/ui**: Web components for the Banner, Footer, About Me, and Admin Portal.
21
- 3. **/api**: Standard handlers for Cloudflare Workers to manage R2 content.
22
- 4. **/prerender**: SEO engine to generate static HTML for crawlers.
22
+ 1. **/shared**: Common interfaces, types, the reactive `SiteStore`, and the `WebsiteUI` bootstrap.
23
+ 2. **/ui**: Web components for the Banner, Footer, About Me, Blog Viewer, Story Viewer, and Admin Portal.
24
+ 3. **/api**: Standard handlers for Cloudflare Workers to manage R2 content with v2 caching.
25
+ 4. **/prerender**: SEO engine to generate static HTML for crawlers with SWR caching.
23
26
 
24
27
  ## Installation
25
28
 
@@ -29,26 +32,100 @@ npm install @leadertechie/personal-site-kit
29
32
 
30
33
  ## Quick Start (Usage)
31
34
 
32
- ### 1. API Worker (`api.ts`)
35
+ ### 1. API Worker (`api/index.ts`)
36
+
33
37
  ```typescript
34
- import { handleAPIRoute } from '@leadertechie/personal-site-kit/api';
38
+ import { WebsiteAPI } from '@leadertechie/personal-site-kit/api';
39
+
40
+ export default new WebsiteAPI();
41
+ ```
42
+
43
+ ### 2. UI Entry (`ui/index.ts`)
35
44
 
36
- export default {
37
- async fetch(request, env) {
38
- return handleAPIRoute(request, env);
45
+ ```typescript
46
+ import { WebsiteUI } from '@leadertechie/personal-site-kit/shared';
47
+ import '@leadertechie/personal-site-kit/styles/theme.css';
48
+ import '@leadertechie/personal-site-kit/ui';
49
+
50
+ // Bootstrap the UI — md2interact is automatically initialized
51
+ WebsiteUI.getInstance({
52
+ apiUrl: window.location.origin,
53
+ // Optional: configure md2interact interactions
54
+ interactConfig: {
55
+ interactions: {
56
+ 'poll': { selector: '[data-interact="poll"]' },
57
+ 'live-update': { selector: '[data-interact="live-update"]' },
58
+ 'click-toggle': { selector: '[data-interact="click-toggle"]' },
59
+ 'infinite-scroll': { selector: '[data-interact="infinite-scroll"]' },
60
+ 'form-live': { selector: '[data-interact="form-live"]' }
61
+ },
62
+ cssHydration: {
63
+ inlineCritical: true,
64
+ layerInjection: true,
65
+ themeToggle: true
66
+ }
39
67
  }
40
- };
68
+ }).bootstrap();
41
69
  ```
42
70
 
43
- ### 2. UI Entry (`main.ts`)
71
+ ### 3. Prerender (`prerender/index.ts`)
72
+
44
73
  ```typescript
45
- import { SiteStore } from '@leadertechie/personal-site-kit/shared';
46
- import '@leadertechie/personal-site-kit/ui/banner';
74
+ import { WebsitePrerender } from '@leadertechie/personal-site-kit/prerender';
47
75
 
48
- const store = SiteStore.getInstance();
49
- await store.init({ apiUrl: 'https://api.yourdomain.com' });
76
+ export default new WebsitePrerender();
50
77
  ```
51
78
 
79
+ ## Client-Side Interactivity with md2interact
80
+
81
+ The kit automatically initializes `@leadertechie/md2interact` during `WebsiteUI.bootstrap()`. This enables:
82
+
83
+ ### DOM Interaction Patterns
84
+
85
+ Markdown content with frontmatter can declare interaction patterns:
86
+
87
+ ```markdown
88
+ ---
89
+ interaction: poll
90
+ interactConfig:
91
+ url: /api/data
92
+ interval: 5000
93
+ ---
94
+
95
+ # Live Poll Data
96
+ ```
97
+
98
+ Supported patterns: `poll`, `live-update`, `click-toggle`, `infinite-scroll`, `form-live`, `mfe`, `custom`
99
+
100
+ ### CSS Hydration
101
+
102
+ - **inlineCritical**: Injects critical CSS directly into the DOM
103
+ - **layerInjection**: Uses CSS `@layer` for organized style injection
104
+ - **themeToggle**: Enables light/dark theme toggling
105
+
106
+ ### SPA Navigation
107
+
108
+ After dynamic content loads (e.g., blog detail pages), call `reinitInteract()` to re-scan the DOM:
109
+
110
+ ```typescript
111
+ const ui = WebsiteUI.getInstance();
112
+ ui.reinitInteract();
113
+ ```
114
+
115
+ ## Caching Strategy (v2)
116
+
117
+ The kit uses `@leadertechie/r2tohtml` with a multi-tier caching strategy:
118
+
119
+ | Tier | Cache | TTL |
120
+ |------|-------|-----|
121
+ | In-Memory | `ContentCacheV2` with SWR | 5 min fresh, 30 min stale |
122
+ | Cloudflare Edge | `cfCache` | 5 min |
123
+
124
+ This means:
125
+ - **Fresh**: Served instantly from memory
126
+ - **Stale**: Served from memory while revalidating in background (SWR)
127
+ - **Miss**: Fetched from R2, cached at edge and in-memory
128
+
52
129
  ## Deployment
53
130
 
54
131
  This kit is designed to be deployed to **Cloudflare**. Ensure your `wrangler.toml` is configured with an R2 bucket named `CONTENT_BUCKET`.
@@ -0,0 +1,27 @@
1
+ export interface ContentMetadata {
2
+ slug: string;
3
+ title: string;
4
+ description: string;
5
+ summary?: string;
6
+ date: string;
7
+ imageUrl?: string;
8
+ tags?: string[];
9
+ author?: string;
10
+ }
11
+ export interface BlogPost extends ContentMetadata {
12
+ content: string;
13
+ }
14
+ export interface StoryPost extends ContentMetadata {
15
+ content: string;
16
+ }
17
+ export declare function getCachedOrFetch<T>(key: string, fetchFn: () => Promise<T>): Promise<T>;
18
+ export declare function clearContentCache(prefix?: string): void;
19
+ export declare function parseFrontmatter(content: string): {
20
+ metadata: ContentMetadata;
21
+ content: string;
22
+ };
23
+ export declare function checkContentBucket(env: any): Response | null;
24
+ export declare function fetchContentItem(bucket: any, type: 'blogs' | 'stories', slug: string): Promise<BlogPost | StoryPost>;
25
+ export declare function fetchContentList(bucket: any, type: 'blogs' | 'stories', latest?: number): Promise<ContentMetadata[]>;
26
+ export declare function searchContent(bucket: any, query: string): Promise<(BlogPost | StoryPost)[]>;
27
+ //# sourceMappingURL=content-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-utils.d.ts","sourceRoot":"","sources":["../../src/api/content-utils.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAS,SAAQ,eAAe;IAC/C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAU,SAAQ,eAAe;IAChD,OAAO,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAoBtF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAMvD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAMhG;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,QAAQ,GAAG,IAAI,CAK5D;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAqB1H;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAiB1H;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC,CA2BjG"}
@@ -1 +1 @@
1
- {"version":3,"file":"about-me.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/about-me.ts"],"names":[],"mappings":"AA4CA,wBAAgB,iBAAiB,SAEhC;AAED,wBAAsB,aAAa,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkDhE"}
1
+ {"version":3,"file":"about-me.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/about-me.ts"],"names":[],"mappings":"AAmDA,wBAAgB,iBAAiB,SAEhC;AAED,wBAAsB,aAAa,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4DhE"}
@@ -0,0 +1,2 @@
1
+ export declare function handleAuth(request: Request, env: any, subpath: string): Promise<Response>;
2
+ //# sourceMappingURL=auth-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-handler.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/auth-handler.ts"],"names":[],"mappings":"AAyBA,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgC/F"}
@@ -0,0 +1,23 @@
1
+ declare const AUTH_KV = "auth_store";
2
+ declare const RATE_LIMIT_KV = "rate_limit";
3
+ declare const MAX_ATTEMPTS = 5;
4
+ declare const BASE_DELAY_MS = 1000;
5
+ interface AuthStore {
6
+ username: string;
7
+ passwordHash: string;
8
+ salt: string;
9
+ }
10
+ declare function hashPassword(password: string, salt: string): Promise<string>;
11
+ declare function generateSalt(): Promise<string>;
12
+ declare function checkRateLimit(env: any, ip: string): Promise<{
13
+ allowed: boolean;
14
+ delayMs: number;
15
+ }>;
16
+ declare function recordFailedAttempt(env: any, ip: string): Promise<void>;
17
+ declare function clearRateLimit(env: any, ip: string): Promise<void>;
18
+ declare function getAuthStore(env: any): Promise<AuthStore | null>;
19
+ declare function setupAuth(env: any, username: string, password: string): Promise<void>;
20
+ declare function verifyCredentials(env: any, username: string, password: string): Promise<boolean>;
21
+ declare function getClientIP(request: Request): string;
22
+ export { hashPassword, generateSalt, checkRateLimit, recordFailedAttempt, clearRateLimit, getAuthStore, setupAuth, verifyCredentials, getClientIP, AUTH_KV, RATE_LIMIT_KV, MAX_ATTEMPTS, BASE_DELAY_MS };
23
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/auth.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,OAAO,eAAe,CAAC;AAC7B,QAAA,MAAM,aAAa,eAAe,CAAC;AACnC,QAAA,MAAM,YAAY,IAAI,CAAC;AACvB,QAAA,MAAM,aAAa,OAAO,CAAC;AAG3B,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAQD,iBAAe,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuB3E;AAED,iBAAe,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAG7C;AAED,iBAAe,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BlG;AAED,iBAAe,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtE;AAED,iBAAe,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjE;AAED,iBAAe,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAG/D;AAED,iBAAe,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASpF;AAED,iBAAe,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa/F;AAED,iBAAS,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAI7C;AAED,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,OAAO,EACP,aAAa,EACb,YAAY,EACZ,aAAa,EACd,CAAC"}
@@ -1,5 +1,4 @@
1
1
  export declare function handleBlogs(env?: any, slug?: string, latest?: number): Promise<Response>;
2
2
  export declare function handleStories(env?: any, slug?: string, latest?: number): Promise<Response>;
3
3
  export declare function handleSearch(env?: any, query?: string): Promise<Response>;
4
- export declare function clearContentCache(): void;
5
4
  //# sourceMappingURL=content-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content-api.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/content-api.ts"],"names":[],"mappings":"AA8EA,wBAAsB,WAAW,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgE9F;AAED,wBAAsB,aAAa,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8DhG;AAED,wBAAsB,YAAY,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuD/E;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
1
+ {"version":3,"file":"content-api.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/content-api.ts"],"names":[],"mappings":"AAWA,wBAAsB,WAAW,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmB9F;AAED,wBAAsB,aAAa,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmBhG;AAED,wBAAsB,YAAY,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkB/E"}
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/content.ts"],"names":[],"mappings":"AAEA,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqElG"}
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/content.ts"],"names":[],"mappings":"AAqBA,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqElG"}
@@ -1 +1 @@
1
- {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/home.ts"],"names":[],"mappings":"AAiCA,wBAAgB,iBAAiB,SAEhC;AAED,wBAAsB,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyC7D"}
1
+ {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/home.ts"],"names":[],"mappings":"AAwCA,wBAAgB,iBAAiB,SAEhC;AAED,wBAAsB,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwC7D"}
@@ -1,2 +1,2 @@
1
- export declare function handleStaticDetails(env?: any, method?: string, body?: any): Promise<Response>;
1
+ export declare function handleStaticDetails(env?: any, method?: string, _body?: any): Promise<Response>;
2
2
  //# sourceMappingURL=static-details.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"static-details.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/static-details.ts"],"names":[],"mappings":"AAQA,wBAAsB,mBAAmB,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuCnG"}
1
+ {"version":3,"file":"static-details.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/static-details.ts"],"names":[],"mappings":"AAQA,wBAAsB,mBAAmB,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuCpG"}
@@ -1,6 +1,8 @@
1
1
  import { WebsiteAPI } from './website-api';
2
2
  export { WebsiteAPI };
3
3
  export type { APIHandler } from './website-api';
4
+ export * from './handlers/auth';
5
+ export * from './handlers/auth-handler';
4
6
  declare const defaultAPI: WebsiteAPI;
5
7
  export default defaultAPI;
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,QAAA,MAAM,UAAU,YAAmB,CAAC;AACpC,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AAGxC,QAAA,MAAM,UAAU,YAAmB,CAAC;AACpC,eAAe,UAAU,CAAC"}
@@ -3,8 +3,8 @@ export declare class WebsiteAPI {
3
3
  private customHandlers;
4
4
  registerHandler(route: string, handler: APIHandler): void;
5
5
  private addCORSHeaders;
6
+ private addAdminCORSHeaders;
6
7
  private handleCORS;
7
- private requireAuth;
8
8
  fetch(request: Request, env: any): Promise<Response>;
9
9
  }
10
10
  //# sourceMappingURL=website-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"website-api.d.ts","sourceRoot":"","sources":["../../src/api/website-api.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE3E,qBAAa,UAAU;IACrB,OAAO,CAAC,cAAc,CAAiC;IAEhD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU;IAIzD,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,WAAW;IAYN,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;CA0ElE"}
1
+ {"version":3,"file":"website-api.d.ts","sourceRoot":"","sources":["../../src/api/website-api.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE3E,qBAAa,UAAU;IACrB,OAAO,CAAC,cAAc,CAAiC;IAEhD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU;IAIzD,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,UAAU;IAqBL,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;CAkGlE"}
package/dist/api.js CHANGED
@@ -1,6 +1,21 @@
1
- import { W as WebsiteAPI } from "./chunks/website-api-CVsi-OLc.js";
1
+ import { W as WebsiteAPI } from "./chunks/website-api-CFRUPu0X.js";
2
+ import { A, B, M, R, c, a, g, b, d, h, e, r, s, v } from "./chunks/website-api-CFRUPu0X.js";
2
3
  const defaultAPI = new WebsiteAPI();
3
4
  export {
5
+ A as AUTH_KV,
6
+ B as BASE_DELAY_MS,
7
+ M as MAX_ATTEMPTS,
8
+ R as RATE_LIMIT_KV,
4
9
  WebsiteAPI,
5
- defaultAPI as default
10
+ c as checkRateLimit,
11
+ a as clearRateLimit,
12
+ defaultAPI as default,
13
+ g as generateSalt,
14
+ b as getAuthStore,
15
+ d as getClientIP,
16
+ h as handleAuth,
17
+ e as hashPassword,
18
+ r as recordFailedAttempt,
19
+ s as setupAuth,
20
+ v as verifyCredentials
6
21
  };
@@ -0,0 +1,21 @@
1
+ <svg width="600" height="320" viewBox="0 0 600 320" xmlns="http://www.w3.org/2000/svg">
2
+ <style>
3
+ .text-main {
4
+ fill: light-dark(#4A5568, #E2E8F0);
5
+ font-family: 'Segoe UI', 'Arial Black', sans-serif;
6
+ font-weight: 900;
7
+ font-size: 46px;
8
+ letter-spacing: -0.01em;
9
+ text-anchor: middle;
10
+ }
11
+ </style>
12
+
13
+ <!-- Big circle placeholder -->
14
+ <circle cx="300" cy="160" r="100" fill="none" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" stroke-dasharray="8 4" opacity="0.5" />
15
+
16
+ <!-- Plus sign in circle -->
17
+ <line x1="300" y1="100" x2="300" y2="220" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" opacity="0.3" />
18
+ <line x1="240" y1="160" x2="360" y2="160" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" opacity="0.3" />
19
+
20
+ <text x="300" y="290" class="text-main">YOUR LOGO</text>
21
+ </svg>
@@ -0,0 +1,211 @@
1
+ class BasePageGenerator {
2
+ generateBanner(routes, siteTitle, logo) {
3
+ const navLinks = routes.map((r) => `<a href="${r.link}" class="nav-link" data-route="${r.link === "/" ? "home" : r.text.toLowerCase()}">${r.text}</a>`).join("");
4
+ return `
5
+ <my-banner header="${siteTitle}" logo="${logo}">
6
+ <theme-toggle slot="theme-switcher"></theme-toggle>
7
+ <nav slot="nav-links">
8
+ ${navLinks}
9
+ </nav>
10
+ </my-banner>`;
11
+ }
12
+ generateFooter(footerLinks, copyright) {
13
+ return `
14
+ <my-footer
15
+ copyright="${copyright}"
16
+ footerlinks='${JSON.stringify(footerLinks)}'>
17
+ </my-footer>`;
18
+ }
19
+ wrapContent(banner, mainContent, footer) {
20
+ return `${banner}${mainContent}${footer}`;
21
+ }
22
+ generatePage(pathname, routes, footerLinks, staticDetails, apiUrl, baseUrl, mainContent, title, description) {
23
+ const logo = "/api/logo";
24
+ const banner = this.generateBanner(routes, staticDetails.siteTitle || "My Personal Website", logo);
25
+ const footer = this.generateFooter(footerLinks, staticDetails.copyright || "2026 My Personal Website");
26
+ const canonicalUrl = new URL(pathname, baseUrl).toString();
27
+ const content = this.wrapContent(banner, mainContent, footer);
28
+ return {
29
+ title,
30
+ description,
31
+ canonicalUrl,
32
+ content
33
+ };
34
+ }
35
+ }
36
+ class HomePageGenerator extends BasePageGenerator {
37
+ generate(data) {
38
+ const { profile, homeContent, latestBlogs, latestStories, staticDetails, ...baseData } = data;
39
+ const name = profile?.name || "User";
40
+ const title = profile?.title || "Professional";
41
+ const homeHtml = homeContent || `<h1>Welcome to ${name}</h1><p>Upload home.md to customize this page.</p>`;
42
+ const blogGists = latestBlogs.map((b) => `<div class="gist-card"><a href="/blogs/${b.slug}"><h4>${b.title}</h4></a><p>${b.summary}</p><small>${b.date}</small></div>`).join("");
43
+ const storyGists = latestStories.map((s) => `<div class="gist-card"><a href="/stories/${s.slug}"><h4>${s.title}</h4></a><p>${s.summary}</p><small>${s.date}</small></div>`).join("");
44
+ const mainContent = `
45
+ <main class="container container-wide column-layout">
46
+ <div class="main-column">
47
+ ${homeHtml}
48
+ </div>
49
+ <div class="sidebar-column">
50
+ <h3>Recent Blogs</h3>
51
+ ${blogGists || "<p>No blogs yet.</p>"}
52
+ <h3 class="mt-2">Recent Stories</h3>
53
+ ${storyGists || "<p>No stories yet.</p>"}
54
+ </div>
55
+ </main>`;
56
+ return this.generatePage(
57
+ baseData.pathname,
58
+ baseData.routes,
59
+ baseData.footerLinks,
60
+ staticDetails,
61
+ baseData.apiUrl,
62
+ baseData.baseUrl,
63
+ mainContent,
64
+ `${name} – ${title}`,
65
+ `Welcome to ${name}'s personal website. Professional portfolio and content.`
66
+ );
67
+ }
68
+ }
69
+ class AboutPageGenerator extends BasePageGenerator {
70
+ generate(data) {
71
+ const { profile, staticDetails, ...baseData } = data;
72
+ const name = profile?.name || "User";
73
+ const mainContent = `
74
+ <main class="container container-narrow">
75
+ <my-aboutme base-url="${baseData.apiUrl}"></my-aboutme>
76
+ </main>`;
77
+ return this.generatePage(
78
+ baseData.pathname,
79
+ baseData.routes,
80
+ baseData.footerLinks,
81
+ staticDetails,
82
+ baseData.apiUrl,
83
+ baseData.baseUrl,
84
+ mainContent,
85
+ `About - ${name}`,
86
+ `Learn more about ${name}'s experience and skills.`
87
+ );
88
+ }
89
+ }
90
+ class BlogsListPageGenerator extends BasePageGenerator {
91
+ generate(data) {
92
+ const { latestBlogs, name, staticDetails, ...baseData } = data;
93
+ const blogGists = latestBlogs.map((b) => `<div class="gist-card"><a href="/blogs/${b.slug}"><h4>${b.title}</h4></a><p>${b.summary}</p><small>${b.date}</small></div>`).join("");
94
+ const mainContent = `
95
+ <main class="container container-wide">
96
+ <h1>Blogs</h1>
97
+ <input type="text" placeholder="Search blogs..." class="search-input" />
98
+ <div class="blog-list">
99
+ ${blogGists || "<p>No blogs yet.</p>"}
100
+ </div>
101
+ </main>`;
102
+ return this.generatePage(
103
+ baseData.pathname,
104
+ baseData.routes,
105
+ baseData.footerLinks,
106
+ staticDetails,
107
+ baseData.apiUrl,
108
+ baseData.baseUrl,
109
+ mainContent,
110
+ `Blogs – ${name}`,
111
+ "Read the latest blog posts."
112
+ );
113
+ }
114
+ }
115
+ class StoriesListPageGenerator extends BasePageGenerator {
116
+ generate(data) {
117
+ const { latestStories, name, staticDetails, ...baseData } = data;
118
+ const storyGists = latestStories.map((s) => `<div class="gist-card"><a href="/stories/${s.slug}"><h4>${s.title}</h4></a><p>${s.summary}</p><small>${s.date}</small></div>`).join("");
119
+ const mainContent = `
120
+ <main class="container container-wide">
121
+ <h1>Stories</h1>
122
+ <input type="text" placeholder="Search stories..." class="search-input" />
123
+ <div class="story-list">
124
+ ${storyGists || "<p>No stories yet.</p>"}
125
+ </div>
126
+ </main>`;
127
+ return this.generatePage(
128
+ baseData.pathname,
129
+ baseData.routes,
130
+ baseData.footerLinks,
131
+ staticDetails,
132
+ baseData.apiUrl,
133
+ baseData.baseUrl,
134
+ mainContent,
135
+ `Stories – ${name}`,
136
+ "Read the latest stories."
137
+ );
138
+ }
139
+ }
140
+ class BlogDetailPageGenerator extends BasePageGenerator {
141
+ generate(data) {
142
+ const { slug, staticDetails, ...baseData } = data;
143
+ const mainContent = `
144
+ <main class="container container-narrow">
145
+ <my-blog-viewer slug="${slug}"></my-blog-viewer>
146
+ </main>`;
147
+ return this.generatePage(
148
+ baseData.pathname,
149
+ baseData.routes,
150
+ baseData.footerLinks,
151
+ staticDetails,
152
+ baseData.apiUrl,
153
+ baseData.baseUrl,
154
+ mainContent,
155
+ `Blog: ${slug}`,
156
+ "Blog post"
157
+ );
158
+ }
159
+ }
160
+ class StoryDetailPageGenerator extends BasePageGenerator {
161
+ generate(data) {
162
+ const { slug, staticDetails, ...baseData } = data;
163
+ const mainContent = `
164
+ <main class="container container-narrow">
165
+ <my-story-viewer slug="${slug}"></my-story-viewer>
166
+ </main>`;
167
+ return this.generatePage(
168
+ baseData.pathname,
169
+ baseData.routes,
170
+ baseData.footerLinks,
171
+ staticDetails,
172
+ baseData.apiUrl,
173
+ baseData.baseUrl,
174
+ mainContent,
175
+ `Story: ${slug}`,
176
+ "Story post"
177
+ );
178
+ }
179
+ }
180
+ class NotFoundPageGenerator extends BasePageGenerator {
181
+ generate(data) {
182
+ const { staticDetails, ...baseData } = data;
183
+ const mainContent = `
184
+ <main class="container container-narrow text-center">
185
+ <h1>Page Not Found</h1>
186
+ <p>The page you're looking for doesn't exist.</p>
187
+ <p><a href="/">Return to home</a></p>
188
+ </main>`;
189
+ return this.generatePage(
190
+ baseData.pathname,
191
+ baseData.routes,
192
+ baseData.footerLinks,
193
+ staticDetails,
194
+ baseData.apiUrl,
195
+ baseData.baseUrl,
196
+ mainContent,
197
+ "404 Not Found",
198
+ "The page you requested could not be found."
199
+ );
200
+ }
201
+ }
202
+ export {
203
+ AboutPageGenerator,
204
+ BasePageGenerator,
205
+ BlogDetailPageGenerator,
206
+ BlogsListPageGenerator,
207
+ HomePageGenerator,
208
+ NotFoundPageGenerator,
209
+ StoriesListPageGenerator,
210
+ StoryDetailPageGenerator
211
+ };