Use SvelteKit render function for templating and add error page

This commit is contained in:
thiloho
2024-08-17 19:29:10 +02:00
parent e41b963666
commit f3278fb1f6
11 changed files with 141 additions and 154 deletions

View File

@@ -100,7 +100,6 @@ in
User = cfg.user;
Group = cfg.group;
Restart = "always";
WorkingDirectory = "${cfg.package}/web-app";
};
script = ''

View File

@@ -32,23 +32,12 @@ let
cp -r db/migrations/* $out/rest-api/db/migrations
'';
};
templates = stdenv.mkDerivation {
inherit pname version;
name = "archtika-templates";
src = ../templates;
installPhase = ''
mkdir -p $out/templates
cp -r * $out/templates
'';
};
in
symlinkJoin {
name = pname;
paths = [
web
api
templates
];
meta = with lib; {

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../styles.css">
<title>Document</title>
</head>
<body>
<nav>
{{logo}}
</nav>
<header>
{{cover_image}}
{{title}}
{{publication_date}}
</header>
<main>
{{main_content}}
</main>
<footer>
{{additional_text}}
</footer>
</body>
</html>

View File

@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>Document</title>
</head>
<body>
<nav>
{{logo}}
</nav>
<header>
{{title}}
</header>
<main>
<section>
{{main_content}}
</section>
<section>
{{articles}}
</section>
</main>
<footer>
{{additional_text}}
</footer>
</body>
</html>

View File

@@ -1,18 +0,0 @@
:root {
color-scheme: light dark;
}
body {
margin-inline: auto;
inline-size: min(100% - 2rem, 75ch);
line-height: 1.5;
font-family: system-ui, sans-serif;
}
img,
picture,
svg,
video {
max-inline-size: 100%;
block-size: auto;
}

View File

@@ -0,0 +1,42 @@
<script lang="ts">
const { title, logoType, logo, mainContent, coverImage, publicationDate, footerAdditionalText } =
$props<{
title: string;
logoType: "text" | "image";
logo: string;
mainContent: string;
coverImage: string;
publicationDate: string;
footerAdditionalText: string;
}>();
</script>
<svelte:head>
<head>
<title>{title}</title>
</head>
</svelte:head>
<nav>
{#if logoType === "text"}
<p>
<strong>{logo}</strong>
</p>
{:else}
<img src={logo} alt="" />
{/if}
</nav>
<header>
<img src={coverImage} alt="" />
<h1>{title}</h1>
<p>{publicationDate}</p>
</header>
<main>
{@html mainContent}
</main>
<footer>
{footerAdditionalText}
</footer>

View File

@@ -0,0 +1,57 @@
<script lang="ts">
const { title, logoType, logo, mainContent, articles, footerAdditionalText } = $props<{
title: string;
logoType: "text" | "image";
logo: string;
mainContent: string;
articles: any[];
footerAdditionalText: string;
}>();
</script>
<svelte:head>
<head>
<title>{title}</title>
</head>
</svelte:head>
<nav>
{#if logoType === "text"}
<p>
<strong>{logo}</strong>
</p>
{:else}
<img src={logo} alt="" />
{/if}
</nav>
<header>
<h1>{title}</h1>
</header>
<main>
<section>
{@html mainContent}
</section>
{#if articles.length > 0}
<section>
<h2>Articles</h2>
{#each articles as article}
{@const articleFileName = article.title.toLowerCase().split(" ").join("-")}
<article>
<p>{article.publication_date}</p>
<h3>
<a href="./articles/{articleFileName}.html">{article.title}</a>
</h3>
<p>{article.meta_description ?? "No description provided"}</p>
</article>
{/each}
</section>
{/if}
</main>
<footer>
{footerAdditionalText}
</footer>

View File

@@ -0,0 +1,7 @@
<h1>Not found or access denied</h1>
<style>
h1 {
text-align: center;
}
</style>

View File

@@ -1,5 +1,6 @@
import type { LayoutServerLoad } from "./$types";
import { API_BASE_PREFIX } from "$lib/utils";
import { error } from "@sveltejs/kit";
export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
const websiteData = await fetch(`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}`, {
@@ -11,6 +12,10 @@ export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
}
});
if (!websiteData.ok) {
throw error(404, "Website not found");
}
const homeData = await fetch(`${API_BASE_PREFIX}/home?website_id=eq.${params.websiteId}`, {
method: "GET",
headers: {

View File

@@ -1,10 +1,13 @@
import { readFile, mkdir, writeFile } from "node:fs/promises";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { md } from "$lib/utils";
import type { Actions, PageServerLoad } from "./$types";
import { API_BASE_PREFIX, NGINX_BASE_PREFIX } from "$lib/utils";
import { render } from "svelte/server";
import BlogIndex from "$lib/templates/blog/BlogIndex.svelte";
import BlogArticle from "$lib/templates/blog/BlogArticle.svelte";
export const load: PageServerLoad = async ({ params, fetch, cookies, locals }) => {
export const load: PageServerLoad = async ({ params, fetch, cookies }) => {
const websiteOverviewData = await fetch(
`${API_BASE_PREFIX}/website_overview?id=eq.${params.websiteId}`,
{
@@ -41,56 +44,18 @@ export const actions: Actions = {
};
const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => {
const templatePath = join(
process.cwd(),
"..",
"templates",
websiteData.content_type.toLowerCase()
);
const indexFile = await readFile(join(templatePath, "index.html"), { encoding: "utf-8" });
const articleFile = await readFile(join(templatePath, "article.html"), {
encoding: "utf-8"
const { head, body } = render(BlogIndex, {
props: {
title: websiteData.title,
logoType: websiteData.logo_type,
logo: websiteData.logo_text,
mainContent: md.render(websiteData.main_content ?? ""),
articles: websiteData.articles ?? [],
footerAdditionalText: websiteData.additional_text ?? ""
}
});
const stylesFile = await readFile(join(templatePath, "styles.css"), { encoding: "utf-8" });
const indexFileContents = indexFile
.replace(
"{{logo}}",
websiteData.logo_type === "text"
? `<strong>${websiteData.logo_text}</strong>`
: `<img src="https://picsum.photos/32/32" />`
)
.replace("{{title}}", `<h1>${websiteData.title}</h1>`)
.replace("{{main_content}}", md.render(websiteData.main_content ?? ""))
.replace(
"{{articles}}",
Array.isArray(websiteData.articles) && websiteData.articles.length > 0
? `
<h2>Articles</h2>
${websiteData.articles
.map(
(article: { title: string; publication_date: string; meta_description: string }) => {
const articleFileName = article.title.toLowerCase().split(" ").join("-");
return `
<article>
<p>${article.publication_date}</p>
<h3>
<a href="./articles/${articleFileName}.html">
${article.title}
</a>
</h3>
<p>${article.meta_description ?? "No description provided"}</p>
</article>
`;
}
)
.join("")}
`
: "<h2>Articles</h2><p>No articles available at this time.</p>"
)
.replace("{{additional_text}}", md.render(websiteData.additional_text ?? ""));
const indexFileContents = head.concat(body);
let uploadDir = "";
@@ -109,32 +74,26 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
}
await mkdir(uploadDir, { recursive: true });
await writeFile(join(uploadDir, "index.html"), indexFileContents);
await mkdir(join(uploadDir, "articles"), { recursive: true });
for (const article of websiteData.articles ?? []) {
const articleFileName = article.title.toLowerCase().split(" ").join("-");
const articleFileContents = articleFile
.replace(
"{{logo}}",
websiteData.logo_type === "text"
? `<strong>${websiteData.logo_text}</strong>`
: `<img src="https://picsum.photos/32/32" />`
)
.replace(
"{{cover_image}}",
`<img src="${article.cover_image ? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}` : "https://picsum.photos/600/200"}" />`
)
.replace("{{title}}", `<h1>${article.title}</h1>`)
.replace("{{publication_date}}", `<p>${article.publication_date}</p>`)
.replace("{{main_content}}", md.render(article.main_content ?? ""))
.replace("{{additional_text}}", md.render(websiteData.additional_text ?? ""));
const { head, body } = render(BlogArticle, {
props: {
title: article.title,
logoType: websiteData.logo_type,
logo: websiteData.logo_text,
coverImage: `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`,
publicationDate: article.publication_date,
mainContent: md.render(article.main_content ?? ""),
footerAdditionalText: websiteData.additional_text ?? ""
}
});
const articleFileContents = head.concat(body);
await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents);
}
await writeFile(join(uploadDir, "styles.css"), stylesFile);
};

View File

@@ -6,7 +6,7 @@
const { data, children } = $props<{ data: LayoutServerData; children: Snippet }>();
const isProjectRoute = $derived($page.url.pathname.startsWith("/website"));
const isProjectRoute = $derived($page.url.pathname.startsWith("/website") && !$page.error);
const routeName = $derived(
$page.url.pathname === "/"
? "Dashboard"
@@ -39,7 +39,7 @@
</ul>
</nav>
{#if !isProjectRoute}
{#if !isProjectRoute && !$page.error}
<header>
<h1>{routeName}</h1>
</header>