thomaswilson-sveltekit/src/lib/blog/BlogPost.ts

107 lines
3.3 KiB
TypeScript

import type { Processor } from 'unified';
import { unified } from 'unified';
import { remark } from 'remark';
import markdown from 'remark-parse';
import markdownFrontmatter from 'remark-frontmatter';
import remarkStringify from 'remark-stringify';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import stripMarkdown from 'strip-markdown';
import remarkFrontmatter from 'remark-frontmatter';
interface BlogPostParams {
title: string;
date: Date;
author: string;
slug: string;
markdownContent: string;
fileName: string; // excluding any leading `..`
}
export class BlogPost {
readonly title: string;
readonly date: Date;
readonly author: string;
readonly slug: string;
readonly markdownContent: string;
readonly fileName: string;
private _html: string | null = null;
private _excerpt: string | null = null;
constructor(params: BlogPostParams) {
this.title = params.title;
this.date = params.date;
this.author = params.author;
this.slug = params.slug;
this.markdownContent = params.markdownContent;
this.fileName = params.fileName.split(`/`)[-1];
}
get html(): string | null {
return this._html;
}
get excerpt(): string | null {
return this._excerpt;
}
get hasBeenBuilt(): boolean {
return this._html !== null && this._excerpt !== null;
}
async build(): Promise<void> {
await this.getHtml();
await this.getExcerpt();
}
async getExcerpt(wordLength = 50): Promise<string> {
const processor = this.markdownToExcerptProcessorFactory();
const value = await processor.process(this.markdownContent);
const textValueWithNoLinebreaks = value.toString();
// A regex that looks for any character, followed by `.`, and then another character.
// e.g. "This is a sentence.This is another sentence."
// becomes "This is a sentence. This is another sentence."
const reg = /([a-zA-Z0-9])\.([a-zA-Z0-9])/g;
const textWithSpacesBetweenSentences = textValueWithNoLinebreaks
.replaceAll('\r', ' ')
.replaceAll('\n', ' ')
.replaceAll(reg, '$1. $2')
.split(' ')
.filter((word) => word !== ' ' && word !== '')
.slice(0, wordLength)
.join(' ');
this._excerpt = textWithSpacesBetweenSentences;
return this._excerpt;
}
async getHtml(): Promise<string> {
const processor = this.markdownToHtmlProcessorFactory();
const html = await processor.process(this.markdownContent);
this._html = html.toString();
return this._html;
}
private markdownToHtmlProcessorFactory(): Processor {
return unified() //
.use(markdown)
.use(markdownFrontmatter)
.use(remarkStringify)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, {
allowDangerousHtml: true,
allowDangerousCharacters: true,
});
}
private markdownToExcerptProcessorFactory(): Processor {
return remark()
.use(markdown)
.use(remarkFrontmatter)
.use(stripMarkdown, { remove: ['list'] });
}
}