33 * `/api/feed-cards` | Astro HTML Partial Endpoint (`export const partial = true`)
44 *
55 * Streams pre-rendered `<ContentCard>` fragments for infinite scrolling and
6- * in-page topic filtering across `/podcasts`, `/blog`, `/authors`, `/guests`,
7- * and `/sponsors`. Zero client hydration bundle or duplicated card templates.
6+ * in-page topic/keyword filtering across `/podcasts`, `/blog`, `/guests`,
7+ * `/authors`, `/sponsors`, `/topic/[slug]`, `/author/[slug]`, `/guest/[slug]`,
8+ * and `/sponsor/[slug]`.
89 */
910export const partial = true ;
1011
12+ import groq from " groq" ;
1113import ContentCard from " @/components/ContentCard.astro" ;
1214import type { ContentListItem } from " @/lib/content" ;
1315import {
@@ -22,6 +24,7 @@ import {
2224 postCountQuery ,
2325 sponsorCountQuery ,
2426} from " @/lib/sanity/queries" ;
27+ import { baseFieldsNoContent , personRefFields } from " @/lib/sanity/queries/shared" ;
2528import { itemMatchesTopic , TOPICS_BY_SLUG } from " @/lib/topics" ;
2629
2730type FeedType = " podcast" | " post" | " author" | " guest" | " sponsor" ;
@@ -43,6 +46,8 @@ if (!VALID_TYPES.has(rawType)) {
4346}
4447
4548const skip = params .get (" skip" )?.trim () || " none" ;
49+ const refId = params .get (" refId" )?.trim () || " " ;
50+ const searchQuery = params .get (" q" )?.trim ().toLowerCase () || " " ;
4651const rawOffset = Number .parseInt (params .get (" offset" ) ?? " 0" , 10 );
4752const rawLimit = Number .parseInt (params .get (" limit" ) ?? " 8" , 10 );
4853const offset = Number .isFinite (rawOffset ) && rawOffset >= 0 ? rawOffset : 0 ;
@@ -53,20 +58,87 @@ const sliceEnd = offset + pageSize;
5358const topicSlug = params .get (" topic" )?.trim () || " " ;
5459const topicDef = topicSlug ? TOPICS_BY_SLUG [topicSlug ] : undefined ;
5560
61+ const refPodcastQuery = groq ` *[_type == "podcast" && references($refId) && defined(slug.current)] | order(date desc, _updatedAt desc) [$offset...$limit] {
62+ ${baseFieldsNoContent },
63+ author[]->{ ${personRefFields } },
64+ guest[]->{ ${personRefFields } }
65+ } ` ;
66+ const refPodcastCountQuery = groq ` count(*[_type == "podcast" && references($refId) && defined(slug.current)]) ` ;
67+
68+ const refPostQuery = groq ` *[_type == "post" && references($refId) && defined(slug.current)] | order(date desc, _updatedAt desc) [$offset...$limit] {
69+ ${baseFieldsNoContent },
70+ author[]->{ ${personRefFields } }
71+ } ` ;
72+ const refPostCountQuery = groq ` count(*[_type == "post" && references($refId) && defined(slug.current)]) ` ;
73+
74+ const matchesSearch = (item : ContentListItem , q : string ) => {
75+ if (! q ) {
76+ return true ;
77+ }
78+ const hay = ` ${item .title ?? " " } ${item .excerpt ?? " " } ` .toLowerCase ();
79+ return hay .includes (q );
80+ };
81+
5682let items: ContentListItem [] = [];
5783let totalCount = 0 ;
5884
59- if (topicDef && (rawType === " podcast" || rawType === " post" )) {
60- const query = rawType === " podcast" ? morePodcastQuery : morePostQuery ;
61- const allItems = ((await sanity .loadQuery (query , {
62- skip ,
63- offset: 0 ,
64- limit: 250 ,
65- })) ?? []) as ContentListItem [];
66-
67- const matched = allItems .filter ((item ) =>
68- itemMatchesTopic (item .title , item .excerpt , topicDef ),
69- );
85+ if (refId && (rawType === " podcast" || rawType === " post" )) {
86+ if (rawType === " podcast" ) {
87+ const [count, batch] = await Promise .all ([
88+ sanity .loadQuery (refPodcastCountQuery , { refId }),
89+ sanity .loadQuery (refPodcastQuery , { refId , offset , limit: sliceEnd }),
90+ ]);
91+ totalCount = typeof count === " number" ? count : 0 ;
92+ items = (batch ?? []) as ContentListItem [];
93+ } else {
94+ const [count, batch] = await Promise .all ([
95+ sanity .loadQuery (refPostCountQuery , { refId }),
96+ sanity .loadQuery (refPostQuery , { refId , offset , limit: sliceEnd }),
97+ ]);
98+ totalCount = typeof count === " number" ? count : 0 ;
99+ items = (batch ?? []) as ContentListItem [];
100+ }
101+ } else if (topicDef || searchQuery ) {
102+ let allItems: ContentListItem [] = [];
103+ if (rawType === " podcast" ) {
104+ allItems = ((await sanity .loadQuery (morePodcastQuery , {
105+ skip ,
106+ offset: 0 ,
107+ limit: 300 ,
108+ })) ?? []) as ContentListItem [];
109+ } else if (rawType === " post" ) {
110+ allItems = ((await sanity .loadQuery (morePostQuery , {
111+ skip ,
112+ offset: 0 ,
113+ limit: 300 ,
114+ })) ?? []) as ContentListItem [];
115+ } else if (rawType === " guest" ) {
116+ allItems = ((await sanity .loadQuery (moreGuestQuery , {
117+ skip ,
118+ offset: 0 ,
119+ limit: 300 ,
120+ })) ?? []) as ContentListItem [];
121+ } else if (rawType === " author" ) {
122+ allItems = ((await sanity .loadQuery (moreAuthorQuery , {
123+ skip ,
124+ offset: 0 ,
125+ limit: 300 ,
126+ })) ?? []) as ContentListItem [];
127+ } else if (rawType === " sponsor" ) {
128+ allItems = ((await sanity .loadQuery (moreSponsorQuery , {
129+ skip ,
130+ offset: 0 ,
131+ limit: 300 ,
132+ })) ?? []) as ContentListItem [];
133+ }
134+
135+ const matched = allItems .filter ((item ) => {
136+ const okTopic = topicDef
137+ ? itemMatchesTopic (item .title , item .excerpt , topicDef )
138+ : true ;
139+ const okSearch = matchesSearch (item , searchQuery );
140+ return okTopic && okSearch ;
141+ });
70142 totalCount = matched .length ;
71143 items = matched .slice (offset , sliceEnd );
72144} else {
@@ -112,7 +184,10 @@ if (topicDef && (rawType === "podcast" || rawType === "post")) {
112184 limit: sliceEnd ,
113185 }),
114186 ]);
115- totalCount = typeof count === " number" ? count : 0 ;
187+ totalCount = Math .max (
188+ 0 ,
189+ (typeof count === " number" ? count : 0 ) - (skip !== " none" ? 1 : 0 ),
190+ );
116191 items = (batch ?? []) as ContentListItem [];
117192 break ;
118193 }
@@ -125,7 +200,10 @@ if (topicDef && (rawType === "podcast" || rawType === "post")) {
125200 limit: sliceEnd ,
126201 }),
127202 ]);
128- totalCount = typeof count === " number" ? count : 0 ;
203+ totalCount = Math .max (
204+ 0 ,
205+ (typeof count === " number" ? count : 0 ) - (skip !== " none" ? 1 : 0 ),
206+ );
129207 items = (batch ?? []) as ContentListItem [];
130208 break ;
131209 }
@@ -138,7 +216,10 @@ if (topicDef && (rawType === "podcast" || rawType === "post")) {
138216 limit: sliceEnd ,
139217 }),
140218 ]);
141- totalCount = typeof count === " number" ? count : 0 ;
219+ totalCount = Math .max (
220+ 0 ,
221+ (typeof count === " number" ? count : 0 ) - (skip !== " none" ? 1 : 0 ),
222+ );
142223 items = (batch ?? []) as ContentListItem [];
143224 break ;
144225 }
0 commit comments