diff --git a/web-app/src/app.css b/web-app/src/app.css index 4c5d928..17a2f8c 100644 --- a/web-app/src/app.css +++ b/web-app/src/app.css @@ -1,7 +1,6 @@ button, label, select, -summary, [role="button"], [role="option"], label[for="toggle-mobile-preview"] { @@ -14,7 +13,7 @@ textarea, select, a[role="button"], label[for="toggle-mobile-preview"], -summary { +:not(.table-of-contents) > summary { font: inherit; color: inherit; border: var(--border-primary); @@ -47,22 +46,27 @@ a[role="button"] { text-decoration: none; } -summary { +:not(.table-of-contents) > summary { max-inline-size: fit-content; } button, a[role="button"], label[for="toggle-mobile-preview"], -summary { +:not(.table-of-contents) > summary { background-color: var(--bg-secondary); } -:is(button, a[role="button"], label[for="toggle-mobile-preview"], summary):hover { +:is( + button, + a[role="button"], + label[for="toggle-mobile-preview"], + :not(.table-of-contents) > summary + ):hover { background-color: var(--bg-tertiary); } -:is(a, button, input, textarea, select, summary):focus, +:is(button, input, textarea, select):focus, #toggle-mobile-preview:checked + label { outline: 0.125rem solid var(--color-accent); outline-offset: 0.25rem; diff --git a/web-app/src/lib/components/WebsiteEditor.svelte b/web-app/src/lib/components/WebsiteEditor.svelte index 6722f8d..4d0c07e 100644 --- a/web-app/src/lib/components/WebsiteEditor.svelte +++ b/web-app/src/lib/components/WebsiteEditor.svelte @@ -56,11 +56,7 @@ {#if fullPreview} {:else} - {#await md(previewContent)} -

Loading preview...

- {:then content} - {@html content} - {/await} + {@html md(previewContent)} {/if} diff --git a/web-app/src/lib/templates/blog/BlogIndex.svelte b/web-app/src/lib/templates/blog/BlogIndex.svelte index 2659c89..5852c4a 100644 --- a/web-app/src/lib/templates/blog/BlogIndex.svelte +++ b/web-app/src/lib/templates/blog/BlogIndex.svelte @@ -34,8 +34,10 @@
{@html mainContent} {#if articles.length > 0} -
-

Articles

+
+

+ Articles +

    {#each articles as article} diff --git a/web-app/src/lib/utils.ts b/web-app/src/lib/utils.ts index 3a297d2..96d28c7 100644 --- a/web-app/src/lib/utils.ts +++ b/web-app/src/lib/utils.ts @@ -13,18 +13,18 @@ export const sortOptions = [ export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/svg+xml", "image/webp"]; -const createMarkdownParser = () => { +const createMarkdownParser = (showToc = true) => { const marked = new Marked(); marked.use({ - async: true, + async: false, pedantic: false, gfm: true }); marked.use( markedHighlight({ - async: true, + async: false, langPrefix: "language-", highlight(code, lang) { const language = hljs.getLanguage(lang) ? lang : "plaintext"; @@ -52,7 +52,7 @@ const createMarkdownParser = () => { let headings: { text: string; raw: string; level: number; id: string }[] = []; let sectionStack: { level: number; id: string }[] = []; - function gfmHeadingId({ prefix = "" } = {}) { + function gfmHeadingId({ prefix = "", showToc = true } = {}) { return { renderer: { heading(this: Renderer, { tokens, depth }: { tokens: Token[]; depth: number }) { @@ -65,14 +65,12 @@ const createMarkdownParser = () => { const heading = { level, text, id, raw }; headings.push(heading); - // Close any sections that are at a higher level than the current heading let closingSections = ""; while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1].level >= level) { sectionStack.pop(); closingSections += "
"; } - // Open a new section for this heading sectionStack.push({ level, id }); const openingSection = `
`; @@ -94,13 +92,11 @@ const createMarkdownParser = () => { return src; }, postprocess(html: string) { - // Close any remaining open sections const closingRemainingSection = "
".repeat(sectionStack.length); - // Generate table of contents const tableOfContents = - headings.length > 0 - ? `
+ showToc && headings.length > 0 + ? `
Table of contents
    ${headings @@ -123,17 +119,16 @@ const createMarkdownParser = () => { }; } - marked.use(gfmHeadingId()); + marked.use(gfmHeadingId({ showToc: showToc })); return marked; }; -const marked = createMarkdownParser(); +export const md = (markdownContent: string, showToc = true) => { + const marked = createMarkdownParser(showToc); + const html = marked.parse(markdownContent); -export const md = async (markdownContent: string) => { - const html = await marked.parse(markdownContent); - - return html; + return html as string; }; export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => { 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 a14327f..435dece 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 @@ -60,7 +60,7 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) title: websiteData.title, logoType: websiteData.logo_type, logo: websiteData.logo_text, - mainContent: await md(websiteData.main_content ?? ""), + mainContent: md(websiteData.main_content ?? "", false), articles: websiteData.articles ?? [], footerAdditionalText: websiteData.additional_text ?? "" } @@ -74,7 +74,7 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) title: websiteData.title, logoType: websiteData.logo_type, logo: websiteData.logo_text, - mainContent: await md(websiteData.main_content ?? ""), + mainContent: md(websiteData.main_content ?? "", false), articles: websiteData.articles ?? [], footerAdditionalText: websiteData.additional_text ?? "" } @@ -117,7 +117,7 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) ? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}` : "", publicationDate: article.publication_date, - mainContent: await md(article.main_content ?? ""), + mainContent: md(article.main_content ?? ""), footerAdditionalText: websiteData.additional_text ?? "" } })); @@ -134,7 +134,7 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) ? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}` : "", publicationDate: article.publication_date, - mainContent: await md(article.main_content ?? ""), + mainContent: md(article.main_content ?? ""), footerAdditionalText: websiteData.additional_text ?? "" } })); diff --git a/web-app/template-styles/blog-styles.css b/web-app/template-styles/blog-styles.css index 9e91dbf..a367443 100644 --- a/web-app/template-styles/blog-styles.css +++ b/web-app/template-styles/blog-styles.css @@ -30,3 +30,7 @@ footer { flex-direction: column; gap: var(--space-xs); } + +.table-of-contents { + margin-block-end: var(--space-s); +} diff --git a/web-app/template-styles/common-styles.css b/web-app/template-styles/common-styles.css index 8ce63fd..308faae 100644 --- a/web-app/template-styles/common-styles.css +++ b/web-app/template-styles/common-styles.css @@ -210,3 +210,12 @@ td { padding-block: var(--space-3xs); border: var(--border-primary); } + +summary { + cursor: pointer; +} + +:is(a, summary):focus { + outline: 0.125rem solid var(--color-accent); + outline-offset: 0.25rem; +}