@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.
- package/README.md +94 -17
- package/dist/api/content-utils.d.ts +27 -0
- package/dist/api/content-utils.d.ts.map +1 -0
- package/dist/api/handlers/about-me.d.ts.map +1 -1
- package/dist/api/handlers/auth-handler.d.ts +2 -0
- package/dist/api/handlers/auth-handler.d.ts.map +1 -0
- package/dist/api/handlers/auth.d.ts +23 -0
- package/dist/api/handlers/auth.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/handlers/content.d.ts.map +1 -1
- package/dist/api/handlers/home.d.ts.map +1 -1
- package/dist/api/handlers/static-details.d.ts +1 -1
- package/dist/api/handlers/static-details.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/website-api.d.ts +1 -1
- package/dist/api/website-api.d.ts.map +1 -1
- package/dist/api.js +17 -2
- package/dist/assets/logo-placeholder.svg +21 -0
- package/dist/chunks/index-C1krnvU3.js +211 -0
- package/dist/chunks/index-DrnbjP2Q.js +2715 -0
- package/dist/chunks/site-store-CGV9c2DI.js +89 -0
- package/dist/chunks/{template-gGTkeOcA.js → template-DVy2k_na.js} +128 -90
- package/dist/chunks/website-api-CFRUPu0X.js +958 -0
- package/dist/index.js +42 -14
- package/dist/prerender/data-fetcher.d.ts +19 -0
- package/dist/prerender/data-fetcher.d.ts.map +1 -0
- package/dist/prerender/page-content.d.ts.map +1 -1
- package/dist/prerender/page-generators/about.d.ts +16 -0
- package/dist/prerender/page-generators/about.d.ts.map +1 -0
- package/dist/prerender/page-generators/base.d.ts +25 -0
- package/dist/prerender/page-generators/base.d.ts.map +1 -0
- package/dist/prerender/page-generators/blog-detail.d.ts +15 -0
- package/dist/prerender/page-generators/blog-detail.d.ts.map +1 -0
- package/dist/prerender/page-generators/blogs-list.d.ts +17 -0
- package/dist/prerender/page-generators/blogs-list.d.ts.map +1 -0
- package/dist/prerender/page-generators/home.d.ts +19 -0
- package/dist/prerender/page-generators/home.d.ts.map +1 -0
- package/dist/prerender/page-generators/index.d.ts +9 -0
- package/dist/prerender/page-generators/index.d.ts.map +1 -0
- package/dist/prerender/page-generators/not-found.d.ts +14 -0
- package/dist/prerender/page-generators/not-found.d.ts.map +1 -0
- package/dist/prerender/page-generators/stories-list.d.ts +17 -0
- package/dist/prerender/page-generators/stories-list.d.ts.map +1 -0
- package/dist/prerender/page-generators/story-detail.d.ts +15 -0
- package/dist/prerender/page-generators/story-detail.d.ts.map +1 -0
- package/dist/prerender/template.d.ts +3 -1
- package/dist/prerender/template.d.ts.map +1 -1
- package/dist/prerender/website-prerender.d.ts +6 -0
- package/dist/prerender/website-prerender.d.ts.map +1 -1
- package/dist/prerender.js +291 -145
- package/dist/shared/config/index.d.ts +1 -0
- package/dist/shared/config/index.d.ts.map +1 -1
- package/dist/shared/core/site-store.d.ts +1 -0
- package/dist/shared/core/site-store.d.ts.map +1 -1
- package/dist/shared/core/theme-toggle.d.ts.map +1 -1
- package/dist/shared/page-content.d.ts.map +1 -1
- package/dist/shared/router.d.ts +9 -3
- package/dist/shared/router.d.ts.map +1 -1
- package/dist/shared/website-ui.d.ts +23 -0
- package/dist/shared/website-ui.d.ts.map +1 -1
- package/dist/shared.js +6 -4
- package/dist/ui/about-me/index.d.ts +2 -10
- package/dist/ui/about-me/index.d.ts.map +1 -1
- package/dist/ui/about-me/styles.d.ts.map +1 -1
- package/dist/ui/admin/api.d.ts +16 -0
- package/dist/ui/admin/api.d.ts.map +1 -0
- package/dist/ui/admin/components/AboutMeSection.d.ts +7 -0
- package/dist/ui/admin/components/AboutMeSection.d.ts.map +1 -0
- package/dist/ui/admin/components/AdminSection.d.ts +13 -0
- package/dist/ui/admin/components/AdminSection.d.ts.map +1 -0
- package/dist/ui/admin/components/BlogsSection.d.ts +7 -0
- package/dist/ui/admin/components/BlogsSection.d.ts.map +1 -0
- package/dist/ui/admin/components/HomeSection.d.ts +7 -0
- package/dist/ui/admin/components/HomeSection.d.ts.map +1 -0
- package/dist/ui/admin/components/ImagesSection.d.ts +7 -0
- package/dist/ui/admin/components/ImagesSection.d.ts.map +1 -0
- package/dist/ui/admin/components/LoginForm.d.ts +9 -0
- package/dist/ui/admin/components/LoginForm.d.ts.map +1 -0
- package/dist/ui/admin/components/LogoSection.d.ts +7 -0
- package/dist/ui/admin/components/LogoSection.d.ts.map +1 -0
- package/dist/ui/admin/components/ProfileSection.d.ts +7 -0
- package/dist/ui/admin/components/ProfileSection.d.ts.map +1 -0
- package/dist/ui/admin/components/StaticSection.d.ts +9 -0
- package/dist/ui/admin/components/StaticSection.d.ts.map +1 -0
- package/dist/ui/admin/components/StoriesSection.d.ts +7 -0
- package/dist/ui/admin/components/StoriesSection.d.ts.map +1 -0
- package/dist/ui/admin/components/index.d.ts +11 -0
- package/dist/ui/admin/components/index.d.ts.map +1 -0
- package/dist/ui/admin/index.d.ts +27 -26
- package/dist/ui/admin/index.d.ts.map +1 -1
- package/dist/ui/admin/styles.d.ts.map +1 -1
- package/dist/ui/admin/types.d.ts +24 -0
- package/dist/ui/admin/types.d.ts.map +1 -0
- package/dist/ui/banner/index.d.ts.map +1 -1
- package/dist/ui/banner/styles.d.ts.map +1 -1
- package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts +2 -0
- package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts.map +1 -0
- package/dist/ui/blog-viewer/index.d.ts +25 -0
- package/dist/ui/blog-viewer/index.d.ts.map +1 -0
- package/dist/ui/blog-viewer/styles.d.ts +2 -0
- package/dist/ui/blog-viewer/styles.d.ts.map +1 -0
- package/dist/ui/footer/index.d.ts.map +1 -1
- package/dist/ui/footer/styles.d.ts.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts +2 -0
- package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts.map +1 -0
- package/dist/ui/story-viewer/index.d.ts +25 -0
- package/dist/ui/story-viewer/index.d.ts.map +1 -0
- package/dist/ui/story-viewer/styles.d.ts +2 -0
- package/dist/ui/story-viewer/styles.d.ts.map +1 -0
- package/dist/ui.js +15 -3
- package/package.json +37 -13
- package/public/assets/logo-placeholder.svg +21 -0
- package/dist/chunks/index-BqixlS-2.js +0 -1157
- package/dist/chunks/website-api-CVsi-OLc.js +0 -596
- package/dist/ui/about-me/renderer.d.ts +0 -5
- package/dist/ui/about-me/renderer.d.ts.map +0 -1
- 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 -99
- package/src/api/handlers/content-api.ts +0 -268
- package/src/api/handlers/content.ts +0 -72
- 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 -7
- package/src/api/utils.ts +0 -16
- package/src/api/website-api.ts +0 -124
- package/src/index.ts +0 -4
- package/src/prerender/__tests__/page-content.test.ts +0 -54
- package/src/prerender/__tests__/template.test.ts +0 -54
- package/src/prerender/index.ts +0 -7
- package/src/prerender/page-content.ts +0 -263
- 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 -41
- 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 -241
- 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 -155
- package/src/ui/about-me/renderer.ts +0 -7
- package/src/ui/about-me/styles.ts +0 -10
- package/src/ui/admin/index.ts +0 -492
- package/src/ui/admin/styles.ts +0 -317
- package/src/ui/banner/index.ts +0 -38
- package/src/ui/banner/styles.ts +0 -10
- package/src/ui/footer/index.ts +0 -37
- package/src/ui/footer/styles.ts +0 -9
- package/src/ui/index.ts +0 -4
- /package/{src/shared → dist}/styles/markdown.css +0 -0
- /package/{src → dist}/styles/theme.css +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const __vite_import_meta_env__ = {};
|
|
2
|
+
const DEFAULT_INFRA = {
|
|
3
|
+
baseUrl: typeof window !== "undefined" ? window.location.origin : "http://localhost:5173",
|
|
4
|
+
apiUrl: typeof window !== "undefined" && (window.__VITE_API_URL__ || __vite_import_meta_env__?.VITE_API_URL) || (typeof window !== "undefined" ? window.location.origin : "http://localhost:8787")
|
|
5
|
+
};
|
|
6
|
+
const DEFAULT_STATIC = {
|
|
7
|
+
siteTitle: "My Personal Website",
|
|
8
|
+
siteDescription: "My Personal Website",
|
|
9
|
+
copyright: "2026 My Personal Website",
|
|
10
|
+
linkedin: "https://linkedin.com/in/yourname",
|
|
11
|
+
github: "https://github.com/yourname",
|
|
12
|
+
email: "yourname@domain.com"
|
|
13
|
+
};
|
|
14
|
+
let activeConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
|
|
15
|
+
async function initializeConfig(infra) {
|
|
16
|
+
if (infra) {
|
|
17
|
+
if (infra.baseUrl) activeConfig.baseUrl = infra.baseUrl;
|
|
18
|
+
if (infra.apiUrl) activeConfig.apiUrl = infra.apiUrl;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(`${activeConfig.apiUrl}/api/static`);
|
|
22
|
+
if (res.ok) {
|
|
23
|
+
const remoteStatic = await res.json().catch(() => ({}));
|
|
24
|
+
const filteredStatic = Object.fromEntries(
|
|
25
|
+
Object.entries(remoteStatic).filter(([_, v]) => v != null && v !== "")
|
|
26
|
+
);
|
|
27
|
+
activeConfig = { ...activeConfig, ...filteredStatic };
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.warn("Failed to load static details from R2, using defaults.");
|
|
31
|
+
}
|
|
32
|
+
return activeConfig;
|
|
33
|
+
}
|
|
34
|
+
async function refreshConfig() {
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(`${activeConfig.apiUrl}/api/static`);
|
|
37
|
+
if (res.ok) {
|
|
38
|
+
const remoteStatic = await res.json().catch(() => ({}));
|
|
39
|
+
activeConfig = { ...activeConfig, ...remoteStatic };
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.warn("Failed to refresh static details.");
|
|
43
|
+
}
|
|
44
|
+
return activeConfig;
|
|
45
|
+
}
|
|
46
|
+
function getConfig() {
|
|
47
|
+
return activeConfig;
|
|
48
|
+
}
|
|
49
|
+
class SiteStore {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.config = null;
|
|
52
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
53
|
+
}
|
|
54
|
+
static getInstance() {
|
|
55
|
+
if (!SiteStore.instance) {
|
|
56
|
+
SiteStore.instance = new SiteStore();
|
|
57
|
+
}
|
|
58
|
+
return SiteStore.instance;
|
|
59
|
+
}
|
|
60
|
+
async init(infra) {
|
|
61
|
+
this.config = await initializeConfig(infra);
|
|
62
|
+
this.notify();
|
|
63
|
+
return this.config;
|
|
64
|
+
}
|
|
65
|
+
subscribe(listener) {
|
|
66
|
+
this.listeners.add(listener);
|
|
67
|
+
if (this.config) listener(this.config);
|
|
68
|
+
return () => this.listeners.delete(listener);
|
|
69
|
+
}
|
|
70
|
+
notify() {
|
|
71
|
+
if (this.config) {
|
|
72
|
+
this.listeners.forEach((l) => l(this.config));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async refresh() {
|
|
76
|
+
await refreshConfig();
|
|
77
|
+
this.config = getConfig();
|
|
78
|
+
this.notify();
|
|
79
|
+
}
|
|
80
|
+
getConfig() {
|
|
81
|
+
return this.config || getConfig();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
SiteStore as S,
|
|
86
|
+
getConfig as g,
|
|
87
|
+
initializeConfig as i,
|
|
88
|
+
refreshConfig as r
|
|
89
|
+
};
|
|
@@ -1,70 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
apiUrl: typeof window !== "undefined" && window.__VITE_API_URL__ || "http://localhost:8787"
|
|
5
|
-
};
|
|
6
|
-
const DEFAULT_STATIC = {
|
|
7
|
-
siteTitle: "My Personal Website",
|
|
8
|
-
siteDescription: "My Personal Website",
|
|
9
|
-
copyright: "2026 My Personal Website",
|
|
10
|
-
linkedin: "https://linkedin.com/in/yourname",
|
|
11
|
-
github: "https://github.com/yourname",
|
|
12
|
-
email: "yourname@domain.com"
|
|
13
|
-
};
|
|
14
|
-
let activeConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
|
|
15
|
-
async function initializeConfig(infra) {
|
|
16
|
-
if (infra) {
|
|
17
|
-
activeConfig = { ...activeConfig, ...infra };
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
const res = await fetch(`${activeConfig.apiUrl}/api/static`);
|
|
21
|
-
if (res.ok) {
|
|
22
|
-
const remoteStatic = await res.json();
|
|
23
|
-
activeConfig = { ...activeConfig, ...remoteStatic };
|
|
24
|
-
}
|
|
25
|
-
} catch (e) {
|
|
26
|
-
console.warn("Failed to load static details from R2, using defaults.");
|
|
27
|
-
}
|
|
28
|
-
return activeConfig;
|
|
29
|
-
}
|
|
30
|
-
function getConfig() {
|
|
31
|
-
return activeConfig;
|
|
32
|
-
}
|
|
33
|
-
class SiteStore {
|
|
34
|
-
constructor() {
|
|
35
|
-
this.config = null;
|
|
36
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
37
|
-
}
|
|
38
|
-
static getInstance() {
|
|
39
|
-
if (!SiteStore.instance) {
|
|
40
|
-
SiteStore.instance = new SiteStore();
|
|
41
|
-
}
|
|
42
|
-
return SiteStore.instance;
|
|
43
|
-
}
|
|
44
|
-
async init(infra) {
|
|
45
|
-
this.config = await initializeConfig(infra);
|
|
46
|
-
this.notify();
|
|
47
|
-
return this.config;
|
|
48
|
-
}
|
|
49
|
-
subscribe(listener) {
|
|
50
|
-
this.listeners.add(listener);
|
|
51
|
-
if (this.config) listener(this.config);
|
|
52
|
-
return () => this.listeners.delete(listener);
|
|
53
|
-
}
|
|
54
|
-
notify() {
|
|
55
|
-
if (this.config) {
|
|
56
|
-
this.listeners.forEach((l) => l(this.config));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
getConfig() {
|
|
60
|
-
return this.config || getConfig();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
import { S as SiteStore } from "./site-store-CGV9c2DI.js";
|
|
2
|
+
import { MarkdownPipeline } from "@leadertechie/r2tohtml";
|
|
3
|
+
import { init, reinit } from "@leadertechie/md2interact";
|
|
63
4
|
const pipeline = new MarkdownPipeline({
|
|
64
5
|
imagePathPrefix: "images/",
|
|
6
|
+
preserveRawHTML: true,
|
|
7
|
+
errorRecovery: "warn",
|
|
8
|
+
maxRecursionDepth: 50,
|
|
65
9
|
styleOptions: {
|
|
66
10
|
classPrefix: "md-",
|
|
67
|
-
addHeadingIds: true
|
|
11
|
+
addHeadingIds: true,
|
|
12
|
+
emitScopeAnchors: true
|
|
13
|
+
},
|
|
14
|
+
slotPattern: /\[\[(.*?)\]\]/g,
|
|
15
|
+
onSlot: (name) => {
|
|
16
|
+
const slotMap = {
|
|
17
|
+
"SITE_TITLE": document?.querySelector("title")?.textContent || "My Site",
|
|
18
|
+
"CURRENT_YEAR": (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
19
|
+
};
|
|
20
|
+
return slotMap[name] || `[[${name}]]`;
|
|
68
21
|
}
|
|
69
22
|
});
|
|
70
23
|
const renderMarkdown = (content) => {
|
|
@@ -91,12 +44,12 @@ const generatePageContent = (pathname, routes, footerLinks, data) => {
|
|
|
91
44
|
const footerTemplate = `
|
|
92
45
|
<my-footer
|
|
93
46
|
copyright="${copyright}"
|
|
94
|
-
|
|
47
|
+
footer-links='${JSON.stringify(footerLinks)}'>
|
|
95
48
|
</my-footer>`;
|
|
96
49
|
const renderContentGists = (items = [], title, type) => {
|
|
97
50
|
const listHtml = items.length > 0 ? items.map((item) => `
|
|
98
51
|
<div class="gist-card">
|
|
99
|
-
<a href="
|
|
52
|
+
<a href="/${type}/${item.slug}" data-route="${type}-${item.slug}"><h4>${item.title}</h4></a>
|
|
100
53
|
<p>${item.summary || item.description || ""}</p>
|
|
101
54
|
<small>${new Date(item.date).toLocaleDateString()}</small>
|
|
102
55
|
</div>
|
|
@@ -164,7 +117,7 @@ const generatePageContent = (pathname, routes, footerLinks, data) => {
|
|
|
164
117
|
</aside>
|
|
165
118
|
<div class="wide-main-column text-left">
|
|
166
119
|
<div id="content-viewer">
|
|
167
|
-
${currentSlug ? isBlog ? `<my-blog-viewer slug="${currentSlug}"></my-blog-viewer>` : `<my-story-viewer slug="${currentSlug}"></my-story-viewer>` : items && items.length > 0 ? `<p>Select a ${type.slice(0, -1)} to read.</p>` : ""}
|
|
120
|
+
${currentSlug ? isBlog ? `<my-blog-viewer slug="${currentSlug}" api-url="${data?.apiUrl || ""}"></my-blog-viewer>` : `<my-story-viewer slug="${currentSlug}" api-url="${data?.apiUrl || ""}"></my-story-viewer>` : items && items.length > 0 ? `<p>Select a ${type.slice(0, -1)} to read.</p>` : ""}
|
|
168
121
|
</div>
|
|
169
122
|
</div>
|
|
170
123
|
</div>
|
|
@@ -220,20 +173,29 @@ class Router {
|
|
|
220
173
|
{ link: "/stories", text: "Stories" },
|
|
221
174
|
{ link: "/about-me", text: "About Me" }
|
|
222
175
|
];
|
|
223
|
-
this.siteTitle = config.siteTitle;
|
|
224
|
-
this.copyright = config.copyright;
|
|
225
176
|
this.apiUrl = config.apiUrl;
|
|
177
|
+
}
|
|
178
|
+
get config() {
|
|
179
|
+
return this.ui.getStore().getConfig();
|
|
180
|
+
}
|
|
181
|
+
get siteTitle() {
|
|
182
|
+
return this.config.siteTitle;
|
|
183
|
+
}
|
|
184
|
+
get copyright() {
|
|
185
|
+
return this.config.copyright;
|
|
186
|
+
}
|
|
187
|
+
get footerLinks() {
|
|
226
188
|
const normalizeUrl = (url) => {
|
|
227
189
|
if (!url) return "";
|
|
228
190
|
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
229
191
|
if (url.startsWith("www.")) return `https://${url}`;
|
|
230
192
|
return url;
|
|
231
193
|
};
|
|
232
|
-
|
|
233
|
-
{ text: "LinkedIn", link: normalizeUrl(config.linkedin) },
|
|
234
|
-
{ text: "GitHub", link: normalizeUrl(config.github) },
|
|
235
|
-
{ text: "Email", link: config.email ? `mailto:${config.email}` : "" }
|
|
236
|
-
]
|
|
194
|
+
return [
|
|
195
|
+
{ text: "LinkedIn", link: normalizeUrl(this.config.linkedin) || "https://linkedin.com" },
|
|
196
|
+
{ text: "GitHub", link: normalizeUrl(this.config.github) || "https://github.com" },
|
|
197
|
+
{ text: "Email", link: this.config.email ? `mailto:${this.config.email}` : "mailto:hello@example.com" }
|
|
198
|
+
];
|
|
237
199
|
}
|
|
238
200
|
init(appElementId = "app") {
|
|
239
201
|
this.appElement = document.getElementById(appElementId);
|
|
@@ -328,6 +290,13 @@ class Router {
|
|
|
328
290
|
}
|
|
329
291
|
canonicalLink.setAttribute("href", url);
|
|
330
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* After rendering new content, reinitialize md2interact so it
|
|
295
|
+
* re-scans the DOM for interaction elements (poll, live-update, etc.)
|
|
296
|
+
*/
|
|
297
|
+
afterRender() {
|
|
298
|
+
this.ui.reinitInteract();
|
|
299
|
+
}
|
|
331
300
|
async renderHomePage() {
|
|
332
301
|
let blogs = [];
|
|
333
302
|
let stories = [];
|
|
@@ -338,9 +307,18 @@ class Router {
|
|
|
338
307
|
fetch(`${this.apiUrl}/api/stories`),
|
|
339
308
|
fetch(`${this.apiUrl}/api/home`)
|
|
340
309
|
]);
|
|
341
|
-
if (blogsRes.ok)
|
|
342
|
-
|
|
343
|
-
|
|
310
|
+
if (blogsRes.ok) {
|
|
311
|
+
const data = await blogsRes.json().catch(() => []);
|
|
312
|
+
blogs = Array.isArray(data) ? data.slice(0, 3) : [];
|
|
313
|
+
}
|
|
314
|
+
if (storiesRes.ok) {
|
|
315
|
+
const data = await storiesRes.json().catch(() => []);
|
|
316
|
+
stories = Array.isArray(data) ? data.slice(0, 3) : [];
|
|
317
|
+
}
|
|
318
|
+
if (homeRes.ok) {
|
|
319
|
+
const data = await homeRes.json().catch(() => ({}));
|
|
320
|
+
homeContent = data.content || "";
|
|
321
|
+
}
|
|
344
322
|
} catch (e) {
|
|
345
323
|
console.error("Failed to fetch home content", e);
|
|
346
324
|
}
|
|
@@ -353,6 +331,7 @@ class Router {
|
|
|
353
331
|
});
|
|
354
332
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
355
333
|
this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
|
|
334
|
+
this.afterRender();
|
|
356
335
|
}
|
|
357
336
|
renderAboutMePage() {
|
|
358
337
|
const pageContent = generatePageContent("/about-me", this.routes, this.footerLinks, {
|
|
@@ -362,6 +341,7 @@ class Router {
|
|
|
362
341
|
});
|
|
363
342
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
364
343
|
this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
|
|
344
|
+
this.afterRender();
|
|
365
345
|
}
|
|
366
346
|
async renderContentListPage(pathname) {
|
|
367
347
|
const type = pathname === "/blogs" ? "blogs" : "stories";
|
|
@@ -372,7 +352,7 @@ class Router {
|
|
|
372
352
|
} catch (e) {
|
|
373
353
|
}
|
|
374
354
|
const latestSlug = items.length > 0 ? items[0].slug : void 0;
|
|
375
|
-
const data = type === "blogs" ? { blogs: items, slug: latestSlug } : { stories: items, slug: latestSlug };
|
|
355
|
+
const data = type === "blogs" ? { blogs: items, slug: latestSlug, apiUrl: this.apiUrl } : { stories: items, slug: latestSlug, apiUrl: this.apiUrl };
|
|
376
356
|
const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
|
|
377
357
|
...data,
|
|
378
358
|
siteTitle: this.siteTitle,
|
|
@@ -380,6 +360,7 @@ class Router {
|
|
|
380
360
|
});
|
|
381
361
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
382
362
|
this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
|
|
363
|
+
this.afterRender();
|
|
383
364
|
}
|
|
384
365
|
async renderContentDetailPage(pathname) {
|
|
385
366
|
const isBlog = pathname.startsWith("/blogs/");
|
|
@@ -391,7 +372,7 @@ class Router {
|
|
|
391
372
|
if (res.ok) items = await res.json();
|
|
392
373
|
} catch (e) {
|
|
393
374
|
}
|
|
394
|
-
const data = type === "blogs" ? { blogs: items, slug } : { stories: items, slug };
|
|
375
|
+
const data = type === "blogs" ? { blogs: items, slug, apiUrl: this.apiUrl } : { stories: items, slug, apiUrl: this.apiUrl };
|
|
395
376
|
const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
|
|
396
377
|
...data,
|
|
397
378
|
siteTitle: this.siteTitle,
|
|
@@ -399,12 +380,9 @@ class Router {
|
|
|
399
380
|
});
|
|
400
381
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
401
382
|
this.setPageMeta(`${slug.replace(/-/g, " ")} - ${this.siteTitle}`, "Read more content", window.location.href);
|
|
383
|
+
this.afterRender();
|
|
402
384
|
}
|
|
403
385
|
async renderAdminPage() {
|
|
404
|
-
generatePageContent("/admin", this.routes, this.footerLinks, {
|
|
405
|
-
siteTitle: this.siteTitle,
|
|
406
|
-
copyright: this.copyright
|
|
407
|
-
});
|
|
408
386
|
if (this.appElement) {
|
|
409
387
|
this.appElement.innerHTML = `
|
|
410
388
|
<my-banner header="${this.siteTitle}" logo="${this.logo}">
|
|
@@ -416,14 +394,16 @@ class Router {
|
|
|
416
394
|
<main class="container container-medium">
|
|
417
395
|
<admin-portal></admin-portal>
|
|
418
396
|
</main>
|
|
419
|
-
<my-footer copyright="${this.copyright}"
|
|
397
|
+
<my-footer copyright="${this.copyright}" footerLinks='${JSON.stringify(this.footerLinks)}'></my-footer>
|
|
420
398
|
`;
|
|
421
399
|
}
|
|
400
|
+
this.afterRender();
|
|
422
401
|
}
|
|
423
402
|
}
|
|
424
403
|
class WebsiteUI {
|
|
425
404
|
constructor(config = {}) {
|
|
426
405
|
this.router = null;
|
|
406
|
+
this.interactInitialized = false;
|
|
427
407
|
this.store = SiteStore.getInstance();
|
|
428
408
|
this.config = config;
|
|
429
409
|
}
|
|
@@ -440,14 +420,64 @@ class WebsiteUI {
|
|
|
440
420
|
apiUrl: this.config.apiUrl,
|
|
441
421
|
baseUrl: this.config.baseUrl
|
|
442
422
|
});
|
|
423
|
+
this.store.subscribe((config) => {
|
|
424
|
+
this.applyTheme();
|
|
425
|
+
this.updateFavicon();
|
|
426
|
+
this.updateBanner(config);
|
|
427
|
+
if (this.router && window.location.pathname !== "/admin") {
|
|
428
|
+
this.router.navigate(window.location.pathname);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
443
431
|
this.applyTheme();
|
|
432
|
+
this.updateFavicon();
|
|
433
|
+
this.updateBanner(this.store.getConfig());
|
|
444
434
|
this.router = new Router(this);
|
|
445
435
|
this.router.init(this.config.appElementId || "app");
|
|
436
|
+
this.initInteract();
|
|
446
437
|
if (this.config.onBootstrap) {
|
|
447
438
|
await this.config.onBootstrap(this);
|
|
448
439
|
}
|
|
449
440
|
console.log("WebsiteUI bootstrapped");
|
|
450
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Initialize md2interact for client-side DOM interactions,
|
|
444
|
+
* CSS hydration, and event bus communication.
|
|
445
|
+
*/
|
|
446
|
+
initInteract() {
|
|
447
|
+
if (this.interactInitialized) return;
|
|
448
|
+
const interactCfg = this.config.interactConfig || {};
|
|
449
|
+
init({
|
|
450
|
+
interactions: interactCfg.interactions || {
|
|
451
|
+
"poll": { selector: '[data-interact="poll"]' },
|
|
452
|
+
"live-update": { selector: '[data-interact="live-update"]' },
|
|
453
|
+
"click-toggle": { selector: '[data-interact="click-toggle"]' },
|
|
454
|
+
"infinite-scroll": { selector: '[data-interact="infinite-scroll"]' },
|
|
455
|
+
"form-live": { selector: '[data-interact="form-live"]' }
|
|
456
|
+
},
|
|
457
|
+
cssHydration: interactCfg.cssHydration || {
|
|
458
|
+
inlineCritical: true,
|
|
459
|
+
layerInjection: true,
|
|
460
|
+
themeToggle: true
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
this.interactInitialized = true;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Reinitialize md2interact after dynamic content changes (e.g., SPA navigation).
|
|
467
|
+
* This re-scans the DOM for new interaction elements.
|
|
468
|
+
*/
|
|
469
|
+
reinitInteract() {
|
|
470
|
+
if (this.interactInitialized) {
|
|
471
|
+
reinit();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
updateBanner(config) {
|
|
475
|
+
const banner = document.querySelector("my-banner");
|
|
476
|
+
if (banner) {
|
|
477
|
+
banner.setAttribute("header", config.siteTitle || "My Site");
|
|
478
|
+
banner.setAttribute("logo", `${config.apiUrl}/api/logo?t=${Date.now()}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
451
481
|
applyTheme() {
|
|
452
482
|
if (!this.config.theme) return;
|
|
453
483
|
const { theme } = this.config;
|
|
@@ -461,6 +491,18 @@ class WebsiteUI {
|
|
|
461
491
|
document.head.appendChild(style);
|
|
462
492
|
}
|
|
463
493
|
}
|
|
494
|
+
updateFavicon() {
|
|
495
|
+
const favicon = document.querySelector('link[rel="icon"]');
|
|
496
|
+
const logoUrl = `/api/logo?t=${Date.now()}`;
|
|
497
|
+
if (favicon) {
|
|
498
|
+
favicon.setAttribute("href", logoUrl);
|
|
499
|
+
} else {
|
|
500
|
+
const newFavicon = document.createElement("link");
|
|
501
|
+
newFavicon.rel = "icon";
|
|
502
|
+
newFavicon.href = logoUrl;
|
|
503
|
+
document.head.appendChild(newFavicon);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
464
506
|
getStore() {
|
|
465
507
|
return this.store;
|
|
466
508
|
}
|
|
@@ -506,13 +548,12 @@ class ThemeToggle extends HTMLElement {
|
|
|
506
548
|
cursor: pointer;
|
|
507
549
|
font-size: 1.5rem;
|
|
508
550
|
padding: 0.5rem;
|
|
509
|
-
color: var(--text-color, #213547);
|
|
551
|
+
color: var(--text-color, #213547);
|
|
510
552
|
transition: color 0.3s ease;
|
|
511
553
|
}
|
|
512
554
|
button:hover {
|
|
513
|
-
color: var(--primary-color, #747bff);
|
|
555
|
+
color: var(--primary-color, #747bff);
|
|
514
556
|
}
|
|
515
|
-
/* Dark mode specific styles for the button */
|
|
516
557
|
html[data-theme='dark'] button {
|
|
517
558
|
color: var(--dark-mode-text-color, rgba(255, 255, 255, 0.87));
|
|
518
559
|
}
|
|
@@ -521,7 +562,7 @@ class ThemeToggle extends HTMLElement {
|
|
|
521
562
|
}
|
|
522
563
|
</style>
|
|
523
564
|
<button id="theme-toggle" aria-label="Toggle theme">
|
|
524
|
-
<span class="icon">${this.getSunIcon()}</span>
|
|
565
|
+
<span class="icon">${this.getSunIcon()}</span>
|
|
525
566
|
</button>
|
|
526
567
|
`;
|
|
527
568
|
}
|
|
@@ -610,13 +651,10 @@ const createHtmlTemplate = ({
|
|
|
610
651
|
};
|
|
611
652
|
export {
|
|
612
653
|
Router as R,
|
|
613
|
-
SiteStore as S,
|
|
614
654
|
ThemeToggle as T,
|
|
615
655
|
WebsiteUI as W,
|
|
616
|
-
getConfig as a,
|
|
617
656
|
bootstrap as b,
|
|
618
657
|
createHtmlTemplate as c,
|
|
619
658
|
generatePageContent as g,
|
|
620
|
-
initializeConfig as i,
|
|
621
659
|
renderMarkdown as r
|
|
622
660
|
};
|