diff --git a/src/assets/favicon.svg b/src/assets/favicon.svg
new file mode 100644
index 0000000..4e4b6de
--- /dev/null
+++ b/src/assets/favicon.svg
@@ -0,0 +1,21 @@
+
diff --git a/src/assets/logo-dark.svg b/src/assets/logo-dark.svg
new file mode 100644
index 0000000..70d6ecf
--- /dev/null
+++ b/src/assets/logo-dark.svg
@@ -0,0 +1,42 @@
+
diff --git a/src/assets/logo-light.svg b/src/assets/logo-light.svg
new file mode 100644
index 0000000..f0e7d75
--- /dev/null
+++ b/src/assets/logo-light.svg
@@ -0,0 +1,42 @@
+
diff --git a/src/server/client-assets.ts b/src/server/client-assets.ts
index 949e162..eb1caa8 100644
--- a/src/server/client-assets.ts
+++ b/src/server/client-assets.ts
@@ -3,24 +3,31 @@ import type { ServerResponse } from "node:http";
const CLIENT_ASSET_PREFIX = "/client/";
const VENDOR_ASSET_PREFIX = "/vendor/";
+const APP_ASSET_PREFIX = "/assets/";
export function isClientAssetPath(pathname: string): boolean {
- return pathname.startsWith(CLIENT_ASSET_PREFIX) || pathname.startsWith(VENDOR_ASSET_PREFIX);
+ return (
+ pathname.startsWith(CLIENT_ASSET_PREFIX) ||
+ pathname.startsWith(VENDOR_ASSET_PREFIX) ||
+ pathname.startsWith(APP_ASSET_PREFIX)
+ );
}
export async function handleClientAssetRequest(url: URL, res: ServerResponse): Promise {
const isVendorAsset = url.pathname.startsWith(VENDOR_ASSET_PREFIX);
- const assetPrefix = isVendorAsset ? VENDOR_ASSET_PREFIX : CLIENT_ASSET_PREFIX;
+ const isAppAsset = url.pathname.startsWith(APP_ASSET_PREFIX);
+ const assetPrefix = isVendorAsset ? VENDOR_ASSET_PREFIX : isAppAsset ? APP_ASSET_PREFIX : CLIENT_ASSET_PREFIX;
const assetName = url.pathname.slice(assetPrefix.length);
- if (!isAllowedAssetPath(assetName, isVendorAsset)) {
+ if (!isAllowedAssetPath(assetName, isVendorAsset, isAppAsset)) {
res.statusCode = 404;
res.end("Not found");
return;
}
try {
- const assetUrl = new URL(`../${isVendorAsset ? "vendor" : "client"}/${assetName}`, import.meta.url);
+ const assetDirectory = isVendorAsset ? "vendor" : isAppAsset ? "assets" : "client";
+ const assetUrl = new URL(`../${assetDirectory}/${assetName}`, import.meta.url);
const source = await readFile(assetUrl);
res.statusCode = 200;
res.setHeader("content-type", getContentType(assetName));
@@ -32,11 +39,15 @@ export async function handleClientAssetRequest(url: URL, res: ServerResponse): P
}
}
-function isAllowedAssetPath(assetName: string, isVendorAsset: boolean): boolean {
+function isAllowedAssetPath(assetName: string, isVendorAsset: boolean, isAppAsset: boolean): boolean {
if (assetName.split("/").includes("..")) {
return false;
}
+ if (isAppAsset) {
+ return /^[a-z0-9_.-]+\.svg$/i.test(assetName);
+ }
+
if (isVendorAsset) {
return /^[a-z0-9/_.-]+\.(css|js|mjs|png)$/i.test(assetName);
}
@@ -53,5 +64,9 @@ function getContentType(assetName: string): string {
return "image/png";
}
+ if (assetName.endsWith(".svg")) {
+ return "image/svg+xml; charset=utf-8";
+ }
+
return "text/javascript; charset=utf-8";
}
diff --git a/src/server/page-template.ts b/src/server/page-template.ts
index d8734a5..88e4938 100644
--- a/src/server/page-template.ts
+++ b/src/server/page-template.ts
@@ -7,6 +7,7 @@ export function htmlPage(): string {
mapix
+