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;
---
-
-
+
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;
+};