From e0a2af6b3c6236fd6b5857b7b5a7ccc3aebc95f8 Mon Sep 17 00:00:00 2001 From: huojl Date: Thu, 30 Apr 2026 13:44:12 +0800 Subject: [PATCH] init --- .gitignore | 4 + esbuild.config.mjs | 31 +++ manifest.json | 9 + package-lock.json | 623 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 ++ src/api.ts | 110 ++++++++ src/formatter.ts | 116 +++++++++ src/main.ts | 123 +++++++++ src/settings.ts | 158 ++++++++++++ src/sync.ts | 204 +++++++++++++++ src/types.ts | 77 ++++++ styles.css | 10 + tsconfig.json | 17 ++ versions.json | 3 + 14 files changed, 1505 insertions(+) create mode 100644 .gitignore create mode 100644 esbuild.config.mjs create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/api.ts create mode 100644 src/formatter.ts create mode 100644 src/main.ts create mode 100644 src/settings.ts create mode 100644 src/sync.ts create mode 100644 src/types.ts create mode 100644 styles.css create mode 100644 tsconfig.json create mode 100644 versions.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d51b70a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +main.js +main.js.map +data.json diff --git a/esbuild.config.mjs b/esbuild.config.mjs new file mode 100644 index 0000000..e9e2af5 --- /dev/null +++ b/esbuild.config.mjs @@ -0,0 +1,31 @@ +import esbuild from "esbuild"; +import process from "process"; +import { builtinModules } from "node:module"; + +const prod = (process.argv[2] === "production"); + +const context = await esbuild.context({ + entryPoints: ["src/main.ts"], + bundle: true, + external: [ + "obsidian", + "electron", + "@codemirror/autocomplete", + "@codemirror/state", + "@codemirror/view", + ...builtinModules + ], + format: "cjs", + target: "es2018", + sourcemap: prod ? false : "inline", + treeShaking: true, + outfile: "main.js", + minify: prod, +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..86df8e0 --- /dev/null +++ b/manifest.json @@ -0,0 +1,9 @@ +{ + "id": "znote-sync", + "name": "Znote Sync", + "version": "1.0.0", + "minAppVersion": "0.15.0", + "description": "Sync recordings from Znote (SonicNote) to Obsidian as Markdown files", + "author": "EasyLink", + "isDesktopOnly": false +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..145eca2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,623 @@ +{ + "name": "znote-sync", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "znote-sync", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^16.11.6", + "esbuild": "0.25.5", + "obsidian": "latest", + "tslib": "2.4.0", + "typescript": "5.8.3" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.0.tgz", + "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.6", + "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.6.tgz", + "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmmirror.com/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmmirror.com/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/obsidian": { + "version": "1.12.3", + "resolved": "https://registry.npmmirror.com/obsidian/-/obsidian-1.12.3.tgz", + "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "6.5.0", + "@codemirror/view": "6.38.6" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT", + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d1346ca --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "znote-sync", + "version": "1.0.0", + "description": "Sync recordings from Znote (SonicNote) to Obsidian as Markdown files", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production" + }, + "keywords": ["obsidian", "znote", "recording", "sync"], + "author": "EasyLink", + "license": "MIT", + "devDependencies": { + "@types/node": "^16.11.6", + "esbuild": "0.25.5", + "obsidian": "latest", + "tslib": "2.4.0", + "typescript": "5.8.3" + } +} diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..8fad6f5 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,110 @@ +import { requestUrl, RequestUrlParam } from 'obsidian'; +import { BackendResponse, Recording, TranscriptSegment, SummaryData, ZnotePluginSettings } from './types'; + +export class ZnoteApiClient { + constructor(private getSettings: () => ZnotePluginSettings) {} + + isAuthenticated(): boolean { + return this.getSettings().token !== ''; + } + + private get serverUrl(): string { + return this.getSettings().serverUrl; + } + + private get token(): string { + return this.getSettings().token; + } + + private async request(method: 'GET' | 'POST', path: string, options?: { query?: Record; body?: unknown }): Promise { + let url = `${this.serverUrl}${path}`; + if (options?.query) { + const params = new URLSearchParams(); + for (const [k, v] of Object.entries(options.query)) { + if (v !== undefined && v !== null) params.set(k, String(v)); + } + url += '?' + params.toString(); + } + + const headers: Record = {}; + if (this.token) { + headers['Authorization'] = `Bearer ${this.token}`; + } + + const params: RequestUrlParam = { + url, + method, + headers, + }; + + if (method === 'POST' && options?.body) { + headers['Content-Type'] = 'application/json'; + params.body = JSON.stringify(options.body); + } + + const response = await requestUrl(params); + return response.json as BackendResponse; + } + + async login(phone: string, code: string): Promise<{ token: string; userId: string }> { + const res = await this.request('POST', '/app/user/loginByPhone', { + body: { phonenumber: phone, code: code }, + }); + if (res.code !== 200) { + throw new Error(res.msg || '登录失败'); + } + return { + token: res.data.token || res.data, + userId: res.data.userId || '', + }; + } + + async fetchRecordingList(page: number, size: number): Promise<{ list: Recording[]; total: number }> { + const res = await this.request('GET', '/app/recording/list', { + query: { page, size }, + }); + if (res.code !== 200) { + throw new Error(res.msg || '获取录音列表失败'); + } + return { + list: res.data?.records || res.data?.list || [], + total: res.data?.total || 0, + }; + } + + async fetchRecordingDetail(audioId: string): Promise { + const res = await this.request('GET', '/app/recording/detail', { + query: { audioId }, + }); + if (res.code !== 200) { + throw new Error(res.msg || '获取录音详情失败'); + } + return res.data; + } + + async fetchNote(audioId: string): Promise { + const res = await this.request('GET', '/app/recording/getNote', { + query: { audioId }, + }); + if (res.code !== 200) { + return ''; + } + return res.data?.note || ''; + } + + async fetchTranscriptResult(audioId: string): Promise { + const res = await this.request('GET', `/share/${audioId}/transcript/result`); + if (res.code !== 200) { + return []; + } + return res.data || []; + } + + async fetchSummary(audioId: string): Promise { + const res = await this.request('GET', `/share/${audioId}/summary`); + if (res.code !== 200) { + return null; + } + return res.data; + } +} diff --git a/src/formatter.ts b/src/formatter.ts new file mode 100644 index 0000000..d07dab4 --- /dev/null +++ b/src/formatter.ts @@ -0,0 +1,116 @@ +import { Recording, TranscriptSegment, SummaryData } from './types'; + +export function sanitizeFileName(name: string): string { + return name + .replace(/[\\/:*?"<>|]/g, '-') + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, '') + || 'untitled'; +} + +export function formatFileName(recording: Recording): string { + const displayName = recording.recordNickName || recording.recordName || '未命名录音'; + const date = recording.recordTime + ? recording.recordTime.substring(0, 10) + : new Date().toISOString().substring(0, 10); + return `${date}-${sanitizeFileName(displayName)}`; +} + +function formatDuration(seconds: string | number): string { + const s = Number(seconds) || 0; + const h = Math.floor(s / 3600); + const m = Math.floor((s % 3600) / 60); + const sec = Math.floor(s % 60); + if (h > 0) { + return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`; + } + return `${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`; +} + +function formatTranscriptTime(ms: number): string { + const totalSeconds = Math.floor(ms / 1000); + return formatDuration(totalSeconds); +} + +export function generateFrontmatter(recording: Recording, syncTime: string): string { + const lines: string[] = [ + '---', + `audio_id: "${recording.audioId}"`, + `record_name: "${recording.recordName || ''}"`, + `record_nick_name: "${(recording.recordNickName || '').replace(/"/g, '\\"')}"`, + `duration: "${recording.duration || '0'}"`, + `record_time: "${recording.recordTime || ''}"`, + `record_type: "${recording.recordType || ''}"`, + `device_name: "${recording.deviceName || ''}"`, + ]; + + if (recording.audioUrl) { + lines.push(`audio_url: "${recording.audioUrl}"`); + } + + lines.push('tags:'); + lines.push(' - znote'); + + const recordTypeLabel = recording.recordType === '00' ? '通话' : '录音'; + lines.push(` - ${recordTypeLabel}`); + + lines.push(`sync_time: "${syncTime}"`); + lines.push('---'); + return lines.join('\n'); +} + +export function formatTranscript(segments: TranscriptSegment[]): string { + if (!segments || segments.length === 0) return ''; + + return segments.map(seg => { + const time = formatTranscriptTime(seg.time); + const speaker = seg.spokesperson || '未知'; + return `**[${time}] ${speaker}:** ${seg.text}`; + }).join('\n\n'); +} + +export function toMarkdown( + recording: Recording, + transcript: TranscriptSegment[] | null, + summary: SummaryData | null, + note: string, + syncTime: string +): string { + const parts: string[] = []; + + // Frontmatter + parts.push(generateFrontmatter(recording, syncTime)); + parts.push(''); + + // Title + const title = recording.recordNickName || recording.recordName || '未命名录音'; + parts.push(`# ${title}`); + parts.push(''); + + // Note section + if (note && note.trim()) { + parts.push('## 笔记'); + parts.push(''); + parts.push(note.trim()); + parts.push(''); + } + + // Transcript section + if (transcript && transcript.length > 0) { + parts.push('## 转录内容'); + parts.push(''); + parts.push(formatTranscript(transcript)); + parts.push(''); + } + + // Summary section + if (summary && summary.summaryContent) { + parts.push('## AI 总结'); + parts.push(''); + parts.push(summary.summaryContent.trim()); + parts.push(''); + } + + return parts.join('\n'); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..39b58e8 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,123 @@ +import { Notice, Plugin } from 'obsidian'; +import { ZnoteApiClient } from './api'; +import { SyncService } from './sync'; +import { ZnoteSettingTab } from './settings'; +import { DEFAULT_SETTINGS, ZnotePluginSettings } from './types'; + +export default class ZnoteSyncPlugin extends Plugin { + settings: ZnotePluginSettings = DEFAULT_SETTINGS; + private api!: ZnoteApiClient; + private syncService!: SyncService; + private statusBarEl!: HTMLElement; + + async onload() { + await this.loadSettings(); + + // Initialize API client and sync service + this.api = new ZnoteApiClient(() => this.settings); + this.syncService = new SyncService( + this.app, + this.api, + () => this.settings, + () => this.saveSettings() + ); + + // Status bar + this.statusBarEl = this.addStatusBarItem(); + this.updateStatusBar(); + + // Ribbon icon + this.addRibbonIcon('headphones', 'Znote Sync: 同步录音', () => { + this.triggerSync(); + }); + + // Commands + this.addCommand({ + id: 'znote-sync:sync', + name: '同步录音', + callback: () => this.triggerSync(), + }); + + this.addCommand({ + id: 'znote-sync:login', + name: '登录', + callback: () => { + // @ts-ignore - internal API to open settings + this.app.setting.open(); + // @ts-ignore + this.app.setting.openTabById('znote-sync'); + }, + }); + + this.addCommand({ + id: 'znote-sync:logout', + name: '登出', + callback: async () => { + this.settings.token = ''; + this.settings.phoneNumber = ''; + await this.saveSettings(); + new Notice('已登出 Znote'); + this.updateStatusBar(); + }, + }); + + // Settings tab + this.addSettingTab(new ZnoteSettingTab( + this.app, + this, + this.api, + () => this.settings, + () => this.saveSettings() + )); + + console.log('Znote Sync plugin loaded'); + } + + onunload() { + console.log('Znote Sync plugin unloaded'); + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } + + private async triggerSync() { + if (!this.api.isAuthenticated()) { + new Notice('请先登录 Znote(设置 → Znote Sync)'); + return; + } + + this.statusBarEl.setText('Znote: 同步中...'); + + try { + const result = await this.syncService.syncAll((msg) => { + this.statusBarEl.setText(`Znote: ${msg}`); + }); + + let message = `同步完成: ${result.synced} 条新/更新`; + if (result.skipped > 0) message += `, ${result.skipped} 条跳过`; + if (result.errors > 0) message += `, ${result.errors} 条失败`; + + new Notice(message, 5000); + this.updateStatusBar(); + } catch (e) { + new Notice(`同步失败: ${e instanceof Error ? e.message : '未知错误'}`); + this.updateStatusBar(); + } + } + + private updateStatusBar() { + if (this.api.isAuthenticated()) { + const lastSync = this.settings.lastSyncTime + ? `上次同步: ${this.settings.lastSyncTime.substring(0, 16).replace('T', ' ')}` + : '未同步'; + this.statusBarEl.setText(`Znote: ${lastSync}`); + } else { + this.statusBarEl.setText('Znote: 未登录'); + } + } +} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..ceac65f --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,158 @@ +import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; +import { ZnoteApiClient } from './api'; +import { ZnotePluginSettings, DEFAULT_SETTINGS } from './types'; + +export class ZnoteSettingTab extends PluginSettingTab { + private api: ZnoteApiClient; + private getSettings: () => ZnotePluginSettings; + private saveSettings: () => Promise; + + constructor( + app: App, + plugin: Plugin, + api: ZnoteApiClient, + getSettings: () => ZnotePluginSettings, + saveSettings: () => Promise + ) { + super(app, plugin); + this.api = api; + this.getSettings = getSettings; + this.saveSettings = saveSettings; + } + + display(): void { + const { containerEl } = this; + const settings = this.getSettings(); + containerEl.empty(); + + containerEl.createEl('h2', { text: 'Znote Sync 设置' }); + + // Server URL + new Setting(containerEl) + .setName('服务器地址') + .setDesc('智音笔记后端服务地址') + .addText(text => text + .setPlaceholder('http://localhost:8048') + .setValue(settings.serverUrl) + .onChange(async (value) => { + settings.serverUrl = value; + await this.saveSettings(); + })); + + // Sync folder + new Setting(containerEl) + .setName('同步文件夹') + .setDesc('录音 Markdown 文件存放的文件夹(相对于 Vault 根目录)') + .addText(text => text + .setPlaceholder('ZnoteSync') + .setValue(settings.syncFolder) + .onChange(async (value) => { + settings.syncFolder = value; + await this.saveSettings(); + })); + + // Login status & actions + const loginSection = containerEl.createDiv(); + loginSection.createEl('h3', { text: '账号' }); + + if (this.api.isAuthenticated()) { + new Setting(loginSection) + .setName('登录状态') + .setDesc(`已登录: ${settings.phoneNumber}`) + .addButton(btn => btn + .setButtonText('登出') + .setWarning() + .onClick(async () => { + settings.token = ''; + settings.phoneNumber = ''; + await this.saveSettings(); + new Notice('已登出'); + this.display(); + })); + } else { + new Setting(loginSection) + .setName('登录') + .setDesc('使用手机号和验证码登录智音笔记') + .addButton(btn => btn + .setButtonText('登录') + .onClick(() => { + new LoginModal(this.app, this.api, this.getSettings, this.saveSettings, () => this.display()).open(); + })); + } + } +} + +class LoginModal extends Modal { + private api: ZnoteApiClient; + private getSettings: () => ZnotePluginSettings; + private saveSettings: () => Promise; + private onCloseCallback: () => void; + + constructor( + app: App, + api: ZnoteApiClient, + getSettings: () => ZnotePluginSettings, + saveSettings: () => Promise, + onCloseCallback: () => void + ) { + super(app); + this.api = api; + this.getSettings = getSettings; + this.saveSettings = saveSettings; + this.onCloseCallback = onCloseCallback; + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + + contentEl.createEl('h2', { text: '登录智音笔记' }); + + let phone = ''; + let code = ''; + + new Setting(contentEl) + .setName('手机号') + .addText(text => text + .setPlaceholder('请输入手机号') + .onChange((value) => { phone = value; })); + + new Setting(contentEl) + .setName('验证码') + .addText(text => text + .setPlaceholder('请输入验证码') + .onChange((value) => { code = value; })); + + new Setting(contentEl) + .addButton(btn => btn + .setButtonText('登录') + .setCta() + .onClick(async () => { + if (!phone || !code) { + new Notice('请输入手机号和验证码'); + return; + } + try { + btn.setButtonText('登录中...'); + btn.setDisabled(true); + const result = await this.api.login(phone, code); + const settings = this.getSettings(); + settings.token = result.token; + settings.phoneNumber = phone; + await this.saveSettings(); + new Notice('登录成功'); + this.close(); + } catch (e) { + new Notice(`登录失败: ${e instanceof Error ? e.message : '未知错误'}`); + btn.setButtonText('登录'); + btn.setDisabled(false); + } + })); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + this.onCloseCallback(); + } +} diff --git a/src/sync.ts b/src/sync.ts new file mode 100644 index 0000000..b568a7a --- /dev/null +++ b/src/sync.ts @@ -0,0 +1,204 @@ +import { App, Notice, TFile, TFolder } from 'obsidian'; +import { ZnoteApiClient } from './api'; +import { ZnotePluginSettings, LocalFileInfo, Recording, SyncResult, TranscriptSegment, SummaryData } from './types'; +import { formatFileName, toMarkdown } from './formatter'; + +export class SyncService { + private api: ZnoteApiClient; + private app: App; + private getSettings: () => ZnotePluginSettings; + private saveSettings: () => Promise; + + constructor( + app: App, + api: ZnoteApiClient, + getSettings: () => ZnotePluginSettings, + saveSettings: () => Promise + ) { + this.app = app; + this.api = api; + this.getSettings = getSettings; + this.saveSettings = saveSettings; + } + + async syncAll(onProgress?: (msg: string) => void): Promise { + const settings = this.getSettings(); + const result: SyncResult = { total: 0, synced: 0, skipped: 0, errors: 0, errorMessages: [] }; + + // 1. Ensure sync folder exists + await this.ensureFolder(settings.syncFolder); + + // 2. Build local index + onProgress?.('正在读取本地索引...'); + const localIndex = await this.buildLocalIndex(settings.syncFolder); + + // 3. Fetch all recordings from backend + onProgress?.('正在从服务器拉取录音列表...'); + let page = 1; + let allRecordings: Recording[] = []; + + while (true) { + try { + const res = await this.api.fetchRecordingList(page, settings.pageSize); + allRecordings = allRecordings.concat(res.list); + if (allRecordings.length >= res.total || res.list.length === 0) break; + page++; + } catch (e) { + const msg = e instanceof Error ? e.message : '获取录音列表失败'; + result.errorMessages.push(msg); + new Notice(`同步失败: ${msg}`); + return result; + } + } + + // Filter out deleted + allRecordings = allRecordings.filter(r => r.delFlag !== '2'); + result.total = allRecordings.length; + + // 4. Process each recording + for (let i = 0; i < allRecordings.length; i++) { + const recording = allRecordings[i]; + onProgress?.(`正在同步 ${i + 1}/${result.total}: ${recording.recordNickName || recording.recordName}`); + + try { + await this.processRecording(recording, localIndex, settings); + result.synced++; + } catch (e) { + result.errors++; + const msg = e instanceof Error ? e.message : '未知错误'; + result.errorMessages.push(`${recording.recordNickName || recording.recordName}: ${msg}`); + } + } + + // 5. Update last sync time + const now = new Date().toISOString(); + settings.lastSyncTime = now; + await this.saveSettings(); + + return result; + } + + private async ensureFolder(folderPath: string): Promise { + const folder = this.app.vault.getAbstractFileByPath(folderPath); + if (!folder) { + await this.app.vault.createFolder(folderPath); + } + } + + async buildLocalIndex(syncFolder: string): Promise> { + const index = new Map(); + const folder = this.app.vault.getAbstractFileByPath(syncFolder); + if (!(folder instanceof TFolder)) return index; + + for (const file of folder.children) { + if (!(file instanceof TFile) || file.extension !== 'md') continue; + + try { + const content = await this.app.vault.read(file); + const audioId = this.extractFrontmatterField(content, 'audio_id'); + const syncTime = this.extractFrontmatterField(content, 'sync_time'); + if (audioId) { + index.set(audioId.replace(/"/g, ''), { path: file.path, syncTime: syncTime || '' }); + } + } catch { + // Skip files that can't be read + } + } + + return index; + } + + private extractFrontmatterField(content: string, field: string): string | null { + const match = content.match(new RegExp(`^${field}:\\s*"?([^"]*)"\\s*$`, 'm')); + return match ? match[1] : null; + } + + private async processRecording(recording: Recording, localIndex: Map, settings: ZnotePluginSettings): Promise { + const syncTime = new Date().toISOString(); + + // Check if we need to update + const local = localIndex.get(recording.audioId); + if (local && local.syncTime && recording.updateTime) { + if (recording.updateTime <= local.syncTime) { + // No update needed + return; + } + + // Check for conflict (user edited file locally after sync) + const file = this.app.vault.getAbstractFileByPath(local.path); + if (file instanceof TFile) { + const fileMtime = new Date(file.stat.mtime).toISOString(); + if (fileMtime > local.syncTime) { + // Conflict — write to a new file instead + await this.writeRecordingToNewFile(recording, syncTime, settings, local.path); + return; + } + + // Update existing file + const content = await this.buildRecordingContent(recording, syncTime); + await this.app.vault.modify(file, content); + localIndex.set(recording.audioId, { path: local.path, syncTime }); + return; + } + } + + // New recording — create file + await this.writeRecordingToNewFile(recording, syncTime, settings); + localIndex.set(recording.audioId, { + path: `${settings.syncFolder}/${formatFileName(recording)}.md`, + syncTime, + }); + } + + private async writeRecordingToNewFile(recording: Recording, syncTime: string, settings: ZnotePluginSettings, conflictWithPath?: string): Promise { + const content = await this.buildRecordingContent(recording, syncTime); + let fileName = formatFileName(recording); + + if (conflictWithPath) { + // Create conflict copy + const timestamp = Date.now(); + fileName = `${fileName}-conflict-${timestamp}`; + } + + // Deduplicate filename + let filePath = `${settings.syncFolder}/${fileName}.md`; + let counter = 2; + while (await this.app.vault.adapter.exists(filePath)) { + filePath = `${settings.syncFolder}/${fileName}-${counter}.md`; + counter++; + } + + await this.app.vault.create(filePath, content); + } + + private async buildRecordingContent(recording: Recording, syncTime: string): Promise { + const tasks: [string, Promise][] = []; + + // Fetch transcript if available (status 2 = completed) + let transcript: TranscriptSegment[] | null = null; + if (recording.transcriptStatus === 2) { + tasks.push(['transcript', this.api.fetchTranscriptResult(recording.audioId).catch(() => null)]); + } + + // Fetch summary if available (status 2 = completed) + let summary: SummaryData | null = null; + if (recording.summaryStatus === 2) { + tasks.push(['summary', this.api.fetchSummary(recording.audioId).catch(() => null)]); + } + + // Fetch note + let note = ''; + tasks.push(['note', this.api.fetchNote(recording.audioId).catch(() => '')]); + + // Execute all requests in parallel + const results = await Promise.all(tasks.map(([_, p]) => p)); + tasks.forEach(([name], i) => { + const val = results[i]; + if (name === 'transcript') transcript = val as TranscriptSegment[] | null; + if (name === 'summary') summary = val as SummaryData | null; + if (name === 'note') note = (val as string) || ''; + }); + + return toMarkdown(recording, transcript, summary, note, syncTime); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..485d5fc --- /dev/null +++ b/src/types.ts @@ -0,0 +1,77 @@ +// ===== 插件设置 ===== + +export interface ZnotePluginSettings { + serverUrl: string; + syncFolder: string; + pageSize: number; + token: string; + phoneNumber: string; + lastSyncTime: string; +} + +export const DEFAULT_SETTINGS: ZnotePluginSettings = { + serverUrl: 'http://ainote.easylinkin.com:8048', + syncFolder: 'ZnoteSync', + pageSize: 50, + token: '', + phoneNumber: '', + lastSyncTime: '', +}; + +// ===== 后端数据类型 ===== + +export interface BackendResponse { + code: number; + msg: string; + data: any; +} + +export interface Recording { + audioId: string; + userId: string; + deviceId: string; + audioUrl: string; + recordTime: string; + recordName: string; + recordNickName: string; + duration: string; + note: string; + recordType: string; + isTranscribed: string; + isSummarized: string; + transcriptStatus: number; + summaryStatus: number; + delFlag: string; + deviceName: string; + createTime: string; + updateTime: string; +} + +export interface TranscriptSegment { + spokesperson: string; + text: string; + time: number; +} + +export interface SummaryData { + summaryId: string; + audioId: string; + summaryContent: string; + templateId: string; + status: number; +} + +// ===== 同步相关 ===== + +export interface LocalFileInfo { + path: string; + syncTime: string; +} + +export interface SyncResult { + total: number; + synced: number; + skipped: number; + errors: number; + errorMessages: string[]; +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..457130d --- /dev/null +++ b/styles.css @@ -0,0 +1,10 @@ +/* Znote Sync Plugin Styles */ + +/* Login modal */ +.znote-login-modal .setting-item { + padding: 8px 0; +} + +.znote-login-modal .setting-item-control input { + width: 200px; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..504ef41 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "lib": ["DOM", "ES5", "ES6", "ES7"] + }, + "include": ["src/**/*.ts"] +} diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..af9a39e --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "1.0.0": "0.15.0" +}