From b643644fb0c511800263f5c21cdcefa241ebd658 Mon Sep 17 00:00:00 2001 From: Lukas Wolfsteiner Date: Mon, 6 Jul 2026 11:28:15 +0200 Subject: [PATCH] feat(renderer): add configurable result display fields Introduce a display fields option so callers can toggle cover, domain, created date, excerpt, tags and collection per result row. Render an optional cover thumbnail and a collection label resolved from a collection title map. Defaults keep the previous appearance, so existing views render unchanged. Co-authored-by: Cursor --- src/renderer.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++--- styles.css | 9 +++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/renderer.ts b/src/renderer.ts index b3eed7e..9c222ec 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -2,10 +2,47 @@ import type { RaindropItem } from "./raindrop-api"; export type RaindropStatusKind = "loading" | "empty" | "error" | "info"; +export interface RaindropDisplayFields { + cover: boolean; + domain: boolean; + created: boolean; + excerpt: boolean; + tags: boolean; + collection: boolean; +} + +export const DEFAULT_DISPLAY_FIELDS: RaindropDisplayFields = { + cover: false, + domain: true, + created: true, + excerpt: true, + tags: true, + collection: false, +}; + +export const RAINDROP_FIELD_NAMES = Object.keys(DEFAULT_DISPLAY_FIELDS) as (keyof RaindropDisplayFields)[]; + +export function isRaindropFieldName(value: string): value is keyof RaindropDisplayFields { + return (RAINDROP_FIELD_NAMES as string[]).includes(value); +} + +export function resolveRaindropDisplayFields( + defaults: RaindropDisplayFields, + show?: (keyof RaindropDisplayFields)[], + hide?: (keyof RaindropDisplayFields)[], +): RaindropDisplayFields { + const fields = { ...defaults }; + for (const field of show ?? []) fields[field] = true; + for (const field of hide ?? []) fields[field] = false; + return fields; +} + export interface RenderRaindropOptions { title?: string; warnings?: string[]; onTagClick?: (tag: string) => void; + fields?: RaindropDisplayFields; + collectionTitles?: Map; } function getDisplayDate(value: string | undefined): string | undefined { @@ -17,6 +54,13 @@ function getDisplayDate(value: string | undefined): string | undefined { return date.toLocaleDateString(); } +function getCollectionTitle(item: RaindropItem, collectionTitles: Map | undefined): string | undefined { + const collectionId = item.collection?.$id; + if (typeof collectionId !== "number") return undefined; + + return collectionTitles?.get(collectionId); +} + export function renderRaindropStatus(container: HTMLElement, message: string, kind: RaindropStatusKind): void { container.empty(); container.createDiv({ @@ -32,6 +76,7 @@ export function renderRaindropItems( ): void { container.empty(); + const fields = options.fields ?? DEFAULT_DISPLAY_FIELDS; const root = container.createDiv({ cls: "raindrop-results" }); if (options.title) { root.createEl("h4", { cls: "raindrop-results-title", text: options.title }); @@ -52,6 +97,18 @@ export function renderRaindropItems( const list = root.createDiv({ cls: "raindrop-list" }); for (const item of items) { const row = list.createDiv({ cls: "raindrop-item" }); + + if (fields.cover && item.cover) { + row.createEl("img", { + cls: "raindrop-item-cover", + attr: { + src: item.cover, + alt: "", + loading: "lazy", + }, + }); + } + const title = row.createEl("a", { cls: "raindrop-item-title", text: item.title || item.link, @@ -63,16 +120,20 @@ export function renderRaindropItems( }); title.setAttr("aria-label", `Open ${item.title || item.link}`); - const metaParts = [item.domain, getDisplayDate(item.created)].filter((part): part is string => Boolean(part)); + const metaParts = [ + fields.collection ? getCollectionTitle(item, options.collectionTitles) : undefined, + fields.domain ? item.domain : undefined, + fields.created ? getDisplayDate(item.created) : undefined, + ].filter((part): part is string => Boolean(part)); if (metaParts.length > 0) { row.createDiv({ cls: "raindrop-item-meta", text: metaParts.join(" - ") }); } - if (item.excerpt) { + if (fields.excerpt && item.excerpt) { row.createDiv({ cls: "raindrop-item-excerpt", text: item.excerpt }); } - if (item.tags && item.tags.length > 0) { + if (fields.tags && item.tags && item.tags.length > 0) { const tags = row.createDiv({ cls: "raindrop-item-tags" }); for (const tag of item.tags) { const tagEl = tags.createEl("a", { diff --git a/styles.css b/styles.css index ded4faa..4d477b4 100644 --- a/styles.css +++ b/styles.css @@ -22,6 +22,15 @@ background: var(--background-primary); } +.raindrop-item-cover { + border-radius: var(--radius-s); + display: block; + margin-bottom: 0.5rem; + max-height: 8rem; + object-fit: cover; + width: 100%; +} + .raindrop-item-title { display: inline-block; font-weight: var(--font-semibold);