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
4 changes: 3 additions & 1 deletion scripts/community-feed-notifier.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ async function main() {
userAgent: USER_AGENT,
});
const enrichedItems = await Promise.all(
items.map((item) => enrichNotifierItemWithLinkedPageImage(item)),
items.map((item) => enrichNotifierItemWithLinkedPageImage(item, {
force: item.source?.imageStrategy === "linked-page-featured",
})),
);
allItems.push(...enrichedItems);
} catch (error) {
Expand Down
11 changes: 7 additions & 4 deletions scripts/fetch-feeds.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ function normalizeItem(rawItem, source) {
link,
publishedAt: publishedAt.toISOString(),
source: {
...(source.imageStrategy ? { imageStrategy: source.imageStrategy } : {}),
name: source.name,
siteUrl: source.siteUrl,
feedUrl: source.feedUrl,
Expand All @@ -441,10 +442,10 @@ function normalizeItem(rawItem, source) {

export async function enrichItemWithLinkedPageImage(
item,
{ fetchTextFn = fetchText } = {},
{ fetchTextFn = fetchText, force = false } = {},
) {
const { inlineImage, ...publicItem } = item;
if (publicItem.image) return publicItem;
if (publicItem.image && !force) return publicItem;
if (
!isHttpUrl(publicItem.link)
) {
Expand All @@ -461,7 +462,7 @@ export async function enrichItemWithLinkedPageImage(
const image = extractPageImage(html, publicItem.link);
return {
...publicItem,
image: image ?? inlineImage ?? null,
image: image ?? publicItem.image ?? inlineImage ?? null,
};
} catch {
return { ...publicItem, image: inlineImage ?? null };
Expand Down Expand Up @@ -514,7 +515,9 @@ async function main() {
});

const itemsWithLinkedPageImages = await Promise.all(
normalizedItems.map((item) => enrichItemWithLinkedPageImage(item)),
normalizedItems.map((item) => enrichItemWithLinkedPageImage(item, {
force: item.source?.imageStrategy === "linked-page-featured",
})),
);

feedsWithItems.push({
Expand Down
7 changes: 4 additions & 3 deletions scripts/lib/community-feed-reader.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export async function loadMemberFeeds(filePath = DEFAULT_MEMBER_FEEDS_PATH) {
name: String(item.name),
feedUrl: String(item.feedUrl),
siteUrl: String(item.siteUrl),
...(item.imageStrategy ? { imageStrategy: String(item.imageStrategy) } : {}),
};
});
}
Expand Down Expand Up @@ -318,15 +319,15 @@ export function normalizeNotifierItem(rawItem, source) {

export async function enrichNotifierItemWithLinkedPageImage(
item,
{ fetchTextFn = fetchText } = {},
{ fetchTextFn = fetchText, force = false } = {},
) {
if (item.imageUrl || !isHttpUrl(item.link)) return item;
if ((item.imageUrl && !force) || !isHttpUrl(item.link)) return item;

try {
const html = await fetchTextFn(item.link);
return {
...item,
imageUrl: resolveHtmlImageUrl(html, item.link) || null,
imageUrl: resolveHtmlImageUrl(html, item.link) || item.imageUrl || null,
};
} catch {
return item;
Expand Down
5 changes: 4 additions & 1 deletion src/data/composite-feed.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-07-10T08:13:47.643Z",
"generatedAt": "2026-07-10T08:34:57.128Z",
"itemsPerFeed": 3,
"feeds": [
{
Expand Down Expand Up @@ -108,6 +108,7 @@
"link": "https://danielkishimoto.com/blog/2026/06/an-ai-roasted-my-brand/",
"publishedAt": "2026-06-26T08:08:21.000Z",
"source": {
"imageStrategy": "linked-page-featured",
"name": "Daniel Kishimoto",
"siteUrl": "https://danielkishimoto.com/",
"feedUrl": "https://danielkishimoto.com/feed/"
Expand All @@ -121,6 +122,7 @@
"link": "https://danielkishimoto.com/blog/2026/04/my-design-system-met-claude-design/",
"publishedAt": "2026-04-22T14:46:18.000Z",
"source": {
"imageStrategy": "linked-page-featured",
"name": "Daniel Kishimoto",
"siteUrl": "https://danielkishimoto.com/",
"feedUrl": "https://danielkishimoto.com/feed/"
Expand All @@ -134,6 +136,7 @@
"link": "https://danielkishimoto.com/blog/2026/02/2-ai-fluency-is-all-you-need/",
"publishedAt": "2026-02-24T08:17:46.000Z",
"source": {
"imageStrategy": "linked-page-featured",
"name": "Daniel Kishimoto",
"siteUrl": "https://danielkishimoto.com/",
"feedUrl": "https://danielkishimoto.com/feed/"
Expand Down
3 changes: 2 additions & 1 deletion src/data/member-feeds.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"id": "daniel-kishimoto",
"name": "Daniel Kishimoto",
"siteUrl": "https://danielkishimoto.com/",
"feedUrl": "https://danielkishimoto.com/feed/"
"feedUrl": "https://danielkishimoto.com/feed/",
"imageStrategy": "linked-page-featured"
},
{
"id": "mere-mortal-dev",
Expand Down
30 changes: 22 additions & 8 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,11 @@
] as const;

type MemberFeed = {
id?: string;
name: string;
siteUrl: string;
feedUrl: string;
imageStrategy?: string;
};

type CompositeItem = {
Expand Down Expand Up @@ -465,14 +467,26 @@
{feed.items.map((item) => (
<div class="group relative z-0 min-w-[280px] max-w-md flex-1 rounded-xl border border-slate-100 bg-slate-50/70 p-3 pb-10 transition hover:border-(--accent) hover:bg-white hover:z-10 after:clear-both after:block after:content-['']">
{item.image ? (
<div class="float-right ml-3 mb-2 h-24 w-28 overflow-hidden rounded-lg border border-slate-100 bg-slate-100">
<img
src={item.image}
alt=""
loading="lazy"
decoding="async"
class="h-full w-full object-cover transition duration-300 group-hover:scale-[1.02]"
/>
<div
class:list={[
"float-right ml-3 mb-2 h-24 w-28 overflow-hidden rounded-lg border border-slate-100",
item.source?.imageStrategy === "linked-page-featured"
? "bg-slate-950"
: "bg-slate-100",
]}
>
<img
src={item.image}
alt=""
loading="lazy"
decoding="async"
class:list={[
"h-full w-full transition duration-300",
item.source?.imageStrategy === "linked-page-featured"
? "object-contain"
: "object-cover group-hover:scale-[1.02]",
]}
/>
</div>
) : null}
<div class="flex flex-wrap items-center gap-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">
Expand Down Expand Up @@ -514,7 +528,7 @@
)}
</div>
</article>
))

Check warning on line 531 in src/pages/index.astro

View workflow job for this annotation

GitHub Actions / build

File has too many lines (535). Maximum allowed is 500
}
</div>
</div>
Expand Down
18 changes: 18 additions & 0 deletions test/community-feed-reader.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ test("loadMemberFeeds normalizes valid source entries", async () => {
name: "Example Author",
feedUrl: "https://example.com/feed.xml",
siteUrl: "https://example.com/",
imageStrategy: "linked-page-featured",
},
]),
);
Expand All @@ -36,6 +37,7 @@ test("loadMemberFeeds normalizes valid source entries", async () => {
name: "Example Author",
feedUrl: "https://example.com/feed.xml",
siteUrl: "https://example.com/",
imageStrategy: "linked-page-featured",
},
]);
});
Expand Down Expand Up @@ -224,6 +226,22 @@ test("enrichNotifierItemWithLinkedPageImage finds WordPress featured images", as
expect(enriched.imageUrl).toBe("https://example.com/featured.jpg");
});

test("enrichNotifierItemWithLinkedPageImage can replace a feed logo", async () => {
const enriched = await enrichNotifierItemWithLinkedPageImage(
{
imageUrl: "https://example.com/logo.png",
link: "https://example.com/post-1",
},
{
force: true,
fetchTextFn: async () =>
'<img class="attachment-post-thumbnail wp-post-image" src="/featured.jpg">',
},
);

expect(enriched.imageUrl).toBe("https://example.com/featured.jpg");
});

test("fetchFeedItems fetches, normalizes, dedupes, sorts, and limits items", async () => {
const source = {
id: "example-author",
Expand Down
19 changes: 19 additions & 0 deletions test/fetch-feeds.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ describe("composite feed image selection", () => {
expect(item).not.toHaveProperty("inlineImage");
});

test("can prefer a linked WordPress featured image over a feed image", async () => {
const item = await enrichItemWithLinkedPageImage(
{
id: "post-1",
link: "https://example.com/post/",
image: "https://example.com/logo.png",
inlineImage: null,
},
{
force: true,
fetchTextFn: vi.fn(async () =>
'<img class="attachment-post-thumbnail wp-post-image" src="/featured.jpg">',
),
},
);

expect(item.image).toBe("https://example.com/featured.jpg");
});

test("retains an inline image when linked-page enrichment fails", async () => {
const item = await enrichItemWithLinkedPageImage(
{
Expand Down
Loading