検索機能が追加されました。右側のやつです。
実装方法
このブログページ自体は nuxt/contentで動いているため、nuxt/content の全文検索機能を用いて実装しました。全文検索自体はドキュメントを読めば簡単にできるので、content の良さを実感しています。
工夫点
もちろんタグ検索ですねー。元々ブログ記事にはタグを埋め込むようにしていたので、それで検索をかけています。タグの埋め込みイメージは下のような感じです。yml のため配列で格納するのも考えたのですが、こちらの形式の方が、全文検索のさいに扱いやすかったので、こちらで保存しています。
tags: Golang CloudRun
全てのタグ自体は、非同期に記事を全検索書けてマップでタグの値を数えて、DOM の値を更新するという風にしています。Vue では Map の変更が上手く反映されなかったので、このように最後に配列に push していくという形式を取っています。そんなに大量に記事が増えることもないと思うので、サーバーレンダリングでも良い気はしますが、一応非同期に行っています。手間がかかった割に地味な機能なんですよね。。。
const tags = reactive<TagCount[]>([]);
onMounted(() => {
context.root
.$content(queryName)
.only("tags")
.fetch<Promise<{ tags: string }[]>>()
.then((resps) => {
const tagMap = new Map<string, number>();
resps.map((resp) => {
_ = resp.tags?.split(" ").forEach((tag) => {
tagMap.set(tag, (tagMap.get(tag) || 0) + 1);
});
});
tagMap.forEach((num, tag) => {
tags.push({
name: tag,
count: num,
});
});
});
});
はまったこと
はまったことは、クエリが変わった際に発火するようにした asyncData の検索結果が、サーバーレンダリング時とクライント実行時で結果が変わったことです。
async asyncData({ $content, query }: Context) {
const text = query.text ?? '';
const resp = await searchContent($content(queryName), text);
return {
contents: resp,
};
},
watchQuery: ['text'],
具体的には時刻文字列のソートがクライント実行時では一切効いていませんでした。検索自体はどちらも nodejs が行っているはずなんですが、ソートが効く時と効かないときがあるのは謎でした。とりあえず、クライント側で受け取る度にソートを行うことで解決しました。謎すぎます。
return result.then((resp: Article[]) => {
return resp
.sort((l, r) => {
const lDate = new Date(l.created);
const rDate = new Date(r.created);
if (lDate === rDate) {
return 0;
}
if (lDate > rDate) {
return -1;
}
return 1;
})
...
因みに、nuxt/content の createdAt プロパティを使わない理由としては、cloud run を使っているからです。cloud run の場合は、全てのファイルの作成日が同じになってしまうため、自身で独自のプロパティを設定してそちらで、作成日を管理しています。(これも改善できそうな気がしますが、取り敢えずって感じです。)
感想
検索機能ぐらい nuxt/content の機能でありますし、ぱっぱと実装できるかと思いましたが、結構かかりました。ただ、使い回せるコンポーネントが出来たので、他の検索機能を実装する際は、少し楽をできるかなって思っています。