When dealing with lots of content, it's a good idea to have some categorization. One versatile approach to categorization is tagging. Nearly all blogs use it, and ours will too.
Note: If you prefer to watch rather than read, there's a screencast version here: https://youtu.be/PGLsFfBf1UA
Adding metadata
Continuing from Part 1, our blog repo has a /posts
folder filled with Markdown files. Our first step will be to add a new field called tags
to the "front matter" metadata in each markdown file.
---
title: Example Post
date: 2020-10-28
+tags: hello, world
---
# Example Title
...
Parsing tags
Sine the tags
string is comma-delimited, it will need to be converted into to an array during the parsing phase.
// in src/posts.js
// transform is called once for each post
function transform({metadata}) {
...
// the `tags` field is optional, so default to empty list
let tags = []
// check if `tags` field is defined on this post
if (metadata.tags) {
// split the tags string by comma, and trim away any extra spaces
tags = metadata.tags.split(',').map(str => str.trim())
}
// return previous data and tags
return {...metadata, tags}
}
Listing tags
Now that each post has a cleaned up tags
array. It's ready to be displayed on the /post/:permalink
page.
<!-- src/routes/posts/[permalink].svelte -->
<script>...</script>
<h1>{post.title}</h1>
<!-- new component to show tag list -->
+<Tags tags={post.tags}/>
{@html post.html}
And the new <Tags/>
component will be defined as:
<!-- src/components/Tags.svelte -->
<script>
// take in tags as a prop
export let tags
</script>
<nav>
<!-- iterate through each tag -->
{#each tags as tag}
<!-- link each tag to /tag/:tag page -->
<a href="/tag/{tag}">#{tag}</a>
{/each}
</nav>
Listing posts by tag
To show all the posts for a given tag, we'll need a new page and some JavaScript logic to filter posts by tag.
First, lets define the findByTag()
function. It will take the tag
as a parameter and return the list of posts matching the tag.
// src/posts.js
export function findByTag(tag) {
// filter out all posts that don't include the tag
return posts.filter(post => !post.tags.includes(tag))
}
Then, define new page for /tag/:tag
that will use findByTag()
to locate posts:
<!-- src/routes/tag/[tag].svelte -->
<script context="module">
import { findByTag } from '@/posts'
export function preload(page) {
// extract tag param
const { tag } = page.params
// find posts based on tag param
const posts = findByTag(tag)
// return props
return { tag, posts }
}
</script>
<script>
// props are provided by preload() function
export let tag, posts
</script>
<!-- show #tag as title -->
<h1>#{tag}</h1>
{#each posts as post}
<!-- show each post -->
{/each}
Extracting a component
Lastly, since we are showing the list of posts on 2 pages /tag/:tag
and /post/:permalink
, it would be to avoid duplication and have a reusable component for listing posts. This will make it easier to adjust and style down the road.
<!-- src/components/PostList.svelte -->
<script>
// it takes an array of posts as a prop
export let posts
</script>
<!-- iterate thru each post and output an <article> tag -->
{#each posts as post}
<article>
<!-- link to /posts/:permalink page -->
<a href={`/posts/${post.permalink}`}>
<h2>{post.title}</h2>
<p>{post.summary}</p>
</a>
</article>
{/each}
And then update the pages src/routes/posts/[permalink].svelte
and src/routes/tag/[tag].svelte
to use <PostList/>
instead of {#each}
.
<!-- use our new shiny component -->
<PostList {posts}/>
Summary
Our little blog is getting better! Now that we have tagging working, in the next post we'll explore adding syntax highlighting.
You can find all the code here:
https://github.com/joshnuss/blog-template
Happy coding! ✌
Screencast