mirror of
https://github.com/selectstarfromusers/obsidian-task-manager.git
synced 2026-07-22 06:05:56 +00:00
v0.1.2: address ObsidianReviewBot findings
Why:
- Fix unhandled promises (void-prefixed calls in event listeners, setTimeout
callbacks, and sync handlers).
- Replace async-without-await signatures (loadTasks, onunload, onClose,
countTasksInBucket) with sync equivalents.
- Narrow nullish-coalesce stringify of keyof TaskItem to exclude TFile /
objects from "[object Object]" stringification.
- Replace innerHTML with DOM API in the empty-state renderer.
- Replace createEl("h2", ...) with setHeading() via Setting API for
settings sections.
- Move inline element.style.* assignments into styles.css classes.
- Use FileManager.trashFile() instead of Vault.delete() for stub cleanup.
- UI strings use sentence case ("Open tasks", "New bucket", "Secondary
grouping", "Getting started").
- Remove unused imports/bindings (BucketGroup, TaskRowCallbacks,
BucketConfig, originalOnComplete).
Co-authored-by: Isaac
This commit is contained in:
parent
b1a966534a
commit
c58cdf5638
12 changed files with 146 additions and 137 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "minimal-task-board",
|
||||
"name": "Minimal Task Board",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"minAppVersion": "1.10.0",
|
||||
"description": "A clean, minimal task board with customizable buckets, secondary grouping, and inline task tracking.",
|
||||
"author": "Art Malanok",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "minimal-task-board",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "node esbuild.config.mjs",
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ export class InlineTaskWatcher {
|
|||
}
|
||||
|
||||
start(): void {
|
||||
const ref = this.app.metadataCache.on("changed", (file, data, cache) => {
|
||||
this.onFileChanged(file, cache);
|
||||
const ref = this.app.metadataCache.on("changed", (file, _data, cache) => {
|
||||
void this.onFileChanged(file, cache);
|
||||
});
|
||||
this.eventRefs.push(ref);
|
||||
}
|
||||
|
|
@ -168,7 +168,7 @@ export class InlineTaskWatcher {
|
|||
// Delete stubs that no longer have a matching #task line
|
||||
for (const stub of existingStubs) {
|
||||
if (!matchedStubs.has(stub.path)) {
|
||||
await this.app.vault.delete(stub);
|
||||
await this.app.fileManager.trashFile(stub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export class TaskStore {
|
|||
this.completingPaths.delete(path);
|
||||
}
|
||||
|
||||
async loadTasks(): Promise<TaskItem[]> {
|
||||
loadTasks(): TaskItem[] {
|
||||
const settings = this.getSettings();
|
||||
const folder = settings.taskFolder;
|
||||
const bucketProp = settings.bucketProperty.toLowerCase();
|
||||
|
|
@ -210,7 +210,13 @@ export class TaskStore {
|
|||
const groupMap = new Map<string, TaskItem[]>();
|
||||
|
||||
for (const task of tasks) {
|
||||
const key = String(task[prop] ?? "");
|
||||
const rawValue = task[prop];
|
||||
const key =
|
||||
typeof rawValue === "string"
|
||||
? rawValue
|
||||
: typeof rawValue === "number" || typeof rawValue === "boolean"
|
||||
? String(rawValue)
|
||||
: "";
|
||||
if (!groupMap.has(key)) {
|
||||
groupMap.set(key, []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,11 +62,12 @@ export class CheckboxHandler {
|
|||
|
||||
// Step 4: write frontmatter after 1700ms
|
||||
timeouts.push(
|
||||
window.setTimeout(async () => {
|
||||
window.setTimeout(() => {
|
||||
this.pendingTimeouts.delete(key);
|
||||
await this.callbacks.onToggleDone(task.file);
|
||||
// Signal animation end so the view can resume re-renders
|
||||
this.callbacks.onAnimationEnd?.();
|
||||
void this.callbacks.onToggleDone(task.file).then(() => {
|
||||
// Signal animation end so the view can resume re-renders
|
||||
this.callbacks.onAnimationEnd?.();
|
||||
});
|
||||
}, 1700)
|
||||
);
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ export class CheckboxHandler {
|
|||
rowElement.classList.remove("done", "completing");
|
||||
checkbox?.classList.remove("checked");
|
||||
|
||||
this.callbacks.onToggleDone(task.file);
|
||||
void this.callbacks.onToggleDone(task.file);
|
||||
}
|
||||
|
||||
private cancelPending(key: string): void {
|
||||
|
|
|
|||
|
|
@ -61,16 +61,17 @@ export class InlineCreator {
|
|||
wrapper.appendChild(datalist);
|
||||
|
||||
// Keyboard handlers
|
||||
const handleKeydown = async (e: KeyboardEvent) => {
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && actionInput.value.trim()) {
|
||||
await this.createTask(
|
||||
void this.createTask(
|
||||
actionInput.value.trim(),
|
||||
bucketName,
|
||||
groupInput.value.trim()
|
||||
);
|
||||
actionInput.value = "";
|
||||
groupInput.value = "";
|
||||
actionInput.focus();
|
||||
).then(() => {
|
||||
actionInput.value = "";
|
||||
groupInput.value = "";
|
||||
actionInput.focus();
|
||||
});
|
||||
} else if (e.key === "Escape") {
|
||||
wrapper.remove();
|
||||
}
|
||||
|
|
|
|||
49
src/main.ts
49
src/main.ts
|
|
@ -13,106 +13,91 @@ export default class TasksPlugin extends Plugin {
|
|||
async onload(): Promise<void> {
|
||||
await this.loadSettings();
|
||||
|
||||
// Initialize the task store
|
||||
this.store = new TaskStore(this.app, () => this.settings);
|
||||
|
||||
// Register the custom view
|
||||
this.registerView(TASKS_VIEW_TYPE, (leaf: WorkspaceLeaf) => {
|
||||
return new TaskView(leaf, () => this.settings, this.store!);
|
||||
});
|
||||
|
||||
// Add ribbon icon to open the task view
|
||||
this.addRibbonIcon("square-check-big", "Open Tasks", () => {
|
||||
this.activateView();
|
||||
this.addRibbonIcon("square-check-big", "Open tasks", () => {
|
||||
void this.activateView();
|
||||
});
|
||||
|
||||
// Add command to open task view
|
||||
this.addCommand({
|
||||
id: "open-tasks-view",
|
||||
name: "Open task board",
|
||||
callback: () => {
|
||||
this.activateView();
|
||||
void this.activateView();
|
||||
},
|
||||
});
|
||||
|
||||
// Add command to toggle between focus and board mode
|
||||
this.addCommand({
|
||||
id: "toggle-tasks-mode",
|
||||
name: "Toggle focus/board mode",
|
||||
callback: () => {
|
||||
// Toggle is handled within the view
|
||||
this.activateView();
|
||||
void this.activateView();
|
||||
},
|
||||
});
|
||||
|
||||
// Register settings tab
|
||||
this.addSettingTab(new TasksSettingTab(this.app, this));
|
||||
|
||||
// Start inline task watcher
|
||||
this.watcher = new InlineTaskWatcher(this.app, () => this.settings);
|
||||
this.watcher.start();
|
||||
|
||||
// Clean up orphaned stubs when source files are deleted
|
||||
this.registerEvent(
|
||||
this.app.vault.on("delete", async (file: TAbstractFile) => {
|
||||
this.app.vault.on("delete", (file: TAbstractFile) => {
|
||||
if (!(file instanceof TFile)) return;
|
||||
const taskFolder = this.settings.taskFolder;
|
||||
// Only act on non-stub files (files outside the task folder)
|
||||
if (file.path.startsWith(taskFolder + "/")) return;
|
||||
const stubs = this.store?.getStubsForSource(file.path) ?? [];
|
||||
for (const stub of stubs) {
|
||||
await this.app.vault.delete(stub);
|
||||
void this.app.fileManager.trashFile(stub);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Update source_file in stubs when source files are renamed
|
||||
this.registerEvent(
|
||||
this.app.vault.on("rename", async (file: TAbstractFile, oldPath: string) => {
|
||||
this.app.vault.on("rename", (file: TAbstractFile, oldPath: string) => {
|
||||
if (!(file instanceof TFile)) return;
|
||||
const taskFolder = this.settings.taskFolder;
|
||||
// Only act on non-stub files (files outside the task folder)
|
||||
if (file.path.startsWith(taskFolder + "/")) return;
|
||||
const stubs = this.store?.getStubsForSource(oldPath) ?? [];
|
||||
for (const stub of stubs) {
|
||||
await this.app.fileManager.processFrontMatter(stub, (fm) => {
|
||||
void this.app.fileManager.processFrontMatter(stub, (fm) => {
|
||||
fm.source_file = file.path;
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// After vault is ready: ensure folder exists, then load tasks
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
await this.ensureTaskFolder();
|
||||
await this.store?.loadTasks();
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
void (async () => {
|
||||
await this.ensureTaskFolder();
|
||||
this.store?.loadTasks();
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
async onunload(): Promise<void> {
|
||||
onunload(): void {
|
||||
this.watcher?.destroy();
|
||||
this.store?.destroy();
|
||||
}
|
||||
|
||||
private async ensureTaskFolder(): Promise<void> {
|
||||
let folder = this.settings.taskFolder;
|
||||
const folder = this.settings.taskFolder;
|
||||
|
||||
// Check if the configured folder already exists
|
||||
if (this.app.vault.getAbstractFileByPath(folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try the default name at vault root
|
||||
const defaultName = "_Tasks";
|
||||
if (folder === defaultName || !this.app.vault.getAbstractFileByPath(defaultName)) {
|
||||
// Use the default name
|
||||
await this.app.vault.createFolder(defaultName);
|
||||
this.settings.taskFolder = defaultName;
|
||||
await this.saveSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
// Default name is taken — append timestamp
|
||||
const now = new Date();
|
||||
const stamp = [
|
||||
now.getFullYear(),
|
||||
|
|
@ -131,12 +116,10 @@ export default class TasksPlugin extends Plugin {
|
|||
private async activateView(): Promise<void> {
|
||||
const existing = this.app.workspace.getLeavesOfType(TASKS_VIEW_TYPE);
|
||||
if (existing.length > 0) {
|
||||
// Focus existing view
|
||||
this.app.workspace.revealLeaf(existing[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open in a new leaf
|
||||
const leaf = this.app.workspace.getLeaf("tab");
|
||||
await leaf.setViewState({
|
||||
type: TASKS_VIEW_TYPE,
|
||||
|
|
@ -148,7 +131,6 @@ export default class TasksPlugin extends Plugin {
|
|||
async loadSettings(): Promise<void> {
|
||||
const loaded = await this.loadData();
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, loaded);
|
||||
// Ensure nested objects are merged properly
|
||||
if (loaded?.display) {
|
||||
this.settings.display = Object.assign(
|
||||
{},
|
||||
|
|
@ -163,7 +145,6 @@ export default class TasksPlugin extends Plugin {
|
|||
|
||||
async saveSettings(): Promise<void> {
|
||||
await this.saveData(this.settings);
|
||||
// Reload tasks to pick up any config changes
|
||||
this.store?.loadTasks();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
121
src/settings.ts
121
src/settings.ts
|
|
@ -18,8 +18,7 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
// ── General ──────────────────────────────────────────────
|
||||
containerEl.createEl("h2", { text: "General" });
|
||||
new Setting(containerEl).setName("General").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Task folder")
|
||||
|
|
@ -61,8 +60,7 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
// ── Buckets ──────────────────────────────────────────────
|
||||
containerEl.createEl("h2", { text: "Buckets" });
|
||||
new Setting(containerEl).setName("Buckets").setHeading();
|
||||
|
||||
const bucketPropertySetting = new Setting(containerEl)
|
||||
.setName("Group by property")
|
||||
|
|
@ -87,7 +85,6 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
text: "Property names are case-sensitive in frontmatter. Use lowercase.",
|
||||
});
|
||||
|
||||
// Bucket list (custom DOM)
|
||||
const bucketSection = containerEl.createDiv({ cls: "tasks-bucket-list" });
|
||||
bucketSection.createEl("div", {
|
||||
text: "Buckets",
|
||||
|
|
@ -102,58 +99,54 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
|
||||
this.plugin.settings.buckets.forEach((bucket, index) => {
|
||||
const row = listEl.createDiv({ cls: "tasks-bucket-row" });
|
||||
row.style.display = "flex";
|
||||
row.style.alignItems = "center";
|
||||
row.style.gap = "6px";
|
||||
row.style.marginBottom = "4px";
|
||||
|
||||
// Up arrow
|
||||
const upBtn = row.createEl("button", { text: "\u2191" });
|
||||
upBtn.setAttribute("aria-label", "Move up");
|
||||
upBtn.disabled = index === 0;
|
||||
upBtn.addEventListener("click", async () => {
|
||||
this.swapBuckets(index, index - 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
upBtn.addEventListener("click", () => {
|
||||
void (async () => {
|
||||
this.swapBuckets(index, index - 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
})();
|
||||
});
|
||||
|
||||
// Down arrow
|
||||
const downBtn = row.createEl("button", { text: "\u2193" });
|
||||
downBtn.setAttribute("aria-label", "Move down");
|
||||
downBtn.disabled = index === this.plugin.settings.buckets.length - 1;
|
||||
downBtn.addEventListener("click", async () => {
|
||||
this.swapBuckets(index, index + 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
downBtn.addEventListener("click", () => {
|
||||
void (async () => {
|
||||
this.swapBuckets(index, index + 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
})();
|
||||
});
|
||||
|
||||
// Name input
|
||||
const input = row.createEl("input", { type: "text" });
|
||||
input.value = bucket.name;
|
||||
input.style.flex = "1";
|
||||
input.addEventListener("change", async () => {
|
||||
const newName = input.value.trim();
|
||||
const duplicate = this.plugin.settings.buckets.some(
|
||||
(b, i) => i !== index && b.name === newName
|
||||
);
|
||||
if (duplicate) {
|
||||
new Notice("A bucket with that name already exists.");
|
||||
input.value = bucket.name;
|
||||
return;
|
||||
}
|
||||
bucket.name = newName;
|
||||
await this.plugin.saveSettings();
|
||||
input.addEventListener("change", () => {
|
||||
void (async () => {
|
||||
const newName = input.value.trim();
|
||||
const duplicate = this.plugin.settings.buckets.some(
|
||||
(b, i) => i !== index && b.name === newName
|
||||
);
|
||||
if (duplicate) {
|
||||
new Notice("A bucket with that name already exists.");
|
||||
input.value = bucket.name;
|
||||
return;
|
||||
}
|
||||
bucket.name = newName;
|
||||
await this.plugin.saveSettings();
|
||||
})();
|
||||
});
|
||||
|
||||
// Delete button with confirmation
|
||||
const delBtn = row.createEl("button", { text: "\u00d7" });
|
||||
delBtn.setAttribute("aria-label", "Delete bucket");
|
||||
let confirmPending = false;
|
||||
let confirmTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
delBtn.addEventListener("click", async () => {
|
||||
delBtn.addEventListener("click", () => {
|
||||
if (!confirmPending) {
|
||||
// First click: count tasks and show confirmation state
|
||||
const taskCount = await this.countTasksInBucket(bucket);
|
||||
const taskCount = this.countTasksInBucket(bucket);
|
||||
delBtn.textContent = `Confirm? (${taskCount} task${taskCount !== 1 ? "s" : ""})`;
|
||||
confirmPending = true;
|
||||
confirmTimeout = setTimeout(() => {
|
||||
|
|
@ -162,7 +155,6 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
}, 3000);
|
||||
return;
|
||||
}
|
||||
// Second click: actually delete
|
||||
if (confirmTimeout) clearTimeout(confirmTimeout);
|
||||
this.plugin.settings.buckets.splice(index, 1);
|
||||
this.plugin.settings.buckets.forEach((b, i) => (b.sortOrder = i));
|
||||
|
|
@ -172,26 +164,30 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
? this.plugin.settings.buckets[0].id
|
||||
: "";
|
||||
}
|
||||
void (async () => {
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
const addBtn = bucketSection.createEl("button", {
|
||||
text: "+ Add bucket",
|
||||
cls: "tasks-bucket-add-btn",
|
||||
});
|
||||
addBtn.addEventListener("click", () => {
|
||||
void (async () => {
|
||||
const id = "bucket_" + Date.now().toString(36);
|
||||
this.plugin.settings.buckets.push({
|
||||
id,
|
||||
name: "New bucket",
|
||||
sortOrder: this.plugin.settings.buckets.length,
|
||||
});
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
||||
// Add bucket button
|
||||
const addBtn = bucketSection.createEl("button", { text: "+ Add bucket" });
|
||||
addBtn.style.marginTop = "6px";
|
||||
addBtn.addEventListener("click", async () => {
|
||||
const id = "bucket_" + Date.now().toString(36);
|
||||
this.plugin.settings.buckets.push({
|
||||
id,
|
||||
name: "New Bucket",
|
||||
sortOrder: this.plugin.settings.buckets.length,
|
||||
});
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
});
|
||||
|
||||
// Default bucket dropdown
|
||||
new Setting(containerEl)
|
||||
.setName("Default bucket for new tasks")
|
||||
.setDesc("Bucket assigned to tasks that don't match any other.")
|
||||
|
|
@ -218,8 +214,7 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
// ── Secondary Grouping ───────────────────────────────────
|
||||
containerEl.createEl("h2", { text: "Secondary Grouping" });
|
||||
new Setting(containerEl).setName("Secondary grouping").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Enable sub-groups")
|
||||
|
|
@ -265,8 +260,7 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
);
|
||||
}
|
||||
|
||||
// ── Display ──────────────────────────────────────────────
|
||||
containerEl.createEl("h2", { text: "Display" });
|
||||
new Setting(containerEl).setName("Display").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Show due date")
|
||||
|
|
@ -334,11 +328,10 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
// ── Getting Started ──────────────────────────────────────
|
||||
containerEl.createEl("h2", { text: "Getting Started" });
|
||||
new Setting(containerEl).setName("Getting started").setHeading();
|
||||
|
||||
containerEl.createEl("p", {
|
||||
text: 'Add #task to any checkbox in your notes: - [ ] Do something #task',
|
||||
text: "Add #task to any checkbox in your notes: - [ ] Do something #task",
|
||||
cls: "setting-item-description",
|
||||
});
|
||||
containerEl.createEl("p", {
|
||||
|
|
@ -361,11 +354,7 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
buckets.forEach((b, i) => (b.sortOrder = i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Count how many task files in the _Tasks/ folder have a bucket property
|
||||
* matching the given bucket's name.
|
||||
*/
|
||||
private async countTasksInBucket(bucket: BucketConfig): Promise<number> {
|
||||
private countTasksInBucket(bucket: BucketConfig): number {
|
||||
const folder = this.plugin.settings.taskFolder;
|
||||
const property = this.plugin.settings.bucketProperty;
|
||||
const files = this.app.vault.getFiles().filter(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BucketGroup, DisplayConfig, BucketConfig, CLS } from "../types";
|
||||
import { BucketGroup, DisplayConfig, CLS } from "../types";
|
||||
import { createBucket, BucketCallbacks } from "./bucketComponent";
|
||||
|
||||
export interface FocusRendererConfig {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@ import {
|
|||
CLS,
|
||||
TaskItem,
|
||||
TasksPluginSettings,
|
||||
BucketGroup,
|
||||
} from "../types";
|
||||
import { TaskStore } from "../data/taskStore";
|
||||
import { FocusRenderer } from "./focusRenderer";
|
||||
import { BoardRenderer } from "./boardRenderer";
|
||||
import { DragManager } from "../interactions/dragManager";
|
||||
import { CheckboxHandler } from "../interactions/checkboxHandler";
|
||||
import { TaskRowCallbacks } from "./taskRow";
|
||||
import { BucketCallbacks } from "./bucketComponent";
|
||||
import { InlineCreator } from "../interactions/inlineCreator";
|
||||
|
||||
|
|
@ -38,8 +36,12 @@ export class TaskView extends ItemView {
|
|||
this.currentMode = settings().defaultView;
|
||||
|
||||
this.dragManager = new DragManager({
|
||||
onMoveToBucket: (file, newBucket) => this.store.moveToBucket(file, newBucket),
|
||||
onReorder: (file, order) => this.store.reorder(file, order),
|
||||
onMoveToBucket: (file, newBucket) => {
|
||||
void this.store.moveToBucket(file, newBucket);
|
||||
},
|
||||
onReorder: (file, order) => {
|
||||
void this.store.reorder(file, order);
|
||||
},
|
||||
});
|
||||
|
||||
this.checkboxHandler = new CheckboxHandler({
|
||||
|
|
@ -90,7 +92,7 @@ export class TaskView extends ItemView {
|
|||
const contentArea = this.rootEl.createDiv({ cls: `${CLS}-content` });
|
||||
|
||||
// Load tasks first, then render
|
||||
await this.store.loadTasks();
|
||||
this.store.loadTasks();
|
||||
this.renderView(contentArea);
|
||||
|
||||
// React to data changes (debounced to avoid re-render storms)
|
||||
|
|
@ -135,12 +137,22 @@ export class TaskView extends ItemView {
|
|||
}
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.className = `${CLS}-empty-state`;
|
||||
emptyState.innerHTML = [
|
||||
`<div class="${CLS}-empty-icon">\u2610</div>`,
|
||||
`<div class="${CLS}-empty-title">No tasks yet</div>`,
|
||||
`<div class="${CLS}-empty-hint">Add #task to any checkbox in your notes to see it here.</div>`,
|
||||
`<div class="${CLS}-empty-hint">Example: - [ ] Send report [[Project Alpha]] #task</div>`,
|
||||
].join("");
|
||||
const iconEl = document.createElement("div");
|
||||
iconEl.className = `${CLS}-empty-icon`;
|
||||
iconEl.textContent = "\u2610";
|
||||
emptyState.appendChild(iconEl);
|
||||
const titleEl = document.createElement("div");
|
||||
titleEl.className = `${CLS}-empty-title`;
|
||||
titleEl.textContent = "No tasks yet";
|
||||
emptyState.appendChild(titleEl);
|
||||
const hint1 = document.createElement("div");
|
||||
hint1.className = `${CLS}-empty-hint`;
|
||||
hint1.textContent = "Add #task to any checkbox in your notes to see it here.";
|
||||
emptyState.appendChild(hint1);
|
||||
const hint2 = document.createElement("div");
|
||||
hint2.className = `${CLS}-empty-hint`;
|
||||
hint2.textContent = "Example: - [ ] Send report [[Project Alpha]] #task";
|
||||
emptyState.appendChild(hint2);
|
||||
contentArea.appendChild(emptyState);
|
||||
return;
|
||||
}
|
||||
|
|
@ -211,7 +223,6 @@ export class TaskView extends ItemView {
|
|||
if (row) {
|
||||
// P0: Block re-renders during the completion animation
|
||||
this.animating = true;
|
||||
const originalOnComplete = this.checkboxHandler.handleToggle.bind(this.checkboxHandler);
|
||||
this.checkboxHandler.handleToggle(task, row);
|
||||
// The checkbox handler uses a 1700ms delay; set a timeout to clear the flag
|
||||
setTimeout(() => {
|
||||
|
|
@ -223,11 +234,9 @@ export class TaskView extends ItemView {
|
|||
this.dragManager.handleDragStart(task, event);
|
||||
},
|
||||
onClick: (task: TaskItem) => {
|
||||
// Open the source note or the task file in a new leaf
|
||||
const targetPath = task.sourceNote || task.file.path;
|
||||
const file = this.app.vault.getAbstractFileByPath(task.file.path);
|
||||
if (file instanceof TFile) {
|
||||
this.app.workspace.getLeaf("tab").openFile(file);
|
||||
void this.app.workspace.getLeaf("tab").openFile(file);
|
||||
}
|
||||
},
|
||||
onAddTask: (bucketName: string) => {
|
||||
|
|
@ -243,7 +252,9 @@ export class TaskView extends ItemView {
|
|||
const menu = new Menu();
|
||||
for (const bucket of this.settings().buckets) {
|
||||
menu.addItem((item) =>
|
||||
item.setTitle(bucket.name).onClick(() => this.store.moveToBucket(task.file, bucket.name))
|
||||
item.setTitle(bucket.name).onClick(() => {
|
||||
void this.store.moveToBucket(task.file, bucket.name);
|
||||
})
|
||||
);
|
||||
}
|
||||
menu.showAtMouseEvent(event);
|
||||
|
|
@ -265,9 +276,10 @@ export class TaskView extends ItemView {
|
|||
this.inlineCreator.showInput(contentArea, bucketName, existingGroups);
|
||||
}
|
||||
|
||||
async onClose(): Promise<void> {
|
||||
onClose(): Promise<void> {
|
||||
this.focusRenderer?.destroy();
|
||||
this.boardRenderer?.destroy();
|
||||
this.dragManager.destroy();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
styles.css
18
styles.css
|
|
@ -541,6 +541,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
SETTINGS — BUCKET LIST
|
||||
============================================================ */
|
||||
.tasks-bucket-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tasks-bucket-row input[type="text"] {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tasks-bucket-add-btn {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
INLINE CREATION — SECONDARY GROUP INPUT
|
||||
============================================================ */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"0.1.0": "1.10.0",
|
||||
"0.1.1": "1.10.0"
|
||||
"0.1.1": "1.10.0",
|
||||
"0.1.2": "1.10.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue