Filter posts by tagA

👤horomi

経緯

footerタグを付けた記事を記事一覧から除外し、

通常の記事と別の扱いをする改造を依頼された


もくじ

構造を整理

`getAllPosts`からデータを使用シーンによって

呼び出しやすいよう小分けになって関数が準備されている。

getAllPosts
export async function getAllPosts(): Promise<Post[]> {
  if (postsCache !== null) {
    return Promise.resolve(postsCache)
  }

  const params: requestParams.QueryDatabase = {
    database_id: 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
  }

  postsCache = results
    .filter((pageObject) => _validPageObject(pageObject))
    .map((pageObject) => _buildPost(pageObject))
  return postsCache
}
src / lib / notion / client.ts

`getAppPosts`をベースに呼び出している関数

  • getPosts
  • getRankedPosts
  • getPostBySlug
  • getPostByPageId
  • getPostsByTag
  • getPostsByPage
  • getPostsByTagAndPage
  • getNumberOfPages
  • getNumberOfPagesByTag
  • getAllSubTags

実際に使われている場所の特定は、

src / pages 内のimport{} を確認すれば分かるとして….

image block

今回依頼された「footerタグを付けた記事は除外する」のに該当する関数と該当しない関数を整理する必要がある。

footerの記事を除きたい関数

用途footerで使う?
getPosts記事一覧で10件取得🙅‍♀
getRankedPostsRankプロパティの値でsortさせて記事を10件取得🙅‍♀
getPostBySlugSlugプロパティで指定された記事を1件取得
getPostByPageIdメンションブロックで記事を指定する時に使う
getPostsByTag任意のタグを持つ記事を10件取得🙅‍♀
getPostsByPage記事一覧の2ページ目以降の記事🙅‍♀
getPostsByTagAndPageタグ一覧ページの2ページ目以降の記事🙅‍♀
getNumberOfPages記事一覧ページのページ数🙅‍♀
getNumberOfPagesByTagタグ一覧ページのページ数🙅‍♀
getAllTagsTagプロパティに登録されてるタグ🙅‍♀

footerで使わない関数をどうやって除外するか….がこの記事の本題w

footerタグの記事は除く調整

💁‍♀️
以下、ChatGPTによる解説です

❓ Prompt

astro.jsで開発しているブログで、特定のTag名以外を表示させる修正をしています。


修正して表示してみるとfooterの記事を含んでしまっているようです。
```

export async function getPosts(pageSize = 10): Promise<Post[]> {
const allPosts = await getAllPosts()

return allPosts
.filter((post) => !!post.Tags.map((tag) => tag.name !== 'footer'))
.slice(0, pageSize)
}
```

💬 Answer

提供されたコードの問題は、filterメソッド内でmapメソッドを使用している点にあります。mapメソッドは元の配列の要素を変換するのに使用され、その結果は新しい配列として返されます。この場合、各postTagsプロパティが新しいブール値の配列に変換されます。しかし、それらのブール値が直接フィルタリングに使用されていません。なので、結果として全てのpostがフィルタリングを通過してしまいます。

この問題を解決するには、filterメソッドを使う代わりにsomeまたはeveryメソッドを使用することをお勧めします。これらのメソッドはブール値を返すため、フィルタリングに適しています。

  • someメソッドは配列のいずれかの要素が条件を満たす場合にtrueを返します。
  • everyメソッドは配列のすべての要素が条件を満たす場合にtrueを返します。

あなたの要件(特定のタグ(ここでは'footer')を持たない投稿だけを取得したい)に合わせて、someメソッドを使用して修正を試みてみてください。以下に修正案を示します:

export async function getPosts(pageSize = 10): Promise<Post[]> {
  const allPosts = await getAllPosts()

  return allPosts
    .filter((post) => !post.Tags.some((tag) => tag.name === 'footer'))
    .slice(0, pageSize)
}

このコードは、タグの名前が'footer'であるものが一つでも存在する場合、someメソッドがtrueを返すようになります。そのtrue値を否定(!)することで、'footer'タグを持つ投稿がフィルタリングから除外されます。

検証

試しに`getPosts`を使って

記事一覧にfooterの記事が表示されるか確認してみると….

NotionDBにTagsプロパティにfooter を追加

image block

image block

footerの記事はカットされた\(^o^)/

無事Supportできてる様子なので、

