Differentiate blog and docs and sectionize headings

This commit is contained in:
thiloho
2024-08-18 18:17:59 +02:00
parent 9ac950e8a1
commit 96d4d5aaa6
7 changed files with 323 additions and 58 deletions

View File

@@ -17,44 +17,50 @@
</svelte:head>
<nav>
{#if logoType === "text"}
<p>
<strong>{logo}</strong>
</p>
{:else}
<img src={logo} alt="" />
{/if}
<div class="container">
{#if logoType === "text"}
<p>
<strong>{logo}</strong>
</p>
{:else}
<img src={logo} alt="" />
{/if}
</div>
</nav>
<header>
<h1>{title}</h1>
<div class="container">
<h1>{title}</h1>
</div>
</header>
<main>
<section>
<div class="container">
{@html mainContent}
</section>
{#if articles.length > 0}
<section>
<h2>Articles</h2>
{#if articles.length > 0}
<section>
<h2>Articles</h2>
{#each articles as article}
{@const articleFileName = article.title.toLowerCase().split(" ").join("-")}
{#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>
{#if article.meta_description}
<p>{article.meta_description}</p>
{/if}
</article>
{/each}
</section>
{/if}
<article>
<p>{article.publication_date}</p>
<h3>
<a href="./articles/{articleFileName}.html">{article.title}</a>
</h3>
{#if article.meta_description}
<p>{article.meta_description}</p>
{/if}
</article>
{/each}
</section>
{/if}
</div>
</main>
<footer>
{footerAdditionalText}
<div class="container">
{footerAdditionalText}
</div>
</footer>

View File

@@ -0,0 +1,45 @@
<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>
<link rel="stylesheet" href="../styles.css" />
</head>
</svelte:head>
<nav>
{#if logoType === "text"}
<p>
<strong>{logo}</strong>
</p>
{:else}
<img src={logo} alt="" />
{/if}
</nav>
<header>
{#if coverImage}
<img src={coverImage} alt="" />
{/if}
<h1>{title}</h1>
<p>{publicationDate}</p>
</header>
<main>
{@html mainContent}
</main>
<footer>
{footerAdditionalText}
</footer>

View File

@@ -0,0 +1,60 @@
<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>
<link rel="stylesheet" href="./styles.css" />
</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="./documents/{articleFileName}.html">{article.title}</a>
</h3>
{#if article.meta_description}
<p>{article.meta_description}</p>
{/if}
</article>
{/each}
</section>
{/if}
</main>
<footer>
{footerAdditionalText}
</footer>

View File

@@ -1,5 +1,6 @@
import markdownit from "markdown-it";
import hljs from "highlight.js";
import type { StateCore } from "markdown-it/index.js";
import { dev } from "$app/environment";
export const sortOptions = [
@@ -14,15 +15,86 @@ export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/svg+xml", "
export const md = markdownit({
linkify: true,
typographer: true,
highlight: (str, lang) => {
highlight: (str: string, lang: string) => {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value;
} catch (_) {}
}
return "";
}
}).use((md) => {
const addSections = (state: StateCore) => {
const tokens = [];
const Token = state.Token;
const sections: { header: number; nesting: number }[] = [];
let nestedLevel = 0;
const openSection = (attrs: [string, string][] | null) => {
const t = new Token("section_open", "section", 1);
t.block = true;
t.attrs = attrs ? attrs.map((attr) => [attr[0], attr[1]]) : null;
return t;
};
const closeSection = () => {
const t = new Token("section_close", "section", -1);
t.block = true;
return t;
};
const closeSections = (section: { header: number; nesting: number }) => {
while (sections.length && section.header <= sections[sections.length - 1].header) {
sections.pop();
tokens.push(closeSection());
}
};
const closeSectionsToCurrentNesting = (nesting: number) => {
while (sections.length && nesting < sections[sections.length - 1].nesting) {
sections.pop();
tokens.push(closeSection());
}
};
const closeAllSections = () => {
while (sections.pop()) {
tokens.push(closeSection());
}
};
for (const token of state.tokens) {
if (token.type.search("heading") !== 0) {
nestedLevel += token.nesting;
}
if (sections.length && nestedLevel < sections[sections.length - 1].nesting) {
closeSectionsToCurrentNesting(nestedLevel);
}
if (token.type === "heading_open") {
const section: { header: number; nesting: number } = {
header: parseInt(token.tag.charAt(1)),
nesting: nestedLevel
};
if (sections.length && section.header <= sections[sections.length - 1].header) {
closeSections(section);
}
tokens.push(openSection(token.attrs));
const idIndex = token.attrIndex("id");
if (idIndex !== -1) {
token.attrs?.splice(idIndex, 1);
}
sections.push(section);
}
tokens.push(token);
}
closeAllSections();
state.tokens = tokens;
};
md.core.ruler.push("header_sections", addSections);
});
export const API_BASE_PREFIX = dev ? "http://localhost:3000" : "/api";

View File

@@ -6,6 +6,8 @@ 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";
import DocsIndex from "$lib/templates/docs/DocsIndex.svelte";
import DocsEntry from "$lib/templates/docs/DocsEntry.svelte";
export const load: PageServerLoad = async ({ params, fetch, cookies }) => {
const websiteOverviewData = await fetch(
@@ -44,16 +46,39 @@ export const actions: Actions = {
};
const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => {
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 ?? ""
}
});
let head = "";
let body = "";
switch (websiteData.content_type) {
case "Blog":
{
({ 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 ?? ""
}
}));
}
break;
case "Docs":
{
({ head, body } = render(DocsIndex, {
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 ?? ""
}
}));
}
break;
}
const indexFileContents = head.concat(body);
@@ -67,28 +92,63 @@ 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 });
await mkdir(join(uploadDir, websiteData.content_type === "Blog" ? "articles" : "documents"), {
recursive: true
});
for (const article of websiteData.articles ?? []) {
const articleFileName = article.title.toLowerCase().split(" ").join("-");
const { head, body } = render(BlogArticle, {
props: {
title: article.title,
logoType: websiteData.logo_type,
logo: websiteData.logo_text,
coverImage: article.cover_image
? `${API_BASE_PREFIX === "/api" ? `${process.env.ORIGIN}/api` : API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`
: "",
publicationDate: article.publication_date,
mainContent: md.render(article.main_content ?? ""),
footerAdditionalText: websiteData.additional_text ?? ""
}
});
let head = "";
let body = "";
switch (websiteData.content_type) {
case "Blog":
{
({ head, body } = render(BlogArticle, {
props: {
title: article.title,
logoType: websiteData.logo_type,
logo: websiteData.logo_text,
coverImage: article.cover_image
? `${API_BASE_PREFIX === "/api" ? `${process.env.ORIGIN}/api` : API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`
: "",
publicationDate: article.publication_date,
mainContent: md.render(article.main_content ?? ""),
footerAdditionalText: websiteData.additional_text ?? ""
}
}));
}
break;
case "Docs":
{
({ head, body } = render(DocsEntry, {
props: {
title: article.title,
logoType: websiteData.logo_type,
logo: websiteData.logo_text,
coverImage: article.cover_image
? `${API_BASE_PREFIX === "/api" ? `${process.env.ORIGIN}/api` : API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`
: "",
publicationDate: article.publication_date,
mainContent: md.render(article.main_content ?? ""),
footerAdditionalText: websiteData.additional_text ?? ""
}
}));
}
break;
}
const articleFileContents = head.concat(body);
await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents);
await writeFile(
join(
uploadDir,
websiteData.content_type === "Blog" ? "articles" : "documents",
`${articleFileName}.html`
),
articleFileContents
);
}
const styles = await readFile(`${process.cwd()}/template-styles/blog-styles.css`, {

View File

@@ -98,7 +98,7 @@
@media (min-width: 640px) {
.editor {
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr 2fr;
}
}
</style>