VuePressでタグ・カテゴリー対応
デフォルトのVuePressではタグ・カテゴリー対応はほとんどない。
@vuepress/blog
などのプラグインを使えばある程度は対応可能だが、Layout.vueを共通のものにしたいなど、色々とかゆいところに手が届かないように感じたため、自分でテーマを修正して対応することにした。
なお、ここではタグ・カテゴリーのことを合わせてタクソノミーと呼ぶことにする。
# まずはテーマをeject
Vue Pressのデフォルトテーマは vuepress 内部にあるが、eject
というコマンドでデフォルトのテーマのソースコードを構築済みのVue Pressに吐き出すことができる。
Vue Pressにはテーマの継承という概念があるため、それでも対応可能なのだが、個人的には元のソースコードを見ながら修正したいのと、細かい修正が可能なため、ejectでテンプレートを吐き出したほうがやりやすいと思った。
$ npx vuepress eject src/.vuepress/theme/
src/.vuepress/theme/
に出力すればそれがテンプレートとして読み込まれるようになるので、ここのソースを修正していく。
# ファイル構成
当初、frontmatter に category / tag を設定すると勝手にページが生成されるようなものを想定していたが、色々試してもうまくいかなかったため、結局は必要な category / tag のmdファイルを追加する形となった。
そのため、この方法では記事に新たなカテゴリーやタグを追加しても、categoriesディレクトリやtagsディレクトリにmdファイルを追加しないとカテゴリーやタグのアーカイブページは追加されない。
最終的にこれに関連して追加、修正を行なったファイルは下記のようになった。
src/
├── .vuepress
│ ├── components
│ │ └── Taxonomies.vue(*タクソノミー一覧)
│ └── theme
│ ├── components
│ │ ├── Sidebar.vue(*サイドバー全体)
│ │ ├── SidebarLatest.vue(*(追加)サイドバー最新記事)
│ │ ├── SidebarLink.vue(*サイドバーリンク:カテゴリーの表記を追記)
│ │ ├── SidebarTaxonomies.vue(*(追加)サイドバータクソノミー)
│ │ └── Taxonomy.vue(*タクソノミーページ)
│ └── layouts
│ └── Layout.vue(*タクソノミーページでTaxonomy.vueを呼び出すように修正)
├── articles(*記事ディレクトリ)
│ ├── README.md
│ └── (略)
├── categories(*カテゴリディレクトリ)
│ ├── README.md
│ ├── nginx.md
│ └── vuepress.md
└── tags(*タクソノミーディレクトリ)
├── README.md
├── nginx.md
└── vuepress.md
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 記事にタクソノミーを設定する
# articles ディレクトリ
articles/ ディレクトリ以下に各記事をmdファイルで入れていく。ここをどのようなディレクトリ分けにするかは特に決まりはないので、わかりやすいようにすればよい。
一例としてこの記事の frontmatter に指定してある内容を下記に記載します。
# articles/vuepress/taxonomies.md
---
title: "VuePressでタグ・カテゴリー対応"
date: 2021-11-26T13:23:00+09:00
category: vuepress
tags:
- "vuepress"
- "customize"
---
2
3
4
5
6
7
8
この記事は category
に vuepress
を、tags
に vuepress
、customize
を指定しているため、それぞれのカテゴリーやタグのページに表記されるようになります。
# タクソノミー一覧ページを作る
ここではカテゴリーやタクソノミーのディレクトリを設置する。
- /tags/ : タグの一覧
- /categories/ : カテゴリの一覧
- /tags/* : タグのアーカイブページ
- /categories/* : カテゴリーのアーカイブページ
# README.md
README.md
ファイルに対応するページでは、カテゴリ一覧、タグ一覧を表示するものとする。
README.md
から Taxonomies
コンポーネントを呼び出し、実際のカテゴリ/タグの一覧表示はTaxonomies
コンポーネント内で行う。
# categories/README.md
---
title: "Categories"
---
# Categories
<Taxonomies taxonomy="category" />
2
3
4
5
6
7
# components/Taxonomies.vue
テンプレートに関連するvueファイルはほとんど、.vuepress/theme/components
以下に作成しているが、このファイルのみmdファイルから呼び出すために .vuepress/components
配下に設置している。
<template>
<div class="sidebar-links">
<ul class="sidebar-links">
<li v-for="post in posts" :key="post.path">
<RouterLink :to="post.path">{{ post.title }}</RouterLink>
</li>
</ul>
</div>
</template>
<script>
import moment from "moment";
export default {
name: 'Taxonomies',
props: ["taxonomy"],
data () {
return {};
},
computed: {
posts() {
const tax = this.taxonomy;
let pages = this.$site.pages.filter(post => {
// タグ指定が指定のものでないものはパス
if (!post.frontmatter || !post.frontmatter.taxonomy ||
post.frontmatter.taxonomy !== tax)
{
return false;
}
return true;
}).map(post => {
post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
return post;
});
pages.sort(()=> Math.random() - 0.5);
return pages;
},
}
}
</script>
<style lang="stylus" scoped>
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# タクソノミーアーカイブページを作る
# mdファイル
その他のmdファイルはカテゴリやタグのアーカイブページが必要なものについてmdファイルを追加していくことにする。
例として、vuepressのタグとカテゴリについては下記のように設定した。
# categories/vuepress.md
---
title: "Vue Press"
taxonomy: "category"
terms: "vuepress"
---
当サイトで利用している静的サイトジェネレーター、Vue Pressについてのページです。
2
3
4
5
6
7
# tags/vuepress.md
---
title: "Vue Press"
taxonomy: "tags"
terms: "vuepress"
---
2
3
4
5
例えば categories/vuepress.md
であれば、frontmatterで category
に vuepress
を指定した記事を引っ張ってくるように指定しています。
カテゴリーやタグを追加した場合はここにmdファイルを追加していきます。
# components/Taxonomy.vue
タクソノミーが指定されている記事一覧を表示するためのコンポーネント。
<template>
<main class="page">
<slot name="top" />
<div class="theme-default-content">
<h2>{{tax_name}}: {{ title }}</h2>
<Content />
<ul>
<li v-for="post in posts" :key="post.path">
{{post._date}}
<RouterLink :to="post.path">
{{ post.title }}
</RouterLink>
</li>
</ul>
</div>
<slot name="bottom" />
</main>
</template>
<script>
import _orderBy from "lodash/orderBy";
import moment from "moment";
export default {
props: [],
computed: {
tax_name() {
return this.$page.frontmatter.taxonomy;
},
title() {
return this.$page.frontmatter.title;
},
posts() {
const self = this;
const taxonomy = this.$page.frontmatter.taxonomy;
const terms = this.$page.frontmatter.terms;
const posts = this.$site.pages.filter(post => {
if (typeof post.frontmatter[taxonomy] !== "undefined") {
if (Array.isArray(post.frontmatter[taxonomy])) {
return post.frontmatter[taxonomy].includes(terms);
} else {
return post.frontmatter[taxonomy] == terms;
}
}
return false;
}).map(post => {
post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
return post;
});
return _orderBy(posts, ["_date"], ["desc"]);
}
},
}
</script>
<style lang="stylus">
@require '../styles/wrapper.styl'
.page
padding-bottom 2rem
display block
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# layouts/Layout.vue の修正
先ほど作成した、Taxonomy.vue
を Layout.vue
から呼び出す。
先に作成したcategories / tags以下のmdファイル内でtaxonomyというfrontmatterを指定したが、このtaxonomyが指定されている場合はTaxonomyコンポーネントを呼び出すようにする。
<template>
<!-- (略) -->
<Home v-if="$page.frontmatter.home" />
<Taxonomy v-else-if="$page.frontmatter.taxonomy" />
<Page ...
/>
<!-- (略) -->
</template>
<script>
// (略)
import Taxonomy from '@theme/components/Taxonomy.vue'
export default {
// (略)
components: {
// (略)
Taxonomy,
},
// (略)
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ここまで作成したら、yarn dev
した上で、http://localhost:8080/tags/vuepress
や http://localhost:8080/categories/vuepress
が正常に表示されるか試してみる。
正常に表示されていたらタグやカテゴリのページはこれで完成 💮
# サイドバーにタグとカテゴリの一覧を表示させる。
サイドバーにカテゴリとタグの一覧を追加するために、下記の4ファイルに手を入れます。
下記ではついでに最新の記事5件を表示できるようにしています。
- SidebarLink.vue(*カテゴリーの表記を追記)
- (追加)SidebarLatest.vue(*サイドバー最新記事)
- (追加)SidebarTaxonomies.vue(*(追加)サイドバータクソノミー)
- Sidebar.vue(*サイドバー全体)
# components/SidebarLink.vue
SidebarLink
はもともと、サイドバーのリンクに使われているコンポーネントだが、ここでカテゴリを表記できるようにcategory
プロパティを追加する。
export default {
props: ['item', 'sidebarDepth', 'category'],
render (h,
{
parent: {
$page,
$site,
$route,
$themeConfig,
$themeLocaleConfig
},
props: {
item,
sidebarDepth,
category, // ここを追加
}
}) {
// このあたりを修正
let text_ = item.title || item.path;
if (category && item.frontmatter.category) {
text_ = `[${item.frontmatter.category}] ${text_}`;
}
const link = item.type === 'external'
? renderExternal(h, item.path, text_)
: renderLink(h, item.path, text_, active)
// (略)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# components/SidebarLatest.vue
サイドバーに最新記事を表示するコンポーネント。
ここで先程の SidebarLink
の category
プロパティを活用する。
<template>
<div class="sidebar-links">
<div class="sidebar-group">
<h4 class="sidebar-heading">Latest Posts</h4>
<ul
v-if="pages.length"
class="sidebar-links"
>
<li
v-for="(page, i) in pages"
:key="i"
>
<SidebarLink
:sidebar-depth="0"
:item="page"
:category="true"
/>
</li>
</ul>
</div>
</div>
</template>
<script>
import SidebarGroup from '@theme/components/SidebarGroup.vue'
import SidebarLink from '@theme/components/SidebarLink.vue'
import _orderBy from "lodash/orderBy";
import moment from "moment";
export default {
name: 'SidebarLinks',
components: { SidebarGroup, SidebarLink },
data () {
return {};
},
computed: {
pages() {
let pages = this.$site.pages.filter(post => {
if (post.frontmatter) {
if (post.frontmatter.home) return false; //home
if (post.frontmatter.taxonomy) return false; // タクソノミーページ
}
if (post.relativePath) {
// articles 以下のみ
if (post.relativePath.indexOf("articles/") !== 0) {
return false;
}
// README.md は無視
if (post.relativePath.substr(-9) === "README.md") {
return false;
}
}
return true;
}).map(post => {
post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
return post;
});
pages = _orderBy(pages, ["_date"], ["desc"]);
if (pages.length > 10) pages = pages.slice(0, 10);
return pages;
},
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# components/SidebarTaonomies.vue
サイドバーにタクソノミーの一覧を表示するコンポーネント。
<template>
<div class="sidebar-links">
<div class="sidebar-group">
<RouterLink :to="root_url" class="sidebar-heading clickable" v-if="root_url">{{title}}</RouterLink>
<h4 class="sidebar-heading" v-else>{{title}}</h4>
<ul class="sidebar-links">
<li
v-for="(tag, i) in tags"
:key="i"
>
<SidebarLink
:sidebar-depth="0"
:item="tag"
/>
</li>
</ul>
</div>
</div>
</template>
<script>
import SidebarGroup from '@theme/components/SidebarGroup.vue'
import SidebarLink from '@theme/components/SidebarLink.vue'
import _orderBy from "lodash/orderBy";
import moment from "moment";
export default {
name: 'SidebarTaxonomies',
components: { SidebarGroup, SidebarLink },
props: ["taxonomy", "title", "root_url"],
data () {
return {};
},
computed: {
tags() {
const tax = this.taxonomy;
let pages = this.$site.pages.filter(post => {
// タグ指定が指定のものでないものはパス
if (!post.frontmatter || !post.frontmatter.taxonomy ||
post.frontmatter.taxonomy !== tax)
{
return false;
}
return true;
}).map(post => {
post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
return post;
});
//pages = _orderBy(pages, ["_date"], ["desc"]);
pages.sort(()=> Math.random() - 0.5);
return pages;
},
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# components/Sidebar.vue
下記のように作成した SidebarLatest
コンポーネントと SidebarTaxonomies
コンポーネントを呼び出す。
<template>
<aside class="sidebar">
<NavLinks />
<slot name="top" />
<template v-if="items.length > 0">
<SidebarLinks
:depth="0"
:items="items"
/>
<hr />
</template>
<slot name="bottom" />
<SidebarLatest />
<SidebarTaxonomies taxonomy="category" title="Categories" root_url="/categories/" />
<SidebarTaxonomies taxonomy="tag" title="Tags" root_url="/tags/" />
</aside>
</template>
<script>
// (略)
import SidebarLatest from '@theme/components/SidebarLatest.vue'
import SidebarTaxonomies from '@theme/components/SidebarTaxonomies.vue'
export default {
// (略)
components: { SidebarLatest, SidebarTaxonomies },
// (略)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34