feat(api): resolve collection titles for raindrops

Expose the parent collection reference on fetched raindrops.

Add a collections lookup that maps collection IDs to titles, including the Unsorted and Trash system collections.

Enables showing which collection a bookmark belongs to in rendered results.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Lukas Wolfsteiner 2026-07-06 11:28:09 +02:00
parent 7f85861cdf
commit a41574e926

View file

@ -9,6 +9,7 @@ export interface RaindropItem {
created?: string;
cover?: string;
tags?: string[];
collection?: { $id: number };
}
interface RaindropListResponse {
@ -17,6 +18,22 @@ interface RaindropListResponse {
count?: number;
}
interface RaindropCollection {
_id: number;
title?: string;
}
interface RaindropCollectionListResponse {
items?: RaindropCollection[];
result?: boolean;
}
// Raindrop.io system collections are not returned by the collections endpoints.
const SYSTEM_COLLECTION_TITLES: ReadonlyMap<number, string> = new Map([
[-1, "Unsorted"],
[-99, "Trash"],
]);
export interface RaindropListParams {
collectionId: number;
search?: string;
@ -69,6 +86,40 @@ export class RaindropApi {
return data.items;
}
async listCollections(): Promise<Map<number, string>> {
if (!this.isConfigured) {
throw new Error("Missing Raindrop.io access token. Add one under Access token in plugin settings.");
}
const titles = new Map<number, string>(SYSTEM_COLLECTION_TITLES);
for (const path of ["/collections", "/collections/childrens"]) {
const res = await requestUrl({
url: `${this.baseUrl}${path}`,
method: "GET",
headers: {
Authorization: `Bearer ${this.token}`,
},
});
if (res.status < 200 || res.status >= 300) {
throw new Error(`Raindrop.io request failed with status ${res.status}.`);
}
const data = res.json as RaindropCollectionListResponse;
if (!data.result || !Array.isArray(data.items)) {
throw new Error("Unexpected Raindrop.io API response.");
}
for (const collection of data.items) {
if (typeof collection._id === "number" && collection.title) {
titles.set(collection._id, collection.title);
}
}
}
return titles;
}
async listAllRaindrops(params: RaindropListParams, maxItems = 500): Promise<RaindropItem[]> {
const items: RaindropItem[] = [];
const perpage = Math.max(1, Math.min(50, params.perpage ?? 50));