diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d8fb648..a082091 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,7 +6,7 @@ on:
- "*"
env:
- PLUGIN_NAME: ultimate-todoist-sync # Change this to match the id of your plugin.
+ PLUGIN_NAME: ultimate-TickTick-sync # Change this to match the id of your plugin.
jobs:
build:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6f11bf..5d719c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,21 +1,2 @@
## CHANGELOG
### prelease [1.0.38] - 2023-06-09
-
-https://github.com/HeroBlackInk/ultimate-todoist-sync-for-obsidian/releases/tag/v1.0.38-beta
-
-- New feature
- - 1.0.38 beta now supports date formats for tasks.
- - Todoist task link is added.
-
-### prelease [1.0.37] - 2023-06-05
-
-https://github.com/HeroBlackInk/ultimate-todoist-sync-for-obsidian/releases/tag/v1.0.37-beta
-
-- New feature
- - Two-way automatic synchronization, no longer need to manually click the sync button.
- - Full vault sync option, automatically adding `#todoist` to all tasks.
- - Notes/comments one-way synchronization from Todoist to Obsidian.
-- Bug fix
- - Fixed the bug of time zone conversion.
- - Removed the "#" from the Todoist label.
- - Update the obsidian link in Todoist after moving or renaming a file.
diff --git a/README.md b/README.md
index 2580d70..682249a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
-# Ultimate Todoist Sync for Obsidian
+# Ultimate TickTick Sync for Obsidian
-The Ultimate Todoist Sync plugin automatically creates tasks in Todoist and synchronizes task state between Obsidian and Todoist.
+> [!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.
## Demo
@@ -15,7 +19,7 @@ The Ultimate Todoist Sync plugin automatically creates tasks in Todoist and sync
## Features
###
-| Feature | Sync from Obsidian to Todoist | Sync from Todoist to Obsidian | Description |
+| Feature | Sync from Obsidian to TickTick | Sync from TickTick to Obsidian | Description |
|-------------------------|-------------------------------|-------------------------------|-------------|
| Add task | ✅ | 🔜 | |
| Delete task | ✅ | 🔜 | |
@@ -27,12 +31,12 @@ The Ultimate Todoist Sync plugin automatically creates tasks in Todoist and sync
| Mark task as uncompleted| ✅ | ✅ | |
| Modify project | 🔜 | 🔜 | |
| Modify section | 🔜 | 🔜 | |
-| Modify priority | ✅ | 🔜 | Currently, task priority only support one-way synchronization from Todoist 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 Todoist to Obsidian. |
+| Task notes | 🔜 | ✅ | Currently, task notes/comments only support one-way synchronization from TickTick to Obsidian. |
## Installation
@@ -45,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 Todoist 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 Todoist 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`
@@ -56,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-todoist-sync-for-obsidian/releases) page.
+1. Download the latest release of the plugin from the [Releases](https://github.com/HeroBlackInk/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.
@@ -65,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 Todoist Sync` plugin
-4. Enter your Todoist API token
+3. Under `Installed plugins`, click the gear icon next to the `Ultimate TickTick Sync` plugin
+4. Enter your TickTick API token
## Settings
@@ -75,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 `#todoist` 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
@@ -84,11 +88,11 @@ By enabling this option, the plugin will automatically add `#todoist` to all tas
| Syntax | Description | Example |
| --- | --- | --- |
-|#todoist|Tasks marked with `#todoist` will be added to Todoist, while tasks without the `#todoist` tag will not be processed.If you have enabled Full vault sync in the settings, `#todoist` will be added automatically.| `- [ ] task #todoist`|
-| 📅YYYY-MM-DD | The date format is 📅YYYY-MM-DD, indicating the due date of a task. | `- [ ] task content 📅2025-02-05 #todoist`
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 #todoist` will be added to inbox.
`- [ ] taskB #tag #testProject #todoist` 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 #todoist` |
-| `!!` | 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 Todoist is designed.). | `- [ ] task !!4 #todoist` |
+|#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/esbuild.config.mjs b/esbuild.config.mjs
index b13282b..e0a2df5 100644
--- a/esbuild.config.mjs
+++ b/esbuild.config.mjs
@@ -2,6 +2,23 @@ import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
+import { copy } from 'esbuild-plugin-copy';
+
+import fs from 'fs';
+
+const copyManifestPlugin = () => ({
+ name: 'copy-manifest-plugin',
+ setup(build) {
+ build.onEnd(async () => {
+ try {
+ fs.copyFileSync('./manifest.json', './dist/manifest.json');
+ } catch (e) {
+ console.error('Failed to copy file:', e);
+ }
+ });
+ },
+});
+
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
@@ -32,17 +49,27 @@ const context = await esbuild.context({
"@lezer/highlight",
"@lezer/lr",
...builtins],
- format: "cjs",
- target: "es2018",
- logLevel: "info",
- sourcemap: prod ? false : "inline",
- treeShaking: true,
- outfile: "main.js",
-});
-
-if (prod) {
- await context.rebuild();
- process.exit(0);
-} else {
- await context.watch();
-}
\ No newline at end of file
+ format: "cjs",
+ target: "es2018",
+ logLevel: "info",
+ sourcemap: prod ? false : "inline",
+ treeShaking: true,
+ outdir: './dist',
+ plugins: [
+ copy({
+ resolveFrom: 'cwd', // Returns name of current working directory
+ assets: {
+ from: ['./src/static/**/*'],
+ to: ['./dist'],
+ },
+ }),
+ copyManifestPlugin(),
+ ],
+ });
+
+ if (prod) {
+ await context.rebuild();
+ process.exit(0);
+ } else {
+ await context.watch();
+ }
\ No newline at end of file
diff --git a/main.ts b/main.ts
index c6536cc..facd0e1 100644
--- a/main.ts
+++ b/main.ts
@@ -2,11 +2,11 @@ import { MarkdownView, Notice, Plugin ,Editor, WorkspaceLeaf} from 'obsidian';
//settings
-import { UltimateTodoistSyncSettings,DEFAULT_SETTINGS,UltimateTodoistSyncSettingTab } from './src/settings';
-//todoist api
-import { TodoistRestAPI } from './src/todoistRestAPI';
-import { TodoistSyncAPI } from './src/todoistSyncAPI';
-//task parser
+import { UltimateTickTickSyncSettings,DEFAULT_SETTINGS,UltimateTickTickSyncSettingTab } from './src/settings';
+//TickTick api
+import { TickTickRestAPI } from './src/TicktickRestAPI';
+import { TickTickSyncAPI } from './src/TicktickSyncAPI';
+//task parser
import { TaskParser } from './src/taskParser';
//cache task read and write
import { CacheOperation } from './src/cacheOperation';
@@ -14,628 +14,634 @@ import { CacheOperation } from './src/cacheOperation';
import { FileOperation } from './src/fileOperation';
//sync module
-import { TodoistSync } from './src/syncModule';
+import { TickTickSync } from './src/syncModule';
//import modal
import { SetDefalutProjectInTheFilepathModal } from 'src/modal';
-export default class UltimateTodoistSyncForObsidian extends Plugin {
- settings: UltimateTodoistSyncSettings;
- todoistRestAPI: TodoistRestAPI | undefined;
- todoistSyncAPI: TodoistSyncAPI | undefined;
+export default class UltimateTickTickSyncForObsidian extends Plugin {
+ settings: UltimateTickTickSyncSettings;
+ tickTickRestAPI: TicktickRestAPI | undefined;
+ tickTickSyncAPI: TicktickSyncAPI | undefined;
taskParser: TaskParser | undefined;
cacheOperation: CacheOperation | undefined;
fileOperation: FileOperation | undefined;
- todoistSync: TodoistSync | undefined;
- lastLines: Map;
- statusBar;
- syncLock: Boolean;
-
- async onload() {
-
- const isSettingsLoaded = await this.loadSettings();
-
- if(!isSettingsLoaded){
- new Notice('Settings failed to load.Please reload the ultimate todoist sync plugin.');
- return;
- }
- // This adds a settings tab so the user can configure various aspects of the plugin
- this.addSettingTab(new UltimateTodoistSyncSettingTab(this.app, this));
- if (!this.settings.todoistAPIToken) {
- new Notice('Please enter your Todoist API.');
- //return
- }else{
- await this.initializePlugin();
- }
-
- //lastLine 对象 {path:line}保存在lastLines map中
- this.lastLines = new Map();
-
-
-
-
-
-
-
-
- //key 事件监听,判断换行和删除
- this.registerDomEvent(document, 'keyup', async (evt: KeyboardEvent) =>{
- if(!this.settings.apiInitialized){
- return
- }
- //console.log(`key pressed`)
-
- //判断点击事件发生的区域,如果不在编辑器中,return
- if (!(this.app.workspace.activeEditor?.editor?.hasFocus())) {
- (console.log(`editor is not focused`))
- return
- }
- const view = this.app.workspace.getActiveViewOfType(MarkdownView);
- const editor = view?.app.workspace.activeEditor?.editor
-
- if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'ArrowLeft' || evt.key === 'ArrowRight' ||evt.key === 'PageUp' || evt.key === 'PageDown') {
- //console.log(`${evt.key} arrow key is released`);
- if(!( this.checkModuleClass())){
- return
- }
- this.lineNumberCheck()
- }
- if(evt.key === "Delete" || evt.key === "Backspace"){
- try{
- //console.log(`${evt.key} key is released`);
- if(!( this.checkModuleClass())){
- return
- }
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.deletedTaskCheck();
- this.syncLock = false;
- this.saveSettings()
- }catch(error){
- console.error(`An error occurred while deleting tasks: ${error}`);
- this.syncLock = false
- }
-
- }
- });
-
- // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
- // Using this function will automatically remove the event listener when this plugin is disabled.
- this.registerDomEvent(document, 'click', async (evt: MouseEvent) => {
- if(!this.settings.apiInitialized){
- return
- }
- //console.log('click', evt);
- if (this.app.workspace.activeEditor?.editor?.hasFocus()) {
- //console.log('Click event: editor is focused');
- const view = this.app.workspace.getActiveViewOfType(MarkdownView)
- const editor = this.app.workspace.activeEditor?.editor
- this.lineNumberCheck()
- }
- else{
- //
- }
-
- const target = evt.target as HTMLInputElement;
-
- if (target.type === "checkbox") {
- if(!(this.checkModuleClass())){
- return
- }
- this.checkboxEventhandle(evt)
- //this.todoistSync.fullTextModifiedTaskCheck()
-
- }
-
- });
-
-
-
- //hook editor-change 事件,如果当前line包含 #todoist,说明有new task
- this.registerEvent(this.app.workspace.on('editor-change',async (editor,view:MarkdownView)=>{
- try{
- if(!this.settings.apiInitialized){
- return
- }
-
- this.lineNumberCheck()
- if(!(this.checkModuleClass())){
- return
- }
- if(this.settings.enableFullVaultSync){
- return
- }
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.lineContentNewTaskCheck(editor,view)
- this.syncLock = false
- this.saveSettings()
-
- }catch(error){
- console.error(`An error occurred while check new task in line: ${error.message}`);
- this.syncLock = false
- }
-
- }))
-
-
-
-/* 使用其他文件管理器移动,obsidian触发了删除事件,删除了所有的任务
- //监听删除事件,当文件被删除后,读取frontMatter中的tasklist,批量删除
- this.registerEvent(this.app.metadataCache.on('deleted', async(file,prevCache) => {
- try{
- if(!this.settings.apiInitialized){
- return
- }
- //console.log('a new file has modified')
- console.log(`file deleted`)
- //读取frontMatter
- const frontMatter = await this.cacheOperation.getFileMetadata(file.path)
- if(frontMatter === null || frontMatter.todoistTasks === undefined){
- console.log('There is no task in the deleted files.')
- return
- }
- //判断todoistTasks是否为null
- console.log(frontMatter.todoistTasks)
- if(!( this.checkModuleClass())){
- return
- }
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.deleteTasksByIds(frontMatter.todoistTasks)
- this.syncLock = false
- this.saveSettings()
- }catch(error){
- console.error(`An error occurred while deleting task in the file: ${error}`);
- this.syncLock = false
- }
-
-
-
- }));
-*/
-
-
- //监听 rename 事件,更新 task data 中的 path
- this.registerEvent(this.app.vault.on('rename', async (file,oldpath) => {
- if(!this.settings.apiInitialized){
- return
- }
- console.log(`${oldpath} is renamed`)
- //读取frontMatter
- //const frontMatter = await this.fileOperation.getFrontMatter(file)
- const frontMatter = await this.cacheOperation.getFileMetadata(oldpath)
- console.log(frontMatter)
- if(frontMatter === null || frontMatter.todoistTasks === undefined){
- //console.log('删除的文件中没有task')
- return
- }
- if(!(this.checkModuleClass())){
- return
- }
- await this.cacheOperation.updateRenamedFilePath(oldpath,file.path)
- this.saveSettings()
-
- //update task description
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.updateTaskDescription(file.path)
- } catch(error) {
- console.error('An error occurred in updateTaskDescription:', error);
- }
- this.syncLock = false;
-
- }));
-
-
- //Listen for file modified events and execute fullTextNewTaskCheck
- this.registerEvent(this.app.vault.on('modify', async (file) => {
- try {
- if(!this.settings.apiInitialized){
- return
- }
- const filepath = file.path
- console.log(`${filepath} is modified`)
-
- //get current view
-
- const activateFile = this.app.workspace.getActiveFile()
-
- console.log(activateFile?.path)
-
- //To avoid conflicts, Do not check files being edited
- if(activateFile?.path == filepath){
- return
- }
-
- if (!await this.checkAndHandleSyncLock()) return;
-
- await this.todoistSync.fullTextNewTaskCheck(filepath)
- this.syncLock = false;
- } catch(error) {
- console.error(`An error occurred while modifying the file: ${error.message}`);
- this.syncLock = false
- // You can add further error handling logic here. For example, you may want to
- // revert certain operations, or alert the user about the error.
- }
- }));
-
- this.registerInterval(window.setInterval(async () => await this.scheduledSynchronization(), this.settings.automaticSynchronizationInterval * 1000));
-
- this.app.workspace.on('active-leaf-change',(leaf)=>{
- this.setStatusBarText()
- })
-
-
- // set default project for todoist task in the current file
- // This adds an editor command that can perform some operation on the current editor instance
- this.addCommand({
- id: 'set-default-project-for-todoist-task-in-the-current-file',
- name: 'Set default project for todoist task in the current file',
- editorCallback: (editor: Editor, view: MarkdownView) => {
- if(!view){
- return
- }
- const filepath = view.file.path
- new SetDefalutProjectInTheFilepathModal(this.app,this,filepath)
-
- }
- });
-
- //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();
-
-
- }
-
-
- async onunload() {
- console.log(`Ultimate Todoist Sync for Obsidian id unloaded!`)
- await this.saveSettings()
-
- }
-
- async loadSettings() {
- try {
- const data = await this.loadData();
- this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
- return true; // 返回 true 表示设置加载成功
- } catch (error) {
- console.error('Failed to load data:', error);
- return false; // 返回 false 表示设置加载失败
- }
- }
-
- async saveSettings() {
- try {
- // 验证设置是否存在且不为空
- if (this.settings && Object.keys(this.settings).length > 0) {
- await this.saveData(this.settings);
- } else {
- console.error('Settings are empty or invalid, not saving to avoid data loss.');
- }
- } catch (error) {
- // 打印或处理错误
- console.error('Error saving settings:', error);
- }
- }
-
- async modifyTodoistAPI(api:string){
- await this.initializePlugin()
- }
-
- // return true of false
- async initializePlugin(){
-
- //initialize todoist restapi
- this.todoistRestAPI = new TodoistRestAPI(this.app, this)
-
- //initialize data read and write object
- this.cacheOperation = new CacheOperation(this.app, this)
- const isProjectsSaved = await this.cacheOperation.saveProjectsToCache()
-
-
-
- if(!isProjectsSaved){
- this.todoistRestAPI = undefined
- this.todoistSyncAPI = undefined
- this.taskParser = undefined
- this.taskParser = undefined
- this.cacheOperation = undefined
- this.fileOperation = undefined
- this.todoistSync = undefined
- new Notice(`Ultimate Todoist Sync plugin initialization failed, please check the todoist api`)
- return;
- }
-
- if(!this.settings.initialized){
-
- //创建备份文件夹备份todoist 数据
- try{
- //第一次启动插件,备份todoist 数据
- this.taskParser = new TaskParser(this.app, this)
-
- //initialize file operation
- this.fileOperation = new FileOperation(this.app,this)
-
- //initialize todoisy sync api
- this.todoistSyncAPI = new TodoistSyncAPI(this.app,this)
-
- //initialize todoist sync module
- this.todoistSync = new TodoistSync(this.app,this)
-
- //每次启动前备份所有数据
- this.todoistSync.backupTodoistAllResources()
-
- }catch(error){
- console.log(`error creating user data folder: ${error}`)
- new Notice(`error creating user data folder`)
- return;
- }
-
-
- //初始化settings
- this.settings.initialized = true
- this.saveSettings()
- new Notice(`Ultimate Todoist Sync initialization successful. Todoist data has been backed up.`)
-
- }
-
-
- this.initializeModuleClass()
-
-
- //get user plan resources
- //const rsp = await this.todoistSyncAPI.getUserResource()
- this.settings.apiInitialized = true
- this.syncLock = false
- new Notice(`Ultimate Todoist Sync loaded successfully.`)
- return true
-
-
-
- }
-
- async initializeModuleClass(){
-
- //initialize todoist restapi
- this.todoistRestAPI = new TodoistRestAPI(this.app,this)
-
- //initialize data read and write object
- this.cacheOperation = new CacheOperation(this.app,this)
- this.taskParser = new TaskParser(this.app,this)
-
- //initialize file operation
- this.fileOperation = new FileOperation(this.app,this)
-
- //initialize todoisy sync api
- this.todoistSyncAPI = new TodoistSyncAPI(this.app,this)
-
- //initialize todoist sync module
- this.todoistSync = new TodoistSync(this.app,this)
-
-
- }
-
- async lineNumberCheck(){
- const view = this.app.workspace.getActiveViewOfType(MarkdownView)
- if(view){
- const cursor = view.app.workspace.getActiveViewOfType(MarkdownView)?.editor.getCursor()
- const line = cursor?.line
- //const lineText = view.editor.getLine(line)
- const fileContent = view.data
-
- //console.log(line)
- //const fileName = view.file?.name
- const fileName = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.name
- const filepath = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.path
- if (typeof this.lastLines === 'undefined' || typeof this.lastLines.get(fileName as string) === 'undefined'){
- this.lastLines.set(fileName as string, line as number);
- return
- }
-
- //console.log(`filename is ${fileName}`)
- 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}`);
- }
-
-
- // 执行你想要的操作
- const lastLineText = view.editor.getLine(lastLine as number)
- //console.log(lastLineText)
- if(!( this.checkModuleClass())){
- return
- }
- this.lastLines.set(fileName as string, line as number);
- try{
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.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
- }
-
-
-
- }
- else {
- //console.log('Line not changed');
- }
-
- }
-
-
-
-
- }
-
- async checkboxEventhandle(evt:MouseEvent){
- if(!( this.checkModuleClass())){
- return
- }
- const target = evt.target as HTMLInputElement;
-
- const taskElement = target.closest("div"); //使用 evt.target.closest() 方法寻找特定的父元素,而不是直接访问事件路径中的特定索引
- //console.log(taskElement)
- if (!taskElement) return;
- const regex = /\[todoist_id:: (\d+)\]/; // 匹配 [todoist_id:: 数字] 格式的字符串
- const match = taskElement.textContent?.match(regex) || false;
- if (match) {
- const taskId = match[1];
- //console.log(taskId)
- //const view = this.app.workspace.getActiveViewOfType(MarkdownView);
- if (target.checked) {
- this.todoistSync.closeTask(taskId);
- } else {
- this.todoistSync.repoenTask(taskId);
- }
- } else {
- //console.log('未找到 todoist_id');
- //开始全文搜索,检查status更新
- try{
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.fullTextModifiedTaskCheck()
- this.syncLock = false;
- }catch(error){
- console.error(`An error occurred while check modified tasks in the file: ${error}`);
- this.syncLock = false;
- }
-
- }
- }
-
- //return true
- checkModuleClass(){
- if(this.settings.apiInitialized === true){
- if(this.todoistRestAPI === undefined || this.todoistSyncAPI === undefined ||this.cacheOperation === undefined || this.fileOperation === undefined ||this.todoistSync === undefined ||this.taskParser === undefined){
- this.initializeModuleClass()
- }
- return true
- }
- else{
- new Notice(`Please enter the correct Todoist API token"`)
- return(false)
- }
-
-
- }
-
- async setStatusBarText(){
- if(!( this.checkModuleClass())){
- return
- }
- const view = this.app.workspace.getActiveViewOfType(MarkdownView)
- if(!view){
- this.statusBar.setText('');
- }
- else{
- const filepath = this.app.workspace.getActiveViewOfType(MarkdownView)?.file.path
- if(filepath === undefined){
- console.log(`file path undefined`)
- return
- }
- const defaultProjectName = await this.cacheOperation.getDefaultProjectNameForFilepath(filepath as string)
- if(defaultProjectName === undefined){
- console.log(`projectName undefined`)
- return
- }
- this.statusBar.setText(defaultProjectName)
- }
-
- }
-
- async scheduledSynchronization() {
- if (!(this.checkModuleClass())) {
- return;
- }
- console.log("Todoist scheduled synchronization task started at", new Date().toLocaleString());
- try {
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.syncTodoistToObsidian();
- } catch(error) {
- console.error('An error occurred in syncTodoistToObsidian:', error);
- }
- this.syncLock = false;
- try {
- await this.saveSettings();
- } catch(error) {
- console.error('An error occurred in saveSettings:', error);
- }
-
- // Sleep for 5 seconds
- await new Promise(resolve => setTimeout(resolve, 5000));
-
- const filesToSync = this.settings.fileMetadata;
- if(this.settings.debugMode){
- console.log(filesToSync)
- }
-
- for (let fileKey in filesToSync) {
- if(this.settings.debugMode){
- console.log(fileKey)
- }
-
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.fullTextNewTaskCheck(fileKey);
- } catch(error) {
- console.error('An error occurred in fullTextNewTaskCheck:', error);
- }
- this.syncLock = false;
-
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.deletedTaskCheck(fileKey);
- } catch(error) {
- console.error('An error occurred in deletedTaskCheck:', error);
- }
- this.syncLock = false;
-
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.fullTextModifiedTaskCheck(fileKey);
- } catch(error) {
- console.error('An error occurred in fullTextModifiedTaskCheck:', error);
- }
- this.syncLock = false;
- }
-
- } catch (error) {
- console.error('An error occurred:', error);
- new Notice('An error occurred:', error);
- this.syncLock = false;
- }
- console.log("Todoist scheduled synchronization task completed at", new Date().toLocaleString());
- }
-
- async checkSyncLock() {
- let checkCount = 0;
- while (this.syncLock == true && checkCount < 10) {
- await new Promise(resolve => setTimeout(resolve, 1000));
- checkCount++;
- }
- if (this.syncLock == true) {
- return false;
- }
- return true;
- }
-
- async checkAndHandleSyncLock() {
- if (this.syncLock) {
- console.log('sync locked.');
- const isSyncLockChecked = await this.checkSyncLock();
- if (!isSyncLockChecked) {
- return false;
- }
- console.log('sync unlocked.')
- }
- this.syncLock = true;
- return true;
- }
-
+ tickTickSync: TickTickSync | undefined;
+ lastLines: Map;
+ statusBar: any;
+ syncLock: Boolean;
+
+ async onload() {
+
+ const isSettingsLoaded = await this.loadSettings();
+
+ if(!isSettingsLoaded){
+ new Notice('Settings failed to load. Please reload the ultimate TickTick sync plugin.');
+ return;
+ }
+ // This adds a settings tab so the user can configure various aspects of the plugin
+ this.addSettingTab(new UltimateTickTickSyncSettingTab(this.app, this));
+ if (!this.settings.username) {
+ new Notice('Please enter your TickTick username and password.');
+ //return
+ }else{
+ await this.initializePlugin();
+ }
+
+ //lastLine object {path:line} is saved in lastLines map
+ this.lastLines = new Map();
+
+
+
+
+
+
+
+
+ //Key event monitoring, judging line breaks and deletions
+ this.registerDomEvent(document, 'keyup', async (evt: KeyboardEvent) =>{
+ if(!this.settings.apiInitialized){
+ return
+ }
+ //console.log(`key pressed`)
+
+ //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`))
+ return
+ }
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView);
+ const editor = view?.app.workspace.activeEditor?.editor
+
+ if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'ArrowLeft' || evt.key === 'ArrowRight' ||evt.key === 'PageUp' || evt.key === 'PageDown') {
+ //console.log(`${evt.key} arrow key is released`);
+ if(!( this.checkModuleClass())){
+ return
+ }
+ this.lineNumberCheck()
+ }
+ if(evt.key === "Delete" || evt.key === "Backspace"){
+ try{
+ //console.log(`${evt.key} key is released`);
+ if(!( this.checkModuleClass())){
+ return
+ }
+ if (!await this.checkAndHandleSyncLock()) return;
+ await this.tickTickSync.deletedTaskCheck();
+ this.syncLock = false;
+ this.saveSettings()
+ }catch(error){
+ console.error(`An error occurred while deleting tasks: ${error}`);
+ this.syncLock = false
+ }
+
+ }
+ });
+
+ // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
+ // Using this function will automatically remove the event listener when this plugin is disabled.
+ this.registerDomEvent(document, 'click', async (evt: MouseEvent) => {
+ if(!this.settings.apiInitialized){
+ return
+ }
+ //console.log('click', evt);
+ if (this.app.workspace.activeEditor?.editor?.hasFocus()) {
+ //console.log('Click event: editor is focused');
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView)
+ const editor = this.app.workspace.activeEditor?.editor
+ this.lineNumberCheck()
+ }
+ else{
+ //
+ }
+
+ const target = evt.target as HTMLInputElement;
+
+ if (target.type === "checkbox") {
+ if(!(this.checkModuleClass())){
+ return
+ }
+ this.checkboxEventhandle(evt)
+ //this.TickTickSync.fullTextModifiedTaskCheck()
+
+ }
+
+ });
+
+
+
+ //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){
+ return
+ }
+
+ this.lineNumberCheck()
+ if(!(this.checkModuleClass())){
+ return
+ }
+ if(this.settings.enableFullVaultSync){
+ return
+ }
+ if (!await this.checkAndHandleSyncLock()) return;
+ console.log(`new task check ${this.tickTickSync}`)
+ await this.tickTickSync.lineContentNewTaskCheck(editor,view)
+ this.syncLock = false
+ this.saveSettings()
+
+ }catch(error){
+ console.error(`An error occurred while check new task in line: ${error.message}`);
+ this.syncLock = false
+ }
+
+ }))
+
+
+
+ /* 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.
+ this.registerEvent(this.app.metadataCache.on('deleted', async(file,prevCache) => {
+ try{
+ if(!this.settings.apiInitialized){
+ return
+ }
+ //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){
+ console.log('There is no task in the deleted files.')
+ return
+ }
+ //Determine whether TickTickTasks is null
+ console.log(frontMatter.TickTickTasks)
+ if(!( this.checkModuleClass())){
+ return
+ }
+ if (!await this.checkAndHandleSyncLock()) return;
+ await this.TickTickSync.deleteTasksByIds(frontMatter.TickTickTasks)
+ this.syncLock = false
+ this.saveSettings()
+ }catch(error){
+ console.error(`An error occurred while deleting task in the file: ${error}`);
+ this.syncLock = false
+ }
+
+
+
+ }));
+ */
+
+
+ //Listen to the rename event and update the path in task data
+ this.registerEvent(this.app.vault.on('rename', async (file,oldpath) => {
+ 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('There is no task in the deleted file')
+ return
+ }
+ if(!(this.checkModuleClass())){
+ return
+ }
+ await this.cacheOperation.updateRenamedFilePath(oldpath,file.path)
+ this.saveSettings()
+
+ //update task description
+ if (!await this.checkAndHandleSyncLock()) return;
+ try {
+ await this.tickTickSync.updateTaskDescription(file.path)
+ } catch(error) {
+ console.error('An error occurred in updateTaskDescription:', error);
+ }
+ this.syncLock = false;
+
+ }));
+
+
+ //Listen for file modified events and execute fullTextNewTaskCheck
+ this.registerEvent(this.app.vault.on('modify', async (file) => {
+ try {
+ if(!this.settings.apiInitialized){
+ return
+ }
+ const filepath = file.path
+ console.log(`${filepath} is modified`)
+
+ //get current view
+
+ const activateFile = this.app.workspace.getActiveFile()
+
+ console.log(activateFile?.path)
+
+ //To avoid conflicts, Do not check files being edited
+ if(activateFile?.path == filepath){
+ return
+ }
+
+ if (!await this.checkAndHandleSyncLock()) return;
+
+ await this.tickTickSync.fullTextNewTaskCheck(filepath)
+ this.syncLock = false;
+ } catch(error) {
+ console.error(`An error occurred while modifying the file: ${error.message}`);
+ this.syncLock = false
+ // You can add further error handling logic here. For example, you may want to
+ // revert certain operations, or alert the user about the error.
+ }
+ }));
+
+ this.registerInterval(window.setInterval(async () => await this.scheduledSynchronization(), this.settings.automaticSynchronizationInterval * 1000));
+
+ this.app.workspace.on('active-leaf-change',(leaf)=>{
+ this.setStatusBarText()
+ })
+
+
+ // set default project for TickTick task in the current file
+ // This adds an editor command that can perform some operation on the current editor instance
+ this.addCommand({
+ id: 'set-default-project-for-TickTick-task-in-the-current-file',
+ name: 'Set default project for TickTick task in the current file',
+ editorCallback: (editor: Editor, view: MarkdownView) => {
+ if(!view){
+ return
+ }
+ const filepath = view.file.path
+ new SetDefalutProjectInTheFilepathModal(this.app,this,filepath)
+
+ }
+ });
+
+ //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();
+
+
+ }
+
+
+ async onunload() {
+ console.log(`Ultimate TickTick Sync for Obsidian unloaded!`)
+ await this.saveSettings()
+
+ }
+
+ async loadSettings() {
+ try {
+ const data = await this.loadData();
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
+ return true; // Returning true indicates that the settings are loaded successfully
+ } catch (error) {
+ console.error('Failed to load data:', error);
+ return false; // Returning false indicates that the setting loading failed
+ }
+ }
+
+ async saveSettings() {
+ try {
+ // Verify that the setting exists and is not empty
+ if (this.settings && Object.keys(this.settings).length > 0) {
+ await this.saveData(this.settings);
+ } else {
+ console.error('Settings are empty or invalid, not saving to avoid data loss.');
+ }
+ } catch (error) {
+ //Print or handle errors
+ console.error('Error saving settings:', error);
+ }
+ }
+
+ async modifyTickTickAPI(){
+ console.log("modify")
+ await this.initializePlugin()
+ }
+
+ // return true of false
+ async initializePlugin(){
+
+ 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)
+
+
+ if(!isProjectsSaved){
+ this.tickTickRestAPI = undefined
+ this.tickTickSyncAPI = undefined
+ this.taskParser = undefined
+ this.taskParser = undefined
+ this.cacheOperation = undefined
+ this.fileOperation = undefined
+ this.tickTickSync = undefined
+ new Notice(`Ultimate TickTick Sync plugin initialization failed, please check the TickTick api`)
+ return;
+ }
+
+ if(!this.settings.initialized){
+
+ //Create a backup folder to back up TickTick data
+ try{
+ //Start the plug-in for the first time and back up TickTick data
+ this.taskParser = new TaskParser(this.app, this)
+
+ //initialize file operation
+ this.fileOperation = new FileOperation(this.app,this)
+
+ // //initialize todoisy 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}`)
+
+ //Back up all data before each startup
+ this.tickTickSync.backupTickTickAllResources()
+
+ }catch(error){
+ console.log(`error creating user data folder: ${error}`)
+ new Notice(`error creating user data folder`)
+ return;
+ }
+
+
+ //Initialize settings
+ this.settings.initialized = true
+ this.saveSettings()
+ new Notice(`Ultimate TickTick Sync initialization successful. TickTick data has been backed up.`)
+
+ }
+
+
+ this.initializeModuleClass()
+
+
+ //get user plan resources
+ //const rsp = await this.TickTickSyncAPI.getUserResource()
+ // this.settings.apiInitialized = true
+ this.syncLock = false
+ new Notice(`Ultimate TickTick Sync loaded successfully.`)
+ return true
+
+
+
+ }
+
+ async initializeModuleClass(){
+
+ //initialize TickTick restapi
+ this.tickTickRestAPI = new TickTickRestAPI(this.app, this);
+
+ //initialize data read and write object
+ this.cacheOperation = new CacheOperation(this.app,this)
+ this.taskParser = new TaskParser(this.app,this)
+
+ //initialize file operation
+ this.fileOperation = new FileOperation(this.app,this)
+
+ //initialize todoisy sync api
+ this.tickTickSyncAPI = new TickTickSyncAPI(this.app,this)
+
+ //initialize TickTick sync module
+ this.tickTickSync = new TickTickSync(this.app,this)
+
+
+ }
+
+ async lineNumberCheck(){
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView)
+ if(view){
+ const cursor = view.app.workspace.getActiveViewOfType(MarkdownView)?.editor.getCursor()
+ const line = cursor?.line
+ //const lineText = view.editor.getLine(line)
+ const fileContent = view.data
+
+ //console.log(line)
+ //const fileName = view.file?.name
+ const fileName = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.name
+ const filepath = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.path
+ if (typeof this.lastLines === 'undefined' || typeof this.lastLines.get(fileName as string) === 'undefined'){
+ this.lastLines.set(fileName as string, line as number);
+ return
+ }
+
+ //console.log(`filename is ${fileName}`)
+ 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}`);
+ }
+
+
+ //Perform the operation you want
+ const lastLineText = view.editor.getLine(lastLine as number)
+ //console.log(lastLineText)
+ if(!( this.checkModuleClass())){
+ return
+ }
+ this.lastLines.set(fileName as string, line as number);
+ 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
+ }
+
+
+
+ }
+ else {
+ //console.log('Line not changed');
+ }
+
+ }
+
+
+
+
+ }
+
+ async checkboxEventhandle(evt:MouseEvent){
+ if(!( this.checkModuleClass())){
+ return
+ }
+ const target = evt.target as HTMLInputElement;
+
+ 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 match = taskElement.textContent?.match(regex) || false;
+ if (match) {
+ const taskId = match[1];
+ //console.log(taskId)
+ //const view = this.app.workspace.getActiveViewOfType(MarkdownView);
+ if (target.checked) {
+ this.tickTickSync.closeTask(taskId);
+ } else {
+ this.tickTickSync.repoenTask(taskId);
+ }
+ } else {
+ //console.log('TickTick_id not found');
+ //Start full-text search and check status updates
+ try{
+ if (!await this.checkAndHandleSyncLock()) return;
+ await this.tickTickSync.fullTextModifiedTaskCheck()
+ this.syncLock = false;
+ }catch(error){
+ console.error(`An error occurred while check modified tasks in the file: ${error}`);
+ this.syncLock = false;
+ }
+
+ }
+ }
+
+ //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){
+ this.initializeModuleClass()
+ }
+ return true
+ }
+ else{
+ new Notice(`Please enter the correct TickTick API token"`)
+ return(false)
+ }
+
+
+ }
+
+ async setStatusBarText(){
+ if(!( this.checkModuleClass())){
+ return
+ }
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView)
+ if(!view){
+ this.statusBar.setText('');
+ }
+ else{
+ const filepath = this.app.workspace.getActiveViewOfType(MarkdownView)?.file.path
+ if(filepath === undefined){
+ console.log(`file path undefined`)
+ return
+ }
+ const defaultProjectName = await this.cacheOperation.getDefaultProjectNameForFilepath(filepath as string)
+ if(defaultProjectName === undefined){
+ console.log(`projectName undefined`)
+ return
+ }
+ this.statusBar.setText(defaultProjectName)
+ }
+
+ }
+
+ async scheduledSynchronization() {
+ if (!(this.checkModuleClass())) {
+ return;
+ }
+ console.log("TickTick scheduled synchronization task started at", new Date().toLocaleString());
+ try {
+ if (!await this.checkAndHandleSyncLock()) return;
+ try {
+ await this.tickTickSync.syncTickTickToObsidian();
+ } catch(error) {
+ console.error('An error occurred in syncTickTickToObsidian:', error);
+ }
+ this.syncLock = false;
+ try {
+ await this.saveSettings();
+ } catch(error) {
+ console.error('An error occurred in saveSettings:', error);
+ }
+
+ // Sleep for 5 seconds
+ await new Promise(resolve => setTimeout(resolve, 5000));
+
+ const filesToSync = this.settings.fileMetadata;
+ if(this.settings.debugMode){
+ console.log(filesToSync)
+ }
+
+ for (let fileKey in filesToSync) {
+ if(this.settings.debugMode){
+ console.log(fileKey)
+ }
+
+ if (!await this.checkAndHandleSyncLock()) return;
+ try {
+ await this.tickTickSync.fullTextNewTaskCheck(fileKey);
+ } catch(error) {
+ console.error('An error occurred in fullTextNewTaskCheck:', error);
+ }
+ this.syncLock = false;
+
+ if (!await this.checkAndHandleSyncLock()) return;
+ try {
+ await this.tickTickSync.deletedTaskCheck(fileKey);
+ } catch(error) {
+ console.error('An error occurred in deletedTaskCheck:', error);
+ }
+ this.syncLock = false;
+
+ if (!await this.checkAndHandleSyncLock()) return;
+ try {
+ await this.tickTickSync.fullTextModifiedTaskCheck(fileKey);
+ } catch(error) {
+ console.error('An error occurred in fullTextModifiedTaskCheck:', error);
+ }
+ this.syncLock = false;
+ }
+
+ } catch (error) {
+ console.error('An error occurred:', error);
+ new Notice('An error occurred:', error);
+ this.syncLock = false;
+ }
+ console.log("TickTick scheduled synchronization task completed at", new Date().toLocaleString());
+ }
+
+ async checkSyncLock() {
+ let checkCount = 0;
+ while (this.syncLock == true && checkCount < 10) {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ checkCount++;
+ }
+ if (this.syncLock == true) {
+ return false;
+ }
+ return true;
+ }
+
+ async checkAndHandleSyncLock() {
+ if (this.syncLock) {
+ console.log('sync locked.');
+ const isSyncLockChecked = await this.checkSyncLock();
+ if (!isSyncLockChecked) {
+ return false;
+ }
+ console.log('sync unlocked.')
+ }
+ this.syncLock = true;
+ return true;
+ }
+
}
diff --git a/manifest.json b/manifest.json
index 312b1d5..e0a8eff 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,11 +1,11 @@
{
- "id": "ultimate-todoist-sync",
- "name": "Ultimate Todoist Sync",
- "version": "1.0.43",
+ "id": "ultimate-TickTick-sync",
+ "name": "Ultimate TickTick Sync",
+ "version": "1.0.1",
"minAppVersion": "1.0.0",
- "description": "This is the best Todoist task synchronization plugin for Obsidian so far.",
- "author": "HeroBlackInk",
- "authorUrl": "https://github.com/HeroBlackInk/",
+ "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/package-lock.json b/package-lock.json
index 6026208..1b8b0d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,17 @@
{
- "name": "obsidian-sample-plugin",
+ "name": "ultimate-ticktick-sync",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "obsidian-sample-plugin",
+ "name": "ultimate-ticktick-sync",
"version": "1.0.0",
- "license": "MIT",
+ "license": "GNU GPLv3",
"dependencies": {
- "@doist/todoist-api-typescript": "^2.1.2"
+ "@doist/TickTick-api-typescript": "^2.1.2",
+ "esbuild-plugin-copy": "^2.0.1",
+ "ticktick-api-lvt": "file:../ticktick-api-lvt"
},
"devDependencies": {
"@types/node": "^16.11.6",
@@ -22,39 +24,76 @@
"typescript": "4.7.4"
}
},
+ "../ticktick-api-lvt": {
+ "version": "0.0.0-development",
+ "license": "MIT",
+ "dependencies": {
+ "bson-objectid": "^2.0.4",
+ "request": "^2.88.2"
+ },
+ "devDependencies": {
+ "@commitlint/cli": "^17.3.0",
+ "@commitlint/config-conventional": "^17.3.0",
+ "@semantic-release/changelog": "^6.0.2",
+ "@semantic-release/git": "^10.0.1",
+ "@types/dotenv": "^8.2.0",
+ "@types/node": "^18.11.9",
+ "@types/request": "^2.48.8",
+ "@typescript-eslint/eslint-plugin": "^5.45.0",
+ "@typescript-eslint/parser": "^5.45.0",
+ "dotenv": "^16.0.3",
+ "eslint": "^8.28.0",
+ "eslint-config-prettier": "^8.5.0",
+ "husky": "^8.0.2",
+ "prettier": "^2.8.0",
+ "semantic-release": "^19.0.5",
+ "ts-node-dev": "^2.0.0",
+ "typescript": "^4.9.3"
+ }
+ },
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/@babel/runtime": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
- "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+ "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.13.11"
+ "regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@codemirror/state": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz",
- "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz",
+ "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==",
"dev": true,
"peer": true
},
"node_modules/@codemirror/view": {
- "version": "6.9.3",
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.3.tgz",
- "integrity": "sha512-BJ5mvEIhFM+SrNwc5X8pLIvMM9ffjkviVbxpg84Xk2OE8ZyKaEbId8kX+nAYEEso7+qnbwsXe1bkAHsasebMow==",
+ "version": "6.19.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.19.0.tgz",
+ "integrity": "sha512-XqNIfW/3GaaF+T7Q1jBcRLCPm1NbrR2DBxrXacSt1FG+rNsdsNn3/azAfgpUoJ7yy4xgd8xTPa3AlL+y0lMizQ==",
"dev": true,
"peer": true,
"dependencies": {
"@codemirror/state": "^6.1.4",
- "style-mod": "^4.0.0",
+ "style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
- "node_modules/@doist/todoist-api-typescript": {
+ "node_modules/@doist/TickTick-api-typescript": {
"version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@doist/todoist-api-typescript/-/todoist-api-typescript-2.1.2.tgz",
+ "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",
@@ -72,7 +111,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -88,7 +126,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -104,7 +141,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -120,7 +156,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -136,7 +171,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -152,7 +186,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -168,7 +201,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -184,7 +216,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -200,7 +231,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -216,7 +246,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -232,7 +261,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -248,7 +276,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -264,7 +291,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -280,7 +306,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -296,7 +321,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -312,7 +336,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -328,7 +351,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -344,7 +366,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -360,7 +381,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"sunos"
@@ -376,7 +396,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -392,7 +411,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -408,7 +426,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -434,9 +451,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz",
- "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==",
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz",
+ "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==",
"dev": true,
"peer": true,
"engines": {
@@ -444,15 +461,15 @@
}
},
"node_modules/@eslint/eslintrc": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz",
- "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
+ "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
"dev": true,
"peer": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.5.0",
+ "espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -468,9 +485,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.36.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz",
- "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==",
+ "version": "8.49.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz",
+ "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==",
"dev": true,
"peer": true,
"engines": {
@@ -478,9 +495,9 @@
}
},
"node_modules/@humanwhocodes/config-array": {
- "version": "0.11.8",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
- "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "version": "0.11.11",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
+ "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
"dev": true,
"peer": true,
"dependencies": {
@@ -517,7 +534,6 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@@ -530,7 +546,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
"engines": {
"node": ">= 8"
}
@@ -539,7 +554,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@@ -549,36 +563,36 @@
}
},
"node_modules/@types/codemirror": {
- "version": "0.0.108",
- "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz",
- "integrity": "sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==",
+ "version": "5.60.8",
+ "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
+ "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
"dev": true,
"dependencies": {
"@types/tern": "*"
}
},
"node_modules/@types/estree": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
- "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
"dev": true
},
"node_modules/@types/json-schema": {
- "version": "7.0.11",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
- "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+ "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==",
"dev": true
},
"node_modules/@types/node": {
- "version": "16.18.21",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.21.tgz",
- "integrity": "sha512-TassPGd0AEZWA10qcNnXnSNwHlLfSth8XwUaWc3gTSDmBz/rKb613Qw5qRf6o2fdRBrLbsgeC9PMZshobkuUqg==",
+ "version": "16.18.50",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.50.tgz",
+ "integrity": "sha512-OiDU5xRgYTJ203v4cprTs0RwOCd5c5Zjv+K5P8KSqfiCsB1W3LcamTUMcnQarpq5kOYbhHfSOgIEJvdPyb5xyw==",
"dev": true
},
"node_modules/@types/tern": {
- "version": "0.23.4",
- "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
- "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.5.tgz",
+ "integrity": "sha512-POau56wDk3TQ0mQ0qG7XDzv96U5whSENZ9lC0htDvEH+9YUREo+J2U+apWcVRgR2UydEE70JXZo44goG+akTNQ==",
"dev": true,
"dependencies": {
"@types/estree": "*"
@@ -769,9 +783,9 @@
}
},
"node_modules/acorn": {
- "version": "8.8.2",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
- "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+ "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true,
"peer": true,
"bin": {
@@ -822,8 +836,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "peer": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -834,6 +846,18 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -845,7 +869,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -879,9 +902,9 @@
}
},
"node_modules/axios-retry": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.4.0.tgz",
- "integrity": "sha512-VdgaP+gHH4iQYCCNUWF2pcqeciVOdGrBBAYUfTY+wPcO5Ltvp/37MLFNCmJKo7Gj3SHvCSdL8ouI1qLYJN3liA==",
+ "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"
@@ -894,6 +917,14 @@
"dev": true,
"peer": true
},
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -909,7 +940,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
@@ -962,8 +992,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "peer": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -975,12 +1003,36 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "peer": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -991,9 +1043,7 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "peer": true
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -1064,7 +1114,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
"dependencies": {
"path-type": "^4.0.0"
},
@@ -1098,7 +1147,6 @@
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz",
"integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==",
- "dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -1131,6 +1179,20 @@
"@esbuild/win32-x64": "0.17.3"
}
},
+ "node_modules/esbuild-plugin-copy": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/esbuild-plugin-copy/-/esbuild-plugin-copy-2.1.1.tgz",
+ "integrity": "sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw==",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.3",
+ "fs-extra": "^10.0.1",
+ "globby": "^11.0.3"
+ },
+ "peerDependencies": {
+ "esbuild": ">= 0.14.0"
+ }
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -1145,28 +1207,28 @@
}
},
"node_modules/eslint": {
- "version": "8.36.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz",
- "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==",
+ "version": "8.49.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz",
+ "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.4.0",
- "@eslint/eslintrc": "^2.0.1",
- "@eslint/js": "8.36.0",
- "@humanwhocodes/config-array": "^0.11.8",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.2",
+ "@eslint/js": "8.49.0",
+ "@humanwhocodes/config-array": "^0.11.11",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
- "ajv": "^6.10.0",
+ "ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.1.1",
- "eslint-visitor-keys": "^3.3.0",
- "espree": "^9.5.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -1174,22 +1236,19 @@
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"globals": "^13.19.0",
- "grapheme-splitter": "^1.0.4",
+ "graphemer": "^1.4.0",
"ignore": "^5.2.0",
- "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
- "js-sdsl": "^4.1.4",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
+ "optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
"bin": {
@@ -1243,18 +1302,21 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
- "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/eslint-scope": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
- "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
"peer": true,
"dependencies": {
@@ -1263,6 +1325,9 @@
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/estraverse": {
@@ -1275,16 +1340,29 @@
"node": ">=4.0"
}
},
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/espree": {
- "version": "9.5.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz",
- "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==",
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"peer": true,
"dependencies": {
- "acorn": "^8.8.0",
+ "acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.3.0"
+ "eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1364,10 +1442,9 @@
"peer": true
},
"node_modules/fast-glob": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
- "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
- "dev": true,
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+ "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@@ -1379,18 +1456,6 @@
"node": ">=8.6.0"
}
},
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -1409,7 +1474,6 @@
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
- "dev": true,
"dependencies": {
"reusify": "^1.0.4"
}
@@ -1431,7 +1495,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1457,17 +1520,18 @@
}
},
"node_modules/flat-cache": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
- "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz",
+ "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==",
"dev": true,
"peer": true,
"dependencies": {
- "flatted": "^3.1.0",
+ "flatted": "^3.2.7",
+ "keyv": "^4.5.3",
"rimraf": "^3.0.2"
},
"engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "node": ">=12.0.0"
}
},
"node_modules/flatted": {
@@ -1509,6 +1573,19 @@
"node": ">= 6"
}
},
+ "node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1516,6 +1593,19 @@
"dev": true,
"peer": true
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/functional-red-black-tree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
@@ -1544,22 +1634,20 @@
}
},
"node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "peer": true,
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": {
- "is-glob": "^4.0.3"
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": ">=10.13.0"
+ "node": ">= 6"
}
},
"node_modules/globals": {
- "version": "13.20.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
- "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "version": "13.21.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
+ "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
"dev": true,
"peer": true,
"dependencies": {
@@ -1576,7 +1664,6 @@
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
- "dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
@@ -1592,10 +1679,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/grapheme-splitter": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
- "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true,
"peer": true
},
@@ -1603,8 +1695,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "peer": true,
"engines": {
"node": ">=8"
}
@@ -1622,7 +1712,6 @@
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
- "dev": true,
"engines": {
"node": ">= 4"
}
@@ -1672,11 +1761,21 @@
"dev": true,
"peer": true
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1685,7 +1784,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -1697,7 +1795,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -1730,17 +1827,6 @@
"dev": true,
"peer": true
},
- "node_modules/js-sdsl": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
- "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==",
- "dev": true,
- "peer": true,
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/js-sdsl"
- }
- },
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -1754,6 +1840,13 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "peer": true
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -1768,6 +1861,27 @@
"dev": true,
"peer": true
},
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz",
+ "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -1829,7 +1943,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
"engines": {
"node": ">= 8"
}
@@ -1838,7 +1951,6 @@
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
- "dev": true,
"dependencies": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
@@ -1910,13 +2022,21 @@
"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",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/obsidian": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.1.1.tgz",
- "integrity": "sha512-GcxhsHNkPEkwHEjeyitfYNBcQuYGeAHFs1pEpZIv0CnzSfui8p8bPLm2YKLgcg20B764770B1sYGtxCvk9ptxg==",
+ "version": "1.4.11",
+ "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.11.tgz",
+ "integrity": "sha512-BCVYTvaXxElJMl6MMbDdY/CGK+aq18SdtDY/7vH8v6BxCBQ6KF4kKxL0vG9UZ0o5qh139KpUoJHNm+6O5dllKA==",
"dev": true,
"dependencies": {
- "@types/codemirror": "0.0.108",
+ "@types/codemirror": "5.60.8",
"moment": "2.29.4"
},
"peerDependencies": {
@@ -1935,18 +2055,18 @@
}
},
"node_modules/optionator": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
- "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
"dev": true,
"peer": true,
"dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.3"
+ "type-check": "^0.4.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -2040,7 +2160,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -2049,7 +2168,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -2081,7 +2199,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -2097,10 +2214,21 @@
}
]
},
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/regenerator-runtime": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
- "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ "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",
@@ -2128,7 +2256,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true,
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
@@ -2154,7 +2281,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -2174,14 +2300,14 @@
}
},
"node_modules/runtypes": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/runtypes/-/runtypes-6.6.0.tgz",
- "integrity": "sha512-ddM7sgB3fyboDlBzEYFQ04L674sKjbs4GyW2W32N/5Ae47NRd/GyMASPC2PFw8drPHYGEcZ0mZ26r5RcB8msfQ=="
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/runtypes/-/runtypes-6.7.0.tgz",
+ "integrity": "sha512-3TLdfFX8YHNFOhwHrSJza6uxVBmBrEjnNQlNXvXCdItS0Pdskfg5vVXUTWIN+Y23QR09jWpSl99UHkA83m4uWA=="
},
"node_modules/semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -2220,7 +2346,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -2261,9 +2386,9 @@
}
},
"node_modules/style-mod": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.2.tgz",
- "integrity": "sha512-C4myMmRTO8iaC5Gg+N1ftK2WT4eXUTMAa+HEFPPrfVeO/NtqLTtAmV1HbqnuGtLwCek44Ra76fdGUkSqjiMPcQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
+ "integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==",
"dev": true,
"peer": true
},
@@ -2271,8 +2396,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -2287,11 +2410,14 @@
"dev": true,
"peer": true
},
+ "node_modules/ticktick-api-lvt": {
+ "resolved": "../ticktick-api-lvt",
+ "link": true
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -2372,6 +2498,14 @@
"node": ">=4.2.0"
}
},
+ "node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "engines": {
+ "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",
@@ -2391,17 +2525,21 @@
}
},
"node_modules/uuid": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
- "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "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.6",
- "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
- "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==",
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"dev": true,
"peer": true
},
@@ -2421,16 +2559,6 @@
"node": ">= 8"
}
},
- "node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/package.json b/package.json
index 6cea7dd..18dbf13 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
{
- "name": "ultimate-todoist-sync",
+ "name": "ultimate-ticktick-sync",
"version": "1.0.0",
- "description": "This is a sample plugin for Obsidian (https://obsidian.md)",
+ "description": "ticktick sync attempt",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
- "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
- "build-without-tsc": "node esbuild.config.mjs production",
+ "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
+ "build-without-tsc": "node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
@@ -23,6 +23,8 @@
"typescript": "4.7.4"
},
"dependencies": {
- "@doist/todoist-api-typescript": "^2.1.2"
+ "@doist/TickTick-api-typescript": "^2.1.2",
+ "esbuild-plugin-copy": "^2.0.1",
+ "ticktick-api-lvt": "file:../ticktick-api-lvt"
}
}
diff --git a/src/TicktickRestAPI.ts b/src/TicktickRestAPI.ts
new file mode 100644
index 0000000..ef4d0b2
--- /dev/null
+++ b/src/TicktickRestAPI.ts
@@ -0,0 +1,224 @@
+import { Tick } from 'ticktick-api-lvt'
+import { App} from 'obsidian';
+import UltimateTickTickSyncForObsidian from "../main";
+//convert date from obsidian event
+// Usage example
+//const str = "2023-03-27";
+//const utcStr = localDateStringToUTCDatetimeString(str);
+//console.log(dateStr); // Output 2023-03-27T00:00:00.000Z
+function localDateStringToUTCDatetimeString(localDateString:string) {
+ try {
+ if(localDateString === null){
+ return null
+ }
+ localDateString = localDateString + "T08:00";
+ let localDateObj = new Date(localDateString);
+ let ISOString = localDateObj.toISOString()
+ return(ISOString);
+ } catch (error) {
+ console.error(`Error extracting date from string '${localDateString}': ${error}`);
+ return null;
+ }
+}
+
+export class TickTickRestAPI {
+ [x: string]: any;
+ app:App;
+ plugin: UltimateTickTickSyncForObsidian;
+ api: TickTickRestAPI;
+
+ 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)
+ }
+
+ 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()
+ try {
+ if(dueDate){
+ dueDatetime = localDateStringToUTCDatetimeString(dueDatetime)
+ dueDate = null
+ }
+ const newTask = await api.addTask({
+ projectId,
+ content,
+ parentId,
+ dueDate,
+ labels,
+ description,
+ priority
+ }, false);
+ return newTask;
+ } catch (error) {
+ throw new Error(`Error adding task: ${error.message}`);
+ }
+ }
+
+
+ //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()
+ try {
+ const result = await api.getTasks(options);
+ return result;
+ } catch (error) {
+ throw new Error(`Error get active tasks: ${error.message}`);
+ }
+ }
+
+
+ //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');
+ }
+ try {
+ if(updates.dueDate){
+ console.log(updates.dueDate)
+ updates.dueDatetime = localDateStringToUTCDatetimeString(updates.dueDate)
+ updates.dueDate = null
+ console.log(updates.dueDatetime)
+ }
+ const updatedTask = await api.updateTask(taskId, updates);
+ return updatedTask;
+ } catch (error) {
+ throw new Error(`Error updating task: ${error.message}`);
+ }
+ }
+
+
+
+
+ //open a task
+ async OpenTask(taskId:string) {
+ const api = await this.initializeAPI()
+ try {
+
+ const isSuccess = await api.reopenTask(taskId);
+ console.log(`Task ${taskId} is reopened`)
+ return(isSuccess)
+
+ } catch (error) {
+ console.error('Error open a task:', error);
+ return
+ }
+ }
+
+ // Close a task in TickTick API
+ async CloseTask(taskId: string): Promise {
+ const api = await this.initializeAPI()
+ try {
+ const isSuccess = await api.closeTask(taskId);
+ console.log(`Task ${taskId} is closed`)
+ return isSuccess;
+ } catch (error) {
+ console.error('Error closing task:', error);
+ throw error; // Throw an error so that the caller can catch and handle it
+ }
+ }
+
+
+
+
+ // get a task by Id
+ async getTaskById(taskId: string) {
+ const api = await this.initializeAPI()
+ if (!taskId) {
+ throw new Error('taskId is required');
+ }
+ try {
+ const task = await api.getTask(taskId);
+ return task;
+ } catch (error) {
+ if (error.response && error.response.status) {
+ const statusCode = error.response.status;
+ throw new Error(`Error retrieving task. Status code: ${statusCode}`);
+ } else {
+ throw new Error(`Error retrieving task: ${error.message}`);
+ }
+ }
+ }
+
+ //get a task due by id
+ async getTaskDueById(taskId: string) {
+ const api = await this.initializeAPI()
+ if (!taskId) {
+ throw new Error('taskId is required');
+ }
+ try {
+ const task = await api.getTask(taskId);
+ const due = task.due ?? null
+ return due;
+ } catch (error) {
+ throw new Error(`Error updating task: ${error.message}`);
+ }
+ }
+
+
+ //get all projects
+ async GetAllProjects() {
+ console.log("Get ALL PRoects")
+ const api = await this.initializeAPI()
+ try {
+ const result = await api.getProjects();
+ return(result)
+
+ } catch (error) {
+ console.error('Error get all projects', error);
+ return false
+ }
+ }
+
+ //get project groups
+ async GetProjectGroups() {
+ console.log("Get project gruops")
+ const api = await this.initializeAPI()
+ try {
+ const result = await api.getProjectGroups()
+
+ return(result)
+
+ } catch (error) {
+ console.error('Error get all projects', error);
+ return false
+ }
+ }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TicktickSyncAPI.ts b/src/TicktickSyncAPI.ts
new file mode 100644
index 0000000..0ef06d8
--- /dev/null
+++ b/src/TicktickSyncAPI.ts
@@ -0,0 +1,380 @@
+import { App} from 'obsidian';
+import UltimateTickTickSyncForObsidian from "../main";
+
+
+type Event = {
+ id: string;
+ object_type: string;
+ object_id: string;
+ event_type: string;
+ event_date: string;
+ parent_project_id: string;
+ parent_item_id: string | null;
+ initiator_id: string | null;
+ extra_data: Record;
+};
+
+type FilterOptions = {
+ event_type?: string;
+ object_type?: string;
+};
+
+export class TickTickSyncAPI {
+ app:App;
+ plugin: UltimateTickTickSyncForObsidian;
+
+ constructor(app:App, plugin:UltimateTickTickSyncForObsidian) {
+ //super(app,settings);
+ this.app = app;
+ this.plugin = plugin;
+ }
+
+ //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();
+
+ return data;
+ } catch (error) {
+ console.error(error);
+ throw new Error('Failed to fetch all resources due to network error');
+ }
+ }
+
+ //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)
+ return data;
+ } catch (error) {
+ console.error(error);
+ throw new Error('Failed to fetch user resources due to network error');
+ }
+ }
+
+
+
+ //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);
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch all resources: ${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 user resources 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}`);
+ }
+
+ 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)
+
+ }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)
+
+ );
+ };
+
+ //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);
+
+ 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');
+ }
+ }
+
+
+
+ //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');
+ }
+ }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/cacheOperation.ts b/src/cacheOperation.ts
index cdc51f5..7555d9a 100644
--- a/src/cacheOperation.ts
+++ b/src/cacheOperation.ts
@@ -1,473 +1,506 @@
import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
+import UltimateTickTickSyncForObsidian from "../main";
interface Due {
-date?: string;
-[key: string]: any; // allow for additional properties
+ date?: string;
+ [key: string]: any; // allow for additional properties
}
export class CacheOperation {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin: UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-}
-
-
-
-
-
-async getFileMetadata(filepath:string) {
-return this.plugin.settings.fileMetadata[filepath] ?? null
-}
-
-async getFileMetadatas(){
-return this.plugin.settings.fileMetadata ?? null
-}
-
-async newEmptyFileMetadata(filepath:string){
-const metadatas = this.plugin.settings.fileMetadata
-if(metadatas[filepath]) {
-return
-}
-else{
-metadatas[filepath] = {}
-}
-metadatas[filepath].todoistTasks = [];
-metadatas[filepath].todoistCount = 0;
-// Save the updated metadatas object back to the settings object
-this.plugin.settings.fileMetadata = metadatas
-
-}
-
-async updateFileMetadata(filepath:string,newMetadata) {
-const metadatas = this.plugin.settings.fileMetadata
-
-// If the metadata object does not exist, create a new object and add it to metadatas
-if (!metadatas[filepath]) {
-metadatas[filepath] = {}
-}
-
-//Update attribute values in the metadata object
-metadatas[filepath].todoistTasks = newMetadata.todoistTasks;
-metadatas[filepath].todoistCount = newMetadata.todoistCount;
-
-// Save the updated metadatas object back to the settings object
-this.plugin.settings.fileMetadata = metadatas
-
-}
-
-async deleteTaskIdFromMetadata(filepath:string,taskId:string){
-console.log(filepath)
-const metadata = await this.getFileMetadata(filepath)
-console.log(metadata)
-const newTodoistTasks = metadata.todoistTasks.filter(function(element){
-return element !== taskId
-})
-const newTodoistCount = metadata.todoistCount - 1
-let newMetadata = {}
-newMetadata.todoistTasks = newTodoistTasks
-newMetadata.todoistCount = newTodoistCount
-console.log(`new metadata ${newMetadata}`)
-
-
-}
-
-//delete filepath from filemetadata
-async deleteFilepathFromMetadata(filepath:string){
-Reflect.deleteProperty(this.plugin.settings.fileMetadata, filepath);
-this.plugin.saveSettings()
-console.log(`${filepath} is deleted from file metadatas.`)
-}
-
-
-//Check errors in filemata where the filepath is incorrect.
-async checkFileMetadata(){
-const metadatas = await this.getFileMetadatas()
-for (const key in metadatas) {
-let filepath = key
-const value = metadatas[key];
-let file = this.app.vault.getAbstractFileByPath(key)
-if(!file && (value.todoistTasks?.length === 0 || !value.todoistTasks)){
-console.log(`${key} is not existed and metadata is empty.`)
-await this.deleteFilepathFromMetadata(key)
-continue
-}
-if(value.todoistTasks?.length === 0 || !value.todoistTasks){
-//todo
-//delelte empty metadata
-continue
-}
-//check if file exists
-
-if(!file){
-//search new filepath
-console.log(`file ${filepath} is not exist`)
-const todoistId1 = value.todoistTasks[0]
-console.log(todoistId1)
-const searchResult = await this.plugin.fileOperation.searchFilepathsByTaskidInVault(todoistId1)
-console.log(`new file path is`)
-console.log(searchResult)
-
-//update metadata
-await this.updateRenamedFilePath(filepath,searchResult)
-this.plugin.saveSettings()
-
-}
-
-
-//const fileContent = await this.app.vault.read(file)
-//check if file include all tasks
-
-
-/*
-value.todoistTasks.forEach(async(taskId) => {
-const taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-
-
-});
-*/
-}
-
-}
-
-getDefaultProjectNameForFilepath(filepath:string){
-const metadatas = this.plugin.settings.fileMetadata
-if (!metadatas[filepath] || metadatas[filepath].defaultProjectId === undefined) {
-return this.plugin.settings.defaultProjectName
-}
-else{
-const defaultProjectId = metadatas[filepath].defaultProjectId
-const defaultProjectName = this.getProjectNameByIdFromCache(defaultProjectId)
-return defaultProjectName
-}
-}
-
-
-getDefaultProjectIdForFilepath(filepath:string){
-const metadatas = this.plugin.settings.fileMetadata
-if (!metadatas[filepath] || metadatas[filepath].defaultProjectId === undefined) {
-return this.plugin.settings.defaultProjectId
-}
-else{
-const defaultProjectId = metadatas[filepath].defaultProjectId
-return defaultProjectId
-}
-}
-
-setDefaultProjectIdForFilepath(filepath:string,defaultProjectId:string){
-const metadatas = this.plugin.settings.fileMetadata
-if (!metadatas[filepath]) {
-metadatas[filepath] = {}
-}
-metadatas[filepath].defaultProjectId = defaultProjectId
-
-// Save the updated metadatas object back to the settings object
-this.plugin.settings.fileMetadata = metadatas
-
-}
-
-
-//Read all tasks from Cache
-loadTasksFromCache() {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-return savedTasks;
-} catch (error) {
-console.error(`Error loading tasks from Cache: ${error}`);
-return [];
-}
-}
-
-
-// Overwrite and save all tasks to cache
-saveTasksToCache(newTasks) {
-try {
-this.plugin.settings.todoistTasksData.tasks = newTasks
-
-} catch (error) {
-console.error(`Error saving tasks to Cache: ${error}`);
-return false;
-}
-}
-
-
-
-
-//append event to Cache
-appendEventToCache(event:Object[]) {
-try {
-this.plugin.settings.todoistTasksData.events.push(event)
-} catch (error) {
-console.error(`Error append event to Cache: ${error}`);
-}
-}
-
-//append events to Cache
-appendEventsToCache(events:Object[]) {
-try {
-this.plugin.settings.todoistTasksData.events.push(...events)
-} catch (error) {
-console.error(`Error append events to Cache: ${error}`);
-}
-}
-
-
-//Read all events from the Cache file
-loadEventsFromCache() {
-try {
-
-const savedEvents = this.plugin.settings.todoistTasksData.events
-return savedEvents;
-} catch (error) {
-console.error(`Error loading events from Cache: ${error}`);
-}
-}
-
-
-
-//Append to Cache file
-appendTaskToCache(task) {
-try {
-if(task === null){
-return
-}
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-//const taskAlreadyExists = savedTasks.some((t) => t.id === task.id);
-//if (!taskAlreadyExists) {
-//, when using the push method to insert a string into a Cache object, it will be treated as a simple key-value pair, where the key is the numeric index of the array and the value is the string itself. But if you use the push method to insert another Cache object (or array) into the Cache object, the object will become a nested sub-object of the original Cache object. In this case, the key is the numeric index and the value is the nested Cache object itself.
-//}
-this.plugin.settings.todoistTasksData.tasks.push(task);
-} catch (error) {
-console.error(`Error appending task to Cache: ${error}`);
-}
-}
-
-
-
-
-//Read the task with the specified id
-loadTaskFromCacheyID(taskId) {
-try {
-
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-//console.log(savedTasks)
-const savedTask = savedTasks.find((t) => t.id === taskId);
-//console.log(savedTask)
-return(savedTask)
-} catch (error) {
-console.error(`Error finding task from Cache: ${error}`);
-return [];
-}
-}
-
-//Overwrite the task with the specified id in update
-updateTaskToCacheByID(task) {
-try {
-
-
-//Delete the existing task
-this.deleteTaskFromCache(task.id)
-//Add new task
-this.appendTaskToCache(task)
-
-} catch (error) {
-console.error(`Error updating task to Cache: ${error}`);
-return [];
-}
-}
-
-//The structure of due {date: "2025-02-25",isRecurring: false,lang: "en",string: "2025-02-25"}
-
-
-
-modifyTaskToCacheByID(taskId: string, { content, due }: { content?: string, due?: Due }): void {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks;
-const taskIndex = savedTasks.findIndex((task) => task.id === taskId);
-
-if (taskIndex !== -1) {
-const updatedTask = { ...savedTasks[taskIndex] };
-
-if (content !== undefined) {
-updatedTask.content = content;
-}
-
-if (due !== undefined) {
-if (due === null) {
-updatedTask.due = null;
-} else {
-updatedTask.due = due;
-}
-}
-
-savedTasks[taskIndex] = updatedTask;
-
-this.plugin.settings.todoistTasksData.tasks = savedTasks;
-} else {
-throw new Error(`Task with ID ${taskId} not found in cache.`);
-}
-} catch (error) {
-// Handle the error appropriately, eg by logging it or re-throwing it.
-}
-}
-
-
-//open a task status
-reopenTaskToCacheByID(taskId:string) {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-
-
-// Loop through the array to find the item with the specified ID
-for (let i = 0; i < savedTasks.length; i++) {
-if (savedTasks[i].id === taskId) {
-//Modify the properties of the object
-savedTasks[i].isCompleted = false;
-break; // Found and modified the item, break out of the loop
-}
-}
-this.plugin.settings.todoistTasksData.tasks = savedTasks
-
-} catch (error) {
-console.error(`Error open task to Cache file: ${error}`);
-return [];
-}
-}
-
-
-
-//close a task status
-closeTaskToCacheByID(taskId:string):Promise {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-
-// Loop through the array to find the item with the specified ID
-for (let i = 0; i < savedTasks.length; i++) {
-if (savedTasks[i].id === taskId) {
-//Modify the properties of the object
-savedTasks[i].isCompleted = true;
-break; // Found and modified the item, break out of the loop
-}
-}
-this.plugin.settings.todoistTasksData.tasks = savedTasks
-
-} catch (error) {
-console.error(`Error close task to Cache file: ${error}`);
-throw error; // Throw an error so that the caller can catch and handle it
-}
-}
-
-
-//Delete task by ID
-deleteTaskFromCache(taskId) {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-const newSavedTasks = savedTasks.filter((t) => t.id !== taskId);
-this.plugin.settings.todoistTasksData.tasks = newSavedTasks
-} catch (error) {
-console.error(`Error deleting task from Cache file: ${error}`);
-}
-}
-
-
-
-
-
-//Delete task through ID array
-deleteTaskFromCacheByIDs(deletedTaskIds) {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-const newSavedTasks = savedTasks.filter((t) => !deletedTaskIds.includes(t.id))
-this.plugin.settings.todoistTasksData.tasks = newSavedTasks
-} catch (error) {
-console.error(`Error deleting task from Cache : ${error}`);
-}
-}
-
-
-//Find project id by name
-getProjectIdByNameFromCache(projectName:string) {
-try {
-const savedProjects = this.plugin.settings.todoistTasksData.projects
-const targetProject = savedProjects.find(obj => obj.name === projectName);
-const projectId = targetProject ? targetProject.id : null;
-return(projectId)
-} catch (error) {
-console.error(`Error finding project from Cache file: ${error}`);
-return(false)
-}
-}
-
-
-
-getProjectNameByIdFromCache(projectId:string) {
-try {
-const savedProjects = this.plugin.settings.todoistTasksData.projects
-const targetProject = savedProjects.find(obj => obj.id === projectId);
-const projectName = targetProject ? targetProject.name : null;
-return(projectName)
-} catch (error) {
-console.error(`Error finding project from Cache file: ${error}`);
-return(false)
-}
-}
-
-
-
-//save projects data to json file
-async saveProjectsToCache() {
-try{
-//get projects
-const projects = await this.plugin.todoistRestAPI.GetAllProjects()
-if(!projects){
-return false
-}
-
-//save to json
-this.plugin.settings.todoistTasksData.projects = projects
-
-return true
-
-}catch(error){
-return false
-console.log(`error downloading projects: ${error}`)
-
-}
-
-}
-
-
-async updateRenamedFilePath(oldpath:string,newpath:string){
-try{
-console.log(`oldpath is ${oldpath}`)
-console.log(`newpath is ${newpath}`)
-const savedTask = await this.loadTasksFromCache()
-//console.log(savedTask)
-const newTasks = savedTask.map(obj => {
-if (obj.path === oldpath) {
-return { ...obj, path: newpath };
-}else {
-return obj;
-}
-})
-//console.log(newTasks)
-await this.saveTasksToCache(newTasks)
-
-//update filepath
-const fileMetadatas = this.plugin.settings.fileMetadata
-fileMetadatas[newpath] = fileMetadatas[oldpath]
-delete fileMetadatas[oldpath]
-this.plugin.settings.fileMetadata = fileMetadatas
-
-}catch(error){
-console.log(`Error updating renamed file path to cache: ${error}`)
-}
-
-
-}
-
+ app:App;
+ plugin: UltimateTickTickSyncForObsidian;
+
+ constructor(app:App, plugin: UltimateTickTickSyncForObsidian) {
+ //super(app,settings);
+ this.app = app;
+ this.plugin = plugin;
+ }
+
+
+
+
+
+ async getFileMetadata(filepath:string) {
+ return this.plugin.settings.fileMetadata[filepath] ?? null
+ }
+
+ async getFileMetadatas(){
+ return this.plugin.settings.fileMetadata ?? null
+ }
+
+ async newEmptyFileMetadata(filepath:string){
+ const metadatas = this.plugin.settings.fileMetadata
+ if(metadatas[filepath]) {
+ return
+ }
+ else{
+ metadatas[filepath] = {}
+ }
+ metadatas[filepath].TickTickTasks = [];
+ metadatas[filepath].TickTickCount = 0;
+ // Save the updated metadatas object back to the settings object
+ this.plugin.settings.fileMetadata = metadatas
+
+ }
+
+ async updateFileMetadata(filepath:string,newMetadata) {
+ const metadatas = this.plugin.settings.fileMetadata
+
+ // If the metadata object does not exist, create a new object and add it to metadatas
+ if (!metadatas[filepath]) {
+ metadatas[filepath] = {}
+ }
+
+ //Update attribute values in the metadata object
+ metadatas[filepath].TickTickTasks = newMetadata.TickTickTasks;
+ metadatas[filepath].TickTickCount = newMetadata.TickTickCount;
+
+ // Save the updated metadatas object back to the settings object
+ this.plugin.settings.fileMetadata = metadatas
+
+ }
+
+ async deleteTaskIdFromMetadata(filepath:string,taskId:string){
+ console.log(filepath)
+ const metadata = await this.getFileMetadata(filepath)
+ console.log(metadata)
+ const newTickTickTasks = metadata.TickTickTasks.filter(function(element){
+ return element !== taskId
+ })
+ const newTickTickCount = metadata.TickTickCount - 1
+ let newMetadata = {}
+ newMetadata.TickTickTasks = newTickTickTasks
+ newMetadata.TickTickCount = newTickTickCount
+ console.log(`new metadata ${newMetadata}`)
+
+
+ }
+
+ //delete filepath from filemetadata
+ async deleteFilepathFromMetadata(filepath:string){
+ Reflect.deleteProperty(this.plugin.settings.fileMetadata, filepath);
+ this.plugin.saveSettings()
+ console.log(`${filepath} is deleted from file metadatas.`)
+ }
+
+
+ //Check errors in filemata where the filepath is incorrect.
+ async checkFileMetadata(){
+ const metadatas = await this.getFileMetadatas()
+ for (const key in metadatas) {
+ let filepath = key
+ 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.`)
+ await this.deleteFilepathFromMetadata(key)
+ continue
+ }
+ if(value.TickTickTasks?.length === 0 || !value.TickTickTasks){
+ //todo
+ //delelte empty metadata
+ continue
+ }
+ //check if file exists
+
+ if(!file){
+ //search new filepath
+ console.log(`file ${filepath} is not exist`)
+ const TickTickId1 = value.TickTickTasks[0]
+ console.log(TickTickId1)
+ const searchResult = await this.plugin.fileOperation.searchFilepathsByTaskidInVault(TickTickId1)
+ console.log(`new file path is`)
+ console.log(searchResult)
+
+ //update metadata
+ await this.updateRenamedFilePath(filepath,searchResult)
+ this.plugin.saveSettings()
+
+ }
+
+
+ //const fileContent = await this.app.vault.read(file)
+ //check if file include all tasks
+
+
+ /*
+ value.TickTickTasks.forEach(async(taskId) => {
+ const taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
+
+
+ });
+ */
+ }
+
+ }
+
+ getDefaultProjectNameForFilepath(filepath:string){
+ const metadatas = this.plugin.settings.fileMetadata
+ if (!metadatas[filepath] || metadatas[filepath].defaultProjectId === undefined) {
+ return this.plugin.settings.defaultProjectName
+ }
+ else{
+ const defaultProjectId = metadatas[filepath].defaultProjectId
+ const defaultProjectName = this.getProjectNameByIdFromCache(defaultProjectId)
+ return defaultProjectName
+ }
+ }
+
+
+ getDefaultProjectIdForFilepath(filepath:string){
+ const metadatas = this.plugin.settings.fileMetadata
+ if (!metadatas[filepath] || metadatas[filepath].defaultProjectId === undefined) {
+ return this.plugin.settings.defaultProjectId
+ }
+ else{
+ const defaultProjectId = metadatas[filepath].defaultProjectId
+ return defaultProjectId
+ }
+ }
+
+ setDefaultProjectIdForFilepath(filepath:string,defaultProjectId:string){
+ const metadatas = this.plugin.settings.fileMetadata
+ if (!metadatas[filepath]) {
+ metadatas[filepath] = {}
+ }
+ metadatas[filepath].defaultProjectId = defaultProjectId
+
+ // Save the updated metadatas object back to the settings object
+ this.plugin.settings.fileMetadata = metadatas
+
+ }
+
+
+ //Read all tasks from Cache
+ loadTasksFromCache() {
+ try {
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks
+ return savedTasks;
+ } catch (error) {
+ console.error(`Error loading tasks from Cache: ${error}`);
+ return [];
+ }
+ }
+
+
+ // Overwrite and save all tasks to cache
+ saveTasksToCache(newTasks) {
+ try {
+ this.plugin.settings.TickTickTasksData.tasks = newTasks
+
+ } catch (error) {
+ console.error(`Error saving tasks to Cache: ${error}`);
+ return false;
+ }
+ }
+
+
+
+
+ //append event to Cache
+ appendEventToCache(event:Object[]) {
+ try {
+ this.plugin.settings.TickTickTasksData.events.push(event)
+ } catch (error) {
+ console.error(`Error append event to Cache: ${error}`);
+ }
+ }
+
+ //append events to Cache
+ appendEventsToCache(events:Object[]) {
+ try {
+ this.plugin.settings.TickTickTasksData.events.push(...events)
+ } catch (error) {
+ console.error(`Error append events to Cache: ${error}`);
+ }
+ }
+
+
+ //Read all events from the Cache file
+ loadEventsFromCache() {
+ try {
+
+ const savedEvents = this.plugin.settings.TickTickTasksData.events
+ return savedEvents;
+ } catch (error) {
+ console.error(`Error loading events from Cache: ${error}`);
+ }
+ }
+
+
+
+ //Append to Cache file
+ appendTaskToCache(task) {
+ try {
+ if(task === null){
+ return
+ }
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks
+ //const taskAlreadyExists = savedTasks.some((t) => t.id === task.id);
+ //if (!taskAlreadyExists) {
+ //, when using the push method to insert a string into a Cache object, it will be treated as a simple key-value pair, where the key is the numeric index of the array and the value is the string itself. But if you use the push method to insert another Cache object (or array) into the Cache object, the object will become a nested sub-object of the original Cache object. In this case, the key is the numeric index and the value is the nested Cache object itself.
+ //}
+ this.plugin.settings.TickTickTasksData.tasks.push(task);
+ } catch (error) {
+ console.error(`Error appending task to Cache: ${error}`);
+ }
+ }
+
+
+
+
+ //Read the task with the specified id
+ loadTaskFromCacheyID(taskId) {
+ try {
+
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks
+ //console.log(savedTasks)
+ const savedTask = savedTasks.find((t) => t.id === taskId);
+ //console.log(savedTask)
+ return(savedTask)
+ } catch (error) {
+ console.error(`Error finding task from Cache: ${error}`);
+ return [];
+ }
+ }
+
+ //Overwrite the task with the specified id in update
+ updateTaskToCacheByID(task) {
+ try {
+
+
+ //Delete the existing task
+ this.deleteTaskFromCache(task.id)
+ //Add new task
+ this.appendTaskToCache(task)
+
+ } catch (error) {
+ console.error(`Error updating task to Cache: ${error}`);
+ return [];
+ }
+ }
+
+ //The structure of due {date: "2025-02-25",isRecurring: false,lang: "en",string: "2025-02-25"}
+
+
+
+ modifyTaskToCacheByID(taskId: string, { content, due }: { content?: string, due?: Due }): void {
+ try {
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks;
+ const taskIndex = savedTasks.findIndex((task) => task.id === taskId);
+
+ if (taskIndex !== -1) {
+ const updatedTask = { ...savedTasks[taskIndex] };
+
+ if (content !== undefined) {
+ updatedTask.content = content;
+ }
+
+ if (due !== undefined) {
+ if (due === null) {
+ updatedTask.due = null;
+ } else {
+ updatedTask.due = due;
+ }
+ }
+
+ savedTasks[taskIndex] = updatedTask;
+
+ this.plugin.settings.TickTickTasksData.tasks = savedTasks;
+ } else {
+ throw new Error(`Task with ID ${taskId} not found in cache.`);
+ }
+ } catch (error) {
+ // Handle the error appropriately, eg by logging it or re-throwing it.
+ }
+ }
+
+
+ //open a task status
+ reopenTaskToCacheByID(taskId:string) {
+ try {
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks
+
+
+ // Loop through the array to find the item with the specified ID
+ for (let i = 0; i < savedTasks.length; i++) {
+ if (savedTasks[i].id === taskId) {
+ //Modify the properties of the object
+ savedTasks[i].isCompleted = false;
+ break; // Found and modified the item, break out of the loop
+ }
+ }
+ this.plugin.settings.TickTickTasksData.tasks = savedTasks
+
+ } catch (error) {
+ console.error(`Error open task to Cache file: ${error}`);
+ return [];
+ }
+ }
+
+
+
+ //close a task status
+ closeTaskToCacheByID(taskId:string):Promise {
+ try {
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks
+
+ // Loop through the array to find the item with the specified ID
+ for (let i = 0; i < savedTasks.length; i++) {
+ if (savedTasks[i].id === taskId) {
+ //Modify the properties of the object
+ savedTasks[i].isCompleted = true;
+ break; // Found and modified the item, break out of the loop
+ }
+ }
+ this.plugin.settings.TickTickTasksData.tasks = savedTasks
+
+ } catch (error) {
+ console.error(`Error close task to Cache file: ${error}`);
+ throw error; // Throw an error so that the caller can catch and handle it
+ }
+ }
+
+
+ //Delete task by ID
+ deleteTaskFromCache(taskId) {
+ try {
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks
+ const newSavedTasks = savedTasks.filter((t) => t.id !== taskId);
+ this.plugin.settings.TickTickTasksData.tasks = newSavedTasks
+ } catch (error) {
+ console.error(`Error deleting task from Cache file: ${error}`);
+ }
+ }
+
+
+
+
+
+ //Delete task through ID array
+ deleteTaskFromCacheByIDs(deletedTaskIds) {
+ try {
+ const savedTasks = this.plugin.settings.TickTickTasksData.tasks
+ const newSavedTasks = savedTasks.filter((t) => !deletedTaskIds.includes(t.id))
+ this.plugin.settings.TickTickTasksData.tasks = newSavedTasks
+ } catch (error) {
+ console.error(`Error deleting task from Cache : ${error}`);
+ }
+ }
+
+
+ //Find project id by name
+ getProjectIdByNameFromCache(projectName:string) {
+ try {
+ const savedProjects = this.plugin.settings.TickTickTasksData.projects
+ const targetProject = savedProjects.find(obj => obj.name === projectName);
+ const projectId = targetProject ? targetProject.id : null;
+ return(projectId)
+ } catch (error) {
+ console.error(`Error finding project from Cache file: ${error}`);
+ return(false)
+ }
+ }
+
+
+
+ getProjectNameByIdFromCache(projectId:string) {
+ try {
+ const savedProjects = this.plugin.settings.TickTickTasksData.projects
+ const targetProject = savedProjects.find(obj => obj.id === projectId);
+ const projectName = targetProject ? targetProject.name : null;
+ return(projectName)
+ } catch (error) {
+ console.error(`Error finding project from Cache file: ${error}`);
+ return(false)
+ }
+ }
+
+
+
+ //save projects data to json file
+ async saveProjectsToCache() {
+ try{
+ //get projects
+ console.log("here")
+ console.log(`Save Projects to cachetry with ${this.plugin.tickTickRestAPI}`)
+ const projectGroups = await this.plugin.tickTickRestAPI?.GetProjectGroups();
+ const projects = await this.plugin.tickTickRestAPI?.GetAllProjects();
+
+
+ if(this.plugin.settings.debugMode){
+ if (projectGroups !== undefined && projectGroups !== null) {
+ console.log("==== projectGroups")
+ console.log(projectGroups.map((item) => item.name));
+ }else {
+ console.log("==== No projectGroups")
+ }
+ // ===============
+ if (projects !== undefined && projects !== null) {
+ console.log("==== projects -->")
+ // console.log(projects.map((item) => item.name));
+ projects.forEach(async project => {
+ const singleProject = await this.plugin.TickTickRestAPI?.getProjects(project.id);
+ const sections = await this.plugin.TickTickRestAPI?.getProjectSections(project.id);
+ console.log(`Project: ${project.name} -- ${sections}`);
+ if (sections !== undefined && sections !== null && sections.length > 0) {
+ sections.forEach(section => {
+ console.log(project.name + '--' + section.name);
+ })
+ } else {
+ console.log(project.name + '--' + 'no sections')
+ }
+ })
+ } else {
+ console.log("==== No projects")
+ }
+
+ // ================
+ }
+ if(!projects){
+ return false
+ }
+
+ //save to json
+ this.plugin.settings.TickTickTasksData.projects = projects
+
+ return true
+
+ }catch(error){
+ console.log(`error downloading projects: ${error}`)
+ return false
+ }
+
+ }
+
+
+ async updateRenamedFilePath(oldpath:string,newpath:string){
+ try{
+ console.log(`oldpath is ${oldpath}`)
+ console.log(`newpath is ${newpath}`)
+ const savedTask = await this.loadTasksFromCache()
+ //console.log(savedTask)
+ const newTasks = savedTask.map(obj => {
+ if (obj.path === oldpath) {
+ return { ...obj, path: newpath };
+ }else {
+ return obj;
+ }
+ })
+ //console.log(newTasks)
+ await this.saveTasksToCache(newTasks)
+
+ //update filepath
+ const fileMetadatas = this.plugin.settings.fileMetadata
+ fileMetadatas[newpath] = fileMetadatas[oldpath]
+ delete fileMetadatas[oldpath]
+ this.plugin.settings.fileMetadata = fileMetadatas
+
+ }catch(error){
+ console.log(`Error updating renamed file path to cache: ${error}`)
+ }
+
+
+ }
+
}
diff --git a/src/fileOperation.ts b/src/fileOperation.ts
index 7f4b4b6..6e347aa 100644
--- a/src/fileOperation.ts
+++ b/src/fileOperation.ts
@@ -1,462 +1,463 @@
import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
+import UltimateTickTickSyncForObsidian from "../main";
export class FileOperation {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-
-}
-/*
-async getFrontMatter(file:TFile): Promise {
-return new Promise((resolve) => {
-this.app.fileManager.processFrontMatter(file, (frontMatter) => {
-resolve(frontMatter);
-});
-});
-}
-*/
-
-
-
-
-/*
-async updateFrontMatter(
-file:TFile,
-updater: (frontMatter: FrontMatter) => 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.todoistTasks = updatedFrontMatter.todoistTasks;
-newFrontMatter.todoistCount = updatedFrontMatter.todoistCount;
-}
-});
-}
-});
-}
-*/
-
-
-
-
-
-//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 filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-lines[i] = line.replace('[ ]', '[x]')
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-await this.app.vault.modify(file, newContent)
-}
-}
-
-// uncheck completed tasks,
-async uncompleteTaskInTheFile(taskId: string) {
-// Get the task file path
-const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-const filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-lines[i] = line.replace(/- \[(x|X)\]/g, '- [ ]');
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-await this.app.vault.modify(file, newContent)
-}
-}
-
-//add #todoist at the end of task line, if full vault sync enabled
-async addTodoistTagToFile(filepath: string) {
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if(!this.plugin.taskParser.isMarkdownTask(line)){
-//console.log(line)
-//console.log("It is not a markdown task.")
-continue;
-}
-//if content is empty
-if(this.plugin.taskParser.getTaskContentFromLineText(line) == ""){
-//console.log("Line content is empty")
-continue;
-}
-if (!this.plugin.taskParser.hasTodoistId(line) && !this.plugin.taskParser.hasTodoistTag(line)) {
-//console.log(line)
-//console.log('prepare to add todoist tag')
-const newLine = this.plugin.taskParser.addTodoistTag(line);
-//console.log(newLine)
-lines[i] = newLine
-modified = true
-}
-}
-
-if (modified) {
-console.log(`New task found in files ${filepath}`)
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-
-//update filemetadate
-const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath)
-if(!metadata){
-await this.plugin.cacheOperation.newEmptyFileMetadata(filepath)
-}
-
-}
-}
-
-
-
-//add todoist at the line
-async addTodoistLinkToFile(filepath: string) {
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (this.plugin.taskParser.hasTodoistId(line) && this.plugin.taskParser.hasTodoistTag(line)) {
-if(this.plugin.taskParser.hasTodoistLink(line)){
-return
-}
-console.log(line)
-//console.log('prepare to add todoist link')
-const taskID = this.plugin.taskParser.getTodoistIdFromLineText(line)
-const taskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(taskID)
-const todoistLink = taskObject.url
-const link = `[link](${todoistLink})`
-const newLine = this.plugin.taskParser.addTodoistLink(line,link)
-console.log(newLine)
-lines[i] = newLine
-modified = true
-}else{
-continue
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-
-
-
-}
-}
-
-
-//add #todoist at the end of task line, if full vault sync enabled
-async addTodoistTagToLine(filepath:string,lineText:string,lineNumber:number,fileContent:string) {
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = fileContent
-
-const lines = content.split('\n')
-let modified = false
-
-
-const line = lineText
-if(!this.plugin.taskParser.isMarkdownTask(line)){
-//console.log(line)
-//console.log("It is not a markdown task.")
-return;
-}
-//if content is empty
-if(this.plugin.taskParser.getTaskContentFromLineText(line) == ""){
-//console.log("Line content is empty")
-return;
-}
-if (!this.plugin.taskParser.hasTodoistId(line) && !this.plugin.taskParser.hasTodoistTag(line)) {
-//console.log(line)
-//console.log('prepare to add todoist tag')
-const newLine = this.plugin.taskParser.addTodoistTag(line);
-//console.log(newLine)
-lines[lineNumber] = newLine
-modified = true
-}
-
-
-if (modified) {
-console.log(`New task found in files ${filepath}`)
-const newContent = lines.join('\n')
-console.log(newContent)
-await this.app.vault.modify(file, newContent)
-
-//update filemetadate
-const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath)
-if(!metadata){
-await this.plugin.cacheOperation.newEmptyFileMetadata(filepath)
-}
-
-}
-}
-
-// sync updated task content to file
-async syncUpdatedTaskContentToTheFile(evt:Object) {
-const taskId = evt.object_id
-// Get the task file path
-const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-const filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-const oldTaskContent = this.plugin.taskParser.getTaskContentFromLineText(line)
-const newTaskContent = evt.extra_data.content
-
-lines[i] = line.replace(oldTaskContent, newTaskContent)
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-}
-
-}
-
-// sync updated task due date to the file
-async syncUpdatedTaskDueDateToTheFile(evt:Object) {
-const taskId = evt.object_id
-// Get the task file path
-const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-const filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-const oldTaskDueDate = this.plugin.taskParser.getDueDateFromLineText(line) || ""
-const newTaskDueDate = this.plugin.taskParser.ISOStringToLocalDateString(evt.extra_data.due_date) || ""
-
-//console.log(`${taskId} duedate is updated`)
-console.log(oldTaskDueDate)
-console.log(newTaskDueDate)
-if(oldTaskDueDate === ""){
-//console.log(this.plugin.taskParser.insertDueDateBeforeTodoist(line,newTaskDueDate))
-lines[i] = this.plugin.taskParser.insertDueDateBeforeTodoist(line,newTaskDueDate)
-modified = true
-
-}
-else if(newTaskDueDate === ""){
-//remove date from text
-const regexRemoveDate = /(🗓️|📅|📆|🗓)\s?\d{4}-\d{2}-\d{2}/; //Matching date 🗓️2023-03-07"
-lines[i] = line.replace(regexRemoveDate,"")
-modified = true
-}
-else{
-
-lines[i] = line.replace(oldTaskDueDate, newTaskDueDate)
-modified = true
-}
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-}
-
-}
-
-
-// sync new task note to file
-async syncAddedTaskNoteToTheFile(evt:Object) {
-
-
-const taskId = evt.parent_item_id
-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 filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-const indent = '\t'.repeat(line.length - line.trimStart().length + 1);
-const noteLine = `${indent}- ${datetime} ${note}`;
-lines.splice(i + 1, 0, noteLine);
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-}
-
-}
-
-
-//Avoid using this method, you can get real-time updated value through view
-async readContentFromFilePath(filepath:string){
-try {
-const file = this.app.vault.getAbstractFileByPath(filepath);
-const content = await this.app.vault.read(file);
-return content
-} catch (error) {
-console.error(`Error loading content from ${filepath}: ${error}`);
-return false;
-}
-}
-
-//get line text from file path
-//Please use view.editor.getLine, the read method has a delay
-async getLineTextFromFilePath(filepath:string,lineNumber:string) {
-
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-return(lines[lineNumber])
-}
-
-//search todoist_id by content
-async searchTodoistIdFromFilePath(filepath: string, searchTerm: string): string | null {
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const fileContent = await this.app.vault.read(file)
-const fileLines = fileContent.split('\n');
-let todoistId: string | null = null;
-
-for (let i = 0; i < fileLines.length; i++) {
-const line = fileLines[i];
-
-if (line.includes(searchTerm)) {
-const regexResult = /\[todoist_id::\s*(\w+)\]/.exec(line);
-
-if (regexResult) {
-todoistId = regexResult[1];
-}
-
-break;
-}
-}
-
-return todoistId;
-}
-
-//get all files in the vault
-async getAllFilesInTheVault(){
-const files = this.app.vault.getFiles()
-return(files)
-}
-
-//search filepath by taskid in vault
-async searchFilepathsByTaskidInVault(taskId:string){
-console.log(`preprare to search task ${taskId}`)
-const files = await this.getAllFilesInTheVault()
-//console.log(files)
-const tasks = files.map(async (file) => {
-if (!this.isMarkdownFile(file.path)) {
-return;
-}
-const fileContent = await this.app.vault.cachedRead(file);
-if (fileContent.includes(taskId)) {
-return file.path;
-}
-});
-
-const results = await Promise.all(tasks);
-const filePaths = results.filter((filePath) => filePath !== undefined);
-return filePaths[0] || null;
-//return filePaths || null
-}
-
-
-isMarkdownFile(filename:string) {
-// Get the extension of the file name
-let extension = filename.split('.').pop();
-
-//Convert the extension to lowercase (the extension of Markdown files is usually .md)
-extension = extension.toLowerCase();
-
-// Determine whether the extension is .md
-if (extension === 'md') {
-return true;
-} else {
-return false;
-}
-}
-
-
-
-
-
-}
+ app:App;
+ plugin: UltimateTickTickSyncForObsidian;
+
+
+ constructor(app:App, plugin:UltimateTickTickSyncForObsidian) {
+ //super(app,settings);
+ this.app = app;
+ this.plugin = plugin;
+
+ }
+ /*
+ async getFrontMatter(file:TFile): Promise {
+ return new Promise((resolve) => {
+ this.app.fileManager.processFrontMatter(file, (frontMatter) => {
+ resolve(frontMatter);
+ });
+ });
+ }
+ */
+
+
+
+
+ /*
+ async updateFrontMatter(
+ file:TFile,
+ updater: (frontMatter: FrontMatter) => 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;
+ }
+ });
+ }
+ });
+ }
+ */
+
+
+
+
+
+ //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 filepath = currentTask.path
+
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ let modified = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ if (line.includes(taskId) && this.plugin.taskParser.hasTickTickTag(line)) {
+ lines[i] = line.replace('[ ]', '[x]')
+ modified = true
+ break
+ }
+ }
+
+ if (modified) {
+ const newContent = lines.join('\n')
+ await this.app.vault.modify(file, newContent)
+ }
+ }
+
+ // uncheck completed tasks,
+ async uncompleteTaskInTheFile(taskId: string) {
+ // Get the task file path
+ const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
+ const filepath = currentTask.path
+
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ let modified = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ if (line.includes(taskId) && this.plugin.taskParser.hasTickTickTag(line)) {
+ lines[i] = line.replace(/- \[(x|X)\]/g, '- [ ]');
+ modified = true
+ break
+ }
+ }
+
+ if (modified) {
+ const newContent = lines.join('\n')
+ await this.app.vault.modify(file, newContent)
+ }
+ }
+
+ //add #TickTick at the end of task line, if full vault sync enabled
+ async addTickTickTagToFile(filepath: string) {
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ let modified = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ if(!this.plugin.taskParser.isMarkdownTask(line)){
+ //console.log(line)
+ //console.log("It is not a markdown task.")
+ continue;
+ }
+ //if content is empty
+ if(this.plugin.taskParser.getTaskContentFromLineText(line) == ""){
+ //console.log("Line content is empty")
+ continue;
+ }
+ if (!this.plugin.taskParser.hasTickTickId(line) && !this.plugin.taskParser.hasTickTickTag(line)) {
+ //console.log(line)
+ //console.log('prepare to add TickTick tag')
+ const newLine = this.plugin.taskParser.addTickTickTag(line);
+ //console.log(newLine)
+ lines[i] = newLine
+ modified = true
+ }
+ }
+
+ if (modified) {
+ console.log(`New task found in files ${filepath}`)
+ const newContent = lines.join('\n')
+ //console.log(newContent)
+ await this.app.vault.modify(file, newContent)
+
+ //update filemetadate
+ const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath)
+ if(!metadata){
+ await this.plugin.cacheOperation.newEmptyFileMetadata(filepath)
+ }
+
+ }
+ }
+
+
+
+ //add TickTick at the line
+ async addTickTickLinkToFile(filepath: string) {
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ let modified = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ if (this.plugin.taskParser.hasTickTickId(line) && this.plugin.taskParser.hasTickTickTag(line)) {
+ if(this.plugin.taskParser.hasTickTickLink(line)){
+ return
+ }
+ console.log(line)
+ //console.log('prepare to add TickTick link')
+ const taskID = this.plugin.taskParser.getTickTickIdFromLineText(line)
+ const taskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(taskID)
+ const TickTickLink = taskObject.url
+ const link = `[link](${TickTickLink})`
+ const newLine = this.plugin.taskParser.addTickTickLink(line,link)
+ console.log(newLine)
+ lines[i] = newLine
+ modified = true
+ }else{
+ continue
+ }
+ }
+
+ if (modified) {
+ const newContent = lines.join('\n')
+ //console.log(newContent)
+ await this.app.vault.modify(file, newContent)
+
+
+
+ }
+ }
+
+
+ //add #TickTick at the end of task line, if full vault sync enabled
+ async addTickTickTagToLine(filepath:string,lineText:string,lineNumber:number,fileContent:string) {
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = fileContent
+
+ const lines = content.split('\n')
+ let modified = false
+
+
+ const line = lineText
+ if(!this.plugin.taskParser.isMarkdownTask(line)){
+ //console.log(line)
+ //console.log("It is not a markdown task.")
+ return;
+ }
+ //if content is empty
+ if(this.plugin.taskParser.getTaskContentFromLineText(line) == ""){
+ //console.log("Line content is empty")
+ return;
+ }
+ if (!this.plugin.taskParser.hasTickTickId(line) && !this.plugin.taskParser.hasTickTickTag(line)) {
+ //console.log(line)
+ //console.log('prepare to add TickTick tag')
+ const newLine = this.plugin.taskParser.addTickTickTag(line);
+ //console.log(newLine)
+ lines[lineNumber] = newLine
+ modified = true
+ }
+
+
+ if (modified) {
+ console.log(`New task found in files ${filepath}`)
+ const newContent = lines.join('\n')
+ console.log(newContent)
+ await this.app.vault.modify(file, newContent)
+
+ //update filemetadate
+ const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath)
+ if(!metadata){
+ await this.plugin.cacheOperation.newEmptyFileMetadata(filepath)
+ }
+
+ }
+ }
+
+ // sync updated task content to file
+ async syncUpdatedTaskContentToTheFile(evt:Object) {
+ const taskId = evt.object_id
+ // Get the task file path
+ const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
+ const filepath = currentTask.path
+
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ let modified = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ if (line.includes(taskId) && this.plugin.taskParser.hasTickTickTag(line)) {
+ const oldTaskContent = this.plugin.taskParser.getTaskContentFromLineText(line)
+ const newTaskContent = evt.extra_data.content
+
+ lines[i] = line.replace(oldTaskContent, newTaskContent)
+ modified = true
+ break
+ }
+ }
+
+ if (modified) {
+ const newContent = lines.join('\n')
+ //console.log(newContent)
+ await this.app.vault.modify(file, newContent)
+ }
+
+ }
+
+ // sync updated task due date to the file
+ async syncUpdatedTaskDueDateToTheFile(evt:Object) {
+ const taskId = evt.object_id
+ // Get the task file path
+ const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
+ const filepath = currentTask.path
+
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ let modified = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ if (line.includes(taskId) && this.plugin.taskParser.hasTickTickTag(line)) {
+ const oldTaskDueDate = this.plugin.taskParser.getDueDateFromLineText(line) || ""
+ const newTaskDueDate = this.plugin.taskParser.ISOStringToLocalDateString(evt.extra_data.due_date) || ""
+
+ //console.log(`${taskId} duedate is updated`)
+ console.log(oldTaskDueDate)
+ console.log(newTaskDueDate)
+ if(oldTaskDueDate === ""){
+ //console.log(this.plugin.taskParser.insertDueDateBeforeTickTick(line,newTaskDueDate))
+ lines[i] = this.plugin.taskParser.insertDueDateBeforeTickTick(line,newTaskDueDate)
+ modified = true
+
+ }
+ else if(newTaskDueDate === ""){
+ //remove date from text
+ const regexRemoveDate = /(🗓️|📅|📆|🗓)\s?\d{4}-\d{2}-\d{2}/; //Matching date 🗓️2023-03-07"
+ lines[i] = line.replace(regexRemoveDate,"")
+ modified = true
+ }
+ else{
+
+ lines[i] = line.replace(oldTaskDueDate, newTaskDueDate)
+ modified = true
+ }
+ break
+ }
+ }
+
+ if (modified) {
+ const newContent = lines.join('\n')
+ //console.log(newContent)
+ await this.app.vault.modify(file, newContent)
+ }
+
+ }
+
+
+ // sync new task note to file
+ async syncAddedTaskNoteToTheFile(evt:Object) {
+
+
+ const taskId = evt.parent_item_id
+ 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 filepath = currentTask.path
+
+ // Get the file object and update the content
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ let modified = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ if (line.includes(taskId) && this.plugin.taskParser.hasTickTickTag(line)) {
+ const indent = '\t'.repeat(line.length - line.trimStart().length + 1);
+ const noteLine = `${indent}- ${datetime} ${note}`;
+ lines.splice(i + 1, 0, noteLine);
+ modified = true
+ break
+ }
+ }
+
+ if (modified) {
+ const newContent = lines.join('\n')
+ //console.log(newContent)
+ await this.app.vault.modify(file, newContent)
+ }
+
+ }
+
+
+ //Avoid using this method, you can get real-time updated value through view
+ async readContentFromFilePath(filepath:string){
+ try {
+ const file = this.app.vault.getAbstractFileByPath(filepath);
+ const content = await this.app.vault.read(file);
+ return content
+ } catch (error) {
+ console.error(`Error loading content from ${filepath}: ${error}`);
+ return false;
+ }
+ }
+
+ //get line text from file path
+ //Please use view.editor.getLine, the read method has a delay
+ async getLineTextFromFilePath(filepath:string,lineNumber:string) {
+
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const content = await this.app.vault.read(file)
+
+ const lines = content.split('\n')
+ return(lines[lineNumber])
+ }
+
+ //search TickTick_id by content
+ async searchTickTickIdFromFilePath(filepath: string, searchTerm: string): string | null {
+ const file = this.app.vault.getAbstractFileByPath(filepath)
+ const fileContent = await this.app.vault.read(file)
+ const fileLines = fileContent.split('\n');
+ let TickTickId: string | null = null;
+
+ for (let i = 0; i < fileLines.length; i++) {
+ const line = fileLines[i];
+
+ if (line.includes(searchTerm)) {
+ const regexResult = /\[TickTick_id::\s*(\w+)\]/.exec(line);
+
+ if (regexResult) {
+ TickTickId = regexResult[1];
+ }
+
+ break;
+ }
+ }
+
+ return TickTickId;
+ }
+
+ //get all files in the vault
+ async getAllFilesInTheVault(){
+ const files = this.app.vault.getFiles()
+ return(files)
+ }
+
+ //search filepath by taskid in vault
+ async searchFilepathsByTaskidInVault(taskId:string){
+ console.log(`preprare to search task ${taskId}`)
+ const files = await this.getAllFilesInTheVault()
+ //console.log(files)
+ const tasks = files.map(async (file) => {
+ if (!this.isMarkdownFile(file.path)) {
+ return;
+ }
+ const fileContent = await this.app.vault.cachedRead(file);
+ if (fileContent.includes(taskId)) {
+ return file.path;
+ }
+ });
+
+ const results = await Promise.all(tasks);
+ const filePaths = results.filter((filePath) => filePath !== undefined);
+ return filePaths[0] || null;
+ //return filePaths || null
+ }
+
+
+ isMarkdownFile(filename:string) {
+ // Get the extension of the file name
+ let extension = filename.split('.').pop();
+
+ //Convert the extension to lowercase (the extension of Markdown files is usually .md)
+ extension = extension.toLowerCase();
+
+ // Determine whether the extension is .md
+ if (extension === 'md') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+
+
+
+ }
+
\ No newline at end of file
diff --git a/src/modal.ts b/src/modal.ts
index baddc5b..e2956e3 100644
--- a/src/modal.ts
+++ b/src/modal.ts
@@ -1,70 +1,71 @@
import { App, Modal ,Setting } from "obsidian";
-import UltimateTodoistSyncForObsidian from "../main"
+import UltimateTickTickSyncForObsidian from "../main"
interface MyProject {
-id: string;
-name: string;
+ id: string;
+ name: string;
}
export class SetDefalutProjectInTheFilepathModal extends Modal {
-defaultProjectId: string
-defaultProjectName: string
-filepath:string
-plugin:UltimateTodoistSyncForObsidian
-
-
-constructor(app: App,plugin:UltimateTodoistSyncForObsidian, filepath:string) {
-super(app);
-this.filepath = filepath
-this.plugin = plugin
-this.open()
-}
-
-async onOpen() {
-const { contentEl } = this;
-contentEl.empty();
-contentEl.createEl('h5', { text: 'Set default project for todoist tasks in the current file' });
-
-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)
-const myProjectsOptions: MyProject | undefined = this.plugin.settings.todoistTasksData?.projects?.reduce((obj, item) => {
-obj[(item.id).toString()] = item.name;
-return obj;
-}, {}
-);
-
-
-
-new Setting(contentEl)
-.setName('Default project')
-//.setDesc('Set default project for todoist tasks in the current file')
-.addDropdown(component =>
-component
-.addOption(this.defaultProjectId,this.defaultProjectName)
-.addOptions(myProjectsOptions)
-.onChange((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()
-this.plugin.cacheOperation.setDefaultProjectIdForFilepath(this.filepath,value)
-this.plugin.setStatusBarText()
-this.close();
-
-})
-
-)
-
-
-
-
-}
-
-onClose() {
-let { contentEl } = this;
-contentEl.empty();
-}
-}
+ defaultProjectId: string
+ defaultProjectName: string
+ filepath:string
+ plugin:UltimateTickTickSyncForObsidian
+
+
+ constructor(app: App,plugin:UltimateTickTickSyncForObsidian, filepath:string) {
+ super(app);
+ this.filepath = filepath
+ this.plugin = plugin
+ this.open()
+ }
+
+ async onOpen() {
+ const { contentEl } = this;
+ contentEl.empty();
+ contentEl.createEl('h5', { text: 'Set default project for TickTick tasks in the current file' });
+
+ 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)
+ const myProjectsOptions: MyProject | undefined = this.plugin.settings.TickTickTasksData?.projects?.reduce((obj, item) => {
+ obj[(item.id).toString()] = item.name;
+ return obj;
+ }, {}
+ );
+
+
+
+ new Setting(contentEl)
+ .setName('Default project')
+ //.setDesc('Set default project for TickTick tasks in the current file')
+ .addDropdown(component =>
+ component
+ .addOption(this.defaultProjectId,this.defaultProjectName)
+ .addOptions(myProjectsOptions)
+ .onChange((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()
+ this.plugin.cacheOperation.setDefaultProjectIdForFilepath(this.filepath,value)
+ this.plugin.setStatusBarText()
+ this.close();
+
+ })
+
+ )
+
+
+
+
+ }
+
+ onClose() {
+ let { contentEl } = this;
+ contentEl.empty();
+ }
+ }
+
\ No newline at end of file
diff --git a/src/settings.ts b/src/settings.ts
index 55ed267..ee6dd94 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -1,22 +1,24 @@
import { App, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
+import UltimateTickTickSyncForObsidian from "../main";
interface MyProject {
id: string;
name: string;
- }
+}
-export interface UltimateTodoistSyncSettings {
- initialized:boolean;
+export interface UltimateTickTickSyncSettings {
+ initialized:boolean;
//mySetting: string;
- //todoistTasksFilePath: string;
- todoistAPIToken: string; // replace with correct type
+ //TickTickTasksFilePath: string;
+ username: string;
+ password: string;
+ // TickTickAPIToken: string; // replace with correct type
apiInitialized:boolean;
defaultProjectName: string;
defaultProjectId:string;
automaticSynchronizationInterval:Number;
- todoistTasksData:any;
+ TickTickTasksData:any;
fileMetadata:any;
enableFullVaultSync: boolean;
statistics: any;
@@ -24,78 +26,113 @@ export interface UltimateTodoistSyncSettings {
}
-export const DEFAULT_SETTINGS: UltimateTodoistSyncSettings = {
+export const DEFAULT_SETTINGS: UltimateTickTickSyncSettings = {
initialized: false,
apiInitialized:false,
defaultProjectName:"Inbox",
automaticSynchronizationInterval: 300, //default aync interval 300s
- todoistTasksData:{"projects":[],"tasks":[],"events":[]},
+ TickTickTasksData:{"projects":[],"tasks":[],"events":[]},
fileMetadata:{},
enableFullVaultSync:false,
statistics:{},
debugMode:false,
//mySetting: 'default',
- //todoistTasksFilePath: 'todoistTasks.json'
-
+ //TickTickTasksFilePath: 'TickTickTasks.json'
+
}
-export class UltimateTodoistSyncSettingTab extends PluginSettingTab {
- plugin: UltimateTodoistSyncForObsidian;
-
- constructor(app: App, plugin: UltimateTodoistSyncForObsidian) {
+export class UltimateTickTickSyncSettingTab extends PluginSettingTab {
+ plugin: UltimateTickTickSyncForObsidian;
+
+ constructor(app: App, plugin: UltimateTickTickSyncForObsidian) {
super(app, plugin);
this.plugin = plugin;
}
-
+
display(): void {
const { containerEl } = this;
-
+
containerEl.empty();
-
- containerEl.createEl('h2', { text: 'Settings for Ultimate Todoist Sync for Obsidian.' });
-
- const myProjectsOptions: MyProject | undefined = this.plugin.settings.todoistTasksData?.projects?.reduce((obj, item) => {
+
+ containerEl.createEl('h2', { text: 'Settings for Ultimate TickTick Sync for Obsidian.' });
+
+ const myProjectsOptions: MyProject | undefined = this.plugin.settings.TickTickTasksData?.projects?.reduce((obj, item) => {
obj[(item.id).toString()] = item.name;
return obj;
- }, {});
-
+ }, {});
+
new Setting(containerEl)
- .setName('Todoist API')
- .setDesc('Please enter todoist api token and click the paper airplane button to submit.')
- .addText((text) =>
- text
- .setPlaceholder('Enter your API')
- .setValue(this.plugin.settings.todoistAPIToken)
- .onChange(async (value) => {
- this.plugin.settings.todoistAPIToken = value;
- this.plugin.settings.apiInitialized = false;
- //
- })
-
- )
- .addExtraButton((button) => {
- button.setIcon('send')
+ .setName('Username')
+ .setDesc('...')
+ .addText(text => text
+ .setPlaceholder('Type username here...')
+ .setValue(this.plugin.settings.username)
+ .onChange(async (value) => {
+ this.plugin.settings.username = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
+ new Setting(containerEl)
+ .setName('Password')
+ .setDesc('...')
+ .addText(text => text
+ .setPlaceholder('Type password here...')
+ .setValue(this.plugin.settings.password)
+ .onChange(async (value) => {
+ this.plugin.settings.password = value;
+ await this.plugin.saveSettings();
+ })
+
+ )
+ .addExtraButton((button) => {
+ button.setIcon('send')
.onClick(async () => {
- await this.plugin.modifyTodoistAPI(this.plugin.settings.todoistAPIToken)
- this.display()
-
- })
+ await this.plugin.modifyTickTickAPI(this.plugin.settings.TickTickAPIToken)
+ this.display()
+
+ })
- })
-
-
-
-
- new Setting(containerEl)
- .setName('Automatic Sync Interval Time')
- .setDesc('Please specify the desired interval time, with seconds as the default unit. The default setting is 300 seconds, which corresponds to syncing once every 5 minutes. You can customize it, but it cannot be lower than 20 seconds.')
- .addText((text) =>
- text
+ });
+
+ // new Setting(containerEl)
+ // .setName('TickTick API')
+ // .setDesc('Please enter TickTick api token and click the paper airplane button to submit.')
+ // .addText((text) =>
+ // text
+ // .setPlaceholder('Enter your API')
+ // .setValue(this.plugin.settings.TickTickAPIToken)
+ // .onChange(async (value) => {
+ // this.plugin.settings.TickTickAPIToken = value;
+ // this.plugin.settings.apiInitialized = false;
+ // //
+ // })
+
+ // )
+ // .addExtraButton((button) => {
+ // button.setIcon('send')
+ // .onClick(async () => {
+ // await this.plugin.modifyTickTickAPI(this.plugin.settings.TickTickAPIToken)
+ // this.display()
+
+ // })
+
+
+ // })
+
+
+
+
+ new Setting(containerEl)
+ .setName('Automatic Sync Interval Time')
+ .setDesc('Please specify the desired interval time, with seconds as the default unit. The default setting is 300 seconds, which corresponds to syncing once every 5 minutes. You can customize it, but it cannot be lower than 20 seconds.')
+ .addText((text) =>
+ text
.setPlaceholder('Sync interval')
.setValue(this.plugin.settings.automaticSynchronizationInterval.toString())
.onChange(async (value) => {
@@ -117,63 +154,63 @@ export class UltimateTodoistSyncSettingTab extends PluginSettingTab {
new Notice('Settings have been updated.');
//
})
-
- )
-
-
- /*
- new Setting(containerEl)
- .setName('The default project for new tasks')
- .setDesc('New tasks are automatically synced to the Inbox. You can modify the project here.')
- .addText((text) =>
+
+ )
+
+
+ /*
+ new Setting(containerEl)
+ .setName('The default project for new tasks')
+ .setDesc('New tasks are automatically synced to the Inbox. You can modify the project here.')
+ .addText((text) =>
text
- .setPlaceholder('Enter default project name:')
- .setValue(this.plugin.settings.defaultProjectName)
- .onChange(async (value) => {
- try{
- //this.plugin.cacheOperation.saveProjectsToCache()
- const newProjectId = this.plugin.cacheOperation.getProjectIdByNameFromCache(value)
- if(!newProjectId){
- new Notice(`This project seems to not exist.`)
- return
- }
- }catch(error){
- new Notice(`Invalid project name `)
+ .setPlaceholder('Enter default project name:')
+ .setValue(this.plugin.settings.defaultProjectName)
+ .onChange(async (value) => {
+ try{
+ //this.plugin.cacheOperation.saveProjectsToCache()
+ const newProjectId = this.plugin.cacheOperation.getProjectIdByNameFromCache(value)
+ if(!newProjectId){
+ new Notice(`This project seems to not exist.`)
return
}
- this.plugin.settings.defaultProjectName = value;
+ }catch(error){
+ new Notice(`Invalid project name `)
+ return
+ }
+ this.plugin.settings.defaultProjectName = value;
+ this.plugin.saveSettings()
+ new Notice(`The default project has been modified successfully.`)
+
+ })
+
+ );
+ */
+
+ new Setting(containerEl)
+ .setName('Default Project')
+ .setDesc('New tasks are automatically synced to the default project. You can modify the project here.')
+ .addDropdown(component =>
+ component
+ .addOption(this.plugin.settings.defaultProjectId,this.plugin.settings.defaultProjectName)
+ .addOptions(myProjectsOptions)
+ .onChange((value)=>{
+ this.plugin.settings.defaultProjectId = value
+ this.plugin.settings.defaultProjectName = this.plugin.cacheOperation.getProjectNameByIdFromCache(value)
this.plugin.saveSettings()
- new Notice(`The default project has been modified successfully.`)
-
- })
-
- );
- */
-
- new Setting(containerEl)
- .setName('Default Project')
- .setDesc('New tasks are automatically synced to the default project. You can modify the project here.')
- .addDropdown(component =>
- component
- .addOption(this.plugin.settings.defaultProjectId,this.plugin.settings.defaultProjectName)
- .addOptions(myProjectsOptions)
- .onChange((value)=>{
- this.plugin.settings.defaultProjectId = value
- this.plugin.settings.defaultProjectName = this.plugin.cacheOperation.getProjectNameByIdFromCache(value)
- this.plugin.saveSettings()
-
-
- })
- )
-
-
-
- new Setting(containerEl)
- .setName('Full Vault Sync')
- .setDesc('By default, only tasks marked with #todoist are synchronized. If this option is turned on, all tasks in the vault will be synchronized.')
- .addToggle(component =>
- component
+
+ })
+
+ )
+
+
+
+ new Setting(containerEl)
+ .setName('Full Vault Sync')
+ .setDesc('By default, only tasks marked with #TickTick are synchronized. If this option is turned on, all tasks in the vault will be synchronized.')
+ .addToggle(component =>
+ component
.setValue(this.plugin.settings.enableFullVaultSync)
.onChange((value)=>{
this.plugin.settings.enableFullVaultSync = value
@@ -181,221 +218,222 @@ export class UltimateTodoistSyncSettingTab extends PluginSettingTab {
new Notice("Full vault sync is enabled.")
})
- )
-
-
-
- new Setting(containerEl)
- .setName('Manual Sync')
- .setDesc('Manually perform a synchronization task.')
- .addButton(button => button
- .setButtonText('Sync')
- .onClick(async () => {
- // Add code here to handle exporting Todoist data
- if(!this.plugin.settings.apiInitialized){
- new Notice(`Please set the todoist api first`)
- return
- }
- try{
- await this.plugin.scheduledSynchronization()
- this.plugin.syncLock = false
- new Notice(`Sync completed..`)
- }catch(error){
- new Notice(`An error occurred while syncing.:${error}`)
- this.plugin.syncLock = false
- }
-
- })
- );
-
-
-
- new Setting(containerEl)
- .setName('Check Database')
- .setDesc('Check for possible issues: sync error, file renaming not updated, or missed tasks not synchronized.')
- .addButton(button => button
- .setButtonText('Check Database')
- .onClick(async () => {
- // Add code here to handle exporting Todoist data
- if(!this.plugin.settings.apiInitialized){
- new Notice(`Please set the todoist api first`)
- return
- }
-
- //reinstall plugin
-
-
-
- //check file metadata
- console.log('checking file metadata')
- await this.plugin.cacheOperation.checkFileMetadata()
- this.plugin.saveSettings()
- const metadatas = await this.plugin.cacheOperation.getFileMetadatas()
- // check default project task amounts
- try{
- const projectId = this.plugin.settings.defaultProjectId
- let options = {}
- options.projectId = projectId
- const tasks = await this.plugin.todoistRestAPI.GetActiveTasks(options)
- 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.`)
- }
- //console.log(tasks)
-
- }catch(error){
- console.error(`An error occurred while get tasks from todoist: ${error.message}`);
- }
-
- if (!await this.plugin.checkAndHandleSyncLock()) return;
-
-
-
- console.log('checking deleted tasks')
- //check empty task
- for (const key in metadatas) {
- const value = metadatas[key];
- //console.log(value)
- for(const taskId of value.todoistTasks) {
+ )
- //console.log(`${taskId}`)
- let taskObject
-
- try{
- taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(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.`)
- //get from todoist
- try {
- taskObject = await this.plugin.todoistRestAPI.getTaskById(taskId);
- } catch (error) {
- if (error.message.includes('404')) {
- // 处理404错误
- console.log(`Task ${taskId} seems to not exist.`);
- await this.plugin.cacheOperation.deleteTaskIdFromMetadata(key,taskId)
- continue
- } else {
- // 处理其他错误
- console.error(error);
- continue
+
+
+ new Setting(containerEl)
+ .setName('Manual Sync')
+ .setDesc('Manually perform a synchronization task.')
+ .addButton(button => button
+ .setButtonText('Sync')
+ .onClick(async () => {
+ // Add code here to handle exporting TickTick data
+ if(!this.plugin.settings.apiInitialized){
+ new Notice(`Please set the TickTick api first`)
+ return
}
- }
-
- }
- };
-
- }
- this.plugin.saveSettings()
-
-
- console.log('checking renamed files')
- try{
- //check renamed files
- for (const key in metadatas) {
- const value = metadatas[key];
- //console.log(value)
- const newDescription = this.plugin.taskParser.getObsidianUrlFromFilepath(key)
- for(const taskId of value.todoistTasks) {
-
- //console.log(`${taskId}`)
- let taskObject
- try{
- taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
- }catch(error){
- console.error(`An error occurred while loading task ${taskId} from cache: ${error.message}`);
- console.log(taskObject)
- }
- if(!taskObject){
- console.log(`Task ${taskId} seems to not exist.`)
- continue
- }
- if(!taskObject?.description){
- console.log(`The description of the task ${taskId} is empty.`)
- }
- const oldDescription = taskObject?.description ?? '';
- if(newDescription != oldDescription){
- console.log('Preparing to update description.')
- console.log(oldDescription)
- console.log(newDescription)
try{
- //await this.plugin.todoistSync.updateTaskDescription(key)
+ await this.plugin.scheduledSynchronization()
+ this.plugin.syncLock = false
+ new Notice(`Sync completed..`)
}catch(error){
- console.error(`An error occurred while updating task discription: ${error.message}`);
+ new Notice(`An error occurred while syncing.:${error}`)
+ this.plugin.syncLock = false
}
-
- }
-
- };
-
- }
-
- //check empty file metadata
-
- //check calendar format
-
-
-
- //check omitted tasks
- console.log('checking unsynced tasks')
- const files = this.app.vault.getFiles()
- files.forEach(async (v, i) => {
- if(v.extension == "md"){
- try{
- //console.log(`Scanning file ${v.path}`)
- await this.plugin.fileOperation.addTodoistLinkToFile(v.path)
- if(this.plugin.settings.enableFullVaultSync){
- await this.plugin.fileOperation.addTodoistTagToFile(v.path)
- }
-
- }catch(error){
- console.error(`An error occurred while check new tasks in the file: ${v.path}, ${error.message}`);
+ })
+ );
+
+
+
+ new Setting(containerEl)
+ .setName('Check Database')
+ .setDesc('Check for possible issues: sync error, file renaming not updated, or missed tasks not synchronized.')
+ .addButton(button => button
+ .setButtonText('Check Database')
+ .onClick(async () => {
+ // Add code here to handle exporting TickTick data
+ if(!this.plugin.settings.apiInitialized){
+ new Notice(`Please set the TickTick api first`)
+ return
+ }
+
+ //reinstall plugin
+
+
+
+ //check file metadata
+ console.log('checking file metadata')
+ await this.plugin.cacheOperation.checkFileMetadata()
+ this.plugin.saveSettings()
+ const metadatas = await this.plugin.cacheOperation.getFileMetadatas()
+ // check default project task amounts
+ try{
+ const projectId = this.plugin.settings.defaultProjectId
+ let options = {}
+ options.projectId = projectId
+ const tasks = await this.plugin.TickTickRestAPI.GetActiveTasks(options)
+ 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.`)
+ }
+ //console.log(tasks)
+
+ }catch(error){
+ console.error(`An error occurred while get tasks from TickTick: ${error.message}`);
+ }
+
+ if (!await this.plugin.checkAndHandleSyncLock()) return;
+
+
+
+ console.log('checking deleted tasks')
+ //check empty task
+ for (const key in metadatas) {
+ const value = metadatas[key];
+ //console.log(value)
+ for(const taskId of value.TickTickTasks) {
+
+ //console.log(`${taskId}`)
+ let taskObject
+
+ try{
+ taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(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.`)
+ //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.`);
+ await this.plugin.cacheOperation.deleteTaskIdFromMetadata(key,taskId)
+ continue
+ } else {
+ // 处理其他错误
+ console.error(error);
+ continue
+ }
+ }
+
+ }
+ };
+
+ }
+ this.plugin.saveSettings()
+
+
+ console.log('checking renamed files')
+ try{
+ //check renamed files
+ for (const key in metadatas) {
+ const value = metadatas[key];
+ //console.log(value)
+ const newDescription = this.plugin.taskParser.getObsidianUrlFromFilepath(key)
+ for(const taskId of value.TickTickTasks) {
+
+ //console.log(`${taskId}`)
+ let taskObject
+ try{
+ taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
+ }catch(error){
+ console.error(`An error occurred while loading task ${taskId} from cache: ${error.message}`);
+ console.log(taskObject)
+ }
+ if(!taskObject){
+ console.log(`Task ${taskId} seems to not exist.`)
+ continue
+ }
+ if(!taskObject?.description){
+ console.log(`The description of the task ${taskId} is empty.`)
+ }
+ const oldDescription = taskObject?.description ?? '';
+ if(newDescription != oldDescription){
+ console.log('Preparing to update description.')
+ console.log(oldDescription)
+ console.log(newDescription)
+ try{
+ //await this.plugin.TickTickSync.updateTaskDescription(key)
+ }catch(error){
+ console.error(`An error occurred while updating task discription: ${error.message}`);
+ }
+
+ }
+
+ };
+
+ }
+
+ //check empty file metadata
+
+ //check calendar format
+
+
+
+ //check omitted tasks
+ console.log('checking unsynced tasks')
+ const files = this.app.vault.getFiles()
+ files.forEach(async (v, i) => {
+ if(v.extension == "md"){
+ try{
+ //console.log(`Scanning file ${v.path}`)
+ await this.plugin.fileOperation.addTickTickLinkToFile(v.path)
+ if(this.plugin.settings.enableFullVaultSync){
+ await this.plugin.fileOperation.addTickTickTagToFile(v.path)
+ }
+
+
+ }catch(error){
+ console.error(`An error occurred while check new tasks in the file: ${v.path}, ${error.message}`);
+
+ }
+
+ }
+ });
+ this.plugin.syncLock = false
+ new Notice(`All files have been scanned.`)
+ }catch(error){
+ console.error(`An error occurred while scanning the vault.:${error}`)
+ this.plugin.syncLock = false
+ }
+
+ })
+ );
- }
-
- }
- });
- this.plugin.syncLock = false
- new Notice(`All files have been scanned.`)
- }catch(error){
- console.error(`An error occurred while scanning the vault.:${error}`)
- this.plugin.syncLock = false
- }
-
- })
- );
-
- new Setting(containerEl)
- .setName('Debug Mode')
- .setDesc('After enabling this option, all log information will be output to the console, which can help check for errors.')
- .addToggle(component =>
- component
- .setValue(this.plugin.settings.debugMode)
- .onChange((value)=>{
- this.plugin.settings.debugMode = value
- this.plugin.saveSettings()
- })
-
- )
-
- new Setting(containerEl)
- .setName('Backup Todoist Data')
- .setDesc('Click to backup Todoist data, The backed-up files will be stored in the root directory of the Obsidian vault.')
- .addButton(button => button
- .setButtonText('Backup')
- .onClick(() => {
- // Add code here to handle exporting Todoist data
- if(!this.plugin.settings.apiInitialized){
- new Notice(`Please set the todoist api first`)
- return
- }
- this.plugin.todoistSync.backupTodoistAllResources()
- })
- );
- }
-}
-
+ new Setting(containerEl)
+ .setName('Debug Mode')
+ .setDesc('After enabling this option, all log information will be output to the console, which can help check for errors.')
+ .addToggle(component =>
+ component
+ .setValue(this.plugin.settings.debugMode)
+ .onChange((value)=>{
+ this.plugin.settings.debugMode = value
+ this.plugin.saveSettings()
+ })
+
+ )
+
+ new Setting(containerEl)
+ .setName('Backup TickTick Data')
+ .setDesc('Click to backup TickTick data, The backed-up files will be stored in the root directory of the Obsidian vault.')
+ .addButton(button => button
+ .setButtonText('Backup')
+ .onClick(() => {
+ // Add code here to handle exporting TickTick data
+ if(!this.plugin.settings.apiInitialized){
+ new Notice(`Please set the TickTick api first`)
+ return
+ }
+ this.plugin.TickTickSync.backupTickTickAllResources()
+ })
+ );
+ }
+ }
+
+
\ No newline at end of file
diff --git a/src/static/index.css b/src/static/index.css
new file mode 100644
index 0000000..fc9206e
--- /dev/null
+++ b/src/static/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/src/static/styles.css b/src/static/styles.css
new file mode 100644
index 0000000..b2ebe00
--- /dev/null
+++ b/src/static/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/src/taskParser.ts b/src/taskParser.ts
index 991c04a..36c22c5 100644
--- a/src/taskParser.ts
+++ b/src/taskParser.ts
@@ -1,546 +1,548 @@
import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
+import UltimateTickTickSyncForObsidian from "../main";
+import { Tick } from 'ticktick-api-lvt'
+import ITask from "ticktick-api-lvt/src/types/Task"
interface dataviewTaskObject {
-status: string;
-checked: boolean;
-completed: boolean;
-fullyCompleted: boolean;
-text: string;
-visual: string;
-line: number;
-lineCount: number;
-path: string;
-section: string;
-tags: string[];
-outlinks: string[];
-link: string;
-children: any[];
-task: boolean;
-annotated: boolean;
-parent: number;
-blockId: string;
+ status: string;
+ checked: boolean;
+ completed: boolean;
+ fullyCompleted: boolean;
+ text: string;
+ visual: string;
+ line: number;
+ lineCount: number;
+ path: string;
+ section: string;
+ tags: string[];
+ outlinks: string[];
+ link: string;
+ children: any[];
+ task: boolean;
+ annotated: boolean;
+ parent: number;
+ blockId: string;
+}
+
+
+interface TickTickTaskObject {
+ content: string;
+ description?: string;
+ project_id?: string;
+ section_id?: string;
+ parent_id?: string;
+ order?: number | null;
+ labels?: string[];
+ priority?: number | null;
+ due_string?: string;
+ due_date?: string;
+ due_datetime?: string;
+ due_lang?: string;
+ assignee_id?: string;
}
-
-
-interface todoistTaskObject {
-content: string;
-description?: string;
-project_id?: string;
-section_id?: string;
-parent_id?: string;
-order?: number | null;
-labels?: string[];
-priority?: number | null;
-due_string?: string;
-due_date?: string;
-due_datetime?: string;
-due_lang?: string;
-assignee_id?: string;
-}
-
+
const keywords = {
-TODOIST_TAG: "#todoist",
-DUE_DATE: "🗓️|📅|📆|🗓",
+ TickTick_TAG: "#TickTick",
+ DUE_DATE: "🗓️|📅|📆|🗓",
};
const REGEX = {
-TODOIST_TAG: new RegExp(`^[\\s]*[-] \\[[x ]\\] [\\s\\S]*${keywords.TODOIST_TAG}[\\s\\S]*$ `, "i"),
-TODOIST_ID: /\[todoist_id::\s*\d+\]/,
-TODOIST_ID_NUM:/\[todoist_id::\s*(.*?)\]/,
-TODOIST_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})`),
-PROJECT_NAME: /\[project::\s*(.*?)\]/,
-TASK_CONTENT: {
-REMOVE_PRIORITY: /\s!!([1-4])\s/,
-REMOVE_TAGS: /(^|\s)(#[a-zA-Z\d\u4e00-\u9fa5-]+)/g,
-REMOVE_SPACE: /^\s+|\s+$/g,
-REMOVE_DATE: new RegExp(`(${keywords.DUE_DATE})\\s?\\d{4}-\\d{2}-\\d{2}`),
-REMOVE_INLINE_METADATA: /%%\[\w+::\s*\w+\]%%/,
-REMOVE_CHECKBOX: /^(-|\*)\s+\[(x|X| )\]\s/,
-REMOVE_CHECKBOX_WITH_INDENTATION: /^([ \t]*)?(-|\*)\s+\[(x|X| )\]\s/,
-REMOVE_TODOIST_LINK: /\[link\]\(.*?\)/,
-},
-ALL_TAGS: /#[\w\u4e00-\u9fa5-]+/g,
-TASK_CHECKBOX_CHECKED: /- \[(x|X)\] /,
-TASK_INDENTATION: /^(\s{2,}|\t)(-|\*)\s+\[(x|X| )\]/,
-TAB_INDENTATION: /^(\t+)/,
-TASK_PRIORITY: /\s!!([1-4])\s/,
-BLANK_LINE: /^\s*$/,
-TODOIST_EVENT_DATE: /(\d{4})-(\d{2})-(\d{2})/
+ 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_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})`),
+ PROJECT_NAME: /\[project::\s*(.*?)\]/,
+ TASK_CONTENT: {
+ REMOVE_PRIORITY: /\s!!([1-4])\s/,
+ REMOVE_TAGS: /(^|\s)(#[a-zA-Z\d\u4e00-\u9fa5-]+)/g,
+ REMOVE_SPACE: /^\s+|\s+$/g,
+ REMOVE_DATE: new RegExp(`(${keywords.DUE_DATE})\\s?\\d{4}-\\d{2}-\\d{2}`),
+ REMOVE_INLINE_METADATA: /%%\[\w+::\s*\w+\]%%/,
+ REMOVE_CHECKBOX: /^(-|\*)\s+\[(x|X| )\]\s/,
+ REMOVE_CHECKBOX_WITH_INDENTATION: /^([ \t]*)?(-|\*)\s+\[(x|X| )\]\s/,
+ REMOVE_TickTick_LINK: /\[link\]\(.*?\)/,
+ },
+ ALL_TAGS: /#[\w\u4e00-\u9fa5-]+/g,
+ TASK_CHECKBOX_CHECKED: /- \[(x|X)\] /,
+ TASK_INDENTATION: /^(\s{2,}|\t)(-|\*)\s+\[(x|X| )\]/,
+ TAB_INDENTATION: /^(\t+)/,
+ TASK_PRIORITY: /\s!!([1-4])\s/,
+ BLANK_LINE: /^\s*$/,
+ TickTick_EVENT_DATE: /(\d{4})-(\d{2})-(\d{2})/
};
export class TaskParser {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin
-}
-
-
-
-
-//convert line text to a task object
-async convertTextToTodoistTaskObject(lineText:string,filepath:string,lineNumber?:number,fileContent?:string) {
-//console.log(`linetext is:${lineText}`)
-
-let hasParent = false
-let parentId = null
-let parentTaskObject = null
-//Detect parentID
-let textWithoutIndentation = lineText
-if(this.getTabIndentation(lineText) > 0){
-//console.log(`Indentation is ${this.getTabIndentation(lineText)}`)
-textWithoutIndentation = this.removeTaskIndentation(lineText)
-//console.log(textWithoutIndentation)
-//console.log(`This is a subtask`)
-//Read filepath
-//const fileContent = await this.plugin.fileOperation.readContentFromFilePath(filepath)
-//Traverse line
-const lines = fileContent.split('\n')
-//console.log(lines)
-for (let i = (lineNumber - 1 ); i >= 0; i--) {
-//console.log(`Checking the indentation of line ${i}`)
-const line = lines[i]
-//console.log(line)
-//If it is a blank line, it means there is no parent
-if(this.isLineBlank(line)){
-break
-}
-//If the number of tabs is greater than or equal to the current line, skip
-if (this.getTabIndentation(line) >= this.getTabIndentation(lineText)) {
-//console.log(`Indentation is ${this.getTabIndentation(line)}`)
-continue
-}
-if((this.getTabIndentation(line) < this.getTabIndentation(lineText))){
-//console.log(`Indentation is ${this.getTabIndentation(line)}`)
-if(this.hasTodoistId(line)){
-parentId = this.getTodoistIdFromLineText(line)
-hasParent = true
-//console.log(`parent id is ${parentId}`)
-parentTaskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(parentId)
-break
-}
-else{
-break
-}
-}
-}
-
-
-}
-
-const dueDate = this.getDueDateFromLineText(textWithoutIndentation)
-const labels = this.getAllTagsFromLineText(textWithoutIndentation)
-//console.log(`labels is ${labels}`)
-
-//dataview format metadata
-//const projectName = this.getProjectNameFromLineText(textWithoutIndentation) ?? this.plugin.settings.defaultProjectName
-//const projectId = await this.plugin.cacheOperation.getProjectIdByNameFromCache(projectName)
-//use tag as project name
-
-let projectId = this.plugin.cacheOperation.getDefaultProjectIdForFilepath(filepath as string)
-let projectName = this.plugin.cacheOperation.getProjectNameByIdFromCache(projectId)
-
-if(hasParent){
-projectId = parentTaskObject.projectId
-projectName =this.plugin.cacheOperation.getProjectNameByIdFromCache(projectId)
-}
-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
-}
-projectName = labelName
-//console.log(`project is ${projectName} ${label}`)
-projectId = hasProjectId
-break
-}
-}
-
-
-const content = this.getTaskContentFromLineText(textWithoutIndentation)
-const isCompleted = this.isTaskCheckboxChecked(textWithoutIndentation)
-let description = ""
-const todoist_id = this.getTodoistIdFromLineText(textWithoutIndentation)
-const priority = this.getTaskPriority(textWithoutIndentation)
-if(filepath){
-let url = encodeURI(`obsidian://open?vault=${this.app.vault.getName()}&file=${filepath}`)
-description =`[${filepath}](${url})`;
-}
-
-const todoistTask = {
-projectId: projectId,
-content: content || '',
-parentId: parentId || null,
-dueDate: dueDate || '',
-labels: labels || [],
-description: description,
-isCompleted:isCompleted,
-todoist_id:todoist_id || null,
-hasParent:hasParent,
-priority:priority
-};
-//console.log(`converted task `)
-//console.log(todoistTask)
-return todoistTask;
-}
-
-
-
-
-hasTodoistTag(text:string){
-//console.log("Check whether todoist tag is included")
-//console.log(text)
-return(REGEX.TODOIST_TAG.test(text))
-}
-
-
-
-hasTodoistId(text:string){
-const result = REGEX.TODOIST_ID.test(text)
-//console.log("Check whether todoist id is included")
-//console.log(text)
-return(result)
-}
-
-
-hasDueDate(text:string){
-return(REGEX.DUE_DATE_WITH_EMOJ.test(text))
-}
-
-
-getDueDateFromLineText(text: string) {
-const result = REGEX.DUE_DATE.exec(text);
-return result ? result[1] : null;
-}
-
-
-
-getProjectNameFromLineText(text:string){
-const result = REGEX.PROJECT_NAME.exec(text);
-return result ? result[1] : null;
-}
-
-
-getTodoistIdFromLineText(text:string){
-//console.log(text)
-const result = REGEX.TODOIST_ID_NUM.exec(text);
-//console.log(result)
-return result ? result[1] : null;
-}
-
-getDueDateFromDataview(dataviewTask:object){
-if(!dataviewTask.due){
-return ""
-}
-else{
-const dataviewTaskDue = dataviewTask.due.toString().slice(0, 10)
-return(dataviewTaskDue)
-}
-
-}
-
-
-
-/*
-//convert line task to dataview task object
-async getLineTask(filepath,line){
-//const tasks = this.app.plugins.plugins.dataview.api.pages(`"${filepath}"`).file.tasks
-const tasks = await getAPI(this.app).pages(`"${filepath}"`).file.tasks
-const tasksValues = tasks.values
-//console.log(`dataview filepath is ${filepath}`)
-//console.log(`dataview line is ${line}`)
-//console.log(tasksValues)
-const currentLineTask = tasksValues.find(obj => obj.line === line )
-console.log(currentLineTask)
-return(currentLineTask)
-
-}
-*/
-
-
-
-getTaskContentFromLineText(lineText:string) {
-const TaskContent = lineText.replace(REGEX.TASK_CONTENT.REMOVE_INLINE_METADATA,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_TODOIST_LINK,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_PRIORITY," ") //There must be spaces before and after priority.
-.replace(REGEX.TASK_CONTENT.REMOVE_TAGS,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_DATE,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_CHECKBOX,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_CHECKBOX_WITH_INDENTATION,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_SPACE,"")
-return(TaskContent)
-}
-
-
-//get all tags from task text
-getAllTagsFromLineText(lineText:string){
-let tags = lineText.match(REGEX.ALL_TAGS);
-
-if (tags) {
-// Remove '#' from each tag
-tags = tags.map(tag => tag.replace('#', ''));
-}
-
-return tags;
-}
-
-//get checkbox status
-isTaskCheckboxChecked(lineText:string) {
-return(REGEX.TASK_CHECKBOX_CHECKED.test(lineText))
-}
-
-
-//task content compare
-taskContentCompare(lineTask:Object,todoistTask:Object) {
-const lineTaskContent = lineTask.content
-//console.log(dataviewTaskContent)
-
-const todoistTaskContent = todoistTask.content
-//console.log(todoistTask.content)
-
-//Whether content is modified?
-const contentModified = (lineTaskContent === todoistTaskContent)
-return(contentModified)
-}
-
-
-//tag compare
-taskTagCompare(lineTask:Object,todoistTask:Object) {
-
-
-const lineTaskTags = lineTask.labels
-//console.log(dataviewTaskTags)
-
-const todoistTaskTags = todoistTask.labels
-//console.log(todoistTaskTags)
-
-//Whether content is modified?
-const tagsModified = lineTaskTags.length === todoistTaskTags.length && lineTaskTags.sort().every((val, index) => val === todoistTaskTags.sort()[index]);
-return(tagsModified)
-}
-
-//task status compare
-taskStatusCompare(lineTask:Object,todoistTask:Object) {
-//Whether status is modified?
-const statusModified = (lineTask.isCompleted === todoistTask.isCompleted)
-//console.log(lineTask)
-//console.log(todoistTask)
-return(statusModified)
-}
-
-
-//task due date compare
-async compareTaskDueDate(lineTask: object, todoistTask: object): boolean {
-const lineTaskDue = lineTask.dueDate
-const todoistTaskDue = todoistTask.due ?? "";
-//console.log(dataviewTaskDue)
-//console.log(todoistTaskDue)
-if (lineTaskDue === "" && todoistTaskDue === "") {
-//console.log('No due date')
-return true;
-}
-
-if ((lineTaskDue || todoistTaskDue) === "") {
-console.log(lineTaskDue);
-console.log(todoistTaskDue)
-//console.log('due date has changed')
-return false;
-}
-
-const oldDueDateUTCString = this.localDateStringToUTCDateString(lineTaskDue)
-if (oldDueDateUTCString === todoistTaskDue.date) {
-//console.log('due date consistent')
-return true;
-} else if (lineTaskDue.toString() === "Invalid Date" || todoistTaskDue.toString() === "Invalid Date") {
-console.log('invalid date')
-return false;
-} else {
-//console.log(lineTaskDue);
-//console.log(todoistTaskDue.date)
-return false;
-}
-}
-
-
-//task project id compare
-async taskProjectCompare(lineTask:Object,todoistTask:Object) {
-//project whether to modify
-//console.log(dataviewTaskProjectId)
-//console.log(todoistTask.projectId)
-return(lineTask.projectId === todoistTask.projectId)
-}
-
-
-//Determine whether the task is indented
-isIndentedTask(text:string) {
-return(REGEX.TASK_INDENTATION.test(text));
-}
-
-
-//Determine the number of tab characters
-//console.log(getTabIndentation("\t\t- [x] This is a task with two tabs")); // 2
-//console.log(getTabIndentation(" - [x] This is a task without tabs")); // 0
-getTabIndentation(lineText:string){
-const match = REGEX.TAB_INDENTATION.exec(lineText)
-return match ? match[1].length : 0;
-}
-
-
-// Task priority from 1 (normal) to 4 (urgent).
-getTaskPriority(lineText:string): number{
-const match = REGEX.TASK_PRIORITY.exec(lineText)
-return match ? Number(match[1]) : 1;
-}
-
-
-
-//remove task indentation
-removeTaskIndentation(text) {
-const regex = /^([ \t]*)?- \[(x| )\] /;
-return text.replace(regex, "- [$2] ");
-}
-
-
-//Judge whether line is a blank line
-isLineBlank(lineText:string) {
-return(REGEX.BLANK_LINE.test(lineText))
-}
-
-
-//Insert date in linetext
-insertDueDateBeforeTodoist(text, dueDate) {
-const regex = new RegExp(`(${keywords.TODOIST_TAG})`)
-return text.replace(regex, `📅 ${dueDate} $1`);
-}
-
-//extra date from obsidian event
-// Usage example
-//const str = "2023-03-27T15:59:59.000000Z";
-//const dateStr = ISOStringToLocalDateString(str);
-//console.log(dateStr); // Output 2023-03-27
-ISOStringToLocalDateString(utcTimeString:string) {
-try {
-if(utcTimeString === null){
-return null
-}
-let utcDateString = utcTimeString;
-let dateObj = new Date(utcDateString); // Convert UTC format string to Date object
-let year = dateObj.getFullYear();
-let month = (dateObj.getMonth() + 1).toString().padStart(2, '0');
-let date = dateObj.getDate().toString().padStart(2, '0');
-let localDateString = `${year}-${month}-${date}`;
-return localDateString;
-return(localDateString);
-} catch (error) {
-console.error(`Error extracting date from string '${utcTimeString}': ${error}`);
-return null;
-}
-}
-
-
-//extra date from obsidian event
-// Usage example
-//const str = "2023-03-27T15:59:59.000000Z";
-//const dateStr = ISOStringToLocalDatetimeString(str);
-//console.log(dateStr); // Output Mon Mar 27 2023 23:59:59 GMT+0800 (China Standard Time)
-ISOStringToLocalDatetimeString(utcTimeString:string) {
-try {
-if(utcTimeString === null){
-return null
-}
-let utcDateString = utcTimeString;
-let dateObj = new Date(utcDateString); // Convert UTC format string to Date object
-let result = dateObj.toString();
-return(result);
-} catch (error) {
-console.error(`Error extracting date from string '${utcTimeString}': ${error}`);
-return null;
-}
-}
-
-
-
-//convert date from obsidian event
-// Usage example
-//const str = "2023-03-27";
-//const utcStr = localDateStringToUTCDatetimeString(str);
-//console.log(dateStr); // Output 2023-03-27T00:00:00.000Z
-localDateStringToUTCDatetimeString(localDateString:string) {
-try {
-if(localDateString === null){
-return null
-}
-localDateString = localDateString + "T08:00";
-let localDateObj = new Date(localDateString);
-let ISOString = localDateObj.toISOString()
-return(ISOString);
-} catch (error) {
-console.error(`Error extracting date from string '${localDateString}': ${error}`);
-return null;
-}
-}
-
-//convert date from obsidian event
-// Usage example
-//const str = "2023-03-27";
-//const utcStr = localDateStringToUTCDateString(str);
-//console.log(dateStr); // Output 2023-03-27
-localDateStringToUTCDateString(localDateString:string) {
-try {
-if(localDateString === null){
-return null
-}
-localDateString = localDateString + "T08:00";
-let localDateObj = new Date(localDateString);
-let ISOString = localDateObj.toISOString()
-let utcDateString = ISOString.slice(0,10)
-return(utcDateString);
-} catch (error) {
-console.error(`Error extracting date from string '${localDateString}': ${error}`);
-return null;
-}
-}
-
-isMarkdownTask(str: string): boolean {
-const taskRegex = /^\s*-\s+\[([x ])\]/;
-return taskRegex.test(str);
-}
-
-addTodoistTag(str: string): string {
-return(str +` ${keywords.TODOIST_TAG}`);
-}
-
-getObsidianUrlFromFilepath(filepath:string){
-const url = encodeURI(`obsidian://open?vault=${this.app.vault.getName()}&file=${filepath}`)
-const obsidianUrl =`[${filepath}](${url})`;
-return(obsidianUrl)
-}
-
-
-addTodoistLink(linetext: string,todoistLink:string): string {
-const regex = new RegExp(`${keywords.TODOIST_TAG}`, "g");
-return linetext.replace(regex, todoistLink + ' ' + '$&');
-}
-
-
-//Check whether todoist link is included
-hasTodoistLink(lineText:string){
-return(REGEX.TODOIST_LINK.test(lineText))
-}
+ app:App;
+ plugin: UltimateTickTickSyncForObsidian;
+
+ constructor(app:App, plugin:UltimateTickTickSyncForObsidian) {
+ //super(app,settings);
+ this.app = app;
+ this.plugin = plugin
+ }
+
+
+
+
+ //convert line text to a task object
+ async convertTextToTickTickTaskObject(lineText:string,filepath:string,lineNumber?:number,fileContent?:string) {
+ //console.log(`linetext is:${lineText}`)
+
+ let hasParent = false
+ let parentId = null
+ let parentTaskObject = null
+ //Detect parentID
+ let textWithoutIndentation = lineText
+ if(this.getTabIndentation(lineText) > 0){
+ //console.log(`Indentation is ${this.getTabIndentation(lineText)}`)
+ textWithoutIndentation = this.removeTaskIndentation(lineText)
+ //console.log(textWithoutIndentation)
+ //console.log(`This is a subtask`)
+ //Read filepath
+ //const fileContent = await this.plugin.fileOperation.readContentFromFilePath(filepath)
+ //Traverse line
+ const lines = fileContent.split('\n')
+ //console.log(lines)
+ for (let i = (lineNumber - 1 ); i >= 0; i--) {
+ //console.log(`Checking the indentation of line ${i}`)
+ const line = lines[i]
+ //console.log(line)
+ //If it is a blank line, it means there is no parent
+ if(this.isLineBlank(line)){
+ break
+ }
+ //If the number of tabs is greater than or equal to the current line, skip
+ if (this.getTabIndentation(line) >= this.getTabIndentation(lineText)) {
+ //console.log(`Indentation is ${this.getTabIndentation(line)}`)
+ continue
+ }
+ if((this.getTabIndentation(line) < this.getTabIndentation(lineText))){
+ //console.log(`Indentation is ${this.getTabIndentation(line)}`)
+ if(this.hasTickTickId(line)){
+ parentId = this.getTickTickIdFromLineText(line)
+ hasParent = true
+ //console.log(`parent id is ${parentId}`)
+ parentTaskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(parentId)
+ break
+ }
+ else{
+ break
+ }
+ }
+ }
+
+
+ }
+
+ const dueDate = this.getDueDateFromLineText(textWithoutIndentation)
+ const labels = this.getAllTagsFromLineText(textWithoutIndentation)
+ //console.log(`labels is ${labels}`)
+
+ //dataview format metadata
+ //const projectName = this.getProjectNameFromLineText(textWithoutIndentation) ?? this.plugin.settings.defaultProjectName
+ //const projectId = await this.plugin.cacheOperation.getProjectIdByNameFromCache(projectName)
+ //use tag as project name
+
+ let projectId = this.plugin.cacheOperation.getDefaultProjectIdForFilepath(filepath as string)
+ let projectName = this.plugin.cacheOperation.getProjectNameByIdFromCache(projectId)
+
+ if(hasParent){
+ projectId = parentTaskObject.projectId
+ projectName =this.plugin.cacheOperation.getProjectNameByIdFromCache(projectId)
+ }
+ 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
+ }
+ projectName = labelName
+ //console.log(`project is ${projectName} ${label}`)
+ projectId = hasProjectId
+ break
+ }
+ }
+
+
+ const content = this.getTaskContentFromLineText(textWithoutIndentation)
+ const isCompleted = this.isTaskCheckboxChecked(textWithoutIndentation)
+ let description = ""
+ const TickTick_id = this.getTickTickIdFromLineText(textWithoutIndentation)
+ const priority = this.getTaskPriority(textWithoutIndentation)
+ if(filepath){
+ let url = encodeURI(`obsidian://open?vault=${this.app.vault.getName()}&file=${filepath}`)
+ description =`[${filepath}](${url})`;
+ }
+
+ const TickTickTask = {
+ projectId: projectId,
+ content: content || '',
+ parentId: parentId || null,
+ 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;
+ }
+
+
+
+
+ hasTickTickTag(text:string){
+ //console.log("Check whether TickTick tag is included")
+ //console.log(text)
+ return(REGEX.TickTick_TAG.test(text))
+ }
+
+
+
+ hasTickTickId(text:string){
+ const result = REGEX.TickTick_ID.test(text)
+ //console.log("Check whether TickTick id is included")
+ //console.log(text)
+ return(result)
+ }
+
+
+ hasDueDate(text:string){
+ return(REGEX.DUE_DATE_WITH_EMOJ.test(text))
+ }
+
+
+ getDueDateFromLineText(text: string) {
+ const result = REGEX.DUE_DATE.exec(text);
+ return result ? result[1] : null;
+ }
+
+
+
+ getProjectNameFromLineText(text:string){
+ const result = REGEX.PROJECT_NAME.exec(text);
+ return result ? result[1] : null;
+ }
+
+
+ getTickTickIdFromLineText(text:string){
+ //console.log(text)
+ const result = REGEX.TickTick_ID_NUM.exec(text);
+ //console.log(result)
+ return result ? result[1] : null;
+ }
+
+ getDueDateFromDataview(dataviewTask:object){
+ if(!dataviewTask.due){
+ return ""
+ }
+ else{
+ const dataviewTaskDue = dataviewTask.due.toString().slice(0, 10)
+ return(dataviewTaskDue)
+ }
+
+ }
+
+
+
+ /*
+ //convert line task to dataview task object
+ async getLineTask(filepath,line){
+ //const tasks = this.app.plugins.plugins.dataview.api.pages(`"${filepath}"`).file.tasks
+ const tasks = await getAPI(this.app).pages(`"${filepath}"`).file.tasks
+ const tasksValues = tasks.values
+ //console.log(`dataview filepath is ${filepath}`)
+ //console.log(`dataview line is ${line}`)
+ //console.log(tasksValues)
+ const currentLineTask = tasksValues.find(obj => obj.line === line )
+ console.log(currentLineTask)
+ return(currentLineTask)
+
+ }
+ */
+
+
+
+ getTaskContentFromLineText(lineText:string) {
+ const TaskContent = lineText.replace(REGEX.TASK_CONTENT.REMOVE_INLINE_METADATA,"")
+ .replace(REGEX.TASK_CONTENT.REMOVE_TickTick_LINK,"")
+ .replace(REGEX.TASK_CONTENT.REMOVE_PRIORITY," ") //There must be spaces before and after priority.
+ .replace(REGEX.TASK_CONTENT.REMOVE_TAGS,"")
+ .replace(REGEX.TASK_CONTENT.REMOVE_DATE,"")
+ .replace(REGEX.TASK_CONTENT.REMOVE_CHECKBOX,"")
+ .replace(REGEX.TASK_CONTENT.REMOVE_CHECKBOX_WITH_INDENTATION,"")
+ .replace(REGEX.TASK_CONTENT.REMOVE_SPACE,"")
+ return(TaskContent)
+ }
+
+
+ //get all tags from task text
+ getAllTagsFromLineText(lineText:string){
+ let tags = lineText.match(REGEX.ALL_TAGS);
+
+ if (tags) {
+ // Remove '#' from each tag
+ tags = tags.map(tag => tag.replace('#', ''));
+ }
+
+ return tags;
+ }
+
+ //get checkbox status
+ isTaskCheckboxChecked(lineText:string) {
+ return(REGEX.TASK_CHECKBOX_CHECKED.test(lineText))
+ }
+
+
+ //task content compare
+ taskContentCompare(lineTask:Object,TickTickTask:Object) {
+ const lineTaskContent = lineTask.content
+ //console.log(dataviewTaskContent)
+
+ const TickTickTaskContent = TickTickTask.content
+ //console.log(TickTickTask.content)
+
+ //Whether content is modified?
+ const contentModified = (lineTaskContent === TickTickTaskContent)
+ return(contentModified)
+ }
+
+
+ //tag compare
+ taskTagCompare(lineTask:Object,TickTickTask:Object) {
+
+
+ const lineTaskTags = lineTask.labels
+ //console.log(dataviewTaskTags)
+
+ const TickTickTaskTags = TickTickTask.labels
+ //console.log(TickTickTaskTags)
+
+ //Whether content is modified?
+ const tagsModified = lineTaskTags.length === TickTickTaskTags.length && lineTaskTags.sort().every((val, index) => val === TickTickTaskTags.sort()[index]);
+ return(tagsModified)
+ }
+
+ //task status compare
+ taskStatusCompare(lineTask:Object,TickTickTask:Object) {
+ //Whether status is modified?
+ const statusModified = (lineTask.isCompleted === TickTickTask.isCompleted)
+ //console.log(lineTask)
+ //console.log(TickTickTask)
+ return(statusModified)
+ }
+
+
+ //task due date compare
+ async compareTaskDueDate(lineTask: object, TickTickTask: object): boolean {
+ const lineTaskDue = lineTask.dueDate
+ const TickTickTaskDue = TickTickTask.due ?? "";
+ //console.log(dataviewTaskDue)
+ //console.log(TickTickTaskDue)
+ if (lineTaskDue === "" && TickTickTaskDue === "") {
+ //console.log('No due date')
+ return true;
+ }
+
+ if ((lineTaskDue || TickTickTaskDue) === "") {
+ console.log(lineTaskDue);
+ console.log(TickTickTaskDue)
+ //console.log('due date has changed')
+ return false;
+ }
+
+ const oldDueDateUTCString = this.localDateStringToUTCDateString(lineTaskDue)
+ if (oldDueDateUTCString === TickTickTaskDue.date) {
+ //console.log('due date consistent')
+ return true;
+ } else if (lineTaskDue.toString() === "Invalid Date" || TickTickTaskDue.toString() === "Invalid Date") {
+ console.log('invalid date')
+ return false;
+ } else {
+ //console.log(lineTaskDue);
+ //console.log(TickTickTaskDue.date)
+ return false;
+ }
+ }
+
+
+ //task project id compare
+ async taskProjectCompare(lineTask:Object,TickTickTask:Object) {
+ //project whether to modify
+ //console.log(dataviewTaskProjectId)
+ //console.log(TickTickTask.projectId)
+ return(lineTask.projectId === TickTickTask.projectId)
+ }
+
+
+ //Determine whether the task is indented
+ isIndentedTask(text:string) {
+ return(REGEX.TASK_INDENTATION.test(text));
+ }
+
+
+ //Determine the number of tab characters
+ //console.log(getTabIndentation("\t\t- [x] This is a task with two tabs")); // 2
+ //console.log(getTabIndentation(" - [x] This is a task without tabs")); // 0
+ getTabIndentation(lineText:string){
+ const match = REGEX.TAB_INDENTATION.exec(lineText)
+ return match ? match[1].length : 0;
+ }
+
+
+ // Task priority from 1 (normal) to 4 (urgent).
+ getTaskPriority(lineText:string): number{
+ const match = REGEX.TASK_PRIORITY.exec(lineText)
+ return match ? Number(match[1]) : 1;
+ }
+
+
+
+ //remove task indentation
+ removeTaskIndentation(text) {
+ const regex = /^([ \t]*)?- \[(x| )\] /;
+ return text.replace(regex, "- [$2] ");
+ }
+
+
+ //Judge whether line is a blank line
+ isLineBlank(lineText:string) {
+ return(REGEX.BLANK_LINE.test(lineText))
+ }
+
+
+ //Insert date in linetext
+ insertDueDateBeforeTickTick(text, dueDate) {
+ const regex = new RegExp(`(${keywords.TickTick_TAG})`)
+ return text.replace(regex, `📅 ${dueDate} $1`);
+ }
+
+ //extra date from obsidian event
+ // Usage example
+ //const str = "2023-03-27T15:59:59.000000Z";
+ //const dateStr = ISOStringToLocalDateString(str);
+ //console.log(dateStr); // Output 2023-03-27
+ ISOStringToLocalDateString(utcTimeString:string) {
+ try {
+ if(utcTimeString === null){
+ return null
+ }
+ let utcDateString = utcTimeString;
+ let dateObj = new Date(utcDateString); // Convert UTC format string to Date object
+ let year = dateObj.getFullYear();
+ let month = (dateObj.getMonth() + 1).toString().padStart(2, '0');
+ let date = dateObj.getDate().toString().padStart(2, '0');
+ let localDateString = `${year}-${month}-${date}`;
+ return localDateString;
+ return(localDateString);
+ } catch (error) {
+ console.error(`Error extracting date from string '${utcTimeString}': ${error}`);
+ return null;
+ }
+ }
+
+
+ //extra date from obsidian event
+ // Usage example
+ //const str = "2023-03-27T15:59:59.000000Z";
+ //const dateStr = ISOStringToLocalDatetimeString(str);
+ //console.log(dateStr); // Output Mon Mar 27 2023 23:59:59 GMT+0800 (China Standard Time)
+ ISOStringToLocalDatetimeString(utcTimeString:string) {
+ try {
+ if(utcTimeString === null){
+ return null
+ }
+ let utcDateString = utcTimeString;
+ let dateObj = new Date(utcDateString); // Convert UTC format string to Date object
+ let result = dateObj.toString();
+ return(result);
+ } catch (error) {
+ console.error(`Error extracting date from string '${utcTimeString}': ${error}`);
+ return null;
+ }
+ }
+
+
+
+ //convert date from obsidian event
+ // Usage example
+ //const str = "2023-03-27";
+ //const utcStr = localDateStringToUTCDatetimeString(str);
+ //console.log(dateStr); // Output 2023-03-27T00:00:00.000Z
+ localDateStringToUTCDatetimeString(localDateString:string) {
+ try {
+ if(localDateString === null){
+ return null
+ }
+ localDateString = localDateString + "T08:00";
+ let localDateObj = new Date(localDateString);
+ let ISOString = localDateObj.toISOString()
+ return(ISOString);
+ } catch (error) {
+ console.error(`Error extracting date from string '${localDateString}': ${error}`);
+ return null;
+ }
+ }
+
+ //convert date from obsidian event
+ // Usage example
+ //const str = "2023-03-27";
+ //const utcStr = localDateStringToUTCDateString(str);
+ //console.log(dateStr); // Output 2023-03-27
+ localDateStringToUTCDateString(localDateString:string) {
+ try {
+ if(localDateString === null){
+ return null
+ }
+ localDateString = localDateString + "T08:00";
+ let localDateObj = new Date(localDateString);
+ let ISOString = localDateObj.toISOString()
+ let utcDateString = ISOString.slice(0,10)
+ return(utcDateString);
+ } catch (error) {
+ console.error(`Error extracting date from string '${localDateString}': ${error}`);
+ return null;
+ }
+ }
+
+ isMarkdownTask(str: string): boolean {
+ const taskRegex = /^\s*-\s+\[([x ])\]/;
+ return taskRegex.test(str);
+ }
+
+ addTickTickTag(str: string): string {
+ return(str +` ${keywords.TickTick_TAG}`);
+ }
+
+ getObsidianUrlFromFilepath(filepath:string){
+ const url = encodeURI(`obsidian://open?vault=${this.app.vault.getName()}&file=${filepath}`)
+ const obsidianUrl =`[${filepath}](${url})`;
+ return(obsidianUrl)
+ }
+
+
+ addTickTickLink(linetext: string,TickTickLink:string): string {
+ const regex = new RegExp(`${keywords.TickTick_TAG}`, "g");
+ return linetext.replace(regex, TickTickLink + ' ' + '$&');
+ }
+
+
+ //Check whether TickTick link is included
+ hasTickTickLink(lineText:string){
+ return(REGEX.TickTick_LINK.test(lineText))
+ }
}
diff --git a/src/todoistRestAPI.ts b/src/todoistRestAPI.ts
deleted file mode 100644
index 246ab31..0000000
--- a/src/todoistRestAPI.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import { TodoistApi } from "@doist/todoist-api-typescript"
-import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
-//convert date from obsidian event
-// Usage example
-//const str = "2023-03-27";
-//const utcStr = localDateStringToUTCDatetimeString(str);
-//console.log(dateStr); // Output 2023-03-27T00:00:00.000Z
-function localDateStringToUTCDatetimeString(localDateString:string) {
-try {
-if(localDateString === null){
-return null
-}
-localDateString = localDateString + "T08:00";
-let localDateObj = new Date(localDateString);
-let ISOString = localDateObj.toISOString()
-return(ISOString);
-} catch (error) {
-console.error(`Error extracting date from string '${localDateString}': ${error}`);
-return null;
-}
-}
-
-export class TodoistRestAPI {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-}
-
-
-initializeAPI(){
-const token = this.plugin.settings.todoistAPIToken
-const api = new TodoistApi(token)
-return(api)
-}
-
-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()
-try {
-if(dueDate){
-dueDatetime = localDateStringToUTCDatetimeString(dueDatetime)
-dueDate = null
-}
-const newTask = await api.addTask({
-projectId,
-content,
-parentId,
-dueDate,
-labels,
-description,
-priority
-});
-return newTask;
-} catch (error) {
-throw new Error(`Error adding task: ${error.message}`);
-}
-}
-
-
-//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()
-try {
-const result = await api.getTasks(options);
-return result;
-} catch (error) {
-throw new Error(`Error get active tasks: ${error.message}`);
-}
-}
-
-
-//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');
-}
-try {
-if(updates.dueDate){
-console.log(updates.dueDate)
-updates.dueDatetime = localDateStringToUTCDatetimeString(updates.dueDate)
-updates.dueDate = null
-console.log(updates.dueDatetime)
-}
-const updatedTask = await api.updateTask(taskId, updates);
-return updatedTask;
-} catch (error) {
-throw new Error(`Error updating task: ${error.message}`);
-}
-}
-
-
-
-
-//open a task
-async OpenTask(taskId:string) {
-const api = await this.initializeAPI()
-try {
-
-const isSuccess = await api.reopenTask(taskId);
-console.log(`Task ${taskId} is reopened`)
-return(isSuccess)
-
-} catch (error) {
-console.error('Error open a task:', error);
-return
-}
-}
-
-// Close a task in Todoist API
-async CloseTask(taskId: string): Promise {
-const api = await this.initializeAPI()
-try {
-const isSuccess = await api.closeTask(taskId);
-console.log(`Task ${taskId} is closed`)
-return isSuccess;
-} catch (error) {
-console.error('Error closing task:', error);
-throw error; // Throw an error so that the caller can catch and handle it
-}
-}
-
-
-
-
-// get a task by Id
-async getTaskById(taskId: string) {
-const api = await this.initializeAPI()
-if (!taskId) {
-throw new Error('taskId is required');
-}
-try {
-const task = await api.getTask(taskId);
-return task;
-} catch (error) {
-if (error.response && error.response.status) {
-const statusCode = error.response.status;
-throw new Error(`Error retrieving task. Status code: ${statusCode}`);
-} else {
-throw new Error(`Error retrieving task: ${error.message}`);
-}
-}
-}
-
-//get a task due by id
-async getTaskDueById(taskId: string) {
-const api = await this.initializeAPI()
-if (!taskId) {
-throw new Error('taskId is required');
-}
-try {
-const task = await api.getTask(taskId);
-const due = task.due ?? null
-return due;
-} catch (error) {
-throw new Error(`Error updating task: ${error.message}`);
-}
-}
-
-
-//get all projects
-async GetAllProjects() {
-const api = await this.initializeAPI()
-try {
-const result = await api.getProjects();
-return(result)
-
-} catch (error) {
-console.error('Error get all projects', error);
-return false
-}
-}
-
-
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/todoistSyncAPI.ts b/src/todoistSyncAPI.ts
deleted file mode 100644
index 7a06d91..0000000
--- a/src/todoistSyncAPI.ts
+++ /dev/null
@@ -1,380 +0,0 @@
-import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
-
-
-type Event = {
-id: string;
-object_type: string;
-object_id: string;
-event_type: string;
-event_date: string;
-parent_project_id: string;
-parent_item_id: string | null;
-initiator_id: string | null;
-extra_data: Record;
-};
-
-type FilterOptions = {
-event_type?: string;
-object_type?: string;
-};
-
-export class TodoistSyncAPI {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-}
-
-//backup todoist
-async getAllResources() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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();
-
-return data;
-} catch (error) {
-console.error(error);
-throw new Error('Failed to fetch all resources due to network error');
-}
-}
-
-//backup todoist
-async getUserResource() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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)
-return data;
-} catch (error) {
-console.error(error);
-throw new Error('Failed to fetch user resources due to network error');
-}
-}
-
-
-
-//update user timezone
-async updateUserTimezone() {
-const unixTimestampString: string = Math.floor(Date.now() / 1000).toString();
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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);
-
-if (!response.ok) {
-throw new Error(`Failed to fetch all resources: ${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 user resources due to network error');
-}
-}
-
-//get activity logs
-//result {count:number,events:[]}
-async getAllActivityEvents() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const headers = new Headers({
-Authorization: `Bearer ${accessToken}`
-});
-
-try {
-const response = await fetch('https://api.todoist.com/sync/v9/activity/get', {
-method: 'POST',
-headers,
-body: JSON.stringify({})
-});
-
-if (!response.ok) {
-throw new Error(`API returned error status: ${response.status}`);
-}
-
-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)
-
-}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)
-
-);
-};
-
-//get completed items activity
-//result {count:number,events:[]}
-async getCompletedItemsActivity() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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);
-
-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');
-}
-}
-
-
-
-//get uncompleted items activity
-//result {count:number,events:[]}
-async getUncompletedItemsActivity() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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.todoistAPIToken
-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.todoistAPIToken
-const url = 'https://api.todoist.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.todoistAPIToken
-const url = 'https://api.todoist.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');
-}
-}
-
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/translate/main.ts b/translate/main.ts
deleted file mode 100644
index 039cde7..0000000
--- a/translate/main.ts
+++ /dev/null
@@ -1,643 +0,0 @@
-import { MarkdownView, Notice, Plugin ,Editor, WorkspaceLeaf} from 'obsidian';
-
-
-//settings
-import { UltimateTodoistSyncSettings,DEFAULT_SETTINGS,UltimateTodoistSyncSettingTab } from './src/settings';
-//todoist api
-import { TodoistRestAPI } from './src/todoistRestAPI';
-import { TodoistSyncAPI } from './src/todoistSyncAPI';
-//task parser
-import { TaskParser } from './src/taskParser';
-//cache task read and write
-import { CacheOperation } from './src/cacheOperation';
-//file operation
-import { FileOperation } from './src/fileOperation';
-
-//sync module
-import { TodoistSync } from './src/syncModule';
-
-
-//import modal
-import { SetDefalutProjectInTheFilepathModal } from 'src/modal';
-
-export default class UltimateTodoistSyncForObsidian extends Plugin {
-settings: UltimateTodoistSyncSettings;
-todoistRestAPI: TodoistRestAPI | undefined;
-todoistSyncAPI: TodoistSyncAPI | undefined;
-taskParser: TaskParser | undefined;
-cacheOperation: CacheOperation | undefined;
-fileOperation: FileOperation | undefined;
-todoistSync: TodoistSync | undefined;
-lastLines: Map;
-statusBar;
-syncLock: Boolean;
-
-async onload() {
-
-const isSettingsLoaded = await this.loadSettings();
-
-if(!isSettingsLoaded){
-new Notice('Settings failed to load. Please reload the ultimate todoist sync plugin.');
-return;
-}
-// This adds a settings tab so the user can configure various aspects of the plugin
-this.addSettingTab(new UltimateTodoistSyncSettingTab(this.app, this));
-if (!this.settings.todoistAPIToken) {
-new Notice('Please enter your Todoist API.');
-//return
-}else{
-await this.initializePlugin();
-}
-
-//lastLine object {path:line} is saved in lastLines map
-this.lastLines = new Map();
-
-
-
-
-
-
-
-
-//Key event monitoring, judging line breaks and deletions
-this.registerDomEvent(document, 'keyup', async (evt: KeyboardEvent) =>{
-if(!this.settings.apiInitialized){
-return
-}
-//console.log(`key pressed`)
-
-//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`))
-return
-}
-const view = this.app.workspace.getActiveViewOfType(MarkdownView);
-const editor = view?.app.workspace.activeEditor?.editor
-
-if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'ArrowLeft' || evt.key === 'ArrowRight' || evt.key = == 'PageUp' || evt.key === 'PageDown') {
-//console.log(`${evt.key} arrow key is released`);
-if(!( this.checkModuleClass())){
-return
-}
-this.lineNumberCheck()
-}
-if(evt.key === "Delete" || evt.key === "Backspace"){
-try{
-//console.log(`${evt.key} key is released`);
-if(!( this.checkModuleClass())){
-return
-}
-if (!await this.checkAndHandleSyncLock()) return;
-await this.todoistSync.deletedTaskCheck();
-this.syncLock = false;
-this.saveSettings()
-}catch(error){
-console.error(`An error occurred while deleting tasks: ${error}`);
-this.syncLock = false
-}
-
-}
-});
-
-// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
-// Using this function will automatically remove the event listener when this plugin is disabled.
-this.registerDomEvent(document, 'click', async (evt: MouseEvent) => {
-if(!this.settings.apiInitialized){
-return
-}
-//console.log('click', evt);
-if (this.app.workspace.activeEditor?.editor?.hasFocus()) {
-//console.log('Click event: editor is focused');
-const view = this.app.workspace.getActiveViewOfType(MarkdownView)
-const editor = this.app.workspace.activeEditor?.editor
-this.lineNumberCheck()
-}
-else{
-//
-}
-
-const target = evt.target as HTMLInputElement;
-
-if (target.type === "checkbox") {
-if(!(this.checkModuleClass())){
-return
-}
-this.checkboxEventhandle(evt)
-//this.todoistSync.fullTextModifiedTaskCheck()
-
-}
-
-});
-
-
-
-//hook editor-change event, if the current line contains #todoist, it means there is a new task
-this.registerEvent(this.app.workspace.on('editor-change',async (editor,view:MarkdownView)=>{
-try{
-if(!this.settings.apiInitialized){
-return
-}
-
-this.lineNumberCheck()
-if(!(this.checkModuleClass())){
-return
-}
-if(this.settings.enableFullVaultSync){
-return
-}
-if (!await this.checkAndHandleSyncLock()) return;
-await this.todoistSync.lineContentNewTaskCheck(editor,view)
-this.syncLock = false
-this.saveSettings()
-
-}catch(error){
-console.error(`An error occurred while check new task in line: ${error.message}`);
-this.syncLock = false
-}
-
-}))
-
-
-
-/* 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.
-this.registerEvent(this.app.metadataCache.on('deleted', async(file,prevCache) => {
-try{
-if(!this.settings.apiInitialized){
-return
-}
-//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.todoistTasks === undefined){
-console.log('There is no task in the deleted files.')
-return
-}
-//Determine whether todoistTasks is null
-console.log(frontMatter.todoistTasks)
-if(!( this.checkModuleClass())){
-return
-}
-if (!await this.checkAndHandleSyncLock()) return;
-await this.todoistSync.deleteTasksByIds(frontMatter.todoistTasks)
-this.syncLock = false
-this.saveSettings()
-}catch(error){
-console.error(`An error occurred while deleting task in the file: ${error}`);
-this.syncLock = false
-}
-
-
-
-}));
-*/
-
-
-//Listen to the rename event and update the path in task data
-this.registerEvent(this.app.vault.on('rename', async (file,oldpath) => {
-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.todoistTasks === undefined){
-//console.log('There is no task in the deleted file')
-return
-}
-if(!(this.checkModuleClass())){
-return
-}
-await this.cacheOperation.updateRenamedFilePath(oldpath,file.path)
-this.saveSettings()
-
-//update task description
-if (!await this.checkAndHandleSyncLock()) return;
-try {
-await this.todoistSync.updateTaskDescription(file.path)
-} catch(error) {
-console.error('An error occurred in updateTaskDescription:', error);
-}
-this.syncLock = false;
-
-}));
-
-
-//Listen for file modified events and execute fullTextNewTaskCheck
-this.registerEvent(this.app.vault.on('modify', async (file) => {
-try {
-if(!this.settings.apiInitialized){
-return
-}
-const filepath = file.path
-console.log(`${filepath} is modified`)
-
-//get current view
-
-const activateFile = this.app.workspace.getActiveFile()
-
-console.log(activateFile?.path)
-
-//To avoid conflicts, Do not check files being edited
-if(activateFile?.path == filepath){
-return
-}
-
-if (!await this.checkAndHandleSyncLock()) return;
-
-await this.todoistSync.fullTextNewTaskCheck(filepath)
-this.syncLock = false;
-} catch(error) {
-console.error(`An error occurred while modifying the file: ${error.message}`);
-this.syncLock = false
-// You can add further error handling logic here. For example, you may want to
-// revert certain operations, or alert the user about the error.
-}
-}));
-
-this.registerInterval(window.setInterval(async () => await this.scheduledSynchronization(), this.settings.automaticSynchronizationInterval * 1000));
-
-this.app.workspace.on('active-leaf-change',(leaf)=>{
-this.setStatusBarText()
-})
-
-
-// set default project for todoist task in the current file
-// This adds an editor command that can perform some operation on the current editor instance
-this.addCommand({
-id: 'set-default-project-for-todoist-task-in-the-current-file',
-name: 'Set default project for todoist task in the current file',
-editorCallback: (editor: Editor, view: MarkdownView) => {
-if(!view){
-return
-}
-const filepath = view.file.path
-new SetDefalutProjectInTheFilepathModal(this.app,this,filepath)
-
-}
-});
-
-//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();
-
-
-}
-
-
-async onunload() {
-console.log(`Ultimate Todoist Sync for Obsidian id unloaded!`)
-await this.saveSettings()
-
-}
-
-async loadSettings() {
-try {
-const data = await this.loadData();
-this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
-return true; // Returning true indicates that the settings are loaded successfully
-} catch (error) {
-console.error('Failed to load data:', error);
-return false; // Returning false indicates that the setting loading failed
-}
-}
-
-async saveSettings() {
-try {
-// Verify that the setting exists and is not empty
-if (this.settings && Object.keys(this.settings).length > 0) {
-await this.saveData(this.settings);
-} else {
-console.error('Settings are empty or invalid, not saving to avoid data loss.');
-}
-} catch (error) {
-//Print or handle errors
-console.error('Error saving settings:', error);
-}
-}
-
-async modifyTodoistAPI(api:string){
-await this.initializePlugin()
-}
-
-// return true of false
-async initializePlugin(){
-
-//initialize todoist restapi
-this.todoistRestAPI = new TodoistRestAPI(this.app, this)
-
-//initialize data read and write object
-this.cacheOperation = new CacheOperation(this.app, this)
-const isProjectsSaved = await this.cacheOperation.saveProjectsToCache()
-
-
-
-if(!isProjectsSaved){
-this.todoistRestAPI = undefined
-this.todoistSyncAPI = undefined
-this.taskParser = undefined
-this.taskParser = undefined
-this.cacheOperation = undefined
-this.fileOperation = undefined
-this.todoistSync = undefined
-new Notice(`Ultimate Todoist Sync plugin initialization failed, please check the todoist api`)
-return;
-}
-
-if(!this.settings.initialized){
-
-//Create a backup folder to back up todoist data
-try{
-//Start the plug-in for the first time and back up todoist data
-this.taskParser = new TaskParser(this.app, this)
-
-//initialize file operation
-this.fileOperation = new FileOperation(this.app,this)
-
-//initialize todoisy sync api
-this.todoistSyncAPI = new TodoistSyncAPI(this.app,this)
-
-//initialize todoist sync module
-this.todoistSync = new TodoistSync(this.app,this)
-
-//Back up all data before each startup
-this.todoistSync.backupTodoistAllResources()
-
-}catch(error){
-console.log(`error creating user data folder: ${error}`)
-new Notice(`error creating user data folder`)
-return;
-}
-
-
-//Initialize settings
-this.settings.initialized = true
-this.saveSettings()
-new Notice(`Ultimate Todoist Sync initialization successful. Todoist data has been backed up.`)
-
-}
-
-
-this.initializeModuleClass()
-
-
-//get user plan resources
-//const rsp = await this.todoistSyncAPI.getUserResource()
-this.settings.apiInitialized = true
-this.syncLock = false
-new Notice(`Ultimate Todoist Sync loaded successfully.`)
-return true
-
-
-
-}
-
-async initializeModuleClass(){
-
-//initialize todoist restapi
-this.todoistRestAPI = new TodoistRestAPI(this.app,this)
-
-//initialize data read and write object
-this.cacheOperation = new CacheOperation(this.app,this)
-this.taskParser = new TaskParser(this.app,this)
-
-//initialize file operation
-this.fileOperation = new FileOperation(this.app,this)
-
-//initialize todoisy sync api
-this.todoistSyncAPI = new TodoistSyncAPI(this.app,this)
-
-//initialize todoist sync module
-this.todoistSync = new TodoistSync(this.app,this)
-
-
-}
-
-async lineNumberCheck(){
-const view = this.app.workspace.getActiveViewOfType(MarkdownView)
-if(view){
-const cursor = view.app.workspace.getActiveViewOfType(MarkdownView)?.editor.getCursor()
-const line = cursor?.line
-//const lineText = view.editor.getLine(line)
-const fileContent = view.data
-
-//console.log(line)
-//const fileName = view.file?.name
-const fileName = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.name
-const filepath = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.path
-if (typeof this.lastLines === 'undefined' || typeof this.lastLines.get(fileName as string) === 'undefined'){
-this.lastLines.set(fileName as string, line as number);
-return
-}
-
-//console.log(`filename is ${fileName}`)
-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}`);
-}
-
-
-//Perform the operation you want
-const lastLineText = view.editor.getLine(lastLine as number)
-//console.log(lastLineText)
-if(!( this.checkModuleClass())){
-return
-}
-this.lastLines.set(fileName as string, line as number);
-try{
-if (!await this.checkAndHandleSyncLock()) return;
-await this.todoistSync.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
-}
-
-
-
-}
-else {
-//console.log('Line not changed');
-}
-
-}
-
-
-
-
-}
-
-async checkboxEventhandle(evt:MouseEvent){
-if(!( this.checkModuleClass())){
-return
-}
-const target = evt.target as HTMLInputElement;
-
-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 = /\[todoist_id:: (\d+)\]/; // Matches a string in the format [todoist_id:: number]
-const match = taskElement.textContent?.match(regex) || false;
-if (match) {
-const taskId = match[1];
-//console.log(taskId)
-//const view = this.app.workspace.getActiveViewOfType(MarkdownView);
-if (target.checked) {
-this.todoistSync.closeTask(taskId);
-} else {
-this.todoistSync.repoenTask(taskId);
-}
-} else {
-//console.log('todoist_id not found');
-//Start full-text search and check status updates
-try{
-if (!await this.checkAndHandleSyncLock()) return;
-await this.todoistSync.fullTextModifiedTaskCheck()
-this.syncLock = false;
-}catch(error){
-console.error(`An error occurred while check modified tasks in the file: ${error}`);
-this.syncLock = false;
-}
-
-}
-}
-
-//return true
-checkModuleClass(){
-if(this.settings.apiInitialized === true){
-if(this.todoistRestAPI === undefined || this.todoistSyncAPI === undefined ||this.cacheOperation === undefined || this.fileOperation === undefined ||this.todoistSync === undefined ||this.taskParser === undefined){
-this.initializeModuleClass()
-}
-return true
-}
-else{
-new Notice(`Please enter the correct Todoist API token"`)
-return(false)
-}
-
-
-}
-
-async setStatusBarText(){
-if(!( this.checkModuleClass())){
-return
-}
-const view = this.app.workspace.getActiveViewOfType(MarkdownView)
-if(!view){
-this.statusBar.setText('');
-}
-else{
-const filepath = this.app.workspace.getActiveViewOfType(MarkdownView)?.file.path
-if(filepath === undefined){
-console.log(`file path undefined`)
-return
-}
-const defaultProjectName = await this.cacheOperation.getDefaultProjectNameForFilepath(filepath as string)
-if(defaultProjectName === undefined){
-console.log(`projectName undefined`)
-return
-}
-this.statusBar.setText(defaultProjectName)
-}
-
-}
-
-async scheduledSynchronization() {
-if (!(this.checkModuleClass())) {
-return;
-}
-console.log("Todoist scheduled synchronization task started at", new Date().toLocaleString());
-try {
-if (!await this.checkAndHandleSyncLock()) return;
-try {
-await this.todoistSync.syncTodoistToObsidian();
-} catch(error) {
-console.error('An error occurred in syncTodoistToObsidian:', error);
-}
-this.syncLock = false;
-try {
-await this.saveSettings();
-} catch(error) {
-console.error('An error occurred in saveSettings:', error);
-}
-
-// Sleep for 5 seconds
-await new Promise(resolve => setTimeout(resolve, 5000));
-
-const filesToSync = this.settings.fileMetadata;
-if(this.settings.debugMode){
-console.log(filesToSync)
-}
-
-for (let fileKey in filesToSync) {
-if(this.settings.debugMode){
-console.log(fileKey)
-}
-
-if (!await this.checkAndHandleSyncLock()) return;
-try {
-await this.todoistSync.fullTextNewTaskCheck(fileKey);
-} catch(error) {
-console.error('An error occurred in fullTextNewTaskCheck:', error);
-}
-this.syncLock = false;
-
-if (!await this.checkAndHandleSyncLock()) return;
-try {
-await this.todoistSync.deletedTaskCheck(fileKey);
-} catch(error) {
-console.error('An error occurred in deletedTaskCheck:', error);
-}
-this.syncLock = false;
-
-if (!await this.checkAndHandleSyncLock()) return;
-try {
-await this.todoistSync.fullTextModifiedTaskCheck(fileKey);
-} catch(error) {
-console.error('An error occurred in fullTextModifiedTaskCheck:', error);
-}
-this.syncLock = false;
-}
-
-} catch (error) {
-console.error('An error occurred:', error);
-new Notice('An error occurred:', error);
-this.syncLock = false;
-}
-console.log("Todoist scheduled synchronization task completed at", new Date().toLocaleString());
-}
-
-async checkSyncLock() {
-let checkCount = 0;
-while (this.syncLock == true && checkCount < 10) {
-await new Promise(resolve => setTimeout(resolve, 1000));
-checkCount++;
-}
-if (this.syncLock == true) {
-return false;
-}
-return true;
-}
-
-async checkAndHandleSyncLock() {
-if (this.syncLock) {
-console.log('sync locked.');
-const isSyncLockChecked = await this.checkSyncLock();
-if (!isSyncLockChecked) {
-return false;
-}
-console.log('sync unlocked.')
-}
-this.syncLock = true;
-return true;
-}
-
-}
-
-
-
-
diff --git a/translate/org/main.txt b/translate/org/main.txt
deleted file mode 100644
index c6536cc..0000000
--- a/translate/org/main.txt
+++ /dev/null
@@ -1,643 +0,0 @@
-import { MarkdownView, Notice, Plugin ,Editor, WorkspaceLeaf} from 'obsidian';
-
-
-//settings
-import { UltimateTodoistSyncSettings,DEFAULT_SETTINGS,UltimateTodoistSyncSettingTab } from './src/settings';
-//todoist api
-import { TodoistRestAPI } from './src/todoistRestAPI';
-import { TodoistSyncAPI } from './src/todoistSyncAPI';
-//task parser
-import { TaskParser } from './src/taskParser';
-//cache task read and write
-import { CacheOperation } from './src/cacheOperation';
-//file operation
-import { FileOperation } from './src/fileOperation';
-
-//sync module
-import { TodoistSync } from './src/syncModule';
-
-
-//import modal
-import { SetDefalutProjectInTheFilepathModal } from 'src/modal';
-
-export default class UltimateTodoistSyncForObsidian extends Plugin {
- settings: UltimateTodoistSyncSettings;
- todoistRestAPI: TodoistRestAPI | undefined;
- todoistSyncAPI: TodoistSyncAPI | undefined;
- taskParser: TaskParser | undefined;
- cacheOperation: CacheOperation | undefined;
- fileOperation: FileOperation | undefined;
- todoistSync: TodoistSync | undefined;
- lastLines: Map;
- statusBar;
- syncLock: Boolean;
-
- async onload() {
-
- const isSettingsLoaded = await this.loadSettings();
-
- if(!isSettingsLoaded){
- new Notice('Settings failed to load.Please reload the ultimate todoist sync plugin.');
- return;
- }
- // This adds a settings tab so the user can configure various aspects of the plugin
- this.addSettingTab(new UltimateTodoistSyncSettingTab(this.app, this));
- if (!this.settings.todoistAPIToken) {
- new Notice('Please enter your Todoist API.');
- //return
- }else{
- await this.initializePlugin();
- }
-
- //lastLine 对象 {path:line}保存在lastLines map中
- this.lastLines = new Map();
-
-
-
-
-
-
-
-
- //key 事件监听,判断换行和删除
- this.registerDomEvent(document, 'keyup', async (evt: KeyboardEvent) =>{
- if(!this.settings.apiInitialized){
- return
- }
- //console.log(`key pressed`)
-
- //判断点击事件发生的区域,如果不在编辑器中,return
- if (!(this.app.workspace.activeEditor?.editor?.hasFocus())) {
- (console.log(`editor is not focused`))
- return
- }
- const view = this.app.workspace.getActiveViewOfType(MarkdownView);
- const editor = view?.app.workspace.activeEditor?.editor
-
- if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'ArrowLeft' || evt.key === 'ArrowRight' ||evt.key === 'PageUp' || evt.key === 'PageDown') {
- //console.log(`${evt.key} arrow key is released`);
- if(!( this.checkModuleClass())){
- return
- }
- this.lineNumberCheck()
- }
- if(evt.key === "Delete" || evt.key === "Backspace"){
- try{
- //console.log(`${evt.key} key is released`);
- if(!( this.checkModuleClass())){
- return
- }
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.deletedTaskCheck();
- this.syncLock = false;
- this.saveSettings()
- }catch(error){
- console.error(`An error occurred while deleting tasks: ${error}`);
- this.syncLock = false
- }
-
- }
- });
-
- // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
- // Using this function will automatically remove the event listener when this plugin is disabled.
- this.registerDomEvent(document, 'click', async (evt: MouseEvent) => {
- if(!this.settings.apiInitialized){
- return
- }
- //console.log('click', evt);
- if (this.app.workspace.activeEditor?.editor?.hasFocus()) {
- //console.log('Click event: editor is focused');
- const view = this.app.workspace.getActiveViewOfType(MarkdownView)
- const editor = this.app.workspace.activeEditor?.editor
- this.lineNumberCheck()
- }
- else{
- //
- }
-
- const target = evt.target as HTMLInputElement;
-
- if (target.type === "checkbox") {
- if(!(this.checkModuleClass())){
- return
- }
- this.checkboxEventhandle(evt)
- //this.todoistSync.fullTextModifiedTaskCheck()
-
- }
-
- });
-
-
-
- //hook editor-change 事件,如果当前line包含 #todoist,说明有new task
- this.registerEvent(this.app.workspace.on('editor-change',async (editor,view:MarkdownView)=>{
- try{
- if(!this.settings.apiInitialized){
- return
- }
-
- this.lineNumberCheck()
- if(!(this.checkModuleClass())){
- return
- }
- if(this.settings.enableFullVaultSync){
- return
- }
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.lineContentNewTaskCheck(editor,view)
- this.syncLock = false
- this.saveSettings()
-
- }catch(error){
- console.error(`An error occurred while check new task in line: ${error.message}`);
- this.syncLock = false
- }
-
- }))
-
-
-
-/* 使用其他文件管理器移动,obsidian触发了删除事件,删除了所有的任务
- //监听删除事件,当文件被删除后,读取frontMatter中的tasklist,批量删除
- this.registerEvent(this.app.metadataCache.on('deleted', async(file,prevCache) => {
- try{
- if(!this.settings.apiInitialized){
- return
- }
- //console.log('a new file has modified')
- console.log(`file deleted`)
- //读取frontMatter
- const frontMatter = await this.cacheOperation.getFileMetadata(file.path)
- if(frontMatter === null || frontMatter.todoistTasks === undefined){
- console.log('There is no task in the deleted files.')
- return
- }
- //判断todoistTasks是否为null
- console.log(frontMatter.todoistTasks)
- if(!( this.checkModuleClass())){
- return
- }
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.deleteTasksByIds(frontMatter.todoistTasks)
- this.syncLock = false
- this.saveSettings()
- }catch(error){
- console.error(`An error occurred while deleting task in the file: ${error}`);
- this.syncLock = false
- }
-
-
-
- }));
-*/
-
-
- //监听 rename 事件,更新 task data 中的 path
- this.registerEvent(this.app.vault.on('rename', async (file,oldpath) => {
- if(!this.settings.apiInitialized){
- return
- }
- console.log(`${oldpath} is renamed`)
- //读取frontMatter
- //const frontMatter = await this.fileOperation.getFrontMatter(file)
- const frontMatter = await this.cacheOperation.getFileMetadata(oldpath)
- console.log(frontMatter)
- if(frontMatter === null || frontMatter.todoistTasks === undefined){
- //console.log('删除的文件中没有task')
- return
- }
- if(!(this.checkModuleClass())){
- return
- }
- await this.cacheOperation.updateRenamedFilePath(oldpath,file.path)
- this.saveSettings()
-
- //update task description
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.updateTaskDescription(file.path)
- } catch(error) {
- console.error('An error occurred in updateTaskDescription:', error);
- }
- this.syncLock = false;
-
- }));
-
-
- //Listen for file modified events and execute fullTextNewTaskCheck
- this.registerEvent(this.app.vault.on('modify', async (file) => {
- try {
- if(!this.settings.apiInitialized){
- return
- }
- const filepath = file.path
- console.log(`${filepath} is modified`)
-
- //get current view
-
- const activateFile = this.app.workspace.getActiveFile()
-
- console.log(activateFile?.path)
-
- //To avoid conflicts, Do not check files being edited
- if(activateFile?.path == filepath){
- return
- }
-
- if (!await this.checkAndHandleSyncLock()) return;
-
- await this.todoistSync.fullTextNewTaskCheck(filepath)
- this.syncLock = false;
- } catch(error) {
- console.error(`An error occurred while modifying the file: ${error.message}`);
- this.syncLock = false
- // You can add further error handling logic here. For example, you may want to
- // revert certain operations, or alert the user about the error.
- }
- }));
-
- this.registerInterval(window.setInterval(async () => await this.scheduledSynchronization(), this.settings.automaticSynchronizationInterval * 1000));
-
- this.app.workspace.on('active-leaf-change',(leaf)=>{
- this.setStatusBarText()
- })
-
-
- // set default project for todoist task in the current file
- // This adds an editor command that can perform some operation on the current editor instance
- this.addCommand({
- id: 'set-default-project-for-todoist-task-in-the-current-file',
- name: 'Set default project for todoist task in the current file',
- editorCallback: (editor: Editor, view: MarkdownView) => {
- if(!view){
- return
- }
- const filepath = view.file.path
- new SetDefalutProjectInTheFilepathModal(this.app,this,filepath)
-
- }
- });
-
- //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();
-
-
- }
-
-
- async onunload() {
- console.log(`Ultimate Todoist Sync for Obsidian id unloaded!`)
- await this.saveSettings()
-
- }
-
- async loadSettings() {
- try {
- const data = await this.loadData();
- this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
- return true; // 返回 true 表示设置加载成功
- } catch (error) {
- console.error('Failed to load data:', error);
- return false; // 返回 false 表示设置加载失败
- }
- }
-
- async saveSettings() {
- try {
- // 验证设置是否存在且不为空
- if (this.settings && Object.keys(this.settings).length > 0) {
- await this.saveData(this.settings);
- } else {
- console.error('Settings are empty or invalid, not saving to avoid data loss.');
- }
- } catch (error) {
- // 打印或处理错误
- console.error('Error saving settings:', error);
- }
- }
-
- async modifyTodoistAPI(api:string){
- await this.initializePlugin()
- }
-
- // return true of false
- async initializePlugin(){
-
- //initialize todoist restapi
- this.todoistRestAPI = new TodoistRestAPI(this.app, this)
-
- //initialize data read and write object
- this.cacheOperation = new CacheOperation(this.app, this)
- const isProjectsSaved = await this.cacheOperation.saveProjectsToCache()
-
-
-
- if(!isProjectsSaved){
- this.todoistRestAPI = undefined
- this.todoistSyncAPI = undefined
- this.taskParser = undefined
- this.taskParser = undefined
- this.cacheOperation = undefined
- this.fileOperation = undefined
- this.todoistSync = undefined
- new Notice(`Ultimate Todoist Sync plugin initialization failed, please check the todoist api`)
- return;
- }
-
- if(!this.settings.initialized){
-
- //创建备份文件夹备份todoist 数据
- try{
- //第一次启动插件,备份todoist 数据
- this.taskParser = new TaskParser(this.app, this)
-
- //initialize file operation
- this.fileOperation = new FileOperation(this.app,this)
-
- //initialize todoisy sync api
- this.todoistSyncAPI = new TodoistSyncAPI(this.app,this)
-
- //initialize todoist sync module
- this.todoistSync = new TodoistSync(this.app,this)
-
- //每次启动前备份所有数据
- this.todoistSync.backupTodoistAllResources()
-
- }catch(error){
- console.log(`error creating user data folder: ${error}`)
- new Notice(`error creating user data folder`)
- return;
- }
-
-
- //初始化settings
- this.settings.initialized = true
- this.saveSettings()
- new Notice(`Ultimate Todoist Sync initialization successful. Todoist data has been backed up.`)
-
- }
-
-
- this.initializeModuleClass()
-
-
- //get user plan resources
- //const rsp = await this.todoistSyncAPI.getUserResource()
- this.settings.apiInitialized = true
- this.syncLock = false
- new Notice(`Ultimate Todoist Sync loaded successfully.`)
- return true
-
-
-
- }
-
- async initializeModuleClass(){
-
- //initialize todoist restapi
- this.todoistRestAPI = new TodoistRestAPI(this.app,this)
-
- //initialize data read and write object
- this.cacheOperation = new CacheOperation(this.app,this)
- this.taskParser = new TaskParser(this.app,this)
-
- //initialize file operation
- this.fileOperation = new FileOperation(this.app,this)
-
- //initialize todoisy sync api
- this.todoistSyncAPI = new TodoistSyncAPI(this.app,this)
-
- //initialize todoist sync module
- this.todoistSync = new TodoistSync(this.app,this)
-
-
- }
-
- async lineNumberCheck(){
- const view = this.app.workspace.getActiveViewOfType(MarkdownView)
- if(view){
- const cursor = view.app.workspace.getActiveViewOfType(MarkdownView)?.editor.getCursor()
- const line = cursor?.line
- //const lineText = view.editor.getLine(line)
- const fileContent = view.data
-
- //console.log(line)
- //const fileName = view.file?.name
- const fileName = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.name
- const filepath = view.app.workspace.getActiveViewOfType(MarkdownView)?.app.workspace.activeEditor?.file?.path
- if (typeof this.lastLines === 'undefined' || typeof this.lastLines.get(fileName as string) === 'undefined'){
- this.lastLines.set(fileName as string, line as number);
- return
- }
-
- //console.log(`filename is ${fileName}`)
- 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}`);
- }
-
-
- // 执行你想要的操作
- const lastLineText = view.editor.getLine(lastLine as number)
- //console.log(lastLineText)
- if(!( this.checkModuleClass())){
- return
- }
- this.lastLines.set(fileName as string, line as number);
- try{
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.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
- }
-
-
-
- }
- else {
- //console.log('Line not changed');
- }
-
- }
-
-
-
-
- }
-
- async checkboxEventhandle(evt:MouseEvent){
- if(!( this.checkModuleClass())){
- return
- }
- const target = evt.target as HTMLInputElement;
-
- const taskElement = target.closest("div"); //使用 evt.target.closest() 方法寻找特定的父元素,而不是直接访问事件路径中的特定索引
- //console.log(taskElement)
- if (!taskElement) return;
- const regex = /\[todoist_id:: (\d+)\]/; // 匹配 [todoist_id:: 数字] 格式的字符串
- const match = taskElement.textContent?.match(regex) || false;
- if (match) {
- const taskId = match[1];
- //console.log(taskId)
- //const view = this.app.workspace.getActiveViewOfType(MarkdownView);
- if (target.checked) {
- this.todoistSync.closeTask(taskId);
- } else {
- this.todoistSync.repoenTask(taskId);
- }
- } else {
- //console.log('未找到 todoist_id');
- //开始全文搜索,检查status更新
- try{
- if (!await this.checkAndHandleSyncLock()) return;
- await this.todoistSync.fullTextModifiedTaskCheck()
- this.syncLock = false;
- }catch(error){
- console.error(`An error occurred while check modified tasks in the file: ${error}`);
- this.syncLock = false;
- }
-
- }
- }
-
- //return true
- checkModuleClass(){
- if(this.settings.apiInitialized === true){
- if(this.todoistRestAPI === undefined || this.todoistSyncAPI === undefined ||this.cacheOperation === undefined || this.fileOperation === undefined ||this.todoistSync === undefined ||this.taskParser === undefined){
- this.initializeModuleClass()
- }
- return true
- }
- else{
- new Notice(`Please enter the correct Todoist API token"`)
- return(false)
- }
-
-
- }
-
- async setStatusBarText(){
- if(!( this.checkModuleClass())){
- return
- }
- const view = this.app.workspace.getActiveViewOfType(MarkdownView)
- if(!view){
- this.statusBar.setText('');
- }
- else{
- const filepath = this.app.workspace.getActiveViewOfType(MarkdownView)?.file.path
- if(filepath === undefined){
- console.log(`file path undefined`)
- return
- }
- const defaultProjectName = await this.cacheOperation.getDefaultProjectNameForFilepath(filepath as string)
- if(defaultProjectName === undefined){
- console.log(`projectName undefined`)
- return
- }
- this.statusBar.setText(defaultProjectName)
- }
-
- }
-
- async scheduledSynchronization() {
- if (!(this.checkModuleClass())) {
- return;
- }
- console.log("Todoist scheduled synchronization task started at", new Date().toLocaleString());
- try {
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.syncTodoistToObsidian();
- } catch(error) {
- console.error('An error occurred in syncTodoistToObsidian:', error);
- }
- this.syncLock = false;
- try {
- await this.saveSettings();
- } catch(error) {
- console.error('An error occurred in saveSettings:', error);
- }
-
- // Sleep for 5 seconds
- await new Promise(resolve => setTimeout(resolve, 5000));
-
- const filesToSync = this.settings.fileMetadata;
- if(this.settings.debugMode){
- console.log(filesToSync)
- }
-
- for (let fileKey in filesToSync) {
- if(this.settings.debugMode){
- console.log(fileKey)
- }
-
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.fullTextNewTaskCheck(fileKey);
- } catch(error) {
- console.error('An error occurred in fullTextNewTaskCheck:', error);
- }
- this.syncLock = false;
-
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.deletedTaskCheck(fileKey);
- } catch(error) {
- console.error('An error occurred in deletedTaskCheck:', error);
- }
- this.syncLock = false;
-
- if (!await this.checkAndHandleSyncLock()) return;
- try {
- await this.todoistSync.fullTextModifiedTaskCheck(fileKey);
- } catch(error) {
- console.error('An error occurred in fullTextModifiedTaskCheck:', error);
- }
- this.syncLock = false;
- }
-
- } catch (error) {
- console.error('An error occurred:', error);
- new Notice('An error occurred:', error);
- this.syncLock = false;
- }
- console.log("Todoist scheduled synchronization task completed at", new Date().toLocaleString());
- }
-
- async checkSyncLock() {
- let checkCount = 0;
- while (this.syncLock == true && checkCount < 10) {
- await new Promise(resolve => setTimeout(resolve, 1000));
- checkCount++;
- }
- if (this.syncLock == true) {
- return false;
- }
- return true;
- }
-
- async checkAndHandleSyncLock() {
- if (this.syncLock) {
- console.log('sync locked.');
- const isSyncLockChecked = await this.checkSyncLock();
- if (!isSyncLockChecked) {
- return false;
- }
- console.log('sync unlocked.')
- }
- this.syncLock = true;
- return true;
- }
-
-}
-
-
-
-
diff --git a/translate/src/cacheOperation.ts b/translate/src/cacheOperation.ts
deleted file mode 100644
index cdc51f5..0000000
--- a/translate/src/cacheOperation.ts
+++ /dev/null
@@ -1,473 +0,0 @@
-import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
-
-interface Due {
-date?: string;
-[key: string]: any; // allow for additional properties
-}
-
-export class CacheOperation {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin: UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-}
-
-
-
-
-
-async getFileMetadata(filepath:string) {
-return this.plugin.settings.fileMetadata[filepath] ?? null
-}
-
-async getFileMetadatas(){
-return this.plugin.settings.fileMetadata ?? null
-}
-
-async newEmptyFileMetadata(filepath:string){
-const metadatas = this.plugin.settings.fileMetadata
-if(metadatas[filepath]) {
-return
-}
-else{
-metadatas[filepath] = {}
-}
-metadatas[filepath].todoistTasks = [];
-metadatas[filepath].todoistCount = 0;
-// Save the updated metadatas object back to the settings object
-this.plugin.settings.fileMetadata = metadatas
-
-}
-
-async updateFileMetadata(filepath:string,newMetadata) {
-const metadatas = this.plugin.settings.fileMetadata
-
-// If the metadata object does not exist, create a new object and add it to metadatas
-if (!metadatas[filepath]) {
-metadatas[filepath] = {}
-}
-
-//Update attribute values in the metadata object
-metadatas[filepath].todoistTasks = newMetadata.todoistTasks;
-metadatas[filepath].todoistCount = newMetadata.todoistCount;
-
-// Save the updated metadatas object back to the settings object
-this.plugin.settings.fileMetadata = metadatas
-
-}
-
-async deleteTaskIdFromMetadata(filepath:string,taskId:string){
-console.log(filepath)
-const metadata = await this.getFileMetadata(filepath)
-console.log(metadata)
-const newTodoistTasks = metadata.todoistTasks.filter(function(element){
-return element !== taskId
-})
-const newTodoistCount = metadata.todoistCount - 1
-let newMetadata = {}
-newMetadata.todoistTasks = newTodoistTasks
-newMetadata.todoistCount = newTodoistCount
-console.log(`new metadata ${newMetadata}`)
-
-
-}
-
-//delete filepath from filemetadata
-async deleteFilepathFromMetadata(filepath:string){
-Reflect.deleteProperty(this.plugin.settings.fileMetadata, filepath);
-this.plugin.saveSettings()
-console.log(`${filepath} is deleted from file metadatas.`)
-}
-
-
-//Check errors in filemata where the filepath is incorrect.
-async checkFileMetadata(){
-const metadatas = await this.getFileMetadatas()
-for (const key in metadatas) {
-let filepath = key
-const value = metadatas[key];
-let file = this.app.vault.getAbstractFileByPath(key)
-if(!file && (value.todoistTasks?.length === 0 || !value.todoistTasks)){
-console.log(`${key} is not existed and metadata is empty.`)
-await this.deleteFilepathFromMetadata(key)
-continue
-}
-if(value.todoistTasks?.length === 0 || !value.todoistTasks){
-//todo
-//delelte empty metadata
-continue
-}
-//check if file exists
-
-if(!file){
-//search new filepath
-console.log(`file ${filepath} is not exist`)
-const todoistId1 = value.todoistTasks[0]
-console.log(todoistId1)
-const searchResult = await this.plugin.fileOperation.searchFilepathsByTaskidInVault(todoistId1)
-console.log(`new file path is`)
-console.log(searchResult)
-
-//update metadata
-await this.updateRenamedFilePath(filepath,searchResult)
-this.plugin.saveSettings()
-
-}
-
-
-//const fileContent = await this.app.vault.read(file)
-//check if file include all tasks
-
-
-/*
-value.todoistTasks.forEach(async(taskId) => {
-const taskObject = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-
-
-});
-*/
-}
-
-}
-
-getDefaultProjectNameForFilepath(filepath:string){
-const metadatas = this.plugin.settings.fileMetadata
-if (!metadatas[filepath] || metadatas[filepath].defaultProjectId === undefined) {
-return this.plugin.settings.defaultProjectName
-}
-else{
-const defaultProjectId = metadatas[filepath].defaultProjectId
-const defaultProjectName = this.getProjectNameByIdFromCache(defaultProjectId)
-return defaultProjectName
-}
-}
-
-
-getDefaultProjectIdForFilepath(filepath:string){
-const metadatas = this.plugin.settings.fileMetadata
-if (!metadatas[filepath] || metadatas[filepath].defaultProjectId === undefined) {
-return this.plugin.settings.defaultProjectId
-}
-else{
-const defaultProjectId = metadatas[filepath].defaultProjectId
-return defaultProjectId
-}
-}
-
-setDefaultProjectIdForFilepath(filepath:string,defaultProjectId:string){
-const metadatas = this.plugin.settings.fileMetadata
-if (!metadatas[filepath]) {
-metadatas[filepath] = {}
-}
-metadatas[filepath].defaultProjectId = defaultProjectId
-
-// Save the updated metadatas object back to the settings object
-this.plugin.settings.fileMetadata = metadatas
-
-}
-
-
-//Read all tasks from Cache
-loadTasksFromCache() {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-return savedTasks;
-} catch (error) {
-console.error(`Error loading tasks from Cache: ${error}`);
-return [];
-}
-}
-
-
-// Overwrite and save all tasks to cache
-saveTasksToCache(newTasks) {
-try {
-this.plugin.settings.todoistTasksData.tasks = newTasks
-
-} catch (error) {
-console.error(`Error saving tasks to Cache: ${error}`);
-return false;
-}
-}
-
-
-
-
-//append event to Cache
-appendEventToCache(event:Object[]) {
-try {
-this.plugin.settings.todoistTasksData.events.push(event)
-} catch (error) {
-console.error(`Error append event to Cache: ${error}`);
-}
-}
-
-//append events to Cache
-appendEventsToCache(events:Object[]) {
-try {
-this.plugin.settings.todoistTasksData.events.push(...events)
-} catch (error) {
-console.error(`Error append events to Cache: ${error}`);
-}
-}
-
-
-//Read all events from the Cache file
-loadEventsFromCache() {
-try {
-
-const savedEvents = this.plugin.settings.todoistTasksData.events
-return savedEvents;
-} catch (error) {
-console.error(`Error loading events from Cache: ${error}`);
-}
-}
-
-
-
-//Append to Cache file
-appendTaskToCache(task) {
-try {
-if(task === null){
-return
-}
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-//const taskAlreadyExists = savedTasks.some((t) => t.id === task.id);
-//if (!taskAlreadyExists) {
-//, when using the push method to insert a string into a Cache object, it will be treated as a simple key-value pair, where the key is the numeric index of the array and the value is the string itself. But if you use the push method to insert another Cache object (or array) into the Cache object, the object will become a nested sub-object of the original Cache object. In this case, the key is the numeric index and the value is the nested Cache object itself.
-//}
-this.plugin.settings.todoistTasksData.tasks.push(task);
-} catch (error) {
-console.error(`Error appending task to Cache: ${error}`);
-}
-}
-
-
-
-
-//Read the task with the specified id
-loadTaskFromCacheyID(taskId) {
-try {
-
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-//console.log(savedTasks)
-const savedTask = savedTasks.find((t) => t.id === taskId);
-//console.log(savedTask)
-return(savedTask)
-} catch (error) {
-console.error(`Error finding task from Cache: ${error}`);
-return [];
-}
-}
-
-//Overwrite the task with the specified id in update
-updateTaskToCacheByID(task) {
-try {
-
-
-//Delete the existing task
-this.deleteTaskFromCache(task.id)
-//Add new task
-this.appendTaskToCache(task)
-
-} catch (error) {
-console.error(`Error updating task to Cache: ${error}`);
-return [];
-}
-}
-
-//The structure of due {date: "2025-02-25",isRecurring: false,lang: "en",string: "2025-02-25"}
-
-
-
-modifyTaskToCacheByID(taskId: string, { content, due }: { content?: string, due?: Due }): void {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks;
-const taskIndex = savedTasks.findIndex((task) => task.id === taskId);
-
-if (taskIndex !== -1) {
-const updatedTask = { ...savedTasks[taskIndex] };
-
-if (content !== undefined) {
-updatedTask.content = content;
-}
-
-if (due !== undefined) {
-if (due === null) {
-updatedTask.due = null;
-} else {
-updatedTask.due = due;
-}
-}
-
-savedTasks[taskIndex] = updatedTask;
-
-this.plugin.settings.todoistTasksData.tasks = savedTasks;
-} else {
-throw new Error(`Task with ID ${taskId} not found in cache.`);
-}
-} catch (error) {
-// Handle the error appropriately, eg by logging it or re-throwing it.
-}
-}
-
-
-//open a task status
-reopenTaskToCacheByID(taskId:string) {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-
-
-// Loop through the array to find the item with the specified ID
-for (let i = 0; i < savedTasks.length; i++) {
-if (savedTasks[i].id === taskId) {
-//Modify the properties of the object
-savedTasks[i].isCompleted = false;
-break; // Found and modified the item, break out of the loop
-}
-}
-this.plugin.settings.todoistTasksData.tasks = savedTasks
-
-} catch (error) {
-console.error(`Error open task to Cache file: ${error}`);
-return [];
-}
-}
-
-
-
-//close a task status
-closeTaskToCacheByID(taskId:string):Promise {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-
-// Loop through the array to find the item with the specified ID
-for (let i = 0; i < savedTasks.length; i++) {
-if (savedTasks[i].id === taskId) {
-//Modify the properties of the object
-savedTasks[i].isCompleted = true;
-break; // Found and modified the item, break out of the loop
-}
-}
-this.plugin.settings.todoistTasksData.tasks = savedTasks
-
-} catch (error) {
-console.error(`Error close task to Cache file: ${error}`);
-throw error; // Throw an error so that the caller can catch and handle it
-}
-}
-
-
-//Delete task by ID
-deleteTaskFromCache(taskId) {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-const newSavedTasks = savedTasks.filter((t) => t.id !== taskId);
-this.plugin.settings.todoistTasksData.tasks = newSavedTasks
-} catch (error) {
-console.error(`Error deleting task from Cache file: ${error}`);
-}
-}
-
-
-
-
-
-//Delete task through ID array
-deleteTaskFromCacheByIDs(deletedTaskIds) {
-try {
-const savedTasks = this.plugin.settings.todoistTasksData.tasks
-const newSavedTasks = savedTasks.filter((t) => !deletedTaskIds.includes(t.id))
-this.plugin.settings.todoistTasksData.tasks = newSavedTasks
-} catch (error) {
-console.error(`Error deleting task from Cache : ${error}`);
-}
-}
-
-
-//Find project id by name
-getProjectIdByNameFromCache(projectName:string) {
-try {
-const savedProjects = this.plugin.settings.todoistTasksData.projects
-const targetProject = savedProjects.find(obj => obj.name === projectName);
-const projectId = targetProject ? targetProject.id : null;
-return(projectId)
-} catch (error) {
-console.error(`Error finding project from Cache file: ${error}`);
-return(false)
-}
-}
-
-
-
-getProjectNameByIdFromCache(projectId:string) {
-try {
-const savedProjects = this.plugin.settings.todoistTasksData.projects
-const targetProject = savedProjects.find(obj => obj.id === projectId);
-const projectName = targetProject ? targetProject.name : null;
-return(projectName)
-} catch (error) {
-console.error(`Error finding project from Cache file: ${error}`);
-return(false)
-}
-}
-
-
-
-//save projects data to json file
-async saveProjectsToCache() {
-try{
-//get projects
-const projects = await this.plugin.todoistRestAPI.GetAllProjects()
-if(!projects){
-return false
-}
-
-//save to json
-this.plugin.settings.todoistTasksData.projects = projects
-
-return true
-
-}catch(error){
-return false
-console.log(`error downloading projects: ${error}`)
-
-}
-
-}
-
-
-async updateRenamedFilePath(oldpath:string,newpath:string){
-try{
-console.log(`oldpath is ${oldpath}`)
-console.log(`newpath is ${newpath}`)
-const savedTask = await this.loadTasksFromCache()
-//console.log(savedTask)
-const newTasks = savedTask.map(obj => {
-if (obj.path === oldpath) {
-return { ...obj, path: newpath };
-}else {
-return obj;
-}
-})
-//console.log(newTasks)
-await this.saveTasksToCache(newTasks)
-
-//update filepath
-const fileMetadatas = this.plugin.settings.fileMetadata
-fileMetadatas[newpath] = fileMetadatas[oldpath]
-delete fileMetadatas[oldpath]
-this.plugin.settings.fileMetadata = fileMetadatas
-
-}catch(error){
-console.log(`Error updating renamed file path to cache: ${error}`)
-}
-
-
-}
-
-}
diff --git a/translate/src/fileOperation.ts b/translate/src/fileOperation.ts
deleted file mode 100644
index 7f4b4b6..0000000
--- a/translate/src/fileOperation.ts
+++ /dev/null
@@ -1,462 +0,0 @@
-import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
-export class FileOperation {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-
-}
-/*
-async getFrontMatter(file:TFile): Promise {
-return new Promise((resolve) => {
-this.app.fileManager.processFrontMatter(file, (frontMatter) => {
-resolve(frontMatter);
-});
-});
-}
-*/
-
-
-
-
-/*
-async updateFrontMatter(
-file:TFile,
-updater: (frontMatter: FrontMatter) => 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.todoistTasks = updatedFrontMatter.todoistTasks;
-newFrontMatter.todoistCount = updatedFrontMatter.todoistCount;
-}
-});
-}
-});
-}
-*/
-
-
-
-
-
-//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 filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-lines[i] = line.replace('[ ]', '[x]')
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-await this.app.vault.modify(file, newContent)
-}
-}
-
-// uncheck completed tasks,
-async uncompleteTaskInTheFile(taskId: string) {
-// Get the task file path
-const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-const filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-lines[i] = line.replace(/- \[(x|X)\]/g, '- [ ]');
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-await this.app.vault.modify(file, newContent)
-}
-}
-
-//add #todoist at the end of task line, if full vault sync enabled
-async addTodoistTagToFile(filepath: string) {
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if(!this.plugin.taskParser.isMarkdownTask(line)){
-//console.log(line)
-//console.log("It is not a markdown task.")
-continue;
-}
-//if content is empty
-if(this.plugin.taskParser.getTaskContentFromLineText(line) == ""){
-//console.log("Line content is empty")
-continue;
-}
-if (!this.plugin.taskParser.hasTodoistId(line) && !this.plugin.taskParser.hasTodoistTag(line)) {
-//console.log(line)
-//console.log('prepare to add todoist tag')
-const newLine = this.plugin.taskParser.addTodoistTag(line);
-//console.log(newLine)
-lines[i] = newLine
-modified = true
-}
-}
-
-if (modified) {
-console.log(`New task found in files ${filepath}`)
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-
-//update filemetadate
-const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath)
-if(!metadata){
-await this.plugin.cacheOperation.newEmptyFileMetadata(filepath)
-}
-
-}
-}
-
-
-
-//add todoist at the line
-async addTodoistLinkToFile(filepath: string) {
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (this.plugin.taskParser.hasTodoistId(line) && this.plugin.taskParser.hasTodoistTag(line)) {
-if(this.plugin.taskParser.hasTodoistLink(line)){
-return
-}
-console.log(line)
-//console.log('prepare to add todoist link')
-const taskID = this.plugin.taskParser.getTodoistIdFromLineText(line)
-const taskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(taskID)
-const todoistLink = taskObject.url
-const link = `[link](${todoistLink})`
-const newLine = this.plugin.taskParser.addTodoistLink(line,link)
-console.log(newLine)
-lines[i] = newLine
-modified = true
-}else{
-continue
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-
-
-
-}
-}
-
-
-//add #todoist at the end of task line, if full vault sync enabled
-async addTodoistTagToLine(filepath:string,lineText:string,lineNumber:number,fileContent:string) {
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = fileContent
-
-const lines = content.split('\n')
-let modified = false
-
-
-const line = lineText
-if(!this.plugin.taskParser.isMarkdownTask(line)){
-//console.log(line)
-//console.log("It is not a markdown task.")
-return;
-}
-//if content is empty
-if(this.plugin.taskParser.getTaskContentFromLineText(line) == ""){
-//console.log("Line content is empty")
-return;
-}
-if (!this.plugin.taskParser.hasTodoistId(line) && !this.plugin.taskParser.hasTodoistTag(line)) {
-//console.log(line)
-//console.log('prepare to add todoist tag')
-const newLine = this.plugin.taskParser.addTodoistTag(line);
-//console.log(newLine)
-lines[lineNumber] = newLine
-modified = true
-}
-
-
-if (modified) {
-console.log(`New task found in files ${filepath}`)
-const newContent = lines.join('\n')
-console.log(newContent)
-await this.app.vault.modify(file, newContent)
-
-//update filemetadate
-const metadata = await this.plugin.cacheOperation.getFileMetadata(filepath)
-if(!metadata){
-await this.plugin.cacheOperation.newEmptyFileMetadata(filepath)
-}
-
-}
-}
-
-// sync updated task content to file
-async syncUpdatedTaskContentToTheFile(evt:Object) {
-const taskId = evt.object_id
-// Get the task file path
-const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-const filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-const oldTaskContent = this.plugin.taskParser.getTaskContentFromLineText(line)
-const newTaskContent = evt.extra_data.content
-
-lines[i] = line.replace(oldTaskContent, newTaskContent)
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-}
-
-}
-
-// sync updated task due date to the file
-async syncUpdatedTaskDueDateToTheFile(evt:Object) {
-const taskId = evt.object_id
-// Get the task file path
-const currentTask = await this.plugin.cacheOperation.loadTaskFromCacheyID(taskId)
-const filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-const oldTaskDueDate = this.plugin.taskParser.getDueDateFromLineText(line) || ""
-const newTaskDueDate = this.plugin.taskParser.ISOStringToLocalDateString(evt.extra_data.due_date) || ""
-
-//console.log(`${taskId} duedate is updated`)
-console.log(oldTaskDueDate)
-console.log(newTaskDueDate)
-if(oldTaskDueDate === ""){
-//console.log(this.plugin.taskParser.insertDueDateBeforeTodoist(line,newTaskDueDate))
-lines[i] = this.plugin.taskParser.insertDueDateBeforeTodoist(line,newTaskDueDate)
-modified = true
-
-}
-else if(newTaskDueDate === ""){
-//remove date from text
-const regexRemoveDate = /(🗓️|📅|📆|🗓)\s?\d{4}-\d{2}-\d{2}/; //Matching date 🗓️2023-03-07"
-lines[i] = line.replace(regexRemoveDate,"")
-modified = true
-}
-else{
-
-lines[i] = line.replace(oldTaskDueDate, newTaskDueDate)
-modified = true
-}
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-}
-
-}
-
-
-// sync new task note to file
-async syncAddedTaskNoteToTheFile(evt:Object) {
-
-
-const taskId = evt.parent_item_id
-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 filepath = currentTask.path
-
-// Get the file object and update the content
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-let modified = false
-
-for (let i = 0; i < lines.length; i++) {
-const line = lines[i]
-if (line.includes(taskId) && this.plugin.taskParser.hasTodoistTag(line)) {
-const indent = '\t'.repeat(line.length - line.trimStart().length + 1);
-const noteLine = `${indent}- ${datetime} ${note}`;
-lines.splice(i + 1, 0, noteLine);
-modified = true
-break
-}
-}
-
-if (modified) {
-const newContent = lines.join('\n')
-//console.log(newContent)
-await this.app.vault.modify(file, newContent)
-}
-
-}
-
-
-//Avoid using this method, you can get real-time updated value through view
-async readContentFromFilePath(filepath:string){
-try {
-const file = this.app.vault.getAbstractFileByPath(filepath);
-const content = await this.app.vault.read(file);
-return content
-} catch (error) {
-console.error(`Error loading content from ${filepath}: ${error}`);
-return false;
-}
-}
-
-//get line text from file path
-//Please use view.editor.getLine, the read method has a delay
-async getLineTextFromFilePath(filepath:string,lineNumber:string) {
-
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const content = await this.app.vault.read(file)
-
-const lines = content.split('\n')
-return(lines[lineNumber])
-}
-
-//search todoist_id by content
-async searchTodoistIdFromFilePath(filepath: string, searchTerm: string): string | null {
-const file = this.app.vault.getAbstractFileByPath(filepath)
-const fileContent = await this.app.vault.read(file)
-const fileLines = fileContent.split('\n');
-let todoistId: string | null = null;
-
-for (let i = 0; i < fileLines.length; i++) {
-const line = fileLines[i];
-
-if (line.includes(searchTerm)) {
-const regexResult = /\[todoist_id::\s*(\w+)\]/.exec(line);
-
-if (regexResult) {
-todoistId = regexResult[1];
-}
-
-break;
-}
-}
-
-return todoistId;
-}
-
-//get all files in the vault
-async getAllFilesInTheVault(){
-const files = this.app.vault.getFiles()
-return(files)
-}
-
-//search filepath by taskid in vault
-async searchFilepathsByTaskidInVault(taskId:string){
-console.log(`preprare to search task ${taskId}`)
-const files = await this.getAllFilesInTheVault()
-//console.log(files)
-const tasks = files.map(async (file) => {
-if (!this.isMarkdownFile(file.path)) {
-return;
-}
-const fileContent = await this.app.vault.cachedRead(file);
-if (fileContent.includes(taskId)) {
-return file.path;
-}
-});
-
-const results = await Promise.all(tasks);
-const filePaths = results.filter((filePath) => filePath !== undefined);
-return filePaths[0] || null;
-//return filePaths || null
-}
-
-
-isMarkdownFile(filename:string) {
-// Get the extension of the file name
-let extension = filename.split('.').pop();
-
-//Convert the extension to lowercase (the extension of Markdown files is usually .md)
-extension = extension.toLowerCase();
-
-// Determine whether the extension is .md
-if (extension === 'md') {
-return true;
-} else {
-return false;
-}
-}
-
-
-
-
-
-}
diff --git a/translate/src/modal.ts b/translate/src/modal.ts
deleted file mode 100644
index baddc5b..0000000
--- a/translate/src/modal.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { App, Modal ,Setting } from "obsidian";
-import UltimateTodoistSyncForObsidian from "../main"
-
-
-interface MyProject {
-id: string;
-name: string;
-}
-
-export class SetDefalutProjectInTheFilepathModal extends Modal {
-defaultProjectId: string
-defaultProjectName: string
-filepath:string
-plugin:UltimateTodoistSyncForObsidian
-
-
-constructor(app: App,plugin:UltimateTodoistSyncForObsidian, filepath:string) {
-super(app);
-this.filepath = filepath
-this.plugin = plugin
-this.open()
-}
-
-async onOpen() {
-const { contentEl } = this;
-contentEl.empty();
-contentEl.createEl('h5', { text: 'Set default project for todoist tasks in the current file' });
-
-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)
-const myProjectsOptions: MyProject | undefined = this.plugin.settings.todoistTasksData?.projects?.reduce((obj, item) => {
-obj[(item.id).toString()] = item.name;
-return obj;
-}, {}
-);
-
-
-
-new Setting(contentEl)
-.setName('Default project')
-//.setDesc('Set default project for todoist tasks in the current file')
-.addDropdown(component =>
-component
-.addOption(this.defaultProjectId,this.defaultProjectName)
-.addOptions(myProjectsOptions)
-.onChange((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()
-this.plugin.cacheOperation.setDefaultProjectIdForFilepath(this.filepath,value)
-this.plugin.setStatusBarText()
-this.close();
-
-})
-
-)
-
-
-
-
-}
-
-onClose() {
-let { contentEl } = this;
-contentEl.empty();
-}
-}
diff --git a/translate/src/syncModule.ts b/translate/src/syncModule.ts
deleted file mode 100644
index 0aaf919..0000000
--- a/translate/src/syncModule.ts
+++ /dev/null
@@ -1,874 +0,0 @@
-import UltimateTodoistSyncForObsidian from "../main";
-import { App, Editor, MarkdownView, Notice} from 'obsidian';
-
-
-type FrontMatter = {
-todoistTasks: string[];
-todoistCount: 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);
-}
-
-
-
-}
-
-
-
-
-
-}
diff --git a/translate/src/taskParser.ts b/translate/src/taskParser.ts
deleted file mode 100644
index 991c04a..0000000
--- a/translate/src/taskParser.ts
+++ /dev/null
@@ -1,546 +0,0 @@
-import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
-
-
-
-
-interface dataviewTaskObject {
-status: string;
-checked: boolean;
-completed: boolean;
-fullyCompleted: boolean;
-text: string;
-visual: string;
-line: number;
-lineCount: number;
-path: string;
-section: string;
-tags: string[];
-outlinks: string[];
-link: string;
-children: any[];
-task: boolean;
-annotated: boolean;
-parent: number;
-blockId: string;
-}
-
-
-interface todoistTaskObject {
-content: string;
-description?: string;
-project_id?: string;
-section_id?: string;
-parent_id?: string;
-order?: number | null;
-labels?: string[];
-priority?: number | null;
-due_string?: string;
-due_date?: string;
-due_datetime?: string;
-due_lang?: string;
-assignee_id?: string;
-}
-
-
-const keywords = {
-TODOIST_TAG: "#todoist",
-DUE_DATE: "🗓️|📅|📆|🗓",
-};
-
-const REGEX = {
-TODOIST_TAG: new RegExp(`^[\\s]*[-] \\[[x ]\\] [\\s\\S]*${keywords.TODOIST_TAG}[\\s\\S]*$ `, "i"),
-TODOIST_ID: /\[todoist_id::\s*\d+\]/,
-TODOIST_ID_NUM:/\[todoist_id::\s*(.*?)\]/,
-TODOIST_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})`),
-PROJECT_NAME: /\[project::\s*(.*?)\]/,
-TASK_CONTENT: {
-REMOVE_PRIORITY: /\s!!([1-4])\s/,
-REMOVE_TAGS: /(^|\s)(#[a-zA-Z\d\u4e00-\u9fa5-]+)/g,
-REMOVE_SPACE: /^\s+|\s+$/g,
-REMOVE_DATE: new RegExp(`(${keywords.DUE_DATE})\\s?\\d{4}-\\d{2}-\\d{2}`),
-REMOVE_INLINE_METADATA: /%%\[\w+::\s*\w+\]%%/,
-REMOVE_CHECKBOX: /^(-|\*)\s+\[(x|X| )\]\s/,
-REMOVE_CHECKBOX_WITH_INDENTATION: /^([ \t]*)?(-|\*)\s+\[(x|X| )\]\s/,
-REMOVE_TODOIST_LINK: /\[link\]\(.*?\)/,
-},
-ALL_TAGS: /#[\w\u4e00-\u9fa5-]+/g,
-TASK_CHECKBOX_CHECKED: /- \[(x|X)\] /,
-TASK_INDENTATION: /^(\s{2,}|\t)(-|\*)\s+\[(x|X| )\]/,
-TAB_INDENTATION: /^(\t+)/,
-TASK_PRIORITY: /\s!!([1-4])\s/,
-BLANK_LINE: /^\s*$/,
-TODOIST_EVENT_DATE: /(\d{4})-(\d{2})-(\d{2})/
-};
-
-export class TaskParser {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin
-}
-
-
-
-
-//convert line text to a task object
-async convertTextToTodoistTaskObject(lineText:string,filepath:string,lineNumber?:number,fileContent?:string) {
-//console.log(`linetext is:${lineText}`)
-
-let hasParent = false
-let parentId = null
-let parentTaskObject = null
-//Detect parentID
-let textWithoutIndentation = lineText
-if(this.getTabIndentation(lineText) > 0){
-//console.log(`Indentation is ${this.getTabIndentation(lineText)}`)
-textWithoutIndentation = this.removeTaskIndentation(lineText)
-//console.log(textWithoutIndentation)
-//console.log(`This is a subtask`)
-//Read filepath
-//const fileContent = await this.plugin.fileOperation.readContentFromFilePath(filepath)
-//Traverse line
-const lines = fileContent.split('\n')
-//console.log(lines)
-for (let i = (lineNumber - 1 ); i >= 0; i--) {
-//console.log(`Checking the indentation of line ${i}`)
-const line = lines[i]
-//console.log(line)
-//If it is a blank line, it means there is no parent
-if(this.isLineBlank(line)){
-break
-}
-//If the number of tabs is greater than or equal to the current line, skip
-if (this.getTabIndentation(line) >= this.getTabIndentation(lineText)) {
-//console.log(`Indentation is ${this.getTabIndentation(line)}`)
-continue
-}
-if((this.getTabIndentation(line) < this.getTabIndentation(lineText))){
-//console.log(`Indentation is ${this.getTabIndentation(line)}`)
-if(this.hasTodoistId(line)){
-parentId = this.getTodoistIdFromLineText(line)
-hasParent = true
-//console.log(`parent id is ${parentId}`)
-parentTaskObject = this.plugin.cacheOperation.loadTaskFromCacheyID(parentId)
-break
-}
-else{
-break
-}
-}
-}
-
-
-}
-
-const dueDate = this.getDueDateFromLineText(textWithoutIndentation)
-const labels = this.getAllTagsFromLineText(textWithoutIndentation)
-//console.log(`labels is ${labels}`)
-
-//dataview format metadata
-//const projectName = this.getProjectNameFromLineText(textWithoutIndentation) ?? this.plugin.settings.defaultProjectName
-//const projectId = await this.plugin.cacheOperation.getProjectIdByNameFromCache(projectName)
-//use tag as project name
-
-let projectId = this.plugin.cacheOperation.getDefaultProjectIdForFilepath(filepath as string)
-let projectName = this.plugin.cacheOperation.getProjectNameByIdFromCache(projectId)
-
-if(hasParent){
-projectId = parentTaskObject.projectId
-projectName =this.plugin.cacheOperation.getProjectNameByIdFromCache(projectId)
-}
-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
-}
-projectName = labelName
-//console.log(`project is ${projectName} ${label}`)
-projectId = hasProjectId
-break
-}
-}
-
-
-const content = this.getTaskContentFromLineText(textWithoutIndentation)
-const isCompleted = this.isTaskCheckboxChecked(textWithoutIndentation)
-let description = ""
-const todoist_id = this.getTodoistIdFromLineText(textWithoutIndentation)
-const priority = this.getTaskPriority(textWithoutIndentation)
-if(filepath){
-let url = encodeURI(`obsidian://open?vault=${this.app.vault.getName()}&file=${filepath}`)
-description =`[${filepath}](${url})`;
-}
-
-const todoistTask = {
-projectId: projectId,
-content: content || '',
-parentId: parentId || null,
-dueDate: dueDate || '',
-labels: labels || [],
-description: description,
-isCompleted:isCompleted,
-todoist_id:todoist_id || null,
-hasParent:hasParent,
-priority:priority
-};
-//console.log(`converted task `)
-//console.log(todoistTask)
-return todoistTask;
-}
-
-
-
-
-hasTodoistTag(text:string){
-//console.log("Check whether todoist tag is included")
-//console.log(text)
-return(REGEX.TODOIST_TAG.test(text))
-}
-
-
-
-hasTodoistId(text:string){
-const result = REGEX.TODOIST_ID.test(text)
-//console.log("Check whether todoist id is included")
-//console.log(text)
-return(result)
-}
-
-
-hasDueDate(text:string){
-return(REGEX.DUE_DATE_WITH_EMOJ.test(text))
-}
-
-
-getDueDateFromLineText(text: string) {
-const result = REGEX.DUE_DATE.exec(text);
-return result ? result[1] : null;
-}
-
-
-
-getProjectNameFromLineText(text:string){
-const result = REGEX.PROJECT_NAME.exec(text);
-return result ? result[1] : null;
-}
-
-
-getTodoistIdFromLineText(text:string){
-//console.log(text)
-const result = REGEX.TODOIST_ID_NUM.exec(text);
-//console.log(result)
-return result ? result[1] : null;
-}
-
-getDueDateFromDataview(dataviewTask:object){
-if(!dataviewTask.due){
-return ""
-}
-else{
-const dataviewTaskDue = dataviewTask.due.toString().slice(0, 10)
-return(dataviewTaskDue)
-}
-
-}
-
-
-
-/*
-//convert line task to dataview task object
-async getLineTask(filepath,line){
-//const tasks = this.app.plugins.plugins.dataview.api.pages(`"${filepath}"`).file.tasks
-const tasks = await getAPI(this.app).pages(`"${filepath}"`).file.tasks
-const tasksValues = tasks.values
-//console.log(`dataview filepath is ${filepath}`)
-//console.log(`dataview line is ${line}`)
-//console.log(tasksValues)
-const currentLineTask = tasksValues.find(obj => obj.line === line )
-console.log(currentLineTask)
-return(currentLineTask)
-
-}
-*/
-
-
-
-getTaskContentFromLineText(lineText:string) {
-const TaskContent = lineText.replace(REGEX.TASK_CONTENT.REMOVE_INLINE_METADATA,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_TODOIST_LINK,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_PRIORITY," ") //There must be spaces before and after priority.
-.replace(REGEX.TASK_CONTENT.REMOVE_TAGS,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_DATE,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_CHECKBOX,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_CHECKBOX_WITH_INDENTATION,"")
-.replace(REGEX.TASK_CONTENT.REMOVE_SPACE,"")
-return(TaskContent)
-}
-
-
-//get all tags from task text
-getAllTagsFromLineText(lineText:string){
-let tags = lineText.match(REGEX.ALL_TAGS);
-
-if (tags) {
-// Remove '#' from each tag
-tags = tags.map(tag => tag.replace('#', ''));
-}
-
-return tags;
-}
-
-//get checkbox status
-isTaskCheckboxChecked(lineText:string) {
-return(REGEX.TASK_CHECKBOX_CHECKED.test(lineText))
-}
-
-
-//task content compare
-taskContentCompare(lineTask:Object,todoistTask:Object) {
-const lineTaskContent = lineTask.content
-//console.log(dataviewTaskContent)
-
-const todoistTaskContent = todoistTask.content
-//console.log(todoistTask.content)
-
-//Whether content is modified?
-const contentModified = (lineTaskContent === todoistTaskContent)
-return(contentModified)
-}
-
-
-//tag compare
-taskTagCompare(lineTask:Object,todoistTask:Object) {
-
-
-const lineTaskTags = lineTask.labels
-//console.log(dataviewTaskTags)
-
-const todoistTaskTags = todoistTask.labels
-//console.log(todoistTaskTags)
-
-//Whether content is modified?
-const tagsModified = lineTaskTags.length === todoistTaskTags.length && lineTaskTags.sort().every((val, index) => val === todoistTaskTags.sort()[index]);
-return(tagsModified)
-}
-
-//task status compare
-taskStatusCompare(lineTask:Object,todoistTask:Object) {
-//Whether status is modified?
-const statusModified = (lineTask.isCompleted === todoistTask.isCompleted)
-//console.log(lineTask)
-//console.log(todoistTask)
-return(statusModified)
-}
-
-
-//task due date compare
-async compareTaskDueDate(lineTask: object, todoistTask: object): boolean {
-const lineTaskDue = lineTask.dueDate
-const todoistTaskDue = todoistTask.due ?? "";
-//console.log(dataviewTaskDue)
-//console.log(todoistTaskDue)
-if (lineTaskDue === "" && todoistTaskDue === "") {
-//console.log('No due date')
-return true;
-}
-
-if ((lineTaskDue || todoistTaskDue) === "") {
-console.log(lineTaskDue);
-console.log(todoistTaskDue)
-//console.log('due date has changed')
-return false;
-}
-
-const oldDueDateUTCString = this.localDateStringToUTCDateString(lineTaskDue)
-if (oldDueDateUTCString === todoistTaskDue.date) {
-//console.log('due date consistent')
-return true;
-} else if (lineTaskDue.toString() === "Invalid Date" || todoistTaskDue.toString() === "Invalid Date") {
-console.log('invalid date')
-return false;
-} else {
-//console.log(lineTaskDue);
-//console.log(todoistTaskDue.date)
-return false;
-}
-}
-
-
-//task project id compare
-async taskProjectCompare(lineTask:Object,todoistTask:Object) {
-//project whether to modify
-//console.log(dataviewTaskProjectId)
-//console.log(todoistTask.projectId)
-return(lineTask.projectId === todoistTask.projectId)
-}
-
-
-//Determine whether the task is indented
-isIndentedTask(text:string) {
-return(REGEX.TASK_INDENTATION.test(text));
-}
-
-
-//Determine the number of tab characters
-//console.log(getTabIndentation("\t\t- [x] This is a task with two tabs")); // 2
-//console.log(getTabIndentation(" - [x] This is a task without tabs")); // 0
-getTabIndentation(lineText:string){
-const match = REGEX.TAB_INDENTATION.exec(lineText)
-return match ? match[1].length : 0;
-}
-
-
-// Task priority from 1 (normal) to 4 (urgent).
-getTaskPriority(lineText:string): number{
-const match = REGEX.TASK_PRIORITY.exec(lineText)
-return match ? Number(match[1]) : 1;
-}
-
-
-
-//remove task indentation
-removeTaskIndentation(text) {
-const regex = /^([ \t]*)?- \[(x| )\] /;
-return text.replace(regex, "- [$2] ");
-}
-
-
-//Judge whether line is a blank line
-isLineBlank(lineText:string) {
-return(REGEX.BLANK_LINE.test(lineText))
-}
-
-
-//Insert date in linetext
-insertDueDateBeforeTodoist(text, dueDate) {
-const regex = new RegExp(`(${keywords.TODOIST_TAG})`)
-return text.replace(regex, `📅 ${dueDate} $1`);
-}
-
-//extra date from obsidian event
-// Usage example
-//const str = "2023-03-27T15:59:59.000000Z";
-//const dateStr = ISOStringToLocalDateString(str);
-//console.log(dateStr); // Output 2023-03-27
-ISOStringToLocalDateString(utcTimeString:string) {
-try {
-if(utcTimeString === null){
-return null
-}
-let utcDateString = utcTimeString;
-let dateObj = new Date(utcDateString); // Convert UTC format string to Date object
-let year = dateObj.getFullYear();
-let month = (dateObj.getMonth() + 1).toString().padStart(2, '0');
-let date = dateObj.getDate().toString().padStart(2, '0');
-let localDateString = `${year}-${month}-${date}`;
-return localDateString;
-return(localDateString);
-} catch (error) {
-console.error(`Error extracting date from string '${utcTimeString}': ${error}`);
-return null;
-}
-}
-
-
-//extra date from obsidian event
-// Usage example
-//const str = "2023-03-27T15:59:59.000000Z";
-//const dateStr = ISOStringToLocalDatetimeString(str);
-//console.log(dateStr); // Output Mon Mar 27 2023 23:59:59 GMT+0800 (China Standard Time)
-ISOStringToLocalDatetimeString(utcTimeString:string) {
-try {
-if(utcTimeString === null){
-return null
-}
-let utcDateString = utcTimeString;
-let dateObj = new Date(utcDateString); // Convert UTC format string to Date object
-let result = dateObj.toString();
-return(result);
-} catch (error) {
-console.error(`Error extracting date from string '${utcTimeString}': ${error}`);
-return null;
-}
-}
-
-
-
-//convert date from obsidian event
-// Usage example
-//const str = "2023-03-27";
-//const utcStr = localDateStringToUTCDatetimeString(str);
-//console.log(dateStr); // Output 2023-03-27T00:00:00.000Z
-localDateStringToUTCDatetimeString(localDateString:string) {
-try {
-if(localDateString === null){
-return null
-}
-localDateString = localDateString + "T08:00";
-let localDateObj = new Date(localDateString);
-let ISOString = localDateObj.toISOString()
-return(ISOString);
-} catch (error) {
-console.error(`Error extracting date from string '${localDateString}': ${error}`);
-return null;
-}
-}
-
-//convert date from obsidian event
-// Usage example
-//const str = "2023-03-27";
-//const utcStr = localDateStringToUTCDateString(str);
-//console.log(dateStr); // Output 2023-03-27
-localDateStringToUTCDateString(localDateString:string) {
-try {
-if(localDateString === null){
-return null
-}
-localDateString = localDateString + "T08:00";
-let localDateObj = new Date(localDateString);
-let ISOString = localDateObj.toISOString()
-let utcDateString = ISOString.slice(0,10)
-return(utcDateString);
-} catch (error) {
-console.error(`Error extracting date from string '${localDateString}': ${error}`);
-return null;
-}
-}
-
-isMarkdownTask(str: string): boolean {
-const taskRegex = /^\s*-\s+\[([x ])\]/;
-return taskRegex.test(str);
-}
-
-addTodoistTag(str: string): string {
-return(str +` ${keywords.TODOIST_TAG}`);
-}
-
-getObsidianUrlFromFilepath(filepath:string){
-const url = encodeURI(`obsidian://open?vault=${this.app.vault.getName()}&file=${filepath}`)
-const obsidianUrl =`[${filepath}](${url})`;
-return(obsidianUrl)
-}
-
-
-addTodoistLink(linetext: string,todoistLink:string): string {
-const regex = new RegExp(`${keywords.TODOIST_TAG}`, "g");
-return linetext.replace(regex, todoistLink + ' ' + '$&');
-}
-
-
-//Check whether todoist link is included
-hasTodoistLink(lineText:string){
-return(REGEX.TODOIST_LINK.test(lineText))
-}
-}
diff --git a/translate/src/todoistRestAPI.ts b/translate/src/todoistRestAPI.ts
deleted file mode 100644
index 246ab31..0000000
--- a/translate/src/todoistRestAPI.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import { TodoistApi } from "@doist/todoist-api-typescript"
-import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
-//convert date from obsidian event
-// Usage example
-//const str = "2023-03-27";
-//const utcStr = localDateStringToUTCDatetimeString(str);
-//console.log(dateStr); // Output 2023-03-27T00:00:00.000Z
-function localDateStringToUTCDatetimeString(localDateString:string) {
-try {
-if(localDateString === null){
-return null
-}
-localDateString = localDateString + "T08:00";
-let localDateObj = new Date(localDateString);
-let ISOString = localDateObj.toISOString()
-return(ISOString);
-} catch (error) {
-console.error(`Error extracting date from string '${localDateString}': ${error}`);
-return null;
-}
-}
-
-export class TodoistRestAPI {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-}
-
-
-initializeAPI(){
-const token = this.plugin.settings.todoistAPIToken
-const api = new TodoistApi(token)
-return(api)
-}
-
-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()
-try {
-if(dueDate){
-dueDatetime = localDateStringToUTCDatetimeString(dueDatetime)
-dueDate = null
-}
-const newTask = await api.addTask({
-projectId,
-content,
-parentId,
-dueDate,
-labels,
-description,
-priority
-});
-return newTask;
-} catch (error) {
-throw new Error(`Error adding task: ${error.message}`);
-}
-}
-
-
-//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()
-try {
-const result = await api.getTasks(options);
-return result;
-} catch (error) {
-throw new Error(`Error get active tasks: ${error.message}`);
-}
-}
-
-
-//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');
-}
-try {
-if(updates.dueDate){
-console.log(updates.dueDate)
-updates.dueDatetime = localDateStringToUTCDatetimeString(updates.dueDate)
-updates.dueDate = null
-console.log(updates.dueDatetime)
-}
-const updatedTask = await api.updateTask(taskId, updates);
-return updatedTask;
-} catch (error) {
-throw new Error(`Error updating task: ${error.message}`);
-}
-}
-
-
-
-
-//open a task
-async OpenTask(taskId:string) {
-const api = await this.initializeAPI()
-try {
-
-const isSuccess = await api.reopenTask(taskId);
-console.log(`Task ${taskId} is reopened`)
-return(isSuccess)
-
-} catch (error) {
-console.error('Error open a task:', error);
-return
-}
-}
-
-// Close a task in Todoist API
-async CloseTask(taskId: string): Promise {
-const api = await this.initializeAPI()
-try {
-const isSuccess = await api.closeTask(taskId);
-console.log(`Task ${taskId} is closed`)
-return isSuccess;
-} catch (error) {
-console.error('Error closing task:', error);
-throw error; // Throw an error so that the caller can catch and handle it
-}
-}
-
-
-
-
-// get a task by Id
-async getTaskById(taskId: string) {
-const api = await this.initializeAPI()
-if (!taskId) {
-throw new Error('taskId is required');
-}
-try {
-const task = await api.getTask(taskId);
-return task;
-} catch (error) {
-if (error.response && error.response.status) {
-const statusCode = error.response.status;
-throw new Error(`Error retrieving task. Status code: ${statusCode}`);
-} else {
-throw new Error(`Error retrieving task: ${error.message}`);
-}
-}
-}
-
-//get a task due by id
-async getTaskDueById(taskId: string) {
-const api = await this.initializeAPI()
-if (!taskId) {
-throw new Error('taskId is required');
-}
-try {
-const task = await api.getTask(taskId);
-const due = task.due ?? null
-return due;
-} catch (error) {
-throw new Error(`Error updating task: ${error.message}`);
-}
-}
-
-
-//get all projects
-async GetAllProjects() {
-const api = await this.initializeAPI()
-try {
-const result = await api.getProjects();
-return(result)
-
-} catch (error) {
-console.error('Error get all projects', error);
-return false
-}
-}
-
-
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/translate/src/todoistSyncAPI.ts b/translate/src/todoistSyncAPI.ts
deleted file mode 100644
index 7a06d91..0000000
--- a/translate/src/todoistSyncAPI.ts
+++ /dev/null
@@ -1,380 +0,0 @@
-import { App} from 'obsidian';
-import UltimateTodoistSyncForObsidian from "../main";
-
-
-type Event = {
-id: string;
-object_type: string;
-object_id: string;
-event_type: string;
-event_date: string;
-parent_project_id: string;
-parent_item_id: string | null;
-initiator_id: string | null;
-extra_data: Record;
-};
-
-type FilterOptions = {
-event_type?: string;
-object_type?: string;
-};
-
-export class TodoistSyncAPI {
-app:App;
-plugin: UltimateTodoistSyncForObsidian;
-
-constructor(app:App, plugin:UltimateTodoistSyncForObsidian) {
-//super(app,settings);
-this.app = app;
-this.plugin = plugin;
-}
-
-//backup todoist
-async getAllResources() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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();
-
-return data;
-} catch (error) {
-console.error(error);
-throw new Error('Failed to fetch all resources due to network error');
-}
-}
-
-//backup todoist
-async getUserResource() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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)
-return data;
-} catch (error) {
-console.error(error);
-throw new Error('Failed to fetch user resources due to network error');
-}
-}
-
-
-
-//update user timezone
-async updateUserTimezone() {
-const unixTimestampString: string = Math.floor(Date.now() / 1000).toString();
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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);
-
-if (!response.ok) {
-throw new Error(`Failed to fetch all resources: ${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 user resources due to network error');
-}
-}
-
-//get activity logs
-//result {count:number,events:[]}
-async getAllActivityEvents() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const headers = new Headers({
-Authorization: `Bearer ${accessToken}`
-});
-
-try {
-const response = await fetch('https://api.todoist.com/sync/v9/activity/get', {
-method: 'POST',
-headers,
-body: JSON.stringify({})
-});
-
-if (!response.ok) {
-throw new Error(`API returned error status: ${response.status}`);
-}
-
-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)
-
-}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)
-
-);
-};
-
-//get completed items activity
-//result {count:number,events:[]}
-async getCompletedItemsActivity() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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);
-
-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');
-}
-}
-
-
-
-//get uncompleted items activity
-//result {count:number,events:[]}
-async getUncompletedItemsActivity() {
-const accessToken = this.plugin.settings.todoistAPIToken
-const url = 'https://api.todoist.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.todoistAPIToken
-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.todoistAPIToken
-const url = 'https://api.todoist.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.todoistAPIToken
-const url = 'https://api.todoist.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');
-}
-}
-
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-