mirror of
https://github.com/dotwee/obsidian-raindropio-plugin.git
synced 2026-07-22 06:50:29 +00:00
feat(blocks): support show and hide field options in raindrop blocks
Add comma-separated show and hide keys to fenced raindrop blocks, layered on top of the result display settings. Unknown field names produce a warning above the rendered results. Collection titles are fetched lazily and cached, only when a block or the explorer shows the collection field. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
fc920c58a2
commit
c12cf641b1
4 changed files with 82 additions and 1 deletions
12
README.md
12
README.md
|
|
@ -74,6 +74,18 @@ Options:
|
|||
- `search`: Raindrop.io search term. This is passed through to Raindrop.io.
|
||||
- `sort`: Raindrop.io sort value, for example `-created`. Defaults to **Default sort**.
|
||||
- `limit`: Number of links to request, clamped between 1 and 100. Defaults to **Default limit**.
|
||||
- `show`: Comma-separated fields to show in addition to the **Result display** settings.
|
||||
- `hide`: Comma-separated fields to hide, applied after `show`.
|
||||
|
||||
Valid field names for `show` and `hide`: `cover`, `domain`, `created`, `excerpt`, `tags`, `collection`. Unknown names produce a warning above the results. For example:
|
||||
|
||||
````markdown
|
||||
```raindrop
|
||||
tag: obsidian
|
||||
hide: excerpt, tags
|
||||
show: collection
|
||||
```
|
||||
````
|
||||
|
||||
You can combine `tag` and `search`; the plugin joins them into one Raindrop.io search term.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { isRaindropFieldName, RAINDROP_FIELD_NAMES, type RaindropDisplayFields } from "./renderer";
|
||||
|
||||
export interface RaindropBlockOptions {
|
||||
collectionId?: number;
|
||||
search?: string;
|
||||
tag?: string;
|
||||
sort?: string;
|
||||
limit?: number;
|
||||
showFields?: (keyof RaindropDisplayFields)[];
|
||||
hideFields?: (keyof RaindropDisplayFields)[];
|
||||
}
|
||||
|
||||
export interface ParsedRaindropBlock {
|
||||
|
|
@ -32,6 +36,28 @@ function clampLimit(limit: number, { min, max }: { min: number; max: number }):
|
|||
return Math.max(min, Math.min(max, Math.floor(limit)));
|
||||
}
|
||||
|
||||
function parseFieldList(
|
||||
raw: string | undefined,
|
||||
key: "show" | "hide",
|
||||
warnings: string[],
|
||||
): (keyof RaindropDisplayFields)[] | undefined {
|
||||
if (raw === undefined) return undefined;
|
||||
|
||||
const fields: (keyof RaindropDisplayFields)[] = [];
|
||||
for (const entry of raw.split(",")) {
|
||||
const name = entry.trim().toLowerCase();
|
||||
if (!name) continue;
|
||||
|
||||
if (isRaindropFieldName(name)) {
|
||||
if (!fields.includes(name)) fields.push(name);
|
||||
} else {
|
||||
warnings.push(`Unknown field \`${name}\` in \`${key}\`; expected one of ${RAINDROP_FIELD_NAMES.join(", ")}.`);
|
||||
}
|
||||
}
|
||||
|
||||
return fields.length > 0 ? fields : undefined;
|
||||
}
|
||||
|
||||
export function parseRaindropBlock(source: string): ParsedRaindropBlock {
|
||||
const warnings: string[] = [];
|
||||
const kv = parseKeyValueLines(source);
|
||||
|
|
@ -51,6 +77,8 @@ export function parseRaindropBlock(source: string): ParsedRaindropBlock {
|
|||
const tag = kv.tag?.trim() || undefined;
|
||||
const search = kv.search?.trim() || undefined;
|
||||
const sort = kv.sort?.trim() || undefined;
|
||||
const showFields = parseFieldList(kv.show, "show", warnings);
|
||||
const hideFields = parseFieldList(kv.hide, "hide", warnings);
|
||||
|
||||
return {
|
||||
options: {
|
||||
|
|
@ -59,6 +87,8 @@ export function parseRaindropBlock(source: string): ParsedRaindropBlock {
|
|||
search,
|
||||
sort,
|
||||
limit,
|
||||
showFields,
|
||||
hideFields,
|
||||
},
|
||||
warnings,
|
||||
};
|
||||
|
|
|
|||
37
src/main.ts
37
src/main.ts
|
|
@ -9,7 +9,7 @@ import {
|
|||
import { RaindropApi } from "./raindrop-api";
|
||||
import { buildRaindropSearchQuery, formatRaindropTagFilter } from "./raindrop-search";
|
||||
import { RaindropSideView } from "./raindrop-view";
|
||||
import { DEFAULT_DISPLAY_FIELDS, renderRaindropItems, renderRaindropStatus } from "./renderer";
|
||||
import { DEFAULT_DISPLAY_FIELDS, renderRaindropItems, renderRaindropStatus, resolveRaindropDisplayFields } from "./renderer";
|
||||
import { DEFAULT_SETTINGS, isRaindropTagClickBehavior, RaindropSettingTab, RaindropViewSettings } from "./settings";
|
||||
|
||||
interface GlobalSearchPluginInstance {
|
||||
|
|
@ -23,6 +23,8 @@ export default class RaindropViewPlugin extends Plugin {
|
|||
activeMarkdownFile: TFile | null = null;
|
||||
private refreshTimeoutId: number | null = null;
|
||||
private readonly refreshDebounceMs = 200;
|
||||
private collectionTitles: Map<number, string> | null = null;
|
||||
private collectionTitlesRequest: Promise<Map<number, string> | undefined> | null = null;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings();
|
||||
|
|
@ -94,9 +96,32 @@ export default class RaindropViewPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async saveSettings(): Promise<void> {
|
||||
// The access token may have changed, so drop cached collection titles.
|
||||
this.collectionTitles = null;
|
||||
this.collectionTitlesRequest = null;
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
async getCollectionTitles(api: RaindropApi): Promise<Map<number, string> | undefined> {
|
||||
if (this.collectionTitles) return this.collectionTitles;
|
||||
|
||||
this.collectionTitlesRequest ??= api
|
||||
.listCollections()
|
||||
.then((titles) => {
|
||||
this.collectionTitles = titles;
|
||||
return titles;
|
||||
})
|
||||
.catch(() => {
|
||||
// Collection titles are optional; results render without them.
|
||||
return undefined;
|
||||
})
|
||||
.finally(() => {
|
||||
this.collectionTitlesRequest = null;
|
||||
});
|
||||
|
||||
return this.collectionTitlesRequest;
|
||||
}
|
||||
|
||||
async openRaindropView(searchQuery?: string, context?: string): Promise<void> {
|
||||
this.captureActiveMarkdownFile();
|
||||
|
||||
|
|
@ -138,6 +163,12 @@ export default class RaindropViewPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
const fields = resolveRaindropDisplayFields(
|
||||
this.settings.displayFields ?? DEFAULT_DISPLAY_FIELDS,
|
||||
parsed.options.showFields,
|
||||
parsed.options.hideFields,
|
||||
);
|
||||
|
||||
const limit = parsed.options.limit ?? this.settings.defaultLimit;
|
||||
const items = await api.listRaindrops({
|
||||
collectionId: parsed.options.collectionId ?? this.settings.defaultCollectionId,
|
||||
|
|
@ -149,9 +180,13 @@ export default class RaindropViewPlugin extends Plugin {
|
|||
perpage: limit,
|
||||
});
|
||||
|
||||
const collectionTitles = fields.collection ? await this.getCollectionTitles(api) : undefined;
|
||||
|
||||
renderRaindropItems(container, items, {
|
||||
title,
|
||||
warnings: parsed.warnings,
|
||||
fields,
|
||||
collectionTitles,
|
||||
onTagClick: (tag) => {
|
||||
void this.handleRaindropTagClick(tag, sourcePath);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -210,8 +210,12 @@ export class RaindropSideView extends ItemView {
|
|||
});
|
||||
|
||||
this.items = reset ? batch : [...this.items, ...batch];
|
||||
const fields = this.plugin.settings.displayFields;
|
||||
const collectionTitles = fields.collection ? await this.plugin.getCollectionTitles(api) : undefined;
|
||||
renderRaindropItems(this.resultsEl, this.items, {
|
||||
title: this.query ? "Filtered Raindrop.io links" : "All Raindrop.io links",
|
||||
fields,
|
||||
collectionTitles,
|
||||
onTagClick: (tag) => {
|
||||
void this.plugin.handleRaindropTagClick(tag, this.getSourceFile()?.path);
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue