debouncing, update list logic, fix bug with removed tags causing list to disappear
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
const { ItemView, Plugin, TFile, MarkdownView } = require("obsidian");
|
const { ItemView, Plugin, TFile, debounce, MarkdownView } = require("obsidian");
|
||||||
|
|
||||||
const VIEW_TYPE = "org-todo-list-view";
|
const VIEW_TYPE = "org-todo-list-view";
|
||||||
|
|
||||||
@@ -58,9 +58,27 @@ class OrgTodoListView extends ItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onOpen() {
|
async onOpen() {
|
||||||
this.registerEvent(this.app.vault.on("modify", () => this.refresh()));
|
this.debouncedUpdate = debounce(
|
||||||
this.registerEvent(this.app.vault.on("delete", () => this.refresh()));
|
(file) => this.updateFile(file),
|
||||||
this.registerEvent(this.app.vault.on("rename", () => this.refresh()));
|
400,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fires AFTER the metadata cache re-parses — cache is fresh here.
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.metadataCache.on("changed", (file) => this.debouncedUpdate(file))
|
||||||
|
);
|
||||||
|
// Deletions: just drop the file's tasks, nothing to reparse.
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.vault.on("delete", (file) => this.removeFile(file.path))
|
||||||
|
);
|
||||||
|
// Rename: remove old-path entries, rescan under the new path.
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.vault.on("rename", (file, oldPath) => {
|
||||||
|
this.removeFile(oldPath);
|
||||||
|
this.updateFile(file);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this.buildChrome();
|
this.buildChrome();
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
@@ -103,29 +121,23 @@ class OrgTodoListView extends ItemView {
|
|||||||
this.renderList();
|
this.renderList();
|
||||||
}
|
}
|
||||||
|
|
||||||
async scanVault() {
|
async scanFile(file) {
|
||||||
const out = [];
|
if (!(file instanceof TFile) || file.extension !== "md") return [];
|
||||||
const files = this.app.vault.getMarkdownFiles();
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const cache = this.app.metadataCache.getFileCache(file);
|
const cache = this.app.metadataCache.getFileCache(file);
|
||||||
const taskItems = cache?.listItems?.filter((li) => li.task !== undefined) ?? [];
|
const taskItems = cache?.listItems?.filter((li) => li.task !== undefined) ?? [];
|
||||||
|
if (taskItems.length === 0) return [];
|
||||||
|
|
||||||
// Cache-gating: no task list items means we never read the file.
|
|
||||||
if (taskItems.length === 0) continue;
|
|
||||||
|
|
||||||
// Build a quick lookup of tags by line, from the cache. tag includes the '#'.
|
|
||||||
const tagsByLine = new Map();
|
const tagsByLine = new Map();
|
||||||
for (const t of cache?.tags ?? []) {
|
for (const t of cache?.tags ?? []) {
|
||||||
const ln = t.position.start.line;
|
const ln = t.position.start.line;
|
||||||
if (!tagsByLine.has(ln)) tagsByLine.set(ln, []);
|
if (!tagsByLine.has(ln)) tagsByLine.set(ln, []);
|
||||||
// store without the leading '#'
|
|
||||||
tagsByLine.get(ln).push(t.tag.replace(/^#/, ""));
|
tagsByLine.get(ln).push(t.tag.replace(/^#/, ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the file once (only because dates + display text need the raw line).
|
|
||||||
const content = await this.app.vault.cachedRead(file);
|
const content = await this.app.vault.cachedRead(file);
|
||||||
const lines = content.split("\n");
|
const lines = content.split("\n");
|
||||||
|
const out = [];
|
||||||
|
|
||||||
for (const li of taskItems) {
|
for (const li of taskItems) {
|
||||||
const line = li.position.start.line;
|
const line = li.position.start.line;
|
||||||
@@ -133,30 +145,56 @@ class OrgTodoListView extends ItemView {
|
|||||||
const m = raw.match(TASK_RE);
|
const m = raw.match(TASK_RE);
|
||||||
const done = (li.task ?? " ") !== " ";
|
const done = (li.task ?? " ") !== " ";
|
||||||
const body = m ? m[2] : raw;
|
const body = m ? m[2] : raw;
|
||||||
|
|
||||||
// Tags straight from the cache for this line — no regex needed.
|
|
||||||
const tags = tagsByLine.get(line) ?? [];
|
const tags = tagsByLine.get(line) ?? [];
|
||||||
|
|
||||||
const doneDate = body.match(DONE_DATE_RE)?.[1] ?? null;
|
const doneDate = body.match(DONE_DATE_RE)?.[1] ?? null;
|
||||||
const dueDate = body.match(DUE_DATE_RE)?.[1] ?? null;
|
const dueDate = body.match(DUE_DATE_RE)?.[1] ?? null;
|
||||||
|
|
||||||
const text = body
|
const text = body
|
||||||
.replace(HASHTAG_RE, "")
|
.replace(HASHTAG_RE, "")
|
||||||
.replace(DONE_DATE_RE, "")
|
.replace(DONE_DATE_RE, "")
|
||||||
.replace(DUE_DATE_RE, "")
|
.replace(DUE_DATE_RE, "")
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
out.push({ text, raw, done, tags, doneDate, dueDate, file, line });
|
out.push({ text, raw, done, tags, doneDate, dueDate, file, line });
|
||||||
}
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
async scanVault() {
|
||||||
|
const out = [];
|
||||||
|
for (const file of this.app.vault.getMarkdownFiles()) {
|
||||||
|
const fileTasks = await this.scanFile(file);
|
||||||
|
for (const t of fileTasks) out.push(t);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all tasks belonging to a given path, then re-render.
|
||||||
|
removeFile(path) {
|
||||||
|
this.tasks = this.tasks.filter((t) => t.file.path !== path);
|
||||||
|
this.renderTagBar();
|
||||||
|
this.renderList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rescan one file and splice its tasks in, replacing its previous entries.
|
||||||
|
async updateFile(file) {
|
||||||
|
if (!(file instanceof TFile) || file.extension !== "md") return;
|
||||||
|
const fresh = await this.scanFile(file);
|
||||||
|
this.tasks = this.tasks.filter((t) => t.file.path !== file.path);
|
||||||
|
for (const t of fresh) this.tasks.push(t);
|
||||||
|
this.renderTagBar();
|
||||||
|
this.renderList();
|
||||||
|
}
|
||||||
|
|
||||||
renderTagBar() {
|
renderTagBar() {
|
||||||
this.tagBarEl.empty();
|
this.tagBarEl.empty();
|
||||||
const all = new Set();
|
const all = new Set();
|
||||||
for (const t of this.tasks) t.tags.forEach((x) => all.add(x));
|
for (const t of this.tasks) t.tags.forEach((x) => all.add(x));
|
||||||
|
|
||||||
|
// Drop any active filters whose tag no longer exists anywhere.
|
||||||
|
for (const tag of [...this.activeTags]) {
|
||||||
|
if (!all.has(tag)) this.activeTags.delete(tag);
|
||||||
|
}
|
||||||
|
|
||||||
const sorted = [...all].sort();
|
const sorted = [...all].sort();
|
||||||
for (const tag of sorted) {
|
for (const tag of sorted) {
|
||||||
const chip = this.tagBarEl.createEl("button", { text: "#" + tag });
|
const chip = this.tagBarEl.createEl("button", { text: "#" + tag });
|
||||||
|
|||||||
Reference in New Issue
Block a user