お察しの通り、Notionサポーターのサイトは私が作っています。
↓
astro-notion-blogを魔改造してサイトっぽくしています。
本来であれば、NotionDBを1つ繋げて記事を表示させていくのが本家(astro-notion-blog)ですが、
このサイトではNotionDBを2つ繋げ、固定ページも繋げています✌
サイトが公開された当初の紹介動画はNotionサポーターのYouTubeで触れられていますので、この記事では構造的な部分を解説できればと思います。
※ 30:42あたり参照
改造するにあたり、気をつけたことは唯一つ【本家のアップデートに対応できるようにいじる】
作って終わりではなく、アップデートの差分を取り込む際になるべく楽できるようにしました。
本家のアップデートがある限り最新版のNotionサポーターサイトを提供し続けたいなという気持ちなのですw
一旦、本家のNotionDBを複製し、DB名を【Sub Post】としてみます。
Notion APIでNotion内のデータを取得できるよう環境変数を追加します。
export const SUB_DATABASE_ID =
import.meta.env.SUB_DATABASE_ID || process.env.SUB_DATABASE_ID || ''
本家の型定義は「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
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
}
記事用のgetPostsも複製し、参照元getAllPostsからgetAllSubPostsに修正します。
export async function getSubPosts(pageSize = 10): Promise<SubPost[]> {
const allSubPosts = await getAllSubPosts()
return allSubPosts.slice(0, pageSize)
}
同様にして、関連する記事一覧の関数をgetAllSubPostsに修正していきます。
ビルドの部分のプロパティも型定義は先程追加したsubPost
を使います。
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
}
記事のパスを追加したディレクトリ名(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()}`)
}
ここも、本家のコードを複製して微調整するだけです。
本家のcomponentを使い回せない部分のみ追加してます。
参照元を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>
変数名も、なるべく本家のままにしてます。
コンフリクトを処理するときに、なるべく重複する書き方は同じにして、手を加えた部分だけに注目して作業ができるようにしています。
参照元が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ページ目以降の記事一覧ページやタグ一覧ページなどを修正しています。
元々、Notionサポーター専用のプロフィールDBがありまして、それを元にカラム名を修正することになりました。
↓
カラム数は26!!
ちなみに本家のカラム数は8なので、だいぶ多いです。
ですが、プロパティの種類を見ると重複しているものばかりで基本的にはベースのコードをコピペすれば対応できそうな見通しが持てます。
※ カテゴリーはリレーションせずMulti-selectで対応していただくこととなりましたm(_ _)m
とっても簡単です
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
}
ビルドのプロパティを増やすだけ!
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 : '',
魔改1と同じ要領なので割愛しますwww
魔改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} />
これは、めちっちゃ楽しいです 🤩
記事用DBでTagsを【footer】にすると、サイト上の記事一覧ページには表示されない…という仕掛けです。
↓
記事一覧には表示されないfooter記事はサイドメニューの「legal Infomation」として並びます。
条件分岐で切り分ければOK🔥
const [allPostsNoFilter] = await Promise.all([getAllPosts()])
const footerPosts = allPostsNoFilter.filter(
(post) => !!post.Tags.some((tag) => tag.name === 'footer')
)
✌
以上の4箇所に手を加えたのが、Notionサポーターサイトです。
作ったのは4月頃で、熱量も低下中ではありますがastro-notion-blog 0.8.0にアップデート作業中ですので、しばしお待ちあれです ☺️
アップデート完了でき次第、Notionサポーターさんが使える機能追加報告記事を投稿できたらいいな〜と思っています♫
本当はね、作ったサイトを沢山の人に見てもらいたいし、使ってもらいたいなって思ってるんですよねっ!!!
プロフィールページの更新で分からないことなどありましたら、お気軽にDiscordでメンションつけていただければ駆けつけます〜〜〜(⊙ꇴ⊙)
▼ この記事に興味があったら同じタグから関連記事をのぞいてみてね
RSSリーダーにatomのリンクを登録すると通知が行くよ🐌
https://herohoro.com/atom
やってみてね(*´ω`*)(*´ω`*)
フォロー大歓迎\(^o^)/
フォロー大歓迎\(^o^)/