From 257edd4641e93de93be9b1e78dba7f84b5511f86 Mon Sep 17 00:00:00 2001 From: arsani Date: Sat, 21 Feb 2026 16:14:53 -0500 Subject: [PATCH] fix: Search and all podcasts are queried for --- src/components/Header.astro | 124 +++++++++++++++++--------- src/components/Pagination.astro | 115 ++++++++++-------------- src/components/PodsGrid.astro | 56 ++++++------ src/layouts/Catalog.astro | 4 +- src/pages/fr/podcasts/[...page].astro | 19 ++++ src/pages/fr/podcasts/index.astro | 9 -- src/pages/podcasts/[...page].astro | 19 ++++ src/pages/podcasts/index.astro | 9 -- src/utils/podcasts.ts | 57 +++++------- src/utils/youtubeApi.ts | 60 +++++++++---- 10 files changed, 263 insertions(+), 209 deletions(-) create mode 100644 src/pages/fr/podcasts/[...page].astro delete mode 100644 src/pages/fr/podcasts/index.astro create mode 100644 src/pages/podcasts/[...page].astro delete mode 100644 src/pages/podcasts/index.astro diff --git a/src/components/Header.astro b/src/components/Header.astro index c7dff57e..a9930b00 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -47,65 +47,101 @@ import SearchIcon from "./SearchIcon.astro"; diff --git a/src/components/Pagination.astro b/src/components/Pagination.astro index ef63f410..92619c73 100644 --- a/src/components/Pagination.astro +++ b/src/components/Pagination.astro @@ -1,13 +1,17 @@ --- +interface Props { + currentPage: number; + totalPages: number; + baseUrl: string; +} + const { currentPage, totalPages, baseUrl } = Astro.props; const pages = Array.from({ length: totalPages }, (_, i) => i + 1); const maxVisiblePages = 5; let visiblePages = pages; -// Clean the baseUrl by removing .html and any existing page numbers const cleanBaseUrl = baseUrl.replace(/\.html$/, "").replace(/\/\d+$/, ""); -// Function to generate the correct URL for a page const getPageUrl = (page: number) => { if (page === 1) return cleanBaseUrl; return `${cleanBaseUrl}/${page}`; @@ -18,71 +22,46 @@ if (totalPages > maxVisiblePages) { const end = Math.min(totalPages, start + maxVisiblePages - 1); visiblePages = pages.slice(start - 1, end); } + +const btnClass = "px-2 py-1 sm:px-4 sm:py-2 rounded-md transition-colors text-tiny md:text-base"; +const defaultBtn = `${btnClass} bg-pearl text-black hover:bg-madder hover:text-white`; +const activeBtn = `${btnClass} bg-madder text-white pointer-events-none`; --- -
- { - currentPage > 1 && ( - - ← - - ) - } - { - visiblePages[0] > 1 && ( - <> - - 1 - - {visiblePages[0] > 2 && ...} - - ) - } - { - visiblePages.map((page) => ( - - {page} - - )) - } - { - visiblePages[visiblePages.length - 1] < totalPages && ( - <> - {visiblePages[visiblePages.length - 1] < totalPages - 1 && ( - ... - )} - - {totalPages} - - - ) - } - { - currentPage < totalPages && ( - - → - - ) - } -
+{ + totalPages > 1 && ( +
+ + {visiblePages[0] > 1 && ( + <> + + 1 + + {visiblePages[0] > 2 && ...} + + )} + {visiblePages.map((page) => + page === currentPage ? ( + + {page} + + ) : ( + + {page} + + ), + )} + {visiblePages[visiblePages.length - 1] < totalPages && ( + <> + {visiblePages[visiblePages.length - 1] < totalPages - 1 && ( + ... + )} + + {totalPages} + + + )} + +
+ ) +} diff --git a/src/components/PodsGrid.astro b/src/components/PodsGrid.astro index 9946832c..778a11d7 100644 --- a/src/components/PodsGrid.astro +++ b/src/components/PodsGrid.astro @@ -1,30 +1,36 @@ --- -import { getPodcasts } from "@utils/podcasts"; +import type { PodcastItem } from "@utils/podcasts"; -const fullPodPlaylistId = import.meta.env.COA_PODCASTS_FULL_PLAYLIST_ID; -const podcasts = await getPodcasts(fullPodPlaylistId); +interface Props { + podcasts: PodcastItem[]; +} + +const { podcasts } = Astro.props; --- -
-
-
- { - podcasts?.map((podcast) => ( - - -
+
+ { + podcasts?.map((podcast) => ( + +
+ {podcast.title} +
+
+ + {podcast.title} + + + {podcast.publishedAt} + +
+
+ )) + }
diff --git a/src/layouts/Catalog.astro b/src/layouts/Catalog.astro index 87350065..ffbe3496 100644 --- a/src/layouts/Catalog.astro +++ b/src/layouts/Catalog.astro @@ -7,13 +7,13 @@ import Pagination from "@components/Pagination.astro"; const { posts, page, title } = Astro.props; const generateTags = title === "Video Library | Coptic Orthodox Answers"; const currentPath = new URL(Astro.request.url).pathname; -// Remove any page numbers from the path const baseUrl = currentPath.replace(/\/\d+$/, ""); +const hasCustomGrid = Astro.slots.has("default"); ---
- {generateTags ? : } + {hasCustomGrid ? : generateTags ? : }
diff --git a/src/pages/fr/podcasts/[...page].astro b/src/pages/fr/podcasts/[...page].astro new file mode 100644 index 00000000..9ab1865d --- /dev/null +++ b/src/pages/fr/podcasts/[...page].astro @@ -0,0 +1,19 @@ +--- +import Catalog from "@layouts/Catalog.astro"; +import PodsGrid from "@components/PodsGrid.astro"; +import { getPodcasts } from "@utils/podcasts"; +import { type GetStaticPathsOptions } from "astro"; +import * as m from "@paraglide/messages.js"; + +export async function getStaticPaths({ paginate }: GetStaticPathsOptions) { + const playlistId = import.meta.env.COA_PODCASTS_FULL_PLAYLIST_ID; + const podcasts = await getPodcasts(playlistId); + return paginate(podcasts, { pageSize: 20 }); +} + +const { page } = Astro.props; +--- + + + + diff --git a/src/pages/fr/podcasts/index.astro b/src/pages/fr/podcasts/index.astro deleted file mode 100644 index 210b92b4..00000000 --- a/src/pages/fr/podcasts/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import Main from "@layouts/Main.astro"; -import PodsGrid from "@components/PodsGrid.astro"; -import * as m from "@paraglide/messages.js"; ---- - -
- -
diff --git a/src/pages/podcasts/[...page].astro b/src/pages/podcasts/[...page].astro new file mode 100644 index 00000000..9ab1865d --- /dev/null +++ b/src/pages/podcasts/[...page].astro @@ -0,0 +1,19 @@ +--- +import Catalog from "@layouts/Catalog.astro"; +import PodsGrid from "@components/PodsGrid.astro"; +import { getPodcasts } from "@utils/podcasts"; +import { type GetStaticPathsOptions } from "astro"; +import * as m from "@paraglide/messages.js"; + +export async function getStaticPaths({ paginate }: GetStaticPathsOptions) { + const playlistId = import.meta.env.COA_PODCASTS_FULL_PLAYLIST_ID; + const podcasts = await getPodcasts(playlistId); + return paginate(podcasts, { pageSize: 20 }); +} + +const { page } = Astro.props; +--- + + + + diff --git a/src/pages/podcasts/index.astro b/src/pages/podcasts/index.astro deleted file mode 100644 index 210b92b4..00000000 --- a/src/pages/podcasts/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import Main from "@layouts/Main.astro"; -import PodsGrid from "@components/PodsGrid.astro"; -import * as m from "@paraglide/messages.js"; ---- - -
- -
diff --git a/src/utils/podcasts.ts b/src/utils/podcasts.ts index d3c4fa65..286afb7f 100644 --- a/src/utils/podcasts.ts +++ b/src/utils/podcasts.ts @@ -1,9 +1,10 @@ -import { getPlaylistItems } from "@utils/youtubeApi"; +import { getAllPlaylistItems } from "@utils/youtubeApi"; export type PodcastItem = { id: string; slug: string; title: string; + description: string; thumbnail: string; publishedAt: string; }; @@ -16,39 +17,27 @@ export function slugify(text: string): string { .replace(/^-+|-+$/g, ""); } -export async function getPodcasts( - playlistId: string, - count = 20, -): Promise { - let data = await getPlaylistItems(count, playlistId); +export async function getPodcasts(playlistId: string): Promise { + const items = await getAllPlaylistItems(playlistId); - let privateVidCount = 0; - data?.items?.forEach((item) => { - if (item.snippet.title === "Private video") privateVidCount++; - }); - if (privateVidCount > 0) { - data = await getPlaylistItems(count + privateVidCount, playlistId); - } - - return ( - data?.items - ?.map( - (item): PodcastItem => ({ - id: item.contentDetails.videoId, - slug: slugify(item.snippet.title), - title: item.snippet.title, - thumbnail: - item.snippet.thumbnails.maxres?.url || - item.snippet.thumbnails.high?.url, - publishedAt: new Date( - item.contentDetails.videoPublishedAt, - ).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - }), + return items + .filter((item) => item.snippet.title !== "Private video") + .map( + (item): PodcastItem => ({ + id: item.contentDetails.videoId, + slug: slugify(item.snippet.title), + title: item.snippet.title, + description: item.snippet.description, + thumbnail: + item.snippet.thumbnails.maxres?.url || + item.snippet.thumbnails.high?.url, + publishedAt: new Date( + item.contentDetails.videoPublishedAt, + ).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", }), - ) - .filter((podcast) => podcast.title !== "Private video") ?? [] - ); + }), + ); } diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts index 0399539f..34f06340 100644 --- a/src/utils/youtubeApi.ts +++ b/src/utils/youtubeApi.ts @@ -2,26 +2,26 @@ import { google } from "googleapis"; const youtubeApiKey = import.meta.env.YOUTUBE_API_KEY; const youtube = google.youtube({ version: "v3", auth: youtubeApiKey }); +export type PlaylistItemEntry = { + id: string; + snippet: { + title: string; + description: string; + thumbnails: { + high: { url: string }; + maxres: { url: string }; + }; + }; + contentDetails: { + videoId: string; + videoPublishedAt: string; + }; +}; + type PlaylistItem = { - items?: [ - { - id: string; - snippet: { - title: string; - thumbnails: { - high: { url: string }; - maxres: { url: string }; - }; - }; - contentDetails: { - videoId: string; - videoPublishedAt: string; - }; - }, - ]; + items?: PlaylistItemEntry[]; }; -// missing return type interface export const getPlaylistItems = async ( maxResults: number, playlistId: string, @@ -35,6 +35,30 @@ export const getPlaylistItems = async ( if (res.status === 200) { data = res.data; } - // error handle other statuses return data; }; + +export const getAllPlaylistItems = async ( + playlistId: string, +): Promise => { + const allItems: PlaylistItemEntry[] = []; + let pageToken: string | undefined; + + do { + const res = await youtube.playlistItems.list({ + maxResults: 50, + part: ["snippet", "contentDetails"], + playlistId: playlistId, + pageToken: pageToken, + }); + + if (res.status === 200 && res.data.items) { + allItems.push(...(res.data.items as PlaylistItemEntry[])); + pageToken = res.data.nextPageToken ?? undefined; + } else { + break; + } + } while (pageToken); + + return allItems; +};