mirror of
https://github.com/selectstarfromusers/obsidian-task-manager.git
synced 2026-07-22 06:05:56 +00:00
fix: board-source sync, mobile context menu, collision handling
- Wire toggleDoneWithSync: checking task on board now checks source note - Add right-click/long-press context menu with "Move to..." bucket list - Warn when changing bucket property in settings - Handle stub filename collisions with random suffix - Document #task position and multiple wikilink behavior Co-authored-by: Isaac
This commit is contained in:
parent
2f8b72c4b2
commit
cb9adf3008
8 changed files with 82 additions and 7 deletions
|
|
@ -36,6 +36,8 @@ There are three ways to add tasks:
|
|||
```
|
||||
The task appears on the board automatically.
|
||||
|
||||
**Syntax notes**: The `#task` tag must be at the **end of the line** -- no text after it, or the plugin won't detect the task. If you include multiple `[[wikilinks]]`, only the first one is used for secondary grouping; the rest are stripped from the task text.
|
||||
|
||||
2. **From the board** -- Click **+ Add a task** at the bottom of any bucket. Type the task text, optionally pick a secondary group, and press Enter.
|
||||
|
||||
3. **Stub files** -- Create a markdown file in the configured task folder with the appropriate frontmatter. The plugin picks it up on the next scan.
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ export class InlineTaskWatcher {
|
|||
});
|
||||
}
|
||||
|
||||
private generateStubFilename(secondaryValue: string): string {
|
||||
private generateStubFilename(secondaryValue: string, folder: string): string {
|
||||
const now = new Date();
|
||||
const yyyy = now.getFullYear();
|
||||
const MM = String(now.getMonth() + 1).padStart(2, "0");
|
||||
|
|
@ -209,11 +209,20 @@ export class InlineTaskWatcher {
|
|||
const ss = String(now.getSeconds()).padStart(2, "0");
|
||||
const timestamp = `${yyyy}-${MM}-${dd}-${HH}${mm}${ss}`;
|
||||
|
||||
let baseName: string;
|
||||
if (secondaryValue) {
|
||||
const safe = secondaryValue.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").trim();
|
||||
return `${safe}-${timestamp}.md`;
|
||||
baseName = `${safe}-${timestamp}`;
|
||||
} else {
|
||||
baseName = `task-${timestamp}`;
|
||||
}
|
||||
return `task-${timestamp}.md`;
|
||||
|
||||
let fileName = `${baseName}.md`;
|
||||
if (this.app.vault.getAbstractFileByPath(`${folder}/${fileName}`)) {
|
||||
const suffix = Math.random().toString(36).substring(2, 5);
|
||||
fileName = `${baseName}-${suffix}.md`;
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private async createStub(task: TrackedTask, sourceFile: TFile): Promise<void> {
|
||||
|
|
@ -238,7 +247,7 @@ export class InlineTaskWatcher {
|
|||
|
||||
const defaultBucket = s.buckets.find((b) => b.id === s.defaultBucketId)?.name ?? "";
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
const fileName = this.generateStubFilename(task.secondaryValue);
|
||||
const fileName = this.generateStubFilename(task.secondaryValue, folder);
|
||||
const filePath = `${folder}/${fileName}`;
|
||||
|
||||
const content = [
|
||||
|
|
|
|||
|
|
@ -265,6 +265,36 @@ export class TaskStore {
|
|||
});
|
||||
}
|
||||
|
||||
async toggleDoneWithSync(file: TFile): Promise<void> {
|
||||
let newDone = false;
|
||||
await this.app.fileManager.processFrontMatter(file, (fm) => {
|
||||
fm.done = !fm.done;
|
||||
newDone = fm.done;
|
||||
});
|
||||
|
||||
// Sync checkbox back to the source note for inline tasks
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
const fm = cache?.frontmatter;
|
||||
if (fm?.source === "inline" && fm?.source_file) {
|
||||
const sourceFile = this.app.vault.getAbstractFileByPath(fm.source_file);
|
||||
if (sourceFile instanceof TFile) {
|
||||
const lineNum = fm.source_line;
|
||||
if (typeof lineNum === "number") {
|
||||
const content = await this.app.vault.read(sourceFile);
|
||||
const lines = content.split("\n");
|
||||
if (lineNum >= 0 && lineNum < lines.length) {
|
||||
if (newDone) {
|
||||
lines[lineNum] = lines[lineNum].replace(/\[ \]/, "[x]");
|
||||
} else {
|
||||
lines[lineNum] = lines[lineNum].replace(/\[[xX]\]/, "[ ]");
|
||||
}
|
||||
await this.app.vault.modify(sourceFile, lines.join("\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async moveToBucket(file: TFile, bucketName: string): Promise<void> {
|
||||
const settings = this.getSettings();
|
||||
// P1: Normalize property name to lowercase
|
||||
|
|
|
|||
|
|
@ -139,7 +139,12 @@ export class InlineCreator {
|
|||
const prefix = secondaryGroup
|
||||
? secondaryGroup.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").toLowerCase()
|
||||
: "task";
|
||||
const fileName = `${folder}/${prefix}-${dateStamp}-${timeStamp}.md`;
|
||||
let baseName = `${prefix}-${dateStamp}-${timeStamp}`;
|
||||
let fileName = `${folder}/${baseName}.md`;
|
||||
if (this.app.vault.getAbstractFileByPath(fileName)) {
|
||||
const suffix = Math.random().toString(36).substring(2, 5);
|
||||
fileName = `${folder}/${baseName}-${suffix}.md`;
|
||||
}
|
||||
|
||||
const today = now.toISOString().split("T")[0];
|
||||
const groupProp = s.secondaryGroupProperty;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,13 @@ export class TasksSettingTab extends PluginSettingTab {
|
|||
.setPlaceholder(DEFAULT_SETTINGS.bucketProperty)
|
||||
.setValue(this.plugin.settings.bucketProperty)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.bucketProperty = value.toLowerCase().trim();
|
||||
const newValue = value.toLowerCase().trim();
|
||||
if (newValue !== this.plugin.settings.bucketProperty) {
|
||||
new Notice(
|
||||
"Changing the bucket property will affect how tasks are grouped. Existing tasks may appear in 'Unclassified' until their frontmatter is updated."
|
||||
);
|
||||
}
|
||||
this.plugin.settings.bucketProperty = newValue;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export interface TaskRowCallbacks {
|
|||
onToggleDone(task: TaskItem): void;
|
||||
onDragStart(task: TaskItem, event: DragEvent): void;
|
||||
onClick(task: TaskItem): void;
|
||||
onContextMenu?(task: TaskItem, event: MouseEvent): void;
|
||||
}
|
||||
|
||||
const SOURCE_ICONS: Record<string, string> = {
|
||||
|
|
@ -144,6 +145,14 @@ export function createTaskRow(
|
|||
row.addEventListener("click", () => callbacks.onClick(task));
|
||||
row.addEventListener("dragstart", (e) => callbacks.onDragStart(task, e));
|
||||
|
||||
// Context menu for "Move to..." bucket
|
||||
row.addEventListener("contextmenu", (e) => {
|
||||
if (callbacks.onContextMenu) {
|
||||
e.preventDefault();
|
||||
callbacks.onContextMenu(task, e);
|
||||
}
|
||||
});
|
||||
|
||||
// P2: Keyboard handler — Enter/Space triggers toggle, arrow keys left to parent
|
||||
row.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export class TaskView extends ItemView {
|
|||
});
|
||||
|
||||
this.checkboxHandler = new CheckboxHandler({
|
||||
onToggleDone: (file) => this.store.toggleDone(file),
|
||||
onToggleDone: (file) => this.store.toggleDoneWithSync(file),
|
||||
});
|
||||
|
||||
this.inlineCreator = new InlineCreator(this.app, settings);
|
||||
|
|
@ -239,6 +239,15 @@ export class TaskView extends ItemView {
|
|||
onDrop: (event: DragEvent, bucketName: string) => {
|
||||
this.dragManager.handleDrop(event, bucketName);
|
||||
},
|
||||
onContextMenu: (task: TaskItem, event: MouseEvent) => {
|
||||
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))
|
||||
);
|
||||
}
|
||||
menu.showAtMouseEvent(event);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -498,6 +498,11 @@
|
|||
/* ============================================================
|
||||
MOBILE IMPROVEMENTS
|
||||
============================================================ */
|
||||
/* Enable long-press context menu on mobile */
|
||||
.ot-task-row {
|
||||
-webkit-touch-callout: default;
|
||||
}
|
||||
|
||||
@media (pointer: coarse) {
|
||||
.ot-task-row {
|
||||
min-height: 44px;
|
||||
|
|
|
|||
Loading…
Reference in a new issue