next/prev page

This commit is contained in:
Evert Prants 2022-10-16 19:17:55 +03:00
parent aceb33fe6a
commit f6ddbf8b28
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
7 changed files with 121 additions and 36 deletions

View File

@ -201,6 +201,37 @@
color: #999; color: #999;
} }
} }
&__page {
display: flex;
margin-top: 1.2rem;
justify-content: space-between;
&-link {
display: flex;
flex-direction: column;
&--newer {
margin-left: auto;
text-align: right;
}
&:hover {
text-decoration: none;
.blog-post__page-name {
text-decoration: underline;
}
}
}
&-title {
color: #999;
line-height: 1em;
text-shadow: 0 1px #fff;
font-weight: bold;
}
}
} }
&__sidebar { &__sidebar {

View File

@ -30,6 +30,26 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="detail" class="blog-post__page">
<NuxtLink
v-if="post.prev"
:to="'/blog/' + post.prev.fullSlug"
class="blog-post__page-link blog-post__page-link--older"
>
<span class="blog-post__page-title">Older</span>
<span class="blog-post__page-name">{{ post.prev.title }}</span>
</NuxtLink>
<NuxtLink
v-if="post.next"
:to="'/blog/' + post.next.fullSlug"
class="blog-post__page-link blog-post__page-link--newer"
>
<span class="blog-post__page-title">Newer</span>
<span class="blog-post__page-name">{{ post.next.title }}</span>
</NuxtLink>
</div>
</article> </article>
</template> </template>

View File

@ -23,38 +23,27 @@ marked.use({
}, },
}); });
export async function readBlogPosts( async function readBlogPosts() {
condition?: (body: BlogPost) => boolean,
render = true,
includeContent = true
) {
const files = await fs.readdir(dir); const files = await fs.readdir(dir);
const readMD = files.filter((file) => file.endsWith('.md')); const readMD = files.filter((file) => file.endsWith('.md'));
const readFiles = []; const readFiles = [];
for (const file of readMD) { for (const file of readMD) {
const post = await readBlogPost( const post = await readBlogPost(file.replace('.md', ''));
file.replace('.md', ''),
condition,
render,
includeContent
);
if (!post) continue; if (!post) continue;
readFiles.push(post); readFiles.push(post);
} }
readFiles.sort((a, b) => readFiles.sort((a, b) =>
new Date(b.date) new Date(b.date)
.toISOString() .toISOString()
.localeCompare(new Date(a.date).toISOString(), 'en', { numeric: true }) .localeCompare(new Date(a.date).toISOString(), 'en', { numeric: true })
); );
return readFiles; return readFiles;
} }
export async function readBlogPost( async function readBlogPost(slug: string): Promise<BlogPost[]> {
slug: string,
condition?: (body: BlogPost) => boolean,
render = true,
includeContent = true
): Promise<BlogPost[]> {
const decoded = decodeURIComponent(slug); const decoded = decodeURIComponent(slug);
if (!slug || decoded.includes('/') || decoded.includes('.')) if (!slug || decoded.includes('/') || decoded.includes('.'))
throw new Error('Invalid post slug'); throw new Error('Invalid post slug');
@ -64,30 +53,74 @@ export async function readBlogPost(
const read = await fs.readFile(mdpath, { encoding: 'utf-8' }); const read = await fs.readFile(mdpath, { encoding: 'utf-8' });
const { header: parsedHeader, length: headerLength } = await readHeader(read); const { header: parsedHeader, length: headerLength } = await readHeader(read);
const renderedMd = render const renderedMd = await marked(read.substring(headerLength), {
? await marked(read.substring(headerLength), { async: true }) async: true,
: undefined; });
const { year, month, day } = getDateObject(parsedHeader); const { year, month, day } = getDateObject(parsedHeader);
const content = { const content = {
...parsedHeader, ...parsedHeader,
file, file,
slug, slug,
fullSlug: `${year}/${month}/${day}/${slug}`, fullSlug: `${year}/${month}/${day}/${slug}`,
markdown: includeContent ? read.substring(headerLength) : undefined, markdown: read.substring(headerLength),
html: renderedMd, html: renderedMd,
}; };
if (condition) {
if (!condition(content)) {
return;
}
}
return content; return content;
} }
let blogPostCache: BlogPost[] = [];
export const blogPostList = async () => {
if (!blogPostCache.length) {
blogPostCache = await readBlogPosts();
}
return blogPostCache;
};
export const getFilteredBlogPosts = async (
condition?: (body: BlogPost) => boolean,
htmlContent = true,
includeMarkdown = true
): Promise<BlogPost[]> => {
const posts = await blogPostList();
return posts
.filter((item) => (condition ? condition(item) : true))
.map((item) => ({
...item,
html: htmlContent ? item.html : undefined,
markdown: includeMarkdown ? item.markdown : undefined,
}));
};
export const getBlogPost = async (
slug: string,
htmlContent = true,
includeMarkdown = true
): Promise<BlogPost> => {
const posts = await blogPostList();
const item = posts.find((item) => item.slug === slug);
if (!item) return;
const index = posts.indexOf(item);
const next = index > 0 ? posts[index - 1] : undefined;
const prev = posts[index + 1];
return {
...item,
html: htmlContent ? item.html : undefined,
markdown: includeMarkdown ? item.markdown : undefined,
next: next
? { title: next.title, slug: next.slug, fullSlug: next.fullSlug }
: undefined,
prev: prev
? { title: prev.title, slug: prev.slug, fullSlug: prev.fullSlug }
: undefined,
};
};
export async function getTags(): Promise<BlogPostTag[]> { export async function getTags(): Promise<BlogPostTag[]> {
const posts = await readBlogPosts(undefined, false, false); const posts = await getFilteredBlogPosts(undefined, false, false);
const obj: BlogPostTag[] = []; const obj: BlogPostTag[] = [];
for (const post of posts) { for (const post of posts) {
@ -113,7 +146,7 @@ export async function getTags(): Promise<BlogPostTag[]> {
} }
export async function getArchiveTree(condition?: (body: BlogPost) => boolean) { export async function getArchiveTree(condition?: (body: BlogPost) => boolean) {
const posts = await readBlogPosts(condition, false, false); const posts = await getFilteredBlogPosts(condition, false, false);
const obj = {}; const obj = {};
for (const post of posts) { for (const post of posts) {

View File

@ -7,6 +7,8 @@ export interface BlogPost {
fullSlug: string; fullSlug: string;
markdown: string; markdown: string;
html: string; html: string;
next?: Partial<BlogPost>;
prev?: Partial<BlogPost>;
} }
export interface BlogPostTag { export interface BlogPostTag {

View File

@ -1,10 +1,9 @@
import { H3Error } from 'h3'; import { getBlogPost } from '~~/lib/blog/read-posts';
import { readBlogPost } from '~~/lib/blog/read-posts';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug'); const slug = getRouterParam(event, 'slug');
try { try {
const post = await readBlogPost(slug); const post = await getBlogPost(slug);
return post; return post;
} catch (e) {} } catch (e) {}
}); });

View File

@ -1,4 +1,4 @@
import { readBlogPosts } from '~~/lib/blog/read-posts'; import { getFilteredBlogPosts } from '~~/lib/blog/read-posts';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const query = getQuery(event); const query = getQuery(event);
@ -32,7 +32,7 @@ export default defineEventHandler(async (event) => {
return true; return true;
}; };
const posts = await readBlogPosts( const posts = await getFilteredBlogPosts(
include, include,
query.render !== 'false', query.render !== 'false',
query.body === 'true' query.body === 'true'

View File

@ -1,10 +1,10 @@
import { Feed } from 'feed'; import { Feed } from 'feed';
import { readBlogPosts } from '~~/lib/blog/read-posts'; import { getFilteredBlogPosts } from '~~/lib/blog/read-posts';
const BASE_URL = 'https://lunasqu.ee/blog'; const BASE_URL = 'https://lunasqu.ee/blog';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const posts = await readBlogPosts(undefined, true, false); const posts = await getFilteredBlogPosts(undefined, true, false);
const feed = new Feed({ const feed = new Feed({
title: "Evert's Blog", title: "Evert's Blog",
description: 'Projects and Tutorials', description: 'Projects and Tutorials',