Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 80 additions & 44 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -47,65 +47,101 @@ import SearchIcon from "./SearchIcon.astro";
</header>

<script is:inline>
const performSearch = async (searchTerm) => {
const resultsContainer = document.querySelector("#results");
const resultsContent = resultsContainer.querySelector(".space-y-4");
(function () {
const searchBox = document.querySelector("#search");
const resultsContainer = document.querySelector("#results");
const resultsContent = resultsContainer?.querySelector(".space-y-4");
if (!searchBox || !resultsContainer || !resultsContent) return;

let debounceTimer = null;
let searchCounter = 0;

// Show/hide results container based on input
if (searchTerm.length > 0) {
function closeResults() {
resultsContainer.classList.add("hidden");
document.body.classList.remove("overflow-hidden");
}

function openResults() {
resultsContainer.classList.remove("hidden");
document.body.classList.add("overflow-hidden");

// Position results below search box
const searchRect = searchBox.getBoundingClientRect();
const resultsDiv = resultsContainer.querySelector("div");
resultsDiv.style.top = `${searchRect.bottom + 8}px`;
} else {
resultsContainer.classList.add("hidden");
document.body.classList.remove("overflow-hidden");
return;
if (resultsDiv) {
resultsDiv.style.top = searchRect.bottom + 8 + "px";
}
}

// only load the pagefind script once
if (searchBox.dataset.loaded !== "true") {
searchBox.dataset.loaded = "true";
// load the pagefind script
window.pagefind = await import("/pagefind/pagefind.js");
}
async function performSearch(searchTerm) {
if (!searchTerm.trim()) {
closeResults();
return;
}

// search the index using the input value
const search = await window.pagefind.search(searchTerm);
openResults();

// clear the old results
resultsContent.innerHTML = "";
if (searchBox.dataset.loaded !== "true") {
searchBox.dataset.loaded = "true";
window.pagefind = await import("/pagefind/pagefind.js");
}

// add the new results
for (const result of search.results) {
const data = await result.data();
resultsContent.innerHTML += `
<a href="${data.url}" class="block p-4 hover:bg-gray-50 rounded-lg">
<h3 class="text-lg font-semibold text-gray-900">${data.meta.title}</h3>
<p class="text-gray-600 mt-1">${data.excerpt}</p>
</a>`;
}
};
const currentSearch = ++searchCounter;
const search = await window.pagefind.search(searchTerm);

if (currentSearch !== searchCounter) return;

document.querySelector("#search")?.addEventListener("input", (e) => {
performSearch(e.target.value);
});
let html = "";
for (const result of search.results) {
const data = await result.data();
if (currentSearch !== searchCounter) return;
html +=
'<a href="' + data.url + '" class="block p-4 hover:bg-gray-50 rounded-lg">' +
'<h3 class="text-lg font-semibold text-gray-900">' + escapeHtml(data.meta.title) + "</h3>" +
'<p class="text-gray-600 mt-1">' + (data.excerpt || "") + "</p>" +
"</a>";
}

document.querySelector("#search")?.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
performSearch(e.target.value);
if (currentSearch !== searchCounter) return;

if (html) {
resultsContent.innerHTML = html;
} else {
resultsContent.innerHTML =
'<p class="text-gray-500 text-center py-4">No results found.</p>';
}
}
});

// Close results when clicking outside
document.querySelector("#results")?.addEventListener("click", (e) => {
if (e.target === e.currentTarget) {
e.currentTarget.classList.add("hidden");
document.body.classList.remove("overflow-hidden");
function escapeHtml(str) {
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
});

searchBox.addEventListener("input", function (e) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
performSearch(e.target.value);
}, 250);
});

searchBox.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
clearTimeout(debounceTimer);
performSearch(e.target.value);
}
});

resultsContainer.addEventListener("click", function (e) {
if (e.target === e.currentTarget) {
closeResults();
}
});

document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !resultsContainer.classList.contains("hidden")) {
closeResults();
searchBox.blur();
}
});
})();
</script>
115 changes: 47 additions & 68 deletions src/components/Pagination.astro
Original file line number Diff line number Diff line change
@@ -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}`;
Expand All @@ -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`;
---

