herohoroブログ

Notionサポーターサイトを紹介しよう



🔄   2024-01-18

お察しの通り、Notionサポーターのサイトは私が作っています。

image block

image block

astro-notion-blogを魔改造してサイトっぽくしています。

本来であれば、NotionDBを1つ繋げて記事を表示させていくのが本家(astro-notion-blog)ですが、

このサイトではNotionDBを2つ繋げ、固定ページも繋げています✌

サイトが公開された当初の紹介動画はNotionサポーターのYouTubeで触れられていますので、この記事では構造的な部分を解説できればと思います。

※ 30:42あたり参照

魔改造お品書き

  1. NotionDBを複数つなげた
  2. サポータープロフィール用DBのプロパティを増やした
  3. サポータープロフィールページにプロパティを表示させた
  4. 記事用DBのfooterタグの有無で一覧に表示されないようにした

改造するにあたり、気をつけたことは唯一つ【本家のアップデートに対応できるようにいじる】

作って終わりではなく、アップデートの差分を取り込む際になるべく楽できるようにしました。

  • 重複するcomponentがあるなら使う
  • 複製して微調整した関数は複製元の関数名をベースに命名する

本家のアップデートがある限り最新版のNotionサポーターサイトを提供し続けたいなという気持ちなのですw

魔改1:NotionDBを複数つなげる

一旦、本家のNotionDBを複製し、DB名を【Sub Post】としてみます。

環境変数

Notion APIでNotion内のデータを取得できるよう環境変数を追加します。

export const SUB_DATABASE_ID =
  import.meta.env.SUB_DATABASE_ID || process.env.SUB_DATABASE_ID || ''
src/server-constants.ts

型定義

本家の型定義は「Post」となっているのでそれをコピペして「SubPost」という名前にします。

export interface SubPost {
  PageId: string
  Title: string
  Icon: FileObject | Emoji | null
  Cover: FileObject | null
  Slug: string
  Date: string
  Tags: SelectProperty[]
  Excerpt: string
  FeaturedImage: FileObject | null
  Rank: number
src/lib/interfaces.ts

データ取得

NotionDBのデータを取得する関数を作ります。

環境変数と型定義をimportしたあと、キャッシュ用の関数を設置して本家のgetAllPostsを複製した関数をベースに修正します。

let subPostsCache: SubPost[] | null = null

export async function getAllSubPosts(): Promise<SubPost[]> {
  if (subPostsCache !== null) {
    return Promise.resolve(subPostsCache)
  }
	const params: requestParams.QueryDatabase = {
	    database_id: SUB_DATABASE_ID,
	    filter: {
	      and: [
	        {
	          property: 'Published',
	          checkbox: {
	            equals: true,
	          },
	        },
	        {
	          property: 'Date',
	          date: {
	            on_or_before: new Date().toISOString(),
	          },
	        },
	      ],
	    },
	    sorts: [
	      {
	        property: 'Date',
	        direction: 'descending',
	      },
	    ],
	    page_size: 100,
	  }
	
	  let results: responses.PageObject[] = []
	  while (true) {
	    const res = (await client.databases.query(
	      params as any // eslint-disable-line @typescript-eslint/no-explicit-any
	    )) as responses.QueryDatabaseResponse
	
	    results = results.concat(res.results)
	
	    if (!res.has_more) {
	      break
	    }
	
	    params['start_cursor'] = res.next_cursor as string
	  }
	
	  subPostsCache = results
	    .filter((pageObject) => _validPageObject(pageObject))
	    .map((pageObject) => _buildSubPost(pageObject))
	  return subPostsCache
}
src/lib/notion/client.ts

記事用のgetPostsも複製し、参照元getAllPostsからgetAllSubPostsに修正します。

export async function getSubPosts(pageSize = 10): Promise<SubPost[]> {
  const allSubPosts = await getAllSubPosts()
  return allSubPosts.slice(0, pageSize)
}
src/lib/notion/client.ts

同様にして、関連する記事一覧の関数をgetAllSubPostsに修正していきます。

  • getRankedPosts➡getRankedSubPosts
  • getStaticBySlug➡getSubPostBySlug
  • getPostByPageId➡getSubPostByPageId
  • getPostsByTag➡getSubPostsByTag
  • getPostsByPage➡getSubPostsBySubPage
  • getPostsByTagAndPage➡getPostsBySubTagAndSubPage
  • getNumberOfPages➡getNumberOfSubPages
  • getNumberOfPagesByTag➡getNumberOfSubPagesBySubTag
  • getAllTags➡getAllSubTags

ビルドの部分のプロパティも型定義は先程追加したsubPostを使います。

