From 351201c44d386a5c7662f4ebcc56af53126472eb Mon Sep 17 00:00:00 2001 From: thesamim Date: Fri, 13 Oct 2023 11:31:23 -0500 Subject: [PATCH] Working prototype. Well, 80% working --- README.md | 32 +- dist/index.css | 31 + dist/manifest.json | 11 + dist/styles.css | 22 + main.ts | 89 +- package-lock.json | 326 +------- package.json | 3 +- src/TicktickRestAPI.ts | 204 +++-- src/TicktickSyncAPI.ts | 450 ++++------- src/cacheOperation.ts | 32 +- src/fileOperation.ts | 58 +- src/modal.ts | 6 +- src/settings.ts | 25 +- src/static/.hotreload | 0 src/syncModule.ts | 1745 ++++++++++++++++++++-------------------- src/taskParser.ts | 85 +- 16 files changed, 1425 insertions(+), 1694 deletions(-) create mode 100644 dist/index.css create mode 100644 dist/manifest.json create mode 100644 dist/styles.css create mode 100644 src/static/.hotreload diff --git a/README.md b/README.md index 682249a..be4ab02 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Ultimate TickTick Sync for Obsidian +# Ultimate ticktick Sync for Obsidian > [!important] > This plugin is based on [Ultimate Todoist Sync for Obsidian](https://github.com/HeroBlackInk/ultimate-todoist-sync-for-obsidian) > as of 2023-09-28 this is a work in progress, guarantied to fail. Just saving progress at this point. -The Ultimate TickTick Sync plugin automatically creates tasks in TickTick and synchronizes task state between Obsidian and TickTick. +The Ultimate ticktick Sync plugin automatically creates tasks in ticktick and synchronizes task state between Obsidian and ticktick. ## Demo @@ -19,7 +19,7 @@ The Ultimate TickTick Sync plugin automatically creates tasks in TickTick and sy ## Features ### -| Feature | Sync from Obsidian to TickTick | Sync from TickTick to Obsidian | Description | +| Feature | Sync from Obsidian to ticktick | Sync from ticktick to Obsidian | Description | |-------------------------|-------------------------------|-------------------------------|-------------| | Add task | ✅ | 🔜 | | | Delete task | ✅ | 🔜 | | @@ -31,12 +31,12 @@ The Ultimate TickTick Sync plugin automatically creates tasks in TickTick and sy | Mark task as uncompleted| ✅ | ✅ | | | Modify project | 🔜 | 🔜 | | | Modify section | 🔜 | 🔜 | | -| Modify priority | ✅ | 🔜 | Currently, task priority only support one-way synchronization from TickTick to Obsidian. | +| Modify priority | ✅ | 🔜 | Currently, task priority only support one-way synchronization from ticktick to Obsidian. | | Add reminder | 🔜 | 🔜 | | | Move tasks between files| 🔜 | 🔜 | | | Added-at date | 🔜 | 🔜 | | | Completed-at date | 🔜 | 🔜 | | -| Task notes | 🔜 | ✅ | Currently, task notes/comments only support one-way synchronization from TickTick to Obsidian. | +| Task notes | 🔜 | ✅ | Currently, task notes/comments only support one-way synchronization from ticktick to Obsidian. | ## Installation @@ -49,10 +49,10 @@ From Obsidian v1.3.5+, you can activate this plugin within Obsidian by doing the 2. Select the `Community plugins` tab on the left 3. Make sure `Restricted mode` is **off** 4. Click `Browse` next to `Community Plugins` -5. Search for and click on `Ultimate TickTick Sync` +5. Search for and click on `Ultimate ticktick Sync` 6. Click `Install` 7. Once installed, close the `Community Plugins` window -8. Under `Installed Plugins`, activate the `Ultimate TickTick Sync` plugin +8. Under `Installed Plugins`, activate the `Ultimate ticktick Sync` plugin You can update the plugin following the same procedure, clicking `Update` instead of `Install` @@ -60,7 +60,7 @@ You can update the plugin following the same procedure, clicking `Update` instea If you would rather install the plugin manually, you can do the following: -1. Download the latest release of the plugin from the [Releases](https://github.com/HeroBlackInk/ultimate-TickTick-sync-for-obsidian/releases) page. +1. Download the latest release of the plugin from the [Releases](https://github.com/thesamim/ultimate-ticktick-sync-for-obsidian/releases) page. 2. Extract the downloaded zip file and copy the entire folder to your Obsidian plugins directory. 3. Enable the plugin in the Obsidian settings. @@ -69,8 +69,8 @@ If you would rather install the plugin manually, you can do the following: 1. Open Obsidian's `Settings` window 2. Select the `Community plugins` tab on the left -3. Under `Installed plugins`, click the gear icon next to the `Ultimate TickTick Sync` plugin -4. Enter your TickTick API token +3. Under `Installed plugins`, click the gear icon next to the `Ultimate ticktick Sync` plugin +4. Enter your ticktick API token ## Settings @@ -79,7 +79,7 @@ The time interval for automatic synchronization is set to 300 seconds by default 2. Default project New tasks will be added to the default project, and you can change the default project in the settings. 3. Full vault sync -By enabling this option, the plugin will automatically add `#TickTick` to all tasks, which will modify all files in the vault. +By enabling this option, the plugin will automatically add `#ticktick` to all tasks, which will modify all files in the vault. ## Usage @@ -88,11 +88,11 @@ By enabling this option, the plugin will automatically add `#TickTick` to all ta | Syntax | Description | Example | | --- | --- | --- | -|#TickTick|Tasks marked with `#TickTick` will be added to TickTick, while tasks without the `#TickTick` tag will not be processed.If you have enabled Full vault sync in the settings, `#TickTick` will be added automatically.| `- [ ] task #TickTick`| -| 📅YYYY-MM-DD | The date format is 📅YYYY-MM-DD, indicating the due date of a task. | `- [ ] task content 📅2025-02-05 #TickTick`
Supports the following calendar emojis.📅📆🗓🗓️| -| #projectTag | New tasks will be added to the default project(For example, inbox .), and you can change the default project in the settings or use a tag with the same name to specify a particular project. | `- [ ] taskA #TickTick` will be added to inbox.
`- [ ] taskB #tag #testProject #TickTick` will be added to testProject.| -| #tag | Note that all tags without a project of the same name are treated as normal tags | `- [ ] task #tagA #tagB #tagC #TickTick` | -| `!!` | The priority of the task (a number between 1 and 4, 4 for very urgent and 1 for natural).
**Note**: Keep in mind that very urgent is the priority 1 on clients. So, the priority 1 in the client corresponds to the number 4 here (Because that's how the official API of TickTick is designed.). | `- [ ] task !!4 #TickTick` | +|#ticktick|Tasks marked with `#ticktick` will be added to ticktick, while tasks without the `#ticktick` tag will not be processed.If you have enabled Full vault sync in the settings, `#ticktick` will be added automatically.| `- [ ] task #ticktick`| +| 📅YYYY-MM-DD | The date format is 📅YYYY-MM-DD, indicating the due date of a task. | `- [ ] task content 📅2025-02-05 #ticktick`
Supports the following calendar emojis.📅📆🗓🗓️| +| #projectTag | New tasks will be added to the default project(For example, inbox .), and you can change the default project in the settings or use a tag with the same name to specify a particular project. | `- [ ] taskA #ticktick` will be added to inbox.
`- [ ] taskB #tag #testProject #ticktick` will be added to testProject.| +| #tag | Note that all tags without a project of the same name are treated as normal tags | `- [ ] task #tagA #tagB #tagC #ticktick` | +| `!!` | The priority of the task (a number between 1 and 4, 4 for very urgent and 1 for natural).
**Note**: Keep in mind that very urgent is the priority 1 on clients. So, the priority 1 in the client corresponds to the number 4 here (Because that's how the official API of ticktick is designed.). | `- [ ] task !!4 #ticktick` | ### Set a default project for each file separately diff --git a/dist/index.css b/dist/index.css new file mode 100644 index 0000000..fc9206e --- /dev/null +++ b/dist/index.css @@ -0,0 +1,31 @@ +.code { + width: 500px; + height: 500px; + display: block; +} + +.config { + width: 500px; + height: 400px; + display: block; +} + +.go { + display: block; + float: right; + font-size: 18px; +} + +.result { + width: 500px; + height: 1000px; + display: block; +} + +.edit { + float: left; +} + +.results { + float: left; +} diff --git a/dist/manifest.json b/dist/manifest.json new file mode 100644 index 0000000..e0a8eff --- /dev/null +++ b/dist/manifest.json @@ -0,0 +1,11 @@ +{ + "id": "ultimate-TickTick-sync", + "name": "Ultimate TickTick Sync", + "version": "1.0.1", + "minAppVersion": "1.0.0", + "description": "This is the best TickTick task synchronization plugin for Obsidian so far.", + "author": "thesamim", + "authorUrl": "https://github.com/thesamim/ultimate-ticktick-sync-for-obsidian.git", + "fundingUrl": "", + "isDesktopOnly": false +} diff --git a/dist/styles.css b/dist/styles.css new file mode 100644 index 0000000..b2ebe00 --- /dev/null +++ b/dist/styles.css @@ -0,0 +1,22 @@ + +.cm-line:not(:hover):not(.cm-active) span.cm-comment { + display: none; +} + + + + + + + +/* +.cm-line span.cm-comment { + display: none; +} + +.cm-line.cm-active span.cm-comment, +.cm-line:hover span.cm-comment { + display: inline-block; +} + +*/ diff --git a/main.ts b/main.ts index facd0e1..2e848e8 100644 --- a/main.ts +++ b/main.ts @@ -14,7 +14,7 @@ import { CacheOperation } from './src/cacheOperation'; import { FileOperation } from './src/fileOperation'; //sync module -import { TickTickSync } from './src/syncModule'; +import { TickTickSync } from './src/syncModule'; //import modal @@ -22,8 +22,8 @@ import { SetDefalutProjectInTheFilepathModal } from 'src/modal'; export default class UltimateTickTickSyncForObsidian extends Plugin { settings: UltimateTickTickSyncSettings; - tickTickRestAPI: TicktickRestAPI | undefined; - tickTickSyncAPI: TicktickSyncAPI | undefined; + tickTickRestAPI: TickTickRestAPI | undefined; + tickTickSyncAPI: TickTickSyncAPI | undefined; taskParser: TaskParser | undefined; cacheOperation: CacheOperation | undefined; fileOperation: FileOperation | undefined; @@ -68,7 +68,7 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { //Determine the area where the click event occurs. If it is not in the editor, return if (!(this.app.workspace.activeEditor?.editor?.hasFocus())) { - (console.log(`editor is not focused`)) + // (console.log(`editor is not focused`)) return } const view = this.app.workspace.getActiveViewOfType(MarkdownView); @@ -131,7 +131,7 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { - //hook editor-change event, if the current line contains #TickTick, it means there is a new task + //hook editor-change event, if the current line contains #ticktick, it means there is a new task this.registerEvent(this.app.workspace.on('editor-change',async (editor,view:MarkdownView)=>{ try{ if(!this.settings.apiInitialized){ @@ -146,7 +146,6 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { return } if (!await this.checkAndHandleSyncLock()) return; - console.log(`new task check ${this.tickTickSync}`) await this.tickTickSync.lineContentNewTaskCheck(editor,view) this.syncLock = false this.saveSettings() @@ -161,7 +160,7 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { /* Using other file managers to move, obsidian triggered the delete event and deleted all tasks - //Listen to the deletion event. When the file is deleted, read the tasklist in frontMatter and delete it in batches. + //Listen to the deletion event. When the file is deleted, read the tasklist in fileMetadata and delete it in batches. this.registerEvent(this.app.metadataCache.on('deleted', async(file,prevCache) => { try{ if(!this.settings.apiInitialized){ @@ -169,19 +168,19 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { } //console.log('a new file has modified') console.log(`file deleted`) - //Read frontMatter - const frontMatter = await this.cacheOperation.getFileMetadata(file.path) - if(frontMatter === null || frontMatter.TickTickTasks === undefined){ + //Read fileMetadata + const fileMetadata = await this.cacheOperation.getFileMetadata(file.path) + if(fileMetadata === null || fileMetadata.TickTickTasks === undefined){ console.log('There is no task in the deleted files.') return } //Determine whether TickTickTasks is null - console.log(frontMatter.TickTickTasks) + console.log(fileMetadata.TickTickTasks) if(!( this.checkModuleClass())){ return } if (!await this.checkAndHandleSyncLock()) return; - await this.TickTickSync.deleteTasksByIds(frontMatter.TickTickTasks) + await this.TickTickSync.deleteTasksByIds(fileMetadata.TickTickTasks) this.syncLock = false this.saveSettings() }catch(error){ @@ -200,12 +199,12 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { if(!this.settings.apiInitialized){ return } - console.log(`${oldpath} is renamed`) - //Read frontMatter - //const frontMatter = await this.fileOperation.getFrontMatter(file) - const frontMatter = await this.cacheOperation.getFileMetadata(oldpath) - console.log(frontMatter) - if(frontMatter === null || frontMatter.TickTickTasks === undefined){ + // console.log(`${oldpath} is renamed`) + //Read fileMetadata + //const fileMetadata = await this.fileOperation.getFileMetadata(file) + const fileMetadata = await this.cacheOperation.getFileMetadata(oldpath) + // console.log(fileMetadata) + if(fileMetadata === null || fileMetadata.TickTickTasks === undefined){ //console.log('There is no task in the deleted file') return } @@ -234,7 +233,7 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { return } const filepath = file.path - console.log(`${filepath} is modified`) + // console.log(`${filepath} is modified`) //get current view @@ -284,11 +283,12 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { //display default project for the current file on status bar // This adds a status bar item to the bottom of the app. Does not work on mobile apps. this.statusBar = this.addStatusBarItem(); + console.log(`Ultimate TickTick Sync for Obsidian loaded!`) } - + async onunload() { console.log(`Ultimate TickTick Sync for Obsidian unloaded!`) await this.saveSettings() @@ -321,25 +321,25 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { } async modifyTickTickAPI(){ - console.log("modify") + // console.log("modify") await this.initializePlugin() } // return true of false async initializePlugin(){ - console.log("new api") + // console.log("new api") //initialize TickTick restapi this.tickTickRestAPI = await new TickTickRestAPI(this.app, this) - + //initialize data read and write object this.cacheOperation = new CacheOperation(this.app, this) const isProjectsSaved = await this.cacheOperation.saveProjectsToCache() - console.log(isProjectsSaved) + // console.log(isProjectsSaved) + - if(!isProjectsSaved){ this.tickTickRestAPI = undefined this.tickTickSyncAPI = undefined @@ -362,12 +362,12 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { //initialize file operation this.fileOperation = new FileOperation(this.app,this) - // //initialize todoisy sync api - // this.TickTickSyncAPI = new TickTickSyncAPI(this.app,this) + //initialize ticktick sync api + this.tickTickSyncAPI = new TickTickSyncAPI(this.app,this) //initialize TickTick sync module this.tickTickSync = new TickTickSync(this.app,this) - console.log(`ticktick sync : ${this.tickTickSync}`) + // console.log('ticktick sync : ', this.tickTickSync) ; //Back up all data before each startup this.tickTickSync.backupTickTickAllResources() @@ -402,9 +402,12 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { } async initializeModuleClass(){ - + // console.log("initializeModuleClass") //initialize TickTick restapi - this.tickTickRestAPI = new TickTickRestAPI(this.app, this); + if (!this.tickTickRestAPI) { + // console.log("API wasn't inited?") + this.tickTickRestAPI = new TickTickRestAPI(this.app, this); + } //initialize data read and write object this.cacheOperation = new CacheOperation(this.app,this) @@ -443,25 +446,25 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { if(this.lastLines.has(fileName as string) && line !== this.lastLines.get(fileName as string)){ const lastLine = this.lastLines.get(fileName as string) if(this.settings.debugMode){ - console.log('Line changed!', `current line is ${line}`, `last line is ${lastLine}`); + // console.log('Line changed!', `current line is ${line}`, `last line is ${lastLine}`); } //Perform the operation you want const lastLineText = view.editor.getLine(lastLine as number) - //console.log(lastLineText) + // console.log(lastLineText) if(!( this.checkModuleClass())){ return } this.lastLines.set(fileName as string, line as number); - try{ + // try{ if (!await this.checkAndHandleSyncLock()) return; await this.tickTickSync.lineModifiedTaskCheck(filepath as string,lastLineText,lastLine as number,fileContent) this.syncLock = false; - }catch(error){ - console.error(`An error occurred while check modified task in line text: ${error}`); - this.syncLock = false - } + // }catch(error){ + // console.error(`An error occurred while check modified task in line text: ${error}`); + // this.syncLock = false + // } @@ -486,7 +489,7 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { const taskElement = target.closest("div"); //Use the evt.target.closest() method to find a specific parent element instead of directly accessing a specific index in the event path //console.log(taskElement) if (!taskElement) return; - const regex = /\[TickTick_id:: (\d+)\]/; // Matches a string in the format [TickTick_id:: number] + const regex = /\[ticktick_id:: (\d+)\]/; // Matches a string in the format [ticktick_id:: number] const match = taskElement.textContent?.match(regex) || false; if (match) { const taskId = match[1]; @@ -515,7 +518,7 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { //return true checkModuleClass(){ if(this.settings.apiInitialized === true){ - if(this.TickTickRestAPI === undefined || this.TickTickSyncAPI === undefined ||this.cacheOperation === undefined || this.fileOperation === undefined ||this.tickTickSync === undefined ||this.taskParser === undefined){ + if(this.tickTickRestAPI === undefined || this.tickTickSyncAPI === undefined ||this.cacheOperation === undefined || this.fileOperation === undefined ||this.tickTickSync === undefined ||this.taskParser === undefined){ this.initializeModuleClass() } return true @@ -539,12 +542,12 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { else{ const filepath = this.app.workspace.getActiveViewOfType(MarkdownView)?.file.path if(filepath === undefined){ - console.log(`file path undefined`) + // console.log(`file path undefined`) return } const defaultProjectName = await this.cacheOperation.getDefaultProjectNameForFilepath(filepath as string) if(defaultProjectName === undefined){ - console.log(`projectName undefined`) + // console.log(`projectName undefined`) return } this.statusBar.setText(defaultProjectName) @@ -631,12 +634,12 @@ export default class UltimateTickTickSyncForObsidian extends Plugin { async checkAndHandleSyncLock() { if (this.syncLock) { - console.log('sync locked.'); + // console.log('sync locked.'); const isSyncLockChecked = await this.checkSyncLock(); if (!isSyncLockChecked) { return false; } - console.log('sync unlocked.') + // console.log('sync unlocked.') } this.syncLock = true; return true; diff --git a/package-lock.json b/package-lock.json index 1b8b0d7..53bb813 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "GNU GPLv3", "dependencies": { - "@doist/TickTick-api-typescript": "^2.1.2", "esbuild-plugin-copy": "^2.0.1", "ticktick-api-lvt": "file:../ticktick-api-lvt" }, @@ -61,17 +60,6 @@ "node": ">=0.10.0" } }, - "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@codemirror/state": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", @@ -80,9 +68,9 @@ "peer": true }, "node_modules/@codemirror/view": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.19.0.tgz", - "integrity": "sha512-XqNIfW/3GaaF+T7Q1jBcRLCPm1NbrR2DBxrXacSt1FG+rNsdsNn3/azAfgpUoJ7yy4xgd8xTPa3AlL+y0lMizQ==", + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.21.3.tgz", + "integrity": "sha512-8l1aSQ6MygzL4Nx7GVYhucSXvW4jQd0F6Zm3v9Dg+6nZEfwzJVqi4C2zHfDljID+73gsQrWp9TgHc81xU15O4A==", "dev": true, "peer": true, "dependencies": { @@ -91,19 +79,6 @@ "w3c-keyname": "^2.2.4" } }, - "node_modules/@doist/TickTick-api-typescript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@doist/TickTick-api-typescript/-/TickTick-api-typescript-2.1.2.tgz", - "integrity": "sha512-6aCY8FVERMBQ+hDcADGjz76f8fZwRww3gIg05/KkcGhwqq2nfzQAPJ0d3T2F67yWTRCAmxMSupERqFJbCRXDXA==", - "dependencies": { - "axios": "^0.27.0", - "axios-case-converter": "^0.11.0", - "axios-retry": "^3.1.9", - "runtypes": "^6.5.0", - "ts-custom-error": "^3.2.0", - "uuid": "^9.0.0" - } - }, "node_modules/@esbuild/android-arm": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", @@ -451,9 +426,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", "dev": true, "peer": true, "engines": { @@ -485,9 +460,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", "dev": true, "peer": true, "engines": { @@ -572,21 +547,21 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", + "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, "node_modules/@types/node": { - "version": "16.18.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.50.tgz", - "integrity": "sha512-OiDU5xRgYTJ203v4cprTs0RwOCd5c5Zjv+K5P8KSqfiCsB1W3LcamTUMcnQarpq5kOYbhHfSOgIEJvdPyb5xyw==", + "version": "16.18.58", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.58.tgz", + "integrity": "sha512-YGncyA25/MaVtQkjWW9r0EFBukZ+JulsLcVZBlGUfIb96OBMjkoRWwQo5IEWJ8Fj06Go3GHw+bjYDitv6BaGsA==", "dev": true }, "node_modules/@types/tern": { @@ -873,43 +848,6 @@ "node": ">=8" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "node_modules/axios-case-converter": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/axios-case-converter/-/axios-case-converter-0.11.1.tgz", - "integrity": "sha512-i5hrkBg7SE9jsm2Q+ClznR5DsKcYXChH6Cc3Rhx2p4gdIfJwvvO5/ATcAg/vN2UVzGE2B1eR1O4VuEGkICdJdQ==", - "dependencies": { - "camel-case": "^4.1.1", - "header-case": "^2.0.3", - "snake-case": "^3.0.3", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "axios": ">=0.23.0 <2.0.0" - } - }, - "node_modules/axios-retry": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.7.0.tgz", - "integrity": "sha512-ZTnCkJbRtfScvwiRnoVskFAfvU0UG3xNcsjwTR0mawSbIJoothxn67gKsMaNAFHRXJ1RmuLhmZBzvyXi3+9WyQ==", - "dependencies": { - "@babel/runtime": "^7.15.4", - "is-retry-allowed": "^2.2.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -969,25 +907,6 @@ "node": ">=6" } }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/capital-case": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1045,17 +964,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1102,14 +1010,6 @@ "dev": true, "peer": true }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1134,15 +1034,6 @@ "node": ">=6.0.0" } }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/esbuild": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", @@ -1207,16 +1098,16 @@ } }, "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", "dev": true, "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", + "@eslint/js": "8.51.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1520,13 +1411,13 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dev": true, "peer": true, "dependencies": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" }, @@ -1535,44 +1426,12 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true, "peer": true }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -1645,9 +1504,9 @@ } }, "node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "peer": true, "dependencies": { @@ -1699,15 +1558,6 @@ "node": ">=8" } }, - "node_modules/header-case": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", - "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", - "dependencies": { - "capital-case": "^1.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -1809,17 +1659,6 @@ "node": ">=8" } }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1873,9 +1712,9 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "peer": true, "dependencies": { @@ -1919,14 +1758,6 @@ "dev": true, "peer": true }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dependencies": { - "tslib": "^2.0.3" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1959,25 +1790,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2013,15 +1825,6 @@ "dev": true, "peer": true }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2117,15 +1920,6 @@ "node": ">=6" } }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2225,11 +2019,6 @@ "node": ">=8.10.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" - }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -2299,11 +2088,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/runtypes": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/runtypes/-/runtypes-6.7.0.tgz", - "integrity": "sha512-3TLdfFX8YHNFOhwHrSJza6uxVBmBrEjnNQlNXvXCdItS0Pdskfg5vVXUTWIN+Y23QR09jWpSl99UHkA83m4uWA==" - }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -2350,15 +2134,6 @@ "node": ">=8" } }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2425,18 +2200,11 @@ "node": ">=8.0" } }, - "node_modules/ts-custom-error": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", - "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true }, "node_modules/tsutils": { "version": "3.21.0", @@ -2506,14 +2274,6 @@ "node": ">= 10.0.0" } }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dependencies": { - "tslib": "^2.0.3" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2524,18 +2284,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/package.json b/package.json index 18dbf13..ceeb303 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,7 @@ "typescript": "4.7.4" }, "dependencies": { - "@doist/TickTick-api-typescript": "^2.1.2", - "esbuild-plugin-copy": "^2.0.1", + "esbuild-plugin-copy": "^2.0.1", "ticktick-api-lvt": "file:../ticktick-api-lvt" } } diff --git a/src/TicktickRestAPI.ts b/src/TicktickRestAPI.ts index ef4d0b2..e4628c9 100644 --- a/src/TicktickRestAPI.ts +++ b/src/TicktickRestAPI.ts @@ -1,4 +1,5 @@ import { Tick } from 'ticktick-api-lvt' +import ITask from "ticktick-api-lvt/src/types/Task" import { App} from 'obsidian'; import UltimateTickTickSyncForObsidian from "../main"; //convert date from obsidian event @@ -25,40 +26,38 @@ export class TickTickRestAPI { [x: string]: any; app:App; plugin: UltimateTickTickSyncForObsidian; - api: TickTickRestAPI; + api: Tick; constructor(app:App, plugin:UltimateTickTickSyncForObsidian) { //super(app,settings); this.app = app; this.plugin = plugin; - this.api = this.initializeAPI(); } async initializeAPI(){ - const api = new Tick({username: this.plugin.settings.username, password: this.plugin.settings.password}); - let apiInitialized = await api.login(); - console.log(`Logged In: ${apiInitialized}`); - this.plugin.settings.apiInitialized = apiInitialized; - return(api) + //Because we can't have async constructors, make sure the first call initializes the API + if (this.api === null || this.api === undefined) { + // console.log("intializing") + const api = await new Tick({username: this.plugin.settings.username, password: this.plugin.settings.password}); + let apiInitialized = await api.login(); + if (apiInitialized) { + this.api = api; + // console.log(`Logged In: ${apiInitialized}`); + this.plugin.settings.apiInitialized = apiInitialized; + } + } } - async AddTask({ projectId, content, parentId = null, dueDate, dueDatetime,labels, description,priority }: { projectId: string, content: string, parentId?: string , dueDate?: string, dueDatetime?: string, labels?: Array, description?: string,priority?:number }) { - const api = await this.initializeAPI() + async AddTask(taskToAdd: ITask) { + await this.initializeAPI(); try { - if(dueDate){ - dueDatetime = localDateStringToUTCDatetimeString(dueDatetime) - dueDate = null + if(taskToAdd.dueDate){ + taskToAdd.dueDate = fixDueDate(taskToAdd.dueDate); } - const newTask = await api.addTask({ - projectId, - content, - parentId, - dueDate, - labels, - description, - priority - }, false); + const newTask = await this.api.addTask(taskToAdd); + //Todo clean this up. + newTask.url = `https://ticktick.com/webapp/#q/all/tasks/${newTask.id}` return newTask; } catch (error) { throw new Error(`Error adding task: ${error.message}`); @@ -67,10 +66,13 @@ export class TickTickRestAPI { //options:{ projectId?: string, section_id?: string, label?: string , filter?: string,lang?: string, ids?: Array} - async GetActiveTasks(options:{ projectId?: string, section_id?: string, label?: string , filter?: string,lang?: string, ids?: Array}) { - const api = await this.initializeAPI() + // async GetActiveTasks(options:{ projectId?: string, section_id?: string, label?: string , filter?: string,lang?: string, ids?: Array}) { + async GetActiveTasks() { + await this.initializeAPI(); try { - const result = await api.getTasks(options); + //TODO: Filtering here. + // console.log("getting all tasks, look into filtering.") + const result = await this.api.getTasks(); return result; } catch (error) { throw new Error(`Error get active tasks: ${error.message}`); @@ -80,22 +82,20 @@ export class TickTickRestAPI { //Also note that to remove the due date of a task completely, you should set the due_string parameter to no date or no due date. //api does not have a function to update task project id - async UpdateTask(taskId: string, updates: { content?: string, description?: string, labels?:Array,dueDate?: string,dueDatetime?: string,dueString?:string,parentId?:string,priority? :number }) { - const api = await this.initializeAPI() - if (!taskId) { - throw new Error('taskId is required'); - } - if (!updates.content && !updates.description &&!updates.dueDate && !updates.dueDatetime && !updates.dueString && !updates.labels &&!updates.parentId && !updates.priority) { - throw new Error('At least one update is required'); - } + //TODO: Project ID???? + async UpdateTask(taskToUpdate: ITask) { + await this.initializeAPI(); + // if (!taskId) { + // throw new Error('taskId is required'); + // } + // if (!updates.content && !updates.description &&!updates.dueDate && !updates.labels &&!updates.parentId && !updates.priority) { + // throw new Error('At least one update is required'); + // } try { - if(updates.dueDate){ - console.log(updates.dueDate) - updates.dueDatetime = localDateStringToUTCDatetimeString(updates.dueDate) - updates.dueDate = null - console.log(updates.dueDatetime) + if(taskToUpdate.dueDate){ + taskToUpdate.dueDate = fixDueDate(taskToUpdate.dueDate); } - const updatedTask = await api.updateTask(taskId, updates); + const updatedTask = await this.api.updateTask(taskToUpdate); return updatedTask; } catch (error) { throw new Error(`Error updating task: ${error.message}`); @@ -103,17 +103,29 @@ export class TickTickRestAPI { } + async modifyTaskStatus(taskId: string, projectId: string, taskStatus: number) { + await this.initializeAPI(); + try { + let thisTask = await this.api.getTask(taskId, projectId); + // console.log("Got task: ", thisTask) + thisTask.status = taskStatus; + thisTask.dueDate = fixDueDate(thisTask.dueDate); + + const isSuccess = await this.api.updateTask(thisTask); + // console.log(`Task ${taskId} is reopened`) + return(isSuccess) + } catch (error) { + console.error('Error modifying task:', error); + return + } + } //open a task - async OpenTask(taskId:string) { - const api = await this.initializeAPI() + async OpenTask(taskId:string, projectId: string) { + await this.initializeAPI(); try { - - const isSuccess = await api.reopenTask(taskId); - console.log(`Task ${taskId} is reopened`) - return(isSuccess) - + this.modifyTaskStatus(taskId, projectId, 0) } catch (error) { console.error('Error open a task:', error); return @@ -121,12 +133,11 @@ export class TickTickRestAPI { } // Close a task in TickTick API - async CloseTask(taskId: string): Promise { - const api = await this.initializeAPI() + async CloseTask(taskId: string, projectId: string): Promise { + await this.initializeAPI(); try { - const isSuccess = await api.closeTask(taskId); - console.log(`Task ${taskId} is closed`) - return isSuccess; + let result = this.modifyTaskStatus(taskId, projectId, 2); + return result; } catch (error) { console.error('Error closing task:', error); throw error; // Throw an error so that the caller can catch and handle it @@ -137,13 +148,13 @@ export class TickTickRestAPI { // get a task by Id - async getTaskById(taskId: string) { - const api = await this.initializeAPI() + async getTaskById(taskId: string, projectId: string) { + await this.initializeAPI(); if (!taskId) { throw new Error('taskId is required'); } try { - const task = await api.getTask(taskId); + const task = await this.api.getTask(taskId, projectId); return task; } catch (error) { if (error.response && error.response.status) { @@ -157,26 +168,25 @@ export class TickTickRestAPI { //get a task due by id async getTaskDueById(taskId: string) { - const api = await this.initializeAPI() + await this.initializeAPI(); if (!taskId) { throw new Error('taskId is required'); } try { - const task = await api.getTask(taskId); - const due = task.due ?? null + const task = await this.api.getTask(taskId); + const due = task[0].due ?? null return due; } catch (error) { - throw new Error(`Error updating task: ${error.message}`); + throw new Error(`Error get Task Due By ID: ${error.message}`); } } //get all projects async GetAllProjects() { - console.log("Get ALL PRoects") - const api = await this.initializeAPI() + await this.initializeAPI(); try { - const result = await api.getProjects(); + const result = await this.api.getProjects(); return(result) } catch (error) { @@ -187,11 +197,10 @@ export class TickTickRestAPI { //get project groups async GetProjectGroups() { - console.log("Get project gruops") - const api = await this.initializeAPI() + await this.initializeAPI(); try { - const result = await api.getProjectGroups() - + const result = await this.api.getProjectGroups() + return(result) } catch (error) { @@ -199,26 +208,55 @@ export class TickTickRestAPI { return false } } + //TODO: Added for completeness, but I don't thing we use it anywhere. + async getUserResources() : Promise { + await this.initializeAPI(); + try { + const result = await this.api.getUserSettings(); + + return(result) + + } catch (error) { + console.error('Error get all projects', error); + return [] + } + } -} - - - - - - - - - - - - - - - - - - + //TODO: Added for completeness, but I don't thing we use it anywhere. + async getAllCompletedItems() : Promise { + await this.initializeAPI(); + try { + const result = await this.api.getAllCompletedItems(); + + return(result) + + } catch (error) { + console.error('Error get all projects', error); + return [] + } + } + //TODO: Will need interpretation + async getAllResources() : Promise { + await this.initializeAPI(); + try { + const result = await this.api.getAllResources(); + + return(result) + + } catch (error) { + console.error('Error get all projects', error); + return [] + } + } +} +function fixDueDate(dueDate: string) : string{ + if (dueDate) { + console.log(dueDate); + dueDate = localDateStringToUTCDatetimeString(dueDate); + console.log(dueDate); + } + return dueDate; +} diff --git a/src/TicktickSyncAPI.ts b/src/TicktickSyncAPI.ts index 0ef06d8..1eca962 100644 --- a/src/TicktickSyncAPI.ts +++ b/src/TicktickSyncAPI.ts @@ -31,30 +31,10 @@ export class TickTickSyncAPI { //backup TickTick async getAllResources() { - const accessToken = this.plugin.settings.TickTickAPIToken - const url = 'https://api.TickTick.com/sync/v9/sync'; - const options = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - sync_token: "*", - resource_types: '["all"]' - }) - }; - try { - const response = await fetch(url, options); - - if (!response.ok) { - throw new Error(`Failed to fetch all resources: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - + let data = this.plugin.tickTickRestAPI.getAllResources(); return data; + } catch (error) { console.error(error); throw new Error('Failed to fetch all resources due to network error'); @@ -63,29 +43,10 @@ export class TickTickSyncAPI { //backup TickTick async getUserResource() { - const accessToken = this.plugin.settings.TickTickAPIToken - const url = 'https://api.TickTick.com/sync/v9/sync'; - const options = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - sync_token: "*", - resource_types: '["user_plan_limits"]' - }) - }; + try { - const response = await fetch(url, options); - - if (!response.ok) { - throw new Error(`Failed to fetch all resources: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - console.log(data) + let data = this.plugin.tickTickRestAPI.getUserResources() return data; } catch (error) { console.error(error); @@ -95,286 +56,183 @@ export class TickTickSyncAPI { - //update user timezone - async updateUserTimezone() { - const unixTimestampString: string = Math.floor(Date.now() / 1000).toString(); - const accessToken = this.plugin.settings.TickTickAPIToken - const url = 'https://api.TickTick.com/sync/v9/sync'; - const commands = [ - { - 'type': "user_update", - 'uuid': unixTimestampString, - 'args': { 'timezone': 'Asia/Shanghai' }, - }, - ]; - const options = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ commands: JSON.stringify(commands) }) - }; - - try { - const response = await fetch(url, options); + + + + async getNonObsidianAllTasks() { + try{ + const allActivity = await this.plugin.tickTickRestAPI?.GetActiveTasks(); + // console.log("this is what we got: " + allActivity?.map(task => task.title)); + //client does not contain obsidian's activity + // const filteredArray = allActivity.filter(obj => !obj.extra_data.client?.includes("obsidian")); + // const filteredArray = allActivity.filter(obj => !obj.tags?.includes("obsidian")); + const filteredArray = allActivity; + return(filteredArray) - if (!response.ok) { - throw new Error(`Failed to fetch all resources: ${response.status} ${response.statusText}`); - } + }catch(err){ + console.error('An error occurred:', err); + } + + } + + + + + + filterActivityTasks(events: Event[], options: FilterOptions): Event[] { + return events.filter(event => + (options.event_type ? event.event_type === options.event_type : true) && + (options.object_type ? event.object_type === options.object_type : true) - const data = await response.json(); - console.log(data) + ); + }; + + //get completed items activity + //result {count:number,events:[]} + async getCompletedItemsActivity() { + const data = this.plugin.tickTickRestAPI.getAllCompletedItems(); return data; } catch (error) { console.error(error); - throw new Error('Failed to fetch user resources due to network error'); + throw new Error('Failed to fetch completed items due to network error'); } - } - - //get activity logs - //result {count:number,events:[]} - async getAllActivityEvents() { - const accessToken = this.plugin.settings.TickTickAPIToken - const headers = new Headers({ - Authorization: `Bearer ${accessToken}` - }); - try { - const response = await fetch('https://api.TickTick.com/sync/v9/activity/get', { - method: 'POST', - headers, - body: JSON.stringify({}) - }); - if (!response.ok) { - throw new Error(`API returned error status: ${response.status}`); + //get uncompleted items activity + //result {count:number,events:[]} + async getUncompletedItemsActivity() : any[] { + + const data = this.plugin.tickTickRestAPI.getTasks(); + + return data; + } + + + //get non-obsidian completed event + async getNonObsidianCompletedItemsActivity() { + const accessToken = this.plugin.settings.TickTickAPIToken + const completedItemsActivity = await this.getCompletedItemsActivity() + const completedItemsActivityEvents = completedItemsActivity.events + //client does not contain obsidian's activity + const filteredArray = completedItemsActivityEvents.filter(obj => !obj.extra_data.client.includes("obsidian")); + return(filteredArray) } - const data = await response.json(); - return data; - } catch (error) { - throw error; - } -} - -async getNonObsidianAllActivityEvents() { - try{ - const allActivity = await this.getAllActivityEvents() - //console.log(allActivity) - const allActivityEvents = allActivity.events - //client does not contain obsidian's activity - const filteredArray = allActivityEvents.filter(obj => !obj.extra_data.client?.includes("obsidian")); - //console.log(filteredArray) - return(filteredArray) + //get non-obsidian uncompleted event + async getNonObsidianUncompletedItemsActivity() { + const uncompletedItemsActivity = await this.getUncompletedItemsActivity() + const uncompletedItemsActivityEvents = uncompletedItemsActivity.events + //client does not contain obsidian's activity + const filteredArray = uncompletedItemsActivityEvents.filter(obj => !obj.extra_data.client.includes("obsidian")); + return(filteredArray) + } - }catch(err){ - console.error('An error occurred:', err); - } - -} - - - - - -filterActivityEvents(events: Event[], options: FilterOptions): Event[] { - return events.filter(event => - (options.event_type ? event.event_type === options.event_type : true) && - (options.object_type ? event.object_type === options.object_type : true) + async getUpdatedItemsActivity() { + throw new Error("Updated Items call not implemented in TickTick. What do we need it for?") + } + // //get updated items activity + // //result {count:number,events:[]} + // async getUpdatedItemsActivity() { + // const accessToken = this.plugin.settings.TickTickAPIToken + // const url = 'https://api.TickTick.com/sync/v9/activity/get'; + // const options = { + // method: 'POST', + // headers: { + // 'Authorization': `Bearer ${accessToken}`, + // 'Content-Type': 'application/x-www-form-urlencoded' + // }, + // body: new URLSearchParams({ + // 'object_type': 'item', + // 'event_type': 'updated' + // }) + // }; - ); - }; - - //get completed items activity - //result {count:number,events:[]} - async getCompletedItemsActivity() { - const accessToken = this.plugin.settings.TickTickAPIToken - const url = 'https://api.TickTick.com/sync/v9/activity/get'; - const options = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - 'object_type': 'item', - 'event_type': 'completed' - }) - }; + // try { + // const response = await fetch(url, options); - try { - const response = await fetch(url, options); - - if (!response.ok) { - throw new Error(`Failed to fetch completed items: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - - return data; - } catch (error) { - console.error(error); - throw new Error('Failed to fetch completed items due to network error'); + // if (!response.ok) { + // throw new Error(`Failed to fetch updated items: ${response.status} ${response.statusText}`); + // } + + // const data = await response.json(); + // //console.log(data) + // return data; + // } catch (error) { + // console.error(error); + // throw new Error('Failed to fetch updated items due to network error'); + // } + // } + + + //get non-obsidian updated event + async getNonObsidianUpdatedItemsActivity() { + const updatedItemsActivity = await this.getUpdatedItemsActivity() + const updatedItemsActivityEvents = updatedItemsActivity.events + //client does not contain obsidian's activity + const filteredArray = updatedItemsActivityEvents.filter(obj => { + const client = obj.extra_data && obj.extra_data.client; + return !client || !client.includes("obsidian"); + }); + return(filteredArray) + } + + async getProjectsActivity() { + throw new Error("Project Activities no impmlemented in TickTick") } + + // //get completed items activity + // //result {count:number,events:[]} + // async getProjectsActivity() { + // const accessToken = this.plugin.settings.TickTickAPIToken + // const url = 'https://api.TickTick.com/sync/v9/activity/get'; + // const options = { + // method: 'POST', + // headers: { + // 'Authorization': `Bearer ${accessToken}`, + // 'Content-Type': 'application/x-www-form-urlencoded' + // }, + // body: new URLSearchParams({ + // 'object_type': 'project' + // }) + // }; + + // try { + // const response = await fetch(url, options); + + // if (!response.ok) { + // throw new Error(`Failed to fetch projects activities: ${response.status} ${response.statusText}`); + // } + + // const data = await response.json(); + + // return data; + // } catch (error) { + // console.error(error); + // throw new Error('Failed to fetch projects activities due to network error'); + // } + // } } - //get uncompleted items activity - //result {count:number,events:[]} - async getUncompletedItemsActivity() { - const accessToken = this.plugin.settings.TickTickAPIToken - const url = 'https://api.TickTick.com/sync/v9/activity/get'; - const options = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - 'object_type': 'item', - 'event_type': 'uncompleted' - }) - }; - - try { - const response = await fetch(url, options); - - if (!response.ok) { - throw new Error(`Failed to fetch uncompleted items: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - - return data; - } catch (error) { - console.error(error); - throw new Error('Failed to fetch uncompleted items due to network error'); - } - } - //get non-obsidian completed event - async getNonObsidianCompletedItemsActivity() { - const accessToken = this.plugin.settings.TickTickAPIToken - const completedItemsActivity = await this.getCompletedItemsActivity() - const completedItemsActivityEvents = completedItemsActivity.events - //client does not contain obsidian's activity - const filteredArray = completedItemsActivityEvents.filter(obj => !obj.extra_data.client.includes("obsidian")); - return(filteredArray) - } - //get non-obsidian uncompleted event - async getNonObsidianUncompletedItemsActivity() { - const uncompletedItemsActivity = await this.getUncompletedItemsActivity() - const uncompletedItemsActivityEvents = uncompletedItemsActivity.events - //client does not contain obsidian's activity - const filteredArray = uncompletedItemsActivityEvents.filter(obj => !obj.extra_data.client.includes("obsidian")); - return(filteredArray) - } - //get updated items activity - //result {count:number,events:[]} - async getUpdatedItemsActivity() { - const accessToken = this.plugin.settings.TickTickAPIToken - const url = 'https://api.TickTick.com/sync/v9/activity/get'; - const options = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - 'object_type': 'item', - 'event_type': 'updated' - }) - }; - - try { - const response = await fetch(url, options); - - if (!response.ok) { - throw new Error(`Failed to fetch updated items: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - //console.log(data) - return data; - } catch (error) { - console.error(error); - throw new Error('Failed to fetch updated items due to network error'); - } - } - //get non-obsidian updated event - async getNonObsidianUpdatedItemsActivity() { - const updatedItemsActivity = await this.getUpdatedItemsActivity() - const updatedItemsActivityEvents = updatedItemsActivity.events - //client does not contain obsidian's activity - const filteredArray = updatedItemsActivityEvents.filter(obj => { - const client = obj.extra_data && obj.extra_data.client; - return !client || !client.includes("obsidian"); - }); - return(filteredArray) - } - //get completed items activity - //result {count:number,events:[]} - async getProjectsActivity() { - const accessToken = this.plugin.settings.TickTickAPIToken - const url = 'https://api.TickTick.com/sync/v9/activity/get'; - const options = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - 'object_type': 'project' - }) - }; - - try { - const response = await fetch(url, options); - - if (!response.ok) { - throw new Error(`Failed to fetch projects activities: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - - return data; - } catch (error) { - console.error(error); - throw new Error('Failed to fetch projects activities due to network error'); - } - } -} - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/cacheOperation.ts b/src/cacheOperation.ts index 7555d9a..7c6e8d7 100644 --- a/src/cacheOperation.ts +++ b/src/cacheOperation.ts @@ -61,9 +61,9 @@ export class CacheOperation { } async deleteTaskIdFromMetadata(filepath:string,taskId:string){ - console.log(filepath) + // console.log(filepath) const metadata = await this.getFileMetadata(filepath) - console.log(metadata) + // console.log(metadata) const newTickTickTasks = metadata.TickTickTasks.filter(function(element){ return element !== taskId }) @@ -71,7 +71,7 @@ export class CacheOperation { let newMetadata = {} newMetadata.TickTickTasks = newTickTickTasks newMetadata.TickTickCount = newTickTickCount - console.log(`new metadata ${newMetadata}`) + // console.log(`new metadata ${newMetadata}`) } @@ -80,7 +80,7 @@ export class CacheOperation { async deleteFilepathFromMetadata(filepath:string){ Reflect.deleteProperty(this.plugin.settings.fileMetadata, filepath); this.plugin.saveSettings() - console.log(`${filepath} is deleted from file metadatas.`) + // console.log(`${filepath} is deleted from file metadatas.`) } @@ -92,7 +92,7 @@ export class CacheOperation { const value = metadatas[key]; let file = this.app.vault.getAbstractFileByPath(key) if(!file && (value.TickTickTasks?.length === 0 || !value.TickTickTasks)){ - console.log(`${key} is not existed and metadata is empty.`) + // console.log(`${key} is not existed and metadata is empty.`) await this.deleteFilepathFromMetadata(key) continue } @@ -105,12 +105,12 @@ export class CacheOperation { if(!file){ //search new filepath - console.log(`file ${filepath} is not exist`) + // console.log(`file ${filepath} is not exist`) const TickTickId1 = value.TickTickTasks[0] - console.log(TickTickId1) + // console.log(TickTickId1) const searchResult = await this.plugin.fileOperation.searchFilepathsByTaskidInVault(TickTickId1) - console.log(`new file path is`) - console.log(searchResult) + // console.log(`new file path is`) + // console.log(searchResult) //update metadata await this.updateRenamedFilePath(filepath,searchResult) @@ -250,13 +250,14 @@ export class CacheOperation { //Read the task with the specified id - loadTaskFromCacheyID(taskId) { + loadTaskFromCacheID(taskId) { + // console.log("loadTaskFromCacheID") try { const savedTasks = this.plugin.settings.TickTickTasksData.tasks - //console.log(savedTasks) + // console.log("saved Tasks", savedTasks) const savedTask = savedTasks.find((t) => t.id === taskId); - //console.log(savedTask) + // console.log("saved Task: ", savedTask) return(savedTask) } catch (error) { console.error(`Error finding task from Cache: ${error}`); @@ -422,8 +423,7 @@ export class CacheOperation { async saveProjectsToCache() { try{ //get projects - console.log("here") - console.log(`Save Projects to cachetry with ${this.plugin.tickTickRestAPI}`) + // console.log(`Save Projects to cache with ${this.plugin.tickTickRestAPI}`) const projectGroups = await this.plugin.tickTickRestAPI?.GetProjectGroups(); const projects = await this.plugin.tickTickRestAPI?.GetAllProjects(); @@ -476,8 +476,8 @@ export class CacheOperation { async updateRenamedFilePath(oldpath:string,newpath:string){ try{ - console.log(`oldpath is ${oldpath}`) - console.log(`newpath is ${newpath}`) + // console.log(`oldpath is ${oldpath}`) + // console.log(`newpath is ${newpath}`) const savedTask = await this.loadTasksFromCache() //console.log(savedTask) const newTasks = savedTask.map(obj => { diff --git a/src/fileOperation.ts b/src/fileOperation.ts index 6e347aa..3ed401a 100644 --- a/src/fileOperation.ts +++ b/src/fileOperation.ts @@ -12,10 +12,10 @@ export class FileOperation { } /* - async getFrontMatter(file:TFile): Promise { + async getFileMetadata(file:TFile): Promise { return new Promise((resolve) => { - this.app.fileManager.processFrontMatter(file, (frontMatter) => { - resolve(frontMatter); + this.app.fileManager.processFileMetadata(file, (fileMetadata) => { + resolve(fileMetadata); }); }); } @@ -25,19 +25,19 @@ export class FileOperation { /* - async updateFrontMatter( + async updateFileMetadata( file:TFile, - updater: (frontMatter: FrontMatter) => void + updater: (fileMetadata: FileMetadata) => void ): Promise { //console.log(`prepare to update front matter`) - this.app.fileManager.processFrontMatter(file, (frontMatter) => { - if (frontMatter !== null) { - const updatedFrontMatter = { ...frontMatter } as FrontMatter; - updater(updatedFrontMatter); - this.app.fileManager.processFrontMatter(file, (newFrontMatter) => { - if (newFrontMatter !== null) { - newFrontMatter.TickTickTasks = updatedFrontMatter.TickTickTasks; - newFrontMatter.TickTickCount = updatedFrontMatter.TickTickCount; + this.app.fileManager.processFileMetadata(file, (fileMetadata) => { + if (fileMetadata !== null) { + const updatedFileMetadata = { ...fileMetadata } as FileMetadata; + updater(updatedFileMetadata); + this.app.fileManager.processFileMetadata(file, (newFileMetadata) => { + if (newFileMetadata !== null) { + newFileMetadata.TickTickTasks = updatedFileMetadata.TickTickTasks; + newFileMetadata.TickTickCount = updatedFileMetadata.TickTickCount; } }); } @@ -52,7 +52,7 @@ export class FileOperation { //Complete a task and mark it as completed async completeTaskInTheFile(taskId: string) { // Get the task file path - const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId) + const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheID(taskId) const filepath = currentTask.path // Get the file object and update the content @@ -80,7 +80,7 @@ export class FileOperation { // uncheck completed tasks, async uncompleteTaskInTheFile(taskId: string) { // Get the task file path - const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId) + const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheID(taskId) const filepath = currentTask.path // Get the file object and update the content @@ -107,6 +107,7 @@ export class FileOperation { //add #TickTick at the end of task line, if full vault sync enabled async addTickTickTagToFile(filepath: string) { + // console.log("addTickTickTagToFile") // Get the file object and update the content const file = this.app.vault.getAbstractFileByPath(filepath) const content = await this.app.vault.read(file) @@ -137,7 +138,7 @@ export class FileOperation { } if (modified) { - console.log(`New task found in files ${filepath}`) + // console.log(`New task found in files ${filepath}`) const newContent = lines.join('\n') //console.log(newContent) await this.app.vault.modify(file, newContent) @@ -168,14 +169,14 @@ export class FileOperation { if(this.plugin.taskParser.hasTickTickLink(line)){ return } - console.log(line) + // console.log("addTickTickLinkToFile", line) //console.log('prepare to add TickTick link') const taskID = this.plugin.taskParser.getTickTickIdFromLineText(line) - const taskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(taskID) + const taskObject = this.plugin.cacheOperation.loadTaskFromCacheID(taskID) const TickTickLink = taskObject.url const link = `[link](${TickTickLink})` const newLine = this.plugin.taskParser.addTickTickLink(line,link) - console.log(newLine) + // console.log(newLine) lines[i] = newLine modified = true }else{ @@ -196,6 +197,7 @@ export class FileOperation { //add #TickTick at the end of task line, if full vault sync enabled async addTickTickTagToLine(filepath:string,lineText:string,lineNumber:number,fileContent:string) { + // console.log("addTickTickTagToLine") // Get the file object and update the content const file = this.app.vault.getAbstractFileByPath(filepath) const content = fileContent @@ -226,9 +228,9 @@ export class FileOperation { if (modified) { - console.log(`New task found in files ${filepath}`) + // console.log(`New task found in files ${filepath}`) const newContent = lines.join('\n') - console.log(newContent) + // console.log(newContent) await this.app.vault.modify(file, newContent) //update filemetadate @@ -244,7 +246,7 @@ export class FileOperation { async syncUpdatedTaskContentToTheFile(evt:Object) { const taskId = evt.object_id // Get the task file path - const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId) + const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheID(taskId) const filepath = currentTask.path // Get the file object and update the content @@ -278,7 +280,7 @@ export class FileOperation { async syncUpdatedTaskDueDateToTheFile(evt:Object) { const taskId = evt.object_id // Get the task file path - const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId) + const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheID(taskId) const filepath = currentTask.path // Get the file object and update the content @@ -295,8 +297,8 @@ export class FileOperation { const newTaskDueDate = this.plugin.taskParser.ISOStringToLocalDateString(evt.extra_data.due_date) || "" //console.log(`${taskId} duedate is updated`) - console.log(oldTaskDueDate) - console.log(newTaskDueDate) + // console.log(oldTaskDueDate) + // console.log(newTaskDueDate) if(oldTaskDueDate === ""){ //console.log(this.plugin.taskParser.insertDueDateBeforeTickTick(line,newTaskDueDate)) lines[i] = this.plugin.taskParser.insertDueDateBeforeTickTick(line,newTaskDueDate) @@ -335,7 +337,7 @@ export class FileOperation { const note = evt.extra_data.content const datetime = this.plugin.taskParser.ISOStringToLocalDatetimeString(evt.event_date) // Get the task file path - const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId) + const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheID(taskId) const filepath = currentTask.path // Get the file object and update the content @@ -399,7 +401,7 @@ export class FileOperation { const line = fileLines[i]; if (line.includes(searchTerm)) { - const regexResult = /\[TickTick_id::\s*(\w+)\]/.exec(line); + const regexResult = /\[ticktick_id::\s*(\w+)\]/.exec(line); if (regexResult) { TickTickId = regexResult[1]; @@ -420,7 +422,7 @@ export class FileOperation { //search filepath by taskid in vault async searchFilepathsByTaskidInVault(taskId:string){ - console.log(`preprare to search task ${taskId}`) + // console.log(`preprare to search task ${taskId}`) const files = await this.getAllFilesInTheVault() //console.log(files) const tasks = files.map(async (file) => { diff --git a/src/modal.ts b/src/modal.ts index e2956e3..70121cc 100644 --- a/src/modal.ts +++ b/src/modal.ts @@ -28,8 +28,8 @@ export class SetDefalutProjectInTheFilepathModal extends Modal { this.defaultProjectId = await this.plugin.cacheOperation.getDefaultProjectIdForFilepath(this.filepath) this.defaultProjectName = await this.plugin.cacheOperation.getProjectNameByIdFromCache(this.defaultProjectId) - console.log(this.defaultProjectId) - console.log(this.defaultProjectName) + // console.log(this.defaultProjectId) + // console.log(this.defaultProjectName) const myProjectsOptions: MyProject | undefined = this.plugin.settings.TickTickTasksData?.projects?.reduce((obj, item) => { obj[(item.id).toString()] = item.name; return obj; @@ -46,7 +46,7 @@ export class SetDefalutProjectInTheFilepathModal extends Modal { .addOption(this.defaultProjectId,this.defaultProjectName) .addOptions(myProjectsOptions) .onChange((value)=>{ - console.log(`project id is ${value}`) + // console.log(`project id is ${value}`) //this.plugin.settings.defaultProjectId = this.result //this.plugin.settings.defaultProjectName = this.plugin.cacheOperation.getProjectNameByIdFromCache(this.result) //this.plugin.saveSettings() diff --git a/src/settings.ts b/src/settings.ts index ee6dd94..0afd69b 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -264,7 +264,7 @@ export class UltimateTickTickSyncSettingTab extends PluginSettingTab { //check file metadata - console.log('checking file metadata') + // console.log('checking file metadata') await this.plugin.cacheOperation.checkFileMetadata() this.plugin.saveSettings() const metadatas = await this.plugin.cacheOperation.getFileMetadatas() @@ -273,7 +273,8 @@ export class UltimateTickTickSyncSettingTab extends PluginSettingTab { const projectId = this.plugin.settings.defaultProjectId let options = {} options.projectId = projectId - const tasks = await this.plugin.TickTickRestAPI.GetActiveTasks(options) + // const tasks = await this.plugin.TickTickRestAPI.GetActiveTasks(options) + const tasks = await this.plugin.tickTickRestAPI.GetActiveTasks() let length = tasks.length if(length >= 300){ new Notice(`The number of tasks in the default project exceeds 300, reaching the upper limit. It is not possible to add more tasks. Please modify the default project.`) @@ -299,24 +300,22 @@ export class UltimateTickTickSyncSettingTab extends PluginSettingTab { let taskObject try{ - taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId) + taskObject = await this.plugin.cacheOperation.loadTaskFromCacheID(taskId) }catch(error){ console.error(`An error occurred while loading task cache: ${error.message}`); } if(!taskObject){ - console.log(`The task data of the ${taskId} is empty.`) + // console.log(`The task data of the ${taskId} is empty.`) //get from TickTick try { taskObject = await this.plugin.TickTickRestAPI.getTaskById(taskId); } catch (error) { if (error.message.includes('404')) { - // 处理404错误 - console.log(`Task ${taskId} seems to not exist.`); + // console.log(`Task ${taskId} seems to not exist.`); await this.plugin.cacheOperation.deleteTaskIdFromMetadata(key,taskId) continue } else { - // 处理其他错误 console.error(error); continue } @@ -329,7 +328,7 @@ export class UltimateTickTickSyncSettingTab extends PluginSettingTab { this.plugin.saveSettings() - console.log('checking renamed files') + // console.log('checking renamed files') try{ //check renamed files for (const key in metadatas) { @@ -341,7 +340,7 @@ export class UltimateTickTickSyncSettingTab extends PluginSettingTab { //console.log(`${taskId}`) let taskObject try{ - taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId) + taskObject = await this.plugin.cacheOperation.loadTaskFromCacheID(taskId) }catch(error){ console.error(`An error occurred while loading task ${taskId} from cache: ${error.message}`); console.log(taskObject) @@ -355,9 +354,9 @@ export class UltimateTickTickSyncSettingTab extends PluginSettingTab { } const oldDescription = taskObject?.description ?? ''; if(newDescription != oldDescription){ - console.log('Preparing to update description.') - console.log(oldDescription) - console.log(newDescription) + // console.log('Preparing to update description.') + // console.log(oldDescription) + // console.log(newDescription) try{ //await this.plugin.TickTickSync.updateTaskDescription(key) }catch(error){ @@ -430,7 +429,7 @@ export class UltimateTickTickSyncSettingTab extends PluginSettingTab { new Notice(`Please set the TickTick api first`) return } - this.plugin.TickTickSync.backupTickTickAllResources() + this.plugin.tickTickSync.backupTickTickAllResources() }) ); } diff --git a/src/static/.hotreload b/src/static/.hotreload new file mode 100644 index 0000000..e69de29 diff --git a/src/syncModule.ts b/src/syncModule.ts index 0aaf919..fd1be6a 100644 --- a/src/syncModule.ts +++ b/src/syncModule.ts @@ -1,874 +1,881 @@ -import UltimateTodoistSyncForObsidian from "../main"; +import UltimateTickTickSyncForObsidian from "../main"; import { App, Editor, MarkdownView, Notice} from 'obsidian'; +import { TickTickSyncAPI } from "./TicktickSyncAPI"; -type FrontMatter = { -todoistTasks: string[]; -todoistCount: number; +type FileMetadata = { + ticktickTasks: string[]; + ticktickCount: number; }; -export class TodoistSync { -app:App; -plugin: UltimateTodoistSyncForObsidian; - - -constructor(app:App, plugin:UltimateTodoistSyncForObsidian) { -//super(app,settings,todoistRestAPI,todoistSyncAPI,taskParser,cacheOperation); -this.app = app; -this.plugin = plugin; - -} - - - - - -async deletedTaskCheck(file_path:string): Promise { - -let file -let currentFileValue -let view -let filepath - -if(file_path){ -file = this.app.vault.getAbstractFileByPath(file_path) -filepath = file_path -currentFileValue = await this.app.vault.read(file) -} -else{ -view = this.app.workspace.getActiveViewOfType(MarkdownView); -//const editor = this.app.workspace.activeEditor?.editor -file = this.app.workspace.getActiveFile() -filepath = file?.path -//Use view.data instead of valut.read. vault.read is delayed -currentFileValue = view?.data -} - - -//console.log(filepath) - - -//const frontMatter = await this.plugin.fileOperation.getFrontMatter(file); -const frontMatter = await this.plugin.cacheOperation.getFileMetadata(filepath) -if (!frontMatter || !frontMatter.todoistTasks) { -console.log('frontmatter has no task') -return; -} - - - - -//console.log(currentFileValue) -const currentFileValueWithOutFrontMatter = currentFileValue.replace(/^---[\s\S]*?---\n/, ''); -const frontMatter_todoistTasks = frontMatter.todoistTasks; -const frontMatter_todoistCount = frontMatter.todoistCount; - -const deleteTasksPromises = frontMatter_todoistTasks -.filter((taskId) => !currentFileValueWithOutFrontMatter.includes(taskId)) -.map(async (taskId) => { -try { -//console.log(`initialize todoist api`) -const api = this.plugin.todoistRestAPI.initializeAPI() -const response = await api.deleteTask(taskId); -//console.log(`response is ${response}`); - -if (response) { -//console.log(`task ${taskId} deleted successfully`); -new Notice(`task ${taskId} is deleted`) -return taskId; // Return the deleted task ID -} -} catch (error) { -console.error(`Failed to delete task ${taskId}: ${error}`); -} -}); - -const deletedTaskIds = await Promise.all(deleteTasksPromises); -const deletedTaskAmount = deletedTaskIds.length -if (!deletedTaskIds.length) { -//console.log("No task deleted"); -return; -} -this.plugin.cacheOperation.deleteTaskFromCacheByIDs(deletedTaskIds) -//console.log(`Deleted ${deletedTaskAmount} tasks`) -this.plugin.saveSettings() -// Update newFrontMatter_todoistTasks array - -// Disable automatic merging - -const newFrontMatter_todoistTasks = frontMatter_todoistTasks.filter( -(taskId) => !deletedTaskIds.includes(taskId) -); - - -/* -await this.plugin.fileOperation.updateFrontMatter(file, (frontMatter) => { -frontMatter.todoistTasks = newFrontMatter_todoistTasks; -frontMatter.todoistCount = frontMatter_todoistCount - deletedTaskAmount; -}); -*/ -const newFileMetadata = {todoistTasks:newFrontMatter_todoistTasks,todoistCount:(frontMatter_todoistCount - deletedTaskAmount)} -await this.plugin.cacheOperation.updateFileMetadata(filepath,newFileMetadata) -} - -async lineContentNewTaskCheck(editor:Editor,view:MarkdownView): Promise{ -//const editor = this.app.workspace.activeEditor?.editor -//const view =this.app.workspace.getActiveViewOfType(MarkdownView) - -const filepath = view.file?.path -const fileContent = view?.data -const cursor = editor.getCursor() -const line = cursor.line -const linetxt = editor.getLine(line) - - - - - -//Add task -if ((!this.plugin.taskParser.hasTodoistId(linetxt) && this.plugin.taskParser.hasTodoistTag(linetxt))) { //Whether #todoist is included -console.log('this is a new task') -console.log(linetxt) -const currentTask =await this.plugin.taskParser.convertTextToTodoistTaskObject(linetxt,filepath,line,fileContent) -//console.log(currentTask) - - - - - -try { -const newTask = await this.plugin.todoistRestAPI.AddTask(currentTask) -const { id: todoist_id, projectId: todoist_projectId, url: todoist_url } = newTask; -newTask.path = filepath; -//console.log(newTask); -new Notice(`new task ${newTask.content} id is ${newTask.id}`) -//newTask writes to cache -this.plugin.cacheOperation.appendTaskToCache(newTask) - -//If the task is completed -if(currentTask.isCompleted === true){ -await this.plugin.todoistRestAPI.CloseTask(newTask.id) -this.plugin.cacheOperation.closeTaskToCacheByID(todoist_id) - -} -this.plugin.saveSettings() - -//todoist id is saved to the end of the task -const text_with_out_link = `${linetxt} %%[todoist_id:: ${todoist_id}]%%`; -const link = `[link](${newTask.url})` -const text = this.plugin.taskParser.addTodoistLink(text_with_out_link,link) -const from = { line: cursor.line, ch: 0 }; -const to = { line: cursor.line, ch: linetxt.length }; -view.app.workspace.activeEditor?.editor?.replaceRange(text, from, to) - -//Process frontMatter -try { -// handle front matter -const frontMatter = await this.plugin.cacheOperation.getFileMetadata(filepath) -//console.log(frontMatter); - -if (!frontMatter) { -//console.log('frontmatter is empty'); -//return; -} - -// Increase todoistCount by 1 -const newFrontMatter = { ...frontMatter }; -newFrontMatter.todoistCount = (newFrontMatter.todoistCount ?? 0) + 1; - -//Record taskID -newFrontMatter.todoistTasks = [...(newFrontMatter.todoistTasks || []), todoist_id]; - -// update front matter -/* -this.plugin.fileOperation.updateFrontMatter(view.file, (frontMatter) => { -frontMatter.todoistTasks = newFrontMatter.todoistTasks; -frontMatter.todoistCount = newFrontMatter.todoistCount; -}); -*/ -//console.log(newFrontMatter) -await this.plugin.cacheOperation.updateFileMetadata(filepath,newFrontMatter) - - - -} catch (error) { -console.error(error); -} - -} catch (error) { -console.error('Error adding task:', error); -console.log(`The error occurred in the file: ${filepath}`) -return -} - -} -} - - -async fullTextNewTaskCheck(file_path:string): Promise{ - -let file -let currentFileValue -let view -let filepath - -if(file_path){ -file = this.app.vault.getAbstractFileByPath(file_path) -filepath = file_path -currentFileValue = await this.app.vault.read(file) -} -else{ -view = this.app.workspace.getActiveViewOfType(MarkdownView); -//const editor = this.app.workspace.activeEditor?.editor -file = this.app.workspace.getActiveFile() -filepath = file?.path -//Use view.data instead of valut.read. vault.read is delayed -currentFileValue = view?.data -} - -if(this.plugin.settings.enableFullVaultSync){ -//console.log('full vault sync enabled') -//console.log(filepath) -await this.plugin.fileOperation.addTodoistTagToFile(filepath) -} - -const content = currentFileValue - -let newFrontMatter -//frontMatteer -const frontMatter = await this.plugin.cacheOperation.getFileMetadata(filepath) -//console.log(frontMatter); - -if (!frontMatter) { -console.log('frontmatter is empty'); -newFrontMatter = {}; -}else{ -newFrontMatter = { ...frontMatter }; -} - - -let hasNewTask = false; -const lines = content.split('\n') - -for (let i = 0; i < lines.length; i++) { -const line = lines[i] -if (!this.plugin.taskParser.hasTodoistId(line) && this.plugin.taskParser.hasTodoistTag(line)) { -//console.log('this is a new task') -//console.log(`current line is ${i}`) -//console.log(`line text: ${line}`) -console.log(filepath) -const currentTask =await this.plugin.taskParser.convertTextToTodoistTaskObject(line,filepath,i,content) -if(typeof currentTask === "undefined"){ -continue -} -console.log(currentTask) -try { -const newTask = await this.plugin.todoistRestAPI.AddTask(currentTask) -const { id: todoist_id, projectId: todoist_projectId, url: todoist_url } = newTask; -newTask.path = filepath; -console.log(newTask); -new Notice(`new task ${newTask.content} id is ${newTask.id}`) -//newTask writes to json file -this.plugin.cacheOperation.appendTaskToCache(newTask) - -//If the task is completed -if(currentTask.isCompleted === true){ -await this.plugin.todoistRestAPI.CloseTask(newTask.id) -this.plugin.cacheOperation.closeTaskToCacheByID(todoist_id) -} -this.plugin.saveSettings() - -//todoist id is saved to the end of the task -const text_with_out_link = `${line} %%[todoist_id:: ${todoist_id}]%%`; -const link = `[link](${newTask.url})` -const text = this.plugin.taskParser.addTodoistLink(text_with_out_link,link) -lines[i] = text; - -newFrontMatter.todoistCount = (newFrontMatter.todoistCount ?? 0) + 1; - -//Record taskID -newFrontMatter.todoistTasks = [...(newFrontMatter.todoistTasks || []), todoist_id]; - -hasNewTask = true - -} catch (error) { -console.error('Error adding task:', error); -continue -} - -} -} -if(hasNewTask){ -//Text and frontMatter -try { -// save file -const newContent = lines.join('\n') -await this.app.vault.modify(file, newContent) - - -// update front matter -/* -this.plugin.fileOperation.updateFrontMatter(file, (frontMatter) => { -frontMatter.todoistTasks = newFrontMatter.todoistTasks; -frontMatter.todoistCount = newFrontMatter.todoistCount; -}); -*/ - -await this.plugin.cacheOperation.updateFileMetadata(filepath,newFrontMatter) - -} catch (error) { -console.error(error); -} - -} - - -} - - -async lineModifiedTaskCheck(filepath:string,lineText:string,lineNumber:number,fileContent:string): Promise{ -//const lineText = await this.plugin.fileOperation.getLineTextFromFilePath(filepath,lineNumber) - -if(this.plugin.settings.enableFullVaultSync){ -//await this.plugin.fileOperation.addTodoistTagToLine(filepath,lineText,lineNumber,fileContent) - -//new empty metadata -const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath) -if(!metadata){ -await this.plugin.cacheOperation.newEmptyFileMetadata(filepath) -} -this.plugin.saveSettings() -} - -//check task -if (this.plugin.taskParser.hasTodoistId(lineText) && this.plugin.taskParser.hasTodoistTag(lineText)) { - -const lineTask = await this.plugin.taskParser.convertTextToTodoistTaskObject(lineText,filepath,lineNumber,fileContent) -//console.log(lastLineTask) -const lineTask_todoist_id = (lineTask.todoist_id).toString() -//console.log(lineTask_todoist_id) -//console.log(`lastline task id is ${lastLineTask_todoist_id}`) -const savedTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(lineTask_todoist_id) //The id in dataview is a number, and the id in todoist is a string, which needs to be converted -if(!savedTask){ -console.log(`There is no task ${lineTask.todoist_id}` in the local cache) -const url = this.plugin.taskParser.getObsidianUrlFromFilepath(filepath) -console.log(url) -return -} -//console.log(savedTask) - -//Check whether the content has been modified -const lineTaskContent = lineTask.content; - - -//Whether content is modified? -const contentModified = !this.plugin.taskParser.taskContentCompare(lineTask,savedTask) -//tag or labels whether to modify -const tagsModified = !this.plugin.taskParser.taskTagCompare(lineTask,savedTask) -//project whether to modify -const projectModified = !(await this.plugin.taskParser.taskProjectCompare(lineTask,savedTask)) -//Whether status is modified? -const statusModified = !this.plugin.taskParser.taskStatusCompare(lineTask,savedTask) -//due date whether to modify -const dueDateModified = !(await this.plugin.taskParser.compareTaskDueDate(lineTask,savedTask)) -// parent id whether to modify -const parentIdModified = !(lineTask.parentId === savedTask.parentId) -//check priority -const priorityModified = !(lineTask.priority === savedTask.priority) - -try { -let contentChanged= false; -let tagsChanged = false; -let projectChanged = false; -let statusChanged = false; -let dueDateChanged = false; -let parentIdChanged = false; -let priorityChanged = false; - -let updatedContent = {} -if (contentModified) { -console.log(`Content modified for task ${lineTask_todoist_id}`) -updatedContent.content = lineTaskContent -contentChanged = true; -} - -if (tagsModified) { -console.log(`Tags modified for task ${lineTask_todoist_id}`) -updatedContent.labels = lineTask.labels -tagsChanged = true; -} - - -if (dueDateModified) { -console.log(`Due date modified for task ${lineTask_todoist_id}`) -console.log(lineTask.dueDate) -//console.log(savedTask.due.date) -if(lineTask.dueDate === ""){ -updatedContent.dueString = "no date" -}else{ -updatedContent.dueDate = lineTask.dueDate -} - -dueDateChanged = true; -} - -//todoist Rest api does not have the function of move task to new project -if (projectModified) { -//console.log(`Project id modified for task ${lineTask_todoist_id}`) -//updatedContent.projectId = lineTask.projectId -//projectChanged = false; -} - -//todoist Rest api has no excuse to modify parent id -if (parentIdModified) { -//console.log(`Parnet id modified for task ${lineTask_todoist_id}`) -//updatedContent.parentId = lineTask.parentId -//parentIdChanged = false; -} - -if (priorityModified) { - -updatedContent.priority = lineTask.priority -priorityChanged = true; -} - - -if (contentChanged || tagsChanged ||dueDateChanged ||projectChanged || parentIdChanged || priorityChanged) { -//console.log("task content was modified"); -//console.log(updatedContent) -const updatedTask = await this.plugin.todoistRestAPI.UpdateTask(lineTask.todoist_id.toString(),updatedContent) -updatedTask.path = filepath -this.plugin.cacheOperation.updateTaskToCacheByID(updatedTask); -} - -if (statusModified) { -console.log(`Status modified for task ${lineTask_todoist_id}`) -if (lineTask.isCompleted === true) { -console.log(`task completed`) -this.plugin.todoistRestAPI.CloseTask(lineTask.todoist_id.toString()); -this.plugin.cacheOperation.closeTaskToCacheByID( lineTask.todoist_id.toString()); -} else { -console.log(`task umcompleted`) -this.plugin.todoistRestAPI.OpenTask(lineTask.todoist_id.toString()); -this.plugin.cacheOperation.reopenTaskToCacheByID( lineTask.todoist_id.toString()); -} - -statusChanged = true; -} - - - -if (contentChanged || statusChanged || dueDateChanged || tagsChanged || projectChanged || priorityChanged) { -console.log(lineTask) -console.log(savedTask) -//`Task ${lastLineTaskTodoistId} was modified` -this.plugin.saveSettings() -let message = `Task ${lineTask_todoist_id} is updated.`; - -if (contentChanged) { -message += "Content was changed."; -} -if (statusChanged) { -message += "Status was changed."; -} -if (dueDateChanged) { -message += "Due date was changed."; -} -if (tagsChanged) { -message += " Tags were changed."; -} -if (projectChanged) { -message += "Project was changed."; -} -if (priorityChanged) { -message += "Priority was changed."; -} - -new Notice(message); - -} else { -//console.log(`Task ${lineTask_todoist_id} did not change`); -} - -} catch (error) { -console.error('Error updating task:', error); -} - - -} -} - - -async fullTextModifiedTaskCheck(file_path: string): Promise { - -let file; -let currentFileValue; -let view; -let filepath; - -try { -if (file_path) { -file = this.app.vault.getAbstractFileByPath(file_path); -filepath = file_path; -currentFileValue = await this.app.vault.read(file); -} else { -view = this.app.workspace.getActiveViewOfType(MarkdownView); -file = this.app.workspace.getActiveFile(); -filepath = file?.path; -currentFileValue = view?.data; -} - -const content = currentFileValue; - -let hasModifiedTask = false; -const lines = content.split('\n'); - -for (let i = 0; i < lines.length; i++) { -const line = lines[i]; -if (this.plugin.taskParser.hasTodoistId(line) && this.plugin.taskParser.hasTodoistTag(line)) { -try { -await this.lineModifiedTaskCheck(filepath, line, i, content); -hasModifiedTask = true; -} catch (error) { -console.error('Error modifying task:', error); -continue; -} -} -} - -if (hasModifiedTask) { -try { -// Perform necessary actions on the modified content and front matter -} catch (error) { -console.error('Error processing modified content:', error); -} -} -} catch (error) { -console.error('Error:', error); -} -} - - -// Close a task by calling API and updating JSON file -async closeTask(taskId: string): Promise { -try { -await this.plugin.todoistRestAPI.CloseTask(taskId); -await this.plugin.fileOperation.completeTaskInTheFile(taskId) -await this.plugin.cacheOperation.closeTaskToCacheByID(taskId); -this.plugin.saveSettings() -new Notice(`Task ${taskId} is closed.`) -} catch (error) { -console.error('Error closing task:', error); -throw error; // Throw an error so that the caller can catch and handle it -} -} - -//open task -async repoenTask(taskId:string) : Promise{ -try { -await this.plugin.todoistRestAPI.OpenTask(taskId) -await this.plugin.fileOperation.uncompleteTaskInTheFile(taskId) -await this.plugin.cacheOperation.reopenTaskToCacheByID(taskId) -this.plugin.saveSettings() -new Notice(`Task ${taskId} is reopened.`) -} catch (error) { -console.error('Error opening task:', error); -throw error; // Throw an error so that the caller can catch and handle it -} -} - - -/** -* Delete the task with the specified ID from the task list and update the JSON file -* @param taskIds array of task IDs to be deleted -* @returns Returns the successfully deleted task ID array -*/ -async deleteTasksByIds(taskIds: string[]): Promise { -const deletedTaskIds = []; - -for (const taskId of taskIds) { -const api = await this.plugin.todoistRestAPI.initializeAPI() -try { -const response = await api.deleteTask(taskId); -console.log(`response is ${response}`); - -if (response) { -//console.log(`Task ${taskId} deleted successfully`); -new Notice(`Task ${taskId} is deleted.`) -deletedTaskIds.push(taskId); // Add the deleted task ID to the array -} -} catch (error) { -console.error(`Failed to delete task ${taskId}: ${error}`); -// You can add better error handling methods, such as throwing exceptions or logging here, etc. -} -} - -if (!deletedTaskIds.length) { -console.log("Task not deleted"); -return []; -} - -await this.plugin.cacheOperation.deleteTaskFromCacheByIDs(deletedTaskIds); // Update JSON file -this.plugin.saveSettings() -//console.log(`A total of ${deletedTaskIds.length} tasks were deleted`); - - -return deletedTaskIds; -} - - - - - - - - - -// Synchronize completed task status to Obsidian file -async syncCompletedTaskStatusToObsidian(unSynchronizedEvents) { -// Get unsynchronized events -//console.log(unSynchronizedEvents) -try { - -// Handle unsynchronized events and wait for all processing to complete -const processedEvents = [] -for (const e of unSynchronizedEvents) { //If you want to modify the code so that completeTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: -//console.log(`Completing ${e.object_id}`) -await this.plugin.fileOperation.completeTaskInTheFile(e.object_id) -await this.plugin.cacheOperation.closeTaskToCacheByID(e.object_id) -new Notice(`Task ${e.object_id} is closed.`) -processedEvents.push(e) -} - -// Save events to the local database." -//const allEvents = [...savedEvents, ...unSynchronizedEvents] -await this.plugin.cacheOperation.appendEventsToCache(processedEvents) -this.plugin.saveSettings() - - - - - -} catch (error) { -console.error('Error synchronizing task status:', error) -} -} - - -// Synchronize completed task status to Obsidian file -async syncUncompletedTaskStatusToObsidian(unSynchronizedEvents) { - -//console.log(unSynchronizedEvents) - -try { - -// Handle unsynchronized events and wait for all processing to complete -const processedEvents = [] -for (const e of unSynchronizedEvents) { //If you want to modify the code so that uncompleteTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: -//console.log(`uncheck task: ${e.object_id}`) -await this.plugin.fileOperation.uncompleteTaskInTheFile(e.object_id) -await this.plugin.cacheOperation.reopenTaskToCacheByID(e.object_id) -new Notice(`Task ${e.object_id} is reopened.`) -processedEvents.push(e) -} - - - -// Merge new events into existing events and save to JSON -//const allEvents = [...savedEvents, ...unSynchronizedEvents] -await this.plugin.cacheOperation.appendEventsToCache(processedEvents) -this.plugin.saveSettings() -} catch (error) { -console.error('Error synchronizing task status:', error) -} -} - -// Synchronize updated item status to Obsidian -async syncUpdatedTaskToObsidian(unSynchronizedEvents) { -//console.log(unSynchronizedEvents) -try { - -// Handle unsynchronized events and wait for all processing to complete -const processedEvents = [] -for (const e of unSynchronizedEvents) { //If you want to modify the code so that completeTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: -//console.log(`Syncing ${e.object_id} changes to local`) -console.log(e) -console.log(typeof e.extra_data.last_due_date === 'undefined') -if(!(typeof e.extra_data.last_due_date === 'undefined')){ -//console.log(`prepare update dueDate`) -await this.syncUpdatedTaskDueDateToObsidian(e) - -} - -if(!(typeof e.extra_data.last_content === 'undefined')){ -//console.log(`prepare update content`) -await this.syncUpdatedTaskContentToObsidian(e) -} - -//await this.plugin.fileOperation.syncUpdatedTaskToTheFile(e) -//Also modify the data in the cache -//new Notice(`Task ${e.object_id} is updated.`) -processedEvents.push(e) -} - - - -// Merge new events into existing events and save to JSON -//const allEvents = [...savedEvents, ...unSynchronizedEvents] -await this.plugin.cacheOperation.appendEventsToCache(processedEvents) -this.plugin.saveSettings() -} catch (error) { -console.error('Error syncing updated item', error) -} - -} - -async syncUpdatedTaskContentToObsidian(e){ -this.plugin.fileOperation.syncUpdatedTaskContentToTheFile(e) -const content = e.extra_data.content -this.plugin.cacheOperation.modifyTaskToCacheByID(e.object_id,{content}) -new Notice(`The content of Task ${e.parent_item_id} has been modified.`) - -} - -async syncUpdatedTaskDueDateToObsidian(e){ -this.plugin.fileOperation.syncUpdatedTaskDueDateToTheFile(e) -//To modify the cache date, use todoist format -const due = await this.plugin.todoistRestAPI.getTaskDueById(e.object_id) -this.plugin.cacheOperation.modifyTaskToCacheByID(e.object_id,{due}) -new Notice(`The due date of Task ${e.parent_item_id} has been modified.`) - -} - -// sync added task note to obsidian -async syncAddedTaskNoteToObsidian(unSynchronizedEvents) { -// Get unsynchronized events -//console.log(unSynchronizedEvents) -try { - -// Handle unsynchronized events and wait for all processing to complete -const processedEvents = [] -for (const e of unSynchronizedEvents) { //If you want to modify the code so that completeTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: -console.log(e) -//const taskid = e.parent_item_id -//const note = e.extra_data.content -await this.plugin.fileOperation.syncAddedTaskNoteToTheFile(e) -//await this.plugin.cacheOperation.closeTaskToCacheByID(e.object_id) -new Notice(`Task ${e.parent_item_id} note is added.`) -processedEvents.push(e) -} - -// Merge new events into existing events and save to JSON - -await this.plugin.cacheOperation.appendEventsToCache(processedEvents) -this.plugin.saveSettings() -} catch (error) { -console.error('Error synchronizing task status:', error) -} -} - - -async syncTodoistToObsidian(){ -try{ -const all_activity_events = await this.plugin.todoistSyncAPI.getNonObsidianAllActivityEvents() - -// remove synchonized events -const savedEvents = await this.plugin.cacheOperation.loadEventsFromCache() -const result1 = all_activity_events.filter( -(objA) => !savedEvents.some((objB) => objB.id === objA.id) -) - - -const savedTasks = await this.plugin.cacheOperation.loadTasksFromCache() -// Find the task activity whose task id exists in Obsidian -const result2 = result1.filter( -(objA) => savedTasks.some((objB) => objB.id === objA.object_id) -) -// Find the note activity whose task id exists in Obsidian -const result3 = result1.filter( -(objA) => savedTasks.some((objB) => objB.id === objA.parent_item_id) -) - - - - -const unsynchronized_item_completed_events = this.plugin.todoistSyncAPI.filterActivityEvents(result2, { event_type: 'completed', object_type: 'item' }) -const unsynchronized_item_uncompleted_events = this.plugin.todoistSyncAPI.filterActivityEvents(result2, { event_type: 'uncompleted', object_type: 'item' }) - -//Items updated (only changes to content, description, due_date and responsible_uid) -const unsynchronized_item_updated_events = this.plugin.todoistSyncAPI.filterActivityEvents(result2, { event_type: 'updated', object_type: 'item' }) - -const unsynchronized_notes_added_events = this.plugin.todoistSyncAPI.filterActivityEvents(result3, { event_type: 'added', object_type: 'note' }) -const unsynchronized_project_events = this.plugin.todoistSyncAPI.filterActivityEvents(result1, { object_type: 'project' }) -console.log(unsynchronized_item_completed_events) -console.log(unsynchronized_item_uncompleted_events) -console.log(unsynchronized_item_updated_events) -console.log(unsynchronized_project_events) -console.log(unsynchronized_notes_added_events) - -await this.syncCompletedTaskStatusToObsidian(unsynchronized_item_completed_events) -await this.syncUncompletedTaskStatusToObsidian(unsynchronized_item_uncompleted_events) -await this.syncUpdatedTaskToObsidian(unsynchronized_item_updated_events) -await this.syncAddedTaskNoteToObsidian(unsynchronized_notes_added_events) -if(unsynchronized_project_events.length){ -console.log('New project event') -await this.plugin.cacheOperation.saveProjectsToCache() -await this.plugin.cacheOperation.appendEventsToCache(unsynchronized_project_events) -} - - -}catch (err){ -console.error('An error occurred while synchronizing:', err); -} - -} - - - -async backupTodoistAllResources() { -try { -const resources = await this.plugin.todoistSyncAPI.getAllResources() - -const now: Date = new Date(); -const timeString: string = `${now.getFullYear()}${now.getMonth()+1}${now.getDate()}${now.getHours()}${now.getMinutes()}${ now.getSeconds()}`; - -const name = "todoist-backup-"+timeString+".json" - -this.app.vault.create(name,JSON.stringify(resources)) -//console.log(`todoist backup successful`) -new Notice(`Todoist backup data is saved in the path ${name}`) -} catch (error) { -console.error("An error occurred while creating Todoist backup:", error); -} - -} - - -//After renaming the file, check all tasks in the file and update all links. -async updateTaskDescription(filepath:string){ -const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath) -if(!metadata || !metadata.todoistTasks){ -return -} -const description = this.plugin.taskParser.getObsidianUrlFromFilepath(filepath) -let updatedContent = {} -updatedContent.description = description -try { -metadata.todoistTasks.forEach(async(taskId) => { -const updatedTask = await this.plugin.todoistRestAPI.UpdateTask(taskId,updatedContent) -updatedTask.path = filepath -this.plugin.cacheOperation.updateTaskToCacheByID(updatedTask); - -}); -} catch(error) { -console.error('An error occurred in updateTaskDescription:', error); -} - - - -} - - - - - -} +export class TickTickSync { + app:App; + plugin: UltimateTickTickSyncForObsidian; + + + constructor(app:App, plugin:UltimateTickTickSyncForObsidian) { + //super(app,settings,ticktickRestAPI,ticktickSyncAPI,taskParser,cacheOperation); + this.app = app; + this.plugin = plugin; + + } + + + + + + async deletedTaskCheck(file_path:string): Promise { + + let file + let currentFileValue + let view + let filepath + + if(file_path){ + file = this.app.vault.getAbstractFileByPath(file_path) + filepath = file_path + currentFileValue = await this.app.vault.read(file) + } + else{ + view = this.app.workspace.getActiveViewOfType(MarkdownView); + //const editor = this.app.workspace.activeEditor?.editor + file = this.app.workspace.getActiveFile() + filepath = file?.path + //Use view.data instead of valut.read. vault.read is delayed + currentFileValue = view?.data + } + + + //console.log(filepath) + + + //const fileMetadata = await this.plugin.fileOperation.getFileMetadata(file); + const fileMetadata = await this.plugin.cacheOperation.getFileMetadata(filepath) + // console.log("frontmatter: ", fileMetadata) + if (!fileMetadata || !fileMetadata.ticktickTasks) { + // console.log('frontmatter has no task') + return; + } + + + + + //console.log(currentFileValue) + const currentFileValueWithOutFileMetadata = currentFileValue.replace(/^---[\s\S]*?---\n/, ''); + const fileMetadata_ticktickTasks = fileMetadata.ticktickTasks; + const fileMetadata_ticktickCount = fileMetadata.ticktickCount; + + const deleteTasksPromises = fileMetadata_ticktickTasks + .filter((taskId) => !currentFileValueWithOutFileMetadata.includes(taskId)) + .map(async (taskId) => { + try { + //console.log(`initialize ticktick api`) + const api = this.plugin.ticktickRestAPI.initializeAPI() + const response = await api.deleteTask(taskId); + //console.log(`response is ${response}`); + + if (response) { + //console.log(`task ${taskId} deleted successfully`); + new Notice(`task ${taskId} is deleted`) + return taskId; // Return the deleted task ID + } + } catch (error) { + console.error(`Failed to delete task ${taskId}: ${error}`); + } + }); + + const deletedTaskIds = await Promise.all(deleteTasksPromises); + const deletedTaskAmount = deletedTaskIds.length + if (!deletedTaskIds.length) { + //console.log("No task deleted"); + return; + } + this.plugin.cacheOperation.deleteTaskFromCacheByIDs(deletedTaskIds) + //console.log(`Deleted ${deletedTaskAmount} tasks`) + this.plugin.saveSettings() + // Update newFileMetadata_ticktickTasks array + + // Disable automatic merging + + const newFileMetadata_ticktickTasks = fileMetadata_ticktickTasks.filter( + (taskId) => !deletedTaskIds.includes(taskId) + ); + + + /* + await this.plugin.fileOperation.updateFileMetadata(file, (fileMetadata) => { + fileMetadata.ticktickTasks = newFileMetadata_ticktickTasks; + fileMetadata.ticktickCount = fileMetadata_ticktickCount - deletedTaskAmount; + }); + */ + const newFileMetadata = {ticktickTasks:newFileMetadata_ticktickTasks,ticktickCount:(fileMetadata_ticktickCount - deletedTaskAmount)} + await this.plugin.cacheOperation.updateFileMetadata(filepath,newFileMetadata) + } + + async lineContentNewTaskCheck(editor:Editor,view:MarkdownView): Promise{ + //const editor = this.app.workspace.activeEditor?.editor + //const view =this.app.workspace.getActiveViewOfType(MarkdownView) + + const filepath = view.file?.path + const fileContent = view?.data + const cursor = editor.getCursor() + const line = cursor.line + const linetxt = editor.getLine(line) + + + + + + //Add task + // console.log("lineContentNewTaskCheck", linetxt, this.plugin.taskParser.hasTickTickId(linetxt), this.plugin.taskParser.hasTickTickTag(linetxt) ) + if ((!this.plugin.taskParser.hasTickTickId(linetxt) && this.plugin.taskParser.hasTickTickTag(linetxt))) { //Whether #ticktick is included + const currentTask =await this.plugin.taskParser.convertTextToTickTickTaskObject(linetxt,filepath,line,fileContent) + + try { + console.log("Adding because line content new check.") + const newTask = await this.plugin.tickTickRestAPI.AddTask(currentTask) + const { id: ticktick_id, projectId: ticktick_projectId, url: ticktick_url } = newTask; + newTask.path = filepath; + //console.log(newTask); + new Notice(`new task ${newTask.content} id is ${newTask.id}`) + //newTask writes to cache + this.plugin.cacheOperation.appendTaskToCache(newTask) + + //If the task is completed + if(currentTask.isCompleted === true){ + await this.plugin.ticktickRestAPI.CloseTask(newTask.id) + this.plugin.cacheOperation.closeTaskToCacheByID(ticktick_id) + + } + this.plugin.saveSettings() + + //ticktick id is saved to the end of the task + const text_with_out_link = `${linetxt} %%[ticktick_id:: ${ticktick_id}]%%`; + const link = `[link](${newTask.url})` + const text = this.plugin.taskParser.addTickTickLink(text_with_out_link,link) + const from = { line: cursor.line, ch: 0 }; + const to = { line: cursor.line, ch: linetxt.length }; + view.app.workspace.activeEditor?.editor?.replaceRange(text, from, to) + + //Process fileMetadata + try { + // handle front matter + const fileMetadata = await this.plugin.cacheOperation.getFileMetadata(filepath) + //console.log(fileMetadata); + + if (!fileMetadata) { + //console.log('frontmatter is empty'); + //return; + } + + // Increase ticktickCount by 1 + const newFileMetadata = { ...fileMetadata }; + newFileMetadata.ticktickCount = (newFileMetadata.ticktickCount ?? 0) + 1; + + //Record taskID + newFileMetadata.ticktickTasks = [...(newFileMetadata.ticktickTasks || []), ticktick_id]; + + // update front matter + /* + this.plugin.fileOperation.updateFileMetadata(view.file, (fileMetadata) => { + fileMetadata.ticktickTasks = newFileMetadata.ticktickTasks; + fileMetadata.ticktickCount = newFileMetadata.ticktickCount; + }); + */ + //console.log(newFileMetadata) + await this.plugin.cacheOperation.updateFileMetadata(filepath,newFileMetadata) + + + + } catch (error) { + console.error(error); + } + + } catch (error) { + console.error('Error adding task:', error); + console.log(`The error occurred in the file: ${filepath}`) + return + } + + } + } + + + async fullTextNewTaskCheck(file_path:string): Promise{ + console.log("fullTextNewTaskCheck") + let file + let currentFileValue + let view + let filepath + + if(file_path){ + file = this.app.vault.getAbstractFileByPath(file_path) + filepath = file_path + currentFileValue = await this.app.vault.read(file) + } + else{ + view = this.app.workspace.getActiveViewOfType(MarkdownView); + //const editor = this.app.workspace.activeEditor?.editor + file = this.app.workspace.getActiveFile() + filepath = file?.path + //Use view.data instead of valut.read. vault.read is delayed + currentFileValue = view?.data + } + + if(this.plugin.settings.enableFullVaultSync){ + //console.log('full vault sync enabled') + //console.log(filepath) + console.log("Called from sync.") + await this.plugin.fileOperation.addTickTickTagToFile(filepath) + } + + const content = currentFileValue + + let newFileMetadata + //frontMatteer + const fileMetadata = await this.plugin.cacheOperation.getFileMetadata(filepath) + //console.log(fileMetadata); + + if (!fileMetadata) { + console.log('frontmatter is empty'); + newFileMetadata = {}; + }else{ + newFileMetadata = { ...fileMetadata }; + } + + + let hasNewTask = false; + const lines = content.split('\n') + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + if (!this.plugin.taskParser.hasTickTickId(line) && this.plugin.taskParser.hasTickTickTag(line)) { + //console.log('this is a new task') + //console.log(`current line is ${i}`) + //console.log(`line text: ${line}`) + console.log(filepath) + const currentTask =await this.plugin.taskParser.convertTextToTickTickTaskObject(line,filepath,i,content) + if(typeof currentTask === "undefined"){ + continue + } + console.log(currentTask) + try { + console.log("adding because full task new check") + const newTask = await this.plugin.ticktickRestAPI.AddTask(currentTask) + const { id: ticktick_id, projectId: ticktick_projectId, url: ticktick_url } = newTask; + newTask.path = filepath; + console.log(newTask); + new Notice(`new task ${newTask.content} id is ${newTask.id}`) + //newTask writes to json file + this.plugin.cacheOperation.appendTaskToCache(newTask) + + //If the task is completed + if(currentTask.isCompleted === true){ + await this.plugin.ticktickRestAPI.CloseTask(newTask.id) + this.plugin.cacheOperation.closeTaskToCacheByID(ticktick_id) + } + this.plugin.saveSettings() + + //ticktick id is saved to the end of the task + const text_with_out_link = `${line} %%[ticktick_id:: ${ticktick_id}]%%`; + const link = `[link](${newTask.url})` + const text = this.plugin.taskParser.addTickTickLink(text_with_out_link,link) + lines[i] = text; + + newFileMetadata.ticktickCount = (newFileMetadata.ticktickCount ?? 0) + 1; + + //Record taskID + newFileMetadata.ticktickTasks = [...(newFileMetadata.ticktickTasks || []), ticktick_id]; + + hasNewTask = true + + } catch (error) { + console.error('Error adding task:', error); + continue + } + + } + } + if(hasNewTask){ + //Text and fileMetadata + try { + // save file + const newContent = lines.join('\n') + await this.app.vault.modify(file, newContent) + + + // update front matter + /* + this.plugin.fileOperation.updateFileMetadata(file, (fileMetadata) => { + fileMetadata.ticktickTasks = newFileMetadata.ticktickTasks; + fileMetadata.ticktickCount = newFileMetadata.ticktickCount; + }); + */ + + await this.plugin.cacheOperation.updateFileMetadata(filepath,newFileMetadata) + + } catch (error) { + console.error(error); + } + + } + + + } + + + async lineModifiedTaskCheck(filepath:string,lineText:string,lineNumber:number,fileContent:string): Promise{ + //const lineText = await this.plugin.fileOperation.getLineTextFromFilePath(filepath,lineNumber) + + if(this.plugin.settings.enableFullVaultSync){ + //await this.plugin.fileOperation.addticktickTagToLine(filepath,lineText,lineNumber,fileContent) + + //new empty metadata + const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath) + if(!metadata){ + await this.plugin.cacheOperation.newEmptyFileMetadata(filepath) + } + this.plugin.saveSettings() + } + + //check task + if (this.plugin.taskParser.hasTickTickId(lineText) && this.plugin.taskParser.hasTickTickTag(lineText)) { + console.log("=======>", lineText) + const lineTask = await this.plugin.taskParser.convertTextToTickTickTaskObject(lineText,filepath,lineNumber,fileContent) + //console.log(lastLineTask) + console.log("ticktickid: ", lineTask.id) + const lineTask_ticktick_id = (lineTask.id).toString() + //console.log(lineTask_ticktick_id) + //console.log(`lastline task id is ${lastLineTask_ticktick_id}`) + const savedTask = await this.plugin.cacheOperation.loadTaskFromCacheID(lineTask_ticktick_id) //The id in dataview is a number, and the id in ticktick is a string, which needs to be converted + if(!savedTask){ + console.log(`There is no task ${lineTask.ticktick_id} in the local cache`) + const url = this.plugin.taskParser.getObsidianUrlFromFilepath(filepath) + console.log(url) + return + } + //console.log(savedTask) + + //Check whether the content has been modified + const lineTaskContent = lineTask.content; + + + //Whether content is modified? + const contentModified = !this.plugin.taskParser.taskContentCompare(lineTask,savedTask) + //tag or labels whether to modify + const tagsModified = !this.plugin.taskParser.taskTagCompare(lineTask,savedTask) + //project whether to modify + const projectModified = !(await this.plugin.taskParser.taskProjectCompare(lineTask,savedTask)) + //Whether status is modified? + const statusModified = !this.plugin.taskParser.taskStatusCompare(lineTask,savedTask) + //due date whether to modify + const dueDateModified = !(await this.plugin.taskParser.compareTaskDueDate(lineTask,savedTask)) + // parent id whether to modify + const parentIdModified = !(lineTask.parentId === savedTask.parentId) + //check priority + const priorityModified = !(lineTask.priority === savedTask.priority) + + try { + let contentChanged= false; + let tagsChanged = false; + let projectChanged = false; + let statusChanged = false; + let dueDateChanged = false; + let parentIdChanged = false; + let priorityChanged = false; + + let updatedContent = {} + if (contentModified) { + console.log(`Content modified for task ${lineTask_ticktick_id}`) + updatedContent.content = lineTaskContent + contentChanged = true; + } + + if (tagsModified) { + console.log(`Tags modified for task ${lineTask_ticktick_id}`) + updatedContent.labels = lineTask.labels + tagsChanged = true; + } + + + if (dueDateModified) { + console.log(`Due date modified for task ${lineTask_ticktick_id}`) + console.log(lineTask.dueDate) + //console.log(savedTask.due.date) + if(lineTask.dueDate === ""){ + updatedContent.dueString = "no date" + }else{ + updatedContent.dueDate = lineTask.dueDate + } + + dueDateChanged = true; + } + + //ticktick Rest api does not have the function of move task to new project + if (projectModified) { + //console.log(`Project id modified for task ${lineTask_ticktick_id}`) + //updatedContent.projectId = lineTask.projectId + //projectChanged = false; + } + + //ticktick Rest api has no excuse to modify parent id + if (parentIdModified) { + //console.log(`Parnet id modified for task ${lineTask_ticktick_id}`) + //updatedContent.parentId = lineTask.parentId + //parentIdChanged = false; + } + + if (priorityModified) { + + updatedContent.priority = lineTask.priority + priorityChanged = true; + } + + + if (contentChanged || tagsChanged ||dueDateChanged ||projectChanged || parentIdChanged || priorityChanged) { + //console.log("task content was modified"); + //console.log(updatedContent) + // const updatedTask = await this.plugin.ticktickRestAPI.UpdateTask(lineTask.ticktick_id.toString(),updatedContent) + //TODO: Just shoving the whole task in. Should we do updated content? + const updatedTask = await this.plugin.tickTickRestAPI.UpdateTask(lineTask) + updatedTask.path = filepath + this.plugin.cacheOperation.updateTaskToCacheByID(updatedTask); + } + + if (statusModified) { + console.log(`Status modified for task ${lineTask_ticktick_id}`) + if (lineTask.isCompleted === true) { + console.log(`task completed`) + this.plugin.tickTickRestAPI.CloseTask(lineTask.id, lineTask.projectId); + this.plugin.cacheOperation.closeTaskToCacheByID( lineTask.id); + } else { + console.log(`task umcompleted`) + this.plugin.tickTickRestAPI.OpenTask(lineTask.id, lineTask.projectId); + this.plugin.cacheOperation.reopenTaskToCacheByID( lineTask.id); + } + + statusChanged = true; + } + + + + if (contentChanged || statusChanged || dueDateChanged || tagsChanged || projectChanged || priorityChanged) { + console.log(lineTask) + console.log(savedTask) + //`Task ${lastLineTaskticktickId} was modified` + this.plugin.saveSettings() + let message = `Task ${lineTask_ticktick_id} is updated.`; + + if (contentChanged) { + message += "Content was changed."; + } + if (statusChanged) { + message += "Status was changed."; + } + if (dueDateChanged) { + message += "Due date was changed."; + } + if (tagsChanged) { + message += " Tags were changed."; + } + if (projectChanged) { + message += "Project was changed."; + } + if (priorityChanged) { + message += "Priority was changed."; + } + + new Notice(message); + + } else { + //console.log(`Task ${lineTask_ticktick_id} did not change`); + } + + } catch (error) { + console.error('Error updating task:', error); + } + + + } + } + + + async fullTextModifiedTaskCheck(file_path: string): Promise { + + let file; + let currentFileValue; + let view; + let filepath; + + try { + if (file_path) { + file = this.app.vault.getAbstractFileByPath(file_path); + filepath = file_path; + currentFileValue = await this.app.vault.read(file); + } else { + view = this.app.workspace.getActiveViewOfType(MarkdownView); + file = this.app.workspace.getActiveFile(); + filepath = file?.path; + currentFileValue = view?.data; + } + + const content = currentFileValue; + + let hasModifiedTask = false; + const lines = content.split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (this.plugin.taskParser.hasTickTickId(line) && this.plugin.taskParser.hasTickTickTag(line)) { + try { + await this.lineModifiedTaskCheck(filepath, line, i, content); + hasModifiedTask = true; + } catch (error) { + console.error('Error modifying task:', error); + continue; + } + } + } + + if (hasModifiedTask) { + try { + // Perform necessary actions on the modified content and front matter + } catch (error) { + console.error('Error processing modified content:', error); + } + } + } catch (error) { + console.error('Error:', error); + } + } + + + // Close a task by calling API and updating JSON file + async closeTask(taskId: string): Promise { + try { + await this.plugin.ticktickRestAPI.CloseTask(taskId); + await this.plugin.fileOperation.completeTaskInTheFile(taskId) + await this.plugin.cacheOperation.closeTaskToCacheByID(taskId); + this.plugin.saveSettings() + new Notice(`Task ${taskId} is closed.`) + } catch (error) { + console.error('Error closing task:', error); + throw error; // Throw an error so that the caller can catch and handle it + } + } + + //open task + async repoenTask(taskId:string) : Promise{ + try { + await this.plugin.ticktickRestAPI.OpenTask(taskId) + await this.plugin.fileOperation.uncompleteTaskInTheFile(taskId) + await this.plugin.cacheOperation.reopenTaskToCacheByID(taskId) + this.plugin.saveSettings() + new Notice(`Task ${taskId} is reopened.`) + } catch (error) { + console.error('Error opening task:', error); + throw error; // Throw an error so that the caller can catch and handle it + } + } + + + /** + * Delete the task with the specified ID from the task list and update the JSON file + * @param taskIds array of task IDs to be deleted + * @returns Returns the successfully deleted task ID array + */ + async deleteTasksByIds(taskIds: string[]): Promise { + const deletedTaskIds = []; + + for (const taskId of taskIds) { + const api = await this.plugin.ticktickRestAPI.initializeAPI() + try { + const response = await api.deleteTask(taskId); + console.log(`response is ${response}`); + + if (response) { + //console.log(`Task ${taskId} deleted successfully`); + new Notice(`Task ${taskId} is deleted.`) + deletedTaskIds.push(taskId); // Add the deleted task ID to the array + } + } catch (error) { + console.error(`Failed to delete task ${taskId}: ${error}`); + // You can add better error handling methods, such as throwing exceptions or logging here, etc. + } + } + + if (!deletedTaskIds.length) { + console.log("Task not deleted"); + return []; + } + + await this.plugin.cacheOperation.deleteTaskFromCacheByIDs(deletedTaskIds); // Update JSON file + this.plugin.saveSettings() + //console.log(`A total of ${deletedTaskIds.length} tasks were deleted`); + + + return deletedTaskIds; + } + + + + + + + + + + // Synchronize completed task status to Obsidian file + async syncCompletedTaskStatusToObsidian(unSynchronizedEvents) { + // Get unsynchronized events + //console.log(unSynchronizedEvents) + try { + + // Handle unsynchronized events and wait for all processing to complete + const processedEvents = [] + for (const e of unSynchronizedEvents) { //If you want to modify the code so that completeTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: + //console.log(`Completing ${e.object_id}`) + await this.plugin.fileOperation.completeTaskInTheFile(e.object_id) + await this.plugin.cacheOperation.closeTaskToCacheByID(e.object_id) + new Notice(`Task ${e.object_id} is closed.`) + processedEvents.push(e) + } + + // Save events to the local database." + //const allEvents = [...savedEvents, ...unSynchronizedEvents] + await this.plugin.cacheOperation.appendEventsToCache(processedEvents) + this.plugin.saveSettings() + + + + + + } catch (error) { + console.error('Error synchronizing task status:', error) + } + } + + + // Synchronize completed task status to Obsidian file + async syncUncompletedTaskStatusToObsidian(unSynchronizedEvents) { + + //console.log(unSynchronizedEvents) + + try { + + // Handle unsynchronized events and wait for all processing to complete + const processedEvents = [] + for (const e of unSynchronizedEvents) { //If you want to modify the code so that uncompleteTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: + //console.log(`uncheck task: ${e.object_id}`) + await this.plugin.fileOperation.uncompleteTaskInTheFile(e.object_id) + await this.plugin.cacheOperation.reopenTaskToCacheByID(e.object_id) + new Notice(`Task ${e.object_id} is reopened.`) + processedEvents.push(e) + } + + + + // Merge new events into existing events and save to JSON + //const allEvents = [...savedEvents, ...unSynchronizedEvents] + await this.plugin.cacheOperation.appendEventsToCache(processedEvents) + this.plugin.saveSettings() + } catch (error) { + console.error('Error synchronizing task status:', error) + } + } + + // Synchronize updated item status to Obsidian + async syncUpdatedTaskToObsidian(unSynchronizedEvents) { + //console.log(unSynchronizedEvents) + try { + + // Handle unsynchronized events and wait for all processing to complete + const processedEvents = [] + for (const e of unSynchronizedEvents) { //If you want to modify the code so that completeTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: + //console.log(`Syncing ${e.object_id} changes to local`) + console.log(e) + console.log(typeof e.extra_data.last_due_date === 'undefined') + if(!(typeof e.extra_data.last_due_date === 'undefined')){ + //console.log(`prepare update dueDate`) + await this.syncUpdatedTaskDueDateToObsidian(e) + + } + + if(!(typeof e.extra_data.last_content === 'undefined')){ + //console.log(`prepare update content`) + await this.syncUpdatedTaskContentToObsidian(e) + } + + //await this.plugin.fileOperation.syncUpdatedTaskToTheFile(e) + //Also modify the data in the cache + //new Notice(`Task ${e.object_id} is updated.`) + processedEvents.push(e) + } + + + + // Merge new events into existing events and save to JSON + //const allEvents = [...savedEvents, ...unSynchronizedEvents] + await this.plugin.cacheOperation.appendEventsToCache(processedEvents) + this.plugin.saveSettings() + } catch (error) { + console.error('Error syncing updated item', error) + } + + } + + async syncUpdatedTaskContentToObsidian(e){ + this.plugin.fileOperation.syncUpdatedTaskContentToTheFile(e) + const content = e.extra_data.content + this.plugin.cacheOperation.modifyTaskToCacheByID(e.object_id,{content}) + new Notice(`The content of Task ${e.parent_item_id} has been modified.`) + + } + + async syncUpdatedTaskDueDateToObsidian(e){ + this.plugin.fileOperation.syncUpdatedTaskDueDateToTheFile(e) + //To modify the cache date, use ticktick format + const due = await this.plugin.ticktickRestAPI.getTaskDueById(e.object_id) + this.plugin.cacheOperation.modifyTaskToCacheByID(e.object_id,{due}) + new Notice(`The due date of Task ${e.parent_item_id} has been modified.`) + + } + + // sync added task note to obsidian + async syncAddedTaskNoteToObsidian(unSynchronizedEvents) { + // Get unsynchronized events + //console.log(unSynchronizedEvents) + try { + + // Handle unsynchronized events and wait for all processing to complete + const processedEvents = [] + for (const e of unSynchronizedEvents) { //If you want to modify the code so that completeTaskInTheFile(e.object_id) is executed in order, you can change the Promise.allSettled() method to use a for...of loop to handle unsynchronized events . Specific steps are as follows: + console.log(e) + //const taskid = e.parent_item_id + //const note = e.extra_data.content + await this.plugin.fileOperation.syncAddedTaskNoteToTheFile(e) + //await this.plugin.cacheOperation.closeTaskToCacheByID(e.object_id) + new Notice(`Task ${e.parent_item_id} note is added.`) + processedEvents.push(e) + } + + // Merge new events into existing events and save to JSON + + await this.plugin.cacheOperation.appendEventsToCache(processedEvents) + this.plugin.saveSettings() + } catch (error) { + console.error('Error synchronizing task status:', error) + } + } + + async SyncTickTickToObsidian(){ + try{ + //TODO: Start FA find the FO + if (!this.plugin.tickTickSyncAPI) { + console.log("Sorry about your api luck") + } + const all_ticktick_resources = await this.plugin.tickTickSyncAPI.getAllResources(); + const all_ticktick_tasks = all_ticktick_resources.syncTaskBean.update; + console.log("All tasks: ", all_ticktick_tasks) + + const savedTasks = await this.plugin.cacheOperation.loadTasksFromCache() + console.log("saved tasks: ", savedTasks); + // Find the task activity whose task id exists in Obsidian + const result2 = all_ticktick_tasks.filter( + (objA) => savedTasks.some((objB) => objB.id === objA.object_id) + ) + // Find the note activity whose task id exists in Obsidian + const result3 = result1.filter( + (objA) => savedTasks.some((objB) => objB.id === objA.parent_item_id) + ) + + + + + const unsynchronized_item_completed_events = this.plugin.ticktickSyncAPI.filterActivityEvents(result2, { event_type: 'completed', object_type: 'item' }) + const unsynchronized_item_uncompleted_events = this.plugin.ticktickSyncAPI.filterActivityEvents(result2, { event_type: 'uncompleted', object_type: 'item' }) + + //Items updated (only changes to content, description, due_date and responsible_uid) + const unsynchronized_item_updated_events = this.plugin.ticktickSyncAPI.filterActivityEvents(result2, { event_type: 'updated', object_type: 'item' }) + + const unsynchronized_notes_added_events = this.plugin.ticktickSyncAPI.filterActivityEvents(result3, { event_type: 'added', object_type: 'note' }) + const unsynchronized_project_events = this.plugin.ticktickSyncAPI.filterActivityEvents(result1, { object_type: 'project' }) + console.log(unsynchronized_item_completed_events) + console.log(unsynchronized_item_uncompleted_events) + console.log(unsynchronized_item_updated_events) + console.log(unsynchronized_project_events) + console.log(unsynchronized_notes_added_events) + + await this.syncCompletedTaskStatusToObsidian(unsynchronized_item_completed_events) + await this.syncUncompletedTaskStatusToObsidian(unsynchronized_item_uncompleted_events) + await this.syncUpdatedTaskToObsidian(unsynchronized_item_updated_events) + await this.syncAddedTaskNoteToObsidian(unsynchronized_notes_added_events) + if(unsynchronized_project_events.length){ + console.log('New project event') + await this.plugin.cacheOperation.saveProjectsToCache() + await this.plugin.cacheOperation.appendEventsToCache(unsynchronized_project_events) + } + + + }catch (err){ + console.error('An error occurred while synchronizing:', err); + } + + } + + + + async backupTickTickAllResources() { + try { + console.log("backing up.") + if (this.plugin.tickTickSyncAPI) { + console.log("It's defined", this.plugin.tickTickSyncAPI) + } + const resources = await this.plugin.tickTickSyncAPI.getAllResources() + + const now: Date = new Date(); + const timeString: string = `${now.getFullYear()}${now.getMonth()+1}${now.getDate()}${now.getHours()}${now.getMinutes()}${ now.getSeconds()}`; + + const name = "ticktick-backup-"+timeString+".json" + + this.app.vault.create(name,JSON.stringify(resources)) + //console.log(`ticktick backup successful`) + new Notice(`TickTick backup data is saved in the path ${name}`) + } catch (error) { + console.error("An error occurred while creating TickTick backup:", error); + } + + } + + + //After renaming the file, check all tasks in the file and update all links. + async updateTaskDescription(filepath:string){ + const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath) + if(!metadata || !metadata.ticktickTasks){ + return + } + const description = this.plugin.taskParser.getObsidianUrlFromFilepath(filepath) + let updatedContent = {} + updatedContent.description = description + try { + metadata.ticktickTasks.forEach(async(taskId) => { + //TODO: Just shoving the whole task in. Should we do updated content? + const updatedTask = await this.plugin.tickTickRestAPI.UpdateTask(lineTask) + updatedTask.path = filepath + this.plugin.cacheOperation.updateTaskToCacheByID(updatedTask); + + }); + } catch(error) { + console.error('An error occurred in updateTaskDescription:', error); + } + + + + } + + + + + + } + \ No newline at end of file diff --git a/src/taskParser.ts b/src/taskParser.ts index 36c22c5..76303bb 100644 --- a/src/taskParser.ts +++ b/src/taskParser.ts @@ -39,21 +39,20 @@ interface TickTickTaskObject { priority?: number | null; due_string?: string; due_date?: string; - due_datetime?: string; due_lang?: string; assignee_id?: string; } const keywords = { - TickTick_TAG: "#TickTick", + TickTick_TAG: "#ticktick", DUE_DATE: "🗓️|📅|📆|🗓", }; const REGEX = { - TickTick_TAG: new RegExp(`^[\\s]*[-] \\[[x ]\\] [\\s\\S]*${keywords.TickTick_TAG}[\\s\\S]*$ `, "i"), - TickTick_ID: /\[TickTick_id::\s*\d+\]/, - TickTick_ID_NUM:/\[TickTick_id::\s*(.*?)\]/, + TickTick_TAG: new RegExp(`^[\\s]*[-] \\[[x ]\\] [\\s\\S]*${keywords.TickTick_TAG}[\\s\\S]*$`, "i"), + TickTick_ID: /\[ticktick_id::\s*[\d\S]+\]/, + TickTick_ID_NUM:/\[ticktick_id::\s*(.*?)\]/, TickTick_LINK:/\[link\]\(.*?\)/, DUE_DATE_WITH_EMOJ: new RegExp(`(${keywords.DUE_DATE})\\s?\\d{4}-\\d{2}-\\d{2}`), DUE_DATE : new RegExp(`(?:${keywords.DUE_DATE})\\s?(\\d{4}-\\d{2}-\\d{2})`), @@ -128,7 +127,7 @@ export class TaskParser { parentId = this.getTickTickIdFromLineText(line) hasParent = true //console.log(`parent id is ${parentId}`) - parentTaskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(parentId) + parentTaskObject = this.plugin.cacheOperation.loadTaskFromCacheID(parentId) break } else{ @@ -158,19 +157,22 @@ export class TaskParser { } if(!hasParent){ //Match tag and person - for (const label of labels){ - - //console.log(label) - let labelName = label.replace(/#/g, ""); - //console.log(labelName) - let hasProjectId = this.plugin.cacheOperation.getProjectIdByNameFromCache(labelName) - if(!hasProjectId){ - continue + console.log("labels: ", labels) + if (labels) { + for (const label of labels){ + + //console.log(label) + let labelName = label.replace(/#/g, ""); + //console.log(labelName) + let hasProjectId = this.plugin.cacheOperation.getProjectIdByNameFromCache(labelName) + if(!hasProjectId){ + continue + } + projectName = labelName + //console.log(`project is ${projectName} ${label}`) + projectId = hasProjectId + break } - projectName = labelName - //console.log(`project is ${projectName} ${label}`) - projectId = hasProjectId - break } } @@ -184,22 +186,23 @@ export class TaskParser { let url = encodeURI(`obsidian://open?vault=${this.app.vault.getName()}&file=${filepath}`) description =`[${filepath}](${url})`; } - - const TickTickTask = { + const task: ITask = { + id:TickTick_id || null, projectId: projectId, - content: content || '', + title: content, + //todo: look for actual content! For now, using description + //content: ?? + content: description, parentId: parentId || null, + //TODO: is this date right? dueDate: dueDate || '', labels: labels || [], - description: description, isCompleted:isCompleted, - TickTick_id:TickTick_id || null, - hasParent:hasParent, - priority:priority - }; - //console.log(`converted task `) - //console.log(TickTickTask) - return TickTickTask; + priority:priority, + status: isCompleted? 2: 0, //Status: 0 is no completed. Anything else is completed. + } + // console.log("new task: ", task) + return task; } @@ -208,6 +211,7 @@ export class TaskParser { hasTickTickTag(text:string){ //console.log("Check whether TickTick tag is included") //console.log(text) + // console.log("Regex is: ", REGEX.TickTick_TAG) return(REGEX.TickTick_TAG.test(text)) } @@ -215,8 +219,8 @@ export class TaskParser { hasTickTickId(text:string){ const result = REGEX.TickTick_ID.test(text) - //console.log("Check whether TickTick id is included") - //console.log(text) + // console.log("Check whether TickTick id is included") + // console.log(text, result) return(result) } @@ -323,13 +327,22 @@ export class TaskParser { //tag compare - taskTagCompare(lineTask:Object,TickTickTask:Object) { - - - const lineTaskTags = lineTask.labels + taskTagCompare(lineTask:ITask,TickTickTask:ITask) { + + const lineTaskTags = lineTask.tags? lineTask.tags : [] + if (!lineTaskTags) { + return false; //no lables. + } else { + // console.log("line tags: ", lineTaskTags); + } //console.log(dataviewTaskTags) - const TickTickTaskTags = TickTickTask.labels + const TickTickTaskTags = TickTickTask.tags? TickTickTask.tags: [] + if (!TickTickTaskTags) { + return false; // no lables. + } else { + // console.log("ticktick tags: ", TickTickTaskTags) + } //console.log(TickTickTaskTags) //Whether content is modified?