Add #m tag filter for webxdc on logged-out homepage

Kind 1063 is generic file metadata, so webxdc events require filtering
by #m=application/x-webxdc. Add extraFilters support to useInfiniteHotFeed
and pass webxdc as a separate filter object with the MIME constraint.
This commit is contained in:
Alex Gleason
2026-03-07 14:15:40 -06:00
parent eeaf6fa2f9
commit 63e8e9b7ec
2 changed files with 32 additions and 9 deletions
+6 -3
View File
@@ -32,12 +32,14 @@ const LANDING_KINDS = [
36767, // Themes
37381, // Magic Decks
3367, // Color Moments
1063, // Webxdc
37516, // Treasures (Geocaches)
7516, // Treasures (Found Logs)
30030, // Emoji Packs
];
/** Webxdc needs a MIME-type tag filter, so it gets its own filter object. */
const LANDING_WEBXDC_FILTER = { kinds: [1063], '#m': ['application/x-webxdc'] };
interface FeedProps {
/** Override the kinds list instead of using feed settings. */
kinds?: number[];
@@ -111,8 +113,9 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage }: F
);
// "Hot" sorted feed query (used when logged out on the home page)
// Shows curated "otherstuff" kinds (photos, videos, articles, themes, etc.) instead of kind 1.
const topQuery = useInfiniteHotFeed(LANDING_KINDS, useTopFeedForLoggedOut);
// Shows curated "otherstuff" kinds instead of kind 1. Webxdc needs a
// separate filter with a MIME-type tag constraint.
const topQuery = useInfiniteHotFeed(LANDING_KINDS, useTopFeedForLoggedOut, undefined, [LANDING_WEBXDC_FILTER]);
// Unify the two query shapes behind a single interface
const activeQuery = useTopFeedForLoggedOut ? topQuery : feedQuery;
+26 -6
View File
@@ -188,22 +188,42 @@ export function useInfiniteSortedPosts(sort: SortMode, enabled = true) {
/**
* Fetches hot-sorted events for specific kinds with infinite scroll.
* Uses NIP-50 search extension `sort:hot` against relay.ditto.pub.
*
* `extraFilters` allows appending additional filter objects to the REQ,
* useful when some kinds need tag constraints (e.g. webxdc needs `#m`).
*/
export function useInfiniteHotFeed(kinds: number[], enabled = true, limit = SORTED_PAGE_SIZE) {
export function useInfiniteHotFeed(
kinds: number[],
enabled = true,
limit = SORTED_PAGE_SIZE,
extraFilters?: Record<string, unknown>[],
) {
const { nostr } = useNostr();
const extraKey = extraFilters ? JSON.stringify(extraFilters) : '';
return useInfiniteQuery<NostrEvent[], Error>({
queryKey: ['infinite-hot-feed', kinds.join(','), limit],
queryKey: ['infinite-hot-feed', kinds.join(','), limit, extraKey],
queryFn: async ({ pageParam, signal }) => {
const ditto = nostr.relay(DITTO_RELAY);
const filter: Record<string, unknown> = {
kinds,
const base: Record<string, unknown> = {
search: 'sort:hot protocol:nostr',
limit,
};
if (pageParam) filter.until = pageParam;
if (pageParam) base.until = pageParam;
// Primary filter for the main kinds list
const filters: Record<string, unknown>[] = [{ ...base, kinds }];
// Append extra filters (each gets the same sort/pagination params)
if (extraFilters) {
for (const extra of extraFilters) {
filters.push({ ...base, ...extra });
}
}
return ditto.query(
[filter as { kinds: number[]; search: string; limit: number; until?: number }],
filters as { kinds: number[]; search: string; limit: number; until?: number }[],
{ signal: AbortSignal.any([signal, AbortSignal.timeout(10000)]) },
);
},