Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b590a2ffef | |||
| 52b254ff59 | |||
| f78387413a | |||
| f17061b4cb | |||
| 00effae167 | |||
| e50b62afa2 | |||
| 6470d7f8bd | |||
| 5a56c572af | |||
| ac8011ddb1 | |||
| d98908ad5c | |||
| 6d58eee37f | |||
| fab6515531 | |||
| 60f6f4cd80 | |||
| 7792e96334 |
@@ -5,6 +5,7 @@
|
|||||||
"start": "astro build --remote && node ./dist/server/entry.mjs",
|
"start": "astro build --remote && node ./dist/server/entry.mjs",
|
||||||
"build": "astro build --remote",
|
"build": "astro build --remote",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
|
"sync": "astro sync",
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
.h-feed .h-entry .p-name {
|
.h-feed .h-entry .p-name {
|
||||||
font-size: var(--font-size-md);
|
font-size: var(--font-size-md);
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-feed .full-feed-link {
|
.h-feed .full-feed-link {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ function pubDate(post: CollectionEntry<'blog'>): Date {
|
|||||||
|
|
||||||
const HeadingElem = `h${headingLevel} class="p-name"`;
|
const HeadingElem = `h${headingLevel} class="p-name"`;
|
||||||
const SubHeadingElem = `h${headingLevel + 1}`;
|
const SubHeadingElem = `h${headingLevel + 1}`;
|
||||||
|
const SubSubHeadingElem = `h${headingLevel + 2}`;
|
||||||
const AuthorElem = `p${hideAuthor ? " hidden" : ""}`;
|
const AuthorElem = `p${hideAuthor ? " hidden" : ""}`;
|
||||||
|
|
||||||
const canonicalBlogUrl = new URL('blog', Astro.site)
|
const canonicalBlogUrl = new URL('blog', Astro.site)
|
||||||
@@ -72,7 +73,9 @@ const canonicalBlogUrl = new URL('blog', Astro.site)
|
|||||||
? <ul>
|
? <ul>
|
||||||
{ posts.sort(sortByPubDateDescending).map(post => (
|
{ posts.sort(sortByPubDateDescending).map(post => (
|
||||||
<li class="h-entry">
|
<li class="h-entry">
|
||||||
|
<SubSubHeadingElem>
|
||||||
<a class="u-url p-name" href={`/blog/${post.id}`}>{post.data.title}</a>
|
<a class="u-url p-name" href={`/blog/${post.id}`}>{post.data.title}</a>
|
||||||
|
</SubSubHeadingElem>
|
||||||
<p class="p-summary" set:html={post.data.description} />
|
<p class="p-summary" set:html={post.data.description} />
|
||||||
<FormattedDate className="dt-published" date={pubDate(post)} />
|
<FormattedDate className="dt-published" date={pubDate(post)} />
|
||||||
</li>
|
</li>
|
||||||
@@ -83,7 +86,9 @@ const canonicalBlogUrl = new URL('blog', Astro.site)
|
|||||||
<ul>
|
<ul>
|
||||||
{ posts.filter(matchesYear(year)).sort(sortByPubDateDescending).map(post => (
|
{ posts.filter(matchesYear(year)).sort(sortByPubDateDescending).map(post => (
|
||||||
<li class="h-entry">
|
<li class="h-entry">
|
||||||
|
<SubSubHeadingElem>
|
||||||
<a class="u-url p-name" href={`/blog/${post.id}`}>{post.data.title}</a>
|
<a class="u-url p-name" href={`/blog/${post.id}`}>{post.data.title}</a>
|
||||||
|
</SubSubHeadingElem>
|
||||||
<p class="p-summary" set:html={post.data.description} />
|
<p class="p-summary" set:html={post.data.description} />
|
||||||
<FormattedDate className="dt-published" date={pubDate(post)} />
|
<FormattedDate className="dt-published" date={pubDate(post)} />
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -2,20 +2,22 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
date: Date | string;
|
date: Date | string;
|
||||||
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { className, date } = Astro.props;
|
let { className, date, hidden } = Astro.props;
|
||||||
if (typeof(date) === 'string') {
|
if (typeof(date) === 'string') {
|
||||||
date = new Date(date);
|
date = new Date(date);
|
||||||
}
|
}
|
||||||
---
|
const dateStr =
|
||||||
|
|
||||||
<time datetime={date.toISOString()} class={className ?? ''}>
|
|
||||||
{
|
|
||||||
date.toLocaleDateString('en-GB', {
|
date.toLocaleDateString('en-GB', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
})
|
});
|
||||||
}
|
---
|
||||||
</time>
|
|
||||||
|
{ hidden
|
||||||
|
? <time datetime={date.toISOString()} class={className ?? ''} hidden>{dateStr}</time>
|
||||||
|
: <time datetime={date.toISOString()} class={className ?? ''}>{dateStr}</time>
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
import type { CollectionEntry } from 'astro:content';
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
import FormattedDate from './FormattedDate.astro';
|
||||||
|
import { renderGemtextToHtml } from '../renderGemtextToHtml';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6,
|
||||||
|
hideAuthor?: boolean,
|
||||||
|
hideSubheadings?: boolean,
|
||||||
|
maxEntries?: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { headingLevel = 2, hideSubheadings = false, hideAuthor = false, maxEntries } = Astro.props;
|
||||||
|
|
||||||
|
const allPosts = await getCollection('microlog');
|
||||||
|
|
||||||
|
const posts = maxEntries === undefined
|
||||||
|
? allPosts
|
||||||
|
: allPosts.sort(sortByIdDescending).slice(0, maxEntries);
|
||||||
|
|
||||||
|
const distinctYears: number[] = posts
|
||||||
|
.map(post => pubDate(post).getFullYear())
|
||||||
|
.reduce<number[]>((acc, curr) => acc.includes(curr) ? acc : [...acc, curr], [])
|
||||||
|
.sort((a, b) => b - a);
|
||||||
|
|
||||||
|
function matchesYear(year: number) {
|
||||||
|
return (post: CollectionEntry<'microlog'>) => pubDate(post).getFullYear() === year;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortByIdDescending(post1: CollectionEntry<'microlog'>, post2: CollectionEntry<'microlog'>) {
|
||||||
|
return post2.id.localeCompare(post1.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pubDate(post: CollectionEntry<'microlog'>): Date {
|
||||||
|
console.warn(post.id);
|
||||||
|
const date = new Date(post.id.replace(/\.[0-9]+$/g, ''));
|
||||||
|
if (isNaN(date.valueOf())) {
|
||||||
|
throw new Error(`Post ${post.id} does not have a valid publication date.`);
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeadingElem = `h${headingLevel} class="p-name"`;
|
||||||
|
const SubHeadingElem = `h${headingLevel + 1}`;
|
||||||
|
const SubSubHeadingElem = `h${headingLevel + 2}`;
|
||||||
|
const AuthorElem = `p${hideAuthor ? " hidden" : ""}`;
|
||||||
|
|
||||||
|
const canonicalMicrologUrl = new URL('microlog', Astro.site)
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="h-feed">
|
||||||
|
<HeadingElem>
|
||||||
|
My microlog
|
||||||
|
</HeadingElem>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<AuthorElem>
|
||||||
|
This microlog is written by <a class="p-author h-card" href="/">Joe Carstairs</a>
|
||||||
|
</AuthorElem>
|
||||||
|
|
||||||
|
<p hidden>
|
||||||
|
<a class="u-url" href={canonicalMicrologUrl}>Permalink</a>
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
{ hideSubheadings
|
||||||
|
? <ul>
|
||||||
|
{ posts.sort(sortByIdDescending).map(post => (
|
||||||
|
<li class="h-entry">
|
||||||
|
<SubSubHeadingElem>
|
||||||
|
<a class="u-url p-name" href={`/microlog/${post.id}`}>{post.id}</a>
|
||||||
|
</SubSubHeadingElem>
|
||||||
|
<section class="e-content" set:html={post.body} />
|
||||||
|
<FormattedDate hidden={true} className="dt-published" date={pubDate(post)} />
|
||||||
|
</li>
|
||||||
|
)) }
|
||||||
|
</ul>
|
||||||
|
: distinctYears.map(year => (
|
||||||
|
<SubHeadingElem>{year}</SubHeadingElem>
|
||||||
|
<ul>
|
||||||
|
{ posts.filter(matchesYear(year)).sort(sortByIdDescending).map(post => (
|
||||||
|
<li class="h-entry">
|
||||||
|
<SubSubHeadingElem>
|
||||||
|
<a class="u-url p-name" href={`/microlog/${post.id}`}>{post.id}</a>
|
||||||
|
</SubSubHeadingElem>
|
||||||
|
<section class="e-content" set:html={renderGemtextToHtml(post.body ?? '')} />
|
||||||
|
<FormattedDate hidden={true} className="dt-published" date={pubDate(post)} />
|
||||||
|
</li>
|
||||||
|
)) }
|
||||||
|
</ul>
|
||||||
|
)) }
|
||||||
|
|
||||||
|
{ (maxEntries !== undefined && maxEntries < allPosts.length)
|
||||||
|
? <p class="full-feed-link"><a href="/microlog">All microlog posts</a></p>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
@@ -9,6 +9,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="/blog">Blog</a>
|
<a href="/blog">Blog</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/microlog">Microlog</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/links">Links</a>
|
<a href="/links">Links</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { defineCollection } from "astro:content";
|
|||||||
import { z } from "astro/zod";
|
import { z } from "astro/zod";
|
||||||
|
|
||||||
import { extendedGlob } from "./loaders/extended-glob";
|
import { extendedGlob } from "./loaders/extended-glob";
|
||||||
|
import { glob } from "astro/loaders";
|
||||||
|
|
||||||
const blog = defineCollection({
|
const blog = defineCollection({
|
||||||
loader: extendedGlob({
|
loader: extendedGlob({
|
||||||
@@ -49,4 +50,12 @@ const blog = defineCollection({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const collections = { blog };
|
const microlog = defineCollection({
|
||||||
|
loader: glob({
|
||||||
|
pattern: "**/*.gmi",
|
||||||
|
base: "./src/content/microlog",
|
||||||
|
generateId: ({ entry }) => entry.replace(/\.gmi$/, ""),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { blog, microlog };
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../common/microlog/
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
---
|
---
|
||||||
import type { CollectionEntry } from 'astro:content';
|
|
||||||
import BaseHead from '../components/BaseHead.astro';
|
import BaseHead from '../components/BaseHead.astro';
|
||||||
import FormattedDate from '../components/FormattedDate.astro';
|
import FormattedDate from '../components/FormattedDate.astro';
|
||||||
import Navbar from '../components/Navbar.astro';
|
import Navbar from '../components/Navbar.astro';
|
||||||
@@ -19,7 +18,7 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<BaseHead title={`${title} | joeac’s blog`} description={description} />
|
<BaseHead title={`${title} | joeac’s blog`} description={description} />
|
||||||
<link rel="stylesheet" href="/css/blog.css">
|
<link rel="stylesheet" href="/css/post.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
import BaseHead from '../components/BaseHead.astro';
|
||||||
|
import FormattedDate from '../components/FormattedDate.astro';
|
||||||
|
import Navbar from '../components/Navbar.astro';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
pubDate: Date;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pubDate, title } = Astro.props;
|
||||||
|
|
||||||
|
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<BaseHead title={`${title} | joeac’s microlog`} description={`${title} | joeac's microlog`} />
|
||||||
|
<link rel="stylesheet" href="/css/post.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<Navbar />
|
||||||
|
<article class="h-entry">
|
||||||
|
<aside>
|
||||||
|
<span>
|
||||||
|
This is a <a href="/microlog">microlog</a> post by
|
||||||
|
<a class="p-author h-card" href="/">Joe Carstairs</a>.
|
||||||
|
</span>
|
||||||
|
<span>Published: <FormattedDate date={pubDate} className="dt-published"/>.</span>
|
||||||
|
<span hidden><a class="u-url uid" href={canonicalUrl}>Permalink</a></span>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1 class="h-name">{title}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="e-content">
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { type CollectionEntry, getCollection, render } from 'astro:content';
|
import { type CollectionEntry, getCollection, render } from 'astro:content';
|
||||||
import BlogPost from '../../layouts/BlogPost.astro';
|
import BlogPost from '../../layouts/BlogPost.astro';
|
||||||
import * as Gemtext from 'gemtext';
|
import { renderGemtextToHtml } from '../../renderGemtextToHtml';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await getCollection('blog');
|
const posts = await getCollection('blog');
|
||||||
@@ -12,57 +12,9 @@ export async function getStaticPaths() {
|
|||||||
}
|
}
|
||||||
type Props = CollectionEntry<'blog'>;
|
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 post = Astro.props;
|
||||||
const Content = post.filePath?.endsWith('.gmi')
|
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;
|
: (await render(post)).Content;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
import { type CollectionEntry, getCollection, render } from 'astro:content';
|
||||||
|
import MicrologPost from '../../layouts/MicrologPost.astro';
|
||||||
|
import { renderGemtextToHtml } from '../../renderGemtextToHtml';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = await getCollection('microlog');
|
||||||
|
return posts.map((post) => ({
|
||||||
|
params: { slug: post.id },
|
||||||
|
props: post,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
type Props = CollectionEntry<'microlog'>;
|
||||||
|
|
||||||
|
const { id: slug, body } = Astro.props;
|
||||||
|
const pubDateStr = slug.replaceAll(/\.[0-9]+/g, '');
|
||||||
|
const pubDate = new Date(pubDateStr);
|
||||||
|
if (isNaN(pubDate.valueOf())) {
|
||||||
|
throw new Error(`Could not construct a valid publication date for microlog post: ${slug}`);
|
||||||
|
}
|
||||||
|
const Content = renderGemtextToHtml(body ?? '', pubDateStr);
|
||||||
|
---
|
||||||
|
|
||||||
|
<MicrologPost pubDate={pubDate} title={pubDateStr}>
|
||||||
|
<Fragment set:html={Content}></Fragment>
|
||||||
|
</MicrologPost>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
|
||||||
|
import MicrologFeed from '../../components/MicrologFeed.astro';
|
||||||
|
import Page from '../../layouts/Page.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page title={SITE_TITLE} description={SITE_DESCRIPTION}>
|
||||||
|
<MicrologFeed headingLevel={1}>
|
||||||
|
<!-- <section>
|
||||||
|
<p><a href="/blog/subscribe">How to subscribe to this blog</a></p>
|
||||||
|
</section> -->
|
||||||
|
</MicrologFeed>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import * as Gemtext from "gemtext";
|
||||||
|
|
||||||
|
export function renderGemtextToHtml(gemtext: string, title?: string): string {
|
||||||
|
gemtext = title ? gemtext.replace(`# ${title}`, "") : gemtext;
|
||||||
|
return postProcessGemtextToHtml(
|
||||||
|
Gemtext.parse(gemtext).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>");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user