From d10e8007c927b48743ec9576896e52709f7e7e3a Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 25 Jul 2024 09:57:50 +0200 Subject: [PATCH 01/11] first try of implementation --- src/commands/index.ts | 1 + src/processing/automaton.ts | 2 ++ src/processing/checkCache.ts | 41 +++++++++++++++++++++++++++ src/processing/processor.ts | 38 +++++++++++++++++-------- src/processing/updateTasks.ts | 12 +------- src/settings/mainSetting.ts | 3 ++ src/vaultSearcher/dataviewSearcher.ts | 17 ++++++----- src/vaultSearcher/vaultSearcher.ts | 32 +++++++++++++++++++-- 8 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 src/processing/checkCache.ts diff --git a/src/commands/index.ts b/src/commands/index.ts index 19bb782..73f8bff 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -141,6 +141,7 @@ export default class Commands { if (this.plugin.settings.debugging) console.log("Resetting tasks in Vikunja done"); await this.plugin.labelsApi.loadLabels(); + this.plugin.settings.cache.clear(); new Notice("Resetting tasks and labels in Vikunja done"); } ).open(); diff --git a/src/processing/automaton.ts b/src/processing/automaton.ts index 05d7c38..091b2b4 100644 --- a/src/processing/automaton.ts +++ b/src/processing/automaton.ts @@ -8,6 +8,7 @@ import UpdateTasks from "./updateTasks"; import CreateTasks from "./createTasks"; import {Processor} from "./processor"; import {SyncLabels} from "./syncLabels"; +import CheckCache from "./checkCache"; interface StepsOutput { localTasks: PluginTask[]; @@ -41,6 +42,7 @@ class Automaton { this.steps = [ new GetTasks(app, plugin, processor), + new CheckCache(plugin, app, processor), new SyncLabels(app, plugin), new RemoveTasks(app, plugin), new CreateTasks(app, plugin, processor), diff --git a/src/processing/checkCache.ts b/src/processing/checkCache.ts new file mode 100644 index 0000000..797a26e --- /dev/null +++ b/src/processing/checkCache.ts @@ -0,0 +1,41 @@ +import {PluginTask} from "src/vaultSearcher/vaultSearcher"; +import {ModelsTask} from "vikunja_sdk"; +import {IAutomatonSteps, StepsOutput} from "./automaton"; +import VikunjaPlugin from "../../main"; +import {App} from "obsidian"; +import {Processor} from "./processor"; + +export default class CheckCache implements IAutomatonSteps { + plugin: VikunjaPlugin; + app: App; + processor: Processor; + + constructor(plugin: VikunjaPlugin, app: App, processor: Processor) { + this.plugin = plugin; + this.app = app; + this.processor = processor; + } + + async step(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): Promise { + this.updateCacheFromVault(localTasks, vikunjaTasks); + return {localTasks, vikunjaTasks}; + } + + updateCacheFromVault(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]) { + const tasksWithId = localTasks.filter(task => { + if (task.task.id === undefined) return false; // task has no id, so it is not in the cache, because not synced to vikunja + + const elem = this.plugin.settings.cache.get(task.task.id) + if (elem === undefined) return false; // task is not in the cache, because not synced to vikunja + + return !elem.isEquals(task); // filter elem, if it is equal to task in cache. False filters out. + } + ); + + if (tasksWithId.length === 0) { + if (this.plugin.settings.debugging) console.log("Step CheckCache: No changes in vault found. Cache is up to date."); + return; + } + if (this.plugin.settings.debugging) console.log("Step CheckCache: Something changed the vault without obsidian! Invalidate cache and creating anew"); + } +} diff --git a/src/processing/processor.ts b/src/processing/processor.ts index 20bcb2e..aaa479c 100644 --- a/src/processing/processor.ts +++ b/src/processing/processor.ts @@ -89,6 +89,10 @@ class Processor { return lines.join("\n"); } }); + + if (task.task.id !== undefined) { + this.plugin.settings.cache.set(task.task.id, task); + } } getTaskContent(task: PluginTask): string { @@ -169,24 +173,34 @@ class Processor { } const lastLine = this.lastLineChecked.get(currentFilename); - let pluginTask = undefined; + let updatedTask = undefined; if (!!lastLine) { const lastLineText = view.editor.getLine(lastLine); if (this.plugin.settings.debugging) console.log("Processor: Last line,", lastLine, "Last line text", lastLineText); try { const parsedTask = await this.taskParser.parse(lastLineText); - pluginTask = { - file: file, - lineno: lastLine, - task: parsedTask - }; + updatedTask = new PluginTask(file, lastLine, parsedTask); + if (updatedTask.task.id === undefined) { + return undefined; + } + const cacheTask = this.plugin.settings.cache.get(updatedTask.task.id); + if (cacheTask === undefined) { + if (this.plugin.settings.debugging) console.error("Processor: Should not be here, because if this task is not in cache, but has an id, it circumvented the cache.") + return undefined; + } + if (cacheTask.isEquals(updatedTask)) { + // Cache and current task are equal, so no update is needed + return undefined; + } + // no guard check fires, so there is an update. + this.plugin.settings.cache.set(updatedTask.task.id, updatedTask); } catch (e) { if (this.plugin.settings.debugging) console.log("Processor: Error while parsing task", e); } } this.lastLineChecked.set(currentFilename, currentLine); - return pluginTask; + return updatedTask; } /* Update a task in the vault @@ -200,6 +214,10 @@ class Processor { lines.splice(task.lineno, 1, newTask); return lines.join("\n"); }); + + if (task.task.id !== undefined) { + this.plugin.settings.cache.set(task.task.id, task); + } } getVaultSearcher(): VaultSearcher { @@ -275,11 +293,7 @@ class Processor { default: throw new Error("No valid chooseOutputFile selected"); } - const pluginTask: PluginTask = { - file: file, - lineno: 0, - task: task - }; + const pluginTask = new PluginTask(file, 0, task); createdTasksInVault.push(pluginTask); } diff --git a/src/processing/updateTasks.ts b/src/processing/updateTasks.ts index 40284cf..f347600 100644 --- a/src/processing/updateTasks.ts +++ b/src/processing/updateTasks.ts @@ -55,7 +55,7 @@ class UpdateTasks implements IAutomatonSteps { if (this.plugin.settings.debugging) console.log("Step UpdateTask: updated field is not defined", task, vikunjaTask); throw new Error("Task updated field is not defined"); } - if (this.areTasksEqual(task.task, vikunjaTask)) { + if (task.isTaskEqual(vikunjaTask)) { if (this.plugin.settings.debugging) console.log("Step UpdateTask: Task is the same in both platforms", task, vikunjaTask); continue; } @@ -79,16 +79,6 @@ class UpdateTasks implements IAutomatonSteps { } private areTasksEqual(local: ModelsTask, vikunja: ModelsTask) { - const title = local.title === vikunja.title; - const description = local.description === vikunja.description; - const dueDate = local.dueDate === vikunja.dueDate; - const labels = local.labels?.filter(label => vikunja.labels?.find(vikunjaLabel => vikunjaLabel.title === label.title)).length === local.labels?.length; - const priority = local.priority === vikunja.priority; - const status = local.done === vikunja.done; - const doneAt = local.doneAt === vikunja.doneAt; -// const updatedAt = local.updated === vikunja.updated; not usable, because it is different if anything changes in local file - - return title && description && dueDate && labels && priority && status && doneAt; } private async updateTasks(tasksToUpdateInVault: PluginTask[], tasksToUpdateInVikunja: PluginTask[]) { diff --git a/src/settings/mainSetting.ts b/src/settings/mainSetting.ts index 3a0e602..cbea87a 100644 --- a/src/settings/mainSetting.ts +++ b/src/settings/mainSetting.ts @@ -3,6 +3,7 @@ import VikunjaPlugin from "../../main"; import {backendToFindTasks, chooseOutputFile, supportedTasksPluginsFormat} from "../enums"; import {ModelsProject, ModelsProjectView} from "../../vikunja_sdk"; import {appHasDailyNotesPluginLoaded} from "obsidian-daily-notes-interface"; +import {PluginTask} from "../vaultSearcher/vaultSearcher"; export interface VikunjaPluginSettings { mySetting: string; @@ -29,6 +30,7 @@ export interface VikunjaPluginSettings { availableViews: ModelsProjectView[], selectedView: number, selectBucketForDoneTasks: number, + cache: Map, // do not touch! Only via processing/processor.ts } export const DEFAULT_SETTINGS: VikunjaPluginSettings = { @@ -56,6 +58,7 @@ export const DEFAULT_SETTINGS: VikunjaPluginSettings = { availableViews: [], selectedView: 0, selectBucketForDoneTasks: 0, + cache: new Map(), } export class MainSetting extends PluginSettingTab { diff --git a/src/vaultSearcher/dataviewSearcher.ts b/src/vaultSearcher/dataviewSearcher.ts index 93ab38f..401583f 100644 --- a/src/vaultSearcher/dataviewSearcher.ts +++ b/src/vaultSearcher/dataviewSearcher.ts @@ -1,4 +1,4 @@ -import {App, moment, TFile} from "obsidian"; +import {App, moment} from "obsidian"; import VikunjaPlugin from "../../main"; import {DataviewApi, getAPI} from "obsidian-dataview"; import {PluginTask, VaultSearcher} from "./vaultSearcher"; @@ -34,13 +34,16 @@ export class DataviewSearcher implements VaultSearcher { console.error("DataviewSearcher: Could not find file for task", task); continue; } - parsed.updated = moment(file.stat.mtime).format("YYYY-MM-DDTHH:mm:ss[Z]"); + const cachedTask = this.plugin.settings.cache.get(task.id); + if (cachedTask !== undefined) { + if (this.plugin.settings.debugging) console.log("DataviewSearcher: Found cached task", cachedTask); + parsed.updated = cachedTask.task.updated; + } else { + if (this.plugin.settings.debugging) console.log("DataviewSearcher: Fallback to file modified date"); + parsed.updated = moment(file.stat.mtime).format("YYYY-MM-DDTHH:mm:ss[Z]"); + } - const vaultParsed: PluginTask = { - file: file, - lineno: task.line, - task: parsed - }; + const vaultParsed = new PluginTask(file, task.line, parsed); if (this.plugin.settings.debugging) console.log("DataviewSearcher: Parsed task", parsed); tasksFormatted.push(vaultParsed); } diff --git a/src/vaultSearcher/vaultSearcher.ts b/src/vaultSearcher/vaultSearcher.ts index a86a8d2..7539a6b 100644 --- a/src/vaultSearcher/vaultSearcher.ts +++ b/src/vaultSearcher/vaultSearcher.ts @@ -2,14 +2,42 @@ import {ModelsTask} from "../../vikunja_sdk"; import {TaskParser} from "../taskFormats/taskFormats"; import {TFile} from "obsidian"; -interface PluginTask { +class PluginTask { file: TFile; lineno: number; task: ModelsTask; + + constructor(file: TFile, lineno: number, task: ModelsTask) { + this.file = file; + this.lineno = lineno; + this.task = task; + } + + isEquals(pluginTask: PluginTask): boolean { + const file = this.file.path === pluginTask.file.path; + const lineno = this.lineno === pluginTask.lineno; + const task = this.isTaskEqual(pluginTask.task); + + return file && lineno && task; + } + + isTaskEqual(vikunja: ModelsTask): boolean { + const title = this.task.title === vikunja.title; + const description = this.task.description === vikunja.description; + const dueDate = this.task.dueDate === vikunja.dueDate; + const labels = this.task.labels?.filter(label => vikunja.labels?.find(vikunjaLabel => vikunjaLabel.title === label.title)).length === this.task.labels?.length; + const priority = this.task.priority === vikunja.priority; + const status = this.task.done === vikunja.done; + const doneAt = this.task.doneAt === vikunja.doneAt; + const updated = this.task.updated === vikunja.updated; + + return title && description && dueDate && labels && priority && status && doneAt && updated; + } } interface VaultSearcher { getTasks(parser: TaskParser): Promise; } -export type {VaultSearcher, PluginTask}; +export type {VaultSearcher}; +export {PluginTask}; From f07f6b62d55be1762ff804348e0a2ea44dcf9836 Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 10:31:28 +0200 Subject: [PATCH 02/11] use dailynote in vault at the same date in vikunja task created --- src/processing/processor.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/processing/processor.ts b/src/processing/processor.ts index aaa479c..866081b 100644 --- a/src/processing/processor.ts +++ b/src/processing/processor.ts @@ -270,8 +270,12 @@ class Processor { for (const task of tasksToPushToVault) { let file: TFile; const chosenFile = this.app.vault.getFileByPath(this.plugin.settings.chosenOutputFile); - // FIXME This should be the date of the vikunja created date, so the task is created in the correct daily note - const date = moment(); + const formattedDate = task.created; + let date = moment(); + if (formattedDate !== undefined) { + if (this.plugin.settings.debugging) console.log("Step CreateTask: Found formatted date", formattedDate, "using it as daily note"); + date = moment(formattedDate, "YYYY-MM-DDTHH:mm:ss[Z]"); + } const dailies = getAllDailyNotes() switch (this.plugin.settings.chooseOutputFile) { From 86d9654bd8798e39971d4c03a20202f6832193af Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 11:15:01 +0200 Subject: [PATCH 03/11] first RC for cache, tests needed --- main.ts | 27 +++++++++++--- src/processing/checkCache.ts | 10 +++--- src/processing/processor.ts | 28 +++++++++------ src/processing/updateTasks.ts | 9 ++--- src/settings/VaultTaskCache.ts | 51 +++++++++++++++++++++++++++ src/settings/mainSetting.ts | 2 +- src/vaultSearcher/dataviewSearcher.ts | 36 ++++++++++++++++--- src/vaultSearcher/vaultSearcher.ts | 14 +++----- src/vikunja/tasks.ts | 2 +- 9 files changed, 139 insertions(+), 40 deletions(-) create mode 100644 src/settings/VaultTaskCache.ts diff --git a/main.ts b/main.ts index 92f6274..7f21eb1 100644 --- a/main.ts +++ b/main.ts @@ -6,6 +6,7 @@ import {UserUser} from "./vikunja_sdk"; import {Label} from "./src/vikunja/labels"; import Commands from "./src/commands"; import {Projects} from "./src/vikunja/projects"; +import VaultTaskCache from "./src/settings/VaultTaskCache"; // Remember to rename these classes and interfaces! @@ -17,6 +18,7 @@ export default class VikunjaPlugin extends Plugin { processor: Processor; commands: Commands; projectsApi: Projects; + cache: VaultTaskCache; async onload() { await this.loadSettings(); @@ -88,6 +90,7 @@ export default class VikunjaPlugin extends Plugin { this.userObject = undefined; this.labelsApi = new Label(this.app, this); this.projectsApi = new Projects(this.app, this); + this.cache = new VaultTaskCache(this.app, this); } private setupCommands() { @@ -122,14 +125,30 @@ export default class VikunjaPlugin extends Plugin { }) } - private async handleEditorChange() { - if (this.settings.debugging) console.log("Editor changed"); + private async handleEditorChange(data: any) { + if (this.settings.debugging) console.log("Editor changed", data); + const currentFile = this.app.workspace.getActiveFile(); + if (!currentFile) { + if (this.settings.debugging) console.log("No file open"); + return; + } + + const tasks = await this.processor.getVaultSearcher().getTasksFromFile(this.processor.getTaskParser(), currentFile); + for (const task of tasks) { + if (task.task.id) { + const cachedTask = this.cache.get(task.task.id); + if (cachedTask === undefined || !cachedTask.isEquals(task)) { + this.cache.update(task); + } + } + } + // FIXME the update line stuff should be communicated in settings return; - //await this.checkLastLineForUpdate(); } private async handleUpDownEvent(evt: KeyboardEvent) { - if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'PageUp' || evt.key === 'PageDown') { + if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'PageUp' || evt.key === 'PageDown' || evt.key === "Enter") { + if (this.settings.debugging) console.log("Line changed via keys"); await this.checkLastLineForUpdate(); } } diff --git a/src/processing/checkCache.ts b/src/processing/checkCache.ts index 797a26e..70ce37d 100644 --- a/src/processing/checkCache.ts +++ b/src/processing/checkCache.ts @@ -3,7 +3,7 @@ import {ModelsTask} from "vikunja_sdk"; import {IAutomatonSteps, StepsOutput} from "./automaton"; import VikunjaPlugin from "../../main"; import {App} from "obsidian"; -import {Processor} from "./processor"; +import {compareModelTasks, Processor} from "./processor"; export default class CheckCache implements IAutomatonSteps { plugin: VikunjaPlugin; @@ -17,18 +17,18 @@ export default class CheckCache implements IAutomatonSteps { } async step(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): Promise { - this.updateCacheFromVault(localTasks, vikunjaTasks); + this.updateCacheFromVault(localTasks); return {localTasks, vikunjaTasks}; } - updateCacheFromVault(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]) { + updateCacheFromVault(localTasks: PluginTask[]) { const tasksWithId = localTasks.filter(task => { if (task.task.id === undefined) return false; // task has no id, so it is not in the cache, because not synced to vikunja - const elem = this.plugin.settings.cache.get(task.task.id) + const elem = this.plugin.cache.get(task.task.id) if (elem === undefined) return false; // task is not in the cache, because not synced to vikunja - return !elem.isEquals(task); // filter elem, if it is equal to task in cache. False filters out. + return !compareModelTasks(elem.task, task.task); // filter elem, if it is equal to task in cache. False filters out. } ); diff --git a/src/processing/processor.ts b/src/processing/processor.ts index 866081b..1f2dac3 100644 --- a/src/processing/processor.ts +++ b/src/processing/processor.ts @@ -90,9 +90,7 @@ class Processor { } }); - if (task.task.id !== undefined) { - this.plugin.settings.cache.set(task.task.id, task); - } + this.plugin.cache.update(task); } getTaskContent(task: PluginTask): string { @@ -183,17 +181,17 @@ class Processor { if (updatedTask.task.id === undefined) { return undefined; } - const cacheTask = this.plugin.settings.cache.get(updatedTask.task.id); + const cacheTask = this.plugin.cache.get(updatedTask.task.id); if (cacheTask === undefined) { if (this.plugin.settings.debugging) console.error("Processor: Should not be here, because if this task is not in cache, but has an id, it circumvented the cache.") return undefined; } - if (cacheTask.isEquals(updatedTask)) { + if (compareModelTasks(updatedTask.task, cacheTask.task)) { // Cache and current task are equal, so no update is needed return undefined; } // no guard check fires, so there is an update. - this.plugin.settings.cache.set(updatedTask.task.id, updatedTask); + this.plugin.cache.update(updatedTask); } catch (e) { if (this.plugin.settings.debugging) console.log("Processor: Error while parsing task", e); } @@ -215,9 +213,7 @@ class Processor { return lines.join("\n"); }); - if (task.task.id !== undefined) { - this.plugin.settings.cache.set(task.task.id, task); - } + this.plugin.cache.update(task); } getVaultSearcher(): VaultSearcher { @@ -319,7 +315,19 @@ class Processor { await this.plugin.tasksApi.deleteTask(task.task); } } +} +function compareModelTasks(local: ModelsTask, vikunja: ModelsTask): boolean { + const title = local.title === vikunja.title; + const description = local.description === vikunja.description; + const dueDate = local.dueDate === vikunja.dueDate; + const labels = local.labels?.filter(label => vikunja.labels?.find(vikunjaLabel => vikunjaLabel.title === label.title)).length === local.labels?.length; + const priority = local.priority === vikunja.priority; + const status = local.done === vikunja.done; + const doneAt = local.doneAt === vikunja.doneAt; + const updated = local.updated === vikunja.updated; + + return title && description && dueDate && labels && priority && status && doneAt && updated; } -export {Processor}; +export {Processor, compareModelTasks}; diff --git a/src/processing/updateTasks.ts b/src/processing/updateTasks.ts index f347600..1ac21d8 100644 --- a/src/processing/updateTasks.ts +++ b/src/processing/updateTasks.ts @@ -44,10 +44,13 @@ class UpdateTasks implements IAutomatonSteps { */ private splitTaskAfterUpdatedStatus(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): UpdatedSplit { if (this.plugin.settings.debugging) console.log("Step UpdateTask: Find tasks which have updates on the other platform"); + const cacheLocalTasks = this.plugin.cache.getCachedTasks(); + + // TODO Check for localTasks which circumvent the cache, how do i find this out!? let tasksToUpdateInVault: PluginTask[] = []; let tasksToUpdateInVikunja: PluginTask[] = []; - for (const task of localTasks) { + for (const task of cacheLocalTasks) { const vikunjaTask = vikunjaTasks.find(vikunjaTask => vikunjaTask.id === task.task.id); if (this.plugin.settings.debugging) console.log("Step UpdateTask: found Vikunja task", vikunjaTask, " for Vault task", task.task); if (!vikunjaTask) continue; @@ -90,9 +93,7 @@ class UpdateTasks implements IAutomatonSteps { private async updateTasksInVikunja(updateTasks: PluginTask[]) { if (this.plugin.settings.debugging) console.log("Step UpdateTask: Update tasks in vikunja"); - for (const task of updateTasks) { - await this.plugin.tasksApi.updateTask(task.task); - } + await Promise.all(updateTasks.map(task => this.plugin.tasksApi.updateTask(task.task))); } private async updateTasksInVault(updateTasks: PluginTask[]) { diff --git a/src/settings/VaultTaskCache.ts b/src/settings/VaultTaskCache.ts new file mode 100644 index 0000000..dfd3622 --- /dev/null +++ b/src/settings/VaultTaskCache.ts @@ -0,0 +1,51 @@ +import VikunjaPlugin from "../../main"; +import {App, moment} from "obsidian"; +import {PluginTask} from "../vaultSearcher/vaultSearcher"; + +/* +* This class is used to cache tasks which are updated in Vault, but not in Vikunja. +* This should help to identify modifications in vault without Obsidian. +* Also it makes it possible to update only modified tasks. See issue #9 for more details. +*/ +export default class VaultTaskCache { + plugin: VikunjaPlugin; + app: App; + + constructor(app: App, plugin: VikunjaPlugin) { + this.app = app; + this.plugin = plugin; + } + + update(local: PluginTask) { + if (local.task.id === undefined) { + throw new Error("VaultTaskCache: Task id is not defined"); + } + const currentDate = moment().format("YYYY-MM-DDTHH:mm:ss[Z]"); + if (this.plugin.settings.debugging) console.log("VaultTaskCache: Updating task", local.task.id, "with updated date", currentDate); + local.task.updated = currentDate; + + this.plugin.settings.cache.set(local.task.id, local); + } + + get(id: number): PluginTask | undefined { + return this.plugin.settings.cache.get(id); + } + + /* + * Useful, when tasks are updated in vikunja and so the task in the cache is outdated. + */ + delete(id: number) { + this.plugin.settings.cache.delete(id); + } + + /* + * Do not forget to call delete after processing the tasks. + */ + getCachedTasks(): PluginTask[] { + return Array.from(this.plugin.settings.cache.values()); + } + + reset() { + this.plugin.settings.cache.clear(); + } +} diff --git a/src/settings/mainSetting.ts b/src/settings/mainSetting.ts index cbea87a..3a65b13 100644 --- a/src/settings/mainSetting.ts +++ b/src/settings/mainSetting.ts @@ -1,7 +1,7 @@ import {App, Notice, PluginSettingTab, Setting} from "obsidian"; import VikunjaPlugin from "../../main"; import {backendToFindTasks, chooseOutputFile, supportedTasksPluginsFormat} from "../enums"; -import {ModelsProject, ModelsProjectView} from "../../vikunja_sdk"; +import {ModelsProject, ModelsProjectView, ModelsTask} from "../../vikunja_sdk"; import {appHasDailyNotesPluginLoaded} from "obsidian-daily-notes-interface"; import {PluginTask} from "../vaultSearcher/vaultSearcher"; diff --git a/src/vaultSearcher/dataviewSearcher.ts b/src/vaultSearcher/dataviewSearcher.ts index 401583f..23d1d13 100644 --- a/src/vaultSearcher/dataviewSearcher.ts +++ b/src/vaultSearcher/dataviewSearcher.ts @@ -1,9 +1,10 @@ -import {App, moment} from "obsidian"; +import {App, moment, TFile} from "obsidian"; import VikunjaPlugin from "../../main"; -import {DataviewApi, getAPI} from "obsidian-dataview"; +import {DataArray, DataviewApi, getAPI} from "obsidian-dataview"; import {PluginTask, VaultSearcher} from "./vaultSearcher"; import {TaskParser} from "src/taskFormats/taskFormats"; + export class DataviewSearcher implements VaultSearcher { app: App; plugin: VikunjaPlugin; @@ -15,10 +16,31 @@ export class DataviewSearcher implements VaultSearcher { this.dataviewPlugin = getAPI(this.app); } + async getTasksFromFile(parser: TaskParser, file: TFile): Promise { + const dv = this.dataviewPlugin; + let tasks = undefined; + + const page = dv.page(file.path); + if (page === undefined) { + console.error("DataviewSearcher: Could not find page for file", file); + return []; + } + if (page.file.tasks === undefined) { + console.error("DataviewSearcher: Could not find tasks for page", page); + return []; + } + tasks = page.file.tasks; + return await this.parseTasks(tasks, parser); + } + async getTasks(parser: TaskParser): Promise { const dv = this.dataviewPlugin; - const tasks = dv.pages().file.tasks.values; + const tasks = dv.pages().file.tasks; + return await this.parseTasks(tasks, parser); + } + + private async parseTasks(tasks: DataArray, parser: TaskParser) { if (this.plugin.settings.debugging) console.log("DataviewSearcher: Found dataview tasks", tasks); const tasksFormatted: PluginTask[] = []; @@ -34,13 +56,17 @@ export class DataviewSearcher implements VaultSearcher { console.error("DataviewSearcher: Could not find file for task", task); continue; } - const cachedTask = this.plugin.settings.cache.get(task.id); + let cachedTask = undefined; + const id = parsed.id; + if (id !== undefined) { + cachedTask = this.plugin.cache.get(id); + } if (cachedTask !== undefined) { if (this.plugin.settings.debugging) console.log("DataviewSearcher: Found cached task", cachedTask); parsed.updated = cachedTask.task.updated; } else { if (this.plugin.settings.debugging) console.log("DataviewSearcher: Fallback to file modified date"); - parsed.updated = moment(file.stat.mtime).format("YYYY-MM-DDTHH:mm:ss[Z]"); + parsed.updated = moment(file.stat.ctime).format("YYYY-MM-DDTHH:mm:ss[Z]"); } const vaultParsed = new PluginTask(file, task.line, parsed); diff --git a/src/vaultSearcher/vaultSearcher.ts b/src/vaultSearcher/vaultSearcher.ts index 7539a6b..631bd65 100644 --- a/src/vaultSearcher/vaultSearcher.ts +++ b/src/vaultSearcher/vaultSearcher.ts @@ -1,6 +1,7 @@ import {ModelsTask} from "../../vikunja_sdk"; import {TaskParser} from "../taskFormats/taskFormats"; import {TFile} from "obsidian"; +import {compareModelTasks} from "../processing/processor"; class PluginTask { file: TFile; @@ -22,21 +23,14 @@ class PluginTask { } isTaskEqual(vikunja: ModelsTask): boolean { - const title = this.task.title === vikunja.title; - const description = this.task.description === vikunja.description; - const dueDate = this.task.dueDate === vikunja.dueDate; - const labels = this.task.labels?.filter(label => vikunja.labels?.find(vikunjaLabel => vikunjaLabel.title === label.title)).length === this.task.labels?.length; - const priority = this.task.priority === vikunja.priority; - const status = this.task.done === vikunja.done; - const doneAt = this.task.doneAt === vikunja.doneAt; - const updated = this.task.updated === vikunja.updated; - - return title && description && dueDate && labels && priority && status && doneAt && updated; + return compareModelTasks(this.task, vikunja); } } interface VaultSearcher { getTasks(parser: TaskParser): Promise; + + getTasksFromFile(parser: TaskParser, file: TFile): Promise; } export type {VaultSearcher}; diff --git a/src/vikunja/tasks.ts b/src/vikunja/tasks.ts index 3e854ba..c09b015 100644 --- a/src/vikunja/tasks.ts +++ b/src/vikunja/tasks.ts @@ -49,7 +49,7 @@ class Tasks implements VikunjaAPI { await this.addLabelToTask(task); const param: TasksIdPostRequest = {id: task.id, task: task}; - return this.tasksApi.tasksIdPost(param); + return await this.tasksApi.tasksIdPost(param); } async updateTasks(tasks: ModelsTask[]): Promise { From feacf50aa1ba4dd426fffa21babd60162fb55d8c Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 11:58:56 +0200 Subject: [PATCH 04/11] move crons listeners to settings and fix restarting them on change intervals --- main.ts | 11 -------- src/settings/VaultTaskCache.ts | 4 +++ src/settings/mainSetting.ts | 46 +++++++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/main.ts b/main.ts index 7f21eb1..3be3b21 100644 --- a/main.ts +++ b/main.ts @@ -72,17 +72,6 @@ export default class VikunjaPlugin extends Plugin { this.registerDomEvent(document, 'keyup', this.handleUpDownEvent.bind(this)); this.registerDomEvent(document, 'click', this.handleClickEvent.bind(this)); this.registerEvent(this.app.workspace.on('editor-change', this.handleEditorChange.bind(this))); - - // When registering intervals, this function will automatically clear the interval when the plugin is disabled. - this.registerInterval(window - .setInterval(async () => { - // this runs anyway, also when cron not enabled, to be dynamically enabled by settings without disable/enable plugin - if (this.settings.enableCron) { - await this.processor.exec() - } - }, - this.settings.cronInterval * 1000) - ); } private setupAPIs() { diff --git a/src/settings/VaultTaskCache.ts b/src/settings/VaultTaskCache.ts index dfd3622..54b17d8 100644 --- a/src/settings/VaultTaskCache.ts +++ b/src/settings/VaultTaskCache.ts @@ -16,6 +16,10 @@ export default class VaultTaskCache { this.plugin = plugin; } + async saveCacheToDisk() { + await this.plugin.saveSettings(); + } + update(local: PluginTask) { if (local.task.id === undefined) { throw new Error("VaultTaskCache: Task id is not defined"); diff --git a/src/settings/mainSetting.ts b/src/settings/mainSetting.ts index 3a65b13..9ba38cc 100644 --- a/src/settings/mainSetting.ts +++ b/src/settings/mainSetting.ts @@ -1,7 +1,7 @@ import {App, Notice, PluginSettingTab, Setting} from "obsidian"; import VikunjaPlugin from "../../main"; import {backendToFindTasks, chooseOutputFile, supportedTasksPluginsFormat} from "../enums"; -import {ModelsProject, ModelsProjectView, ModelsTask} from "../../vikunja_sdk"; +import {ModelsProject, ModelsProjectView} from "../../vikunja_sdk"; import {appHasDailyNotesPluginLoaded} from "obsidian-daily-notes-interface"; import {PluginTask} from "../vaultSearcher/vaultSearcher"; @@ -30,7 +30,8 @@ export interface VikunjaPluginSettings { availableViews: ModelsProjectView[], selectedView: number, selectBucketForDoneTasks: number, - cache: Map, // do not touch! Only via processing/processor.ts + cache: Map, // do not touch! Only via settings/VaultTaskCache.ts + saveCacheToDiskFrequency: number, } export const DEFAULT_SETTINGS: VikunjaPluginSettings = { @@ -59,15 +60,21 @@ export const DEFAULT_SETTINGS: VikunjaPluginSettings = { selectedView: 0, selectBucketForDoneTasks: 0, cache: new Map(), + saveCacheToDiskFrequency: 10, } export class MainSetting extends PluginSettingTab { plugin: VikunjaPlugin; projects: ModelsProject[] = []; + private cacheListener: number; + private cronListener: number; constructor(app: App, plugin: VikunjaPlugin) { super(app, plugin); this.plugin = plugin; + + this.startCacheListener(); + this.startCronListener(); } display(): void { @@ -187,10 +194,22 @@ export class MainSetting extends PluginSettingTab { this.plugin.settings.cronInterval = parseInt(value); await this.plugin.saveSettings(); } - )) - ; + )); } + new Setting(containerEl) + .setName("Save cache to disk frequency") + .setDesc("Set the interval in minutes to save the cache to disk. Lower values will result in more frequent saves, but may cause performance issues. Limits are 1 to 60. This will be enforced by the plugin.") + .addText(text => text + .setValue(this.plugin.settings.saveCacheToDiskFrequency.toString()) + .onChange(async (value: string) => { + this.plugin.settings.saveCacheToDiskFrequency = Math.max(Math.min(parseInt(value), 60), 1); + await this.plugin.saveSettings(); + this.startCacheListener(); + } + ) + ) + new Setting(containerEl).setHeading().setName('Vikunja Settings').setDesc('Settings to connect to Vikunja.'); const hostDesc = document.createDocumentFragment(); @@ -551,6 +570,25 @@ export class MainSetting extends PluginSettingTab { this.display(); } + private startCacheListener() { + window.clearInterval(this.cacheListener); + this.cacheListener = window.setInterval(this.plugin.cache.saveCacheToDisk.bind(this), this.plugin.settings.saveCacheToDiskFrequency * 60 * 1000); + this.plugin.registerInterval(this.cacheListener); + } + + private startCronListener() { + window.clearInterval(this.cronListener); + this.cronListener = window + .setInterval(async () => { + // this runs anyway, also when cron not enabled, to be dynamically enabled by settings without disable/enable plugin + if (this.plugin.settings.enableCron) { + await this.plugin.processor.exec() + } + }, + this.plugin.settings.cronInterval * 1000) + this.plugin.registerInterval(this.cronListener); + } + private resetApis() { // TODO: Implement an event to reload API configurations this.plugin.tasksApi.init(); From 03ffba957751f598b7260c3141a529018c5121c9 Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 12:09:03 +0200 Subject: [PATCH 05/11] write cache only when changes were made in tasks --- src/settings/VaultTaskCache.ts | 10 +++++++++- src/settings/mainSetting.ts | 14 +++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/settings/VaultTaskCache.ts b/src/settings/VaultTaskCache.ts index 54b17d8..6af8dc0 100644 --- a/src/settings/VaultTaskCache.ts +++ b/src/settings/VaultTaskCache.ts @@ -10,14 +10,19 @@ import {PluginTask} from "../vaultSearcher/vaultSearcher"; export default class VaultTaskCache { plugin: VikunjaPlugin; app: App; + changesMade: boolean; constructor(app: App, plugin: VikunjaPlugin) { this.app = app; this.plugin = plugin; + this.changesMade = false; } async saveCacheToDisk() { - await this.plugin.saveSettings(); + if (this.changesMade) { + await this.plugin.saveSettings(); + } + this.changesMade = false; } update(local: PluginTask) { @@ -29,6 +34,7 @@ export default class VaultTaskCache { local.task.updated = currentDate; this.plugin.settings.cache.set(local.task.id, local); + this.changesMade = true; } get(id: number): PluginTask | undefined { @@ -40,6 +46,7 @@ export default class VaultTaskCache { */ delete(id: number) { this.plugin.settings.cache.delete(id); + this.changesMade = true; } /* @@ -51,5 +58,6 @@ export default class VaultTaskCache { reset() { this.plugin.settings.cache.clear(); + this.changesMade = true; } } diff --git a/src/settings/mainSetting.ts b/src/settings/mainSetting.ts index 9ba38cc..1f5338b 100644 --- a/src/settings/mainSetting.ts +++ b/src/settings/mainSetting.ts @@ -60,7 +60,7 @@ export const DEFAULT_SETTINGS: VikunjaPluginSettings = { selectedView: 0, selectBucketForDoneTasks: 0, cache: new Map(), - saveCacheToDiskFrequency: 10, + saveCacheToDiskFrequency: 1, } export class MainSetting extends PluginSettingTab { @@ -199,11 +199,19 @@ export class MainSetting extends PluginSettingTab { new Setting(containerEl) .setName("Save cache to disk frequency") - .setDesc("Set the interval in minutes to save the cache to disk. Lower values will result in more frequent saves, but may cause performance issues. Limits are 1 to 60. This will be enforced by the plugin.") + .setDesc("This plugin uses a cache to calculate correct dates. Set the interval in minutes to save the cache to disk. Lower values will result in more frequent saves, but may cause performance issues. Set too high, task dates are not correctly calculated, because they are missing in cache in next startup. If you make bulk edits of tasks in your vault, you should set higher value. Cache will be only written, if changes were made since last check. If you are unsure, try lowest value and increase it, if you experience performance issues. Limits are 1 to 60 minutes.") .addText(text => text .setValue(this.plugin.settings.saveCacheToDiskFrequency.toString()) .onChange(async (value: string) => { - this.plugin.settings.saveCacheToDiskFrequency = Math.max(Math.min(parseInt(value), 60), 1); + const parsedNumber = parseInt(value); + if (isNaN(parsedNumber)) { + return; + } + const lowerThanMax = Math.min(parsedNumber, 60); + if (this.plugin.settings.debugging) console.log("Save cache to disk frequency - high limits", lowerThanMax); + const higherThanMin = Math.max(lowerThanMax, 1); + if (this.plugin.settings.debugging) console.log("Save cache to disk frequency - low limits", higherThanMin); + this.plugin.settings.saveCacheToDiskFrequency = higherThanMin; await this.plugin.saveSettings(); this.startCacheListener(); } From cc3ece940467f40620924aff361c03b18c42705a Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 12:53:14 +0200 Subject: [PATCH 06/11] cache is only available for tasks, which have an id so the processor runs over the whole vault and uses modified date for creating, but after updates, will be used the cache and helps parser to take the correct updated value. the cache looks always at the whole file and add them to cache. also the pulling stuff will be cached. --- main.ts | 2 +- src/processing/processor.ts | 3 +-- src/processing/updateTasks.ts | 5 +---- src/settings/VaultTaskCache.ts | 9 ++++++--- src/vaultSearcher/dataviewSearcher.ts | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/main.ts b/main.ts index 3be3b21..e47346a 100644 --- a/main.ts +++ b/main.ts @@ -126,7 +126,7 @@ export default class VikunjaPlugin extends Plugin { for (const task of tasks) { if (task.task.id) { const cachedTask = this.cache.get(task.task.id); - if (cachedTask === undefined || !cachedTask.isEquals(task)) { + if (cachedTask === undefined || !cachedTask.isTaskEqual(task.task)) { this.cache.update(task); } } diff --git a/src/processing/processor.ts b/src/processing/processor.ts index 1f2dac3..66f6f03 100644 --- a/src/processing/processor.ts +++ b/src/processing/processor.ts @@ -86,11 +86,10 @@ class Processor { break; } } + this.plugin.cache.update(task); return lines.join("\n"); } }); - - this.plugin.cache.update(task); } getTaskContent(task: PluginTask): string { diff --git a/src/processing/updateTasks.ts b/src/processing/updateTasks.ts index 1ac21d8..cd4a62a 100644 --- a/src/processing/updateTasks.ts +++ b/src/processing/updateTasks.ts @@ -44,13 +44,10 @@ class UpdateTasks implements IAutomatonSteps { */ private splitTaskAfterUpdatedStatus(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): UpdatedSplit { if (this.plugin.settings.debugging) console.log("Step UpdateTask: Find tasks which have updates on the other platform"); - const cacheLocalTasks = this.plugin.cache.getCachedTasks(); - - // TODO Check for localTasks which circumvent the cache, how do i find this out!? let tasksToUpdateInVault: PluginTask[] = []; let tasksToUpdateInVikunja: PluginTask[] = []; - for (const task of cacheLocalTasks) { + for (const task of localTasks) { const vikunjaTask = vikunjaTasks.find(vikunjaTask => vikunjaTask.id === task.task.id); if (this.plugin.settings.debugging) console.log("Step UpdateTask: found Vikunja task", vikunjaTask, " for Vault task", task.task); if (!vikunjaTask) continue; diff --git a/src/settings/VaultTaskCache.ts b/src/settings/VaultTaskCache.ts index 6af8dc0..d2e5371 100644 --- a/src/settings/VaultTaskCache.ts +++ b/src/settings/VaultTaskCache.ts @@ -29,10 +29,13 @@ export default class VaultTaskCache { if (local.task.id === undefined) { throw new Error("VaultTaskCache: Task id is not defined"); } - const currentDate = moment().format("YYYY-MM-DDTHH:mm:ss[Z]"); - if (this.plugin.settings.debugging) console.log("VaultTaskCache: Updating task", local.task.id, "with updated date", currentDate); - local.task.updated = currentDate; + const cachedTask = this.get(local.task.id); + const currentDate = moment().format("YYYY-MM-DDTHH:mm:ss[Z]"); + if (cachedTask !== undefined && !cachedTask.isTaskEqual(local.task)) { + if (this.plugin.settings.debugging) console.log("VaultTaskCache: Updating task", local.task.id, "with updated date", currentDate); + local.task.updated = currentDate; + } this.plugin.settings.cache.set(local.task.id, local); this.changesMade = true; } diff --git a/src/vaultSearcher/dataviewSearcher.ts b/src/vaultSearcher/dataviewSearcher.ts index 23d1d13..7b4ee34 100644 --- a/src/vaultSearcher/dataviewSearcher.ts +++ b/src/vaultSearcher/dataviewSearcher.ts @@ -66,7 +66,7 @@ export class DataviewSearcher implements VaultSearcher { parsed.updated = cachedTask.task.updated; } else { if (this.plugin.settings.debugging) console.log("DataviewSearcher: Fallback to file modified date"); - parsed.updated = moment(file.stat.ctime).format("YYYY-MM-DDTHH:mm:ss[Z]"); + parsed.updated = moment(file.stat.mtime).format("YYYY-MM-DDTHH:mm:ss[Z]"); } const vaultParsed = new PluginTask(file, task.line, parsed); From a4f0780166027bd5317e06d0608177d9d53b456d Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 21:18:41 +0200 Subject: [PATCH 07/11] complete rewrite, so cache can be stored to disk what a mess. Objects cannot be serialized without hassle. after some tryouts, it works now without obsidian types. --- main.ts | 6 ++++- src/commands/index.ts | 2 +- src/processing/processor.ts | 25 +++++++++++------ src/settings/VaultTaskCache.ts | 39 +++++++++++++++++++++++---- src/settings/mainSetting.ts | 14 +++++++--- src/vaultSearcher/dataviewSearcher.ts | 2 +- src/vaultSearcher/vaultSearcher.ts | 33 +++++++++++++++++++---- 7 files changed, 96 insertions(+), 25 deletions(-) diff --git a/main.ts b/main.ts index e47346a..5eca4d7 100644 --- a/main.ts +++ b/main.ts @@ -40,16 +40,20 @@ export default class VikunjaPlugin extends Plugin { async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + this.cache = VaultTaskCache.fromJson(this.settings.cache, this.app, this); } async saveSettings() { + this.settings.cache = this.cache.getCachedTasks().map(task => { + return task.toJson(); + }); await this.saveData(this.settings); } async checkLastLineForUpdate() { if (this.settings.debugging) console.log("Checking for task update"); const updateTask = await this.processor.checkUpdateInLineAvailable() - if (!!updateTask) { + if (updateTask !== undefined) { await this.tasksApi.updateTask(updateTask.task); } else { if (this.settings.debugging) console.log("No task to update found"); diff --git a/src/commands/index.ts b/src/commands/index.ts index 73f8bff..f168574 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -141,7 +141,7 @@ export default class Commands { if (this.plugin.settings.debugging) console.log("Resetting tasks in Vikunja done"); await this.plugin.labelsApi.loadLabels(); - this.plugin.settings.cache.clear(); + this.plugin.cache.reset(); new Notice("Resetting tasks and labels in Vikunja done"); } ).open(); diff --git a/src/processing/processor.ts b/src/processing/processor.ts index 66f6f03..ea808cd 100644 --- a/src/processing/processor.ts +++ b/src/processing/processor.ts @@ -75,7 +75,11 @@ class Processor { async saveToVault(task: PluginTask) { const newTask = this.getTaskContent(task); - await this.app.vault.process(task.file, data => { + const file = this.app.vault.getFileByPath(task.filepath); + if (file === null) { + return; + } + await this.app.vault.process(file, data => { if (this.plugin.settings.appendMode) { return data + "\n" + newTask; } else { @@ -86,8 +90,9 @@ class Processor { break; } } + const content = lines.join("\n"); this.plugin.cache.update(task); - return lines.join("\n"); + return content; } }); } @@ -176,7 +181,7 @@ class Processor { if (this.plugin.settings.debugging) console.log("Processor: Last line,", lastLine, "Last line text", lastLineText); try { const parsedTask = await this.taskParser.parse(lastLineText); - updatedTask = new PluginTask(file, lastLine, parsedTask); + updatedTask = new PluginTask(file.path, lastLine, parsedTask); if (updatedTask.task.id === undefined) { return undefined; } @@ -205,14 +210,18 @@ class Processor { */ async updateToVault(task: PluginTask, metadata: boolean = true) { const newTask = (metadata) ? this.getTaskContent(task) : this.getTaskContentWithoutVikunja(task); - - await this.app.vault.process(task.file, (data: string) => { + const file = this.app.vault.getFileByPath(task.filepath); + if (file === null) { + return; + } + await this.app.vault.process(file, (data: string) => { const lines = data.split("\n"); lines.splice(task.lineno, 1, newTask); - return lines.join("\n"); + const content = lines.join("\n"); + this.plugin.cache.update(task); + return content; }); - this.plugin.cache.update(task); } getVaultSearcher(): VaultSearcher { @@ -292,7 +301,7 @@ class Processor { default: throw new Error("No valid chooseOutputFile selected"); } - const pluginTask = new PluginTask(file, 0, task); + const pluginTask = new PluginTask(file.path, 0, task); createdTasksInVault.push(pluginTask); } diff --git a/src/settings/VaultTaskCache.ts b/src/settings/VaultTaskCache.ts index d2e5371..9da38d2 100644 --- a/src/settings/VaultTaskCache.ts +++ b/src/settings/VaultTaskCache.ts @@ -2,6 +2,11 @@ import VikunjaPlugin from "../../main"; import {App, moment} from "obsidian"; import {PluginTask} from "../vaultSearcher/vaultSearcher"; + +interface Cache { + [key: number]: T; +} + /* * This class is used to cache tasks which are updated in Vault, but not in Vikunja. * This should help to identify modifications in vault without Obsidian. @@ -11,15 +16,35 @@ export default class VaultTaskCache { plugin: VikunjaPlugin; app: App; changesMade: boolean; + private cache: Map constructor(app: App, plugin: VikunjaPlugin) { this.app = app; this.plugin = plugin; this.changesMade = false; + this.cache = new Map(); + } + + public static fromJson(json: any, app: App, plugin: VikunjaPlugin): VaultTaskCache { + const cache = new VaultTaskCache(app, plugin); + console.log("VaultTaskCache: Loading cache from disk", json); + const tempCache = Object.entries(json).map((taskJson: any) => { + const id = parseInt(taskJson[0]); + const task = PluginTask.fromJson(taskJson[1]); + if (task === undefined) { + return undefined + } + return [id, task]; + }).filter((task: any) => task !== undefined); + // @ts-ignore + cache.cache = new Map(tempCache); + console.log("VaultTaskCache: Loaded cache from disk", cache.cache); + return cache; } async saveCacheToDisk() { if (this.changesMade) { + if (this.plugin.settings.debugging) console.log("VaultTaskCache: Saving cache to disk"); await this.plugin.saveSettings(); } this.changesMade = false; @@ -36,19 +61,23 @@ export default class VaultTaskCache { if (this.plugin.settings.debugging) console.log("VaultTaskCache: Updating task", local.task.id, "with updated date", currentDate); local.task.updated = currentDate; } - this.plugin.settings.cache.set(local.task.id, local); + this.cache.set(local.task.id, local); + + console.log("VaultTaskCache: Updated cache", this.cache); + this.plugin.settings.cache = this.getCachedTasks().map(task => task.toJson()); + console.log("VaultTaskCache: Updated cache in settings", this.plugin.settings.cache); this.changesMade = true; } get(id: number): PluginTask | undefined { - return this.plugin.settings.cache.get(id); + return this.cache.get(id); } /* * Useful, when tasks are updated in vikunja and so the task in the cache is outdated. */ delete(id: number) { - this.plugin.settings.cache.delete(id); + this.cache.delete(id); this.changesMade = true; } @@ -56,11 +85,11 @@ export default class VaultTaskCache { * Do not forget to call delete after processing the tasks. */ getCachedTasks(): PluginTask[] { - return Array.from(this.plugin.settings.cache.values()); + return Array.from(this.cache.values()); } reset() { - this.plugin.settings.cache.clear(); + this.cache.clear(); this.changesMade = true; } } diff --git a/src/settings/mainSetting.ts b/src/settings/mainSetting.ts index 1f5338b..8b54744 100644 --- a/src/settings/mainSetting.ts +++ b/src/settings/mainSetting.ts @@ -30,7 +30,7 @@ export interface VikunjaPluginSettings { availableViews: ModelsProjectView[], selectedView: number, selectBucketForDoneTasks: number, - cache: Map, // do not touch! Only via settings/VaultTaskCache.ts + cache: PluginTask[], // do not touch! Only via settings/VaultTaskCache.ts saveCacheToDiskFrequency: number, } @@ -59,7 +59,7 @@ export const DEFAULT_SETTINGS: VikunjaPluginSettings = { availableViews: [], selectedView: 0, selectBucketForDoneTasks: 0, - cache: new Map(), + cache: [], saveCacheToDiskFrequency: 1, } @@ -162,6 +162,7 @@ export class MainSetting extends PluginSettingTab { .onChange(async (value: boolean) => { this.plugin.settings.enableCron = value; await this.plugin.saveSettings(); + this.startCronListener(); this.display(); })); @@ -193,6 +194,7 @@ export class MainSetting extends PluginSettingTab { this.plugin.settings.cronInterval = parseInt(value); await this.plugin.saveSettings(); + this.startCronListener(); } )); } @@ -204,7 +206,7 @@ export class MainSetting extends PluginSettingTab { .setValue(this.plugin.settings.saveCacheToDiskFrequency.toString()) .onChange(async (value: string) => { const parsedNumber = parseInt(value); - if (isNaN(parsedNumber)) { + if (Number.isNaN(parsedNumber)) { return; } const lowerThanMax = Math.min(parsedNumber, 60); @@ -579,12 +581,16 @@ export class MainSetting extends PluginSettingTab { } private startCacheListener() { + if (this.plugin.settings.debugging) console.log("SettingsTab: Start cache listener"); window.clearInterval(this.cacheListener); - this.cacheListener = window.setInterval(this.plugin.cache.saveCacheToDisk.bind(this), this.plugin.settings.saveCacheToDiskFrequency * 60 * 1000); + this.cacheListener = window.setInterval(async () => { + await this.plugin.cache.saveCacheToDisk() + }, this.plugin.settings.saveCacheToDiskFrequency * 60 * 1000); this.plugin.registerInterval(this.cacheListener); } private startCronListener() { + if (this.plugin.settings.debugging) console.log("SettingsTab: Start cron listener"); window.clearInterval(this.cronListener); this.cronListener = window .setInterval(async () => { diff --git a/src/vaultSearcher/dataviewSearcher.ts b/src/vaultSearcher/dataviewSearcher.ts index 7b4ee34..4638db4 100644 --- a/src/vaultSearcher/dataviewSearcher.ts +++ b/src/vaultSearcher/dataviewSearcher.ts @@ -69,7 +69,7 @@ export class DataviewSearcher implements VaultSearcher { parsed.updated = moment(file.stat.mtime).format("YYYY-MM-DDTHH:mm:ss[Z]"); } - const vaultParsed = new PluginTask(file, task.line, parsed); + const vaultParsed = new PluginTask(file.path, task.line, parsed); if (this.plugin.settings.debugging) console.log("DataviewSearcher: Parsed task", parsed); tasksFormatted.push(vaultParsed); } diff --git a/src/vaultSearcher/vaultSearcher.ts b/src/vaultSearcher/vaultSearcher.ts index 631bd65..4ee6508 100644 --- a/src/vaultSearcher/vaultSearcher.ts +++ b/src/vaultSearcher/vaultSearcher.ts @@ -1,21 +1,44 @@ -import {ModelsTask} from "../../vikunja_sdk"; +import {ModelsTask, ModelsTaskFromJSON, ModelsTaskToJSON} from "../../vikunja_sdk"; import {TaskParser} from "../taskFormats/taskFormats"; import {TFile} from "obsidian"; import {compareModelTasks} from "../processing/processor"; class PluginTask { - file: TFile; + filepath: string; lineno: number; task: ModelsTask; - constructor(file: TFile, lineno: number, task: ModelsTask) { - this.file = file; + constructor(filepath: string, lineno: number, task: ModelsTask) { + this.filepath = filepath; this.lineno = lineno; this.task = task; } + public static fromJson(json: any): PluginTask | undefined { + try { + const file = json["file"]; + const lineno = json["lineno"]; + const taskObj = json["task"]; + const task = ModelsTaskFromJSON(taskObj); + + return new PluginTask(file, lineno, task); + } catch (error) { + console.error("Error parsing json", json, error); + return undefined; + } + } + + public toJson(): any { + return { + file: this.filepath, + lineno: this.lineno, + task: ModelsTaskToJSON(this.task) + }; + + } + isEquals(pluginTask: PluginTask): boolean { - const file = this.file.path === pluginTask.file.path; + const file = this.filepath === pluginTask.filepath; const lineno = this.lineno === pluginTask.lineno; const task = this.isTaskEqual(pluginTask.task); From ca15a57187db30f5338b6763ffc92caa41874d0c Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 21:42:00 +0200 Subject: [PATCH 08/11] fix cache when pulling from vikunja --- src/processing/processor.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/processing/processor.ts b/src/processing/processor.ts index ea808cd..2d7d42c 100644 --- a/src/processing/processor.ts +++ b/src/processing/processor.ts @@ -80,8 +80,9 @@ class Processor { return; } await this.app.vault.process(file, data => { + let content; if (this.plugin.settings.appendMode) { - return data + "\n" + newTask; + content = data + "\n" + newTask; } else { const lines = data.split("\n"); for (let i = 0; i < lines.length; i++) { @@ -90,10 +91,10 @@ class Processor { break; } } - const content = lines.join("\n"); - this.plugin.cache.update(task); - return content; + content = lines.join("\n"); } + this.plugin.cache.update(task); + return content; }); } From e6657ce8f2080e56bb1a8644fcb05c1501576d4f Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 22:04:31 +0200 Subject: [PATCH 09/11] add cache to quick updates --- main.ts | 10 +++++--- src/commands/index.ts | 2 +- src/processing/updateTasks.ts | 2 +- src/vikunja/tasks.ts | 43 +++++++++++++++-------------------- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/main.ts b/main.ts index 5eca4d7..03718a1 100644 --- a/main.ts +++ b/main.ts @@ -54,7 +54,7 @@ export default class VikunjaPlugin extends Plugin { if (this.settings.debugging) console.log("Checking for task update"); const updateTask = await this.processor.checkUpdateInLineAvailable() if (updateTask !== undefined) { - await this.tasksApi.updateTask(updateTask.task); + await this.tasksApi.updateTask(updateTask); } else { if (this.settings.debugging) console.log("No task to update found"); } @@ -165,8 +165,12 @@ export default class VikunjaPlugin extends Plugin { const taskId = parseInt(match[1]); if (this.settings.debugging) console.log("Checkbox clicked for task", taskId); const task = await this.tasksApi.getTaskById(taskId); - task.done = target.checked; - await this.tasksApi.updateTask(task); + const cachedTask = this.cache.get(taskId); + if (cachedTask !== undefined) { + cachedTask.task = task; + cachedTask.task.done = target.checked; + await this.tasksApi.updateTask(cachedTask); + } } else { if (this.settings.debugging) console.log("No task id found for checkbox"); } diff --git a/src/commands/index.ts b/src/commands/index.ts index f168574..cb45819 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -79,7 +79,7 @@ export default class Commands { } if (this.plugin.settings.debugging) console.log("Move all tasks to default project"); - const tasks = (await this.getTasksFromVault()).filter(task => task.task.id !== undefined).map(task => task.task); + const tasks = (await this.getTasksFromVault()).filter(task => task.task.id !== undefined); await this.plugin.tasksApi.updateProjectsIdInVikunja(tasks, this.plugin.settings.defaultVikunjaProject); } diff --git a/src/processing/updateTasks.ts b/src/processing/updateTasks.ts index cd4a62a..058f50b 100644 --- a/src/processing/updateTasks.ts +++ b/src/processing/updateTasks.ts @@ -90,7 +90,7 @@ class UpdateTasks implements IAutomatonSteps { private async updateTasksInVikunja(updateTasks: PluginTask[]) { if (this.plugin.settings.debugging) console.log("Step UpdateTask: Update tasks in vikunja"); - await Promise.all(updateTasks.map(task => this.plugin.tasksApi.updateTask(task.task))); + await Promise.all(updateTasks.map(task => this.plugin.tasksApi.updateTask(task))); } private async updateTasksInVault(updateTasks: PluginTask[]) { diff --git a/src/vikunja/tasks.ts b/src/vikunja/tasks.ts index c09b015..b6f9d0d 100644 --- a/src/vikunja/tasks.ts +++ b/src/vikunja/tasks.ts @@ -12,6 +12,7 @@ import { TasksTaskLabelsPutRequest } from "../../vikunja_sdk"; import VikunjaAPI from "./VikunjaAPI"; +import {PluginTask} from "../vaultSearcher/vaultSearcher"; class Tasks implements VikunjaAPI { plugin: VikunjaPlugin; @@ -39,23 +40,21 @@ class Tasks implements VikunjaAPI { return await this.tasksApi.tasksAllGet(); } - async updateTask(task: ModelsTask): Promise { - if (!task.id) throw new Error("TasksApi: Task id is not defined"); - if (this.plugin.settings.debugging) console.log("TasksApi: Updating task", task.id, task); - if (task.done) { - task.bucketId = this.plugin.settings.selectBucketForDoneTasks; + async updateTask(task: PluginTask): Promise { + if (!task.task.id) { + throw new Error("TasksApi: Task id is not defined"); + } + if (this.plugin.settings.debugging) console.log("TasksApi: Updating task", task.task.id, task); + if (task.task.done) { + task.task.bucketId = this.plugin.settings.selectBucketForDoneTasks; } - await this.addLabelToTask(task); - - const param: TasksIdPostRequest = {id: task.id, task: task}; + await this.addLabelToTask(task.task); + this.plugin.cache.update(task); + const param: TasksIdPostRequest = {id: task.task.id, task: task.task}; return await this.tasksApi.tasksIdPost(param); } - async updateTasks(tasks: ModelsTask[]): Promise { - return Promise.all(tasks.map(task => this.updateTask(task))); - } - async createTask(task: ModelsTask): Promise { if (this.plugin.settings.debugging) console.log("TasksApi: Creating task", task); if (!task.projectId) throw new Error("TasksApi: Task projectId is not defined"); @@ -144,30 +143,24 @@ class Tasks implements VikunjaAPI { } } - async getTaskById(taskId: number) { + async getTaskById(taskId: number): Promise { const param: TasksIdGetRequest = {id: taskId}; - return this.tasksApi.tasksIdGet(param); + return await this.tasksApi.tasksIdGet(param); } - async updateProjectsIdInVikunja(tasks: ModelsTask[], projectId: number) { + async updateProjectsIdInVikunja(tasks: PluginTask[], projectId: number) { if (this.plugin.settings.debugging) console.log("TasksApi: Updating project id in tasks", projectId); // FIXME there is a bulkPost in tasksApi, use it instead of update any task separately return await Promise.all(tasks.map(task => this.updateProjectIdInVikunja(task, projectId))); } - async updateProjectIdInVikunja(task: ModelsTask, projectId: number) { - if (!task.id) throw new Error("TasksApi: Task id is not defined"); - if (this.plugin.settings.debugging) console.log("TasksApi: Updating project id in task", task.id, projectId); + async updateProjectIdInVikunja(task: PluginTask, projectId: number) { + if (!task.task.id) throw new Error("TasksApi: Task id is not defined"); + if (this.plugin.settings.debugging) console.log("TasksApi: Updating project id in task", task.task.id, projectId); - task.projectId = projectId; + task.task.projectId = projectId; await this.updateTask(task); } - - async updateBucketInVikunja(task: ModelsTask, bucketId: number) { - if (!task.id) throw new Error("TasksApi: Task id is not defined"); - if (this.plugin.settings.debugging) console.log("TasksApi: Updating bucket in task", task.id, bucketId); - - } } export {Tasks}; From 425a97512c5c6244cfd2867f8173fb3e471d5bc4 Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 22:20:29 +0200 Subject: [PATCH 10/11] add new setting option and move options around in settings --- main.ts | 3 + src/settings/mainSetting.ts | 208 +++++++++++++++++++----------------- 2 files changed, 114 insertions(+), 97 deletions(-) diff --git a/main.ts b/main.ts index 03718a1..1f9843a 100644 --- a/main.ts +++ b/main.ts @@ -147,6 +147,9 @@ export default class VikunjaPlugin extends Plugin { } private async handleClickEvent(evt: MouseEvent) { + if (!this.settings.updateOnCursorMovement) { + return; + } const target = evt.target as HTMLInputElement; if (this.app.workspace.activeEditor?.editor?.hasFocus()) { await this.checkLastLineForUpdate(); diff --git a/src/settings/mainSetting.ts b/src/settings/mainSetting.ts index 8b54744..f8a38eb 100644 --- a/src/settings/mainSetting.ts +++ b/src/settings/mainSetting.ts @@ -32,6 +32,7 @@ export interface VikunjaPluginSettings { selectBucketForDoneTasks: number, cache: PluginTask[], // do not touch! Only via settings/VaultTaskCache.ts saveCacheToDiskFrequency: number, + updateCompletedStatusImmediately: boolean, } export const DEFAULT_SETTINGS: VikunjaPluginSettings = { @@ -61,6 +62,7 @@ export const DEFAULT_SETTINGS: VikunjaPluginSettings = { selectBucketForDoneTasks: 0, cache: [], saveCacheToDiskFrequency: 1, + updateCompletedStatusImmediately: false, } export class MainSetting extends PluginSettingTab { @@ -284,6 +286,97 @@ export class MainSetting extends PluginSettingTab { }, 2000); })); + new Setting(containerEl) + .setHeading() + .setName("Updates: Obsidian <-> Vikunja") + + new Setting(containerEl) + .setDesc("This plugin prioritizes changes in Obsidian over Vikunja. This means, that if you make changes in both systems, the changes in Obsidian will be used over the one in Vikunja. To prevent data loss, do not make any changes in your markdown files without Obsidian."); + + new Setting(containerEl) + .setName("Check for updates on startup") + .setDesc("This will check for changes in Vault and Vikunja and update the tasks vice versa, but prioritize the changes in Obsidian. Useful, if you want to use Vikunja, but do not make any changes directly on the markdown files while obsidian is closed.") + .addToggle(toggle => + toggle + .setValue(this.plugin.settings.updateOnStartup) + .onChange(async (value: boolean) => { + this.plugin.settings.updateOnStartup = value; + await this.plugin.saveSettings(); + })); + + + new Setting(containerEl) + .setName("Select default project") + .setDesc("This project will be used to place new tasks created by this plugin.") + .addDropdown(async dropdown => { + if (this.plugin.settings.debugging) { + console.log(`SettingsTab: Got projects:`, this.projects); + } + + for (const project of this.projects) { + if (project.id === undefined || project.title === undefined) { + throw new Error("Project id or title is undefined"); + } + dropdown.addOption(project.id.toString(), project.title); + } + + dropdown.setValue(this.plugin.settings.defaultVikunjaProject.toString()); + + dropdown.onChange(async (value: string) => { + this.plugin.settings.defaultVikunjaProject = parseInt(value); + if (this.plugin.settings.debugging) console.log(`SettingsTab: Selected Vikunja project:`, this.plugin.settings.defaultVikunjaProject); + + this.plugin.settings.availableViews = await this.plugin.projectsApi.getViewsByProjectId(this.plugin.settings.defaultVikunjaProject); + if (this.plugin.settings.debugging) console.log(`SettingsTab: Available views:`, this.plugin.settings.availableViews); + + if (this.plugin.settings.availableViews.length === 1) { + const id = this.plugin.settings.availableViews[0].id; + if (id === undefined) throw new Error("View id is undefined"); + this.plugin.settings.selectedView = id; + this.plugin.settings.selectBucketForDoneTasks = await this.plugin.projectsApi.getDoneBucketIdFromKanbanView(this.plugin.settings.defaultVikunjaProject); + if (this.plugin.settings.debugging) console.log(`SettingsTab: Done bucket set to:`, this.plugin.settings.selectBucketForDoneTasks); + } + await this.plugin.saveSettings(); + this.display(); + }); + } + ) + + if (this.plugin.settings.availableViews.length > 1) { + new Setting(containerEl) + .setName("Select bucket") + .setDesc("Because vikunja does not move done tasks to the correct bucket, you have to select the bucket where the done tasks are placed, so this plugin can do it for you.") + .addDropdown(dropdown => { + let i = 0; + for (const view of this.plugin.settings.availableViews) { + if (view.id === undefined || view.title === undefined) { + throw new Error("View id or title is undefined"); + } + dropdown.addOption((i++).toString(), view.title); + } + + dropdown.setValue(this.plugin.settings.selectedView.toString()); + + dropdown.onChange(async (value: string) => { + this.plugin.settings.selectedView = parseInt(value); + if (this.plugin.settings.debugging) console.log(`SettingsTab: Selected Vikunja bucket:`, this.plugin.settings.selectedView); + + this.plugin.settings.selectBucketForDoneTasks = await this.plugin.projectsApi.getDoneBucketIdFromKanbanView(this.plugin.settings.defaultVikunjaProject); + if (this.plugin.settings.debugging) console.log(`SettingsTab: Done bucket set to:`, this.plugin.settings.selectBucketForDoneTasks); + await this.plugin.saveSettings(); + }); + }); + } + + new Setting(containerEl) + .setName("Move all tasks to selected default project") + .setDesc("This will move all tasks from Vault to the selected default project in Vikunja. This will not delete any tasks in Vikunja, but only move them to the selected project. This helps, if you make a wrong decision in the past. This does not create any tasks in Vikunja.") + .addButton(button => button + .setButtonText("Move all tasks") + .onClick(async () => { + await this.plugin.commands.moveAllTasksToDefaultProject(); + } + )); new Setting(containerEl).setHeading().setName('Pull: Obsidian <- Vikunja').setDesc(''); @@ -375,6 +468,17 @@ export class MainSetting extends PluginSettingTab { ); } + new Setting(containerEl) + .setName("Pull tasks only from default project") + .setDesc("If enabled, only tasks from the default project will be pulled from Vikunja. Useful, if you use Vikunja with several apps or different projects and Obsidian is only one of them. Beware: If you select that labels should be deleted in vikunja, if not found in vault, this will sync all labels regardless of projects.") + .addToggle(toggle => + toggle + .setValue(this.plugin.settings.pullTasksOnlyFromDefaultProject) + .onChange(async (value: boolean) => { + this.plugin.settings.pullTasksOnlyFromDefaultProject = value; + await this.plugin.saveSettings(); + })); + new Setting(containerEl) .setHeading() @@ -453,28 +557,9 @@ export class MainSetting extends PluginSettingTab { return; } - new Setting(containerEl) - .setHeading() - .setName("Updates: Obsidian <-> Vikunja") - - new Setting(containerEl) - .setDesc("This plugin prioritizes changes in Obsidian over Vikunja. This means, that if you make changes in both systems, the changes in Obsidian will be used over the one in Vikunja. To prevent data loss, do not make any changes in your markdown files without Obsidian."); - - new Setting(containerEl) - .setName("Check for updates on startup") - .setDesc("This will check for changes in Vault and Vikunja and update the tasks vice versa, but prioritize the changes in Obsidian. Useful, if you want to use Vikunja, but do not make any changes directly on the markdown files while obsidian is closed.") - .addToggle(toggle => - toggle - .setValue(this.plugin.settings.updateOnStartup) - .onChange(async (value: boolean) => { - this.plugin.settings.updateOnStartup = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName("Check for updates on cursor movement") - .setDesc("This will check for changes only on cursors last line in Vault. Useful, if you want to reduce the load on your system and faster updates.") + .setName("Check for changes on cursor movement") + .setDesc("This will check for changes on cursors last line in Vault, too. Useful, if you want to reduce the load on your system and faster updates.") .addToggle(toggle => toggle .setValue(this.plugin.settings.updateOnCursorMovement) @@ -484,88 +569,17 @@ export class MainSetting extends PluginSettingTab { })); new Setting(containerEl) - .setName("Select default project") - .setDesc("This project will be used to place new tasks created by this plugin.") - .addDropdown(async dropdown => { - if (this.plugin.settings.debugging) { - console.log(`SettingsTab: Got projects:`, this.projects); - } - - for (const project of this.projects) { - if (project.id === undefined || project.title === undefined) { - throw new Error("Project id or title is undefined"); - } - dropdown.addOption(project.id.toString(), project.title); - } - - dropdown.setValue(this.plugin.settings.defaultVikunjaProject.toString()); - - dropdown.onChange(async (value: string) => { - this.plugin.settings.defaultVikunjaProject = parseInt(value); - if (this.plugin.settings.debugging) console.log(`SettingsTab: Selected Vikunja project:`, this.plugin.settings.defaultVikunjaProject); - - this.plugin.settings.availableViews = await this.plugin.projectsApi.getViewsByProjectId(this.plugin.settings.defaultVikunjaProject); - if (this.plugin.settings.debugging) console.log(`SettingsTab: Available views:`, this.plugin.settings.availableViews); - - if (this.plugin.settings.availableViews.length === 1) { - const id = this.plugin.settings.availableViews[0].id; - if (id === undefined) throw new Error("View id is undefined"); - this.plugin.settings.selectedView = id; - this.plugin.settings.selectBucketForDoneTasks = await this.plugin.projectsApi.getDoneBucketIdFromKanbanView(this.plugin.settings.defaultVikunjaProject); - if (this.plugin.settings.debugging) console.log(`SettingsTab: Done bucket set to:`, this.plugin.settings.selectBucketForDoneTasks); - } - await this.plugin.saveSettings(); - this.display(); - }); - } - ) - - if (this.plugin.settings.availableViews.length > 1) { - new Setting(containerEl) - .setName("Select bucket") - .setDesc("Because vikunja does not move done tasks to the correct bucket, you have to select the bucket where the done tasks are placed, so this plugin can do it for you.") - .addDropdown(dropdown => { - let i = 0; - for (const view of this.plugin.settings.availableViews) { - if (view.id === undefined || view.title === undefined) { - throw new Error("View id or title is undefined"); - } - dropdown.addOption((i++).toString(), view.title); - } - - dropdown.setValue(this.plugin.settings.selectedView.toString()); - - dropdown.onChange(async (value: string) => { - this.plugin.settings.selectedView = parseInt(value); - if (this.plugin.settings.debugging) console.log(`SettingsTab: Selected Vikunja bucket:`, this.plugin.settings.selectedView); - - this.plugin.settings.selectBucketForDoneTasks = await this.plugin.projectsApi.getDoneBucketIdFromKanbanView(this.plugin.settings.defaultVikunjaProject); - if (this.plugin.settings.debugging) console.log(`SettingsTab: Done bucket set to:`, this.plugin.settings.selectBucketForDoneTasks); - await this.plugin.saveSettings(); - }); - }); - } - - new Setting(containerEl) - .setName("Pull tasks only from default project") - .setDesc("If enabled, only tasks from the default project will be pulled from Vikunja. Useful, if you use Vikunja with several apps or different projects and Obsidian is only one of them. Beware: If you select that labels should be deleted in vikunja, if not found in vault, this will sync all labels regardless of projects.") + .setName("Update completed status immediately") + .setDesc("This will update the completed status of tasks immediately to Vikunja.") .addToggle(toggle => toggle - .setValue(this.plugin.settings.pullTasksOnlyFromDefaultProject) + .setValue(this.plugin.settings.updateCompletedStatusImmediately) .onChange(async (value: boolean) => { - this.plugin.settings.pullTasksOnlyFromDefaultProject = value; + this.plugin.settings.updateCompletedStatusImmediately = value; await this.plugin.saveSettings(); })); - new Setting(containerEl) - .setName("Move all tasks to selected default project") - .setDesc("This will move all tasks from Vault to the selected default project in Vikunja. This will not delete any tasks in Vikunja, but only move them to the selected project. This helps, if you make a wrong decision in the past. This does not create any tasks in Vikunja.") - .addButton(button => button - .setButtonText("Move all tasks") - .onClick(async () => { - await this.plugin.commands.moveAllTasksToDefaultProject(); - } - )); + } async loadApi() { From b51b2befd9092624701172875991238df26484f1 Mon Sep 17 00:00:00 2001 From: Peter Heiss Date: Thu, 1 Aug 2024 22:34:11 +0200 Subject: [PATCH 11/11] updates cache fileinfo on changes in editor --- main.ts | 4 ++++ src/settings/VaultTaskCache.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/main.ts b/main.ts index 1f9843a..092bd67 100644 --- a/main.ts +++ b/main.ts @@ -132,6 +132,10 @@ export default class VikunjaPlugin extends Plugin { const cachedTask = this.cache.get(task.task.id); if (cachedTask === undefined || !cachedTask.isTaskEqual(task.task)) { this.cache.update(task); + } else { + if (cachedTask.lineno !== task.lineno || cachedTask.filepath !== task.filepath) { + this.cache.updateFileInfos(task.task.id, task.filepath, task.lineno); + } } } } diff --git a/src/settings/VaultTaskCache.ts b/src/settings/VaultTaskCache.ts index 9da38d2..9b86eae 100644 --- a/src/settings/VaultTaskCache.ts +++ b/src/settings/VaultTaskCache.ts @@ -50,6 +50,18 @@ export default class VaultTaskCache { this.changesMade = false; } + updateFileInfos(id: number, filepath: string, lineno: number) { + const cachedTask = this.get(id); + if (cachedTask === undefined) { + throw new Error("VaultTaskCache: Task is not in cache"); + } + if (this.plugin.settings.debugging) console.log("VaultTaskCache: Updating task", id, "with updated file infos", filepath, lineno); + cachedTask.filepath = filepath; + cachedTask.lineno = lineno; + this.cache.set(id, cachedTask); + this.changesMade = true; + } + update(local: PluginTask) { if (local.task.id === undefined) { throw new Error("VaultTaskCache: Task id is not defined");