いよいよ最後になりました。
振り返ってみると......
少しずつeasy-notion-blogっぽくなってきました★
残すは本文を表示させるのみ\(^o^)/
Notionには様々なblockがありますが、
テキストのみを表示させていきます。
テキストを表示させる流れが分かると、
他のblockも応用させて実装できるので、
基本的な部分に絞って説明していきます(*´ω`*)(*´ω`*)
NotionAPI公式リファレンスのサイドメニューにある【Block object】を開くと説明があります。
https://developers.notion.com/reference/block
Block objectには
10項目ある。
blockの種類によって変わるのがtype
と{type}
の部分。
テキスト(paragraph)blockの場合は.....
rich text objectの配列を含めているので、
Paragraph blocksの説明では少し省略されています。
https://developers.notion.com/reference/rich-text
▼ まとめるとこんな感じ
取得するためには
easy-notion-blogはTypeScriptなので型定義をinterfaces.tsで行います。
export interface Block {
Id: string;
Type: string;
HasChildren: boolean;
Paragraph?: Paragraph;
}
「なんでstring?なんでboolean?」の確認は
Blocks object keysの表にあるType列を参照してください。
https://developers.notion.com/reference/block#block-object-keys
使いたい部分だけを定義していきますーーーー(●´ω`●)
最後のParagraph?
の部分はParagraphだったら......
この後に追加するexport interface Paragraph
に続きます。
//つづき
export interface Paragraph {
RichTexts: RichText[];
Children?: Block[];
}
ひとまず文字の色などは実装せず純粋なテキストを表示させてみようと思います。
RichTextは配列になっているのでまた追加していきます。
//つづき
export interface RichText {
Text: Text;
PlainText: string;
}
rich textのタイプをtextに指定したので、また後で詳しく追加します。
//つづき
export interface Text {
Content: string;
}
client.tsxで必要な情報を仕分けていきます。
interfaces.tsで記述した型定義を取り込み......
import {
Post,
Block,
Paragraph,
RichText,
Text
} from "./interfaces";
block IDで取得するための関数を用意します。
export async function getAllBlocksByBlockId(blockId) {
let allBlocks: Block[] = [];
const params = {
block_id: blockId
};
// このあとまだ続けるよ
NotionDBのitemは.....blockとして扱う!!
https://zenn.dev/5t111111/articles/80934cdda55328
Page content はそれを構成するブロックの集合体であり、そのデータを取得するためにはまた別のエンドポイントを使わないといけない、ということです。
blockをあのblockと考えると混乱するので、
ブログのページのことだな....と考えると急に納得してきます。
export async function getAllBlocksByBlockId(blockId) {
let allBlocks: Block[] = [];
const params = {
block_id: blockId
};
// このあとまだ続けるよ
Retrieve block childrenを使っていきます。
childrenは本来のblockのことなのかも....(*´ω`*)(*´ω`*)
リファレンスにあるサンプルコードはこんな感じ
const { Client } = require('@notionhq/client');
const notion = new Client({ auth: process.env.NOTION_API_KEY });
(async () => {
const blockId = 'b55c9c91-384d-452b-81db-d1ef79372b75';
const response = await notion.blocks.children.list({
block_id: blockId,
page_size: 50,
});
console.log(response);
})();
既にClientや環境変数は記述済なので.....
export async function getAllBlocksByBlockId(blockId) {
let allBlocks: Block[] = [];
const params = {
block_id: blockId
};
// 👆 ここまで同じ 👇 ここからがサンプルコード参照箇所
const data = await client.blocks.children.list(params);
const blocks = data.results.map((item) => {
const block: Block = {
Id: item.id,
Type: item.type,
HasChildren: item.has_children
};
//このあとまだ続けるよ
const blockの中身はinterfaces.tsで定義した部分と重なる〜
export interface Block {
Id: string;
Type: string;
HasChildren: boolean;
Paragraph?: Paragraph;
}
型定義にはParagraphについてもあったので、
つづきはParagraphだった場合の記述
//つづき
switch (item.type) {
case "paragraph":
const paragraph: Paragraph = {
RichTexts: item.paragraph.rich_text.map(_buildRichText)
};
block.Paragraph = paragraph;
break;
}
他にもblockの種類があればcaseで追加していくようだけど、
今回はParagraphのみなので閉じちゃいます。
//つづき
return block;
});
allBlocks = allBlocks.concat(blocks);
params["start_cursor"] = data.next_cursor;
return allBlocks;
}
blocksを1つずつ取り出してblockの種類ごとに必要な要素を取得してからallBlocksにしまっちゃう。
params["start_cursor"] = data.next_cursor;
は前々回やったPaginationのResponseの要素
https://developers.notion.com/reference/pagination
NotionAPIで取得したslugの値で記事リンクを作成する | getStaticPathとDynamic routesが便利だ_Blog learn04
今回なるべく真偽判定をカット(動くからいいや!という判断...)して説明してしまっていますが、
本来はhas_moreの判定もあります!!!
export async function getAllBlocksByBlockId(blockId) {
let allBlocks: Block[] = [];
const params = {
block_id: blockId
};
while (true) { 👉 これ
const data = await client.blocks.children.list(params);
const blocks = data.results.map((item) => {
const block: Block = {
Id: item.id,
Type: item.type,
HasChildren: item.has_children
};
switch (item.type) {
case "paragraph":
const paragraph: Paragraph = {
RichTexts: item.paragraph.rich_text.map(_buildRichText)
};
block.Paragraph = paragraph;
break;
}
return block;
});
allBlocks = allBlocks.concat(blocks);
if (!data.has_more) { 👉 これ
break;
}
params["start_cursor"] = data.next_cursor;
}
return allBlocks;
}
while (true)
って何に対してなのかが謎のままです.....(知ってる人いたら教えて~~~~)if(!data.has_more)
はpaginationのresponseの要素のhas_moreで最後ならfalseになるってのは分かるんだけど、削除しても動いちゃってるんだよね。。。謎.......最後にmapの引数の_buildRichTextを記述したら完成★
// つづき
function _buildRichText(item) {
const text: Text = {
Content: item.text.content
};
const richText: RichText = {
Text: text,
PlainText: item.plain_text
};
return richText;
}
interfaces.tsで定義した部分と見比べてみると.....
export interface RichText {
Text: Text;
PlainText: string;
}
export interface Text {
Content: string;
}
同じだねーーー\(^o^)/
石橋を叩いて渡る慎重さで進めます。
.....というのも、エラーにハマってだいぶ苦労しました。
console.dir()
を使ってちゃんと取得できているかを確認しみます(´・ω・`)
console.logはよく耳にします。
実際に使ってみると入れ子になってるNotionAPIの情報を深くまで確認できず困りました。
調べてみると....
2階層までしか表示できないconsole.logを
console.dirにすることで丸っと確認できるってことが分かりました\(^o^)/
console.dir()
はオブジェクトを何階層下まで出力するかを第二引数で指定できる模様。第二引数はオプションのオブジェクト。そこにdepth: null
を指定すると、一番下まで出力してくれる。(デフォルト値はdepth: 2
)
Node.js の console.log() で [Object] とかの中身を見る方法
👆
こちらの記事、すごく分かりやすかったです。
clientで記述した内容を[slug].tsxで使えるように追加していきます。
// getAllBlocksByBlockIdを追加
import {
getPostBySlug,
getAllPosts,
getAllBlocksByBlockId
} from "../../lib/client";
//他のimportは同じなので省略
// const blocksの行を追加し、propsにblocksを追加
export async function getStaticProps({ params: { slug } }) {
const post = await getPostBySlug(slug);
const blocks = await getAllBlocksByBlockId(post.PageId);
return {
props: { post, blocks },
revalidate: 60
};
}
//export async function getStaticPaths()は同じなので省略
//blocks=[]を追加
const RenderPost = ({ post, blocks = [] }) => {
// console.dirを使いたい場所に追加する
return (
<div className={styles.container}>
<div className={styles.mainContent}>
<p>Post: {slug}</p>
<PostDate post={post} />
<PostTitle post={post} enableLink={false} />
{console.dir(blocks, { depth: null })} 👉 追加
</div>
</div>
);
};
export default RenderPost;
するとちゃんと取得できているかを確認できます\(^o^)/
私はハマりました。
怪しいparagraphの場合分けの部分に失敗フラグ🚩を追加して確認することで
原因を見つけることが出来ました。
export async function getAllBlocksByBlockId(blockId) {
let allBlocks: Block[] = [];
const params = {
block_id: blockId
};
// while (true) {
const data = await client.blocks.children.list(params);
const blocks = data.results.map((item) => {
const block: Block = {
Id: item.id,
Type: item.type,
HasChildren: item.has_children
};
switch (item.type) {
case "paragraph":
const paragraph: Paragraph = {
RichTexts: item.paragraph.rich_text.map(_buildRichText)
};
block.Paragraph = paragraph;
break;
// ▼ paragraphと認識できていなかったら表示させる作戦
default:
console.log("失敗....");
// ▲ ▲ ▲
}
return block;
});
allBlocks = allBlocks.concat(blocks);
// if (!data.has_more) {
// break;
// }
params["start_cursor"] = data.next_cursor;
// }
return allBlocks;
無事paragraphを取得できたので表示させていきますーーー(*^^*)
NotionDBの列情報を表示させる時は簡単に済ませられましたが、
page内の情報を表示させるにはまたひと手間必要なようです。(^_^;)
流れとしては...
取得したblocksから
オブジェクト配列に含まれる配列のみを
結合して整理する......部分で泣きそうになりました(´・ω・`)www
componentsフォルダ内のblog-parts.tsx
にPostBodyを追加します。
export const PostBody = ({ blocks }) => (
<div className={styles.postBody}>
{wrapListItems(blocks).map((block, i) => (
<NotionBlock block={block} key={`post-body-${i}`} />
))}
</div>
);
まだ記述していない関数もありますが、
ざっくり見てみると......
取得したblocksを1つずつmapで取り出してNotionBlockコンポネントで処理しているのが分かります。
wrapListItems
をPostBodyの上に追加します。
const wrapListItems = (blocks) =>
blocks.reduce((arr, block) => {
return arr.concat(block);
}, []);
//この後PostBodyの記述が始まる
Mdn Web Docsを参照すると、
リストの値を1つずつ処理していっている....
wrapsListItemに置き換えて考えてみる.....
const wrapListItems = (blocks) =>
blocks.reduce((arr, block) => {
return arr.concat(block);
}, []);
//この後PostBodyの記述が始まる
arr
block
return arr.concat(block)
[]
blocksが二次元配列だったのかを確認してみると.....
[
{
Id: '0b0****************',
Type: 'paragraph',
HasChildren: false,
Paragraph: {
RichTexts: [
{
Text: {
Content: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
},
PlainText: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
}
]
}
},
{
Id: '3d6*******************',
Type: 'paragraph',
HasChildren: false,
Paragraph: { RichTexts: [] }
},
{
Id: '7b********************',
Type: 'paragraph',
HasChildren: false,
Paragraph: {
RichTexts: [ { Text: { Content: '2つ目テキスト' }, PlainText: '2つ目テキスト' } ]
}
}
]
もう少し解説記事読み進めていくと....
// friends - an array of objects
// where object field "books" is a list of favorite books
let friends = [{
name: 'Anna',
books: ['Bible', 'Harry Potter'],
age: 21
}, {
name: 'Bob',
books: ['War and peace', 'Romeo and Juliet'],
age: 26
}, {
name: 'Alice',
books: ['The Lord of the Rings', 'The Shining'],
age: 18
}]
// allbooks - list which will contain all friends' books +
// additional list contained in initialValue
let allbooks = friends.reduce(function(previousValue, currentValue) {
return [...previousValue, ...currentValue.books]
}, ['Alphabet'])
// allbooks = [
// 'Alphabet', 'Bible', 'Harry Potter', 'War and peace',
// 'Romeo and Juliet', 'The Lord of the Rings',
// 'The Shining'
// ]
returnの[...previousValue, ...currentValue.books]
はfriendsのbooksの値を指定してる!!
booksのみをくっつけていったら....
[ ]
が入っていたはずだけど入れずに羅列していくのがreduce
ってやつなのかな...。
// allbooks = [
// 'Alphabet', ['Bible', 'Harry Potter' ], ['War and peace',
// 'Romeo and Juliet' ], ['The Lord of the Rings',
// 'The Shining' ]
// ]
👇
// allbooks = [
// 'Alphabet', 'Bible', 'Harry Potter', 'War and peace',
// 'Romeo and Juliet', 'The Lord of the Rings',
// 'The Shining'
// ]
入れ子になっているbooksの値がリストになって整頓できるってことだから、
blocksの場合に置き換えて考えてみると.....
const wrapListItems = (blocks) =>
blocks.reduce((arr, block) => {
return arr.concat(block);
}, []);
//この後PostBodyの記述が始まる
arr
block
return arr.concat(block)
[]
[{
Id: '0b0****************',
Type: 'paragraph',
HasChildren: false,
Paragraph: {
RichTexts: [
{
Text: {
Content: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
},
PlainText: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
}
]
}
},
{
//つづく....
確認するためには....
wrapListItemsがどんな結果になっているのかを知りたい。
const wrapListItems = (blocks) =>
blocks.reduce((arr, block) => {
return arr.concat(block);
}, []); // 👉 この[ ]を確認したい...
export const PostBody = ({ blocks }) => (
<div>
{wrapListItems(blocks).map((block, i) => (
<NotionBlock block={block} key={`post-body-${i}`} />
))}
</div>
);
// PostBodyの後に追記
export const PostBodyTest = ({ blocks }) => (
<div>
{wrapListItems(blocks).map((block) => console.dir(block, { depth: null }))}
</div>
);
mapでwrapListItems
の中身[ ]
を1つずつ取り出すconsoleを作ってみた。
{
Id: '0b*******************',
Type: 'paragraph',
HasChildren: false,
Paragraph: {
RichTexts: [
{
Text: {
Content: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
},
PlainText: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
}
]
}
}
{
Id: '3************************',
Type: 'paragraph',
HasChildren: false,
Paragraph: { RichTexts: [] }
}
{
Id: '7b**********************',
Type: 'paragraph',
HasChildren: false,
Paragraph: {
RichTexts: [ { Text: { Content: '2つ目テキスト' }, PlainText: '2つ目テキスト' } ]
}
}
map前のwrapListItemsの姿は...
1つずつ[ ]
に戻した形...
reduce処理の前と同じだ....。
とりあえず、PostBodyTestコンポネントのようなconsoleを作ってみると....
export const PostBodyNoReduce=({blocks})=>{
const noreduce={blocks}
return(
<div>
{noreduce.map((block)=>console.dir(block,{depth:null}))}
</div>
)
}
Σ(゚Д゚)Σ(゚Д゚)
map
がエラー.....TypeError: noreduce.map is not a function
出直しますーーーーm(_ _)m
処理する前の[ ]
ってreduceで収納してる[ ]
と意味違うのかな??.... 05/22
wrapListItemsを使って
mapで1つずつ取り出しながらNotionBlockコンポネントに処理を投げます。
const wrapListItems = (blocks) =>
blocks.reduce((arr, block) => {
return arr.concat(block);
}, []);
// ▼ これ
export const PostBody = ({ blocks }) => (
<div className={styles.postBody}>
{wrapListItems(blocks).map((block, i) => (
<NotionBlock block={block} key={`post-body-${i}`} />
))}
</div>
);
これで取得したblocksを1つずつ表示できるようにすることができました\(^o^)/
まだ終わりではない....
(ツライね。)
componentsフォルダにnotion-block.tsxを用意します。
paragraphの場合に処理する内容を記述.....
const NotionBlock = ({ block }) => {
if (block.Type === "paragraph") {
return <Paragraph block={block} />;
}
return null;
};
export default NotionBlock;
paragraphの場合にParagraphコンポネントを使う。
const Paragraph = ({ block }) => (
<p>
{block.Paragraph.RichTexts.map((richText, i) => (
<RichText richText={richText} key={`paragraph-${block.Id}-${i}`} />
))}
</p>
);
// このあと NotionBlockの記述が始まる
ちょっとblockを確認
[
// ▼ block でまとめられている{}の部分
{
Id: '0b0***************',
Type: 'paragraph',
HasChildren: false,
Paragraph: {
RichTexts: [
{
Text: {
Content: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
},
PlainText: 'テキストをnotionAPIを使ってブログを作る方法というタイトルのページ内に入力。'
}
]
}
},
mapで1つずつRichTextsの要素(Text・PlainText)を取って生きながらRichTextコンポネントに投げてる!!
RichTextコンポネントも追加
const RichText = ({ richText }) => {
let element = richText.Text.Content;
return element;
};
//このあと Paragraphコンポネントの記述がはじまる
Contentの内容を返してる!!
これでやっと
blog-parts.tsxで記述したPostBody
コンポネントを
使えるようになりました\(^o^)/
blog-parts.tsxにNotionBlockをimport
import NotionBlock from "./notion-block";
notion-block.tsxにはReactをimport
import React from "react";
これでOK\(^o^)/
ここからはいつもと同じ流れ。
使いたい場所である[slug].tsxに必要なPostBodyコンポネントを追加でimport
import {
PostDate,
PostTitle,
PostBody
} from "../../components/blog-parts";
const RenderPost = ({ post, blocks = [] }) => {
const router = useRouter();
const { slug } = router.query;
return (
<div className={styles.container}>
<div className={styles.mainContent}>
<p>Post: {slug}</p>
<PostDate post={post} />
<PostTitle post={post} enableLink={false} />
<PostBody blocks={blocks}/> *** 👉 これ追加 ***
</div>
</div>
);
};
出てきたーー\(^o^)/\(^o^)/
取得する
表示させる
今回使用したcode sandboxはこちら
https://codesandbox.io/embed/slug-block-paragraph-825ojw?fontsize=14&hidenavigation=1&theme=dark
GitHubリポジトリに収納済です\(^o^)/
No. | branch | description | リンク | PR |
---|---|---|---|---|
7 | rseo19 | API routing _入力フォーム | https://github.com/herohoro/Blog_learn/pull/9 | #9 |
8 | rw064k | Data fetching _収納したディレクトリからテキストデータを表示_lib | https://github.com/herohoro/Blog_learn/pull/10 | #10 |
9 | 9cp6j7 | NotionAPIからDBのタイトル・テキスト・日付列を表示させる_他_lib/環境変数module/getStaticProps/props | https://github.com/herohoro/Blog_learn/pull/11 | #11 |
10 | oek04h | slugを使って画面遷移_getStaticPath | https://github.com/herohoro/Blog_learn/pull/12 | #12 |
11 | oek04h | 修正_画面遷移のエラーを解消 | https://github.com/herohoro/Blog_learn/pull/13 | #13 |
12 | zz22j8 | DB列のタイトル・日付を記事ページに表示させる | https://github.com/herohoro/Blog_learn/pull/15 | #15 |
13 | 825ojw | NotionDBのpage内テキストを表示させる | https://github.com/herohoro/Blog_learn/pull/16 | #16 |
リンク列のPRページへ行くと、コメント内に解説記事も追記してます。
このブログ(Blog learnタグ)からでも
作成過程を追えるようになっているので活用してみてくださいーーー
本文を表示させるのが予想以上に難しくて
だいぶ格闘しました(´・ω・`)(;・∀・)
実際はparagraphブロックだけではなく
いろいろな種類のブロックを場合分けしながら
取得・表示させているアルパカ先生は本当すごい方だなと
思うと同時に、
「いったいどんなスキルを積んだらこんな記述を使いこなせるの?」
と驚かされるばかりでした。
見た目は質素で実用的なレベルとは程遠いBlog Learnの実装レベルですが、
この調子で作り込んでいくには....
いったい何日かかるのやらです。
OSSありがたき。
easy-notion-blogバンザイです。
(*´ω`*)(*´ω`*)🚩
途中とっちらかった説明をしてる部分があったかと思います。
私一人では手に負えなかった.....
分かり次第修正して完成させていく予定です。
Twitterで教えてくれると嬉しいです🙌
Twitterでは更新のお知らせを随時行っています
興味ある方はLet'sフォロー★▼ この記事に興味があったら同じタグから関連記事をのぞいてみてね
RSSリーダーにatomのリンクを登録すると通知が行くよ🐌
https://herohoro.com/atom
やってみてね(*´ω`*)(*´ω`*)
フォロー大歓迎\(^o^)/
フォロー大歓迎\(^o^)/