ECMaker / blog

https://blog.ec-maker.com/
0 stars 0 forks source link

bookmark, quote, Todo型の修正 #62

Closed u-ecmaker closed 5 months ago

u-ecmaker commented 5 months ago

現象

Notion
(アイキャッチ画像が取得できないのはもともとのバグ。別起票 #9 ) image 表示
Normalのみ表示できる
image

u-ecmaker commented 5 months ago

quote, ToDO

NotionではQuoteのみ
image

VSCode debug を使って PostContentのblockを取得
ここまでは のぶs vs かなるs に差はない image

{
  "object": "block",
  "id": "87194b6a-23c4-43c7-858e-5de4b9987738",
  "parent": {
    "type": "page_id",
    "page_id": "ea24329b-4657-4beb-a4ed-ef2367f6f4d9"
  },
  "created_time": "2024-03-17T14:39:00.000Z",
  "last_edited_time": "2024-03-17T15:01:00.000Z",
  "created_by": {
    "object": "user",
    "id": "a77106c5-a562-456a-9b47-255677e64984"
  },
  "last_edited_by": {
    "object": "user",
    "id": "a77106c5-a562-456a-9b47-255677e64984"
  },
  "has_children": false,
  "archived": false,
  "type": "quote",
  "quote": {
    "rich_text": [
      {
        "type": "text",
        "text": {
          "content": "quote",
          "link": null
        },
        "annotations": {
          "bold": false,
          "italic": false,
          "strikethrough": false,
          "underline": false,
          "code": false,
          "color": "default"
        },
        "plain_text": "quote",
        "href": null
      }
    ],
    "color": "default"
  }
}

解決 #63

bug/#62_quote,ToDo fix: コピーミスしてただけ、ダブってただけ。

Remaning Task

u-ecmaker commented 5 months ago

OGPのネスト

現状把握

[slug]、childrenとchildrenWithOgpで何が違うのかを得る

  const children = (await /*かなるs方式*/ getAllBlocks( /*のぶs方式 getChildrenAllInBlock( */
    page_id
  )) as ExpandedBlockObjectResponse[];

  const childrenWithOgp = await setOgp(children);

Normal

//children
[
  {
    "object": "block",
    "id": "047cb96b-f476-40eb-bad5-1711771a4d42",
    "parent": {
      "type": "page_id",
      "page_id": "ea24329b-4657-4beb-a4ed-ef2367f6f4d9"
    },
    "created_time": "2024-03-20T01:38:00.000Z",
    "last_edited_time": "2024-03-20T01:38:00.000Z",
    "created_by": {
      "object": "user",
      "id": "a77106c5-a562-456a-9b47-255677e64984"
    },
    "last_edited_by": {
      "object": "user",
      "id": "a77106c5-a562-456a-9b47-255677e64984"
    },
    "has_children": false,
    "archived": false,
    "type": "bookmark",
    "bookmark": {
      "caption": [],
      "url": "https://blog.ec-maker.com/"
    }
  }
]
//childrenWithOgp
[
  {
    "object": "block",
    "id": "047cb96b-f476-40eb-bad5-1711771a4d42",
    "parent": {
      "type": "page_id",
      "page_id": "ea24329b-4657-4beb-a4ed-ef2367f6f4d9"
    },
    "created_time": "2024-03-20T01:38:00.000Z",
    "last_edited_time": "2024-03-20T01:38:00.000Z",
    "created_by": {
      "object": "user",
      "id": "a77106c5-a562-456a-9b47-255677e64984"
    },
    "last_edited_by": {
      "object": "user",
      "id": "a77106c5-a562-456a-9b47-255677e64984"
    },
    "has_children": false,
    "archived": false,
    "type": "bookmark",
    "bookmark": {
      "caption": [],
      "url": "https://blog.ec-maker.com/"
    },
    "ogp": {
      "url": "https://blog.ec-maker.com/",
      "title": "EC maker",
      "description": "Notion API と Next.js / Tailwind CSS による本格ブログ",
      "imageUrl": "https://blog.ec-maker.com/ECmaker.png",
      "faviconUrl": "https://www.google.com/s2/favicons?domain=https://blog.ec-maker.com/"
    }
  }
]

"ogp"のリストが追加されているよう。

箇条書き

//childrenWithOgp
  {
    "id": "0.6336239133603938",
    "type": "bulleted_list",
    "bulleted_list": {
      "children": [
        {
          "object": "block",
          "id": "6765f5a1-7eb2-4650-9a07-61ef09241149",
          "parent": {
            "type": "page_id",
            "page_id": "ea24329b-4657-4beb-a4ed-ef2367f6f4d9"
          },
          "created_time": "2024-03-20T00:48:00.000Z",
          "last_edited_time": "2024-03-20T01:39:00.000Z",
          "created_by": {
            "object": "user",
            "id": "a77106c5-a562-456a-9b47-255677e64984"
          },
          "last_edited_by": {
            "object": "user",
            "id": "a77106c5-a562-456a-9b47-255677e64984"
          },
          "has_children": true,
          "archived": false,
          "type": "bulleted_list_item",
          "bulleted_list_item": {
            "rich_text": [
              {
                "type": "text",
                "text": {
                  "content": "箇条書き",
                  "link": null
                },
                "annotations": {
                  "bold": false,
                  "italic": false,
                  "strikethrough": false,
                  "underline": false,
                  "code": false,
                  "color": "default"
                },
                "plain_text": "箇条書き",
                "href": null
              }
            ],
            "color": "default"
          },
          "children": [
            {
              "object": "block",
              "id": "d2796adb-dddf-480a-9592-5958437980bb",
              "parent": {
                "type": "block_id",
                "block_id": "6765f5a1-7eb2-4650-9a07-61ef09241149"
              },
              "created_time": "2024-03-20T01:39:00.000Z",
              "last_edited_time": "2024-03-20T01:39:00.000Z",
              "created_by": {
                "object": "user",
                "id": "a77106c5-a562-456a-9b47-255677e64984"
              },
              "last_edited_by": {
                "object": "user",
                "id": "a77106c5-a562-456a-9b47-255677e64984"
              },
              "has_children": false,
              "archived": false,
              "type": "bookmark",
              "bookmark": {
                "caption": [],
                "url": "https://blog.ec-maker.com/"
              }
            }
          ]
        }
      ]
    }
  }

"ogp"のリストが追加できていない。

u-ecmaker commented 5 months ago

非同期の概念

プロパティ 'ogp' は型 'Promise<BookmarkBlockObjectResponse & { ogp: Ogp; }>' に存在しません。

GitHub Copilot @workspace /explain 使用済み このエラーは、setOgp関数が非同期関数であり、その結果はPromiseとして返されるために発生しています。しかし、その後のコードでは、Promiseではなく、直接オブジェクトにアクセスしようとしています。

この問題を解決するためには、setOgp関数の結果を適切に待つ必要があります。Reactでは、非同期操作は通常、useEffectフック内で行います。そして、その結果をコンポーネントの状態として保存します。以下にその例を示します。

import { useEffect, useState } from 'react';

export const Bookmark: FC<Props> = ({ block }: Props) => {
  const [blockWithOgp, setBlockWithOgp] = useState<BookmarkBlockObjectResponse & { ogp: Ogp } | null>(null);

  useEffect(() => {
    const fetchOgp = async () => {
      const result = await setOgp(block);
      setBlockWithOgp(result);
    };

    fetchOgp();
  }, [block]);

  if (!blockWithOgp) {
    // ローディング状態を表示するなど
    return null;
  }

  const ogp = blockWithOgp.ogp
    ? blockWithOgp.ogp
    : {
        url: blockWithOgp.bookmark.url,
        title: '',
        description: '',
        imageUrl: '',
        faviconUrl: '',
      };
  const noOgp = !ogp.title && !ogp.description && !ogp.imageUrl;

  // 以降のコードは blockWithOgp を使用します
  // ...
};

このコードでは、useStateとuseEffectを使用して、setOgp関数からの非同期結果を取得し、それをコンポーネントの状態として保存しています。そして、その状態が存在する場合にのみ、以降のコードを実行します。


u-ecmaker
Promiseとして返されるとはどういうことでしょうか、同期、非同期に関係していますか

はい、PromiseとはJavaScriptで非同期処理を扱うためのオブジェクトです。非同期処理とは、結果がすぐに得られない処理のことを指します。例えば、ネットワークリクエストやファイルの読み書きなどは非同期処理に該当します。

Promiseは、非同期処理が完了したときにその結果を提供する「約束」を表現します。Promiseは以下の3つの状態を持ちます:

pending(未解決): 非同期処理がまだ完了していない状態 fulfilled(解決済み): 非同期処理が成功して結果が得られた状態 rejected(拒否済み): 非同期処理が失敗した状態 Promiseはthenメソッドを提供しており、これを使って非同期処理が完了したときの処理を記述することができます。また、async/await構文を使って非同期処理を同期的に書くこともできます。

例えば、ある非同期関数asyncFunctionがあるとき、その結果を待つには以下のようにします:

asyncFunction().then(result => {
  console.log(result); // 非同期処理の結果
});

// または

(async () => {
  const result = await asyncFunction();
  console.log(result); // 非同期処理の結果
})();

このように、Promiseは非同期処理の結果を扱うための重要な概念です。


GitHub Copilot
@workspace /explain 使用済み
このエラーは、blockWithOgpがnullの可能性があるために発生しています。
useStateで初期化したときにnullを設定しているため、blockWithOgpはnullになる可能性があります。

この問題を解決するためには、blockWithOgpがnullでないことを確認する必要があります。以下にその例を示します。

// ...

if (!blockWithOgp) {
  // ローディング状態を表示するなど
  return null;
}

const ogp = blockWithOgp.ogp
  ? blockWithOgp.ogp
  : {
      url: blockWithOgp.bookmark.url,
      title: '',
      description: '',
      imageUrl: '',
      faviconUrl: '',
    };
// ...

このコードでは、blockWithOgpがnullの場合には何も表示せずに早期に関数から抜け出しています。これにより、blockWithOgpがnullの場合に発生するエラーを防ぐことができます。

u-ecmaker すぐに抜出内で取得するまである程度待つようにしたい

GitHub Copilot その場合、非同期処理が完了するまでローディング状態を表示することが一般的です。以下のように修正することで、blockWithOgpがnullの間はローディング状態を表示し、非同期処理が完了したらその結果を表示するようにできます。

このコードでは、blockWithOgpがnullの間はLoading...と表示し、blockWithOgpが非nullになったらその結果を表示します。Loading...の部分は、実際のローディング状態を表示するコンポーネントに置き換えることができます。

なお、このコードはblockWithOgpがnullでないことを確認してからそのプロパティにアクセスするため、blockWithOgpがnullの可能性があるというエラーは発生しません。

// ...

if (!blockWithOgp) {
  // ローディング状態を表示する
  return <div>Loading...</div>;
}

const ogp = blockWithOgp.ogp
  ? blockWithOgp.ogp
  : {
      url: blockWithOgp.bookmark.url,
      title: '',
      description: '',
      imageUrl: '',
      faviconUrl: '',
    };
// ...

断念

8bc013fa
bookmark内で呼び出そうと思ったが、うまくいかないので辞める。
おとなしく[slug]内で呼び出して、listのとき~と追記していく。

u-ecmaker commented 5 months ago

objectの中も再帰的に探索してbookmarkを見つけるアプローチ

copilotさんに書かせたらなんか出来てしまった。。

image

解説

u-ecmaker @workspace /explain このchildrenのデータからtype:bookmarkのところでOgpを差し込むsetOgp関数があります。しかし、入れ子になっている箇所で見逃していまい、正しい条件文になっていません。入れ子になっているbookmarkにもsetOgpをできるように条件文を見直してください。

GitHub Copilot @workspace /explain 使用済み あなたが提供したデータ構造を見ると、type: "bookmark"のプロパティは、データのルートレベルとbulleted_list_itemのchildrenプロパティの両方に存在します。したがって、setOgp関数を適用するためには、これらの両方のレベルでチェックする必要があります。

以下に、再帰的にデータを探索し、type: "bookmark"のプロパティが見つかった場合にsetOgp関数を適用するJavaScriptの関数を示します。

この関数は、配列やオブジェクトの中に深くネストされたtype: "bookmark"のプロパティを見つけ出し、それに対してsetOgp関数を適用します。

export const setOgp = async (
  children: ExpandedBlockObjectResponse[]
): Promise<ExpandedBlockObjectResponse[]> => {
  const applySetOgpToBookmarks = async (
    data: ExpandedBlockObjectResponse | ExpandedBlockObjectResponse[]
  ): Promise<ExpandedBlockObjectResponse | ExpandedBlockObjectResponse[]> => {
    if (Array.isArray(data)) {
      return await Promise.all(data.map(applySetOgpToBookmarks)) as ExpandedBlockObjectResponse[];
    } else if (typeof data === 'object' && data !== null) {
      for (const key in data) {
        if (key === 'type' && data[key] === 'bookmark') {
          const url = (data as ExpandedBlockObjectResponse & { bookmark: { url: string } }).bookmark.url;
          const ogp = await getOgp(url);

          return {
            ...data,
            ogp,
          } as BookmarkBlockObjectResponse & { ogp: Ogp };
        } else {
          (data as any)[key] = await applySetOgpToBookmarks((data as any)[key]);
        }
      }
    }

    return data;
  };

  return await Promise.all(children.map(applySetOgpToBookmarks));
};

このコードでは、data[key]にアクセスする前にdataをany型にキャストしています。これにより、TypeScriptはdata[key]の型チェックをスキップします。ただし、この方法は型安全性を損なう可能性があるため、注意が必要です。