🗒️
Next.jsでNotion APIを使った<ul>と<li>の取り扱い【notion-blog-nextjs】
やること
-
notion-blog-nextjs
を元にして -
<li>
を<ul>
で囲み - Notionと表記を(だいたい)同期させる
- …あっ、CSSは
tailwind
で書いていきます
notion-blog-nextjs
を導入する
- Githubに公開されている
samuelkraft / notion-blog-nextjs
を - 好きなフォルダに
clone
して - 該当ディレクトリに
cd ~
して -
npm install
してパッケージをインストールして -
npm run dev
で動かせます - あとは
.env.local
にいろいろ書けば自分のNotionのデータを参照させられます。
詳しくは以下リポジトリのREADMEを参考にしてください。
https://github.com/samuelkraft/notion-blog-nextjs
notion-blog-nextjs
のしくみ
- ビルドすると
index.js
が組み立てられる - Notionのページはそれぞれ
[id].js
を雛形に組み立てられる
むっちゃ簡単に言うとこんな感じです。
つまり、 [id].js
をいじればいろいろ変えられるということですね。
idページを編集する
[id].js
のここをいじると、idページの見た目を変えることができます。
自由に書き加えていきましょう。
export default function Post({ page, blocks }) {
if (!page || !blocks) {
return <div />;
}
return (
<div>
<Head>
<title>{page.properties.Name.title[0].plain_text}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<article className={styles.container}>
<h1 className={styles.name}>
<Text text={page.properties.Name.title} />
</h1>
<section>
{blocks.map((block) => (
<Fragment key={block.id}>{renderBlock(block)}</Fragment>
))}
<Link href="/">
<a className={styles.back}>← Go home</a>
</Link>
</section>
</article>
</div>
);
}
今回大事になってくるのがここになります。
{blocks.map((block) => (
<Fragment key={block.id}>{renderBlock(block)}</Fragment>
))}
今の時点ではとてもシンプルですが、ここに以下の機能を足していきます。
- リストの状況を見ながら
<ul>~</ul>
を挿入する。 - Notion側の表記に応じて、リストの点を数字かポチか決める。
つまるところ目標は
Notionで書いたこれの再現。
とりあえず想定していることとして
- 複数個のリストの取扱い
- 種類の違うリストの取扱い
- リストをつなげるとどうなるのか
- リストをネストさせているとどうなるのか
- ちゃんと他の要素(今回はプレーンなテキストも表示できるか)
を見ていきます。
とりあえず結論
こう書けばいい感じの挙動になります。(投げやり)
ちなみに、ここから tailwind
を使って装飾しています。
export function make_ul_li(list_blocks, now_block = null) {
const listclass = "ml-5 p-5";
var ul_class;
switch (list_blocks[0].type) {
case "numbered_list_item":
ul_class = "list-decimal";
break;
case "bulleted_list_item":
ul_class = "list-disc";
break;
}
return (
<>
<ul key={list_blocks.key} className={`${ul_class} ${listclass}`}>
{list_blocks.map((list_block) => renderBlock(list_block))}
</ul>
</>
);
}
export function Pages_template({ blocks, pagename }) {
var disc_list = [];
var decimal_list = [];
const TYPE_DECIMAL = "numbered_list_item";
const TYPE_DISC = "bulleted_list_item";
const c = "flex flex-col w-5/6 m-auto content-center";
var output_code = "";
return (
<div className={c}>
<Header pagename={pagename} />
<h1 className="text-4xl font-bold pb-5">{pagename}</h1>
<section>
{blocks.map((block) => {
if (~block.type.indexOf("list")) {
if (decimal_list.length > 0) {
switch (block.type) {
case TYPE_DISC:
output_code = make_ul_li(decimal_list);
disc_list.push(block);
decimal_list = [];
return output_code;
case TYPE_DECIMAL:
decimal_list.push(block);
break;
}
} else if (disc_list.length > 0) {
switch (block.type) {
case TYPE_DISC:
disc_list.push(block);
break;
case TYPE_DECIMAL:
output_code = make_ul_li(disc_list);
decimal_list.push(block);
disc_list = [];
return output_code;
}
} else if (
decimal_list.length == 0 ||
disc_list.length == 0
) {
switch (block.type) {
case TYPE_DECIMAL:
decimal_list.push(block);
break;
case TYPE_DISC:
disc_list.push(block);
break;
}
}
} else {
if (decimal_list.length > 0) {
output_code = make_ul_li(decimal_list, block);
decimal_list = [];
return output_code;
} else if (disc_list.length > 0) {
output_code = make_ul_li(disc_list, block);
disc_list = [];
return output_code;
} else if (
decimal_list.length == 0 &&
disc_list.length == 0
) {
return renderBlock(block);
}
}
})}
</section>
<Footer />
</div>
);
}
これをいい感じに [id].js
に流し込んでやると、
こんな感じになります。
さしすせそ
の部分や、 おお
とかか
の間あたりのスペースが残念な結果になりましたが、
まあこう書くほうが悪いだろってことで無視していきます。
コード解説
分けて解説していきます。
Pages_template
について
- いろいろな変数や定数を設定していきます。
-
disc_list
とdecimal_list
で、点のタイプを仕分けていきます。 -
const
の類はいちいち変えるのがめんどくさい箇所に設定しています。
var disc_list = [];
var decimal_list = [];
const TYPE_DECIMAL = "numbered_list_item";
const TYPE_DISC = "bulleted_list_item";
const c = "flex flex-col w-5/6 m-auto content-center";
var output_code = "";
-
<Header ~/>
と<Footer ~/>
は他コンポーネントからimportしています。
ちなみに中身は文字通りページのヘッダーやフッター、head部分の処理を担っています。
return (
<div className={c}>
<Header pagename={pagename} />
<h1 className="text-4xl font-bold pb-5">{pagename}</h1>
<section>
~
</section>
<Footer />
</div>
);
}
今回一番のキモです。
{blocks.map((block) => {
if (~block.type.indexOf("list")) {
if (decimal_list.length > 0) {
switch (block.type) {
case TYPE_DISC:
output_code = make_ul_li(decimal_list);
disc_list.push(block);
decimal_list = [];
return output_code;
case TYPE_DECIMAL:
decimal_list.push(block);
break;
}
} else if (disc_list.length > 0) {
switch (block.type) {
case TYPE_DISC:
disc_list.push(block);
break;
case TYPE_DECIMAL:
output_code = make_ul_li(disc_list);
decimal_list.push(block);
disc_list = [];
return output_code;
}
} else if (
decimal_list.length == 0 ||
disc_list.length == 0
) {
switch (block.type) {
case TYPE_DECIMAL:
decimal_list.push(block);
break;
case TYPE_DISC:
disc_list.push(block);
break;
}
}
} else {
if (decimal_list.length > 0) {
output_code = make_ul_li(decimal_list, block);
decimal_list = [];
return output_code;
} else if (disc_list.length > 0) {
output_code = make_ul_li(disc_list, block);
disc_list = [];
return output_code;
} else if (
decimal_list.length == 0 &&
disc_list.length == 0
) {
return renderBlock(block);
}
}
})}
日本語にすると、処理はだいたい以下のようになっています。
-
block.type
に”list”
という文字列が入っているか?- 入ってます、それは間違いなく
<li>
に該当する要素です-
disc_list
、もしくはdecimal_list
に値が入っていますか?- 入っています
- 現在値が入るのは同じリストですか?
- 同じリストです、現在値をそのリストに追加して最初に戻ります。
- 違うリストです
- リストを
<ul>
にまとめて、リストは初期化します。
違うリストに現在値を追加して最初に戻ります。
- リストを
- 現在値が入るのは同じリストですか?
- 入っていません
- 現在値が入るのは同じリストですか?
- 同じリストです、現在値をそのリストに追加して最初に戻ります。
- 違うリストです
- リストを
<ul>
にまとめて書き出します。
リストは初期化します。
違うリストに現在値を追加して最初に戻ります。
- リストを
- 現在値が入るのは同じリストですか?
- 入っています
-
- 入っていません、それは別の要素になります。
-
disc_list
、もしくはdecimal_list
に値が入っていますか?- 入っています
- リストを
<ul>
にまとめて書き出します。
リストは初期化します。
要素をそのまま書き出します。
最初に戻ります。
- リストを
- 入っていません
- 要素をそのまま書き出します。
最初に戻ります。
- 要素をそのまま書き出します。
- 入っています
-
- 入ってます、それは間違いなく
で、 <ul>
を書き出す際には以下の処理に通していきます。
export function make_ul_li(list_blocks) {
const listclass = "ml-5 p-5";
var ul_class;
switch (list_blocks[0].type) {
case "numbered_list_item":
ul_class = "list-decimal";
break;
case "bulleted_list_item":
ul_class = "list-disc";
break;
}
return (
<>
<ul key={list_blocks.key} className={`${ul_class} ${listclass}`}>
{list_blocks.map((list_block) => renderBlock(list_block))}
</ul>
</>
);
}
以上です。
割と力技みたいな感じになったので、もっとスマートな書き方があればご教示ください。
詰まったところ
.length
つけてあげる
リストの個数はちゃんと 動かない
if (配列 > 0)
動く
if (配列.length > 0)
.map
は案外自由が効く
関数と考え方が似ているなと思いました。
仕組みさえわかればこっちのものですね。
Discussion