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](https://github.com/samuelkraft/notion-blog-nextjs)

# `notion-blog-nextjs`のしくみ

- ビルドすると `index.js` が組み立てられる
- Notionのページはそれぞれ `[id].js` を雛形に組み立てられる

 むっちゃ簡単に言うとこんな感じです。
つまり、 `[id].js` をいじればいろいろ変えられるということですね。

# idページを編集する

`[id].js`のここをいじると、idページの見た目を変えることができます。
自由に書き加えていきましょう。

```jsx
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>
  );
}
```

今回大事になってくるのがここになります。

```jsx
{blocks.map((block) => (
  <Fragment key={block.id}>{renderBlock(block)}</Fragment>
))}
```

今の時点ではとてもシンプルですが、ここに以下の機能を足していきます。

- リストの状況を見ながら `<ul>~</ul>` を挿入する。
- Notion側の表記に応じて、リストの点を数字かポチか決める。

## つまるところ目標は

Notionで書いたこれの再現。

![https://i.gyazo.com/746e64a0ebc8a5fedff8948947df94c1.png](https://i.gyazo.com/746e64a0ebc8a5fedff8948947df94c1.png)

とりあえず想定していることとして

- 複数個のリストの取扱い
- 種類の違うリストの取扱い
- リストをつなげるとどうなるのか
- リストをネストさせているとどうなるのか
- ちゃんと他の要素(今回はプレーンなテキストも表示できるか)

を見ていきます。

# とりあえず結論

こう書けばいい感じの挙動になります。(投げやり)
ちなみに、ここから `tailwind`を使って装飾しています。

```jsx
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` に流し込んでやると、

![https://i.gyazo.com/5c1b60c98d7e24c19aa15f7018c67209.png](https://i.gyazo.com/5c1b60c98d7e24c19aa15f7018c67209.png)

こんな感じになります。

`さしすせそ` の部分や、 `おお`と`かか`の間あたりのスペースが残念な結果になりましたが、
まあこう書くほうが悪いだろってことで無視していきます。

# コード解説

分けて解説していきます。

## `Pages_template`について

- いろいろな変数や定数を設定していきます。
- `disc_list`と `decimal_list`で、点のタイプを仕分けていきます。
- `const`の類はいちいち変えるのがめんどくさい箇所に設定しています。

```jsx
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部分の処理を担っています。

```jsx
return (
        <div className={c}>
            <Header pagename={pagename} />
            <h1 className="text-4xl font-bold pb-5">{pagename}</h1>
            <section>
               ~
            </section>
            <Footer />
        </div>
    );
}
```

今回一番のキモです。

```jsx
{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>`を書き出す際には以下の処理に通していきます。

```jsx
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`つけてあげる

動かない

```jsx
if (配列 > 0) 
```

動く

```jsx
if (配列.length > 0) 
```

## `.map`は案外自由が効く

関数と考え方が似ているなと思いました。
仕組みさえわかればこっちのものですね。