Various AT Protocol integrations with obsidian
20
fork

Configure Feed

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

refresh bookmarks on change (#24)

* add refresh btn

* refresh on card/collection/tag changes

* bump version

authored by

treethought and committed by
GitHub
5862b6bc 72eba588

+77 -15
+1 -1
manifest.json
··· 1 1 { 2 2 "id": "atmosphere", 3 3 "name": "Atmosphere", 4 - "version": "0.1.13", 4 + "version": "0.1.15", 5 5 "minAppVersion": "0.15.0", 6 6 "description": "Various integrations with AT Protocol.", 7 7 "author": "treethought",
+1 -1
package.json
··· 1 1 { 2 2 "name": "obsidian-atmosphere", 3 - "version": "0.1.13", 3 + "version": "0.1.15", 4 4 "description": "Various integrations with AT Protocol.", 5 5 "main": "main.js", 6 6 "type": "module",
+12
src/lib/client.ts
··· 46 46 return this.hh.getActor(identifier); 47 47 } 48 48 49 + clearCache(): void { 50 + this.hh.clearCache(); 51 + } 52 + 49 53 handleOAuthCallback(params: URLSearchParams): void { 50 54 this.oauth.handleCallback(params); 51 55 } ··· 95 99 return actor.pds; 96 100 } 97 101 return null; 102 + } 103 + 104 + clearCache(): void { 105 + this.cache.clear(); 98 106 } 99 107 100 108 async handle(pathname: string, init: RequestInit): Promise<Response> { ··· 160 168 161 169 set<T>(key: string, value: T): void { 162 170 this.#store.set(key, new CacheEntry(value)); 171 + } 172 + 173 + clear(): void { 174 + this.#store.clear(); 163 175 } 164 176 }
+2 -2
src/sources/bookmark.ts
··· 194 194 })); 195 195 } 196 196 197 - renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: AtmospherePlugin): void { 197 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, onDataChange: () => void, plugin: AtmospherePlugin): void { 198 198 const section = container.createEl("div", { cls: "atmosphere-filter-section" }); 199 199 200 200 const titleRow = section.createEl("div", { cls: "atmosphere-filter-title-row" }); ··· 203 203 const createBtn = titleRow.createEl("button", { cls: "atmosphere-filter-create-btn" }); 204 204 setIcon(createBtn, "plus"); 205 205 createBtn.addEventListener("click", () => { 206 - new CreateTagModal(plugin, onChange).open(); 206 + new CreateTagModal(plugin, onDataChange).open(); 207 207 }); 208 208 209 209 const chips = section.createEl("div", { cls: "atmosphere-filter-chips" });
+2 -2
src/sources/margin.ts
··· 238 238 return filters; 239 239 } 240 240 241 - renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: AtmospherePlugin): void { 241 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, onDataChange: () => void, plugin: AtmospherePlugin): void { 242 242 const collectionsSection = container.createEl("div", { cls: "atmosphere-filter-section" }); 243 243 244 244 const collectionsTitleRow = collectionsSection.createEl("div", { cls: "atmosphere-filter-title-row" }); ··· 247 247 const createCollectionBtn = collectionsTitleRow.createEl("button", { cls: "atmosphere-filter-create-btn" }); 248 248 setIcon(createCollectionBtn, "plus"); 249 249 createCollectionBtn.addEventListener("click", () => { 250 - new CreateMarginCollectionModal(plugin, onChange).open(); 250 + new CreateMarginCollectionModal(plugin, onDataChange).open(); 251 251 }); 252 252 253 253 const collectionsChips = collectionsSection.createEl("div", { cls: "atmosphere-filter-chips" });
+2 -4
src/sources/semble.ts
··· 215 215 })); 216 216 } 217 217 218 - renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: AtmospherePlugin): void { 218 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, onDataChange: () => void, plugin: AtmospherePlugin): void { 219 219 const section = container.createEl("div", { cls: "atmosphere-filter-section" }); 220 220 221 221 const titleRow = section.createEl("div", { cls: "atmosphere-filter-title-row" }); ··· 224 224 const createBtn = titleRow.createEl("button", { cls: "atmosphere-filter-create-btn" }); 225 225 setIcon(createBtn, "plus"); 226 226 createBtn.addEventListener("click", () => { 227 - new CreateCollectionModal(plugin, onChange).open(); 227 + new CreateCollectionModal(plugin, onDataChange).open(); 228 228 }); 229 229 230 230 const chips = section.createEl("div", { cls: "atmosphere-filter-chips" }); ··· 238 238 onChange(); 239 239 }); 240 240 241 - // Get collections synchronously - note: this is a limitation 242 - // In a real app, we'd want to cache these or handle async properly 243 241 void this.getAvailableFilters().then(collections => { 244 242 for (const collection of collections) { 245 243 const chip = chips.createEl("button", {
+1 -1
src/sources/types.ts
··· 23 23 readonly name: "semble" | "bookmark" | "margin"; 24 24 fetchItems(filters: SourceFilter[], plugin: AtmospherePlugin): Promise<ATBookmarkItem[]>; 25 25 getAvailableFilters(): Promise<SourceFilter[]>; 26 - renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: AtmospherePlugin): void; 26 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, onDataChange: () => void, plugin: AtmospherePlugin): void; 27 27 }
+23 -3
src/views/bookmarks.ts
··· 106 106 } 107 107 } 108 108 109 + private async refresh() { 110 + this.plugin.client.clearCache(); 111 + await this.render(); 112 + 113 + } 114 + 109 115 private renderHeader(container: HTMLElement) { 110 116 const header = container.createEl("div", { cls: "atmosphere-header" }); 111 117 112 - const sourceSelector = header.createEl("div", { cls: "atmosphere-source-selector" }); 118 + const topRow = header.createEl("div", { cls: "atmosphere-header-top-row" }); 119 + 120 + const sourceSelector = topRow.createEl("div", { cls: "atmosphere-source-selector" }); 113 121 const sources: SourceType[] = ["semble", "margin", "bookmark"]; 114 122 115 123 for (const source of sources) { ··· 132 140 }); 133 141 } 134 142 143 + const refreshBtn = topRow.createEl("button", { 144 + cls: "atmosphere-refresh-btn", 145 + attr: { "aria-label": "Refresh bookmarks" } 146 + }); 147 + setIcon(refreshBtn, "refresh-cw"); 148 + refreshBtn.addEventListener("click", () => { 149 + refreshBtn.addClass("atmosphere-refresh-btn-spinning"); 150 + void this.refresh(); 151 + refreshBtn.removeClass("atmosphere-refresh-btn-spinning"); 152 + }); 153 + 135 154 const filtersContainer = container.createEl("div", { cls: "atmosphere-filters" }); 136 155 const sourceData = this.sources.get(this.activeSource); 137 156 if (sourceData) { ··· 139 158 filtersContainer, 140 159 sourceData.filters, 141 160 () => void this.render(), 161 + () => void this.refresh(), 142 162 this.plugin 143 163 ); 144 164 } ··· 153 173 return; 154 174 } 155 175 new CardDetailModal(this.plugin, item, () => { 156 - void this.render(); 176 + void this.refresh(); 157 177 }).open(); 158 178 }); 159 179 ··· 172 192 editBtn.addEventListener("click", (e) => { 173 193 e.stopPropagation(); 174 194 item.openEditModal(() => { 175 - void this.render(); 195 + void this.refresh(); 176 196 }); 177 197 }); 178 198 }
+33 -1
styles.css
··· 9 9 border-bottom: 1px solid var(--background-modifier-border); 10 10 } 11 11 12 + .atmosphere-header-top-row { 13 + display: flex; 14 + align-items: center; 15 + gap: 12px; 16 + margin-bottom: 12px; 17 + } 18 + 12 19 .atmosphere-source-selector { 13 20 display: flex; 14 21 align-items: center; 15 22 justify-content: center; 16 23 gap: 0; 17 - margin-bottom: 12px; 24 + flex: 1; 18 25 border-bottom: 1px solid var(--background-modifier-border); 19 26 position: relative; 20 27 } ··· 66 73 font-size: var(--font-ui-small); 67 74 font-weight: var(--font-medium); 68 75 color: var(--text-muted); 76 + } 77 + 78 + .atmosphere-refresh-btn { 79 + background: transparent; 80 + border: none; 81 + color: var(--text-muted); 82 + cursor: pointer; 83 + padding: 4px; 84 + display: flex; 85 + align-items: center; 86 + opacity: 0.6; 87 + transition: opacity 0.15s ease; 88 + } 89 + 90 + .atmosphere-refresh-btn:hover { 91 + opacity: 1; 92 + } 93 + 94 + .atmosphere-refresh-btn-spinning { 95 + animation: atmosphere-spin 0.6s linear infinite; 96 + } 97 + 98 + @keyframes atmosphere-spin { 99 + from { transform: rotate(0deg); } 100 + to { transform: rotate(360deg); } 69 101 } 70 102 71 103 .atmosphere-filters {