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 { await this.getHtml(); await this.getExcerpt(); } async getExcerpt(wordLength = 50): Promise { 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 { 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'] }); } }