他の関数にも取り入れてこう〜〜〜

実装過程

🏋️
ChatGPTに教えてもらったコードを少しずつ自分で修正できるように頑張りました
以下はチャットの様子ですw

❓ Prompt_getRankedPostsの修正提案

他の関数も取り入れたいです。
でも、filterが入ってるためどこを修正すべきか判断しかねています。

export async function getRankedPosts(pageSize = 10): Promise<Post[]> {
  const allPosts = await getAllPosts()

  return allPosts
    .filter((post) => !!post.Rank)
    .sort((a, b) => {
      if (a.Rank > b.Rank) {
        return -1
      } else if (a.Rank === b.Rank) {
        return 0
      }
      return 1
    })
    .slice(0, pageSize)
}

💬 Answer

こちらのgetRankedPosts関数では、Rankフィールドを持つ投稿のみをフィルタリングし、その後Rankフィールドの降順でソートしています。ここに「"footer" タグを除外する」機能を追加したいという理解で良いでしょうか?

その場合、以下のように修正できます:

export async function getRankedPosts(pageSize = 10): Promise<Post[]> {
  const allPosts = await getAllPosts()

  return allPosts
    .filter((post) => !!post.Rank && !post.Tags.some((tag) => tag.name === 'footer'))
    .sort((a, b) => {
      if (a.Rank > b.Rank) {
        return -1
      } else if (a.Rank === b.Rank) {
        return 0
      }
      return 1
    })
    .slice(0, pageSize)
}

