@leadertechie/personal-site-kit 0.0.0
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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/api/handlers/aboutme.d.ts +3 -0
- package/dist/api/handlers/aboutme.d.ts.map +1 -0
- package/dist/api/handlers/content-api.d.ts +5 -0
- package/dist/api/handlers/content-api.d.ts.map +1 -0
- package/dist/api/handlers/content.d.ts +2 -0
- package/dist/api/handlers/content.d.ts.map +1 -0
- package/dist/api/handlers/home.d.ts +3 -0
- package/dist/api/handlers/home.d.ts.map +1 -0
- package/dist/api/handlers/info.d.ts +2 -0
- package/dist/api/handlers/info.d.ts.map +1 -0
- package/dist/api/handlers/logo.d.ts +2 -0
- package/dist/api/handlers/logo.d.ts.map +1 -0
- package/dist/api/handlers/staticdetails.d.ts +2 -0
- package/dist/api/handlers/staticdetails.d.ts.map +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/utils.d.ts +8 -0
- package/dist/api/utils.d.ts.map +1 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +591 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +354 -0
- package/dist/prerender/index.d.ts +5 -0
- package/dist/prerender/index.d.ts.map +1 -0
- package/dist/prerender/pageContent.d.ts +16 -0
- package/dist/prerender/pageContent.d.ts.map +1 -0
- package/dist/prerender/prerender.d.ts +3 -0
- package/dist/prerender/prerender.d.ts.map +1 -0
- package/dist/prerender/template.d.ts +10 -0
- package/dist/prerender/template.d.ts.map +1 -0
- package/dist/prerender.d.ts +4 -0
- package/dist/prerender.js +399 -0
- package/dist/shared/config/api.d.ts +2 -0
- package/dist/shared/config/api.d.ts.map +1 -0
- package/dist/shared/config/index.d.ts +5 -0
- package/dist/shared/config/index.d.ts.map +1 -0
- package/dist/shared/config/types.d.ts +16 -0
- package/dist/shared/config/types.d.ts.map +1 -0
- package/dist/shared/core/site-store.d.ts +16 -0
- package/dist/shared/core/site-store.d.ts.map +1 -0
- package/dist/shared/core/theme-toggle.d.ts +13 -0
- package/dist/shared/core/theme-toggle.d.ts.map +1 -0
- package/dist/shared/index.d.ts +9 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/interfaces/iFooterLink.d.ts +5 -0
- package/dist/shared/interfaces/iFooterLink.d.ts.map +1 -0
- package/dist/shared/interfaces/iRoute.d.ts +5 -0
- package/dist/shared/interfaces/iRoute.d.ts.map +1 -0
- package/dist/shared/pageContent.d.ts +35 -0
- package/dist/shared/pageContent.d.ts.map +1 -0
- package/dist/shared/runtime.d.ts +7 -0
- package/dist/shared/runtime.d.ts.map +1 -0
- package/dist/shared/template.d.ts +8 -0
- package/dist/shared/template.d.ts.map +1 -0
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +10 -0
- package/dist/ui/aboutme/api.d.ts +11 -0
- package/dist/ui/aboutme/api.d.ts.map +1 -0
- package/dist/ui/aboutme/index.d.ts +26 -0
- package/dist/ui/aboutme/index.d.ts.map +1 -0
- package/dist/ui/aboutme/renderer.d.ts +5 -0
- package/dist/ui/aboutme/renderer.d.ts.map +1 -0
- package/dist/ui/aboutme/styles.d.ts +2 -0
- package/dist/ui/aboutme/styles.d.ts.map +1 -0
- package/dist/ui/admin/index.d.ts +42 -0
- package/dist/ui/admin/index.d.ts.map +1 -0
- package/dist/ui/admin/styles.d.ts +2 -0
- package/dist/ui/admin/styles.d.ts.map +1 -0
- package/dist/ui/banner/index.d.ts +17 -0
- package/dist/ui/banner/index.d.ts.map +1 -0
- package/dist/ui/banner/styles.d.ts +2 -0
- package/dist/ui/banner/styles.d.ts.map +1 -0
- package/dist/ui/footer/index.d.ts +9 -0
- package/dist/ui/footer/index.d.ts.map +1 -0
- package/dist/ui/footer/styles.d.ts +2 -0
- package/dist/ui/footer/styles.d.ts.map +1 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +820 -0
- package/package.json +41 -0
- package/src/api/__tests__/info.test.ts +44 -0
- package/src/api/__tests__/utils.test.ts +78 -0
- package/src/api/handlers/aboutme.ts +99 -0
- package/src/api/handlers/content-api.ts +268 -0
- package/src/api/handlers/content.ts +72 -0
- package/src/api/handlers/home.ts +79 -0
- package/src/api/handlers/info.ts +12 -0
- package/src/api/handlers/logo.ts +55 -0
- package/src/api/handlers/staticdetails.ts +48 -0
- package/src/api/index.ts +125 -0
- package/src/api/utils.ts +16 -0
- package/src/prerender/__tests__/pageContent.test.ts +54 -0
- package/src/prerender/__tests__/template.test.ts +54 -0
- package/src/prerender/index.ts +138 -0
- package/src/prerender/pageContent.ts +263 -0
- package/src/prerender/prerender.ts +25 -0
- package/src/prerender/template.ts +65 -0
- package/src/shared/config/api.ts +16 -0
- package/src/shared/config/index.ts +41 -0
- package/src/shared/config/types.ts +16 -0
- package/src/shared/core/__tests__/theme-toggle.test.ts +204 -0
- package/src/shared/core/site-store.ts +38 -0
- package/src/shared/core/theme-toggle.ts +118 -0
- package/src/shared/index.ts +15 -0
- package/src/shared/interfaces/iFooterLink.ts +4 -0
- package/src/shared/interfaces/iRoute.ts +4 -0
- package/src/shared/models/theme-variables.css +25 -0
- package/src/shared/pageContent.ts +209 -0
- package/src/shared/runtime.ts +11 -0
- package/src/shared/styles/markdown.css +129 -0
- package/src/shared/template.ts +35 -0
- package/src/styles/markdown.css +129 -0
- package/src/styles/theme.css +432 -0
- package/src/ui/aboutme/api.ts +12 -0
- package/src/ui/aboutme/index.ts +155 -0
- package/src/ui/aboutme/renderer.ts +7 -0
- package/src/ui/aboutme/styles.ts +10 -0
- package/src/ui/admin/index.ts +492 -0
- package/src/ui/admin/styles.ts +317 -0
- package/src/ui/banner/index.ts +38 -0
- package/src/ui/banner/styles.ts +10 -0
- package/src/ui/footer/index.ts +37 -0
- package/src/ui/footer/styles.ts +9 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export class ThemeToggle extends HTMLElement {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
this.attachShadow({ mode: 'open' });
|
|
5
|
+
this.render();
|
|
6
|
+
this.applyThemeFromLocalStorage();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connectedCallback() {
|
|
10
|
+
this.shadowRoot?.querySelector('button')?.addEventListener('click', this.toggleTheme);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
disconnectedCallback() {
|
|
14
|
+
this.shadowRoot?.querySelector('button')?.removeEventListener('click', this.toggleTheme);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render() {
|
|
18
|
+
if (this.shadowRoot) {
|
|
19
|
+
this.shadowRoot.innerHTML = `
|
|
20
|
+
<style>
|
|
21
|
+
:host {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
}
|
|
24
|
+
button {
|
|
25
|
+
background: none;
|
|
26
|
+
border: none;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
font-size: 1.5rem;
|
|
29
|
+
padding: 0.5rem;
|
|
30
|
+
color: var(--text-color, #213547); /* Default for light mode */
|
|
31
|
+
transition: color 0.3s ease;
|
|
32
|
+
}
|
|
33
|
+
button:hover {
|
|
34
|
+
color: var(--primary-color, #747bff); /* Hover color */
|
|
35
|
+
}
|
|
36
|
+
/* Dark mode specific styles for the button */
|
|
37
|
+
html[data-theme='dark'] button {
|
|
38
|
+
color: var(--dark-mode-text-color, rgba(255, 255, 255, 0.87));
|
|
39
|
+
}
|
|
40
|
+
html[data-theme='dark'] button:hover {
|
|
41
|
+
color: var(--dark-mode-primary-color, #646cff);
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
44
|
+
<button id="theme-toggle" aria-label="Toggle theme">
|
|
45
|
+
<span class="icon">${this.getSunIcon()}</span> <!-- Default to sun for light mode -->
|
|
46
|
+
</button>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
applyThemeFromLocalStorage() {
|
|
52
|
+
const savedTheme = localStorage.getItem('theme');
|
|
53
|
+
if (savedTheme) {
|
|
54
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
55
|
+
this.updateToggleButton(savedTheme);
|
|
56
|
+
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
57
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
58
|
+
this.updateToggleButton('dark');
|
|
59
|
+
} else {
|
|
60
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
61
|
+
this.updateToggleButton('light');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
toggleTheme = () => {
|
|
66
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
67
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
68
|
+
document.documentElement.setAttribute('data-theme', newTheme);
|
|
69
|
+
localStorage.setItem('theme', newTheme);
|
|
70
|
+
this.updateToggleButton(newTheme);
|
|
71
|
+
this.dispatchThemeChangeEvent(newTheme);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
updateToggleButton(theme: string) {
|
|
75
|
+
const button = this.shadowRoot?.querySelector('#theme-toggle');
|
|
76
|
+
if (button) {
|
|
77
|
+
const iconSpan = button.querySelector('.icon');
|
|
78
|
+
if (iconSpan) {
|
|
79
|
+
iconSpan.innerHTML = theme === 'dark' ? this.getMoonIcon() : this.getSunIcon();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getSunIcon(): string {
|
|
85
|
+
return `
|
|
86
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
87
|
+
<circle cx="12" cy="12" r="5"/>
|
|
88
|
+
<line x1="12" y1="1" x2="12" y2="3"/>
|
|
89
|
+
<line x1="12" y1="21" x2="12" y2="23"/>
|
|
90
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
|
91
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
92
|
+
<line x1="1" y1="12" x2="3" y2="12"/>
|
|
93
|
+
<line x1="21" y1="12" x2="23" y2="12"/>
|
|
94
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
|
95
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
96
|
+
</svg>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getMoonIcon(): string {
|
|
101
|
+
return `
|
|
102
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
103
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
104
|
+
</svg>
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
dispatchThemeChangeEvent(theme: string) {
|
|
109
|
+
const event = new CustomEvent('theme-changed', {
|
|
110
|
+
detail: { theme: theme },
|
|
111
|
+
bubbles: true,
|
|
112
|
+
composed: true,
|
|
113
|
+
});
|
|
114
|
+
this.dispatchEvent(event);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
customElements.define('theme-toggle', ThemeToggle);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Shared Config and Store
|
|
2
|
+
export * from './config';
|
|
3
|
+
export * from './core/site-store';
|
|
4
|
+
|
|
5
|
+
// Interfaces
|
|
6
|
+
export * from './interfaces/iRoute';
|
|
7
|
+
export * from './interfaces/iFooterLink';
|
|
8
|
+
|
|
9
|
+
// Core Logic
|
|
10
|
+
export * from './core/theme-toggle';
|
|
11
|
+
export * from './template';
|
|
12
|
+
export * from './pageContent';
|
|
13
|
+
|
|
14
|
+
// Runtime
|
|
15
|
+
export * from './runtime';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* Light Theme Variables */
|
|
3
|
+
--text-color: #213547;
|
|
4
|
+
--background-color: #ffffff;
|
|
5
|
+
--link-color: #646cff;
|
|
6
|
+
--link-hover-color: #747bff;
|
|
7
|
+
--nav-link-color: #007bff;
|
|
8
|
+
--nav-link-hover-bg: #e2e6ea;
|
|
9
|
+
--footer-bg: #fff;
|
|
10
|
+
--footer-color: #222;
|
|
11
|
+
--footer-border: #eee;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Dark Theme Variables */
|
|
15
|
+
html[data-theme='dark'] {
|
|
16
|
+
--text-color: rgba(255, 255, 255, 0.87);
|
|
17
|
+
--background-color: #242424;
|
|
18
|
+
--link-color: #646cff;
|
|
19
|
+
--link-hover-color: #535bf2;
|
|
20
|
+
--nav-link-color: #007bff; /* Can be adjusted for better contrast in dark mode */
|
|
21
|
+
--nav-link-hover-bg: #4a4a4a;
|
|
22
|
+
--footer-bg: #222;
|
|
23
|
+
--footer-color: #fff;
|
|
24
|
+
--footer-border: #444;
|
|
25
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { IFooterLink } from './interfaces/iFooterLink';
|
|
2
|
+
import { IRoute } from './interfaces/iRoute';
|
|
3
|
+
import { MarkdownPipeline } from '@leadertechie/md2html';
|
|
4
|
+
|
|
5
|
+
export interface PageContent {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
canonicalUrl: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ContentMetadata {
|
|
13
|
+
slug: string;
|
|
14
|
+
title: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
summary?: string;
|
|
17
|
+
date: string;
|
|
18
|
+
imageUrl?: string;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
author?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pipeline = new MarkdownPipeline({
|
|
24
|
+
imagePathPrefix: 'images/',
|
|
25
|
+
styleOptions: {
|
|
26
|
+
classPrefix: 'md-',
|
|
27
|
+
addHeadingIds: true
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const renderMarkdown = (content: string): string => {
|
|
32
|
+
if (!content) return '';
|
|
33
|
+
return pipeline.renderMarkdown(content);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const generatePageContent = (
|
|
37
|
+
pathname: string,
|
|
38
|
+
routes: IRoute[],
|
|
39
|
+
footerLinks: IFooterLink[],
|
|
40
|
+
data?: {
|
|
41
|
+
blogs?: ContentMetadata[];
|
|
42
|
+
stories?: ContentMetadata[];
|
|
43
|
+
profile?: any;
|
|
44
|
+
content?: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
date?: string;
|
|
48
|
+
slug?: string;
|
|
49
|
+
siteTitle?: string;
|
|
50
|
+
siteDescription?: string;
|
|
51
|
+
copyright?: string;
|
|
52
|
+
apiUrl?: string;
|
|
53
|
+
baseUrl?: string;
|
|
54
|
+
}
|
|
55
|
+
): PageContent => {
|
|
56
|
+
const logo = "/api/logo";
|
|
57
|
+
const siteTitle = data?.siteTitle || "My Personal Website";
|
|
58
|
+
const siteDescription = data?.siteDescription || "Welcome to my professional portfolio and blog.";
|
|
59
|
+
const copyright = data?.copyright || "2026 All Rights Reserved";
|
|
60
|
+
const baseUrl = data?.baseUrl || "";
|
|
61
|
+
const canonicalUrl = baseUrl ? new URL(pathname, baseUrl).toString() : pathname;
|
|
62
|
+
|
|
63
|
+
const navLinks = routes
|
|
64
|
+
.map(
|
|
65
|
+
(route) =>
|
|
66
|
+
`<a href="${route.link}" class="nav-link" data-route="${route.link === "/" ? "home" : route.text.toLowerCase()}">${route.text}</a>`,
|
|
67
|
+
)
|
|
68
|
+
.join("");
|
|
69
|
+
|
|
70
|
+
const bannerTemplate = `
|
|
71
|
+
<my-banner header="${siteTitle}" logo="${logo}">
|
|
72
|
+
<theme-toggle slot="theme-switcher"></theme-toggle>
|
|
73
|
+
<nav slot="nav-links">
|
|
74
|
+
${navLinks}
|
|
75
|
+
</nav>
|
|
76
|
+
</my-banner>`;
|
|
77
|
+
|
|
78
|
+
const footerTemplate = `
|
|
79
|
+
<my-footer
|
|
80
|
+
copyright="${copyright}"
|
|
81
|
+
footerlinks=\\'\${JSON.stringify(footerLinks)}\\'\'>
|
|
82
|
+
</my-footer>`;
|
|
83
|
+
|
|
84
|
+
const renderContentGists = (items: ContentMetadata[] = [], title: string, type: 'blogs' | 'stories') => {
|
|
85
|
+
const listHtml = items.length > 0
|
|
86
|
+
? items.map(item => `
|
|
87
|
+
<div class="gist-card">
|
|
88
|
+
<a href="${type}/${item.slug}" data-route="${type}-${item.slug}"><h4>${item.title}</h4></a>
|
|
89
|
+
<p>${item.summary || item.description || ''}</p>
|
|
90
|
+
<small>${new Date(item.date).toLocaleDateString()}</small>
|
|
91
|
+
</div>
|
|
92
|
+
`).join('')
|
|
93
|
+
: `<p>No ${type} available yet.</p>`;
|
|
94
|
+
|
|
95
|
+
return `
|
|
96
|
+
<div class="recent-content-section ${title.includes('Stories') ? 'mt-2' : ''}">
|
|
97
|
+
<h3 class="border-bottom pb-05">${title}</h3>
|
|
98
|
+
${listHtml}
|
|
99
|
+
</div>
|
|
100
|
+
`;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (pathname === "/" || pathname === "") {
|
|
104
|
+
const homeHtml = data?.content || `<h1>Welcome to ${siteTitle}</h1><p>Start by configuring your content in the admin portal.</p>`;
|
|
105
|
+
|
|
106
|
+
const mainContent = `
|
|
107
|
+
${bannerTemplate}
|
|
108
|
+
<main class="container container-wide column-layout row-layout">
|
|
109
|
+
<div class="home-main-content main-column text-left">
|
|
110
|
+
${homeHtml}
|
|
111
|
+
</div>
|
|
112
|
+
<aside class="home-side-content sidebar-column">
|
|
113
|
+
${renderContentGists(data?.blogs, 'Recent Blogs', 'blogs')}
|
|
114
|
+
${renderContentGists(data?.stories, 'Recent Stories', 'stories')}
|
|
115
|
+
</aside>
|
|
116
|
+
</main>
|
|
117
|
+
${footerTemplate}`;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
title: `${siteTitle} - Home`,
|
|
121
|
+
description: siteDescription,
|
|
122
|
+
canonicalUrl,
|
|
123
|
+
content: mainContent
|
|
124
|
+
};
|
|
125
|
+
} else if (pathname === "/blogs" || pathname === "/stories" || pathname.startsWith("/blogs/") || pathname.startsWith("/stories/")) {
|
|
126
|
+
const isBlog = pathname.includes("blogs");
|
|
127
|
+
const type = isBlog ? "blogs" : "stories";
|
|
128
|
+
const items = isBlog ? data?.blogs : data?.stories;
|
|
129
|
+
const title = type.charAt(0).toUpperCase() + type.slice(1);
|
|
130
|
+
const currentSlug = data?.slug;
|
|
131
|
+
|
|
132
|
+
const mainContent = `
|
|
133
|
+
${bannerTemplate}
|
|
134
|
+
<main class="container container-wide">
|
|
135
|
+
<div class="mb-2">
|
|
136
|
+
<input type="text" id="content-search" placeholder="Search ${type}..." class="search-input search-input-lg">
|
|
137
|
+
</div>
|
|
138
|
+
<div class="column-layout">
|
|
139
|
+
<aside class="sidebar-nav sidebar-column">
|
|
140
|
+
<div id="content-list">
|
|
141
|
+
${items?.map(item => {
|
|
142
|
+
const searchTerms = [
|
|
143
|
+
item.title,
|
|
144
|
+
...(item.tags || []),
|
|
145
|
+
item.summary || item.description || '',
|
|
146
|
+
item.slug
|
|
147
|
+
].join(' ').toLowerCase();
|
|
148
|
+
|
|
149
|
+
return `
|
|
150
|
+
<div class="list-item sidebar-item ${item.slug === currentSlug ? 'active' : ''}"
|
|
151
|
+
data-search="${searchTerms}">
|
|
152
|
+
<a href="/${type}/${item.slug}" data-route="${type}-${item.slug}" class="sidebar-item sidebar-item-link">
|
|
153
|
+
<h4>${item.title}</h4>
|
|
154
|
+
<small>${new Date(item.date).toLocaleDateString()}</small>
|
|
155
|
+
</a>
|
|
156
|
+
</div>
|
|
157
|
+
`}).join('') || `<p>No ${type} available yet.</p>`}
|
|
158
|
+
</div>
|
|
159
|
+
</aside>
|
|
160
|
+
<div class="wide-main-column text-left">
|
|
161
|
+
<div id="content-viewer">
|
|
162
|
+
${currentSlug
|
|
163
|
+
? (isBlog ? `<my-blog-viewer slug="${currentSlug}"></my-blog-viewer>` : `<my-story-viewer slug="${currentSlug}"></my-story-viewer>`)
|
|
164
|
+
: (items && items.length > 0 ? `<p>Select a ${type.slice(0, -1)} to read.</p>` : '')}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</main>
|
|
169
|
+
${footerTemplate}`;
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
title: `${title} - ${siteTitle}`,
|
|
173
|
+
description: `Read the latest ${type} from ${siteTitle}.`,
|
|
174
|
+
canonicalUrl,
|
|
175
|
+
content: mainContent
|
|
176
|
+
};
|
|
177
|
+
} else if (pathname === "/about-me") {
|
|
178
|
+
const apiBaseUrl = data?.apiUrl || "";
|
|
179
|
+
const mainContent = `
|
|
180
|
+
${bannerTemplate}
|
|
181
|
+
<main class="container container-narrow">
|
|
182
|
+
<my-aboutme base-url="${apiBaseUrl}"></my-aboutme>
|
|
183
|
+
</main>
|
|
184
|
+
${footerTemplate}`;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
title: `About - ${siteTitle}`,
|
|
188
|
+
description: `Learn more about ${siteTitle}.`,
|
|
189
|
+
canonicalUrl,
|
|
190
|
+
content: mainContent
|
|
191
|
+
};
|
|
192
|
+
} else {
|
|
193
|
+
const mainContent = `
|
|
194
|
+
${bannerTemplate}
|
|
195
|
+
<main class="container container-narrow text-center page-content">
|
|
196
|
+
<h1 class="page-title">Page Not Found</h1>
|
|
197
|
+
<p>The page you\\'re looking for doesn\\'t exist.</p>
|
|
198
|
+
<p><a href="/">Return to home</a></p>
|
|
199
|
+
</main>
|
|
200
|
+
${footerTemplate}`;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
title: `404 Not Found - ${siteTitle}`,
|
|
204
|
+
description: "The page you requested could not be found.",
|
|
205
|
+
canonicalUrl,
|
|
206
|
+
content: mainContent
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Runtime-only exports for Cloudflare Workers (no DOM/web components)
|
|
2
|
+
export * from './interfaces/iRoute';
|
|
3
|
+
export * from './interfaces/iFooterLink';
|
|
4
|
+
export * from './pageContent';
|
|
5
|
+
|
|
6
|
+
// Core utilities and types (DOM-free)
|
|
7
|
+
export type { IRoute } from './interfaces/iRoute';
|
|
8
|
+
export type { IFooterLink } from './interfaces/iFooterLink';
|
|
9
|
+
|
|
10
|
+
// Page content generation utilities
|
|
11
|
+
export { generatePageContent } from './pageContent';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* Markdown Content Styles */
|
|
2
|
+
|
|
3
|
+
.md-heading {
|
|
4
|
+
margin: 1.5rem 0 0.75rem 0;
|
|
5
|
+
font-weight: 600;
|
|
6
|
+
line-height: 1.3;
|
|
7
|
+
color: var(--text-color, #213547);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.md-heading h1 { font-size: 2rem; }
|
|
11
|
+
.md-heading h2 { font-size: 1.5rem; }
|
|
12
|
+
.md-heading h3 { font-size: 1.25rem; }
|
|
13
|
+
.md-heading h4 { font-size: 1.1rem; }
|
|
14
|
+
.md-heading h5, .md-heading h6 { font-size: 1rem; }
|
|
15
|
+
|
|
16
|
+
.md-paragraph {
|
|
17
|
+
margin: 0 0 1rem 0;
|
|
18
|
+
line-height: 1.7;
|
|
19
|
+
color: var(--text-color, #213547);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.md-list {
|
|
23
|
+
margin: 0 0 1rem 0;
|
|
24
|
+
padding-left: 1.5rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.md-list-item {
|
|
28
|
+
margin-bottom: 0.5rem;
|
|
29
|
+
line-height: 1.6;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.md-blockquote {
|
|
33
|
+
margin: 1rem 0;
|
|
34
|
+
padding: 0.75rem 1rem;
|
|
35
|
+
border-left: 4px solid var(--link-color, #646cff);
|
|
36
|
+
background: var(--nav-link-hover-bg, #f8f9fa);
|
|
37
|
+
border-radius: 0 8px 8px 0;
|
|
38
|
+
color: var(--secondary-text, #666);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.md-blockquote .md-paragraph {
|
|
42
|
+
margin: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.md-code {
|
|
46
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
47
|
+
font-size: 0.9em;
|
|
48
|
+
background: var(--nav-link-hover-bg, #f5f5f5);
|
|
49
|
+
padding: 0.2rem 0.4rem;
|
|
50
|
+
border-radius: 4px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.md-image {
|
|
54
|
+
max-width: 100%;
|
|
55
|
+
height: auto;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
margin: 1rem 0;
|
|
58
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.md-container {
|
|
62
|
+
margin: 1rem 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.md-container hr {
|
|
66
|
+
border: none;
|
|
67
|
+
height: 2px;
|
|
68
|
+
background: var(--border-color, #eee);
|
|
69
|
+
margin: 2rem 0;
|
|
70
|
+
border-radius: 1px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.md-container[tag="hr"] {
|
|
74
|
+
margin: 2rem 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Links within markdown content */
|
|
78
|
+
.md-paragraph a,
|
|
79
|
+
.md-list-item a {
|
|
80
|
+
color: var(--link-color, #646cff);
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
transition: color 0.2s ease;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.md-paragraph a:hover,
|
|
86
|
+
.md-list-item a:hover {
|
|
87
|
+
color: var(--link-hover-color, #535bf2);
|
|
88
|
+
text-decoration: underline;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Strong and emphasis */
|
|
92
|
+
.md-paragraph strong,
|
|
93
|
+
.md-list-item strong {
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
color: var(--text-color, #1a1a1a);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.md-paragraph em,
|
|
99
|
+
.md-list-item em {
|
|
100
|
+
font-style: italic;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Tables (if supported) */
|
|
104
|
+
.md-container table {
|
|
105
|
+
width: 100%;
|
|
106
|
+
border-collapse: collapse;
|
|
107
|
+
margin: 1rem 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.md-container th,
|
|
111
|
+
.md-container td {
|
|
112
|
+
padding: 0.75rem;
|
|
113
|
+
border: 1px solid var(--border-color, #ddd);
|
|
114
|
+
text-align: left;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.md-container th {
|
|
118
|
+
background: var(--nav-link-hover-bg, #f5f5f5);
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Dark theme adjustments */
|
|
123
|
+
html[data-theme="dark"] .md-code {
|
|
124
|
+
background: rgba(255, 255, 255, 0.1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
html[data-theme="dark"] .md-container th {
|
|
128
|
+
background: rgba(255, 255, 255, 0.05);
|
|
129
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface TemplateProps {
|
|
2
|
+
title: string;
|
|
3
|
+
description: string;
|
|
4
|
+
canonicalUrl: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const createHtmlTemplate = ({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
canonicalUrl,
|
|
12
|
+
content
|
|
13
|
+
}: TemplateProps): string => {
|
|
14
|
+
return `<!doctype html>
|
|
15
|
+
<html lang="en" data-theme="light">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8" />
|
|
18
|
+
<link rel="icon" type="image/svg+xml" href="/api/logo" />
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
20
|
+
<title>${title}</title>
|
|
21
|
+
<meta name="description" content="${description}" />
|
|
22
|
+
<meta property="og:title" content="${title}" />
|
|
23
|
+
<meta property="og:description" content="${description}" />
|
|
24
|
+
<meta property="og:url" content="${canonicalUrl}" />
|
|
25
|
+
<link rel="canonical" href="${canonicalUrl}" />
|
|
26
|
+
<link rel="stylesheet" href="/src/styles.css" />
|
|
27
|
+
<script type="module" src="/src/index.ts"></script>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<div id="app">
|
|
31
|
+
${content}
|
|
32
|
+
</div>
|
|
33
|
+
</body>
|
|
34
|
+
</html>`;
|
|
35
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* Markdown Content Styles */
|
|
2
|
+
|
|
3
|
+
.md-heading {
|
|
4
|
+
margin: 1.5rem 0 0.75rem 0;
|
|
5
|
+
font-weight: 600;
|
|
6
|
+
line-height: 1.3;
|
|
7
|
+
color: var(--text-color, #213547);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.md-heading h1 { font-size: 2rem; }
|
|
11
|
+
.md-heading h2 { font-size: 1.5rem; }
|
|
12
|
+
.md-heading h3 { font-size: 1.25rem; }
|
|
13
|
+
.md-heading h4 { font-size: 1.1rem; }
|
|
14
|
+
.md-heading h5, .md-heading h6 { font-size: 1rem; }
|
|
15
|
+
|
|
16
|
+
.md-paragraph {
|
|
17
|
+
margin: 0 0 1rem 0;
|
|
18
|
+
line-height: 1.7;
|
|
19
|
+
color: var(--text-color, #213547);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.md-list {
|
|
23
|
+
margin: 0 0 1rem 0;
|
|
24
|
+
padding-left: 1.5rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.md-list-item {
|
|
28
|
+
margin-bottom: 0.5rem;
|
|
29
|
+
line-height: 1.6;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.md-blockquote {
|
|
33
|
+
margin: 1rem 0;
|
|
34
|
+
padding: 0.75rem 1rem;
|
|
35
|
+
border-left: 4px solid var(--link-color, #646cff);
|
|
36
|
+
background: var(--nav-link-hover-bg, #f8f9fa);
|
|
37
|
+
border-radius: 0 8px 8px 0;
|
|
38
|
+
color: var(--secondary-text, #666);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.md-blockquote .md-paragraph {
|
|
42
|
+
margin: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.md-code {
|
|
46
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
47
|
+
font-size: 0.9em;
|
|
48
|
+
background: var(--nav-link-hover-bg, #f5f5f5);
|
|
49
|
+
padding: 0.2rem 0.4rem;
|
|
50
|
+
border-radius: 4px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.md-image {
|
|
54
|
+
max-width: 100%;
|
|
55
|
+
height: auto;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
margin: 1rem 0;
|
|
58
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.md-container {
|
|
62
|
+
margin: 1rem 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.md-container hr {
|
|
66
|
+
border: none;
|
|
67
|
+
height: 2px;
|
|
68
|
+
background: var(--border-color, #eee);
|
|
69
|
+
margin: 2rem 0;
|
|
70
|
+
border-radius: 1px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.md-container[tag="hr"] {
|
|
74
|
+
margin: 2rem 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Links within markdown content */
|
|
78
|
+
.md-paragraph a,
|
|
79
|
+
.md-list-item a {
|
|
80
|
+
color: var(--link-color, #646cff);
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
transition: color 0.2s ease;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.md-paragraph a:hover,
|
|
86
|
+
.md-list-item a:hover {
|
|
87
|
+
color: var(--link-hover-color, #535bf2);
|
|
88
|
+
text-decoration: underline;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Strong and emphasis */
|
|
92
|
+
.md-paragraph strong,
|
|
93
|
+
.md-list-item strong {
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
color: var(--text-color, #1a1a1a);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.md-paragraph em,
|
|
99
|
+
.md-list-item em {
|
|
100
|
+
font-style: italic;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Tables (if supported) */
|
|
104
|
+
.md-container table {
|
|
105
|
+
width: 100%;
|
|
106
|
+
border-collapse: collapse;
|
|
107
|
+
margin: 1rem 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.md-container th,
|
|
111
|
+
.md-container td {
|
|
112
|
+
padding: 0.75rem;
|
|
113
|
+
border: 1px solid var(--border-color, #ddd);
|
|
114
|
+
text-align: left;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.md-container th {
|
|
118
|
+
background: var(--nav-link-hover-bg, #f5f5f5);
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Dark theme adjustments */
|
|
123
|
+
html[data-theme="dark"] .md-code {
|
|
124
|
+
background: rgba(255, 255, 255, 0.1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
html[data-theme="dark"] .md-container th {
|
|
128
|
+
background: rgba(255, 255, 255, 0.05);
|
|
129
|
+
}
|