BlogProfile

Notion Blogにタグ機能を追加する

最近ブログのコンセプトというか内容がブレブレでなんでもありな感じになってきたのでタグ機能をつけて整理していきたいと思う。

成果物は例の如くこのブログそのものです。

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

一覧ページこんな感じ!

自分の選色のセンスが絶望的すぎて微妙…

やっぱ白黒だけでいいのかなあ…🤔