diff --git a/web-app/src/lib/templates/blog/BlogIndex.svelte b/web-app/src/lib/templates/blog/BlogIndex.svelte index a3a3a21..31e93a8 100644 --- a/web-app/src/lib/templates/blog/BlogIndex.svelte +++ b/web-app/src/lib/templates/blog/BlogIndex.svelte @@ -17,44 +17,50 @@
-

{title}

+
+

{title}

+
-
+
{@html mainContent} -
- {#if articles.length > 0} -
-

Articles

+ {#if articles.length > 0} +
+

Articles

- {#each articles as article} - {@const articleFileName = article.title.toLowerCase().split(" ").join("-")} + {#each articles as article} + {@const articleFileName = article.title.toLowerCase().split(" ").join("-")} -
-

{article.publication_date}

-

- {article.title} -

- {#if article.meta_description} -

{article.meta_description}

- {/if} -
- {/each} -
- {/if} +
+

{article.publication_date}

+

+ {article.title} +

+ {#if article.meta_description} +

{article.meta_description}

+ {/if} +
+ {/each} +
+ {/if} +
diff --git a/web-app/src/lib/templates/docs/DocsEntry.svelte b/web-app/src/lib/templates/docs/DocsEntry.svelte new file mode 100644 index 0000000..66e6397 --- /dev/null +++ b/web-app/src/lib/templates/docs/DocsEntry.svelte @@ -0,0 +1,45 @@ + + + + + {title} + + + + + + +
+ {#if coverImage} + + {/if} +

{title}

+

{publicationDate}

+
+ +
+ {@html mainContent} +
+ + diff --git a/web-app/src/lib/templates/docs/DocsIndex.svelte b/web-app/src/lib/templates/docs/DocsIndex.svelte new file mode 100644 index 0000000..8eaf5ae --- /dev/null +++ b/web-app/src/lib/templates/docs/DocsIndex.svelte @@ -0,0 +1,60 @@ + + + + + {title} + + + + + + +
+

{title}

+
+ +
+
+ {@html mainContent} +
+ {#if articles.length > 0} +
+

Articles

+ + {#each articles as article} + {@const articleFileName = article.title.toLowerCase().split(" ").join("-")} + +
+

{article.publication_date}

+

+ {article.title} +

+ {#if article.meta_description} +

{article.meta_description}

+ {/if} +
+ {/each} +
+ {/if} +
+ + diff --git a/web-app/src/lib/utils.ts b/web-app/src/lib/utils.ts index 9cd2a2c..4c6a1e5 100644 --- a/web-app/src/lib/utils.ts +++ b/web-app/src/lib/utils.ts @@ -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"; diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts index d9fa1e1..408e14e 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts @@ -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`, { diff --git a/web-app/src/routes/+layout.svelte b/web-app/src/routes/+layout.svelte index 5abf5c5..b5a4519 100644 --- a/web-app/src/routes/+layout.svelte +++ b/web-app/src/routes/+layout.svelte @@ -98,7 +98,7 @@ @media (min-width: 640px) { .editor { - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr 2fr; } } diff --git a/web-app/template-styles/blog-styles.css b/web-app/template-styles/blog-styles.css index 30939ae..1c5a41f 100644 --- a/web-app/template-styles/blog-styles.css +++ b/web-app/template-styles/blog-styles.css @@ -2,13 +2,24 @@ color-scheme: light dark; } +*, +*::before, +*::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + body { - margin-inline: auto; - inline-size: min(100% - 2rem, 75ch); line-height: 1.5; font-family: system-ui, sans-serif; } +.container { + margin-inline: auto; + inline-size: min(100% - 2rem, 75ch); +} + img, picture, svg, @@ -16,3 +27,14 @@ video { max-inline-size: 100%; block-size: auto; } + +nav, +header, +main, +footer { + padding-block: 1rem; +} + +section + section { + margin-block-start: 1rem; +}