このコードでは、filterメソッド内で2つの条件をチェックしています:

  1. 投稿がRankフィールドを持っていること(!!post.Rank
  2. 投稿が'footer'タグを持っていないこと(!post.Tags.some((tag) => tag.name === 'footer')

これにより、「Rankを持つ」かつ「'footer'タグを持たない」投稿のみがフィルタリングを通過するようになります。

❓ Prompt_getPostBySlugのフィードバック

できました!!

今度は自分で修正してみました。
正しいかをアドバイスしてください。

修正前:

export async function getPostBySlug(slug: string): Promise<Post | null> {
  const allPosts = await getAllPosts()
  return allPosts.find((post) => post.Slug === slug) || null
}

修正後:

export async function getPostBySlug(slug: string): Promise<Post | null> {
  const allPosts = await getAllPosts()
  return (
    allPosts
      .filter((post) => !post.Tags.some((tag) => tag.name === 'footer'))
      .find((post) => post.Slug === slug) || null
  )
}

💬 Answer

提出された修正は正しいです!

あなたの修正されたコードでは、まずgetAllPosts関数を使用して全ての投稿を取得しています。その後、filterメソッドを使用して'footer'タグを持つ投稿を除外します。そして、findメソッドを使用して指定されたスラッグに一致する投稿を探します。この修正により、'footer'タグを持つ投稿が返されることはありません。

ただし、この変更により一つ注意点があります。それは、「Slugが一致する投稿が'footer'タグを持っている場合、その投稿は結果から除外される」ことです。もしそれが意図した挙動であれば、あなたの修正は完全に正しいです。

以下が修正されたコードです:

export async function getPostBySlug(slug: string): Promise<Post | null> {
  const allPosts = await getAllPosts()
  return (
    allPosts
      .filter((post) => !post.Tags.some((tag) => tag.name === 'footer'))
      .find((post) => post.Slug === slug) || null
  )
}

この修正があなたの求める動作を満たしている場合、それをそのまま使用しても問題ありません。

❓ Prompt

忘れてました!この関数は除外してはいけないものでしたね!
教えてくれてありがとう

💬 Answer

どういたしまして!修正が不要な関数についてはそのまま保持することは全く問題ありません。各関数が求められる動作を適切に満たすように、必要に応じてコードを修正することが重要です。

何か他にご質問や助けが必要な点がございましたら、いつでもお気軽にお申し付けください。プログラミングにおける問題解決をお手伝いすることが私の役目です。

❓ Prompt_ getPostsByTagのフィードバック

関数の内容を確認し、不要だとわかったので修正してみました。

正しいかどうかを教えてください。

修正前:

export async function getPostsByTag(
  tagName: string,
  pageSize = 10
): Promise<Post[]> {
  if (!tagName) return []

  const allPosts = await getAllPosts()
  return allPosts
    .filter((post) => post.Tags.find((tag) => tag.name === tagName))
    .slice(0, pageSize)
}

修正後:

export async function getPostsByTag(
  tagName: string,
  pageSize = 10
): Promise<Post[]> {
  if (!tagName) return []

  const allPosts = await getAllPosts()
  return allPosts
    .filter(
      (post) =>
        !post.Tags.some((tag) => tag.name === 'footer') &&
        post.Tags.find((tag) => tag.name === tagName)
    )
    .slice(0, pageSize)
}

💬 Answer

提出された修正は正しいです!ただし、findメソッドの使用について一点提案があります。

あなたの修正されたコードでは、まずgetAllPosts関数を使用して全ての投稿を取得しています。その後、filterメソッドを使用して以下の条件を満たす投稿を選択しています:

  1. 投稿が'footer'タグを持たないこと(!post.Tags.some((tag) => tag.name === 'footer')
  2. 投稿が指定されたタグを持つこと(post.Tags.find((tag) => tag.name === tagName)

ただし、2つ目の条件でfindメソッドを使用していますが、someメソッドの方がより適しているかもしれません。findメソッドは配列の中から条件に一致する最初の要素を返しますが、このコードではその要素自体は利用されていません。一方、someメソッドは配列の中に条件に一致する要素が存在するかどうかを判定し、存在する場合はtrueを返します。

したがって、以下のように修正することを提案します:

export async function getPostsByTag(
  tagName: string,
  pageSize = 10
): Promise<Post[]> {
  if (!tagName) return []

  const allPosts = await getAllPosts()
  return allPosts
    .filter(
      (post) =>
        !post.Tags.some((tag) => tag.name === 'footer') &&
        post.Tags.some((tag) => tag.name === tagName)
    )
    .slice(0, pageSize)
}

この修正があなたの求める動作を満たしている場合、それをそのまま使用しても問題ありません。

❓ Prompt

本家のコードをなるべく使っておくことで保守が簡単になるので、findの部分はそのままにしようと思います。アドバイスありがとう

💬 Answer

そうですね、ある程度既存のコードを維持することは、将来的な保守性やコードの一貫性を保つ上で重要です。findメソッドも確かにその要件を満たしているので、そのまま使用するのも良い選択です。

私の提案は単なるオプションであり、必ずしもそれに従う必要はありません。あなたのプロジェクトの要件とコードベースの文脈に最も合致する選択をすることが最も重要です。

他に何かご質問があればいつでもお知らせください。お手伝いすることができれば幸いです。

❓ Prompt_パフォーマンスを意識したfilterの位置

`!post.Tags.some((tag) => tag.name === 'footer') `を`post.Tags.some((tag) => tag.name === tagName)`の前に入れるのと、後に入れるのとではパフォーマンスは変わりますか?

💬 Answer

はい、パフォーマンスに影響を及ぼす可能性があります。

JavaScriptの論理演算子は「短絡評価(Short-circuit evaluation)」を行います。つまり、&&(AND)演算子については、最初の条件がfalseであれば、JavaScriptは二つ目の条件を評価せずに全体の結果をfalseとします。これは||(OR)演算子についても同様で、最初の条件がtrueであれば、二つ目の条件は評価されません。

したがって、!post.Tags.some((tag) => tag.name === 'footer') && post.Tags.some((tag) => tag.name === tagName)という表現では、最初の条件がfalse(つまり、postfooterタグを持っている)であれば、二つ目の条件は評価されません。

そのため、一般的には、計算コストが低い、またはfalseになりやすい条件を先に配置すると、全体の評価が早く終わる可能性が高まり、パフォーマンスが向上します。

ただし、このパフォーマンスの違いは、大抵の場合、非常に微小であり、特に投稿やタグの数が多くない場合、顕著な違いとはなりません。ですので、コードの可読性や保守性といった要素を優先することも重要です。

❓ Prompt_getNumberOfPagesのフィードバック

ありがとうございます。

可読性や保守を意識して修正してみました。
正しいかを教えてください。

修正前:

export async function getNumberOfPages(): Promise<number> {
  const allPosts = await getAllPosts()
  return (
    Math.floor(allPosts.length / NUMBER_OF_POSTS_PER_PAGE) +
    (allPosts.length % NUMBER_OF_POSTS_PER_PAGE > 0 ? 1 : 0)
  )
}

修正後:

export async function getNumberOfPages(): Promise<number> {
  const allPosts = await getAllPosts()
  const outOfFooterPosts = allPosts.filter(
    (post) => !post.Tags.some((tag) => tag.name === 'footer')
  )
  return (
    Math.floor(outOfFooterPosts.length / NUMBER_OF_POSTS_PER_PAGE) +
    (outOfFooterPosts.length % NUMBER_OF_POSTS_PER_PAGE > 0 ? 1 : 0)
  )
}

💬 Answer

あなたの修正は完全に正しいです!

修正後のコードでは、まずgetAllPosts関数を使って全ての投稿を取得し、その後filterメソッドを使って'footer'タグを持つ投稿を除外します。この修正後のリストの長さを用いてページ数を計算します。

したがって、この修正により'footer'タグを持つ投稿がページ数の計算から除外されます。

以下が修正後のコードです:

export async function getNumberOfPages(): Promise<number> {
  const allPosts = await getAllPosts()
  const outOfFooterPosts = allPosts.filter(
    (post) => !post.Tags.some((tag) => tag.name === 'footer')
  )
  return (
    Math.floor(outOfFooterPosts.length / NUMBER_OF_POSTS_PER_PAGE) +
    (outOfFooterPosts.length % NUMBER_OF_POSTS_PER_PAGE > 0 ? 1 : 0)
  )
}

このコードがあなたの求める動作を満たしている場合、それをそのまま使用しても問題ありません。

❓ Prompt_分かりやすい関数名

関数名をoutOfFooterPostsにしていますが、今後footer以外の用途で除外したいTagが発生することも加味して、もっと分かりやすい関数名にしたいです。

💬 Answer

すみません、私が誤解を生んでしまったようです。outOfFooterPostsはここでは一時変数の名前として使っていますね。そしてその変数名が将来的に他のタグをフィルターする際に役立つような名前にしたいという理解で合っていますか?

もしその理解が正しければ、変数名をfilteredPostsselectedPostsのように一般的な名前に変更すると、将来的に他のタグでフィルタリングを行う場合にもその名前が適用できます。

修正後のコードは以下のようになります:

export async function getNumberOfPages(): Promise<number> {
  const allPosts = await getAllPosts()
  const filteredPosts = allPosts.filter(
    (post) => !post.Tags.some((tag) => tag.name === 'footer')
  )
  return (
    Math.floor(filteredPosts.length / NUMBER_OF_POSTS_PER_PAGE) +
    (filteredPosts.length % NUMBER_OF_POSTS_PER_PAGE > 0 ? 1 : 0)
  )
}

ここでのfilteredPostsは、元の投稿リストから'footer'タグを持つ投稿を除外した結果を保持する一時的な変数です。この変数名は一般的な名前であり、他の任意のタグでフィルタリングを行う場合でもその名前が適用できます。

❓ Prompt

Tag名で除外するという意味を含めた関数名にしたいです。

コードの提示は不要です。関数名の候補を5つ教えてください

💬 Answer

変数名に除外するタグ名を指定する意味を含めることは、その変数が何を表しているのかを明示的にするのに役立ちます。以下にいくつかの候補を示します:

  1. excludedTagPosts
  2. postsWithoutSpecifiedTag
  3. filteredByTagPosts
  4. postsExcludingTag
  5. nonTaggedPosts

これらの名前はすべて、特定のタグを含まない投稿の集合を表すことを示しています。ただし、これらの名前は一般的な意味を持っており、どのタグが除外されているのかは明示していません。変数名に具体的なタグ名を含める場合は、コードの可読性と保守性のバランスを取る必要があります。

❓ Prompt

postsWithoutSpecifiedTagが分かりやすいと思いました

💬 Answer

素晴らしい選択です!postsWithoutSpecifiedTagという名前は、その変数が特定のタグを除外した投稿の集合を保持していることを明示的に示しています。これはコードを読む人にとって非常に理解しやすいでしょう。

以下のように修正できます:

export async function getNumberOfPages(): Promise<number> {
  const allPosts = await getAllPosts()
  const postsWithoutSpecifiedTag = allPosts.filter(
    (post) => !post.Tags.some((tag) => tag.name === 'footer')
  )
  return (
    Math.floor(postsWithoutSpecifiedTag.length / NUMBER_OF_POSTS_PER_PAGE) +
    (postsWithoutSpecifiedTag.length % NUMBER_OF_POSTS_PER_PAGE > 0 ? 1 : 0)
  )
}

この修正があなたの求める動作を満たしている場合、それをそのまま使用しても問題ありません。何か他に質問がありましたら、お気軽にお申し付けください。

❓ Prompt_getAllTagsのフィードバック

最後の修正となりました!!

flatMapを初めて目にしたので、この修正であっているか自信がないです。

修正前:

export async function getAllTags(): Promise<SelectProperty[]> {
  const allPosts = await getAllPosts()

  const tagNames: string[] = []
  return allPosts
    .flatMap((post) => post.Tags)
    .reduce((acc, tag) => {
      if (!tagNames.includes(tag.name)) {
        acc.push(tag)
        tagNames.push(tag.name)
      }
      return acc
    }, [] as SelectProperty[])
    .sort((a: SelectProperty, b: SelectProperty) =>
      a.name.localeCompare(b.name)
    )
}


修正後:

export async function getAllTags(): Promise<SelectProperty[]> {
  const allPosts = await getAllPosts()

  const tagNames: string[] = []
  return allPosts
    .filter(
      (post) => !!post.Rank && !post.Tags.some((tag) => tag.name === 'footer')
    )
    .flatMap((post) => post.Tags)
    .reduce((acc, tag) => {
      if (!tagNames.includes(tag.name)) {
        acc.push(tag)
        tagNames.push(tag.name)
      }
      return acc
    }, [] as SelectProperty[])
    .sort((a: SelectProperty, b: SelectProperty) =>
      a.name.localeCompare(b.name)
    )
}

💬 Answer

あなたが提供した修正は、全体としては正確ですが、一部注意すべき点があります。

あなたの修正コードでは、まずすべての投稿を取得し、それから特定の投稿をフィルタリングしています。そのフィルタリングの条件は、「投稿がランクを持っている(!!post.Rank)」と「投稿が'footer'タグを持っていない(!post.Tags.some((tag) => tag.name === 'footer'))」の両方を満たすものです。

ただし、ここで注意すべきなのは、!!post.Rankという部分です。これはその投稿がランクを持っているかどうかを判定しますが、元々の関数の目的は「すべてのタグを取得する」ことです。つまり、ランクを持っているかどうかはこの関数の目的とは関係がありません。したがって、これが意図したものでなければ、!!post.Rankという条件は削除するべきです。

修正後のコードは以下のようになります:

export async function getAllTags(): Promise<SelectProperty[]> {
  const allPosts = await getAllPosts()

  const tagNames: string[] = []
  return allPosts
    .filter((post) => !post.Tags.some((tag) => tag.name === 'footer'))
    .flatMap((post) => post.Tags)
    .reduce((acc, tag) => {
      if (!tagNames.includes(tag.name)) {
        acc.push(tag)
        tagNames.push(tag.name)
      }
      return acc
    }, [] as SelectProperty[])
    .sort((a: SelectProperty, b: SelectProperty) =>
      a.name.localeCompare(b.name)
    )
}

この修正により、'footer'タグを持つ投稿が全てのタグを取得する処理から除外されます。もしこれがあなたの求める動作であれば、この修正をそのまま使っても問題ありません。

❓ Prompt

すみません。参考にしたコードをそのままコピペしてしまい、余計な部分がついてましたね。以後気をつけます

💬 Answer

全く問題ありません!修正すべき部分を見つけ出すのは、コードを学ぶ上で非常に重要なスキルです。そして、それはあなたが成功裏に行ったことです。コードについて理解を深め、それを修正し、それを他人と共有することは、非常に重要なステップです。何か他の質問がありましたら、お気軽にお申し付けください。

ありがとう\(^o^)/\(^o^)/

反映されてない箇所1つ発見👀

image block

記事ページのコード

export async function getStaticPaths() {
  const posts = await getAllPosts()
  return posts.map((post: interfaces.Post) => ({ params: { slug: post.Slug } }))
}

const { slug } = Astro.params

const post = await getPostBySlug(slug)
if (!post) {
  throw new Error('Post not found. slug: ${slug}')
}

const [blocks, allPosts, rankedPosts, recentPosts, tags, postsHavingSameTag] =
  await Promise.all([
    getAllBlocksByBlockId(post.PageId),
    getAllPosts(),
    getRankedPosts(),
    getPosts(5),
    getAllTags(),
    getPostsByTag(post.Tags[0]?.name, 6),
  ])

const fileAtacchedBlocks = extractTargetBlocks('image', blocks)
  .concat(extractTargetBlocks('file', blocks))
  .filter((block) => {
    if (!block) {
      return false
    }
    const imageOrFile = block.Image || block.File
    return imageOrFile && imageOrFile.File && imageOrFile.File.Url
  })

// Download files
await Promise.all(
  fileAtacchedBlocks
    .map(async (block) => {
      const expiryTime = (block.Image || block.File).File.ExpiryTime
      if (Date.parse(expiryTime) > Date.now()) {
        return Promise.resolve(block)
      }
      return getBlock(block.Id)
    })
    .map((promise) =>
      promise.then((block) => {
        let url!: URL
        try {
          url = new URL((block.Image || block.File).File.Url)
        } catch (err) {
          console.log('Invalid file URL')
          return Promise.reject()
        }
        return Promise.resolve(url)
      })
    )
    .map((promise) => promise.then(downloadFile))
)

const currentPostIndex = allPosts.findIndex((post) => post.Slug === slug)
const prevPost = allPosts[currentPostIndex + 1]
const nextPost = allPosts[currentPostIndex - 1]

let ogImage = ''
if (post.FeaturedImage && post.FeaturedImage.Url) {
  ogImage = new URL(filePath(new URL(post.FeaturedImage.Url)), Astro.site)
}
src / pages / posts / [slug].astro

そもそもの原因が、直接getAllPostsからデータを取得していることによるもの👀

さて、どこにfilterをかけようか….

Promptが長いのでトグルに収納してお届けしますww

❓ Prompt

保守性を意識して定義する関数の部分でfilterを加えました。パフォーマンスやわかりやすさも加味して評価いただけると嬉しいです

修正前:

```export async function getStaticPaths() { const posts = await getAllPosts() return posts.map((post: interfaces.Post) => ({ params: { slug: post.Slug } }))}const { slug } = Astro.paramsconst post = await getPostBySlug(slug)if (!post) { throw new Error('Post not found. slug: ${slug}')}const [blocks, allPosts, rankedPosts, recentPosts, tags, postsHavingSameTag] = await Promise.all([ getAllBlocksByBlockId(post.PageId), getAllPosts(), getRankedPosts(), getPosts(5), getAllTags(), getPostsByTag(post.Tags[0]?.name, 6), ])const fileAtacchedBlocks = extractTargetBlocks('image', blocks) .concat(extractTargetBlocks('file', blocks)) .filter((block) => { if (!block) { return false } const imageOrFile = block.Image || block.File return imageOrFile && imageOrFile.File && imageOrFile.File.Url })// Download filesawait Promise.all( fileAtacchedBlocks .map(async (block) => { const expiryTime = (block.Image || block.File).File.ExpiryTime if (Date.parse(expiryTime) > Date.now()) { return Promise.resolve(block) } return getBlock(block.Id) }) .map((promise) => promise.then((block) => { let url!: URL try { url = new URL((block.Image || block.File).File.Url) } catch (err) { console.log('Invalid file URL') return Promise.reject() } return Promise.resolve(url) }) ) .map((promise) => promise.then(downloadFile)))const currentPostIndex = allPosts.findIndex((post) => post.Slug === slug)const prevPost = allPosts[currentPostIndex + 1]const nextPost = allPosts[currentPostIndex - 1]let ogImage = ''if (post.FeaturedImage && post.FeaturedImage.Url) { ogImage = new URL(filePath(new URL(post.FeaturedImage.Url)), Astro.site)}```

修正後:

```export async function getStaticPaths() { const posts = await getAllPosts() return posts.map((post: interfaces.Post) => ({ params: { slug: post.Slug } }))}const { slug } = Astro.paramsconst post = await getPostBySlug(slug)if (!post) { throw new Error('Post not found. slug: ${slug}')}const [blocks, allPosts, rankedPosts, recentPosts, tags, postsHavingSameTag] = await Promise.all([ getAllBlocksByBlockId(post.PageId), getAllPosts().filter( (post) => !post.Tags.some((tag) => tag.name === 'footer') ), getRankedPosts(), getPosts(5), getAllTags(), getPostsByTag(post.Tags[0]?.name, 6), ])const fileAtacchedBlocks = extractTargetBlocks('image', blocks) .concat(extractTargetBlocks('file', blocks)) .filter((block) => { if (!block) { return false } const imageOrFile = block.Image || block.File return imageOrFile && imageOrFile.File && imageOrFile.File.Url })// Download filesawait Promise.all( fileAtacchedBlocks .map(async (block) => { const expiryTime = (block.Image || block.File).File.ExpiryTime

if (Date.parse(expiryTime) > Date.now()) { return Promise.resolve(block) } return getBlock(block.Id) }) .map((promise) => promise.then((block) => { let url!: URL try { url = new URL((block.Image || block.File).File.Url) } catch (err) { console.log('Invalid file URL') return Promise.reject() } return Promise.resolve(url) }) ) .map((promise) => promise.then(downloadFile)))const currentPostIndex = allPosts.findIndex((post) => post.Slug === slug)const prevPost = allPosts[currentPostIndex + 1]const nextPost = allPosts[currentPostIndex - 1]let ogImage = ''if (post.FeaturedImage && post.FeaturedImage.Url) { ogImage = new URL(filePath(new URL(post.FeaturedImage.Url)), Astro.site)}```

💬 Answer

ご指摘いただいたコードを見たところ、修正点についてはおおむね正しく反映されているように思います。ただし、修正後のコードで注意すべき点が一つあります。

修正後のコードで、getAllPosts()関数がPromiseを返す非同期関数であることを考慮して、filter()を使用する前に非同期処理が終わるのを待つ必要があります。そのため、修正後のコードの該当箇所は以下のようになるべきです:

const allPostsFiltered = (await getAllPosts()).filter((post) => !post.Tags.some((tag) => tag.name === 'footer'));

あるいは以下のように非同期処理を待つ部分を明示的に分けて書くこともできます:

const allPosts = await getAllPosts();
const allPostsFiltered = allPosts.filter((post) => !post.Tags.some((tag) => tag.name === 'footer'));

また、その修正によりallPostsFilteredを参照する他の部分(例えばconst currentPostIndex = allPosts.findIndex((post) => post.Slug === slug), const prevPost = allPosts[currentPostIndex + 1], const nextPost = allPosts[currentPostIndex - 1]など)もallPostsではなくallPostsFilteredを使用するように修正する必要があります。

❓ Prompt

本家の関数名はなるべく残したいのでallPostsを使って修正したいです

💬 Answer

その場合、以下のようにallPostsを再定義して、それを使用するという方法があります:

let allPosts = await getAllPosts()
allPosts = allPosts.filter((post) => !post.Tags.some((tag) => tag.name === 'footer'))

このようにすれば、関数内のその後のallPostsの参照すべてがフィルタリングされた結果を指すようになります。ただし、変数allPostsが再代入可能である(letで定義されている)ことを確認してください。constで定義されている場合、再代入は不可能です。

❓ Prompt

このように修正してみました。

const [
  blocks,
  allPostsNoFinter,
  rankedPosts,
  recentPosts,
  tags,
  postsHavingSameTag,
] = await Promise.all([
  getAllBlocksByBlockId(post.PageId),
  getAllPosts(),
  getRankedPosts(),
  getPosts(5),
  getAllTags(),
  getPostsByTag(post.Tags[0]?.name, 6),
])

const allPosts = allPostsNoFinter.filter(
  (post) => !post.Tags.some((tag) => tag.name === 'footer')
)
💬 Answer

その修正は適切です。allPostsNoFilterという新しい変数名を導入することで、コードの意味がより明確になりました。これにより、他の開発者(あるいは将来のあなた自身)がコードを読むときに、その変数がフィルタリングされていない全てのポストを含んでいるということを理解しやすくなります。

また、allPostsの再定義も適切で、この変数が以前と同じ目的(すなわち、全てのポストを含むが今回は'footer'タグのポストを除いている)に用いられることが分かります。

この修正により、コードの可読性と保守性が向上したと言えます。


さいごに

安易に関数名を変えると、

本家からのアップデート作業で苦労するのが目に見えていたので

なるべく内側を変更することにしました🎉

PR

今回のコードはこちら!!