site: tag pages include finds; find-only tags get pages
Tag pages are now generated from the union of entry and find tags — 1,179 tags that existed only on finds (most tag links on find pages) previously 404'd. Tagged finds render in a compact secondary "Finds" list below the entry grid, keeping the editorial tier on top. Find-only tag pages with fewer than 3 finds are noindexed until they fill out. Tag feeds stay entries-only; the RSS pill and autodiscovery link only render where a feed exists. Category pages unchanged (finds have no category). Claude-Session: https://claude.ai/code/session_01WZaczDJjL3xZ3u5spsN5AL
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
---
|
||||
import type { Entry } from '../lib/entries';
|
||||
|
||||
// Structural subset of lib/entries' Entry so callers can also feed mapped
|
||||
// find items (tag pages) without pretending they're full entries.
|
||||
interface Props {
|
||||
entries: Entry[];
|
||||
entries: Array<{
|
||||
href: string;
|
||||
date: Date;
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
const { entries } = Astro.props;
|
||||
|
||||
@@ -31,6 +31,7 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
title="Feeds"
|
||||
description={description}
|
||||
subheader="Take Unique with you."
|
||||
ogImage={ogImage}
|
||||
jsonLd={[collectionPage, breadcrumbs]}
|
||||
>
|
||||
<h1>Feeds</h1>
|
||||
|
||||
+60
-10
@@ -1,7 +1,9 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import EntryGrid from '../../components/EntryGrid.astro';
|
||||
import EntryList from '../../components/EntryList.astro';
|
||||
import { getAllEntries } from '../../lib/entries';
|
||||
import { getAllFinds } from '../../lib/find-items';
|
||||
import {
|
||||
buildOgImage,
|
||||
buildBreadcrumbs,
|
||||
@@ -11,29 +13,58 @@ import {
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const entries = await getAllEntries();
|
||||
const tags = [...new Set(entries.flatMap((e) => e.tags))];
|
||||
return tags.map((tag) => ({
|
||||
const finds = await getAllFinds();
|
||||
const tags = new Set([
|
||||
...entries.flatMap((e) => e.tags),
|
||||
...finds.flatMap((f) => f.data.tags),
|
||||
]);
|
||||
return [...tags].map((tag) => ({
|
||||
params: { tag },
|
||||
props: {
|
||||
tag,
|
||||
entries: entries.filter((e) => e.tags.includes(tag)),
|
||||
finds: finds.filter((f) => f.data.tags.includes(tag)),
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const { tag, entries } = Astro.props;
|
||||
const lastUpdated = entries[0]?.date;
|
||||
const count = entries.length;
|
||||
const topNames = entries.slice(0, 3).map((e) => e.primary).join(', ');
|
||||
const { tag, entries, finds } = Astro.props;
|
||||
|
||||
const findRows = finds.map((f) => ({
|
||||
href: `/find/${f.id}/`,
|
||||
date: f.data.date,
|
||||
primary: f.data.name,
|
||||
secondary: f.data.subtitle ?? f.data.description,
|
||||
}));
|
||||
|
||||
const count = entries.length + finds.length;
|
||||
const lastUpdated = [entries[0]?.date, finds[0]?.data.date]
|
||||
.filter((d): d is Date => Boolean(d))
|
||||
.sort((a, b) => b.valueOf() - a.valueOf())[0];
|
||||
|
||||
const topNames = [...entries.map((e) => e.primary), ...finds.map((f) => f.data.name)]
|
||||
.slice(0, 3)
|
||||
.join(', ');
|
||||
const description =
|
||||
topNames.length > 0
|
||||
? `All ${count} entries tagged #${tag}: ${topNames}${count > 3 ? '…' : '.'}`
|
||||
: `All entries tagged #${tag}.`;
|
||||
|
||||
// Find-only tags with a couple of items are thin; keep them browsable but
|
||||
// out of the index until they accumulate content.
|
||||
const noindex = entries.length === 0 && finds.length < 3;
|
||||
|
||||
// A scoped feed only exists when the tag has entries (feeds mirror the
|
||||
// editorial surface: reviews + posts + bundles).
|
||||
const feedHref = entries.length > 0 ? `/tags/${tag}/rss.xml` : undefined;
|
||||
|
||||
const ogImage = buildOgImage(undefined, 'site', `#${tag} on Unique`);
|
||||
|
||||
const itemList = buildItemList(
|
||||
entries.map((e) => ({ url: e.href, name: e.primary })),
|
||||
[
|
||||
...entries.map((e) => ({ url: e.href, name: e.primary })),
|
||||
...finds.map((f) => ({ url: `/find/${f.id}/`, name: f.data.name })),
|
||||
],
|
||||
`#${tag} on Unique`
|
||||
);
|
||||
|
||||
@@ -59,15 +90,34 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
subheader={`Tagged: ${tag}.`}
|
||||
lastUpdated={lastUpdated}
|
||||
ogImage={ogImage}
|
||||
noindex={noindex}
|
||||
jsonLd={[collectionPage, itemList, breadcrumbs]}
|
||||
feeds={[{ title: `Unique — #${tag} (RSS)`, href: `/tags/${tag}/rss.xml` }]}
|
||||
feedHref={`/tags/${tag}/rss.xml`}
|
||||
feeds={feedHref ? [{ title: `Unique — #${tag} (RSS)`, href: feedHref }] : undefined}
|
||||
feedHref={feedHref}
|
||||
wide
|
||||
>
|
||||
<EntryGrid entries={entries} />
|
||||
{entries.length > 0 && <EntryGrid entries={entries} />}
|
||||
|
||||
{finds.length > 0 && (
|
||||
<section class="tag-finds">
|
||||
<h2>Finds</h2>
|
||||
<EntryList entries={findRows} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
<p class="back"><a href="/">← back</a></p>
|
||||
|
||||
<style>
|
||||
.tag-finds {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
.tag-finds:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.tag-finds h2 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
.back { margin-top: 2rem; font-size: 0.9rem; }
|
||||
</style>
|
||||
</BaseLayout>
|
||||
|
||||
Reference in New Issue
Block a user