VuePressでタグ・カテゴリー対応

Category:
Last Updated: 2021/12/13 12:40:54

デフォルトのVuePressではタグ・カテゴリー対応はほとんどない。

@vuepress/blog などのプラグインを使えばある程度は対応可能だが、Layout.vueを共通のものにしたいなど、色々とかゆいところに手が届かないように感じたため、自分でテーマを修正して対応することにした。

なお、ここではタグ・カテゴリーのことを合わせてタクソノミーと呼ぶことにする。

# まずはテーマをeject

Vue Pressのデフォルトテーマは vuepress 内部にあるが、ejectというコマンドでデフォルトのテーマのソースコードを構築済みのVue Pressに吐き出すことができる。

Vue Pressにはテーマの継承という概念があるため、それでも対応可能なのだが、個人的には元のソースコードを見ながら修正したいのと、細かい修正が可能なため、ejectでテンプレートを吐き出したほうがやりやすいと思った。

$ npx vuepress eject src/.vuepress/theme/
1

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
1
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"
---
1
2
3
4
5
6
7
8

この記事は categoryvuepress を、tagsvuepresscustomize を指定しているため、それぞれのカテゴリーやタグのページに表記されるようになります。

# タクソノミー一覧ページを作る

ここではカテゴリーやタクソノミーのディレクトリを設置する。

  • /tags/ : タグの一覧
  • /categories/ : カテゴリの一覧
  • /tags/* : タグのアーカイブページ
  • /categories/* : カテゴリーのアーカイブページ

# README.md

README.md ファイルに対応するページでは、カテゴリ一覧、タグ一覧を表示するものとする。

README.md から Taxonomies コンポーネントを呼び出し、実際のカテゴリ/タグの一覧表示はTaxonomies コンポーネント内で行う。

# categories/README.md

---
title:  "Categories"
---

# Categories

<Taxonomies taxonomy="category" />
1
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>

1
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についてのページです。
1
2
3
4
5
6
7

# tags/vuepress.md

---
title:  "Vue Press"
taxonomy: "tags"
terms: "vuepress"
---
1
2
3
4
5

例えば categories/vuepress.md であれば、frontmatterで categoryvuepress を指定した記事を引っ張ってくるように指定しています。

カテゴリーやタグを追加した場合はここに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>

1
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.vueLayout.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>
1
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/vuepresshttp://localhost:8080/categories/vuepress が正常に表示されるか試してみる。

正常に表示されていたらタグやカテゴリのページはこれで完成 💮

# サイドバーにタグとカテゴリの一覧を表示させる。

サイドバーにカテゴリとタグの一覧を追加するために、下記の4ファイルに手を入れます。
下記ではついでに最新の記事5件を表示できるようにしています。

  • SidebarLink.vue(*カテゴリーの表記を追記)
  • (追加)SidebarLatest.vue(*サイドバー最新記事)
  • (追加)SidebarTaxonomies.vue(*(追加)サイドバータクソノミー)
  • Sidebar.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)
    
    // (略)
  }
}
1
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

サイドバーに最新記事を表示するコンポーネント。
ここで先程の SidebarLinkcategory プロパティを活用する。



<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>

1
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>



1
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>

1
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

Category:
Last Updated: 2021/12/13 12:40:54
Copyright © Web Ninja All Rights Reserved.