<div class="flex justify-center items-center gap-1 sm:gap-2 py-19">
{
currentPage > 1 && (
<a
href={getPageUrl(currentPage - 1)}
class="px-2 py-1 sm:px-4 sm:py-2 rounded-md bg-pearl text-black hover:bg-madder hover:text-white transition-colors text-tiny md:text-base"
aria-label="Previous page"
>
</a>
)
}
{
visiblePages[0] > 1 && (
<>
<a
href={getPageUrl(1)}
class="px-2 py-1 sm:px-4 sm:py-2 rounded-md bg-pearl text-black hover:bg-madder hover:text-white transition-colors text-tiny md:text-base"
>
1
</a>
{visiblePages[0] > 2 && <span class="text-pearl text-tiny md:text-base">...</span>}
</>
)
}
{
visiblePages.map((page) => (
<a
href={getPageUrl(page)}
class={`px-2 py-1 sm:px-4 sm:py-2 rounded-md transition-colors text-tiny md:text-base ${
page === currentPage
? "bg-madder text-white"
: "bg-pearl text-black hover:bg-madder hover:text-white"
}`}
>
{page}
</a>
))
}
{
visiblePages[visiblePages.length - 1] < totalPages && (
<>
{visiblePages[visiblePages.length - 1] < totalPages - 1 && (
<span class="text-pearl text-tiny md:text-base">...</span>
)}
<a
href={getPageUrl(totalPages)}
class="px-2 py-1 sm:px-4 sm:py-2 rounded-md bg-pearl text-black hover:bg-madder hover:text-white transition-colors text-tiny md:text-base"
>
{totalPages}
</a>
</>
)
}
{
currentPage < totalPages && (
<a
href={getPageUrl(currentPage + 1)}
class="px-2 py-1 sm:px-4 sm:py-2 rounded-md bg-pearl text-black hover:bg-madder hover:text-white transition-colors text-tiny md:text-base"
aria-label="Next page"
>
</a>
)
}
</div>
{
totalPages > 1 && (
<div class="flex justify-center items-center gap-1 sm:gap-2 py-19">

{visiblePages[0] > 1 && (
<>
<a href={getPageUrl(1)} class={defaultBtn}>
1
</a>
{visiblePages[0] > 2 && <span class="text-pearl text-tiny md:text-base">...</span>}
</>
)}
{visiblePages.map((page) =>
page === currentPage ? (
<span class={activeBtn} aria-current="page">
{page}
</span>
) : (
<a href={getPageUrl(page)} class={defaultBtn}>
{page}
</a>
),
)}
{visiblePages[visiblePages.length - 1] < totalPages && (
<>
{visiblePages[visiblePages.length - 1] < totalPages - 1 && (
<span class="text-pearl text-tiny md:text-base">...</span>
)}
<a href={getPageUrl(totalPages)} class={defaultBtn}>
{totalPages}
</a>
</>
)}

</div>
)
}
56 changes: 31 additions & 25 deletions src/components/PodsGrid.astro
Original file line number Diff line number Diff line change
@@ -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;
---

<div class="bg-gray p-8 lg:p-12 xl:p-19">
<div class="mx-auto">
<div class="grid grid-cols-1 w-full md:grid-cols-2 lg:grid-cols-4 gap-14 sm:gap-8 lg:gap-8">
{
podcasts?.map((podcast) => (
<a href={`/podcasts/${podcast.slug}`} class="group cursor-pointer">
<div class="relative aspect-video overflow-hidden rounded-lg">
<img
src={podcast.thumbnail}
alt={podcast.title}
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
loading="lazy"
/>
<div class="absolute inset-0 transition-colors duration-300" />
</div>
<h3 class="mt-2 text-pearl text-sm font-barlow line-clamp-2">{podcast.title}</h3>
<p class="text-jonquil text-xs mt-1">{podcast.publishedAt}</p>
</a>
))
}
</div>
</div>
<div
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-14 sm:gap-8 lg:gap-8 px-8 pt-8 lg:px-12 lg:pt-12 xl:px-19 xl:pt-19"
>
{
podcasts?.map((podcast) => (
<a class="group block" href={`/podcasts/${podcast.slug}`}>
<div class="aspect-video overflow-hidden rounded-lg">
<img
src={podcast.thumbnail}
alt={podcast.title}
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
loading="lazy"
/>
</div>
<div class="flex flex-col pt-4">
<span class="group-hover:underline group-hover:underline-offset-4 font-barlow text-lg sm:text-xl text-pearl line-clamp-2">
{podcast.title}
</span>
<span class="font-barlow text-xs text-jonquil mt-2">
{podcast.publishedAt}
</span>
</div>
</a>
))
}
</div>
4 changes: 2 additions & 2 deletions src/layouts/Catalog.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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");
---

<Main title={title}>
<div class="bg-gray">
{generateTags ? <PostsGridWithTags posts={posts} /> : <PostsGrid posts={posts} />}
{hasCustomGrid ? <slot /> : generateTags ? <PostsGridWithTags posts={posts} /> : <PostsGrid posts={posts} />}
<Pagination currentPage={page.currentPage} totalPages={page.lastPage} baseUrl={baseUrl} />
</div>
</Main>
19 changes: 19 additions & 0 deletions src/pages/fr/podcasts/[...page].astro
Original file line number Diff line number Diff line change
@@ -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;
---

<Catalog page={page} title={`${m.podcastsLabel()} | ${m.siteName()}`}>
<PodsGrid podcasts={page.data} />
</Catalog>
9 changes: 0 additions & 9 deletions src/pages/fr/podcasts/index.astro

This file was deleted.

Loading