最近ブログのコンセプトというか内容がブレブレでなんでもありな感じになってきたのでタグ機能をつけて整理していきたいと思う。
成果物は例の如くこのブログそのものです。
Notion 側の設定
とりあえずタグを定義するための新しいテーブルを作ります。
まあこの辺のデータはなんでもいい。
そしたらメインの記事テーブルにさっきの Tag テーブルのリレーションを作ります。
あとは記事テーブル側からさっき作成したタグテーブルのアイテムを選択するだけ。
Next.js 側の設定
とりあえず一覧表示にタグを出したい。出させてください。
ソースコードをみると Notion のデータの呼び出しは getTableData 関数を使っているようなのでここをいじってみる。
// src/lib/notion/getTableData.ts
// 略
schemaKeys.forEach(async (key) => {
// might be undefined
let val = props[key] && props[key][0][0]
// authors and blocks are centralized
if (val && props[key][0][1]) {
const type = props[key][0][1][0]
switch (type[0]) {
case 'a': // link
val = type[1]
break
case 'u': // user
val = props[key]
.filter((arr: any[]) => arr.length > 1)
.map((arr: any[]) => arr[1][0][1])
break
case 'p': // page (block)
const page = col.recordMap.block[type[1]]
row.id = page.value.id
val = page.value.properties.title[0][0]
}
break
case 'd': // date
// start_date: 2019-06-18
// start_time: 07:00
// time_zone: Europe/Berlin, America/Los_Angeles
if (!type[1].start_date) {
break
}
// initial with provided date
const providedDate = new Date(
type[1].start_date + ' ' + (type[1].start_time || '')
).getTime()
// calculate offset from provided time zone
const timezoneOffset =
new Date(
new Date().toLocaleString('en-US', {
timeZone: type[1].time_zone
})
).getTime() - new Date().getTime()
// initialize subtracting time zone offset
val = new Date(providedDate - timezoneOffset).getTime()
break
default:
console.error('unknown type', type[0], type)
break
}
}
リレーションの type は case でいう'p'に引っかかるのでここの条件分岐を編集する。
次に複数のタグが入ってきた場合に対応するため、上記の const type = の部分を以下のように書き換える。
もうこれは説明できないけどこれで動く。
// src/lib/notion/getTableData.ts
const type =
props[key].length < 2
? props[key][0][1][0]
: [
props[key][0][1][0][0],
...props[key]
.map((p) => p[1])
.filter((f) => !!f)
.map((fp) => fp[0][1])
]
次に Notion_id からタグのデータを取得する getTagData 関数を新たに作成する。
// src/lib/notion/getTagData.ts
import rpc, { values } from './rpc'
export default async function getTagData(pageId: string) {
const obj = {
Color: null,
Title: null,
Slug: null
}
try {
const data = await loadPageChunk({ pageId })
const blocks = values(data.recordMap.block)
if (blocks[0].value?.properties) {
const props = blocks[0].value?.properties
obj.Color = props['n<}n'][0]
obj.Title = props['title'][0]
obj.Slug = props['e;z{'][0]
}
} catch (err) {
console.error(`Failed to load pageData for ${pageId}`, err)
}
if (obj.Title && obj.Slug && obj.Color) {
return obj
}
}
export function loadPageChunk({
pageId,
limit = 100,
cursor = { stack: [] },
chunkNumber = 0,
verticalColumns = false
}: any): any {
return rpc('loadPageChunk', {
pageId,
limit,
cursor,
chunkNumber,
verticalColumns
})
}
これをさっきの Switch 文の中で呼び出してあげる。
// src/lib/notion/getTableData.ts
// 略
case 'p':
const page = col.recordMap.block[type[1]]
if (page) {
row.id = page.value.id
val = page.value.properties.title[0][0]
}
//追加!!!
if (!page) {
const tagsPromise = type
.map(async (t) => {
const tag = await getTagData(t)
return tag
})
.filter((_, i) => i !== 0)
const tags = await Promise.all(tagsPromise)
val = tags
}
break
新たに作成した Tag を Post の型定義に追加する。
// types/post.ts
export type Post = {
id: string
Authors: string[]
Slug: string
Published: 'Yes' | 'No'
Date: number
Page: string
preview: unknown[][]
content: PostContent[]
hasTweet: boolean
Ogp: string
Tag: {
Color: string
Title: string
Slug: string
}[]
}
あとは見た目に反映するだけ!
こんな感じで呼び出せば一覧ページに表示できる!
// src/components/Blog.tsx
//タグの背景色算出する
const getBlackOrWhite = (hexcolor: string) => {
const r = parseInt(hexcolor.substr(1, 2), 16)
const g = parseInt(hexcolor.substr(3, 2), 16)
const b = parseInt(hexcolor.substr(5, 2), 16)
return (r * 299 + g * 587 + b * 114) / 1000 < 128 ? 'white' : 'black'
}
//略
{
post.Tag.map((t) => (
<Link key={t.Slug} href={{ pathname: 'blog', query: { tag: t.Slug } }}>
<a
className="post-tags__item"
style={{
backgroundColor: t.Color,
color: `${getBlackOrWhite(t.Color || '#ffffff')}`
}}
>
{t.Title}
</a>
</Link>
))
}
一覧ページこんな感じ!
自分の選色のセンスが絶望的すぎて微妙…
やっぱ白黒だけでいいのかなあ…🤔