From e54040bf1bf703ee535c0899aa0b9ee0d64b8973 Mon Sep 17 00:00:00 2001 From: Chentao Yang Date: Tue, 23 Jun 2026 21:33:31 +0200 Subject: [PATCH] Improvements --- README.md | 22 +- eslint.config.mjs | 8 +- manifest.json | 2 +- package-lock.json | 488 ++++++++++++++++++++++++++++++++++++++++- package.json | 10 +- src/index.ts | 11 +- src/renderer.ts | 8 +- src/scheduler.ts | 9 + src/sqlite.ts | 4 +- src/things.ts | 4 +- test/scheduler.test.ts | 16 ++ tsconfig.json | 4 +- versions.json | 3 +- 13 files changed, 561 insertions(+), 28 deletions(-) create mode 100644 src/scheduler.ts create mode 100644 test/scheduler.test.ts diff --git a/README.md b/README.md index f6fce92..59a4a4d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Things Toolkit uses two access paths: In Auto mode, the plugin tries SQLite first and falls back to AppleScript if macOS privacy blocks the database. The settings tab shows the current access status and includes shortcuts to Full Disk Access and Automation privacy settings. +Failed periodic syncs wait for the configured sync interval before retrying. Repair syncs are idempotent: unchanged Things sections are left untouched. + ## Settings | Setting | Purpose | @@ -34,7 +36,25 @@ In Auto mode, the plugin tries SQLite first and falls back to AppleScript if mac | Section heading | Markdown heading where synced Things items are written. | | Tag prefix | Prefix used for imported Things tags. | | Include notes | Include Things task notes below each task. | -| Include project | Group by project when available. | +| Include project | Group project tasks under project headings instead of area headings. | + +## Development + +Install dependencies and run the complete local verification pipeline: + +```sh +npm install +npm run build +``` + +The build runs type-aware linting, scheduler regression tests, TypeScript checking, and the Rollup bundle. Test plugin builds in a dedicated Obsidian vault before using them with personal notes. + +## 1.6.0 + +- Fix external source audits that omit development-only type packages. +- Prevent rapid retry loops after failed periodic sync attempts. +- Correct project-heading precedence when project grouping is enabled. +- Add type-aware linting and scheduler regression tests to the build. ## Publishing diff --git a/eslint.config.mjs b/eslint.config.mjs index 6de4147..574c29e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,8 +16,14 @@ export default tseslint.config( files: ["src/**/*.ts"], extends: [ js.configs.recommended, - ...tseslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, ], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, rules: { "@typescript-eslint/no-explicit-any": "warn" }, diff --git a/manifest.json b/manifest.json index cc98471..917e37f 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "things-toolkit", "name": "Things Toolkit", "description": "Sync Things3 completions into daily notes with review stats and privacy-aware macOS access.", - "version": "1.5.0", + "version": "1.6.0", "author": "yangcht", "authorUrl": "https://github.com/yangcht", "isDesktopOnly": false, diff --git a/package-lock.json b/package-lock.json index 7cfdfa0..46896d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,16 @@ { "name": "obsidian-things-toolkit-plugin", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-things-toolkit-plugin", - "version": "1.5.0", + "version": "1.6.0", "license": "MIT", "dependencies": { + "@types/papaparse": "5.3.1", + "obsidian": "1.13.1", "obsidian-daily-notes-interface": "0.9.2", "papaparse": "5.3.1", "tslib": "2.2.0" @@ -19,12 +21,11 @@ "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "@types/node": "latest", - "@types/papaparse": "5.3.1", "@typescript-eslint/eslint-plugin": "latest", "@typescript-eslint/parser": "latest", "eslint": "latest", - "obsidian": "latest", "rollup": "^4.62.0", + "tsx": "4.22.4", "typescript": "latest", "typescript-eslint": "^8.61.0" } @@ -52,6 +53,422 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -732,7 +1149,6 @@ "version": "25.9.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -742,7 +1158,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz", "integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -1134,6 +1549,47 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1683,7 +2139,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.13.1.tgz", "integrity": "sha512-qtTEA2pmhJzhuhJqzbBFRYhpIOqvW+krDYjtFynv66KbxBbumHBlsJfWw3I4jtnK/6fZwbQhCrmmDdRwXmX56w==", - "dev": true, "license": "MIT", "dependencies": { "@types/codemirror": "5.60.8", @@ -2002,6 +2457,24 @@ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2057,7 +2530,6 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "dev": true, "license": "MIT" }, "node_modules/uri-js": { diff --git a/package.json b/package.json index 0c9e706..e109c14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-things-toolkit-plugin", - "version": "1.5.0", + "version": "1.6.0", "type": "module", "description": "Sync Things3 completions into Obsidian daily notes with review stats", "author": "yangcht", @@ -8,14 +8,17 @@ "license": "MIT", "scripts": { "lint": "eslint src/**/*.ts", + "test": "tsx --test test/*.test.ts", "typecheck": "tsc --noEmit", - "build": "npm run lint && npm run typecheck && rollup -c", + "build": "npm run lint && npm test && npm run typecheck && rollup -c", "build:nolint": "rollup -c", "release:create": "npm run build && VERSION=$(node -p \"require('./manifest.json').version\") && gh release create \"$VERSION\" main.js manifest.json styles.css --title \"$VERSION\" --notes \"Release $VERSION\"", "release:upload": "npm run build && VERSION=$(node -p \"require('./manifest.json').version\") && (gh release delete-asset \"$VERSION\" versions.json -y >/dev/null 2>&1 || true) && gh release upload \"$VERSION\" main.js manifest.json styles.css --clobber", "test:watch": "yarn test -- --watch" }, "dependencies": { + "@types/papaparse": "5.3.1", + "obsidian": "1.13.1", "obsidian-daily-notes-interface": "0.9.2", "papaparse": "5.3.1", "tslib": "2.2.0" @@ -26,12 +29,11 @@ "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "@types/node": "latest", - "@types/papaparse": "5.3.1", "@typescript-eslint/eslint-plugin": "latest", "@typescript-eslint/parser": "latest", "eslint": "latest", - "obsidian": "latest", "rollup": "^4.62.0", + "tsx": "4.22.4", "typescript": "latest", "typescript-eslint": "^8.61.0" } diff --git a/src/index.ts b/src/index.ts index 1cbeff4..63d10d3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ import { import { getMoment, type MomentLike } from "./moment"; import { ConfirmationModal } from "./modal"; import { ToolkitRenderer } from "./renderer"; +import { getNextSyncDelayMs } from "./scheduler"; import { ThingsToolkitReviewView, VIEW_TYPE_THINGS_TOOLKIT_REVIEW, @@ -66,6 +67,7 @@ export default class ThingsToolkitPlugin extends Plugin { }; private syncTimeoutId?: number; + private lastSyncAttemptTime = 0; private settingsTab?: ThingsToolkitSettingsTab; private selectedReviewDate = ""; private statusBarEl?: HTMLElement; @@ -271,6 +273,7 @@ export default class ThingsToolkitPlugin extends Plugin { return; } + this.lastSyncAttemptTime = moment().unix(); this.syncStatus = { isSyncing: true, message: "Syncing...", @@ -577,8 +580,12 @@ export default class ThingsToolkitPlugin extends Plugin { } const { latestSyncTime, syncInterval } = this.options; - const secondsUntilNextSync = latestSyncTime + syncInterval - now; - const nextSync = Math.max(secondsUntilNextSync * 1000, 20); + const nextSync = getNextSyncDelayMs( + latestSyncTime, + this.lastSyncAttemptTime, + syncInterval, + now + ); console.debug(`[Things Toolkit] next sync scheduled in ${nextSync}ms`); diff --git a/src/renderer.ts b/src/renderer.ts index 392a3d9..129e9a5 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1,6 +1,6 @@ -import { App } from "obsidian"; -import { ISettings } from "./settings"; -import { ISubTask, ITask } from "./things"; +import type { App } from "obsidian"; +import type { ISettings } from "./settings"; +import type { ISubTask, ITask } from "./things"; import { getHeadingLevel, getTab, groupBy, toHeading } from "./textUtils"; interface VaultConfigReader { @@ -64,7 +64,7 @@ export class ToolkitRenderer { const { sectionHeading, doesSyncProject, doesAddNewlineBeforeHeadings } = this.settings; const headings = groupBy( tasks, - (task) => task.area || (doesSyncProject ? task.project : "") || "" + (task) => (doesSyncProject ? task.project : undefined) || task.area || "" ); const headingLevel = getHeadingLevel(sectionHeading) ?? 2; diff --git a/src/scheduler.ts b/src/scheduler.ts new file mode 100644 index 0000000..d00f0a4 --- /dev/null +++ b/src/scheduler.ts @@ -0,0 +1,9 @@ +export function getNextSyncDelayMs( + latestSyncTime: number, + lastSyncAttemptTime: number, + syncInterval: number, + now: number +): number { + const scheduleFrom = Math.max(latestSyncTime, lastSyncAttemptTime); + return Math.max((scheduleFrom + syncInterval - now) * 1000, 20); +} diff --git a/src/sqlite.ts b/src/sqlite.ts index ac1fa06..d027d6c 100644 --- a/src/sqlite.ts +++ b/src/sqlite.ts @@ -38,8 +38,8 @@ async function handleSqliteQuery( } }; - const { spawn } = getChildProcessModule(); - const spawned = spawn( + const childProcess = getChildProcessModule(); + const spawned = childProcess.spawn( "sqlite3", ["-csv", "-header", "-readonly", dbPath, query], { detached: true } diff --git a/src/things.ts b/src/things.ts index 7155f80..93a2d26 100644 --- a/src/things.ts +++ b/src/things.ts @@ -199,8 +199,8 @@ function runAppleScript(script: string): Promise { } }; - const { spawn } = getChildProcessModule(); - const spawned = spawn("osascript", ["-e", script]); + const childProcess = getChildProcessModule(); + const spawned = childProcess.spawn("osascript", ["-e", script]); spawned.stdout.on("data", (chunk: unknown) => stdOut.push(chunkToString(chunk))); spawned.stderr.on("data", (chunk: unknown) => stdErr.push(chunkToString(chunk))); spawned.on("error", finish); diff --git a/test/scheduler.test.ts b/test/scheduler.test.ts new file mode 100644 index 0000000..fb9b352 --- /dev/null +++ b/test/scheduler.test.ts @@ -0,0 +1,16 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { getNextSyncDelayMs } from "../src/scheduler"; + +test("runs an overdue sync immediately", () => { + assert.equal(getNextSyncDelayMs(0, 0, 1800, 10_000), 20); +}); + +test("waits a full interval after a failed attempt", () => { + assert.equal(getNextSyncDelayMs(0, 10_000, 1800, 10_000), 1_800_000); +}); + +test("uses a newer successful sync when scheduling", () => { + assert.equal(getNextSyncDelayMs(12_000, 10_000, 1800, 12_300), 1_500_000); +}); diff --git a/tsconfig.json b/tsconfig.json index 948914b..fb5a16c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,6 @@ "lib": ["DOM", "ES2022"], "skipLibCheck": true }, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "test/**/*.ts"], "exclude": ["node_modules", "main.js"] -} \ No newline at end of file +} diff --git a/versions.json b/versions.json index ca9dabc..050ea8f 100644 --- a/versions.json +++ b/versions.json @@ -4,5 +4,6 @@ "1.2.0": "1.1.0", "1.3.0": "1.1.0", "1.4.0": "1.1.0", - "1.5.0": "1.1.0" + "1.5.0": "1.1.0", + "1.6.0": "1.1.0" }