summaryrefslogtreecommitdiff
path: root/website
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2026-04-25 11:34:41 +0100
committerJoe Carstairs <me@joeac.net>2026-04-25 11:35:01 +0100
commit7792e9633431ab918aee0a33483b6b16bce4069d (patch)
tree509785d1ca3b5dc8387edc59a6c9d0b081ad61dd /website
parent8744ba58c9bf46886a79cb3ca7df8480a90542d2 (diff)
factor out renderGemtextToHtml()
Diffstat (limited to 'website')
-rw-r--r--website/src/pages/blog/[...slug].astro52
-rw-r--r--website/src/renderGemtextToHtml.ts72
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
-}
-
-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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+}
+
+function postProcessGemtextToHtml(html: string): string {
+ html = mergeAdjacentBlockquotes(html);
+ return html;
+}
+
+function mergeAdjacentBlockquotes(html: string): string {
+ return html.replaceAll(/\<\/blockquote\>\<blockquote\>/gm, "<br>");
+}