Compare commits
4 Commits
2aaa28cabe
...
b33ffa3371
| Author | SHA1 | Date | |
|---|---|---|---|
| b33ffa3371 | |||
| 0890ae3ef9 | |||
| 16cf44b42d | |||
| cc79afaea0 |
103
bun.lock
Normal file
103
bun.lock
Normal file
@ -0,0 +1,103 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "portfolio",
|
||||
"dependencies": {
|
||||
"@elysiajs/html": "1.4.0",
|
||||
"@elysiajs/static": "1.4.4",
|
||||
"elysia": "^1.4.11",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
|
||||
|
||||
"@elysiajs/html": ["@elysiajs/html@1.4.0", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-j4jFqGEkIC8Rg2XiTOujb9s0WLnz1dnY/4uqczyCdOVruDeJtGP+6+GvF0A76SxEvltn8UR1yCUnRdLqRi3vuw=="],
|
||||
|
||||
"@elysiajs/static": ["@elysiajs/static@1.4.4", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-PT/uGvBHQL5I+APAGiuRjhVfySe5YmrJdPtSc2QyM6CgNp4WDCmPfhPoVYkHNaH5QGWdP62hMq0HUnClNxR3zw=="],
|
||||
|
||||
"@kitajs/html": ["@kitajs/html@4.2.10", "", { "dependencies": { "csstype": "^3.1.3" } }, "sha512-q9n2Ig7GlAYOdL+CeWxsIIZFIKna+eCJah15eK8PBIFHW3UcWayAMs8QYGJNYgP3uMucDimIAUBH26xnE7GILw=="],
|
||||
|
||||
"@kitajs/ts-html-plugin": ["@kitajs/ts-html-plugin@4.1.3", "", { "dependencies": { "chalk": "^5.6.2", "tslib": "^2.8.1", "yargs": "^18.0.0" }, "peerDependencies": { "@kitajs/html": "^4.2.10", "typescript": "^5.6.2" }, "bin": { "ts-html-plugin": "dist/cli.js", "xss-scan": "dist/cli.js" } }, "sha512-NlYrID5yMxfRKiO1eiiSC4MWveKe0ffoCJOZm4idNOqwimmLXr0g1NmvCcquOU2XLRrgzynxZqw6rhwR5CY5Nw=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="],
|
||||
|
||||
"@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.7.2", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
|
||||
|
||||
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"elysia": ["elysia@1.4.11", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-cphuzQj0fRw1ICRvwHy2H3xQio9bycaZUVHnDHJQnKqBfMNlZ+Hzj6TMmt9lc0Az0mvbCnPXWVF7y1MCRhUuOA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="],
|
||||
|
||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||
|
||||
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
|
||||
|
||||
"file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
|
||||
|
||||
"token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||
|
||||
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||
}
|
||||
}
|
||||
28
index.tsx
Normal file
28
index.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { Elysia } from "elysia";
|
||||
import { html } from "@elysiajs/html";
|
||||
import { staticPlugin } from "@elysiajs/static";
|
||||
|
||||
import { AppShell } from "./src/frontend/AppShell";
|
||||
import { app } from "./src/backend";
|
||||
|
||||
const index = new Elysia()
|
||||
.use(html())
|
||||
.onRequest(({ request }) => {
|
||||
console.log(`Request ${request.method} ${request.url}`);
|
||||
})
|
||||
.onAfterHandle(({ request, responseValue }) => {
|
||||
if (request.headers.get("shell-loaded") === "true") {
|
||||
return responseValue; // Return the <main> element if the AppShell has already been loaded
|
||||
}
|
||||
return AppShell(responseValue); // Return the <main> element wrapped by the AppShell
|
||||
})
|
||||
.use(staticPlugin({
|
||||
assets: './src/public',
|
||||
prefix: '/public'
|
||||
}))
|
||||
.use(app)
|
||||
.listen(3000);
|
||||
|
||||
console.log(
|
||||
`🦊 Elysia is running at ${index.server?.hostname}:${index.server?.port}`
|
||||
);
|
||||
10
package.json
10
package.json
@ -3,15 +3,15 @@
|
||||
"version": "1.0.50",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "bun run --watch src/index.tsx"
|
||||
"dev": "bun run --watch ./index.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elysiajs/html": "1.0.2",
|
||||
"@elysiajs/static": "1.0.3",
|
||||
"elysia": "^1.0.17"
|
||||
"@elysiajs/html": "1.4.0",
|
||||
"@elysiajs/static": "1.4.4",
|
||||
"elysia": "^1.4.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
"@types/bun": "^1.3.0"
|
||||
},
|
||||
"module": "src/index.js"
|
||||
}
|
||||
15
src/app.tsx
15
src/app.tsx
@ -1,15 +0,0 @@
|
||||
import { Elysia } from "elysia";
|
||||
import { Index, selectPage } from "./frontend";
|
||||
|
||||
export const app = new Elysia()
|
||||
.get("/", (context) => isHTMXResponse( context, "/"))
|
||||
.get("/blog", (context) => isHTMXResponse( context, "/blog"))
|
||||
.get("/projects", (context) => isHTMXResponse( context, "/projects"))
|
||||
.get("/content/post", () => "I'm from the server")
|
||||
|
||||
function isHTMXResponse ( context: any, path: string) {
|
||||
if (context.headers["hx-request"] === "true") {
|
||||
return selectPage(path);
|
||||
}
|
||||
return Index(path);
|
||||
}
|
||||
22
src/backend/index.tsx
Normal file
22
src/backend/index.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { Html } from "@elysiajs/html";
|
||||
import { Elysia, NotFoundError } from "elysia";
|
||||
|
||||
import { Home } from "../frontend/home";
|
||||
import { Blog } from "../frontend/blog";
|
||||
import { NotFound } from "../frontend/not-found";
|
||||
|
||||
|
||||
export const app = new Elysia()
|
||||
.onError(({ error }) => {
|
||||
if(error instanceof NotFoundError) {
|
||||
return <NotFound />;
|
||||
}
|
||||
return error;
|
||||
})
|
||||
.get("/", () => { return <Home /> })
|
||||
.get("/:path", ({ path }) => {
|
||||
if(path === "/blog") {
|
||||
return <Blog />;
|
||||
}
|
||||
throw new NotFoundError();
|
||||
})
|
||||
76
src/frontend/AppShell.tsx
Normal file
76
src/frontend/AppShell.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { Html } from "@elysiajs/html";
|
||||
|
||||
// Helper: Minify CSS using simple but effective regex
|
||||
async function minifyCSS(css: string): Promise<string> {
|
||||
return css
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
|
||||
.replace(/\s+/g, ' ') // Collapse whitespace
|
||||
.replace(/\s*([{}:;,])\s*/g, '$1') // Remove space around delimiters
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Helper: Minify JS/TS using Bun.Transpiler
|
||||
async function minifyJS(code: string): Promise<string> {
|
||||
const transpiler = new Bun.Transpiler({
|
||||
loader: 'ts',
|
||||
minifyWhitespace: true,
|
||||
});
|
||||
|
||||
return transpiler.transformSync(code);
|
||||
}
|
||||
|
||||
// Read and minify files at module load time (runs once)
|
||||
console.log('[AppShell] Loading and minifying assets...');
|
||||
|
||||
const rawStyles = await Bun.file("./src/public/styles.css").text();
|
||||
const rawHeadScript = await Bun.file("./src/public/head.ts").text();
|
||||
const rawOnLoadScript = await Bun.file("./src/public/onLoad.ts").text();
|
||||
|
||||
// Minify all assets
|
||||
const styles = await minifyCSS(rawStyles);
|
||||
const headScript = await minifyJS(rawHeadScript);
|
||||
const onLoadScript = await minifyJS(rawOnLoadScript);
|
||||
|
||||
console.log('[AppShell] Assets minified and cached in memory');
|
||||
console.log(` CSS: ${rawStyles.length} → ${styles.length} bytes (-${Math.round((1 - styles.length / rawStyles.length) * 100)}%)`);
|
||||
console.log(` head.ts: ${rawHeadScript.length} → ${headScript.length} bytes (-${Math.round((1 - headScript.length / rawHeadScript.length) * 100)}%)`);
|
||||
console.log(` onLoad.ts: ${rawOnLoadScript.length} → ${onLoadScript.length} bytes (-${Math.round((1 - onLoadScript.length / rawOnLoadScript.length) * 100)}%)`);
|
||||
|
||||
export function AppShell(responseValue: any) {
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
<title>Caleb's Blog</title>
|
||||
<style>
|
||||
{styles}
|
||||
</style>
|
||||
<script>
|
||||
{headScript}
|
||||
</script>
|
||||
<script defer>
|
||||
{onLoadScript}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{responseValue}
|
||||
<aside>
|
||||
<h3>About Me</h3>
|
||||
<p>I'm a software engineer</p>
|
||||
<ul>
|
||||
<li>Twitter</li>
|
||||
<li>GitHub</li>
|
||||
<li>LinkedIn</li>
|
||||
</ul>
|
||||
<h3>Categories</h3>
|
||||
<ul>
|
||||
<li>Web Development</li>
|
||||
<li>UI/UX Design</li>
|
||||
<li>Productivity</li>
|
||||
<li>Career</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
10
src/frontend/blog.tsx
Normal file
10
src/frontend/blog.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { Html } from "@elysiajs/html";
|
||||
|
||||
export function Blog() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Blog</h1>
|
||||
<a href="/">Home</a>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
export function Header() {
|
||||
return (
|
||||
<div>
|
||||
<header hx-boost="true">
|
||||
<span>Caleb Braaten</span>
|
||||
<nav>
|
||||
<a hx-target="main" href="/">Home</a>
|
||||
<a hx-target="main" href="/blog">Blog</a>
|
||||
<a hx-target="main" href="/projects">Projects</a>
|
||||
</nav>
|
||||
</header>
|
||||
<hr />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
export function GithubIcon(props: any) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function LinkedinIcon(props: any) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
|
||||
<rect width="4" height="12" x="2" y="9" />
|
||||
<circle cx="4" cy="4" r="2" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function XIcon(props: any) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 1200 1227"
|
||||
preserveAspectRatio="none"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="black" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
10
src/frontend/home.tsx
Normal file
10
src/frontend/home.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { Html } from "@elysiajs/html";
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Home</h1>
|
||||
<a href="/blog">Blog</a>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { Header } from "./components/Header";
|
||||
import { Home } from "./pages/home";
|
||||
import { Blog } from "./pages/blog";
|
||||
import { Projects } from "./pages/projects";
|
||||
|
||||
export function Index(path: string) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script defer src="./htmx.min.js"></script>
|
||||
<title>Caleb's Blog</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
<main>
|
||||
{selectPage(path)}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
export function selectPage(path: string) {
|
||||
switch (path) {
|
||||
case '/':
|
||||
return <Home />
|
||||
case '/blog':
|
||||
return <Blog />
|
||||
case '/projects':
|
||||
return <Projects />
|
||||
}
|
||||
}
|
||||
9
src/frontend/not-found.tsx
Normal file
9
src/frontend/not-found.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Html } from "@elysiajs/html";
|
||||
|
||||
export function NotFound() {
|
||||
return (
|
||||
<main>
|
||||
<h1>404 Not Found</h1>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
export function Blog(){
|
||||
return (
|
||||
<div>
|
||||
<section>
|
||||
<article>
|
||||
<h2>Blog Post Title</h2>
|
||||
<div>Posted on January 1, 2024</div>
|
||||
<p>Google Chrome is a web browser developed by Google, released in 2008. Chrome is the world's most popular web browser today!</p>
|
||||
<a href="/post/3">Read More</a>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Another Blog Post Title</h2>
|
||||
<div>Posted on December 1, 2023</div>
|
||||
<p>Mozilla Firefox is an open-source web browser developed by Mozilla. Firefox has been the second most popular web browser since January, 2018.</p>
|
||||
<a href="/post/2">Read More</a>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>The First Blog Post</h2>
|
||||
<div>Posted on November 1, 2023</div>
|
||||
<p>Microsoft Edge is a web browser developed by Microsoft, released in 2015. Microsoft Edge replaced Internet Explorer.</p>
|
||||
<a href="/post/1">Read More</a>
|
||||
</article>
|
||||
</section>
|
||||
<aside>
|
||||
<div>
|
||||
<h3>About Me</h3>
|
||||
<p>I'm a software engineer</p>
|
||||
|
||||
<h3>Connect with me</h3>
|
||||
<ul>
|
||||
<li>Twitter</li>
|
||||
<li>GitHub</li>
|
||||
<li>LinkedIn</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Categories</h3>
|
||||
<ul>
|
||||
<li>Web Development</li>
|
||||
<li>UI/UX Design</li>
|
||||
<li>Productivity</li>
|
||||
<li>Career</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import { GithubIcon, LinkedinIcon, XIcon } from "../components/icons"
|
||||
|
||||
export function Home(){
|
||||
return (
|
||||
<div>
|
||||
<article class="post-spotlight">
|
||||
<h2>Most Recent Blog Post Title</h2>
|
||||
<div>Posted on January 1, 2024</div>
|
||||
<p>Google Chrome is a web browser developed by Google, released in 2008. Chrome is the world's most popular web browser today!</p>
|
||||
<a href="/post/3">Continue Reading...</a>
|
||||
</article>
|
||||
<hr />
|
||||
<aside>
|
||||
<div class="about">
|
||||
<h3>About Me</h3>
|
||||
<p>I'm a software engineer</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href=""><XIcon /></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href=""><GithubIcon /></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href=""><LinkedinIcon /></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content-categories">
|
||||
<h3>Categories</h3>
|
||||
<ul>
|
||||
<li>Web Development</li>
|
||||
<li>UI/UX Design</li>
|
||||
<li>Productivity</li>
|
||||
<li>Career</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
export function Projects(){
|
||||
return (
|
||||
<div class="projects">
|
||||
<div>
|
||||
<img src="https://via.placeholder.com/150" />
|
||||
<h2>This Site</h2>
|
||||
<p>Project 1 description</p>
|
||||
<a href="/project/1">Git Repo</a>
|
||||
</div>
|
||||
<div>
|
||||
<img src="https://via.placeholder.com/150" />
|
||||
<h2>Home Server</h2>
|
||||
<p>Project 1 description</p>
|
||||
<a href="/project/1">Git Repo</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import { Elysia } from "elysia";
|
||||
import { html } from "@elysiajs/html";
|
||||
import { staticPlugin } from "@elysiajs/static";
|
||||
import { app } from "./app";
|
||||
|
||||
const index = new Elysia()
|
||||
.use(html())
|
||||
.use(staticPlugin({
|
||||
assets: './src/frontend/public',
|
||||
prefix: '/'
|
||||
}))
|
||||
.use(app)
|
||||
.onRequest(({ request }) => {
|
||||
console.log(`Request ${request.method} ${request.url}`);
|
||||
})
|
||||
.listen(3000);
|
||||
|
||||
console.log(
|
||||
`🦊 Elysia is running at ${index.server?.hostname}:${index.server?.port}`
|
||||
);
|
||||
11
src/public/head.ts
Normal file
11
src/public/head.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// Client-side script that runs in <head>
|
||||
// Example: TypeScript with DOM types
|
||||
(() => {
|
||||
const logPageInfo = (): void => {
|
||||
console.log('Page loaded in <head>');
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
logPageInfo();
|
||||
}
|
||||
})();
|
||||
43
src/public/onLoad.ts
Normal file
43
src/public/onLoad.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// Client-side script that runs on page load
|
||||
// Example: TypeScript with type annotations
|
||||
|
||||
async function loadContent(url: string) {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'shell-loaded': 'true'
|
||||
}
|
||||
});
|
||||
const html = await response.text();
|
||||
const mainElement = document.querySelector('main');
|
||||
if (mainElement) {
|
||||
mainElement.outerHTML = html;
|
||||
// Re-attach handlers to new links after content swap
|
||||
attachLinkHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
function attachLinkHandlers() {
|
||||
const links: NodeListOf<HTMLAnchorElement> = document.querySelectorAll('a');
|
||||
console.log('Found links:', links.length);
|
||||
|
||||
links.forEach(link => {
|
||||
console.log('Attaching listener to:', link.href);
|
||||
link.onclick = async (e) => {
|
||||
console.log('clicked', link.href);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
window.history.pushState({}, '', link.href);
|
||||
await loadContent(link.href);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for back/forward button clicks
|
||||
window.addEventListener('popstate', async (event) => {
|
||||
await loadContent(window.location.href);
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
attachLinkHandlers();
|
||||
});
|
||||
@ -12,12 +12,12 @@
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
"jsx": "react", /* Specify what JSX code is generated. */
|
||||
"lib": ["ES2021", "DOM", "DOM.Iterable"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
"jsx": "react", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
"jsxFactory": "Html.createElement", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
"jsxFragmentFactory": "Html.Fragment", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
"jsxFactory": "Html.createElement", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
"jsxFragmentFactory": "Html.Fragment", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user