mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 10:51:36 +01:00
Make markdown synchronous and add ability to toggle TOC
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
button,
|
button,
|
||||||
label,
|
label,
|
||||||
select,
|
select,
|
||||||
summary,
|
|
||||||
[role="button"],
|
[role="button"],
|
||||||
[role="option"],
|
[role="option"],
|
||||||
label[for="toggle-mobile-preview"] {
|
label[for="toggle-mobile-preview"] {
|
||||||
@@ -14,7 +13,7 @@ textarea,
|
|||||||
select,
|
select,
|
||||||
a[role="button"],
|
a[role="button"],
|
||||||
label[for="toggle-mobile-preview"],
|
label[for="toggle-mobile-preview"],
|
||||||
summary {
|
:not(.table-of-contents) > summary {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border: var(--border-primary);
|
border: var(--border-primary);
|
||||||
@@ -47,22 +46,27 @@ a[role="button"] {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary {
|
:not(.table-of-contents) > summary {
|
||||||
max-inline-size: fit-content;
|
max-inline-size: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
a[role="button"],
|
a[role="button"],
|
||||||
label[for="toggle-mobile-preview"],
|
label[for="toggle-mobile-preview"],
|
||||||
summary {
|
:not(.table-of-contents) > summary {
|
||||||
background-color: var(--bg-secondary);
|
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);
|
background-color: var(--bg-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(a, button, input, textarea, select, summary):focus,
|
:is(button, input, textarea, select):focus,
|
||||||
#toggle-mobile-preview:checked + label {
|
#toggle-mobile-preview:checked + label {
|
||||||
outline: 0.125rem solid var(--color-accent);
|
outline: 0.125rem solid var(--color-accent);
|
||||||
outline-offset: 0.25rem;
|
outline-offset: 0.25rem;
|
||||||
|
|||||||
@@ -56,11 +56,7 @@
|
|||||||
{#if fullPreview}
|
{#if fullPreview}
|
||||||
<iframe src={previewContent} title="Preview"></iframe>
|
<iframe src={previewContent} title="Preview"></iframe>
|
||||||
{:else}
|
{:else}
|
||||||
{#await md(previewContent)}
|
{@html md(previewContent)}
|
||||||
<p>Loading preview...</p>
|
|
||||||
{:then content}
|
|
||||||
{@html content}
|
|
||||||
{/await}
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,10 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
{@html mainContent}
|
{@html mainContent}
|
||||||
{#if articles.length > 0}
|
{#if articles.length > 0}
|
||||||
<section class="articles">
|
<section class="articles" id="articles">
|
||||||
<h2>Articles</h2>
|
<h2>
|
||||||
|
<a href="#articles">Articles</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
<ul class="unpadded">
|
<ul class="unpadded">
|
||||||
{#each articles as article}
|
{#each articles as article}
|
||||||
|
|||||||
@@ -13,18 +13,18 @@ export const sortOptions = [
|
|||||||
|
|
||||||
export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/svg+xml", "image/webp"];
|
export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/svg+xml", "image/webp"];
|
||||||
|
|
||||||
const createMarkdownParser = () => {
|
const createMarkdownParser = (showToc = true) => {
|
||||||
const marked = new Marked();
|
const marked = new Marked();
|
||||||
|
|
||||||
marked.use({
|
marked.use({
|
||||||
async: true,
|
async: false,
|
||||||
pedantic: false,
|
pedantic: false,
|
||||||
gfm: true
|
gfm: true
|
||||||
});
|
});
|
||||||
|
|
||||||
marked.use(
|
marked.use(
|
||||||
markedHighlight({
|
markedHighlight({
|
||||||
async: true,
|
async: false,
|
||||||
langPrefix: "language-",
|
langPrefix: "language-",
|
||||||
highlight(code, lang) {
|
highlight(code, lang) {
|
||||||
const language = hljs.getLanguage(lang) ? lang : "plaintext";
|
const language = hljs.getLanguage(lang) ? lang : "plaintext";
|
||||||
@@ -52,7 +52,7 @@ const createMarkdownParser = () => {
|
|||||||
let headings: { text: string; raw: string; level: number; id: string }[] = [];
|
let headings: { text: string; raw: string; level: number; id: string }[] = [];
|
||||||
let sectionStack: { level: number; id: string }[] = [];
|
let sectionStack: { level: number; id: string }[] = [];
|
||||||
|
|
||||||
function gfmHeadingId({ prefix = "" } = {}) {
|
function gfmHeadingId({ prefix = "", showToc = true } = {}) {
|
||||||
return {
|
return {
|
||||||
renderer: {
|
renderer: {
|
||||||
heading(this: Renderer, { tokens, depth }: { tokens: Token[]; depth: number }) {
|
heading(this: Renderer, { tokens, depth }: { tokens: Token[]; depth: number }) {
|
||||||
@@ -65,14 +65,12 @@ const createMarkdownParser = () => {
|
|||||||
const heading = { level, text, id, raw };
|
const heading = { level, text, id, raw };
|
||||||
headings.push(heading);
|
headings.push(heading);
|
||||||
|
|
||||||
// Close any sections that are at a higher level than the current heading
|
|
||||||
let closingSections = "";
|
let closingSections = "";
|
||||||
while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1].level >= level) {
|
while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1].level >= level) {
|
||||||
sectionStack.pop();
|
sectionStack.pop();
|
||||||
closingSections += "</section>";
|
closingSections += "</section>";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a new section for this heading
|
|
||||||
sectionStack.push({ level, id });
|
sectionStack.push({ level, id });
|
||||||
const openingSection = `<section id="${id}">`;
|
const openingSection = `<section id="${id}">`;
|
||||||
|
|
||||||
@@ -94,13 +92,11 @@ const createMarkdownParser = () => {
|
|||||||
return src;
|
return src;
|
||||||
},
|
},
|
||||||
postprocess(html: string) {
|
postprocess(html: string) {
|
||||||
// Close any remaining open sections
|
|
||||||
const closingRemainingSection = "</section>".repeat(sectionStack.length);
|
const closingRemainingSection = "</section>".repeat(sectionStack.length);
|
||||||
|
|
||||||
// Generate table of contents
|
|
||||||
const tableOfContents =
|
const tableOfContents =
|
||||||
headings.length > 0
|
showToc && headings.length > 0
|
||||||
? `<details>
|
? `<details class="table-of-contents">
|
||||||
<summary>Table of contents</summary>
|
<summary>Table of contents</summary>
|
||||||
<ul>
|
<ul>
|
||||||
${headings
|
${headings
|
||||||
@@ -123,17 +119,16 @@ const createMarkdownParser = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
marked.use(gfmHeadingId());
|
marked.use(gfmHeadingId({ showToc: showToc }));
|
||||||
|
|
||||||
return marked;
|
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) => {
|
return html as string;
|
||||||
const html = await marked.parse(markdownContent);
|
|
||||||
|
|
||||||
return html;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => {
|
export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
|
|||||||
title: websiteData.title,
|
title: websiteData.title,
|
||||||
logoType: websiteData.logo_type,
|
logoType: websiteData.logo_type,
|
||||||
logo: websiteData.logo_text,
|
logo: websiteData.logo_text,
|
||||||
mainContent: await md(websiteData.main_content ?? ""),
|
mainContent: md(websiteData.main_content ?? "", false),
|
||||||
articles: websiteData.articles ?? [],
|
articles: websiteData.articles ?? [],
|
||||||
footerAdditionalText: websiteData.additional_text ?? ""
|
footerAdditionalText: websiteData.additional_text ?? ""
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
|
|||||||
title: websiteData.title,
|
title: websiteData.title,
|
||||||
logoType: websiteData.logo_type,
|
logoType: websiteData.logo_type,
|
||||||
logo: websiteData.logo_text,
|
logo: websiteData.logo_text,
|
||||||
mainContent: await md(websiteData.main_content ?? ""),
|
mainContent: md(websiteData.main_content ?? "", false),
|
||||||
articles: websiteData.articles ?? [],
|
articles: websiteData.articles ?? [],
|
||||||
footerAdditionalText: websiteData.additional_text ?? ""
|
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}`
|
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`
|
||||||
: "",
|
: "",
|
||||||
publicationDate: article.publication_date,
|
publicationDate: article.publication_date,
|
||||||
mainContent: await md(article.main_content ?? ""),
|
mainContent: md(article.main_content ?? ""),
|
||||||
footerAdditionalText: websiteData.additional_text ?? ""
|
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}`
|
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`
|
||||||
: "",
|
: "",
|
||||||
publicationDate: article.publication_date,
|
publicationDate: article.publication_date,
|
||||||
mainContent: await md(article.main_content ?? ""),
|
mainContent: md(article.main_content ?? ""),
|
||||||
footerAdditionalText: websiteData.additional_text ?? ""
|
footerAdditionalText: websiteData.additional_text ?? ""
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -30,3 +30,7 @@ footer {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-of-contents {
|
||||||
|
margin-block-end: var(--space-s);
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,3 +210,12 @@ td {
|
|||||||
padding-block: var(--space-3xs);
|
padding-block: var(--space-3xs);
|
||||||
border: var(--border-primary);
|
border: var(--border-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(a, summary):focus {
|
||||||
|
outline: 0.125rem solid var(--color-accent);
|
||||||
|
outline-offset: 0.25rem;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user