mirror of
https://github.com/heroblackink/ultimate-todoist-sync-for-obsidian.git
synced 2026-07-22 07:40:27 +00:00
fix: Full Vault Sync typing hijack — defer new task creation to line leave
Previously, lineContentNewTaskCheck fired on every keystroke in Full Vault Sync mode, creating a Todoist task after typing a single character. Now new task detection in Full Vault Sync is handled by lastLineNewTaskCheck, which only fires when the cursor leaves the line. lineContentNewTaskCheck is narrowed to only handle manually-tagged #todoist tasks. Fixes #110 (problem 4)
This commit is contained in:
parent
799bd13a71
commit
9f600b9e21
6 changed files with 99 additions and 9 deletions
|
|
@ -1,5 +1,12 @@
|
|||
## CHANGELOG
|
||||
|
||||
### [2.0.2] - 2026-03-17
|
||||
|
||||
#### Fixed
|
||||
- Full Vault Sync: new tasks are now created only when the cursor leaves the line, preventing the "typing hijack" bug where every keystroke triggered task creation
|
||||
|
||||
---
|
||||
|
||||
### [2.0.0] - 2026-03-15
|
||||
|
||||
#### Added
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ultimate-todoist-sync",
|
||||
"name": "Ultimate Todoist Sync",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"minAppVersion": "1.0.0",
|
||||
"description": "This is the best Todoist task synchronization plugin for Obsidian so far.",
|
||||
"author": "HeroBlackInk",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ultimate-todoist-sync",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -166,8 +166,9 @@ export class EventHandlers {
|
|||
if (!await this.plugin.syncLockManager.acquire('obsidianToTodoist')) return;
|
||||
try {
|
||||
await this.plugin.obsidianToTodoist!.lineModifiedTaskCheck(filepath as string, lastLineText, lastLine as number, fileContent);
|
||||
await this.plugin.obsidianToTodoist!.lastLineNewTaskCheck(filepath as string, lastLineText, lastLine as number, fileContent);
|
||||
} catch (error) {
|
||||
console.error(`An error occurred while check modified task in line text: ${error}`);
|
||||
console.error(`An error occurred while checking task on line leave: ${error}`);
|
||||
} finally {
|
||||
this.plugin.syncLockManager.release();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,11 +88,7 @@ export class ObsidianToTodoistSync {
|
|||
|
||||
const hasId = this.plugin.taskParser.hasTodoistId(linetxt);
|
||||
const hasTag = this.plugin.taskParser.hasTodoistTag(linetxt);
|
||||
const fullVault = this.plugin.settings.enableFullVaultSync;
|
||||
const isTask = this.plugin.taskParser.isMarkdownTask(linetxt);
|
||||
const contentNotEmpty = isTask && this.plugin.taskParser.getTaskContentFromLineText(linetxt) !== "";
|
||||
|
||||
const isNewTask = !hasId && (hasTag || (fullVault && contentNotEmpty));
|
||||
const isNewTask = !hasId && hasTag;
|
||||
|
||||
if (isNewTask) {
|
||||
const processedLine = hasTag ? linetxt : this.plugin.taskParser.addTodoistTag(linetxt);
|
||||
|
|
@ -169,6 +165,91 @@ export class ObsidianToTodoistSync {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect new tasks when the user leaves a line (Full Vault Sync only).
|
||||
* Unlike lineContentNewTaskCheck (which fires on every keystroke for #todoist),
|
||||
* this fires once when the cursor moves away, so the user can finish typing.
|
||||
*/
|
||||
async lastLineNewTaskCheck(filepath: string, lineText: string, lineNumber: number, fileContent: string): Promise<void> {
|
||||
if (!this.plugin.isPrimaryDevice()) return;
|
||||
if (!this.plugin.settings.enableFullVaultSync) return;
|
||||
|
||||
const isTask = this.plugin.taskParser.isMarkdownTask(lineText);
|
||||
if (!isTask) return;
|
||||
|
||||
const contentNotEmpty = this.plugin.taskParser.getTaskContentFromLineText(lineText) !== '';
|
||||
if (!contentNotEmpty) return;
|
||||
|
||||
const hasId = this.plugin.taskParser.hasTodoistId(lineText);
|
||||
if (hasId) return;
|
||||
|
||||
const hasTag = this.plugin.taskParser.hasTodoistTag(lineText);
|
||||
if (hasTag) return; // Already has #todoist — lineContentNewTaskCheck will handle it
|
||||
|
||||
// Add #todoist tag
|
||||
const processedLine = this.plugin.taskParser.addTodoistTag(lineText);
|
||||
this.plugin.debugLog('[lastLineNewTaskCheck] New task detected on line leave:', processedLine);
|
||||
|
||||
const currentTask = await this.plugin.taskParser.convertTextToTodoistTaskObject(processedLine, filepath, lineNumber, fileContent);
|
||||
if (typeof currentTask === 'undefined') return;
|
||||
|
||||
try {
|
||||
const newTask = await this.plugin.todoistSyncAPI.AddTask(currentTask);
|
||||
const { id: todoist_id } = newTask;
|
||||
newTask.path = filepath;
|
||||
new Notice(`new task ${newTask.content} id is ${newTask.id}`);
|
||||
|
||||
this.plugin.logOperation?.log('OBSIDIAN_TASK_CREATED', `Created task in Obsidian: ${newTask.content}`, filepath, todoist_id, 'obsidian\u2192todoist');
|
||||
this.plugin.logOperation?.log('TODOIST_TASK_CREATED', `Created task in Todoist: ${newTask.content}`, filepath, todoist_id, 'obsidian\u2192todoist');
|
||||
|
||||
await this.plugin.cacheOperation.setTaskFileMapping(todoist_id, filepath || '');
|
||||
|
||||
// Immediately sync so syncData contains the new task
|
||||
try {
|
||||
await this.plugin.todoistSyncAPI.incrementalSync();
|
||||
const updatedTask = await this.plugin.todoistSyncAPI.GetTaskById(todoist_id);
|
||||
if (updatedTask?.updated_at) {
|
||||
await this.plugin.cacheOperation.updateTaskMappingSyncMeta(todoist_id, { updated_at: updatedTask.updated_at });
|
||||
}
|
||||
} catch (syncErr) {
|
||||
console.error('[lastLineNewTaskCheck] Post-create incremental sync failed:', syncErr);
|
||||
}
|
||||
|
||||
if (currentTask.isCompleted === true) {
|
||||
await this.plugin.todoistSyncAPI.CloseTask(newTask.id);
|
||||
this.plugin.logOperation?.log('OBSIDIAN_TASK_COMPLETED', `Completed task in Obsidian: ${newTask.content}`, filepath, todoist_id, 'obsidian\u2192todoist');
|
||||
this.plugin.logOperation?.log('TODOIST_TASK_COMPLETED', `Completed task in Todoist: ${newTask.content}`, filepath, todoist_id, 'obsidian\u2192todoist');
|
||||
}
|
||||
|
||||
// Write tag + id + link back to the file
|
||||
const text_with_out_link = `${processedLine} %%[todoist_id:: ${todoist_id}]%%`;
|
||||
const link = this.plugin.settings.useAppURI ? `[link](todoist://task?id=${newTask.id})` : `[link](https://app.todoist.com/app/task/${newTask.id})`;
|
||||
const text = this.plugin.taskParser.addTodoistLink(text_with_out_link, link);
|
||||
|
||||
// Use vault.read + vault.modify since cursor is no longer on this line
|
||||
const file = this.app.vault.getAbstractFileByPath(filepath);
|
||||
if (!file) {
|
||||
console.error(`[lastLineNewTaskCheck] File not found: ${filepath}`);
|
||||
return;
|
||||
}
|
||||
const currentContent = await this.app.vault.read(file);
|
||||
const lines = currentContent.split('\n');
|
||||
if (lineNumber < lines.length) {
|
||||
lines[lineNumber] = text;
|
||||
await this.app.vault.modify(file, lines.join('\n'));
|
||||
}
|
||||
|
||||
const saved = await this.plugin.saveSettings();
|
||||
if (!saved) {
|
||||
console.warn('[lastLineNewTaskCheck] saveSettings skipped or failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[lastLineNewTaskCheck] Error adding task:', error);
|
||||
this.plugin.debugLog(`The error occurred in the file: ${filepath}`);
|
||||
new Notice(`Failed to create task. Check console for details.`);
|
||||
}
|
||||
}
|
||||
|
||||
async fullTextNewTaskCheck(file_path: string): Promise<void> {
|
||||
if (!this.plugin.isPrimaryDevice()) {
|
||||
this.plugin.debugLog('[toTodoist] Push blocked: not primary device');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"1.0.0": "0.15.0",
|
||||
"2.0.0": "1.0.0",
|
||||
"2.0.1": "1.0.0"
|
||||
"2.0.1": "1.0.0",
|
||||
"2.0.2": "1.0.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue