Skip to content

Commit 0793f78

Browse files
authored
Merge pull request #39 from joeriddles/27-nested-TODO-md
feat: nested, generated TODO.md
2 parents 7d77a56 + 456d86b commit 0793f78

4 files changed

Lines changed: 367 additions & 10 deletions

File tree

main.ts

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import TodoService, { Todo } from "src/todoService";
99

1010
export default class ExtendedTaskListsPlugin extends Plugin {
1111
settings!: ExtendedTaskListsSettings;
12+
private isUpdating = false;
1213

1314
async onload() {
1415
await this.loadSettings();
@@ -117,20 +118,83 @@ export default class ExtendedTaskListsPlugin extends Plugin {
117118
}
118119

119120
updateTodoFile = async () => {
121+
if (this.isUpdating) return;
122+
this.isUpdating = true;
123+
try {
124+
await this.doUpdateTodoFile();
125+
} finally {
126+
this.isUpdating = false;
127+
}
128+
};
129+
130+
private async doUpdateTodoFile() {
120131
const vault = this.app.vault;
121132
const fileService = new VaultFileService(vault);
122133
const service = new TodoService(fileService, this.settings);
123-
const todoFiles = await service.findTodosFiles();
124-
const todos: Todo[] = todoFiles
134+
const sourceFiles = await service.findTodosFiles();
135+
const allTodos: Todo[] = sourceFiles
125136
.map((todoFile) => {
126137
const todos = service.parseTodos(todoFile.contents);
127138
todos.forEach((todo) => (todo.file = todoFile.file));
128139
return todos;
129140
})
130141
.reduce((prev, cur) => prev.concat(cur), []);
131-
const todoFile = await this.getOrCreateTodoFile(vault);
132-
await service.saveTodos(todoFile as IFile, todos);
133-
};
142+
143+
if (!this.settings.enableNestedTodos) {
144+
const todoFile = await this.getOrCreateTodoFile(vault);
145+
await service.saveTodos(todoFile as IFile, allTodos);
146+
return;
147+
}
148+
149+
const allTodoTargets = await service.findAllTodoFiles();
150+
const isRootTodoFile = (f: IFile) =>
151+
f.path === this.settings.todoFilename ||
152+
f.path === "/" + this.settings.todoFilename;
153+
const rootTodoFile = allTodoTargets.find(isRootTodoFile);
154+
const nestedTodoFiles = allTodoTargets.filter(
155+
(f) => !isRootTodoFile(f),
156+
);
157+
158+
const claimedTodoKeys = new Set<string>();
159+
160+
nestedTodoFiles.sort((a, b) => {
161+
const depthA = a.path.split("/").length;
162+
const depthB = b.path.split("/").length;
163+
return depthB - depthA;
164+
});
165+
166+
for (const nestedTodo of nestedTodoFiles) {
167+
let scopedTodos = service.filterTodosByScope(
168+
allTodos,
169+
nestedTodo.path,
170+
);
171+
172+
if (this.settings.excludeNestedFromParent) {
173+
scopedTodos = scopedTodos.filter(
174+
(todo) =>
175+
!claimedTodoKeys.has(
176+
`${todo.file.path}:${todo.lineno}`,
177+
),
178+
);
179+
for (const todo of scopedTodos) {
180+
claimedTodoKeys.add(`${todo.file.path}:${todo.lineno}`);
181+
}
182+
}
183+
184+
await service.saveTodos(nestedTodo, scopedTodos);
185+
}
186+
187+
const rootFile =
188+
rootTodoFile ?? ((await this.getOrCreateTodoFile(vault)) as IFile);
189+
let rootTodos = allTodos;
190+
if (this.settings.excludeNestedFromParent && claimedTodoKeys.size > 0) {
191+
rootTodos = allTodos.filter(
192+
(todo) =>
193+
!claimedTodoKeys.has(`${todo.file.path}:${todo.lineno}`),
194+
);
195+
}
196+
await service.saveTodos(rootFile, rootTodos);
197+
}
134198

135199
onTodoFileUpdated = async (todoFile: TFile): Promise<boolean> => {
136200
const vault = this.app.vault;

src/settings.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ interface ExtendedTaskListsSettings {
44
todoFilename: string;
55
useFullFilepath: boolean;
66
useHierarchy: boolean;
7+
enableNestedTodos: boolean;
8+
excludeNestedFromParent: boolean;
79
excludeFilePattern: string;
810
excludeFolderFilename: string;
911
excludeRegionBegin: string;
@@ -18,6 +20,8 @@ const DEFAULT_SETTINGS: ExtendedTaskListsSettings = {
1820
todoFilename: "TODO.md",
1921
useFullFilepath: false,
2022
useHierarchy: false,
23+
enableNestedTodos: false,
24+
excludeNestedFromParent: true,
2125
excludeFilePattern: "<!-- exclude TODO -->",
2226
excludeFolderFilename: ".exclude_todos",
2327
excludeRegionBegin: "%% exclude: start %%",
@@ -85,6 +89,34 @@ class ExtendedTaskListsSettingTab extends PluginSettingTab {
8589
}),
8690
);
8791

92+
new Setting(containerEl)
93+
.setName("Enable nested TODOs")
94+
.setDesc(
95+
"When enabled, any TODO file you create in a subfolder will be populated with task items from that folder and its subfolders.",
96+
)
97+
.addToggle((toggle) =>
98+
toggle
99+
.setValue(this.plugin.settings.enableNestedTodos)
100+
.onChange(async (value) => {
101+
this.plugin.settings.enableNestedTodos = value;
102+
await this.plugin.saveSettings();
103+
}),
104+
);
105+
106+
new Setting(containerEl)
107+
.setName("Exclude nested from parent")
108+
.setDesc(
109+
"When enabled, task items that appear in a nested TODO file are excluded from ancestor TODO files to avoid duplication.",
110+
)
111+
.addToggle((toggle) =>
112+
toggle
113+
.setValue(this.plugin.settings.excludeNestedFromParent)
114+
.onChange(async (value) => {
115+
this.plugin.settings.excludeNestedFromParent = value;
116+
await this.plugin.saveSettings();
117+
}),
118+
);
119+
88120
new Setting(containerEl).setName("Excludes").setHeading();
89121

90122
new Setting(containerEl)

0 commit comments

Comments
 (0)