tangled
alpha
login
or
join now
treethought.xyz
/
obsidian-atmosphere
19
fork
atom
Various AT Protocol integrations with obsidian
19
fork
atom
overview
issues
pulls
pipelines
better styling of filters
treethought
2 weeks ago
7c0e2e5a
34e53586
+102
-84
2 changed files
expand all
collapse all
unified
split
src
views
bookmarks.ts
styles.css
+76
-72
src/views/bookmarks.ts
···
229
229
230
230
private renderFilters(container: HTMLElement) {
231
231
const filtersEl = container.createEl("div", { cls: "atmosphere-filters" });
232
232
+
const toolbarRow = filtersEl.createEl("div", { cls: "atmosphere-filter-toolbar" });
232
233
233
234
const collectionSources = (["semble", "margin"] as SourceName[]).filter(s => this.activeSources.has(s));
234
235
if (collectionSources.length > 0) {
235
235
-
void this.renderCollectionsFilter(filtersEl, collectionSources);
236
236
+
this.renderCollectionsFilterBtn(toolbarRow, collectionSources);
236
237
}
237
238
238
239
const tagSources = (["margin", "bookmark"] as SourceName[]).filter(s => this.activeSources.has(s));
239
240
if (tagSources.length > 0) {
240
240
-
void this.renderTagsFilter(filtersEl, tagSources);
241
241
+
this.renderTagsFilterBtn(toolbarRow, tagSources);
241
242
}
242
243
243
243
-
const searchInput = new SearchComponent(filtersEl)
244
244
-
searchInput.setValue(this.searchQuery)
244
244
+
const searchInput = new SearchComponent(toolbarRow);
245
245
+
searchInput.setValue(this.searchQuery);
245
246
searchInput.setPlaceholder("Search bookmarks...");
246
247
searchInput.onChange(() => {
247
248
this.searchQuery = searchInput.getValue();
248
249
this.renderGrid(this.cachedItems);
249
249
-
})
250
250
-
}
250
250
+
});
251
251
252
252
-
private async fetchAllCollections(sources: SourceName[]): Promise<(SourceFilter & { source: SourceName })[]> {
253
253
-
const results = await Promise.all(
254
254
-
sources.map(async s => {
255
255
-
const items = await (this.sources.get(s)?.getAvailableCollections?.() ?? Promise.resolve([]));
256
256
-
return items.map(c => ({ ...c, source: s }));
257
257
-
})
258
258
-
);
259
259
-
const seen = new Set<string>();
260
260
-
return results.flat().filter(c => !seen.has(c.value) && Boolean(seen.add(c.value)));
261
261
-
}
262
262
-
263
263
-
private async fetchAllTags(sources: SourceName[]): Promise<(SourceFilter & { source: SourceName })[]> {
264
264
-
const results = await Promise.all(
265
265
-
sources.map(async s => {
266
266
-
const items = await (this.sources.get(s)?.getAvilableTags?.() ?? Promise.resolve([]));
267
267
-
return items.map(t => ({ ...t, source: s }));
268
268
-
})
269
269
-
);
270
270
-
const seen = new Set<string>();
271
271
-
return results.flat().filter(t => !seen.has(t.value) && Boolean(seen.add(t.value)));
252
252
+
if (this.selectedCollections.size > 0 || this.selectedTags.size > 0) {
253
253
+
const chipsRow = filtersEl.createEl("div", { cls: "atmosphere-active-chips-row" });
254
254
+
if (this.selectedCollections.size > 0 && collectionSources.length > 0) {
255
255
+
void this.renderActiveCollectionChips(chipsRow, collectionSources);
256
256
+
}
257
257
+
if (this.selectedTags.size > 0 && tagSources.length > 0) {
258
258
+
void this.renderActiveTagChips(chipsRow, tagSources);
259
259
+
}
260
260
+
}
272
261
}
273
262
274
274
-
private async renderCollectionsFilter(container: HTMLElement, collectionSources: SourceName[]) {
275
275
-
const section = container.createEl("div", { cls: "atmosphere-filter-section" });
263
263
+
private renderCollectionsFilterBtn(toolbar: HTMLElement, collectionSources: SourceName[]) {
264
264
+
const group = toolbar.createEl("div", { cls: "atmosphere-filter-btn-group" });
276
265
277
277
-
const titleRow = section.createEl("div", { cls: "atmosphere-filter-title-row" });
278
278
-
279
279
-
const pickerBtn = titleRow.createEl("button", {
266
266
+
const pickerBtn = group.createEl("button", {
280
267
cls: "atmosphere-filter-picker-btn",
281
268
attr: { "aria-label": "Filter collections" },
282
269
});
283
270
setIcon(pickerBtn, "folder");
284
271
pickerBtn.createEl("span", { text: "Collections", cls: "atmosphere-filter-title" });
272
272
+
pickerBtn.addEventListener("click", (e) => void this.showCollectionsMenu(e, collectionSources));
285
273
286
286
-
const btn = titleRow.createEl("button", {
274
274
+
const btn = group.createEl("button", {
287
275
cls: "atmosphere-filter-create-btn",
288
276
attr: { "aria-label": "New collection" },
289
277
});
···
296
284
() => void this.refresh()
297
285
).open();
298
286
});
299
299
-
pickerBtn.addEventListener("click", (e) => void this.showCollectionsMenu(e, collectionSources));
300
300
-
301
301
-
if (this.selectedCollections.size > 0) {
302
302
-
const chipsRow = section.createEl("div", { cls: "atmosphere-filter-active-chips" });
303
303
-
const collections = await this.fetchAllCollections(collectionSources);
304
304
-
for (const c of collections) {
305
305
-
if (!this.selectedCollections.has(c.value)) continue;
306
306
-
const chip = chipsRow.createEl("span", { cls: "atmosphere-chip atmosphere-chip-active atmosphere-chip-removable" });
307
307
-
setIcon(chip, sourceIconId(c.source));
308
308
-
chip.createEl("span", { text: c.label ?? c.value });
309
309
-
const x = chip.createEl("button", { cls: "atmosphere-chip-remove-btn", attr: { "aria-label": `Remove ${c.label ?? c.value}` } });
310
310
-
setIcon(x, "x");
311
311
-
x.addEventListener("click", () => {
312
312
-
this.selectedCollections.delete(c.value);
313
313
-
void this.render();
314
314
-
});
315
315
-
}
316
316
-
}
317
287
}
318
288
319
319
-
private async renderTagsFilter(container: HTMLElement, tagSources: SourceName[]) {
320
320
-
const section = container.createEl("div", { cls: "atmosphere-filter-section" });
321
321
-
322
322
-
const titleRow = section.createEl("div", { cls: "atmosphere-filter-title-row" });
289
289
+
private renderTagsFilterBtn(toolbar: HTMLElement, tagSources: SourceName[]) {
290
290
+
const group = toolbar.createEl("div", { cls: "atmosphere-filter-btn-group" });
323
291
324
324
-
const pickerBtn = titleRow.createEl("button", {
292
292
+
const pickerBtn = group.createEl("button", {
325
293
cls: "atmosphere-filter-picker-btn",
326
294
attr: { "aria-label": "Filter tags" },
327
295
});
328
296
setIcon(pickerBtn, "tag");
329
297
pickerBtn.createEl("span", { text: "Tags", cls: "atmosphere-filter-title" });
298
298
+
pickerBtn.addEventListener("click", (e) => void this.showTagsMenu(e, tagSources));
330
299
331
300
if (tagSources.includes("bookmark")) {
332
332
-
const btn = titleRow.createEl("button", {
301
301
+
const btn = group.createEl("button", {
333
302
cls: "atmosphere-filter-create-btn",
334
303
attr: { "aria-label": "New tag" },
335
304
});
336
305
setIcon(btn, "plus");
337
306
btn.addEventListener("click", (e) => { e.stopPropagation(); new CreateTagModal(this.plugin, () => void this.refresh()).open(); });
338
307
}
339
339
-
pickerBtn.addEventListener("click", (e) => void this.showTagsMenu(e, tagSources));
308
308
+
}
340
309
341
341
-
if (this.selectedTags.size > 0) {
342
342
-
const chipsRow = section.createEl("div", { cls: "atmosphere-filter-active-chips" });
343
343
-
const tags = await this.fetchAllTags(tagSources);
344
344
-
for (const t of tags) {
345
345
-
if (!this.selectedTags.has(t.value)) continue;
346
346
-
const chip = chipsRow.createEl("span", { cls: "atmosphere-chip atmosphere-chip-active atmosphere-chip-removable" });
347
347
-
setIcon(chip, sourceIconId(t.source))
310
310
+
private async fetchAllCollections(sources: SourceName[]): Promise<(SourceFilter & { source: SourceName })[]> {
311
311
+
const results = await Promise.all(
312
312
+
sources.map(async s => {
313
313
+
const items = await (this.sources.get(s)?.getAvailableCollections?.() ?? Promise.resolve([]));
314
314
+
return items.map(c => ({ ...c, source: s }));
315
315
+
})
316
316
+
);
317
317
+
const seen = new Set<string>();
318
318
+
return results.flat().filter(c => !seen.has(c.value) && Boolean(seen.add(c.value)));
319
319
+
}
348
320
349
349
-
chip.createEl("span", { text: t.label ?? t.value });
350
350
-
const x = chip.createEl("button", { cls: "atmosphere-chip-remove-btn", attr: { "aria-label": `Remove ${t.label ?? t.value}` } });
351
351
-
setIcon(x, "x");
352
352
-
x.addEventListener("click", () => {
353
353
-
this.selectedTags.delete(t.value);
354
354
-
void this.render();
355
355
-
});
356
356
-
}
321
321
+
private async fetchAllTags(sources: SourceName[]): Promise<(SourceFilter & { source: SourceName })[]> {
322
322
+
const results = await Promise.all(
323
323
+
sources.map(async s => {
324
324
+
const items = await (this.sources.get(s)?.getAvilableTags?.() ?? Promise.resolve([]));
325
325
+
return items.map(t => ({ ...t, source: s }));
326
326
+
})
327
327
+
);
328
328
+
const seen = new Set<string>();
329
329
+
return results.flat().filter(t => !seen.has(t.value) && Boolean(seen.add(t.value)));
330
330
+
}
331
331
+
332
332
+
private async renderActiveCollectionChips(chipsRow: HTMLElement, collectionSources: SourceName[]) {
333
333
+
const collections = await this.fetchAllCollections(collectionSources);
334
334
+
for (const c of collections) {
335
335
+
if (!this.selectedCollections.has(c.value)) continue;
336
336
+
const chip = chipsRow.createEl("span", { cls: "atmosphere-chip atmosphere-chip-active atmosphere-chip-removable" });
337
337
+
setIcon(chip, sourceIconId(c.source));
338
338
+
chip.createEl("span", { text: c.label ?? c.value });
339
339
+
const x = chip.createEl("button", { cls: "atmosphere-chip-remove-btn", attr: { "aria-label": `Remove ${c.label ?? c.value}` } });
340
340
+
setIcon(x, "x");
341
341
+
x.addEventListener("click", () => {
342
342
+
this.selectedCollections.delete(c.value);
343
343
+
void this.render();
344
344
+
});
345
345
+
}
346
346
+
}
347
347
+
348
348
+
private async renderActiveTagChips(chipsRow: HTMLElement, tagSources: SourceName[]) {
349
349
+
const tags = await this.fetchAllTags(tagSources);
350
350
+
for (const t of tags) {
351
351
+
if (!this.selectedTags.has(t.value)) continue;
352
352
+
const chip = chipsRow.createEl("span", { cls: "atmosphere-chip atmosphere-chip-active atmosphere-chip-removable" });
353
353
+
setIcon(chip, sourceIconId(t.source));
354
354
+
chip.createEl("span", { text: "#" + (t.label ?? t.value) });
355
355
+
const x = chip.createEl("button", { cls: "atmosphere-chip-remove-btn", attr: { "aria-label": `Remove ${t.label ?? t.value}` } });
356
356
+
setIcon(x, "x");
357
357
+
x.addEventListener("click", () => {
358
358
+
this.selectedTags.delete(t.value);
359
359
+
void this.render();
360
360
+
});
357
361
}
358
362
}
359
363
+26
-12
styles.css
···
103
103
.atmosphere-filters {
104
104
display: flex;
105
105
flex-direction: column;
106
106
-
gap: 16px;
106
106
+
gap: 8px;
107
107
margin-bottom: 16px;
108
108
+
container-type: inline-size;
108
109
}
109
110
110
110
-
.atmosphere-filter-section {
111
111
+
.atmosphere-filter-toolbar {
111
112
display: flex;
112
112
-
flex-direction: column;
113
113
-
gap: 6px;
113
113
+
align-items: center;
114
114
+
gap: 8px;
115
115
+
flex-wrap: wrap;
116
116
+
}
117
117
+
118
118
+
.atmosphere-filter-btn-group {
119
119
+
display: inline-flex;
120
120
+
align-items: center;
121
121
+
gap: 2px;
114
122
}
115
123
116
116
-
.atmosphere-filter-title-row {
124
124
+
.atmosphere-active-chips-row {
117
125
display: flex;
118
118
-
align-items: center;
126
126
+
flex-wrap: wrap;
119
127
gap: 6px;
120
120
-
margin-bottom: 2px;
121
128
}
122
129
123
130
···
1224
1231
gap: 12px;
1225
1232
}
1226
1233
1227
1227
-
.atmosphere-filter-section {
1228
1228
-
margin-bottom: 12px;
1234
1234
+
.atmosphere-filter-toolbar {
1235
1235
+
flex-wrap: wrap;
1236
1236
+
gap: 6px;
1229
1237
}
1230
1238
}
1231
1239
···
1590
1598
height: 14px;
1591
1599
}
1592
1600
1593
1593
-
.atmosphere-filters .search-input-container {
1594
1594
-
width: 180px;
1595
1595
-
margin: 0 auto;
1601
1601
+
.atmosphere-filter-toolbar .search-input-container {
1602
1602
+
width: 160px;
1603
1603
+
margin-left: auto;
1604
1604
+
}
1605
1605
+
1606
1606
+
@container (max-width: 380px) {
1607
1607
+
.atmosphere-filter-toolbar .search-input-container {
1608
1608
+
margin: 0 auto;
1609
1609
+
}
1596
1610
}