From a97f7879b215f938d6b081a1955be56b60de82e4 Mon Sep 17 00:00:00 2001 From: Lycoon Date: Mon, 20 Apr 2026 16:29:39 +0200 Subject: [PATCH] removed sqlite provider and fixed some eslint errors --- .../projects/ProjectUnavailableDialog.tsx | 7 +- package.json | 1 - src-tauri/Cargo.lock | 719 +----------------- src-tauri/Cargo.toml | 1 - src-tauri/capabilities/default.json | 4 - src-tauri/src/lib.rs | 1 - .../storage-provider/local-persistence.ts | 64 +- .../sqlite-storage-provider.ts | 319 -------- .../storage-provider/storage-provider.ts | 15 +- src/lib/persistence/y-local-provider.ts | 29 +- src/lib/persistence/y-sqlite.ts | 196 ----- src/lib/project/project-state.ts | 21 +- .../extensions/scene-bookmark-extension.ts | 13 +- .../extensions/search-highlight-extension.ts | 17 +- src/lib/screenplay/statistics.ts | 47 +- src/lib/spellcheck/spellcheck-extension.ts | 27 +- src/lib/titlepage/editor.ts | 10 +- src/lib/titlepage/nodes/format-marks.ts | 16 +- src/lib/utils/api-handler.ts | 6 +- 19 files changed, 155 insertions(+), 1358 deletions(-) delete mode 100644 src/lib/persistence/storage-provider/sqlite-storage-provider.ts delete mode 100644 src/lib/persistence/y-sqlite.ts diff --git a/components/projects/ProjectUnavailableDialog.tsx b/components/projects/ProjectUnavailableDialog.tsx index 2ebaeb7d..70a8db88 100644 --- a/components/projects/ProjectUnavailableDialog.tsx +++ b/components/projects/ProjectUnavailableDialog.tsx @@ -19,9 +19,8 @@ const ProjectUnavailableDialog = () => { if (!projectId) return; setLoading(true); try { - const { getCachedProject } = await import("@src/lib/persistence/storage-provider/local-persistence"); - const { migrateToCachedProject } = - await import("@src/lib/persistence/storage-provider/sqlite-storage-provider"); + const { getCachedProject, migrateToCachedProject } = + await import("@src/lib/persistence/storage-provider/local-persistence"); const cachedProject = await getCachedProject(projectId); const metadataTitle = repository?.getState().metadata().get("title"); const title = cachedProject?.title || project?.project?.title || metadataTitle || "Untitled Project"; @@ -38,7 +37,7 @@ const ProjectUnavailableDialog = () => { setLoading(true); try { const { discardCloudProjectData } = - await import("@src/lib/persistence/storage-provider/sqlite-storage-provider"); + await import("@src/lib/persistence/storage-provider/local-persistence"); await discardCloudProjectData(projectId); router.replace("/projects"); } catch (e) { diff --git a/package.json b/package.json index 5b390169..3b0736cf 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "@tauri-apps/plugin-dialog": "2.6.0", "@tauri-apps/plugin-fs": "2.4.5", "@tauri-apps/plugin-opener": "2.5.3", - "@tauri-apps/plugin-sql": "2.3.2", "@tauri-apps/plugin-store": "^2.4.2", "@tiptap/extension-bold": "^3.13.0", "@tiptap/extension-collaboration": "^3.13.0", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9926f3bb..211f6a79 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -16,7 +16,6 @@ dependencies = [ "tauri-plugin-iap", "tauri-plugin-log", "tauri-plugin-opener", - "tauri-plugin-sql", "tauri-plugin-store", ] @@ -61,12 +60,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android_log-sys" version = "0.3.2" @@ -259,15 +252,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -292,12 +276,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bitflags" version = "1.3.2" @@ -608,12 +586,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "convert_case" version = "0.4.0" @@ -679,21 +651,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc32fast" version = "1.5.0" @@ -712,15 +669,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -809,17 +757,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "deranged" version = "0.5.5" @@ -850,9 +787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", ] [[package]] @@ -928,12 +863,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "dpi" version = "0.1.2" @@ -970,15 +899,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] - [[package]] name = "embed-resource" version = "3.0.6" @@ -1063,17 +983,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - [[package]] name = "event-listener" version = "5.4.1" @@ -1145,29 +1054,12 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foreign-types" version = "0.5.0" @@ -1227,7 +1119,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -1247,17 +1138,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" version = "0.3.31" @@ -1626,32 +1506,12 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "heck" version = "0.4.1" @@ -1676,33 +1536,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "html5ever" version = "0.29.1" @@ -2119,9 +1952,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libappindicator" @@ -2163,12 +1993,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "libredox" version = "0.1.12" @@ -2177,18 +2001,6 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.7.0", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", ] [[package]] @@ -2258,16 +2070,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[package]] name = "memchr" version = "2.7.6" @@ -2373,48 +2175,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2422,7 +2188,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2751,7 +2516,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link 0.2.1", ] @@ -2762,15 +2527,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -2934,27 +2690,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -3245,15 +2980,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "redox_syscall" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" -dependencies = [ - "bitflags 2.10.0", -] - [[package]] name = "redox_users" version = "0.5.2" @@ -3410,26 +3136,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rust_decimal" version = "1.40.0" @@ -3474,12 +3180,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - [[package]] name = "same-file" version = "1.0.6" @@ -3675,18 +3375,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_with" version = "3.16.1" @@ -3750,17 +3438,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha2" version = "0.10.9" @@ -3788,16 +3465,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.8" @@ -3833,9 +3500,6 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] [[package]] name = "socket2" @@ -3862,7 +3526,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.18", + "redox_syscall", "tracing", "wasm-bindgen", "web-sys", @@ -3895,221 +3559,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64 0.22.1", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap 2.13.0", - "log", - "memchr", - "once_cell", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror 2.0.18", - "time", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.114", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.114", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.10.0", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "time", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.10.0", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "time", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.18", - "time", - "tracing", - "url", - "uuid", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4141,29 +3590,12 @@ dependencies = [ "quote", ] -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "swift-bridge" version = "0.1.59" @@ -4573,26 +4005,6 @@ dependencies = [ "zbus", ] -[[package]] -name = "tauri-plugin-sql" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27454a5476feb6ea78d74d0f07085fc8a5e97565d7ae825373011fd27ec30303" -dependencies = [ - "futures-core", - "indexmap 2.13.0", - "log", - "serde", - "serde_json", - "sqlx", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", - "time", - "tokio", - "uuid", -] - [[package]] name = "tauri-plugin-store" version = "2.4.2" @@ -4858,17 +4270,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -5029,7 +4430,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5147,33 +4547,12 @@ dependencies = [ "unic-common", ] -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -5241,12 +4620,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version-compare" version = "0.2.1" @@ -5319,12 +4692,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -5487,16 +4854,6 @@ dependencies = [ "windows-core 0.61.2", ] -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - [[package]] name = "winapi" version = "0.3.9" @@ -5691,15 +5048,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -5742,21 +5090,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -5814,12 +5147,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5838,12 +5165,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5862,12 +5183,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5898,12 +5213,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5922,12 +5231,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5946,12 +5249,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5970,12 +5267,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6228,12 +5519,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - [[package]] name = "zerotrie" version = "0.2.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fcbc9eef..2018e0c7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -24,7 +24,6 @@ log = "0.4" tauri = { version = "2.10", features = [] } tauri-plugin-log = "2.8.0" tauri-plugin-store = "2.4.2" -tauri-plugin-sql = { version = "2.3.2", features = ["sqlite"] } tauri-plugin-dialog = "2.6.0" tauri-plugin-fs = "2.4.5" tauri-plugin-opener = "2.5.3" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 09a59fbc..d5e7ffa9 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -10,10 +10,6 @@ "store:allow-delete", "store:allow-save", "store:allow-load", - "sql:allow-load", - "sql:allow-execute", - "sql:allow-select", - "sql:allow-close", "dialog:allow-save", "fs:allow-write-file", "opener:default", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6daf35ac..d021d2a3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,7 +7,6 @@ fn some_noop_command() { pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_store::Builder::default().build()) - .plugin(tauri_plugin_sql::Builder::default().build()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_opener::init()) diff --git a/src/lib/persistence/storage-provider/local-persistence.ts b/src/lib/persistence/storage-provider/local-persistence.ts index 8320a92b..1d3c605b 100644 --- a/src/lib/persistence/storage-provider/local-persistence.ts +++ b/src/lib/persistence/storage-provider/local-persistence.ts @@ -1,12 +1,11 @@ /** * Cached projects persistence facade. - * Delegates to the appropriate StorageProvider (IndexedDB on browser, SQLite on Tauri). - * - * This file preserves the public API consumed by all components and hooks. + * Delegates to the IndexedDB StorageProvider on all platforms. */ import type { UserSettings } from "@src/lib/utils/types"; import { getStorageProvider, type CachedProject, type ProjectEntryInput } from "./storage-provider"; +import { yjsDbKey } from "../y-local-provider"; export type { CachedProject }; @@ -84,3 +83,62 @@ export async function getPersistedSettings(): Promise> { export async function persistSettings(updates: Partial): Promise { return (await getStorageProvider()).saveSettings(updates); } + +// ── Project migration / discard ────────────────────────────────────────────── + +/** + * Migrate a cloud project to a new local-only project. + * Creates a new cached entry and copies the Yjs document from the old + * IndexedDB database (`scriptio-`) to a new one (`scriptio-`). + */ +export async function migrateToCachedProject( + oldProjectId: string, + title: string, + description?: string, +): Promise { + const { Doc, applyUpdate, encodeStateAsUpdate } = await import("yjs"); + const { IndexeddbPersistence } = await import("y-indexeddb"); + + // 1. Load the old project's Yjs document from IndexedDB + const oldDoc = new Doc(); + const oldProvider = new IndexeddbPersistence(yjsDbKey(oldProjectId), oldDoc); + await new Promise((resolve) => oldProvider.on("synced", () => resolve())); + + const snapshot = encodeStateAsUpdate(oldDoc); + oldProvider.destroy(); + oldDoc.destroy(); + + // 2. Create a new local-only cached project entry + const newProject = await createCachedProject(title, description); + + // 3. Write the snapshot into the new project's IndexedDB + const newDoc = new Doc(); + applyUpdate(newDoc, snapshot); + const newProvider = new IndexeddbPersistence(yjsDbKey(newProject.id), newDoc); + await new Promise((resolve) => newProvider.on("synced", () => resolve())); + await new Promise((resolve) => setTimeout(resolve, 100)); + newProvider.destroy(); + newDoc.destroy(); + + // 4. Clean up old project data + await discardCloudProjectData(oldProjectId); + + return newProject; +} + +/** + * Discard a cloud project's local data (cached entry + Yjs IndexedDB database). + */ +export async function discardCloudProjectData(projectId: string): Promise { + // Remove the cached project entry + await deleteCachedProject(projectId); + + // Delete the Yjs IndexedDB database for this project + const dbName = yjsDbKey(projectId); + await new Promise((resolve, reject) => { + const req = indexedDB.deleteDatabase(dbName); + req.onsuccess = () => resolve(); + req.onerror = () => reject(req.error); + req.onblocked = () => resolve(); + }); +} diff --git a/src/lib/persistence/storage-provider/sqlite-storage-provider.ts b/src/lib/persistence/storage-provider/sqlite-storage-provider.ts deleted file mode 100644 index 77f244ec..00000000 --- a/src/lib/persistence/storage-provider/sqlite-storage-provider.ts +++ /dev/null @@ -1,319 +0,0 @@ -/** - * SQLite storage provider for Tauri desktop environments. - * Stores project metadata and settings in a local SQLite database. - * - * Storage location (managed by Tauri, based on app identifier "ArkoLogic.Scriptio"): - * - Windows: %APPDATA%\ArkoLogic.Scriptio\ - * - macOS: ~/Library/Application Support/ArkoLogic.Scriptio/ - * - Linux: ~/.local/share/arkologic.scriptio/ - */ - -import type { InstalledDictionary, UserSettings } from "@src/lib/utils/types"; -import { CachedProject, ProjectEntryInput, StorageProvider } from "./storage-provider"; - -function uint8ArrayToBase64(bytes: Uint8Array): string { - let binary = ""; - for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]); - return btoa(binary); -} - -function base64ToUint8Array(base64: string): Uint8Array { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); - return bytes; -} - -const DB_NAME = "sqlite:scriptio.db"; -const SETTINGS_KEY = "global"; - -// Use any for database type to avoid TypeScript issues with Tauri plugin types -type Database = any; - -let dbInstance: Database | null = null; -let initPromise: Promise | null = null; - -/** - * Initialize the database connection and create tables if needed. - * Uses singleton pattern to avoid multiple connections. - * Exported so other modules (e.g. dictionary store) can reuse the same connection. - */ -export async function getDb(): Promise { - if (dbInstance) return dbInstance; - if (initPromise) return initPromise; - - initPromise = (async () => { - const Database = (await import("@tauri-apps/plugin-sql")).default; - const db = await Database.load(DB_NAME); - - await db.execute(` - CREATE TABLE IF NOT EXISTS cached_projects ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, - author TEXT, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - is_synced INTEGER NOT NULL DEFAULT 0, - data TEXT - ) - `); - - await db.execute(` - CREATE TABLE IF NOT EXISTS dictionaries ( - code TEXT PRIMARY KEY, - aff_data TEXT NOT NULL, - dic_data TEXT NOT NULL, - size INTEGER NOT NULL, - installed_at INTEGER NOT NULL - ) - `); - - await db.execute(` - CREATE TABLE IF NOT EXISTS settings ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - ) - `); - - dbInstance = db; - return db; - })(); - - return initPromise; -} - -// ── StorageProvider implementation ─────────────────────────────────────────── - -export class SqliteStorageProvider implements StorageProvider { - async createProject( - id: string, - title: string, - description?: string, - synced: boolean = false, - author?: string, - ): Promise { - const db = await getDb(); - const now = Date.now(); - await db.execute( - `INSERT INTO cached_projects (id, title, description, author, created_at, updated_at, is_synced) VALUES (?, ?, ?, ?, ?, ?, ?)`, - [id, title, description || null, author || null, now, now, synced ? 1 : 0], - ); - } - - async getAll(): Promise { - const db = await getDb(); - const results = (await db.select("SELECT * FROM cached_projects ORDER BY updated_at DESC")) as { - id: string; - title: string; - description: string | null; - author: string | null; - created_at: number; - updated_at: number; - is_synced: number; - }[]; - - return results.map((row) => ({ - id: row.id, - title: row.title, - description: row.description, - author: row.author, - createdAt: new Date(row.created_at), - updatedAt: new Date(row.updated_at), - isLocalOnly: row.is_synced === 0, - })); - } - - async get(id: string): Promise { - const db = await getDb(); - const results = (await db.select("SELECT * FROM cached_projects WHERE id = ?", [id])) as { - id: string; - title: string; - description: string | null; - author: string | null; - created_at: number; - updated_at: number; - is_synced: number; - }[]; - - if (results.length === 0) return null; - - const row = results[0]; - return { - id: row.id, - title: row.title, - description: row.description, - author: row.author, - createdAt: new Date(row.created_at), - updatedAt: new Date(row.updated_at), - isLocalOnly: row.is_synced === 0, - }; - } - - async update(id: string, updates: { title?: string; description?: string; author?: string }): Promise { - const db = await getDb(); - const now = Date.now(); - - const setClauses: string[] = ["updated_at = ?"]; - const values: (string | number | null)[] = [now]; - - if (updates.title !== undefined) { - setClauses.push("title = ?"); - values.push(updates.title); - } - if (updates.description !== undefined) { - setClauses.push("description = ?"); - values.push(updates.description); - } - if (updates.author !== undefined) { - setClauses.push("author = ?"); - values.push(updates.author); - } - - values.push(id); - await db.execute(`UPDATE cached_projects SET ${setClauses.join(", ")} WHERE id = ?`, values); - } - - async touch(id: string): Promise { - const db = await getDb(); - await db.execute(`UPDATE cached_projects SET updated_at = ? WHERE id = ?`, [Date.now(), id]); - } - - async delete(id: string): Promise { - const db = await getDb(); - await db.execute("DELETE FROM cached_projects WHERE id = ?", [id]); - } - - async exists(id: string): Promise { - const db = await getDb(); - const results = (await db.select("SELECT COUNT(*) as count FROM cached_projects WHERE id = ?", [id])) as { - count: number; - }[]; - return results[0].count > 0; - } - - async ensureEntries(projects: ProjectEntryInput[]): Promise { - const db = await getDb(); - for (const p of projects) { - await db.execute( - `INSERT OR IGNORE INTO cached_projects (id, title, description, author, created_at, updated_at, is_synced) VALUES (?, ?, ?, ?, ?, ?, 1)`, - [p.id, p.title, p.description, p.author || null, p.createdAt.getTime(), p.updatedAt.getTime()], - ); - } - } - - async getSettings(): Promise> { - const db = await getDb(); - const rows = (await db.select("SELECT value FROM settings WHERE key = ?", [SETTINGS_KEY])) as { - value: string; - }[]; - if (rows.length === 0) return {}; - try { - return JSON.parse(rows[0].value); - } catch { - return {}; - } - } - - async saveSettings(updates: Partial): Promise { - const current = await this.getSettings(); - const merged = { ...current, ...updates }; - const db = await getDb(); - await db.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", [ - SETTINGS_KEY, - JSON.stringify(merged), - ]); - } - - async saveDictionary(code: string, aff: Uint8Array, dic: Uint8Array): Promise { - const db = await getDb(); - await db.execute( - `INSERT OR REPLACE INTO dictionaries (code, aff_data, dic_data, size, installed_at) VALUES (?, ?, ?, ?, ?)`, - [code, uint8ArrayToBase64(aff), uint8ArrayToBase64(dic), aff.byteLength + dic.byteLength, Date.now()], - ); - } - - async loadDictionary(code: string): Promise<{ aff: Uint8Array; dic: Uint8Array } | null> { - const db = await getDb(); - const results = (await db.select("SELECT aff_data, dic_data FROM dictionaries WHERE code = ?", [code])) as { - aff_data: string; - dic_data: string; - }[]; - if (results.length === 0) return null; - return { aff: base64ToUint8Array(results[0].aff_data), dic: base64ToUint8Array(results[0].dic_data) }; - } - - async deleteDictionary(code: string): Promise { - const db = await getDb(); - await db.execute("DELETE FROM dictionaries WHERE code = ?", [code]); - } - - async listInstalledDictionaries(): Promise { - const db = await getDb(); - const results = (await db.select("SELECT code, size, installed_at FROM dictionaries")) as { - code: string; - size: number; - installed_at: number; - }[]; - return results.map((row) => ({ - code: row.code as InstalledDictionary["code"], - size: row.size, - installedAt: row.installed_at, - })); - } -} - -// ── Desktop-only utilities ─────────────────────────────────────────────────── - -/** - * Migrate a cloud project to a new local project. - * Creates a new local project entry and copies the document data. - * Only available on desktop (Tauri). - */ -export async function migrateToCachedProject( - oldProjectId: string, - title: string, - description?: string, -): Promise { - const db = await getDb(); - - const result = (await db.select("SELECT data FROM cached_projects WHERE id = ?", [oldProjectId])) as { - data: string | null; - }[]; - - const id = crypto.randomUUID(); - const now = Date.now(); - await db.execute( - `INSERT INTO cached_projects (id, title, description, author, created_at, updated_at, is_synced) VALUES (?, ?, ?, ?, ?, ?, 0)`, - [id, title, description || null, null, now, now], - ); - - if (result.length > 0 && result[0].data) { - await db.execute("UPDATE cached_projects SET data = ?, updated_at = ? WHERE id = ?", [ - result[0].data, - Date.now(), - id, - ]); - } - - await db.execute("DELETE FROM cached_projects WHERE id = ?", [oldProjectId]); - - return { - id, - title, - description: description || null, - author: null, - createdAt: new Date(now), - updatedAt: new Date(now), - isLocalOnly: true, - }; -} - -/** - * Discard a cloud project's local data. - * Only available on desktop (Tauri). - */ -export async function discardCloudProjectData(projectId: string): Promise { - const db = await getDb(); - await db.execute("DELETE FROM cached_projects WHERE id = ?", [projectId]); -} diff --git a/src/lib/persistence/storage-provider/storage-provider.ts b/src/lib/persistence/storage-provider/storage-provider.ts index 0c764d02..4ce89296 100644 --- a/src/lib/persistence/storage-provider/storage-provider.ts +++ b/src/lib/persistence/storage-provider/storage-provider.ts @@ -1,6 +1,6 @@ /** * Storage provider abstraction for local persistence. - * Implementations: IndexedDB (browser) and SQLite (Tauri desktop). + * Uses IndexedDB on both browser and Tauri desktop. */ import type { InstalledDictionary, UserSettings } from "@src/lib/utils/types"; @@ -53,20 +53,13 @@ export interface StorageProvider { let cachedProvider: StorageProvider | null = null; /** - * Returns the appropriate StorageProvider for the current environment. - * Tauri desktop → SQLite, Browser → IndexedDB. + * Returns the IndexedDB StorageProvider (used on both browser and desktop). */ export async function getStorageProvider(): Promise { if (cachedProvider) return cachedProvider; - const { isTauri } = await import("@tauri-apps/api/core"); - if (isTauri()) { - const { SqliteStorageProvider } = await import("./sqlite-storage-provider"); - cachedProvider = new SqliteStorageProvider(); - } else { - const { IndexedDBStorageProvider } = await import("./indexeddb-storage-provider"); - cachedProvider = new IndexedDBStorageProvider(); - } + const { IndexedDBStorageProvider } = await import("./indexeddb-storage-provider"); + cachedProvider = new IndexedDBStorageProvider(); return cachedProvider; } diff --git a/src/lib/persistence/y-local-provider.ts b/src/lib/persistence/y-local-provider.ts index d535a824..03a7c936 100644 --- a/src/lib/persistence/y-local-provider.ts +++ b/src/lib/persistence/y-local-provider.ts @@ -1,11 +1,6 @@ /** * Shared factory for local Yjs persistence providers. - * Picks the right implementation based on environment: - * - Browser: y-indexeddb (one DB per project, keyed as "scriptio-") - * - Desktop (Tauri): SQLitePersistence (data column in cached_projects table) - * - * Centralises the isTauri() branch so project-state.ts and import-project.ts - * don't duplicate the same switching logic. + * Uses y-indexeddb on both browser and desktop (one DB per project, keyed as "scriptio-"). */ import type * as Y from "yjs"; @@ -16,25 +11,17 @@ export const yjsDbKey = (projectId: string) => `scriptio-${projectId}`; export interface YjsLocalProvider { on(event: "synced", callback: (provider: any) => void): void; destroy(): void; - /** Force an immediate write (available on SQLitePersistence, no-op on IndexedDB). */ - flush?(): Promise; /** Clear all stored data for this project (used when server restores a snapshot). */ clearData?(): Promise; } /** - * Create the appropriate local Yjs persistence provider for the current environment. + * Create a local Yjs persistence provider backed by IndexedDB. * The provider is returned before sync completes — attach to the "synced" event. */ export async function createLocalYjsProvider(projectId: string, ydoc: Y.Doc): Promise { - const { isTauri } = await import("@tauri-apps/api/core"); - if (isTauri()) { - const { SQLitePersistence } = await import("./y-sqlite"); - return new SQLitePersistence(projectId, ydoc); - } else { - const { IndexeddbPersistence } = await import("y-indexeddb"); - return new IndexeddbPersistence(yjsDbKey(projectId), ydoc); - } + const { IndexeddbPersistence } = await import("y-indexeddb"); + return new IndexeddbPersistence(yjsDbKey(projectId), ydoc); } /** @@ -49,12 +36,8 @@ export async function writeYjsDocumentLocally(projectId: string, ydoc: Y.Doc): P provider.on("synced", () => resolve()); }); - if (provider.flush) { - await provider.flush(); - } else { - // Give IndexedDB time to finish writing - await new Promise((resolve) => setTimeout(resolve, 100)); - } + // Give IndexedDB time to finish writing + await new Promise((resolve) => setTimeout(resolve, 100)); provider.destroy(); } diff --git a/src/lib/persistence/y-sqlite.ts b/src/lib/persistence/y-sqlite.ts deleted file mode 100644 index 25413dac..00000000 --- a/src/lib/persistence/y-sqlite.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * SQLite persistence provider for Yjs documents in Tauri desktop app. - * This replaces y-indexeddb for more reliable local storage on desktop. - * - * Storage location (managed by Tauri, based on app identifier "ArkoLogic.Scriptio"): - * - Windows: %APPDATA%\ArkoLogic.Scriptio\ - * - macOS: ~/Library/Application Support/ArkoLogic.Scriptio/ - * - Linux: ~/.local/share/arkologic.scriptio/ - * - * For store distribution (Microsoft Store/Mac App Store), the paths are automatically - * virtualized/sandboxed by the OS, and Tauri's path APIs handle this correctly. - * - * Note: We use Base64 encoding for binary data because Tauri's SQL plugin - * has issues with Uint8Array serialization (see: https://github.com/tauri-apps/plugins-workspace/issues/105) - */ - -import * as Y from "yjs"; -import { Observable } from "lib0/observable"; - -// Base64 encoding/decoding utilities -function uint8ArrayToBase64(bytes: Uint8Array): string { - let binary = ""; - for (let i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); -} - -function base64ToUint8Array(base64: string): Uint8Array { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - return bytes; -} - -const DB_NAME = "sqlite:scriptio.db"; - -// Use any for database type to avoid TypeScript issues with Tauri plugin types -type Database = any; - -/** - * SQLite persistence provider for Yjs documents. - * Compatible with the same interface as IndexeddbPersistence from y-indexeddb. - */ -export class SQLitePersistence extends Observable { - private doc: Y.Doc; - private projectId: string; - private db: Database | null = null; - private isInitialized = false; - private saveTimeout: ReturnType | null = null; - private isSynced = false; - - // Debounce saves to avoid excessive writes - private readonly SAVE_DEBOUNCE_MS = 1000; - - constructor(projectId: string, doc: Y.Doc) { - super(); - this.projectId = projectId; - this.doc = doc; - - this.init(); - } - - private async init(): Promise { - try { - // Dynamically import to avoid SSR issues - const Database = (await import("@tauri-apps/plugin-sql")).default; - this.db = await Database.load(DB_NAME); - - // Load existing document state from cached_projects table - const result = (await this.db.select("SELECT data FROM cached_projects WHERE id = ?", [this.projectId])) as { - data: string | null; - }[]; - - if (result.length > 0 && result[0].data) { - try { - const update = base64ToUint8Array(result[0].data); - Y.applyUpdate(this.doc, update); - console.log(`[SqlitePersistence] Loaded ${update.length} bytes for project ${this.projectId}`); - } catch (updateError) { - console.warn( - `[SqlitePersistence] Corrupted data for project ${this.projectId}, clearing local cache:`, - updateError, - ); - await this.db.execute("UPDATE cached_projects SET data = NULL WHERE id = ?", [this.projectId]); - console.log(`[SqlitePersistence] Cleared corrupted data, will sync from cloud`); - } - } else { - console.log(`[SqlitePersistence] No existing data for project ${this.projectId}`); - } - - this.isInitialized = true; - this.isSynced = true; - - this.doc.on("update", this.onDocumentUpdate); // Listen for document updates - this.emit("synced", [this]); // Emit synced event (compatible with y-indexeddb) - } catch (error) { - console.error("[SqlitePersistence] Initialization failed:", error); - // Still emit synced to not block the app, but with empty state - this.isSynced = true; - this.emit("synced", [this]); - } - } - - private onDocumentUpdate = (_update: Uint8Array, origin: any): void => { - // Don't save updates that originated from loading - if (origin === this) return; - - this.scheduleSave(); - }; - - private scheduleSave(): void { - if (this.saveTimeout) { - clearTimeout(this.saveTimeout); - } - - this.saveTimeout = setTimeout(() => { - this.saveToDatabase(); - }, this.SAVE_DEBOUNCE_MS); - } - - private async saveToDatabase(): Promise { - if (!this.db || !this.isInitialized) return; - - try { - const state = Y.encodeStateAsUpdate(this.doc); - const base64Data = uint8ArrayToBase64(state); - - await this.db.execute(`UPDATE cached_projects SET data = ?, updated_at = ? WHERE id = ?`, [ - base64Data, - Date.now(), - this.projectId, - ]); - - console.log(`[SqlitePersistence] Saved ${state.length} bytes for project ${this.projectId}`); - } catch (error) { - console.error("[SqlitePersistence] Save failed:", error); - } - } - - /** - * Check if the persistence provider has synced with local storage - */ - get synced(): boolean { - return this.isSynced; - } - - /** - * Force an immediate save - */ - async flush(): Promise { - if (this.saveTimeout) { - clearTimeout(this.saveTimeout); - this.saveTimeout = null; - } - await this.saveToDatabase(); - } - - /** - * Clear all data for this project - */ - async clearData(): Promise { - if (!this.db) return; - - try { - await this.db.execute("UPDATE cached_projects SET data = NULL WHERE id = ?", [this.projectId]); - console.log(`[SqlitePersistence] Cleared data for project ${this.projectId}`); - } catch (error) { - console.error("[SqlitePersistence] Clear failed:", error); - } - } - - /** - * Destroy the persistence provider and clean up resources - */ - destroy(): void { - if (this.saveTimeout) { - clearTimeout(this.saveTimeout); - this.saveTimeout = null; - } - - this.doc.off("update", this.onDocumentUpdate); - - // Flush any pending saves before destroying - if (this.db && this.isInitialized) { - this.saveToDatabase().catch(console.error); - } - - this.db = null; - this.isInitialized = false; - - console.log(`[SqlitePersistence] Destroyed for project ${this.projectId}`); - } -} diff --git a/src/lib/project/project-state.ts b/src/lib/project/project-state.ts index b08e4bd9..5ec822c4 100644 --- a/src/lib/project/project-state.ts +++ b/src/lib/project/project-state.ts @@ -571,25 +571,20 @@ export const useCloudSync = (projectId: string | null, ydoc: ProjectState | null }); // Handle document restore — server replaced the doc with a snapshot. - // We must clear the local IndexedDB/SQLite so the old state doesn't + // We must clear the local IndexedDB so the old state doesn't // merge back when we reconnect, then reload to get a clean slate. cloudProvider.on("document-restored", async () => { if (!isMountedRef.current) return; console.log("[ProjectYjs] Document restored — clearing local cache and reloading"); try { - const { isTauri } = await import("@tauri-apps/api/core"); - if (!isTauri()) { - const { IndexeddbPersistence } = await import("y-indexeddb"); - const Y = await getYjs(); - const tmpDoc = new Y.Doc(); - const tmpPersistence = new IndexeddbPersistence(`scriptio-${projectId}`, tmpDoc); - await (tmpPersistence as any).clearData(); - tmpPersistence.destroy(); - tmpDoc.destroy(); - } - // Desktop (SQLite): the SqlitePersistence will be overwritten on - // reconnect since the server state wins the CRDT merge from a clean doc. + const { IndexeddbPersistence } = await import("y-indexeddb"); + const Y = await getYjs(); + const tmpDoc = new Y.Doc(); + const tmpPersistence = new IndexeddbPersistence(`scriptio-${projectId}`, tmpDoc); + await (tmpPersistence as any).clearData(); + tmpPersistence.destroy(); + tmpDoc.destroy(); } catch (e) { console.warn("[ProjectYjs] Failed to clear local cache:", e); } diff --git a/src/lib/screenplay/extensions/scene-bookmark-extension.ts b/src/lib/screenplay/extensions/scene-bookmark-extension.ts index ed38a2f6..02002313 100644 --- a/src/lib/screenplay/extensions/scene-bookmark-extension.ts +++ b/src/lib/screenplay/extensions/scene-bookmark-extension.ts @@ -1,5 +1,6 @@ import { Editor, Extension } from "@tiptap/core"; -import { Plugin, PluginKey } from "@tiptap/pm/state"; +import { Node } from "@tiptap/pm/model"; +import { Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; import { Decoration, DecorationSet } from "@tiptap/pm/view"; const sceneBookmarkPluginKey = new PluginKey("sceneBookmark"); @@ -14,10 +15,10 @@ type SceneBookmarkConfig = { * Uses a widget decoration (instead of a node ::after) so that the scene * node's ::after pseudo-element remains free for right scene numbers. */ -function computeBookmarkDecorations(doc: any, getSceneColor: (sceneId: string) => string | undefined): DecorationSet { +function computeBookmarkDecorations(doc: Node, getSceneColor: (sceneId: string) => string | undefined): DecorationSet { const decorations: Decoration[] = []; - doc.forEach((node: any, pos: number) => { + doc.forEach((node: Node, pos: number) => { if (node.attrs?.class !== "scene") return; const sceneId: string | undefined = node.attrs?.["data-id"]; @@ -49,7 +50,7 @@ function computeBookmarkDecorations(doc: any, getSceneColor: (sceneId: string) = * Uses nodesBetween on both old and new docs to accurately check if the affected * range overlaps with a scene node, avoiding false positives from adjacent nodes. */ -function didSceneNodesChange(tr: any): boolean { +function didSceneNodesChange(tr: Transaction): boolean { if (!tr.docChanged) return false; for (const step of tr.steps) { @@ -60,7 +61,7 @@ function didSceneNodesChange(tr: any): boolean { try { const oldDoc = tr.docs[0]; if (oldDoc) { - oldDoc.nodesBetween(oldStart, oldEnd, (node: any) => { + oldDoc.nodesBetween(oldStart, oldEnd, (node: Node) => { if (node.attrs?.class === "scene") affectsScene = true; }); } @@ -68,7 +69,7 @@ function didSceneNodesChange(tr: any): boolean { // Check new doc: did the change produce a scene node? try { - tr.doc.nodesBetween(newStart, newEnd, (node: any) => { + tr.doc.nodesBetween(newStart, newEnd, (node: Node) => { if (node.attrs?.class === "scene") affectsScene = true; }); } catch { /* position out of bounds */ } diff --git a/src/lib/screenplay/extensions/search-highlight-extension.ts b/src/lib/screenplay/extensions/search-highlight-extension.ts index 4594b453..d2785b7f 100644 --- a/src/lib/screenplay/extensions/search-highlight-extension.ts +++ b/src/lib/screenplay/extensions/search-highlight-extension.ts @@ -1,5 +1,6 @@ import { Editor, Extension } from "@tiptap/core"; -import { Plugin, PluginKey } from "@tiptap/pm/state"; +import { Node } from "@tiptap/pm/model"; +import { Plugin, PluginKey, Transaction } from "@tiptap/pm/state"; import { Decoration, DecorationSet } from "@tiptap/pm/view"; import { ScreenplayElement } from "../../utils/enums"; @@ -48,7 +49,7 @@ function mergeOverlappingRanges(ranges: Array<{ from: number; to: number }>): Ar * Ranges are expanded to parent node boundaries so regex matches at edit * boundaries are correctly found. */ -function getChangedRanges(tr: any): Array<{ from: number; to: number }> { +function getChangedRanges(tr: Transaction): Array<{ from: number; to: number }> { const ranges: Array<{ from: number; to: number }> = []; for (let i = 0; i < tr.mapping.maps.length; i++) { const stepMap = tr.mapping.maps[i]; @@ -79,7 +80,7 @@ function getChangedRanges(tr: any): Array<{ from: number; to: number }> { * Scan text nodes within a specific range for search matches. */ function scanRangeForMatches( - doc: any, + doc: Node, from: number, to: number, searchTerm: string, @@ -88,7 +89,7 @@ function scanRangeForMatches( const matches: SearchMatch[] = []; const regex = new RegExp(escapeRegex(searchTerm), "gi"); - doc.nodesBetween(from, to, (node: any, pos: number) => { + doc.nodesBetween(from, to, (node: Node, pos: number) => { if (!node.isText) return; const resolvedPos = doc.resolve(pos); @@ -122,7 +123,7 @@ function scanRangeForMatches( * Returns both the DecorationSet and the list of matches for navigation. */ function computeSearchDecorations( - doc: any, + doc: Node, searchTerm: string, enabledFilters: Set, currentMatchIndex: number, @@ -151,7 +152,7 @@ function computeSearchDecorations( * Returns a new DecorationSet with the correct "current" highlight applied. */ function applyCurrentMatchClass( - doc: any, + doc: Node, decoSet: DecorationSet, matches: SearchMatch[], currentMatchIndex: number, @@ -165,14 +166,14 @@ function applyCurrentMatchClass( const atCurrent = decoSet.find(currentMatch.from, currentMatch.to); // Check if the current decoration already has the correct class const existing = atCurrent.find((d) => { - const spec = (d as any).type?.attrs?.class; + const spec = (d as { type?: { attrs?: { class?: string } } }).type?.attrs?.class; return spec && spec.includes("search-highlight-current"); }); if (existing) return decoSet; // Remove the old decoration at this position and add one with the current class const toRemove = atCurrent.filter((d) => { - const spec = (d as any).type?.attrs?.class; + const spec = (d as { type?: { attrs?: { class?: string } } }).type?.attrs?.class; return spec && spec.includes("search-highlight"); }); let result = decoSet; diff --git a/src/lib/screenplay/statistics.ts b/src/lib/screenplay/statistics.ts index 3f3becf0..98c5a830 100644 --- a/src/lib/screenplay/statistics.ts +++ b/src/lib/screenplay/statistics.ts @@ -34,17 +34,15 @@ const ScreenplayElements: { [type: string]: IElementData } = { const average = (list: number[]) => list.reduce((prev, curr) => prev + curr, 0) / list.length; -const cleanFrequency = (frequency: any) => { - let items = Object.keys(frequency).map((key) => { - return [key, frequency[key]]; - }); +const cleanFrequency = (frequency: Frequency) => { + const items: [string, number][] = Object.entries(frequency); items.sort((first, second) => { return second[1] - first[1]; }); // Computing other characters - let others = items.splice(8); + const others = items.splice(8); let freqOthers = 0; for (const e in others) { freqOthers += others[e][1]; @@ -58,10 +56,10 @@ const cleanFrequency = (frequency: any) => { }); // Converting array to dictionary - let sorted: any = {}; - items.forEach((e: any) => { - let key = e[0]; - let value = e[1]; + const sorted: Frequency = {}; + items.forEach((e: [string, number]) => { + const key = e[0]; + const value = e[1]; sorted[key] = value; }); @@ -69,26 +67,24 @@ const cleanFrequency = (frequency: any) => { }; const getQuantity = (dialLengths: DialogueLengths) => { - let quantity: Quantity = {}; + const quantity: Quantity = {}; for (const actor in dialLengths) { quantity[actor] = average(dialLengths[actor]); } - let items = Object.keys(quantity).map((key: string) => { - return [key, quantity[key]]; - }); + const items: [string, number][] = Object.entries(quantity); - items.sort((first: any, second: any) => { + items.sort((first: [string, number], second: [string, number]) => { return second[1] - first[1]; }); items.splice(8); // Converting array to dictionary - let sorted: any = {}; - items.forEach((e: any) => { - let key = e[0]; - let value = e[1]; + const sorted: Quantity = {}; + items.forEach((e: [string, number]) => { + const key = e[0]; + const value = e[1]; sorted[key] = value; }); @@ -96,7 +92,7 @@ const getQuantity = (dialLengths: DialogueLengths) => { }; const getFrequency = (distribution: Distribution) => { - let frequency: Frequency = {}; + const frequency: Frequency = {}; for (const page in distribution) { const actors = distribution[page]; @@ -108,8 +104,9 @@ const getFrequency = (distribution: Distribution) => { return frequency; }; -export const getScreenplayData = (json: any): ScreenplayData => { - const nodes = json.content!; +import { JSONContent } from "@tiptap/core"; + +export const getScreenplayData = (nodes: JSONContent[]): ScreenplayData => { const maxPageY: number = 990; const pageLimits: number[] = []; // contains at which character page stops const distribution: Distribution = {}; @@ -127,8 +124,8 @@ export const getScreenplayData = (json: any): ScreenplayData => { continue; } - const type: string = currNode["attrs"]["class"]; - const content: string = currNode["content"][0]["text"]; + const type = (currNode.attrs?.["class"] as string) ?? ""; + const content = currNode.content![0].text ?? ""; const length = content.length; characters += length; words += content.split(" ").length; @@ -151,8 +148,8 @@ export const getScreenplayData = (json: any): ScreenplayData => { const nodeJ = nodes[j]; if (!nodeJ["content"]) continue; - const typeJ = nodeJ["attrs"]["class"]; - const contentJ = nodeJ["content"][0]["text"]; + const typeJ = nodeJ.attrs?.["class"]; + const contentJ = nodeJ.content![0].text ?? ""; if (typeJ === "parenthetical") continue; if (typeJ === "dialogue") { diff --git a/src/lib/spellcheck/spellcheck-extension.ts b/src/lib/spellcheck/spellcheck-extension.ts index 8f82255f..f57d88db 100644 --- a/src/lib/spellcheck/spellcheck-extension.ts +++ b/src/lib/spellcheck/spellcheck-extension.ts @@ -1,6 +1,7 @@ import { Editor, Extension } from "@tiptap/core"; +import { Node } from "@tiptap/pm/model"; import { Plugin, PluginKey } from "@tiptap/pm/state"; -import { Decoration, DecorationSet } from "@tiptap/pm/view"; +import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view"; import type { SpellWorkerRequest, SpellWorkerResponse } from "./spellcheck-types"; import { ScreenplayElement } from "../utils/enums"; @@ -61,11 +62,11 @@ const SPELL_ATTRS = { class: "spellcheck-error", nodeName: "span" }; * content start (i.e. relative to `nodePos + 1` in the document). * Also returns the combined text of the node to act as a cache key. */ -function extractNodeWords(node: any): { words: RelativeWord[]; text: string } { +function extractNodeWords(node: Node): { words: RelativeWord[]; text: string } { const words: RelativeWord[] = []; let combined = ""; - node.forEach((child: any) => { + node.forEach((child: Node) => { if (child.isText) { combined += child.text; } else { @@ -118,7 +119,7 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { let nextRequestId = 0; let debounceTimer: ReturnType | null = null; - let editorView: any = null; + let editorView: EditorView | null = null; /** * Pending spellcheck requests: id → nodes included in that request. @@ -270,7 +271,7 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { .setMeta("spellcheckViewport", { from, to }) .setMeta("addToHistory", false), ); - } catch (err) { + } catch { // posAtCoords might fail if outside coordinates } }; @@ -302,7 +303,7 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { init(_, { doc }): SpellPluginState { if (getEnabled()) { const nodesToCheck: Array<{ nodeId: string; words: RelativeWord[]; text: string }> = []; - doc.descendants((node: any) => { + doc.descendants((node: Node) => { if (node.isTextblock) { const nodeId = node.attrs?.["data-id"]; if (nodeId && SPELLCHECKED_TYPES.has(node.type.name)) { @@ -332,7 +333,7 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { wordCache.clear(); pendingRequests.clear(); const nodesToCheck: Array<{ nodeId: string; words: RelativeWord[]; text: string }> = []; - newState.doc.descendants((node: any) => { + newState.doc.descendants((node: Node) => { if (node.isTextblock) { const nodeId = node.attrs?.["data-id"]; if (nodeId && SPELLCHECKED_TYPES.has(node.type.name)) { @@ -357,7 +358,7 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { const { from, to } = tr.getMeta("spellcheckViewport"); const decos: Decoration[] = []; - newState.doc.nodesBetween(from, to, (node: any, pos: number) => { + newState.doc.nodesBetween(from, to, (node: Node, pos: number) => { if (node.isTextblock) { const nodeId = node.attrs?.["data-id"]; if (nodeId) { @@ -408,8 +409,8 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { const nodesToUpdate = new Set(); // Map nodeIds to absolute positions to add new decorations - const nodePositions = new Map(); - newState.doc.descendants((node: any, pos: number) => { + const nodePositions = new Map(); + newState.doc.descendants((node: Node, pos: number) => { if (node.isTextblock) { const nodeId = node.attrs?.["data-id"]; if (nodeId) nodePositions.set(nodeId, { pos, node }); @@ -485,13 +486,13 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { // 4. Document changed — blazingly fast remapping if (tr.docChanged) { // Map all existing decorations to their new positions - let decorations = prev.decorations.map(tr.mapping, newState.doc); + const decorations = prev.decorations.map(tr.mapping, newState.doc); const checkedNodes = prev.checkedNodes; const nodeErrors = new Map(prev.nodeErrors); // Find which nodes were actually affected by the transaction - const affectedMap = new Map(); + const affectedMap = new Map(); tr.mapping.maps.forEach((stepMap, i) => { stepMap.forEach((_os: number, _oe: number, ns: number, ne: number) => { @@ -501,7 +502,7 @@ export const createSpellcheckExtension = (config: SpellcheckConfig) => { const from = Math.min(mFrom, mTo); const to = Math.max(mFrom, mTo); - newState.doc.nodesBetween(from, to, (node: any, pos: number) => { + newState.doc.nodesBetween(from, to, (node: Node, pos: number) => { if (!node.isTextblock) return true; const nodeId = node.attrs?.["data-id"]; if (nodeId && SPELLCHECKED_TYPES.has(node.type.name)) { diff --git a/src/lib/titlepage/editor.ts b/src/lib/titlepage/editor.ts index b6ad3e82..161fee3d 100644 --- a/src/lib/titlepage/editor.ts +++ b/src/lib/titlepage/editor.ts @@ -1,4 +1,5 @@ import { Editor, Extension, getSchema } from "@tiptap/react"; +import { NodeSelection } from "@tiptap/pm/state"; import { TitlePageElement, Style } from "../utils/enums"; import Document from "@tiptap/extension-document"; @@ -116,7 +117,7 @@ export const getActiveTitlePageElement = (editor: Editor): TitlePageElement => { // Also check if the selection itself is a node selection const sel = editor.state.selection; if ("node" in sel) { - const node = (sel as any).node; + const node = (sel as NodeSelection).node; if (node && isFormatNode(node.type.name)) { return node.type.name as TitlePageElement; } @@ -148,14 +149,17 @@ export const TITLEPAGE_BASE_EXTENSIONS = [ export const TitlePageSchema = getSchema(TITLEPAGE_BASE_EXTENSIONS); -const LINE = (align: string = "left", content?: any[]) => ({ +type JSONMark = { type: string; attrs?: Record }; +type JSONInlineNode = { type: string; text?: string; marks?: JSONMark[] }; + +const LINE = (align: string = "left", content?: JSONInlineNode[]) => ({ type: "tp-text", attrs: { textAlign: align }, content, }); const TEXT = (text: string) => ({ type: "text", text }); -const FORMAT_NODE = (type: TitlePageElement, marks?: any[]) => (marks ? { type, marks } : { type }); +const FORMAT_NODE = (type: TitlePageElement, marks?: JSONMark[]) => (marks ? { type, marks } : { type }); const EMPTY = (align: string = "left") => LINE(align); export const DEFAULT_TITLEPAGE_CONTENT = [ diff --git a/src/lib/titlepage/nodes/format-marks.ts b/src/lib/titlepage/nodes/format-marks.ts index 77071d51..ab948044 100644 --- a/src/lib/titlepage/nodes/format-marks.ts +++ b/src/lib/titlepage/nodes/format-marks.ts @@ -1,4 +1,5 @@ import { Node } from "@tiptap/core"; +import { Mark, Node as PMNode } from "@tiptap/pm/model"; import { TitlePageElement } from "../../utils/enums"; import { titlePageMetadataRef } from "../metadata-ref"; @@ -55,14 +56,14 @@ function createFormatNode(name: TitlePageElement) { return [ { tag: "span", - getAttrs: (el: any) => { + getAttrs: (el: HTMLElement) => { return el.getAttribute("data-tp-type") === name ? {} : false; }, }, // Backward compat: parse old mark-based spans { tag: "span", - getAttrs: (el: any) => { + getAttrs: (el: HTMLElement) => { return el.getAttribute("class") === name ? {} : false; }, }, @@ -84,9 +85,9 @@ function createFormatNode(name: TitlePageElement) { dom.setAttribute("data-tp-type", name); // Apply base classes plus any mark-driven classes (e.g. underline) - const applyMarkClasses = (marks: readonly any[]) => { + const applyMarkClasses = (marks: readonly Mark[]) => { const markClasses = marks - .map((m: any) => m.attrs?.class || m.type.name) + .map((m: Mark) => m.attrs?.class || m.type.name) .filter(Boolean); // Preserve tp-format-placeholder before rebuilding className const isPlaceholder = dom.classList.contains("tp-format-placeholder"); @@ -108,14 +109,15 @@ function createFormatNode(name: TitlePageElement) { refresh(); // Register for metadata-driven updates - const updaters: Set<() => void> | undefined = (editor.storage as any) - .titlePageMetadata?.nodeViewUpdaters; + const updaters: Set<() => void> | undefined = ( + editor.storage as unknown as Record void> }> + ).titlePageMetadata?.nodeViewUpdaters; updaters?.add(refresh); return { dom, // Called by ProseMirror when this node's attrs or marks change - update(updatedNode: any) { + update(updatedNode: PMNode) { applyMarkClasses(updatedNode.marks); return true; }, diff --git a/src/lib/utils/api-handler.ts b/src/lib/utils/api-handler.ts index 7516bb93..7cc7582e 100644 --- a/src/lib/utils/api-handler.ts +++ b/src/lib/utils/api-handler.ts @@ -14,7 +14,7 @@ export type AuthApiContext = ApiContext & { user: CookieUser }; export type RouteParams = Record; export type SearchParams = Record; -function handleError(err: any): NextResponse { +function handleError(err: unknown): NextResponse { if (err instanceof AppError) { return NextResponse.json( { status: "error", message: err.message }, @@ -27,7 +27,7 @@ function handleError(err: any): NextResponse { } export const apiHandler = ( - handler: (req: NextRequest, context: T) => Promise, + handler: (req: NextRequest, context: T) => Promise, ) => { return async ( req: NextRequest, @@ -51,7 +51,7 @@ export const apiHandler = ( if (result instanceof Response) return result; return NextResponse.json(result, { status: 200 }); - } catch (err: any) { + } catch (err: unknown) { if (isRedirectError(err)) throw err; return handleError(err); }