diff options
| author | Joe Carstairs <me@joeac.net> | 2026-04-25 11:34:41 +0100 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2026-04-25 11:35:01 +0100 |
| commit | 7792e9633431ab918aee0a33483b6b16bce4069d (patch) | |
| tree | 509785d1ca3b5dc8387edc59a6c9d0b081ad61dd /website/src | |
| parent | 8744ba58c9bf46886a79cb3ca7df8480a90542d2 (diff) | |
factor out renderGemtextToHtml()
Diffstat (limited to 'website/src')
| -rw-r--r-- | website/src/pages/blog/[...slug].astro | 52 | ||||
| -rw-r--r-- | website/src/renderGemtextToHtml.ts | 72 |
2 files changed, 74 insertions, 50 deletions
diff --git a/website/src/pages/blog/[...slug].astro b/website/src/pages/blog/[...slug].astro index c362dfc..54cf9ac 100644 --- a/website/src/pages/blog/[...slug].astro +++ b/website/src/pages/blog/[...slug].astro @@ -1,7 +1,7 @@ --- import { type CollectionEntry, getCollection, render } from 'astro:content'; import BlogPost from '../../layouts/BlogPost.astro'; -import * as Gemtext from 'gemtext'; +import { renderGemtextToHtml } from '../../renderGemtextToHtml'; export async function getStaticPaths() { const posts = await getCollection('blog'); @@ -12,57 +12,9 @@ export async function getStaticPaths() { } type Props = CollectionEntry<'blog'>; -function htmlEscape(str: string) { - return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); -} - -const GemtextHTMLRenderer: Gemtext.Renderer<string> = { - preamble: function (): string { - return ''; - }, - postamble: function (): string { - return ''; - }, - text: function (content: string): string { - content = content.trim(); - if (content === '') { - return ''; - } - return `<p>${htmlEscape(content)}</p>` - }, - link: function (url: string, alt: string): string { - const imgExtensions: (string | undefined)[] = ['webp', 'png', 'svg', 'gif', 'jpg', 'jpeg', 'apng'] - if (imgExtensions.includes(url.split('.').at(-1)?.toLowerCase())) { - return `<img src="${url}" alt="${alt}" />`; - } - return `<p>=> <a href="${url}">${alt}</a></p>`; - }, - preformatted: function (content: string[], alt: string): string { - return `<pre alt="${alt}">${htmlEscape(content.join('\n'))}</pre>` - }, - heading: function (level: number, text: string): string { - return `<h${level}>${htmlEscape(text)}</h${level}>`; - }, - unorderedList: function (content: string[]): string { - return `<ul>${content.map(li=>`<li>${htmlEscape(li)}</li>`).join('')}</ul>`; - }, - quote: function (content: string): string { - return `<blockquote>${htmlEscape(content)}</blockquote>`; - } -}; - -function postProcessGemtextToHtml(html: string): string { - html = mergeAdjacentBlockquotes(html); - return html; -} - -function mergeAdjacentBlockquotes(html: string): string { - return html.replaceAll(/\<\/blockquote\>\<blockquote\>/gm, '<br>'); -} - const post = Astro.props; const Content = post.filePath?.endsWith('.gmi') - ? postProcessGemtextToHtml(Gemtext.parse((post.body ?? '').replace(`# ${post.data.title}`, '')).generate(GemtextHTMLRenderer)) + ? renderGemtextToHtml(post.body ?? '', post.data.title) : (await render(post)).Content; --- diff --git a/website/src/renderGemtextToHtml.ts b/website/src/renderGemtextToHtml.ts new file mode 100644 index 0000000..f934f1e --- /dev/null +++ b/website/src/renderGemtextToHtml.ts @@ -0,0 +1,72 @@ +import * as Gemtext from "gemtext"; + +export function renderGemtextToHtml(gemtext: string, title: string): string { + return postProcessGemtextToHtml( + Gemtext.parse(gemtext.replace(`# ${title}`, "")).generate( + GemtextHTMLRenderer, + ), + ); +} + +const GemtextHTMLRenderer: Gemtext.Renderer<string> = { + preamble: function (): string { + return ""; + }, + + postamble: function (): string { + return ""; + }, + + text: function (content: string): string { + content = content.trim(); + if (content === "") { + return ""; + } + return `<p>${htmlEscape(content)}</p>`; + }, + + link: function (url: string, alt: string): string { + const imgExtensions: (string | undefined)[] = [ + "webp", + "png", + "svg", + "gif", + "jpg", + "jpeg", + "apng", + ]; + if (imgExtensions.includes(url.split(".").at(-1)?.toLowerCase())) { + return `<img src="${url}" alt="${alt}" />`; + } + return `<p>=> <a href="${url}">${alt}</a></p>`; + }, + + preformatted: function (content: string[], alt: string): string { + return `<pre alt="${alt}">${htmlEscape(content.join("\n"))}</pre>`; + }, + + heading: function (level: number, text: string): string { + return `<h${level}>${htmlEscape(text)}</h${level}>`; + }, + + unorderedList: function (content: string[]): string { + return `<ul>${content.map((li) => `<li>${htmlEscape(li)}</li>`).join("")}</ul>`; + }, + + quote: function (content: string): string { + return `<blockquote>${htmlEscape(content)}</blockquote>`; + }, +}; + +function htmlEscape(str: string) { + return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); +} + +function postProcessGemtextToHtml(html: string): string { + html = mergeAdjacentBlockquotes(html); + return html; +} + +function mergeAdjacentBlockquotes(html: string): string { + return html.replaceAll(/\<\/blockquote\>\<blockquote\>/gm, "<br>"); +} |
