mirror of
https://github.com/cryptic0011/real-tasks.git
synced 2026-07-22 06:54:55 +00:00
126 lines
3.9 KiB
JavaScript
126 lines
3.9 KiB
JavaScript
"use strict";
|
|
|
|
const { Plugin, MarkdownView, Notice, TFile } = require("obsidian");
|
|
|
|
const TASK_PREFIX = "- [ ] ";
|
|
|
|
const EDITOR_DELAY_MS = 800;
|
|
const VAULT_DELAY_MS = 400;
|
|
const COMPLETED_TASK_RE = /^\s*[-*+]\s+\[[xX]\]\s/;
|
|
|
|
module.exports = class RealTasksPlugin extends Plugin {
|
|
onload() {
|
|
this._editorTimer = null;
|
|
this._vaultTimers = new Map(); // path -> timeout handle
|
|
this._suppressUntil = new Map(); // path -> timestamp we wrote the file
|
|
|
|
// Interactive typing path: preserves undo via the editor API.
|
|
this.registerEvent(
|
|
this.app.workspace.on("editor-change", (editor) => {
|
|
if (this._editorTimer) clearTimeout(this._editorTimer);
|
|
this._editorTimer = setTimeout(() => this.sweepEditor(editor), EDITOR_DELAY_MS);
|
|
})
|
|
);
|
|
|
|
// Programmatic writes (Dataview checkbox, sync, etc.) — use vault API.
|
|
this.registerEvent(
|
|
this.app.vault.on("modify", (file) => {
|
|
if (!(file instanceof TFile) || file.extension !== "md") return;
|
|
const until = this._suppressUntil.get(file.path) || 0;
|
|
if (Date.now() < until) return; // our own write bouncing back
|
|
|
|
const existing = this._vaultTimers.get(file.path);
|
|
if (existing) clearTimeout(existing);
|
|
const handle = setTimeout(() => {
|
|
this._vaultTimers.delete(file.path);
|
|
this.sweepFile(file);
|
|
}, VAULT_DELAY_MS);
|
|
this._vaultTimers.set(file.path, handle);
|
|
})
|
|
);
|
|
|
|
this.addCommand({
|
|
id: "sweep-now",
|
|
name: "Sweep completed tasks in this note now",
|
|
editorCallback: (editor) => this.sweepEditor(editor),
|
|
});
|
|
|
|
this.addCommand({
|
|
id: "insert-task",
|
|
name: "Insert new task",
|
|
editorCallback: (editor) => this.insertTask(editor),
|
|
});
|
|
|
|
this.addRibbonIcon("check-square", "Insert new task", () => {
|
|
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
|
if (!view) {
|
|
new Notice("Open a note first to insert a task.");
|
|
return;
|
|
}
|
|
this.insertTask(view.editor);
|
|
view.editor.focus();
|
|
});
|
|
}
|
|
|
|
onunload() {
|
|
if (this._editorTimer) clearTimeout(this._editorTimer);
|
|
for (const h of this._vaultTimers.values()) clearTimeout(h);
|
|
this._vaultTimers.clear();
|
|
}
|
|
|
|
insertTask(editor) {
|
|
if (!editor) return;
|
|
const cursor = editor.getCursor();
|
|
const currentLine = editor.getLine(cursor.line) || "";
|
|
const atLineStart = currentLine.trim() === "";
|
|
const prefix = atLineStart ? TASK_PREFIX : "\n" + TASK_PREFIX;
|
|
editor.replaceRange(prefix, cursor);
|
|
const newLine = atLineStart ? cursor.line : cursor.line + 1;
|
|
editor.setCursor({ line: newLine, ch: TASK_PREFIX.length });
|
|
}
|
|
|
|
sweepEditor(editor) {
|
|
if (!editor) return;
|
|
for (let i = editor.lineCount() - 1; i >= 0; i--) {
|
|
const line = editor.getLine(i);
|
|
if (line == null) continue;
|
|
if (!COMPLETED_TASK_RE.test(line)) continue;
|
|
|
|
const lastLine = i === editor.lineCount() - 1;
|
|
const from = { line: i, ch: 0 };
|
|
const to = lastLine
|
|
? { line: i, ch: line.length }
|
|
: { line: i + 1, ch: 0 };
|
|
editor.replaceRange("", from, to);
|
|
}
|
|
}
|
|
|
|
async sweepFile(file) {
|
|
const leaves = this.app.workspace.getLeavesOfType("markdown");
|
|
for (const leaf of leaves) {
|
|
const view = leaf.view;
|
|
if (view instanceof MarkdownView && view.file && view.file.path === file.path) {
|
|
this.sweepEditor(view.editor);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let content;
|
|
try {
|
|
content = await this.app.vault.read(file);
|
|
} catch {
|
|
return;
|
|
}
|
|
const lines = content.split("\n");
|
|
const kept = lines.filter((l) => !COMPLETED_TASK_RE.test(l));
|
|
if (kept.length === lines.length) return;
|
|
|
|
const next = kept.join("\n");
|
|
this._suppressUntil.set(file.path, Date.now() + 1500);
|
|
try {
|
|
await this.app.vault.modify(file, next);
|
|
} catch (e) {
|
|
console.warn("[real-tasks] vault write failed", file.path, e);
|
|
}
|
|
}
|
|
};
|