From 35659829c98010d5636511dbb930410d52b341f7 Mon Sep 17 00:00:00 2001 From: Eric Neuber Date: Fri, 26 Jun 2026 10:04:47 +0200 Subject: [PATCH] initialer Commit --- .gitignore | 3 + README.md | 33 ++ index.html | 28 + package-lock.json | 1163 ++++++++++++++++++++++++++++++++++++++++++ package.json | 31 ++ src/aggregation.ts | 72 +++ src/export.ts | 27 + src/index.ts | 202 ++++++++ src/storage.ts | 47 ++ src/timer-service.ts | 140 +++++ src/types.ts | 42 ++ src/ui.ts | 390 ++++++++++++++ tsconfig.json | 21 + vite.config.ts | 10 + 14 files changed, 2209 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/aggregation.ts create mode 100644 src/export.ts create mode 100644 src/index.ts create mode 100644 src/storage.ts create mode 100644 src/timer-service.ts create mode 100644 src/types.ts create mode 100644 src/ui.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c25e1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2b3e3e --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Logseq Page Timetracker + +Track time per page in Logseq with one active timer at a time. + +## Current status + +Initial MVP implementation in progress. + +## Development + +1. Install dependencies: `npm install` +2. Build once: `npm run build` +3. Build in watch mode: `npm run dev` +4. In Logseq, load plugin from this folder. + +## Commands (MVP) + +- Start tracking current page +- Stop active tracking +- Switch tracking to current page +- Open timetracker UI +- Export entries JSON + +## Data model + +- Single active timer at any time. +- Completed entries with editable timestamps. +- Stored in browser local storage by plugin key. + +## Export + +- JSON export is the canonical, versioned format. +- The file can be consumed by external scripts. diff --git a/index.html b/index.html new file mode 100644 index 0000000..b220430 --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ + + + + + + Logseq Page Timetracker + + + +
Loading timetracker...
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f5e59ac --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1163 @@ +{ + "name": "logseq-page-timetracker", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "logseq-page-timetracker", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@logseq/libs": "^0.0.17" + }, + "devDependencies": { + "typescript": "^5.5.4", + "vite": "^5.3.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@logseq/libs": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/@logseq/libs/-/libs-0.0.17.tgz", + "integrity": "sha512-SkzzAaocmrgeHYrCOaRyEqzPOxw3d0qVEZSrt9qVvXE4tuEgbvEHR8tzI1N5RjgAv+PDWuGPiP7/mhcXHpINEw==", + "dependencies": { + "csstype": "3.1.0", + "debug": "4.3.4", + "deepmerge": "4.3.1", + "dompurify": "2.3.8", + "eventemitter3": "4.0.7", + "fast-deep-equal": "3.1.3", + "lodash-es": "4.17.21", + "path": "0.12.7", + "snake-case": "3.0.4" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dompurify": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz", + "integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw==", + "license": "(MPL-2.0 OR Apache-2.0)" + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f31cdcb --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "logseq-page-timetracker", + "version": "0.1.0", + "description": "Logseq plugin to track time per page with a single active timer.", + "main": "dist/index.html", + "type": "module", + "scripts": { + "dev": "vite build --watch", + "build": "vite build", + "check": "tsc --noEmit" + }, + "keywords": [ + "logseq", + "plugin", + "time-tracking" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@logseq/libs": "^0.0.17" + }, + "devDependencies": { + "typescript": "^5.5.4", + "vite": "^5.3.1" + }, + "logseq": { + "id": "logseq-page-timetracker", + "title": "Page Timetracker", + "icon": "./icon.png" + } +} diff --git a/src/aggregation.ts b/src/aggregation.ts new file mode 100644 index 0000000..2ac13c8 --- /dev/null +++ b/src/aggregation.ts @@ -0,0 +1,72 @@ +import type { DaySummary, TimeEntry, WeekSummary } from "./types"; + +const DAY_MS = 24 * 60 * 60 * 1000; + +function dayStartMs(value: number): number { + const d = new Date(value); + return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); +} + +function dayKey(value: number): string { + const d = new Date(value); + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +} + +function isoWeekKey(value: number): string { + const d = new Date(value); + const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); + const dayNum = date.getUTCDay() || 7; + date.setUTCDate(date.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1)); + const weekNo = Math.ceil((((date.getTime() - yearStart.getTime()) / DAY_MS) + 1) / 7); + return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`; +} + +export function splitEntryAcrossDays(entry: TimeEntry): DaySummary[] { + const result: DaySummary[] = []; + let cursor = entry.startedAtEpochMs; + const end = entry.endedAtEpochMs; + + while (cursor < end) { + const startOfDay = dayStartMs(cursor); + const nextDay = startOfDay + DAY_MS; + const segmentEnd = Math.min(end, nextDay); + result.push({ + day: dayKey(cursor), + durationMs: segmentEnd - cursor + }); + cursor = segmentEnd; + } + + return result; +} + +export function computeDaySummaries(entries: TimeEntry[]): DaySummary[] { + const map = new Map(); + for (const entry of entries) { + for (const segment of splitEntryAcrossDays(entry)) { + map.set(segment.day, (map.get(segment.day) ?? 0) + segment.durationMs); + } + } + + return [...map.entries()] + .map(([day, durationMs]) => ({ day, durationMs })) + .sort((a, b) => a.day.localeCompare(b.day)); +} + +export function computeWeekSummaries(entries: TimeEntry[]): WeekSummary[] { + const daySummaries = computeDaySummaries(entries); + const map = new Map(); + + for (const day of daySummaries) { + const week = isoWeekKey(new Date(`${day.day}T12:00:00`).getTime()); + map.set(week, (map.get(week) ?? 0) + day.durationMs); + } + + return [...map.entries()] + .map(([week, durationMs]) => ({ week, durationMs })) + .sort((a, b) => a.week.localeCompare(b.week)); +} diff --git a/src/export.ts b/src/export.ts new file mode 100644 index 0000000..f31aed6 --- /dev/null +++ b/src/export.ts @@ -0,0 +1,27 @@ +import type { TimeEntry, TrackerState } from "./types"; + +interface ExportEnvelope { + schema: "logseq-page-timetracker-export"; + schemaVersion: 1; + exportedAtIso: string; + entries: TimeEntry[]; +} + +export function createExportPayload(state: TrackerState): ExportEnvelope { + return { + schema: "logseq-page-timetracker-export", + schemaVersion: 1, + exportedAtIso: new Date().toISOString(), + entries: state.entries + }; +} + +export function downloadJson(filename: string, content: string): void { + const blob = new Blob([content], { type: "application/json;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.click(); + URL.revokeObjectURL(url); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d4a6746 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,202 @@ +import "@logseq/libs"; + +import { createExportPayload, downloadJson } from "./export"; +import { TimerService } from "./timer-service"; +import type { PageRef } from "./types"; +import { TimetrackerUi } from "./ui"; + +declare const logseq: any; + +const service = new TimerService(); +let isUiVisible = false; + +async function currentPage(): Promise { + const page = await logseq.Editor.getCurrentPage(); + if (!page) { + return null; + } + return { + uuid: page.uuid ?? null, + name: page.name ?? page.originalName ?? "Untitled" + }; +} + +async function listPages(): Promise { + try { + const rows = await logseq.DB.datascriptQuery( + `[:find ?name ?uuid + :where + [?p :block/name ?name] + [?p :block/uuid ?uuid]]` + ); + + if (!Array.isArray(rows)) { + return []; + } + + return rows + .map((row: unknown) => { + if (!Array.isArray(row) || row.length < 2) { + return null; + } + + const [name, uuid] = row; + return { + name: String(name), + uuid: uuid ? String(uuid) : null + }; + }) + .filter((page: PageRef | null): page is PageRef => Boolean(page)) + .sort((a, b) => a.name.localeCompare(b.name)); + } catch { + return []; + } +} + +function openUi(): void { + logseq.showMainUI({ autoFocus: false }); +} + +function closeUi(): void { + logseq.hideMainUI(); +} + +function toggleUi(): void { + if (isUiVisible) { + closeUi(); + return; + } + + openUi(); +} + +async function startCurrentPage(): Promise { + const page = await currentPage(); + if (!page) { + logseq.App.showMsg("No current page found.", "warning"); + return; + } + service.startForPage(page); + logseq.App.showMsg(`Tracking started for ${page.name}.`, "success"); +} + +async function switchCurrentPage(): Promise { + const page = await currentPage(); + if (!page) { + logseq.App.showMsg("No current page found.", "warning"); + return; + } + service.switchToPage(page); + logseq.App.showMsg(`Tracking switched to ${page.name}.`, "success"); +} + +function stopActive(): void { + service.stopActive(); + logseq.App.showMsg("Active tracking stopped.", "success"); +} + +function exportJson(): void { + const payload = createExportPayload(service.getState()); + const content = JSON.stringify(payload, null, 2); + downloadJson(`timetracker-export-${new Date().toISOString().slice(0, 10)}.json`, content); + logseq.App.showMsg("JSON export downloaded.", "success"); +} + +async function main(): Promise { + const ui = new TimetrackerUi(service, currentPage, listPages); + + logseq.setMainUIInlineStyle({ + zIndex: 11, + width: "100vw", + height: "100vh", + left: "0", + top: "0", + backgroundColor: "transparent", + overflow: "hidden" + }); + + ui.init(); + + logseq.App.registerCommandPalette( + { + key: "page-timetracker-start-current-page", + label: "Page Timetracker: Start tracking current page" + }, + startCurrentPage + ); + + logseq.App.registerCommandPalette( + { + key: "page-timetracker-stop-active", + label: "Page Timetracker: Stop active tracking" + }, + stopActive + ); + + logseq.App.registerCommandPalette( + { + key: "page-timetracker-switch-current-page", + label: "Page Timetracker: Switch tracking to current page" + }, + switchCurrentPage + ); + + logseq.App.registerCommandPalette( + { + key: "page-timetracker-open-ui", + label: "Page Timetracker: Open timetracker UI" + }, + openUi + ); + + logseq.App.registerCommandPalette( + { + key: "page-timetracker-close-ui", + label: "Page Timetracker: Close timetracker UI" + }, + closeUi + ); + + logseq.App.registerCommandPalette( + { + key: "page-timetracker-export-json", + label: "Page Timetracker: Export entries JSON" + }, + exportJson + ); + + logseq.Editor.registerSlashCommand("Timetracker: Start current page", startCurrentPage); + logseq.Editor.registerSlashCommand("Timetracker: Stop active", stopActive); + logseq.Editor.registerSlashCommand("Timetracker: Switch to current page", switchCurrentPage); + + logseq.App.registerPageMenuItem("Track this page", startCurrentPage); + + logseq.App.registerUIItem("toolbar", { + key: "page-timetracker-toolbar", + template: `TT` + }); + + logseq.provideModel({ + pageTimetrackerOpen: openUi, + pageTimetrackerClose: closeUi, + pageTimetrackerToggle: toggleUi + }); + + logseq.on("ui:visible:changed", ({ visible }: { visible: boolean }) => { + isUiVisible = visible; + if (visible) { + ui.render(); + } + }); + + logseq.beforeunload(() => { + service.persist(); + ui.destroy(); + }); + + logseq.App.showMsg("Page Timetracker loaded.", "success"); +} + +logseq.ready(main).catch((error: unknown) => { + console.error("Page Timetracker failed to load", error); +}); diff --git a/src/storage.ts b/src/storage.ts new file mode 100644 index 0000000..4f57be3 --- /dev/null +++ b/src/storage.ts @@ -0,0 +1,47 @@ +import type { TrackerState } from "./types"; + +const STORAGE_KEY = "logseq-page-timetracker/state"; +const SCHEMA = "logseq-page-timetracker"; +const SCHEMA_VERSION = 1; + +function defaultState(): TrackerState { + return { + schema: SCHEMA, + schemaVersion: SCHEMA_VERSION, + activeTimer: null, + entries: [] + }; +} + +export function loadState(): TrackerState { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + return defaultState(); + } + + try { + const parsed = JSON.parse(raw) as Partial; + if (parsed.schema !== SCHEMA) { + return defaultState(); + } + + return { + schema: SCHEMA, + schemaVersion: SCHEMA_VERSION, + activeTimer: parsed.activeTimer ?? null, + entries: Array.isArray(parsed.entries) ? parsed.entries : [] + }; + } catch { + return defaultState(); + } +} + +export function saveState(state: TrackerState): void { + const persistable: TrackerState = { + schema: SCHEMA, + schemaVersion: SCHEMA_VERSION, + activeTimer: state.activeTimer, + entries: state.entries + }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(persistable)); +} diff --git a/src/timer-service.ts b/src/timer-service.ts new file mode 100644 index 0000000..98f8da0 --- /dev/null +++ b/src/timer-service.ts @@ -0,0 +1,140 @@ +import { loadState, saveState } from "./storage"; +import type { PageRef, TimeEntry, TrackerState } from "./types"; + +function nowMs(): number { + return Date.now(); +} + +function id(): string { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return crypto.randomUUID(); + } + return `${Date.now()}-${Math.floor(Math.random() * 100000)}`; +} + +function assertValidRange(startedAtEpochMs: number, endedAtEpochMs: number): void { + if (!Number.isFinite(startedAtEpochMs) || !Number.isFinite(endedAtEpochMs)) { + throw new Error("Invalid timestamp."); + } + if (endedAtEpochMs < startedAtEpochMs) { + throw new Error("End must be greater than or equal to start."); + } +} + +export class TimerService { + private state: TrackerState; + + constructor() { + this.state = loadState(); + } + + public getState(): TrackerState { + return this.state; + } + + public startForPage(page: PageRef): TrackerState { + if (this.state.activeTimer?.pageUuid === page.uuid) { + return this.state; + } + + if (this.state.activeTimer) { + this.stopActive(); + } + + this.state = { + ...this.state, + activeTimer: { + id: id(), + pageUuid: page.uuid, + pageNameSnapshot: page.name, + startedAtEpochMs: nowMs() + } + }; + this.persist(); + return this.state; + } + + public stopActive(): TrackerState { + const active = this.state.activeTimer; + if (!active) { + return this.state; + } + + const endedAtEpochMs = nowMs(); + assertValidRange(active.startedAtEpochMs, endedAtEpochMs); + + const entry: TimeEntry = { + id: id(), + pageUuid: active.pageUuid, + pageNameSnapshot: active.pageNameSnapshot, + startedAtEpochMs: active.startedAtEpochMs, + endedAtEpochMs, + note: "", + createdAtEpochMs: endedAtEpochMs, + updatedAtEpochMs: endedAtEpochMs, + revision: 1 + }; + + this.state = { + ...this.state, + activeTimer: null, + entries: [entry, ...this.state.entries] + }; + + this.persist(); + return this.state; + } + + public switchToPage(page: PageRef): TrackerState { + if (this.state.activeTimer?.pageUuid === page.uuid) { + return this.state; + } + this.stopActive(); + return this.startForPage(page); + } + + public editEntry( + entryId: string, + update: { + pageNameSnapshot: string; + pageUuid: string | null; + startedAtEpochMs: number; + endedAtEpochMs: number; + note: string; + } + ): TrackerState { + assertValidRange(update.startedAtEpochMs, update.endedAtEpochMs); + const ts = nowMs(); + + this.state = { + ...this.state, + entries: this.state.entries.map((entry) => { + if (entry.id !== entryId) { + return entry; + } + return { + ...entry, + ...update, + updatedAtEpochMs: ts, + revision: entry.revision + 1 + }; + }) + }; + + this.persist(); + return this.state; + } + + public deleteEntry(entryId: string): TrackerState { + this.state = { + ...this.state, + entries: this.state.entries.filter((entry) => entry.id !== entryId) + }; + this.persist(); + return this.state; + } + + public persist(): void { + saveState(this.state); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..37f7880 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,42 @@ +export type IsoDateTime = string; + +export interface ActiveTimer { + id: string; + pageUuid: string | null; + pageNameSnapshot: string; + startedAtEpochMs: number; +} + +export interface TimeEntry { + id: string; + pageUuid: string | null; + pageNameSnapshot: string; + startedAtEpochMs: number; + endedAtEpochMs: number; + note: string; + createdAtEpochMs: number; + updatedAtEpochMs: number; + revision: number; +} + +export interface TrackerState { + schema: "logseq-page-timetracker"; + schemaVersion: number; + activeTimer: ActiveTimer | null; + entries: TimeEntry[]; +} + +export interface PageRef { + uuid: string | null; + name: string; +} + +export interface DaySummary { + day: string; + durationMs: number; +} + +export interface WeekSummary { + week: string; + durationMs: number; +} diff --git a/src/ui.ts b/src/ui.ts new file mode 100644 index 0000000..5a00080 --- /dev/null +++ b/src/ui.ts @@ -0,0 +1,390 @@ +import { computeDaySummaries, computeWeekSummaries } from "./aggregation"; +import { createExportPayload, downloadJson } from "./export"; +import { TimerService } from "./timer-service"; +import type { PageRef, TimeEntry } from "./types"; + +interface EditDraft { + entryId: string; + pageNameSnapshot: string; + startedAtInput: string; + endedAtInput: string; + note: string; +} + +function formatDuration(ms: number): string { + const totalSeconds = Math.floor(ms / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; +} + +function toIsoLocalInput(epochMs: number): string { + const d = new Date(epochMs); + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + const h = String(d.getHours()).padStart(2, "0"); + const m = String(d.getMinutes()).padStart(2, "0"); + return `${year}-${month}-${day}T${h}:${m}`; +} + +function parseLocalInput(value: string): number { + return new Date(value).getTime(); +} + +function row(entry: TimeEntry): string { + const duration = entry.endedAtEpochMs - entry.startedAtEpochMs; + return ` + ${entry.pageNameSnapshot} + ${new Date(entry.startedAtEpochMs).toLocaleString()} + ${new Date(entry.endedAtEpochMs).toLocaleString()} + ${formatDuration(duration)} + ${entry.note || ""} + + + + + `; +} + +export class TimetrackerUi { + private readonly service: TimerService; + private readonly resolveCurrentPage: () => Promise; + private readonly listPages: () => Promise; + private intervalId: number | null = null; + private editDraft: EditDraft | null = null; + private pageOptions: PageRef[] = []; + + constructor( + service: TimerService, + resolveCurrentPage: () => Promise, + listPages: () => Promise + ) { + this.service = service; + this.resolveCurrentPage = resolveCurrentPage; + this.listPages = listPages; + } + + public init(): void { + this.ensureContainer(); + this.bindEvents(); + this.render(); + this.intervalId = window.setInterval(() => { + // Keep focus stable while an entry is being edited. + if (this.editDraft) { + return; + } + this.render(); + }, 1000); + } + + public destroy(): void { + if (this.intervalId !== null) { + window.clearInterval(this.intervalId); + this.intervalId = null; + } + } + + public render(): void { + const app = this.ensureContainer(); + const state = this.service.getState(); + const active = state.activeTimer; + const activeDuration = active ? Date.now() - active.startedAtEpochMs : 0; + + const daySummaries = computeDaySummaries(state.entries); + const weekSummaries = computeWeekSummaries(state.entries); + + const today = new Date(); + const todayKey = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`; + const todayMs = daySummaries.find((x) => x.day === todayKey)?.durationMs ?? 0; + const currentWeek = weekSummaries[weekSummaries.length - 1]?.durationMs ?? 0; + + app.innerHTML = ` + +
+
+
+
+ Page Timetracker + +
+
+

Active

${active ? active.pageNameSnapshot : "None"}

+

Active Time

${formatDuration(activeDuration)}

+

Today

${formatDuration(todayMs)}

+

This Week

${formatDuration(currentWeek)}

+
+
+ + + +
+ ${this.editDraft ? this.renderEditPanel() : ""} + + + + + + + + + + + + + ${state.entries.map(row).join("")} + +
PageStartEndDurationNoteActions
+
+
+
+ `; + } + + private renderEditPanel(): string { + if (!this.editDraft) { + return ""; + } + + return ` +
+ Edit entry +
+ + + + +
+
+ + +
+
+ `; + } + + private ensureContainer(): HTMLElement { + const app = document.getElementById("app"); + if (!app) { + throw new Error("Missing UI container #app."); + } + return app; + } + + private bindEvents(): void { + document.addEventListener("click", async (event) => { + const target = event.target as HTMLElement; + if (!target) { + return; + } + + if (target.id === "tt-start") { + const page = await this.resolveCurrentPage(); + if (!page) { + logseq.App.showMsg("No current page found.", "warning"); + return; + } + this.service.startForPage(page); + this.render(); + return; + } + + if (target.id === "tt-stop") { + this.service.stopActive(); + this.render(); + return; + } + + if (target.id === "tt-close") { + logseq.hideMainUI(); + return; + } + + if (target.id === "tt-overlay") { + logseq.hideMainUI(); + return; + } + + if (target.id === "tt-export") { + const payload = createExportPayload(this.service.getState()); + const content = JSON.stringify(payload, null, 2); + downloadJson(`timetracker-export-${new Date().toISOString().slice(0, 10)}.json`, content); + logseq.App.showMsg("Exported JSON file.", "success"); + return; + } + + if (target.id === "tt-edit-save") { + this.saveEditDraft(); + return; + } + + if (target.id === "tt-edit-cancel") { + this.editDraft = null; + this.render(); + return; + } + + const editId = target.getAttribute("data-id"); + if (target.classList.contains("tt-edit") && editId) { + await this.startEditing(editId); + return; + } + + if (target.classList.contains("tt-delete") && editId) { + this.deleteEntry(editId); + } + }); + + document.addEventListener("input", (event) => { + const target = event.target as HTMLInputElement | HTMLTextAreaElement | null; + if (!target || !this.editDraft) { + return; + } + + const field = target.getAttribute("data-edit-field"); + if (!field) { + return; + } + + if (field === "pageNameSnapshot" || field === "startedAtInput" || field === "endedAtInput" || field === "note") { + this.editDraft = { + ...this.editDraft, + [field]: target.value + }; + } + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + if (this.editDraft) { + this.editDraft = null; + this.render(); + return; + } + logseq.hideMainUI(); + } + }); + } + + private deleteEntry(entryId: string): void { + const ok = window.confirm("Delete this entry?"); + if (!ok) { + return; + } + this.service.deleteEntry(entryId); + this.render(); + } + + private async startEditing(entryId: string): Promise { + const entry = this.service.getState().entries.find((x) => x.id === entryId); + if (!entry) { + return; + } + + try { + this.pageOptions = await this.listPages(); + } catch { + this.pageOptions = []; + } + + this.editDraft = { + entryId, + pageNameSnapshot: entry.pageNameSnapshot, + startedAtInput: toIsoLocalInput(entry.startedAtEpochMs), + endedAtInput: toIsoLocalInput(entry.endedAtEpochMs), + note: entry.note + }; + this.render(); + } + + private saveEditDraft(): void { + if (!this.editDraft) { + return; + } + + const entry = this.service.getState().entries.find((x) => x.id === this.editDraft?.entryId); + if (!entry) { + this.editDraft = null; + this.render(); + return; + } + + const pageNameSnapshot = this.editDraft.pageNameSnapshot.trim(); + if (!pageNameSnapshot) { + logseq.App.showMsg("Page name is required.", "error"); + return; + } + + const matchedPage = this.pageOptions.find((page) => page.name.toLowerCase() === pageNameSnapshot.toLowerCase()); + + const startedAtEpochMs = parseLocalInput(this.editDraft.startedAtInput); + const endedAtEpochMs = parseLocalInput(this.editDraft.endedAtInput); + + if (!Number.isFinite(startedAtEpochMs) || !Number.isFinite(endedAtEpochMs)) { + logseq.App.showMsg("Invalid date format.", "error"); + return; + } + + try { + this.service.editEntry(this.editDraft.entryId, { + pageNameSnapshot, + pageUuid: matchedPage?.uuid ?? null, + startedAtEpochMs, + endedAtEpochMs, + note: this.editDraft.note + }); + this.editDraft = null; + this.render(); + } catch (error) { + logseq.App.showMsg((error as Error).message, "error"); + } + } + + private escapeHtml(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">"); + } + + private escapeAttribute(value: string): string { + return this.escapeHtml(value).replace(/"/g, """); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5162031 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "types": [ + "@logseq/libs" + ], + "lib": [ + "ES2020", + "DOM" + ] + }, + "include": [ + "src" + ] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..495f860 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + base: "./", + build: { + outDir: "dist", + sourcemap: true, + target: "es2020" + } +});