  • _buildPost➡_buildSubPost
function _buildSubPost(pageObject: responses.PageObject): SubPost {
.
.
.
	const subPost: SubPost = {
	    PageId: pageObject.id,
	    Title: prop.Page.title ? prop.Page.title[0].plain_text : '',
	    Icon: icon,
	    Cover: cover,
	    Slug: prop.Slug.rich_text ? prop.Slug.rich_text[0].plain_text : '',
	    Date: prop.Date.date ? prop.Date.date.start : '',
	    Tags: prop.Tags.multi_select ? prop.Tags.multi_select : [],
	    Excerpt:
	      prop.Excerpt.rich_text && prop.Excerpt.rich_text.length > 0
	        ? prop.Excerpt.rich_text.map((t) => t.plain_text).join('')
	        : '',
	    FeaturedImage: featuredImage,
	    Rank: prop.Rank.number ? prop.Rank.number : 0,
	}
return subPost
}
src/lib/notion/client.ts

パス調整

記事のパスを追加したディレクトリ名(supporters)にします。

export const getSubPostLink = (slug: string) => {
  return pathJoin(BASE_PATH, `/supporters/${slug}`)
}
export const getSubTagLink = (tag: string) => {
  return pathJoin(BASE_PATH, `/supporters/tag/${encodeURIComponent(tag)}`)
}
export const getSubPageLink = (page: number, tag: string) => {
  if (page === 1) {
    return tag ? getSubTagLink(tag) : pathJoin(BASE_PATH, '/supporters')
  }
  return tag
    ? pathJoin(
        BASE_PATH,
        `/supporters/tag/${encodeURIComponent(tag)}/page/${page.toString()}`
      )
    : pathJoin(BASE_PATH, `/supporters/page/${page.toString()}`)
}
src/lib/blog-helpers.ts

ここも、本家のコードを複製して微調整するだけです。

component

本家のcomponentを使い回せない部分のみ追加してます。

