Various AT Protocol integrations with obsidian
20
fork

Configure Feed

Select the types of activity you want to include in your feed.

support searching bookmarks

+48 -16
+43 -16
src/views/bookmarks.ts
··· 1 - import { ItemView, WorkspaceLeaf, setIcon, Menu } from "obsidian"; 1 + import { ItemView, WorkspaceLeaf, setIcon, Menu, SearchComponent } from "obsidian"; 2 2 import type AtmospherePlugin from "../main"; 3 3 import { CardDetailModal } from "../components/cardDetailModal"; 4 4 import { CreateCollectionModal } from "../components/createCollectionModal"; ··· 19 19 selectedCollections: Set<string> = new Set(); 20 20 selectedTags: Set<string> = new Set(); 21 21 sources: Map<SourceName, DataSource> = new Map(); 22 + searchQuery: string = ""; 23 + private cachedItems: ATBookmarkItem[] = []; 22 24 23 25 constructor(leaf: WorkspaceLeaf, plugin: AtmospherePlugin) { 24 26 super(leaf); ··· 135 137 136 138 try { 137 139 const items = await this.fetchItems(); 140 + this.cachedItems = items; 138 141 loading.remove(); 139 - 140 - if (items.length === 0) { 141 - container.createEl("p", { text: "No items found." }); 142 - return; 143 - } 144 - 145 - const grid = container.createEl("div", { cls: "atmosphere-grid" }); 146 - for (const item of items) { 147 - try { 148 - void this.renderItem(grid, item); 149 - } catch (err) { 150 - const message = err instanceof Error ? err.message : String(err); 151 - console.error(`Failed to render item ${item.getUri()}: ${message}`); 152 - } 153 - } 142 + this.renderGrid(items); 154 143 } catch (err) { 155 144 loading.remove(); 156 145 const message = err instanceof Error ? err.message : String(err); ··· 158 147 } 159 148 } 160 149 150 + private renderGrid(items: ATBookmarkItem[]) { 151 + this.contentEl.querySelector(".atmosphere-grid")?.remove(); 152 + this.contentEl.querySelector(".atmosphere-empty")?.remove(); 153 + 154 + const query = this.searchQuery.trim().toLowerCase(); 155 + const filtered = query 156 + ? items.filter(item => this.matchesSearch(item, query)) 157 + : items; 158 + 159 + if (filtered.length === 0) { 160 + this.contentEl.createEl("p", { text: "No items found.", cls: "atmosphere-empty" }); 161 + return; 162 + } 163 + const grid = this.contentEl.createEl("div", { cls: "atmosphere-grid" }); 164 + for (const item of filtered) { 165 + try { void this.renderItem(grid, item); } 166 + catch (err) { console.error(`Failed to render item ${item.getUri()}:`, err); } 167 + } 168 + } 169 + 170 + private matchesSearch(item: ATBookmarkItem, query: string): boolean { 171 + return ( 172 + item.getTitle()?.toLowerCase().includes(query) || 173 + item.getDescription()?.toLowerCase().includes(query) || 174 + item.getUrl()?.toLowerCase().includes(query) || 175 + item.getSiteName()?.toLowerCase().includes(query) || 176 + item.getTags().some(t => t.toLowerCase().includes(query)) 177 + ) ?? false; 178 + } 179 + 161 180 private async refresh() { 162 181 this.plugin.client.clearCache(); 163 182 await this.render(); ··· 220 239 if (tagSources.length > 0) { 221 240 void this.renderTagsFilter(filtersEl, tagSources); 222 241 } 242 + 243 + const searchInput = new SearchComponent(filtersEl) 244 + searchInput.setValue(this.searchQuery) 245 + searchInput.setPlaceholder("Search bookmarks..."); 246 + searchInput.onChange(() => { 247 + this.searchQuery = searchInput.getValue(); 248 + this.renderGrid(this.cachedItems); 249 + }) 223 250 } 224 251 225 252 private async fetchAllCollections(sources: SourceName[]): Promise<(SourceFilter & { source: SourceName })[]> {
+5
styles.css
··· 1589 1589 width: 14px; 1590 1590 height: 14px; 1591 1591 } 1592 + 1593 + .atmosphere-filters .search-input-container { 1594 + width: 180px; 1595 + margin: 0 auto; 1596 + }