@leadertechie/personal-site-kit 0.1.0-alpha.2 → 0.1.0-alpha.21
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-DsRjL9Uy.js +2727 -0
- package/dist/chunks/site-store-CGV9c2DI.js +89 -0
- package/dist/chunks/{template-gGTkeOcA.js → template-Lmx7Dxoc.js} +132 -91
- 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);
|
|
@@ -246,7 +208,10 @@ class Router {
|
|
|
246
208
|
}
|
|
247
209
|
setupEventListeners() {
|
|
248
210
|
document.body.addEventListener("click", (event) => {
|
|
249
|
-
const
|
|
211
|
+
const path = event.composedPath();
|
|
212
|
+
const target = path.find(
|
|
213
|
+
(el) => el instanceof HTMLElement && (el.matches("a[data-route]") || el.closest("a[data-route]"))
|
|
214
|
+
);
|
|
250
215
|
if (target) {
|
|
251
216
|
event.preventDefault();
|
|
252
217
|
const route = target.getAttribute("href");
|
|
@@ -328,6 +293,13 @@ class Router {
|
|
|
328
293
|
}
|
|
329
294
|
canonicalLink.setAttribute("href", url);
|
|
330
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* After rendering new content, reinitialize md2interact so it
|
|
298
|
+
* re-scans the DOM for interaction elements (poll, live-update, etc.)
|
|
299
|
+
*/
|
|
300
|
+
afterRender() {
|
|
301
|
+
this.ui.reinitInteract();
|
|
302
|
+
}
|
|
331
303
|
async renderHomePage() {
|
|
332
304
|
let blogs = [];
|
|
333
305
|
let stories = [];
|
|
@@ -338,9 +310,18 @@ class Router {
|
|
|
338
310
|
fetch(`${this.apiUrl}/api/stories`),
|
|
339
311
|
fetch(`${this.apiUrl}/api/home`)
|
|
340
312
|
]);
|
|
341
|
-
if (blogsRes.ok)
|
|
342
|
-
|
|
343
|
-
|
|
313
|
+
if (blogsRes.ok) {
|
|
314
|
+
const data = await blogsRes.json().catch(() => []);
|
|
315
|
+
blogs = Array.isArray(data) ? data.slice(0, 3) : [];
|
|
316
|
+
}
|
|
317
|
+
if (storiesRes.ok) {
|
|
318
|
+
const data = await storiesRes.json().catch(() => []);
|
|
319
|
+
stories = Array.isArray(data) ? data.slice(0, 3) : [];
|
|
320
|
+
}
|
|
321
|
+
if (homeRes.ok) {
|
|
322
|
+
const data = await homeRes.json().catch(() => ({}));
|
|
323
|
+
homeContent = data.content || "";
|
|
324
|
+
}
|
|
344
325
|
} catch (e) {
|
|
345
326
|
console.error("Failed to fetch home content", e);
|
|
346
327
|
}
|
|
@@ -353,6 +334,7 @@ class Router {
|
|
|
353
334
|
});
|
|
354
335
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
355
336
|
this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
|
|
337
|
+
this.afterRender();
|
|
356
338
|
}
|
|
357
339
|
renderAboutMePage() {
|
|
358
340
|
const pageContent = generatePageContent("/about-me", this.routes, this.footerLinks, {
|
|
@@ -362,6 +344,7 @@ class Router {
|
|
|
362
344
|
});
|
|
363
345
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
364
346
|
this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
|
|
347
|
+
this.afterRender();
|
|
365
348
|
}
|
|
366
349
|
async renderContentListPage(pathname) {
|
|
367
350
|
const type = pathname === "/blogs" ? "blogs" : "stories";
|
|
@@ -372,7 +355,7 @@ class Router {
|
|
|
372
355
|
} catch (e) {
|
|
373
356
|
}
|
|
374
357
|
const latestSlug = items.length > 0 ? items[0].slug : void 0;
|
|
375
|
-
const data = type === "blogs" ? { blogs: items, slug: latestSlug } : { stories: items, slug: latestSlug };
|
|
358
|
+
const data = type === "blogs" ? { blogs: items, slug: latestSlug, apiUrl: this.apiUrl } : { stories: items, slug: latestSlug, apiUrl: this.apiUrl };
|
|
376
359
|
const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
|
|
377
360
|
...data,
|
|
378
361
|
siteTitle: this.siteTitle,
|
|
@@ -380,6 +363,7 @@ class Router {
|
|
|
380
363
|
});
|
|
381
364
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
382
365
|
this.setPageMeta(pageContent.title, pageContent.description, pageContent.canonicalUrl);
|
|
366
|
+
this.afterRender();
|
|
383
367
|
}
|
|
384
368
|
async renderContentDetailPage(pathname) {
|
|
385
369
|
const isBlog = pathname.startsWith("/blogs/");
|
|
@@ -391,7 +375,7 @@ class Router {
|
|
|
391
375
|
if (res.ok) items = await res.json();
|
|
392
376
|
} catch (e) {
|
|
393
377
|
}
|
|
394
|
-
const data = type === "blogs" ? { blogs: items, slug } : { stories: items, slug };
|
|
378
|
+
const data = type === "blogs" ? { blogs: items, slug, apiUrl: this.apiUrl } : { stories: items, slug, apiUrl: this.apiUrl };
|
|
395
379
|
const pageContent = generatePageContent(pathname, this.routes, this.footerLinks, {
|
|
396
380
|
...data,
|
|
397
381
|
siteTitle: this.siteTitle,
|
|
@@ -399,12 +383,9 @@ class Router {
|
|
|
399
383
|
});
|
|
400
384
|
if (this.appElement) this.appElement.innerHTML = pageContent.content;
|
|
401
385
|
this.setPageMeta(`${slug.replace(/-/g, " ")} - ${this.siteTitle}`, "Read more content", window.location.href);
|
|
386
|
+
this.afterRender();
|
|
402
387
|
}
|
|
403
388
|
async renderAdminPage() {
|
|
404
|
-
generatePageContent("/admin", this.routes, this.footerLinks, {
|
|
405
|
-
siteTitle: this.siteTitle,
|
|
406
|
-
copyright: this.copyright
|
|
407
|
-
});
|
|
408
389
|
if (this.appElement) {
|
|
409
390
|
this.appElement.innerHTML = `
|
|
410
391
|
<my-banner header="${this.siteTitle}" logo="${this.logo}">
|
|
@@ -416,14 +397,16 @@ class Router {
|
|
|
416
397
|
<main class="container container-medium">
|
|
417
398
|
<admin-portal></admin-portal>
|
|
418
399
|
</main>
|
|
419
|
-
<my-footer copyright="${this.copyright}"
|
|
400
|
+
<my-footer copyright="${this.copyright}" footerLinks='${JSON.stringify(this.footerLinks)}'></my-footer>
|
|
420
401
|
`;
|
|
421
402
|
}
|
|
403
|
+
this.afterRender();
|
|
422
404
|
}
|
|
423
405
|
}
|
|
424
406
|
class WebsiteUI {
|
|
425
407
|
constructor(config = {}) {
|
|
426
408
|
this.router = null;
|
|
409
|
+
this.interactInitialized = false;
|
|
427
410
|
this.store = SiteStore.getInstance();
|
|
428
411
|
this.config = config;
|
|
429
412
|
}
|
|
@@ -440,14 +423,64 @@ class WebsiteUI {
|
|
|
440
423
|
apiUrl: this.config.apiUrl,
|
|
441
424
|
baseUrl: this.config.baseUrl
|
|
442
425
|
});
|
|
426
|
+
this.store.subscribe((config) => {
|
|
427
|
+
this.applyTheme();
|
|
428
|
+
this.updateFavicon();
|
|
429
|
+
this.updateBanner(config);
|
|
430
|
+
if (this.router && window.location.pathname !== "/admin") {
|
|
431
|
+
this.router.navigate(window.location.pathname);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
443
434
|
this.applyTheme();
|
|
435
|
+
this.updateFavicon();
|
|
436
|
+
this.updateBanner(this.store.getConfig());
|
|
444
437
|
this.router = new Router(this);
|
|
445
438
|
this.router.init(this.config.appElementId || "app");
|
|
439
|
+
this.initInteract();
|
|
446
440
|
if (this.config.onBootstrap) {
|
|
447
441
|
await this.config.onBootstrap(this);
|
|
448
442
|
}
|
|
449
443
|
console.log("WebsiteUI bootstrapped");
|
|
450
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Initialize md2interact for client-side DOM interactions,
|
|
447
|
+
* CSS hydration, and event bus communication.
|
|
448
|
+
*/
|
|
449
|
+
initInteract() {
|
|
450
|
+
if (this.interactInitialized) return;
|
|
451
|
+
const interactCfg = this.config.interactConfig || {};
|
|
452
|
+
init({
|
|
453
|
+
interactions: interactCfg.interactions || {
|
|
454
|
+
"poll": { selector: '[data-interact="poll"]' },
|
|
455
|
+
"live-update": { selector: '[data-interact="live-update"]' },
|
|
456
|
+
"click-toggle": { selector: '[data-interact="click-toggle"]' },
|
|
457
|
+
"infinite-scroll": { selector: '[data-interact="infinite-scroll"]' },
|
|
458
|
+
"form-live": { selector: '[data-interact="form-live"]' }
|
|
459
|
+
},
|
|
460
|
+
cssHydration: interactCfg.cssHydration || {
|
|
461
|
+
inlineCritical: true,
|
|
462
|
+
layerInjection: true,
|
|
463
|
+
themeToggle: true
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
this.interactInitialized = true;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Reinitialize md2interact after dynamic content changes (e.g., SPA navigation).
|
|
470
|
+
* This re-scans the DOM for new interaction elements.
|
|
471
|
+
*/
|
|
472
|
+
reinitInteract() {
|
|
473
|
+
if (this.interactInitialized) {
|
|
474
|
+
reinit();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
updateBanner(config) {
|
|
478
|
+
const banner = document.querySelector("my-banner");
|
|
479
|
+
if (banner) {
|
|
480
|
+
banner.setAttribute("header", config.siteTitle || "My Site");
|
|
481
|
+
banner.setAttribute("logo", `${config.apiUrl}/api/logo?t=${Date.now()}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
451
484
|
applyTheme() {
|
|
452
485
|
if (!this.config.theme) return;
|
|
453
486
|
const { theme } = this.config;
|
|
@@ -461,6 +494,18 @@ class WebsiteUI {
|
|
|
461
494
|
document.head.appendChild(style);
|
|
462
495
|
}
|
|
463
496
|
}
|
|
497
|
+
updateFavicon() {
|
|
498
|
+
const favicon = document.querySelector('link[rel="icon"]');
|
|
499
|
+
const logoUrl = `/api/logo?t=${Date.now()}`;
|
|
500
|
+
if (favicon) {
|
|
501
|
+
favicon.setAttribute("href", logoUrl);
|
|
502
|
+
} else {
|
|
503
|
+
const newFavicon = document.createElement("link");
|
|
504
|
+
newFavicon.rel = "icon";
|
|
505
|
+
newFavicon.href = logoUrl;
|
|
506
|
+
document.head.appendChild(newFavicon);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
464
509
|
getStore() {
|
|
465
510
|
return this.store;
|
|
466
511
|
}
|
|
@@ -506,13 +551,12 @@ class ThemeToggle extends HTMLElement {
|
|
|
506
551
|
cursor: pointer;
|
|
507
552
|
font-size: 1.5rem;
|
|
508
553
|
padding: 0.5rem;
|
|
509
|
-
color: var(--text-color, #213547);
|
|
554
|
+
color: var(--text-color, #213547);
|
|
510
555
|
transition: color 0.3s ease;
|
|
511
556
|
}
|
|
512
557
|
button:hover {
|
|
513
|
-
color: var(--primary-color, #747bff);
|
|
558
|
+
color: var(--primary-color, #747bff);
|
|
514
559
|
}
|
|
515
|
-
/* Dark mode specific styles for the button */
|
|
516
560
|
html[data-theme='dark'] button {
|
|
517
561
|
color: var(--dark-mode-text-color, rgba(255, 255, 255, 0.87));
|
|
518
562
|
}
|
|
@@ -521,7 +565,7 @@ class ThemeToggle extends HTMLElement {
|
|
|
521
565
|
}
|
|
522
566
|
</style>
|
|
523
567
|
<button id="theme-toggle" aria-label="Toggle theme">
|
|
524
|
-
<span class="icon">${this.getSunIcon()}</span>
|
|
568
|
+
<span class="icon">${this.getSunIcon()}</span>
|
|
525
569
|
</button>
|
|
526
570
|
`;
|
|
527
571
|
}
|
|
@@ -610,13 +654,10 @@ const createHtmlTemplate = ({
|
|
|
610
654
|
};
|
|
611
655
|
export {
|
|
612
656
|
Router as R,
|
|
613
|
-
SiteStore as S,
|
|
614
657
|
ThemeToggle as T,
|
|
615
658
|
WebsiteUI as W,
|
|
616
|
-
getConfig as a,
|
|
617
659
|
bootstrap as b,
|
|
618
660
|
createHtmlTemplate as c,
|
|
619
661
|
generatePageContent as g,
|
|
620
|
-
initializeConfig as i,
|
|
621
662
|
renderMarkdown as r
|
|
622
663
|
};
|