mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 10:51:36 +01:00
Differentiate blog and docs and sectionize headings
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
|
<div class="container">
|
||||||
{#if logoType === "text"}
|
{#if logoType === "text"}
|
||||||
<p>
|
<p>
|
||||||
<strong>{logo}</strong>
|
<strong>{logo}</strong>
|
||||||
@@ -24,16 +25,18 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<img src={logo} alt="" />
|
<img src={logo} alt="" />
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
|
<div class="container">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<div class="container">
|
||||||
{@html mainContent}
|
{@html mainContent}
|
||||||
</section>
|
|
||||||
{#if articles.length > 0}
|
{#if articles.length > 0}
|
||||||
<section>
|
<section>
|
||||||
<h2>Articles</h2>
|
<h2>Articles</h2>
|
||||||
@@ -53,8 +56,11 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
<div class="container">
|
||||||
{footerAdditionalText}
|
{footerAdditionalText}
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
45
web-app/src/lib/templates/docs/DocsEntry.svelte
Normal file
45
web-app/src/lib/templates/docs/DocsEntry.svelte
Normal 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>
|
||||||
60
web-app/src/lib/templates/docs/DocsIndex.svelte
Normal file
60
web-app/src/lib/templates/docs/DocsIndex.svelte
Normal 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>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import markdownit from "markdown-it";
|
import markdownit from "markdown-it";
|
||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
|
import type { StateCore } from "markdown-it/index.js";
|
||||||
import { dev } from "$app/environment";
|
import { dev } from "$app/environment";
|
||||||
|
|
||||||
export const sortOptions = [
|
export const sortOptions = [
|
||||||
@@ -14,15 +15,86 @@ export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/svg+xml", "
|
|||||||
export const md = markdownit({
|
export const md = markdownit({
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
highlight: (str, lang) => {
|
highlight: (str: string, lang: string) => {
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
return hljs.highlight(str, { language: lang }).value;
|
return hljs.highlight(str, { language: lang }).value;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
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";
|
export const API_BASE_PREFIX = dev ? "http://localhost:3000" : "/api";
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { API_BASE_PREFIX, NGINX_BASE_PREFIX } from "$lib/utils";
|
|||||||
import { render } from "svelte/server";
|
import { render } from "svelte/server";
|
||||||
import BlogIndex from "$lib/templates/blog/BlogIndex.svelte";
|
import BlogIndex from "$lib/templates/blog/BlogIndex.svelte";
|
||||||
import BlogArticle from "$lib/templates/blog/BlogArticle.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 }) => {
|
export const load: PageServerLoad = async ({ params, fetch, cookies }) => {
|
||||||
const websiteOverviewData = await fetch(
|
const websiteOverviewData = await fetch(
|
||||||
@@ -44,7 +46,13 @@ export const actions: Actions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => {
|
const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => {
|
||||||
const { head, body } = render(BlogIndex, {
|
let head = "";
|
||||||
|
let body = "";
|
||||||
|
|
||||||
|
switch (websiteData.content_type) {
|
||||||
|
case "Blog":
|
||||||
|
{
|
||||||
|
({ head, body } = render(BlogIndex, {
|
||||||
props: {
|
props: {
|
||||||
title: websiteData.title,
|
title: websiteData.title,
|
||||||
logoType: websiteData.logo_type,
|
logoType: websiteData.logo_type,
|
||||||
@@ -53,7 +61,24 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
|
|||||||
articles: websiteData.articles ?? [],
|
articles: websiteData.articles ?? [],
|
||||||
footerAdditionalText: websiteData.additional_text ?? ""
|
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);
|
const indexFileContents = head.concat(body);
|
||||||
|
|
||||||
@@ -67,12 +92,20 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
|
|||||||
|
|
||||||
await mkdir(uploadDir, { recursive: true });
|
await mkdir(uploadDir, { recursive: true });
|
||||||
await writeFile(join(uploadDir, "index.html"), indexFileContents);
|
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 ?? []) {
|
for (const article of websiteData.articles ?? []) {
|
||||||
const articleFileName = article.title.toLowerCase().split(" ").join("-");
|
const articleFileName = article.title.toLowerCase().split(" ").join("-");
|
||||||
|
|
||||||
const { head, body } = render(BlogArticle, {
|
let head = "";
|
||||||
|
let body = "";
|
||||||
|
|
||||||
|
switch (websiteData.content_type) {
|
||||||
|
case "Blog":
|
||||||
|
{
|
||||||
|
({ head, body } = render(BlogArticle, {
|
||||||
props: {
|
props: {
|
||||||
title: article.title,
|
title: article.title,
|
||||||
logoType: websiteData.logo_type,
|
logoType: websiteData.logo_type,
|
||||||
@@ -84,11 +117,38 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
|
|||||||
mainContent: md.render(article.main_content ?? ""),
|
mainContent: md.render(article.main_content ?? ""),
|
||||||
footerAdditionalText: websiteData.additional_text ?? ""
|
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);
|
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`, {
|
const styles = await readFile(`${process.cwd()}/template-styles/blog-styles.css`, {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
.editor {
|
.editor {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 2fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,13 +2,24 @@
|
|||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin-inline: auto;
|
|
||||||
inline-size: min(100% - 2rem, 75ch);
|
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin-inline: auto;
|
||||||
|
inline-size: min(100% - 2rem, 75ch);
|
||||||
|
}
|
||||||
|
|
||||||
img,
|
img,
|
||||||
picture,
|
picture,
|
||||||
svg,
|
svg,
|
||||||
@@ -16,3 +27,14 @@ video {
|
|||||||
max-inline-size: 100%;
|
max-inline-size: 100%;
|
||||||
block-size: auto;
|
block-size: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav,
|
||||||
|
header,
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
padding-block: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section + section {
|
||||||
|
margin-block-start: 1rem;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user