Initial commit

This commit is contained in:
Grayson Patterson 2026-04-23 14:07:13 -04:00
commit fbcf455e0f
6 changed files with 234 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.DS_Store
node_modules/
data.json

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Grayson Patterson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

71
README.md Normal file
View file

@ -0,0 +1,71 @@
# Real Tasks
An Obsidian plugin that deletes task lines shortly after their checkbox is ticked. Your notes keep only the tasks that still need doing — no graveyard of strikethroughs, no manual cleanup.
## Why
Most task workflows leave completed tasks in the file as `- [x]` lines. That's great if you want a historical log; it's noise if you want your notes to function like a todo app. Real Tasks removes that noise automatically while preserving undo.
## How it works
- When you check off a task in the editor, Real Tasks waits **800 ms**, then removes the whole line via the editor API. Undo (`Cmd/Ctrl-Z`) restores it.
- When a task gets completed programmatically — for example by Dataview checkbox clicks in an embedded query, or by a sync client — Real Tasks catches the `vault.modify` event, waits **400 ms**, and rewrites the file with the completed lines stripped out.
- A ribbon button and command insert a new `- [ ] ` task line at the cursor, ready to type.
## Features
- Auto-delete completed task lines (`- [x]`, `- [X]`, `* [x]`, `+ [x]`).
- Short delay after completion to allow for accidental clicks (`Cmd/Ctrl-Z` to recover).
- Works from both the editor and external writes (Dataview, sync, external tools).
- Ribbon icon and command **Insert new task** for quick task creation.
- Command **Sweep completed tasks in this note now** for ad-hoc cleanup.
- Desktop and mobile support.
## Usage
1. Install and enable the plugin.
2. Write tasks as normal: `- [ ] something to do`.
3. Check the box. ~1 second later the line vanishes.
4. Accidentally checked? Press `Cmd/Ctrl-Z` within the delay window, or immediately after, to restore the line.
5. Tap the ribbon icon (a check-square) to insert a new task at your cursor. Optionally bind `Insert new task` to a hotkey in Settings → Hotkeys.
## Commands
| Command | Description |
| ---------------------------------------------- | ----------------------------------------------------------- |
| `Real Tasks: Insert new task` | Insert a `- [ ] ` line at the cursor. |
| `Real Tasks: Sweep completed tasks in this note now` | Immediately remove all completed tasks from the active note. |
## Compatibility
- Tested on Obsidian `1.4.0` and above.
- Desktop and mobile (iOS, Android).
- Compatible with Dataview `TASK` queries — checking a task in an embedded Dataview result triggers the auto-delete.
## Installation
### Community plugins (once published)
1. Open **Settings → Community plugins**.
2. Search for **Real Tasks**.
3. Install and enable.
### Manual install
1. Download `main.js` and `manifest.json` from the latest [release](https://github.com/cryptic0011/real-tasks/releases).
2. Create a folder `<VAULT>/.obsidian/plugins/real-tasks/`.
3. Drop both files inside.
4. Enable the plugin in **Settings → Community plugins**.
## Settings
No settings page in this release. Everything runs on sensible defaults. Future versions may expose the delete delay and checkbox filters.
## Development
- `main.js` is hand-written plain JavaScript — no build step, no bundler, no TypeScript.
- To modify locally, edit `main.js` then reload the plugin via **Settings → Community plugins** toggle, or restart Obsidian.
## License
[MIT](LICENSE)

126
main.js Normal file
View file

@ -0,0 +1,126 @@
"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);
}
}
};

10
manifest.json Normal file
View file

@ -0,0 +1,10 @@
{
"id": "real-tasks",
"name": "Real Tasks",
"version": "1.0.0",
"minAppVersion": "1.4.0",
"description": "Deletes task lines shortly after their checkbox is marked done so your notes only keep real, open work. Includes a ribbon button for quickly inserting a new task.",
"author": "Grayson Patterson",
"authorUrl": "https://github.com/YOUR-GITHUB-USERNAME",
"isDesktopOnly": false
}

3
versions.json Normal file
View file

@ -0,0 +1,3 @@
{
"1.0.0": "1.4.0"
}