BlogEngine: Re-add Book Reviews to the Blog Index page
This commit is contained in:
parent
9fca4a6867
commit
a8fc9a2691
15 changed files with 344 additions and 87 deletions
|
|
@ -1,9 +1,8 @@
|
||||||
import type { BlogPost } from '$lib/blog/BlogPost.js';
|
|
||||||
import { describe, it, beforeEach, expect } from 'vitest';
|
import { describe, it, beforeEach, expect } from 'vitest';
|
||||||
import { BlogController } from './BlogController.js';
|
import { BlogController } from './BlogController.js';
|
||||||
|
|
||||||
describe(`BlogController`, () => {
|
describe(`BlogController`, () => {
|
||||||
describe(`Getting all blog posts`, () => {
|
describe(`Getting all blog posts and book reviews`, () => {
|
||||||
let controller: BlogController;
|
let controller: BlogController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|
@ -16,11 +15,13 @@ describe(`BlogController`, () => {
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const aKnownBlogPost = blogPosts.find((post) => post.title === 'Vibe Check #10');
|
const aKnownBlogPost = blogPosts.find((post) => post.title === 'Vibe Check #10');
|
||||||
|
const aKnownBookReview = blogPosts.find((post) => post.title === 'After');
|
||||||
const aMadeUpBlogPost = blogPosts.find((post) => post.title === 'Some made up blog post');
|
const aMadeUpBlogPost = blogPosts.find((post) => post.title === 'Some made up blog post');
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(aMadeUpBlogPost).toBeNull();
|
expect(aMadeUpBlogPost).toBeUndefined();
|
||||||
expect(aKnownBlogPost).not.toBeNull();
|
expect(aKnownBlogPost).not.toBeUndefined();
|
||||||
|
expect(aKnownBookReview).not.toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
70
src/lib/blog/BlogController.ts
Normal file
70
src/lib/blog/BlogController.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { MarkdownRepository } from './markdown-repository.js';
|
||||||
|
|
||||||
|
const blogPostMetaGlobImport = import.meta.glob('../../content/blog/*.md', { as: 'raw' });
|
||||||
|
const bookReviewsMetaGlobImport = import.meta.glob('../../content/book-reviews/*.md', { as: 'raw' });
|
||||||
|
|
||||||
|
interface BlogPostListItem {
|
||||||
|
title: string;
|
||||||
|
author: string;
|
||||||
|
date: string;
|
||||||
|
book_review: boolean;
|
||||||
|
preview: string;
|
||||||
|
content: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookReviewListItem {
|
||||||
|
book_review: true;
|
||||||
|
title: string;
|
||||||
|
author: string;
|
||||||
|
image: string;
|
||||||
|
slug: string;
|
||||||
|
score: number;
|
||||||
|
finished: string;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlogController {
|
||||||
|
static async singleton(): Promise<BlogController> {
|
||||||
|
const markdownRepository = await MarkdownRepository.fromViteGlobImport(
|
||||||
|
blogPostMetaGlobImport,
|
||||||
|
bookReviewsMetaGlobImport
|
||||||
|
);
|
||||||
|
return new BlogController(markdownRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly markdownRepository: MarkdownRepository) {}
|
||||||
|
|
||||||
|
async getAllBlogPosts(): Promise<Array<BlogPostListItem | BookReviewListItem>> {
|
||||||
|
const blogPosts = await this.markdownRepository.blogPosts;
|
||||||
|
const bookReviews = await this.markdownRepository.bookReviews;
|
||||||
|
await blogPosts.buildAllBlogPosts();
|
||||||
|
|
||||||
|
const blogPostListItems: BlogPostListItem[] = blogPosts.blogPosts.map((blogPost) => {
|
||||||
|
return {
|
||||||
|
title: blogPost.title,
|
||||||
|
author: blogPost.author,
|
||||||
|
book_review: false,
|
||||||
|
content: blogPost.html,
|
||||||
|
date: blogPost.date.toISOString(),
|
||||||
|
preview: blogPost.excerpt,
|
||||||
|
slug: blogPost.slug,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const bookReviewListItems: BookReviewListItem[] = bookReviews.bookReviews.map((bookReview) => {
|
||||||
|
return {
|
||||||
|
book_review: true,
|
||||||
|
title: bookReview.title,
|
||||||
|
author: bookReview.author,
|
||||||
|
date: bookReview.date.toISOString(),
|
||||||
|
finished: bookReview.finished.toISOString(),
|
||||||
|
image: bookReview.image,
|
||||||
|
score: bookReview.score,
|
||||||
|
slug: bookReview.slug,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...blogPostListItems, ...bookReviewListItems].sort((a, b) => (a.date > b.date ? -1 : 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/lib/blog/BookReview.test.ts
Normal file
34
src/lib/blog/BookReview.test.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
import { BookReview } from './BookReview.js';
|
||||||
|
import { aBookReview } from './test-builders/book-review-builder.js';
|
||||||
|
|
||||||
|
describe(`BookReview`, () => {
|
||||||
|
it(`should construct`, () => {
|
||||||
|
// GIVEN
|
||||||
|
const bookReview = new BookReview({
|
||||||
|
title: 'After',
|
||||||
|
author: 'Dr Bruce Greyson',
|
||||||
|
score: 3.5,
|
||||||
|
image: 'after',
|
||||||
|
slug: 'after',
|
||||||
|
date: new Date('2021-05-05'),
|
||||||
|
finished: new Date('2021-04-20'),
|
||||||
|
draft: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const expectedBookReview = aBookReview()
|
||||||
|
.withTitle('After')
|
||||||
|
.withAuthor('Dr Bruce Greyson')
|
||||||
|
.withScore(3.5)
|
||||||
|
.withImage('after')
|
||||||
|
.withSlug('after')
|
||||||
|
.withDate(new Date('2021-05-05'))
|
||||||
|
.withFinished(new Date('2021-04-20'))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(bookReview).toEqual(expectedBookReview);
|
||||||
|
});
|
||||||
|
});
|
||||||
30
src/lib/blog/BookReview.ts
Normal file
30
src/lib/blog/BookReview.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
interface BookReviewProps {
|
||||||
|
title: string;
|
||||||
|
author: string;
|
||||||
|
score: number;
|
||||||
|
image: string;
|
||||||
|
slug: string;
|
||||||
|
date: Date;
|
||||||
|
finished: Date;
|
||||||
|
draft: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BookReview {
|
||||||
|
readonly title: string;
|
||||||
|
readonly author: string;
|
||||||
|
readonly score: number;
|
||||||
|
readonly image: string;
|
||||||
|
readonly slug: string;
|
||||||
|
readonly date: Date;
|
||||||
|
readonly finished: Date;
|
||||||
|
|
||||||
|
constructor(props: BookReviewProps) {
|
||||||
|
this.title = props.title;
|
||||||
|
this.author = props.author;
|
||||||
|
this.score = props.score;
|
||||||
|
this.image = props.image;
|
||||||
|
this.slug = props.slug;
|
||||||
|
this.date = props.date;
|
||||||
|
this.finished = props.finished;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/lib/blog/BookReviewSet.test.ts
Normal file
17
src/lib/blog/BookReviewSet.test.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
import { BookReviewSet } from './BookReviewSet.js';
|
||||||
|
import { aBookReview } from './test-builders/book-review-builder.js';
|
||||||
|
|
||||||
|
describe(`BookReviewSet`, () => {
|
||||||
|
it(`should construct`, () => {
|
||||||
|
// GIVEN
|
||||||
|
const bookReview = aBookReview().withTitle(`The title`).build();
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const bookReviewSet = new BookReviewSet([bookReview]);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(bookReviewSet.bookReviews).toStrictEqual([bookReview]);
|
||||||
|
});
|
||||||
|
});
|
||||||
13
src/lib/blog/BookReviewSet.ts
Normal file
13
src/lib/blog/BookReviewSet.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { BookReview } from './BookReview.js';
|
||||||
|
|
||||||
|
export class BookReviewSet {
|
||||||
|
private _bookReviews: BookReview[] = [];
|
||||||
|
|
||||||
|
constructor(bookReviews: BookReview[]) {
|
||||||
|
this._bookReviews = bookReviews;
|
||||||
|
}
|
||||||
|
|
||||||
|
get bookReviews(): BookReview[] {
|
||||||
|
return this._bookReviews;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,10 @@ import { describe, it, expect } from 'vitest';
|
||||||
import { MarkdownRepository } from './markdown-repository.js';
|
import { MarkdownRepository } from './markdown-repository.js';
|
||||||
|
|
||||||
import { MarkdownFile } from './MarkdownFile.js';
|
import { MarkdownFile } from './MarkdownFile.js';
|
||||||
import { BlogPost } from './BlogPost.js';
|
|
||||||
import { aBlogPost } from './test-builders/blog-post-builder.js';
|
import { aBlogPost } from './test-builders/blog-post-builder.js';
|
||||||
|
|
||||||
const globImport = import.meta.glob(`./test-fixtures/*.md`, { as: 'raw' });
|
const blogPostImport = import.meta.glob(`./test-fixtures/blog-*.md`, { as: 'raw' });
|
||||||
|
const bookReviewImport = import.meta.glob(`./test-fixtures/book-review-*.md`, { as: 'raw' });
|
||||||
|
|
||||||
const testMarkdownContent = `---
|
const testMarkdownContent = `---
|
||||||
title: "Test Blog Post"
|
title: "Test Blog Post"
|
||||||
|
|
@ -23,12 +23,7 @@ This is a [link](http://www.bbc.co.uk)
|
||||||
describe(`Blog MarkdownRepository`, () => {
|
describe(`Blog MarkdownRepository`, () => {
|
||||||
it(`should load`, async () => {
|
it(`should load`, async () => {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
const repository = await MarkdownRepository.fromViteGlobImport(globImport);
|
const repository = await MarkdownRepository.fromViteGlobImport(blogPostImport, bookReviewImport);
|
||||||
|
|
||||||
const expectedFile = new MarkdownFile({
|
|
||||||
fileName: './test-fixtures/2023-02-01-test.md',
|
|
||||||
content: testMarkdownContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedBlogPost = aBlogPost()
|
const expectedBlogPost = aBlogPost()
|
||||||
.withAuthor('Thomas Wilson')
|
.withAuthor('Thomas Wilson')
|
||||||
|
|
@ -39,12 +34,10 @@ describe(`Blog MarkdownRepository`, () => {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const file = repository.getMarkdownFileForFileName('./test-fixtures/2023-02-01-test.md');
|
const blogPost = repository.blogPosts.getBlogPostWithTitle('Test Blog Post');
|
||||||
const blogPost = repository.getBlogPostWithTitle('Test Blog Post');
|
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
expect(repository).toBeDefined();
|
expect(repository).toBeDefined();
|
||||||
expect(file).toStrictEqual(expectedFile);
|
|
||||||
expect(blogPost).toStrictEqual(expectedBlogPost);
|
expect(blogPost).toStrictEqual(expectedBlogPost);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,51 @@
|
||||||
import { BlogPost } from './BlogPost.js';
|
import { BlogPost } from './BlogPost.js';
|
||||||
import { MarkdownFile } from './MarkdownFile.js';
|
import { MarkdownFile } from './MarkdownFile.js';
|
||||||
import { BlogPostSet } from './BlogPostSet.js';
|
import { BlogPostSet } from './BlogPostSet.js';
|
||||||
|
import { BookReviewSet } from './BookReviewSet.js';
|
||||||
|
import { BookReview } from './BookReview.js';
|
||||||
|
|
||||||
interface FrontmatterValues {
|
interface BlogPostFrontmatterValues {
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
author: string;
|
author: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MarkdownRepository {
|
interface BookReviewFrontmatterValues {
|
||||||
readonly markdownFiles: MarkdownFile[];
|
title: string;
|
||||||
readonly blogPosts: BlogPostSet;
|
author: string; // Author of the book, not the review
|
||||||
|
slug: string;
|
||||||
private constructor(files: MarkdownFile[], blogPosts: BlogPost[]) {
|
date: Date;
|
||||||
this.blogPosts = new BlogPostSet([]);
|
finished: Date;
|
||||||
this.markdownFiles = files;
|
score: number;
|
||||||
this.blogPosts = new BlogPostSet(blogPosts);
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromViteGlobImport(globImport): Promise<MarkdownRepository> {
|
export class MarkdownRepository {
|
||||||
let fileImports: MarkdownFile<FrontmatterValues>[] = [];
|
readonly blogPosts: BlogPostSet;
|
||||||
let blogPosts: BlogPost[] = [];
|
readonly bookReviews: BookReviewSet;
|
||||||
const allFiles = Object.entries(globImport);
|
|
||||||
|
|
||||||
for (const entry of allFiles) {
|
private constructor(blogPosts: BlogPost[], bookReviews: BookReview[]) {
|
||||||
const [filename, module] = entry as [string, () => Promise<string>];
|
this.blogPosts = new BlogPostSet(blogPosts);
|
||||||
|
this.bookReviews = new BookReviewSet(bookReviews);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fromViteGlobImport(blogGlobImport, bookReviewGlobImport): Promise<MarkdownRepository> {
|
||||||
|
let fileImports: MarkdownFile<BlogPostFrontmatterValues>[] = [];
|
||||||
|
let blogPosts: BlogPost[] = [];
|
||||||
|
let bookReviews: BookReview[] = [];
|
||||||
|
|
||||||
|
const blogPostFiles = Object.entries(blogGlobImport);
|
||||||
|
|
||||||
|
for (const blogPostFile of blogPostFiles) {
|
||||||
|
const [filename, module] = blogPostFile as [string, () => Promise<string>];
|
||||||
try {
|
try {
|
||||||
const fileContent = await module();
|
const fileContent = await module();
|
||||||
|
|
||||||
const markdownFile = new MarkdownFile<FrontmatterValues>({ fileName: filename, content: fileContent });
|
const markdownFile = new MarkdownFile<BlogPostFrontmatterValues>({
|
||||||
|
fileName: filename,
|
||||||
|
content: fileContent,
|
||||||
|
});
|
||||||
const blogPost = new BlogPost({
|
const blogPost = new BlogPost({
|
||||||
markdownContent: markdownFile.content,
|
markdownContent: markdownFile.content,
|
||||||
title: markdownFile.frontmatter.title,
|
title: markdownFile.frontmatter.title,
|
||||||
|
|
@ -48,14 +64,36 @@ export class MarkdownRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MarkdownRepository(fileImports, blogPosts);
|
for (const bookReviewFile of Object.entries(bookReviewGlobImport)) {
|
||||||
|
const [filename, module] = bookReviewFile as [string, () => Promise<string>];
|
||||||
|
try {
|
||||||
|
const fileContent = await module();
|
||||||
|
|
||||||
|
const markdownFile = new MarkdownFile<BookReviewFrontmatterValues>({
|
||||||
|
fileName: filename,
|
||||||
|
content: fileContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bookReview = new BookReview({
|
||||||
|
author: markdownFile.frontmatter.author,
|
||||||
|
title: markdownFile.frontmatter.title,
|
||||||
|
slug: markdownFile.frontmatter.slug,
|
||||||
|
date: markdownFile.frontmatter.date,
|
||||||
|
draft: false,
|
||||||
|
finished: markdownFile.frontmatter.finished,
|
||||||
|
image: markdownFile.frontmatter.image,
|
||||||
|
score: markdownFile.frontmatter.score,
|
||||||
|
});
|
||||||
|
|
||||||
|
bookReviews = [...bookReviews, bookReview];
|
||||||
|
} catch (e) {
|
||||||
|
console.error({
|
||||||
|
message: `[MarkdownRespository::fromViteGlobImport] Error loading file ${filename}`,
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarkdownFileForFileName(fileName: string): MarkdownFile | null {
|
return new MarkdownRepository(blogPosts, bookReviews);
|
||||||
return this.markdownFiles.find((file) => file.fileName === fileName) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getBlogPostWithTitle(title: string): BlogPost | null {
|
|
||||||
return this.blogPosts.getBlogPostWithTitle(title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
src/lib/blog/test-builders/book-review-builder.ts
Normal file
69
src/lib/blog/test-builders/book-review-builder.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { BookReview } from '../BookReview.js';
|
||||||
|
|
||||||
|
class BookReviewBuilder {
|
||||||
|
private title = 'default title';
|
||||||
|
private author = 'default author';
|
||||||
|
private date = new Date();
|
||||||
|
private draft = false;
|
||||||
|
private finished = new Date();
|
||||||
|
private image = 'default image';
|
||||||
|
private score = 0;
|
||||||
|
private slug = 'default slug';
|
||||||
|
|
||||||
|
withTitle(title: string): BookReviewBuilder {
|
||||||
|
this.title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withAuthor(author: string): BookReviewBuilder {
|
||||||
|
this.author = author;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withDate(date: Date): BookReviewBuilder {
|
||||||
|
this.date = date;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withDraft(draft: boolean): BookReviewBuilder {
|
||||||
|
this.draft = draft;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withFinished(finished: Date): BookReviewBuilder {
|
||||||
|
this.finished = finished;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withImage(image: string): BookReviewBuilder {
|
||||||
|
this.image = image;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withScore(score: number): BookReviewBuilder {
|
||||||
|
this.score = score;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withSlug(slug: string): BookReviewBuilder {
|
||||||
|
this.slug = slug;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): BookReview {
|
||||||
|
return new BookReview({
|
||||||
|
title: this.title,
|
||||||
|
author: this.author,
|
||||||
|
date: this.date,
|
||||||
|
draft: this.draft,
|
||||||
|
finished: this.finished,
|
||||||
|
image: this.image,
|
||||||
|
score: this.score,
|
||||||
|
slug: this.slug,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aBookReview(): BookReviewBuilder {
|
||||||
|
return new BookReviewBuilder();
|
||||||
|
}
|
||||||
23
src/lib/blog/test-fixtures/book-review-test.md
Normal file
23
src/lib/blog/test-fixtures/book-review-test.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
title: "After"
|
||||||
|
author: "Dr Bruce Greyson"
|
||||||
|
score: 3.5
|
||||||
|
image: "after"
|
||||||
|
slug: "after"
|
||||||
|
book_review: true
|
||||||
|
date: 2021-05-05
|
||||||
|
finished: 2021-04-20
|
||||||
|
draft: false
|
||||||
|
tags:
|
||||||
|
- non-fiction
|
||||||
|
- death
|
||||||
|
links:
|
||||||
|
- country: "🇬🇧"
|
||||||
|
store_name: "Hive"
|
||||||
|
link: "https://www.hive.co.uk/Product/MD-Dr-Bruce-Greyson/After--A-Doctor-Explores-What-Near-Death-Experiences-Reve/25523446"
|
||||||
|
- country: "🇺🇸"
|
||||||
|
store_name: "bookshop.org"
|
||||||
|
link: "https://bookshop.org/books/after-a-doctor-explores-what-near-death-experiences-reveal-about-life-and-beyond/9781250263032"
|
||||||
|
---
|
||||||
|
|
||||||
|
This is some test content.
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { BlogController } from './BlogController.js';
|
import { BlogController } from '../../../lib/blog/BlogController';
|
||||||
|
|
||||||
export const GET = async () => {
|
export const GET = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import type { BlogPostSet } from '../../../lib/blog/BlogPostSet.js';
|
|
||||||
import { MarkdownRepository } from '../../../lib/blog/markdown-repository.js';
|
|
||||||
|
|
||||||
const blogPostMetaGlobImport = import.meta.glob('../../../content/blog/*.md', { as: 'raw' });
|
|
||||||
|
|
||||||
interface BlogPostListItem {
|
|
||||||
title: string;
|
|
||||||
author: string;
|
|
||||||
date: string;
|
|
||||||
book_review: boolean;
|
|
||||||
preview: string;
|
|
||||||
content: string;
|
|
||||||
slug: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BlogController {
|
|
||||||
static async singleton(): Promise<BlogController> {
|
|
||||||
const markdownRepository = await MarkdownRepository.fromViteGlobImport(blogPostMetaGlobImport);
|
|
||||||
return new BlogController(markdownRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(private readonly markdownRepository: MarkdownRepository) {}
|
|
||||||
|
|
||||||
async getAllBlogPosts(): Promise<BlogPostListItem[]> {
|
|
||||||
const blogPosts = await this.markdownRepository.blogPosts;
|
|
||||||
await blogPosts.buildAllBlogPosts();
|
|
||||||
|
|
||||||
const blogPostListItems: BlogPostListItem[] = blogPosts.blogPosts.map((blogPost) => {
|
|
||||||
return {
|
|
||||||
title: blogPost.title,
|
|
||||||
author: blogPost.author,
|
|
||||||
book_review: false,
|
|
||||||
content: blogPost.html,
|
|
||||||
date: blogPost.date.toISOString(),
|
|
||||||
preview: blogPost.excerpt,
|
|
||||||
slug: blogPost.slug,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return blogPostListItems.sort((a, b) => (a.date > b.date ? -1 : 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { json, type LoadEvent, error } from '@sveltejs/kit';
|
import { json, type LoadEvent, error } from '@sveltejs/kit';
|
||||||
import { fetchBlogPostBySlug } from '$lib';
|
import { fetchBlogPostBySlug } from '$lib';
|
||||||
|
import { BlogController } from '../../../../lib/blog/BlogController.js';
|
||||||
|
|
||||||
export const GET = async ({ params }: LoadEvent) => {
|
export const GET = async ({ params }: LoadEvent) => {
|
||||||
|
// const controller = await BlogController.singleton();
|
||||||
const { slug } = params;
|
const { slug } = params;
|
||||||
|
|
||||||
const post = await fetchBlogPostBySlug(slug);
|
const post = await fetchBlogPostBySlug(slug);
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,18 @@
|
||||||
aria-setsize={posts.length}
|
aria-setsize={posts.length}
|
||||||
>
|
>
|
||||||
<a href={`/blog/${post.slug}`}>
|
<a href={`/blog/${post.slug}`}>
|
||||||
{#if post.book_review} 📚 {/if}
|
<div class="post-title">
|
||||||
<div class="post-title">{post.title}</div>
|
{#if post.book_review} 📚 {/if}{post.title}
|
||||||
<div class="post-preview">{post.preview}...</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="post-preview">
|
||||||
|
{#if post.preview}
|
||||||
|
{post.preview}...
|
||||||
|
{:else}
|
||||||
|
No preview available ): Click to read the full post.
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="post-date">
|
<div class="post-date">
|
||||||
{intlFormat(
|
{intlFormat(
|
||||||
new Date(post.date),
|
new Date(post.date),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue