Skip to content

Commit eaebcdd

Browse files
committed
feat: template management system and folder suggest improvements
1 parent 3ca7dfe commit eaebcdd

File tree

12 files changed

+403
-68
lines changed

12 files changed

+403
-68
lines changed

.claude/settings.local.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(npx tsc:*)",
5+
"Bash(cd:*)",
6+
"Bash(npx eslint:*)",
7+
"Bash(npm run build:*)"
8+
]
9+
}
10+
}

docs/releases/2.1.8.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## 2.1.8
2+
3+
### Fixed
4+
5+
- Fixed folder selection not persisting after selecting from suggestions by dispatching proper DOM events
6+
- Fixed template name modal not receiving input focus by using Obsidian's native Modal class
7+
- Fixed ESLint "unsafe assignment" warnings in reader-view.ts with proper type annotations
8+
- Removed manual event listener management in discover-view.ts (using Obsidian's registerDomEvent)
9+
- Fixed folder suggestions showing RSS sidebar folders for article save path (now shows vault folders)
10+
11+
### Added
12+
13+
- Template management system for article saving:
14+
- "Reset to default" button to restore the default article template
15+
- "Save as template" button to save current template with a custom name
16+
- Saved templates section with Load, Update, and Delete functionality
17+
- Per-feed template selection in Feed Manager modal
18+
- Feeds can now have a custom template assigned
19+
- Template is resolved at save time from saved templates
20+
- VaultFolderSuggest component for suggesting folders from the entire vault
21+
22+
### Changed
23+
24+
- Article save path setting now suggests folders from the vault instead of RSS sidebar folders
25+
- YouTube and Podcast folder settings continue to suggest RSS sidebar folders
26+
- Improved type safety with explicit ArticleSavingSettings and SavedTemplate types

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "rss-dashboard",
33
"name": "RSS Dashboard",
4-
"version": "2.1.7",
4+
"version": "2.1.8",
55
"minAppVersion": "1.1.0",
66
"description": "A dashboard for organizing and consuming RSS feeds, YouTube channels, and podcasts with smart tagging, media playback, and seamless content flow.",
77
"author": "Aditya Amatya",

src/components/folder-suggest.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,56 @@
1-
import { AbstractInputSuggest, App } from "obsidian";
1+
import { AbstractInputSuggest, App, TFolder } from "obsidian";
22
import type { Folder } from "../types/types";
33

44
/**
5-
* Provides type-ahead folder suggestions for text inputs
5+
* Provides type-ahead folder suggestions from the vault
6+
*/
7+
export class VaultFolderSuggest extends AbstractInputSuggest<TFolder> {
8+
private inputEl: HTMLInputElement;
9+
10+
constructor(app: App, inputEl: HTMLInputElement) {
11+
super(app, inputEl);
12+
this.inputEl = inputEl;
13+
}
14+
15+
protected getSuggestions(query: string): TFolder[] {
16+
const lowerQuery = query.toLowerCase();
17+
const folders: TFolder[] = [];
18+
19+
const rootFolder = this.app.vault.getRoot();
20+
this.collectFolders(rootFolder, folders);
21+
22+
return folders.filter(folder =>
23+
folder.path.toLowerCase().includes(lowerQuery)
24+
);
25+
}
26+
27+
private collectFolders(folder: TFolder, result: TFolder[]): void {
28+
for (const child of folder.children) {
29+
if (child instanceof TFolder) {
30+
result.push(child);
31+
this.collectFolders(child, result);
32+
}
33+
}
34+
}
35+
36+
public renderSuggestion(folder: TFolder, el: HTMLElement): void {
37+
el.setText(folder.path);
38+
}
39+
40+
public selectSuggestion(folder: TFolder, _evt: MouseEvent | KeyboardEvent): void {
41+
this.inputEl.value = folder.path;
42+
this.inputEl.dispatchEvent(new Event("input", { bubbles: true }));
43+
this.inputEl.dispatchEvent(new Event("change", { bubbles: true }));
44+
this.close();
45+
}
46+
}
47+
48+
/**
49+
* Provides type-ahead folder suggestions for RSS sidebar folders
650
*/
751
export class FolderSuggest extends AbstractInputSuggest<string> {
852
private folders: string[];
53+
private inputEl: HTMLInputElement;
954

1055
/**
1156
* Collects all folder paths from the folder tree
@@ -24,6 +69,7 @@ export class FolderSuggest extends AbstractInputSuggest<string> {
2469

2570
constructor(app: App, inputEl: HTMLInputElement, folders: Folder[]) {
2671
super(app, inputEl);
72+
this.inputEl = inputEl;
2773
this.folders = this.collectAllFolders(folders);
2874
}
2975

@@ -54,8 +100,10 @@ export class FolderSuggest extends AbstractInputSuggest<string> {
54100
/**
55101
* Called when a folder is selected
56102
*/
57-
public selectSuggestion(folder: string): void {
58-
this.setValue(folder);
103+
public selectSuggestion(folder: string, _evt: MouseEvent | KeyboardEvent): void {
104+
this.inputEl.value = folder;
105+
this.inputEl.dispatchEvent(new Event("input", { bubbles: true }));
106+
this.inputEl.dispatchEvent(new Event("change", { bubbles: true }));
59107
this.close();
60108
}
61109
}

src/modals/feed-manager-modal.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Modal, App, Setting, Notice, requestUrl } from "obsidian";
22
import type RssDashboardPlugin from "../../main";
3-
import type { Feed, Folder } from "../types/types";
3+
import type { Feed, Folder, SavedTemplate } from "../types/types";
44
import { FolderSuggest } from "../components/folder-suggest";
55

66
function collectAllFolders(folders: Folder[], base = ""): string[] {
@@ -291,6 +291,24 @@ export class EditFeedModal extends Modal {
291291
});
292292
});
293293

294+
// Template selection
295+
let customTemplate = this.feed.customTemplate || "";
296+
const savedTemplates = this.plugin.settings.articleSaving.savedTemplates || [];
297+
298+
new Setting(contentEl)
299+
.setName("Article template")
300+
.setDesc("Select a template to use when saving articles from this feed")
301+
.addDropdown(dropdown => {
302+
dropdown.addOption("", "Use default template");
303+
savedTemplates.forEach((template: SavedTemplate) => {
304+
dropdown.addOption(template.id, template.name);
305+
});
306+
dropdown.setValue(customTemplate);
307+
dropdown.onChange(value => {
308+
customTemplate = value;
309+
});
310+
});
311+
294312
const btns = contentEl.createDiv("rss-dashboard-modal-buttons");
295313
const saveBtn = btns.createEl("button", { text: "Save", cls: "rss-dashboard-primary-button" });
296314
const cancelBtn = btns.createEl("button", { text: "Cancel" });
@@ -313,8 +331,8 @@ export class EditFeedModal extends Modal {
313331

314332
this.feed.maxItemsLimit = newMaxItemsLimit;
315333
this.feed.scanInterval = scanInterval;
316-
317-
334+
this.feed.customTemplate = customTemplate || undefined;
335+
318336
if (newMaxItemsLimit > 0 && this.feed.items.length > newMaxItemsLimit) {
319337

320338
this.feed.items.sort((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime());

0 commit comments

Comments
 (0)