  • src/components/BlogSubPostsLink.astro
  • src/components/BlogSubTagsLink.astro
  • src/components/SubPostTags.astro
  • src/components/SubPostTitle.astro
  • src/components/SubReadMoreLink.astro

参照元をgetAllSubPostsに修正し、client内に定義した先程の関数を使えるようにします。

表示

記事一覧(https://notionsupporter.com/supporters/)を表示させるのに、本家のcomponentはそのまま使い、専用で用意したcomponentには【Sub】の文字が追加されています。

見た目は悪いですが、アップデートをしやすくするための工夫ですw

<Layout>
  <div slot="main" class={styles.main}>
    {
      posts.length === 0 ? (
        <NoContents contents={posts} />
      ) : (
        posts.map((post) => (
          <div class={styles.post} key={post.Slug}>
            <PostDate post={post} />
            <SubPostTags post={post} />
            <SubPostTitle post={post} />
            <PostFeaturedImage post={post} />
            <PostExcerpt post={post} />
            <PostLastEditedTime post={post} />
            <SubReadMoreLink post={post} />
          </div>
        ))
      )
    }

    <footer>
      <Pagination currentPage={1} numberOfPages={numberOfPages} />
    </footer>
  </div>

  <div slot="aside" class={styles.aside}>
    <BlogSubPostsLink heading="Recommended" posts={rankedPosts} />
    <BlogSubTagsLink heading="Categories" tags={tags} />
  </div>
</Layout>
src/pages/supporters/index.astro

変数名も、なるべく本家のままにしてます。

コンフリクトを処理するときに、なるべく重複する書き方は同じにして、手を加えた部分だけに注目して作業ができるようにしています。

参照元がsub系だけど、変数名はsubが無いのはこのためです。

const posts = await getAllSubPosts()
const post = await getSubPostBySlug(slug)
if (!post) {
  throw new Error('Post not found. slug: ${slug}')
}
const [blocks, allPosts, rankedPosts, recentPosts, tags, postsHavingSameTag] =
  await Promise.all([
    getAllBlocksByBlockId(post.PageId),
    getAllSubPosts(),
    getRankedSubPosts(),
    getSubPosts(5),
    getAllSubTags(),
    getSubPostsByTag(post.Tags[0]?.name, 6),
  ])

同様にして、2ページ目以降の記事一覧ページやタグ一覧ページなどを修正しています。

  • src/pages/supporters/page/[page].astro
  • src/pages/supporters/[slug].astro
  • src/pages/supporters/tag/[tag].astro
  • src/pages/supporters/tag/[tag]/page/[page].astro

魔改2:サポータープロフィール用DBのプロパティを増やす

元々、Notionサポーター専用のプロフィールDBがありまして、それを元にカラム名を修正することになりました。

image block
image block
image block

image block
1/3
image block
2/3
image block
3/3

カラム数は26!!

ちなみに本家のカラム数は8なので、だいぶ多いです。

ですが、プロパティの種類を見ると重複しているものばかりで基本的にはベースのコードをコピペすれば対応できそうな見通しが持てます。

image block

※ カテゴリーはリレーションせずMulti-selectで対応していただくこととなりましたm(_ _)m

新たに必要なプロパティは、formulaとURL!!

型定義

とっても簡単です

export interface SubPost {
.
.
	Katagaki: string
  Kyoten: SelectProperty[]
  TokuinaKoto: SelectProperty[]
  TokuinaGyoushu: string
  Certifiled: SelectProperty[]
  NotionStartDate: string
  NotionUseYears: string
  Ryoukin: SelectProperty[]
  Soudan: boolean
  Shoukai: boolean
  Homepage: string
  Twitter: string
  Instagram: string
  YouTube: string
  Note: string
}
src/lib/interfaces.ts

データ取得

ビルドのプロパティを増やすだけ!

function _buildSubPost(pageObject: responses.PageObject): SubPost {
.
.
	const subPost: SubPost = {
		Katagaki:
      prop.Katagaki.rich_text && prop.Katagaki.rich_text.length > 0
        ? prop.Katagaki.rich_text.map((t) => t.plain_text).join('')
        : '',
		Kyoten: prop.Kyoten.multi_select ? prop.Kyoten.multi_select : [],
    TokuinaKoto: prop.TokuinaKoto.multi_select
      ? prop.TokuinaKoto.multi_select
      : [],
    TokuinaGyoushu:
      prop.TokuinaGyoushu.rich_text && prop.TokuinaGyoushu.rich_text.length > 0
        ? prop.TokuinaGyoushu.rich_text.map((t) => t.plain_text).join('')
        : '',
    Tags: prop.Tags.multi_select ? prop.Tags.multi_select : [],
    Certifiled: prop.Certifiled.multi_select
      ? prop.Certifiled.multi_select
      : [],
    NotionStartDate: prop.NotionStartDate.date
      ? prop.NotionStartDate.date.start
      : '',
    NotionUseYears: prop.NotionUseYears.formula.string
      ? prop.NotionUseYears.formula.string
      : '',
    Ryoukin: prop.Ryoukin.multi_select ? prop.Ryoukin.multi_select : [],
    Soudan: prop.Soudan.checkbox ? true : false,
    Shoukai: prop.Shoukai.checkbox ? true : false,
    Homepage: prop.Homepage.url ? prop.Homepage.url : '',
    Twitter: prop.Twitter.url ? prop.Twitter.url : '',
    Instagram: prop.Instagram.url ? prop.Instagram.url : '',
    YouTube: prop.YouTube.url ? prop.YouTube.url : '',
    Note: prop.Note.url ? prop.Note.url : '',

Component・表示

魔改1と同じ要領なので割愛しますwww

魔改3:サポータープロフィールページにプロパティも表示させる

魔改2で用意したComponentを使って表示させつつStyleをいじるだけです💖

  		<SubPostTags post={post} />
      <PostFeaturedImage post={post} />
      <SubPostTitle post={post} enableLink={false} />
      <SubPostKatagaki post={post} />
      <SubPostKyoten post={post} enableLink={false} />
      <SubPostTokuinaKoto post={post} />
      <SubPostTokuinaGyoushu post={post} />
      <SubPostNotionUseYears post={post} />
      <SubPostRyoukin post={post} />
      <SubPostSoudan post={post} />
      <SubPostShoukai post={post} />
      <SubPostHomepage post={post} />
      <SubPostTwitter post={post} />
      <SubPostInstagram post={post} />
      <SubPostYouTube post={post} />
src/pages/supporters/[slug].astro
image block
image block

魔改4:記事用DBのfooterタグの有無で一覧に表示されないようにする

これは、めちっちゃ楽しいです 🤩

記事用DBでTagsを【footer】にすると、サイト上の記事一覧ページには表示されない…という仕掛けです。

image block

記事一覧には表示されないfooter記事はサイドメニューの「legal Infomation」として並びます。

image block

条件分岐で切り分ければOK🔥

const [allPostsNoFilter] = await Promise.all([getAllPosts()])
const footerPosts = allPostsNoFilter.filter(
  (post) => !!post.Tags.some((tag) => tag.name === 'footer')
)
src/layouts/Layout.astro

おわり

以上の4箇所に手を加えたのが、Notionサポーターサイトです。

作ったのは4月頃で、熱量も低下中ではありますがastro-notion-blog 0.8.0にアップデート作業中ですので、しばしお待ちあれです ☺️

アップデート完了でき次第、Notionサポーターさんが使える機能追加報告記事を投稿できたらいいな〜と思っています♫

本当はね、作ったサイトを沢山の人に見てもらいたいし、使ってもらいたいなって思ってるんですよねっ!!!

プロフィールページの更新で分からないことなどありましたら、お気軽にDiscordでメンションつけていただければ駆けつけます〜〜〜(⊙ꇴ⊙)

Twitterでは更新のお知らせを随時行っています

興味ある方はLet'sフォロー★

▼ この記事に興味があったら同じタグから関連記事をのぞいてみてね

Buy Me A Coffee

新着記事を通知したい??


RSSリーダーにatomのリンクを登録すると通知が行くよ🐌

https://herohoro.com/atom

やってみてね(*´ω`*)(*´ω`*)

Twitter Timeline


フォロー大歓迎\(^o^)/