forked from akai/readest
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 72c6455fca | |||
| 3d0a2b82ba | |||
| 72949e726f | |||
| 23206bd528 | |||
| 8f26f9a7fa | |||
| a16ca22814 | |||
| 82fb92cf86 | |||
| f76bce5575 | |||
| a23447a813 |
@@ -82,6 +82,13 @@ This project is a monorepo. The code for the `readest-app` is in the `app/reades
|
||||
| `pnpm dev-web` | Starts the development server for the web app only |
|
||||
| `pnpm build-web` | Builds the web app |
|
||||
|
||||
Recommended Visual Studio Code plugins for development:
|
||||
|
||||
- JavaScript and TypeScript Nightly (ms-vscode.vscode-typescript-next)
|
||||
- VS Code ESLint extension (dbaeumer.vscode-eslint)
|
||||
- Prettier - Code formatter (esbenp.prettier-vscode)
|
||||
- rust-analyzer (rust-lang.rust-analyzer) (for Tauri development only)
|
||||
|
||||
### When you're done
|
||||
|
||||
Check that your code follows the project's style guidelines by running:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.8.5",
|
||||
"version": "0.8.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev",
|
||||
@@ -38,6 +38,7 @@
|
||||
"@tauri-apps/plugin-fs": "^2.2.0",
|
||||
"@tauri-apps/plugin-http": "^2.2.0",
|
||||
"@tauri-apps/plugin-log": "^2.2.0",
|
||||
"@tauri-apps/plugin-opener": "^2.2.2",
|
||||
"@tauri-apps/plugin-os": "^2.2.0",
|
||||
"@tauri-apps/plugin-process": "^2.2.0",
|
||||
"@tauri-apps/plugin-shell": "~2.2.0",
|
||||
|
||||
Generated
+311
-11
@@ -21,6 +21,7 @@ dependencies = [
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-oauth",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-os",
|
||||
"tauri-plugin-process",
|
||||
"tauri-plugin-shell",
|
||||
@@ -197,7 +198,7 @@ dependencies = [
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"zbus",
|
||||
"zbus 5.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -212,6 +213,91 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-executor"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand",
|
||||
"futures-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-fs"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"blocking",
|
||||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
@@ -223,6 +309,24 @@ dependencies = [
|
||||
"syn 2.0.91",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
@@ -245,6 +349,12 @@ dependencies = [
|
||||
"syn 2.0.91",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.83"
|
||||
@@ -414,6 +524,19 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.5.3"
|
||||
@@ -1486,6 +1609,19 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
@@ -1898,6 +2034,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@@ -3351,6 +3493,17 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
@@ -3383,6 +3536,21 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.10.0"
|
||||
@@ -4252,6 +4420,17 @@ 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.8"
|
||||
@@ -4906,6 +5085,28 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63ac39033ef1bb4d52da4878c3d8ab6d80b0a569d69208c884e6d4d54eb427b9"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"glob",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"open",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.9",
|
||||
"url",
|
||||
"windows",
|
||||
"zbus 4.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-os"
|
||||
version = "2.2.0"
|
||||
@@ -6569,6 +6770,44 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-task",
|
||||
"async-trait",
|
||||
"blocking",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
"zbus_macros 4.4.0",
|
||||
"zbus_names 3.0.0",
|
||||
"zvariant 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.2.0"
|
||||
@@ -6594,9 +6833,22 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"winnow 0.6.20",
|
||||
"xdg-home",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zbus_macros 5.2.0",
|
||||
"zbus_names 4.1.0",
|
||||
"zvariant 5.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.91",
|
||||
"zvariant_utils 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6609,9 +6861,20 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.91",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
"zbus_names 4.1.0",
|
||||
"zvariant 5.1.0",
|
||||
"zvariant_utils 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6623,7 +6886,7 @@ dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"winnow 0.6.20",
|
||||
"zvariant",
|
||||
"zvariant 5.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6711,6 +6974,19 @@ dependencies = [
|
||||
"thiserror 2.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.1.0"
|
||||
@@ -6723,8 +6999,21 @@ dependencies = [
|
||||
"static_assertions",
|
||||
"url",
|
||||
"winnow 0.6.20",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
"zvariant_derive 5.1.0",
|
||||
"zvariant_utils 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.91",
|
||||
"zvariant_utils 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6737,7 +7026,18 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.91",
|
||||
"zvariant_utils",
|
||||
"zvariant_utils 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.91",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -33,6 +33,7 @@ tauri-plugin-devtools = "2.0.0"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri-plugin-oauth = "2"
|
||||
tauri-plugin-opener = "2.2.2"
|
||||
[patch.crates-io]
|
||||
tauri = { path = "../../../packages/tauri/crates/tauri" }
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"process:allow-restart",
|
||||
"cli:default",
|
||||
"oauth:allow-start",
|
||||
"oauth:allow-cancel"
|
||||
"oauth:allow-cancel",
|
||||
"opener:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_oauth::init())
|
||||
.invoke_handler(tauri::generate_handler![start_server])
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use tauri::menu::MenuEvent;
|
||||
use tauri::menu::{SubmenuBuilder, HELP_SUBMENU_ID};
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
let global_menu = app.menu().unwrap();
|
||||
@@ -27,17 +27,12 @@ pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
}
|
||||
|
||||
pub fn handle_menu_event(app: &AppHandle, event: &MenuEvent) {
|
||||
let opener = app.opener();
|
||||
if event.id() == "privacy_policy" {
|
||||
if let Err(e) = app.shell().open("https://readest.com/privacy-policy", None) {
|
||||
eprintln!("Failed to open privacy policy: {}", e);
|
||||
}
|
||||
let _ = opener.open_url("https://readest.com/privacy-policy", None::<&str>);
|
||||
} else if event.id() == "report_issue" {
|
||||
if let Err(e) = app.shell().open("mailto:support@bilingify.com", None) {
|
||||
eprintln!("Failed to open mail client: {}", e);
|
||||
}
|
||||
let _ = opener.open_url("https://github.com/readest/readest/issues", None::<&str>);
|
||||
} else if event.id() == "readest_help" {
|
||||
if let Err(e) = app.shell().open("https://readest.com/support", None) {
|
||||
eprintln!("Failed to open support page: {}", e);
|
||||
}
|
||||
let _ = opener.open_url("https://readest.com/support", None::<&str>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "12.0"
|
||||
},
|
||||
"linux": {
|
||||
"deb": {
|
||||
"section": "text"
|
||||
}
|
||||
},
|
||||
"fileAssociations": [
|
||||
{
|
||||
"name": "epub",
|
||||
@@ -126,7 +131,7 @@
|
||||
},
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU0OTAxMURGQkUzQjFENTQKUldSVUhUdSszeEdRNUExdmFkWnlvYWNYNG5wamkxMmUxRk5SejlMOTJVd28yNXlVTDh6Wld4OC8K",
|
||||
"endpoints": ["https://github.com/chrox/readest/releases/latest/download/latest.json"]
|
||||
"endpoints": ["https://github.com/readest/readest/releases/latest/download/latest.json"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ import { useAuth } from '@/context/AuthContext';
|
||||
import { supabase } from '@/utils/supabase';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { start, cancel, onUrl, onInvalidUrl } from '@fabianlars/tauri-plugin-oauth';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { handleAuthCallback } from '@/helpers/auth';
|
||||
import { IoArrowBack } from 'react-icons/io5';
|
||||
|
||||
type OAuthProvider = 'google' | 'apple' | 'azure' | 'github';
|
||||
|
||||
@@ -48,6 +49,7 @@ export default function AuthPage() {
|
||||
const { isDarkMode } = useTheme();
|
||||
const [port, setPort] = useState<number | null>(null);
|
||||
const isOAuthServerRunning = useRef(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
|
||||
const signIn = async (provider: OAuthProvider) => {
|
||||
if (!supabase) {
|
||||
@@ -66,7 +68,7 @@ export default function AuthPage() {
|
||||
console.error('Authentication error:', error);
|
||||
return;
|
||||
}
|
||||
open(data.url);
|
||||
openUrl(data.url);
|
||||
};
|
||||
|
||||
const startOAuthServer = async () => {
|
||||
@@ -134,8 +136,22 @@ export default function AuthPage() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!isMounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isTauriAppPlatform() ? (
|
||||
<div className='flex pt-11'>
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className='btn btn-ghost fixed left-3 top-11 h-8 min-h-8 w-8 p-0'
|
||||
>
|
||||
<IoArrowBack size={20} />
|
||||
</button>
|
||||
<div style={{ maxWidth: '420px', margin: 'auto', padding: '2rem' }}>
|
||||
<ProviderLogin
|
||||
provider='google'
|
||||
@@ -173,7 +189,13 @@ export default function AuthPage() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ maxWidth: '420px', margin: 'auto', padding: '2rem' }}>
|
||||
<div style={{ maxWidth: '420px', margin: 'auto', padding: '2rem', paddingTop: '4rem' }}>
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className='btn btn-ghost fixed left-10 top-6 h-8 min-h-8 w-8 p-0'
|
||||
>
|
||||
<IoArrowBack size={20} />
|
||||
</button>
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
|
||||
@@ -8,12 +8,20 @@ import { MdCheckCircle, MdCheckCircleOutline } from 'react-icons/md';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Menu, MenuItem } from '@tauri-apps/api/menu';
|
||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
import { Book, BooksGroup } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { navigateToReader } from '@/utils/nav';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
import { getFilename } from '@/utils/book';
|
||||
import { FILE_REVEAL_LABELS, FILE_REVEAL_PLATFORMS } from '@/utils/os';
|
||||
|
||||
import Alert from '@/components/Alert';
|
||||
import Spinner from '@/components/Spinner';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
|
||||
type BookshelfItem = Book | BooksGroup;
|
||||
|
||||
@@ -51,6 +59,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { deleteBook } = useLibraryStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedBooks, setSelectedBooks] = useState<string[]>([]);
|
||||
@@ -123,6 +132,32 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
|
||||
setShowDeleteAlert(true);
|
||||
};
|
||||
|
||||
const bookContextMenuHandler = async (book: Book, e: React.MouseEvent) => {
|
||||
if (!isTauriAppPlatform()) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const osPlatform = getOSPlatform();
|
||||
const fileRevealLabel =
|
||||
FILE_REVEAL_LABELS[osPlatform as FILE_REVEAL_PLATFORMS] || FILE_REVEAL_LABELS.default;
|
||||
const showBookInFinderMenuItem = await MenuItem.new({
|
||||
text: fileRevealLabel,
|
||||
action: async () => {
|
||||
const folder = `${settings.localBooksDir}/${getFilename(book)}`;
|
||||
revealItemInDir(folder);
|
||||
},
|
||||
});
|
||||
const deleteBookMenuItem = await MenuItem.new({
|
||||
text: 'Delete',
|
||||
action: async () => {
|
||||
deleteBook(envConfig, book);
|
||||
},
|
||||
});
|
||||
const menu = await Menu.new();
|
||||
menu.append(showBookInFinderMenuItem);
|
||||
menu.append(deleteBookMenuItem);
|
||||
menu.popup();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='bookshelf'>
|
||||
<div className='grid grid-cols-3 gap-0 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8'>
|
||||
@@ -135,6 +170,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
|
||||
{'format' in item ? (
|
||||
<div
|
||||
className='book-item cursor-pointer'
|
||||
onContextMenu={bookContextMenuHandler.bind(null, item as Book)}
|
||||
onClick={() => handleBookClick(item.hash)}
|
||||
>
|
||||
<div key={(item as Book).hash} className='bg-base-100 shadow-md'>
|
||||
|
||||
@@ -41,7 +41,15 @@ const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
bookmarks.push(bookmark);
|
||||
const existingBookmark = bookmarks.find(
|
||||
(item) => item.type === 'bookmark' && item.cfi === cfi,
|
||||
);
|
||||
if (existingBookmark) {
|
||||
existingBookmark.deletedAt = null;
|
||||
existingBookmark.updatedAt = Date.now();
|
||||
} else {
|
||||
bookmarks.push(bookmark);
|
||||
}
|
||||
const updatedConfig = updateBooknotes(bookKey, bookmarks);
|
||||
if (updatedConfig) {
|
||||
saveConfig(envConfig, bookKey, updatedConfig, settings);
|
||||
@@ -50,14 +58,15 @@ const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
|
||||
setIsBookmarked(false);
|
||||
const start = CFI.collapse(cfi);
|
||||
const end = CFI.collapse(cfi, true);
|
||||
const updatedConfig = updateBooknotes(
|
||||
bookKey,
|
||||
bookmarks.filter(
|
||||
(item) =>
|
||||
item.type !== 'bookmark' ||
|
||||
CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) > 0,
|
||||
),
|
||||
);
|
||||
bookmarks.forEach((item) => {
|
||||
if (
|
||||
item.type === 'bookmark' &&
|
||||
CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) <= 0
|
||||
) {
|
||||
item.deletedAt = Date.now();
|
||||
}
|
||||
});
|
||||
const updatedConfig = updateBooknotes(bookKey, bookmarks);
|
||||
if (updatedConfig) {
|
||||
saveConfig(envConfig, bookKey, updatedConfig, settings);
|
||||
}
|
||||
@@ -72,7 +81,7 @@ const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
|
||||
const start = CFI.collapse(cfi);
|
||||
const end = CFI.collapse(cfi, true);
|
||||
const locationBookmarked = booknotes
|
||||
.filter((booknote) => booknote.type === 'bookmark')
|
||||
.filter((booknote) => booknote.type === 'bookmark' && !booknote.deletedAt)
|
||||
.some((item) => CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) <= 0);
|
||||
setIsBookmarked(locationBookmarked);
|
||||
setBookmarkRibbonVisibility(bookKey, locationBookmarked);
|
||||
|
||||
@@ -104,7 +104,9 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const detail = (event as CustomEvent).detail;
|
||||
const { value: cfi, index, range } = detail;
|
||||
const { booknotes = [] } = config;
|
||||
const annotations = booknotes.filter((booknote) => booknote.type === 'annotation');
|
||||
const annotations = booknotes.filter(
|
||||
(booknote) => booknote.type === 'annotation' && !booknote.deletedAt,
|
||||
);
|
||||
const annotation = annotations.find((annotation) => annotation.cfi === cfi);
|
||||
if (!annotation) return;
|
||||
const selection = { key: bookKey, annotated: true, text: annotation.text ?? '', range, index };
|
||||
@@ -235,7 +237,8 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
};
|
||||
|
||||
const existingIndex = annotations.findIndex(
|
||||
(annotation) => annotation.cfi === cfi && annotation.type === 'excerpt',
|
||||
(annotation) =>
|
||||
annotation.cfi === cfi && annotation.type === 'excerpt' && !annotation.deletedAt,
|
||||
);
|
||||
if (existingIndex !== -1) {
|
||||
annotations[existingIndex] = annotation;
|
||||
@@ -270,7 +273,8 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
const existingIndex = annotations.findIndex(
|
||||
(annotation) => annotation.cfi === cfi && annotation.type === 'annotation',
|
||||
(annotation) =>
|
||||
annotation.cfi === cfi && annotation.type === 'annotation' && !annotation.deletedAt,
|
||||
);
|
||||
const views = getViewsById(bookKey.split('-')[0]!);
|
||||
if (existingIndex !== -1) {
|
||||
|
||||
@@ -84,14 +84,18 @@ const Notebook: React.FC = ({}) => {
|
||||
setNotebookNewAnnotation(null);
|
||||
};
|
||||
|
||||
const handleEditNote = (annotation: BookNote) => {
|
||||
const handleEditNote = (note: BookNote, isDelete: boolean) => {
|
||||
if (!sideBarBookKey) return;
|
||||
const config = getConfig(sideBarBookKey)!;
|
||||
const { booknotes: annotations = [] } = config;
|
||||
const existingIndex = annotations.findIndex((item) => item.id === annotation.id);
|
||||
const existingIndex = annotations.findIndex((item) => item.id === note.id);
|
||||
if (existingIndex === -1) return;
|
||||
annotation.updatedAt = Date.now();
|
||||
annotations[existingIndex] = annotation;
|
||||
if (isDelete) {
|
||||
note.deletedAt = Date.now();
|
||||
} else {
|
||||
note.updatedAt = Date.now();
|
||||
}
|
||||
annotations[existingIndex] = note;
|
||||
const updatedConfig = updateBooknotes(sideBarBookKey, annotations);
|
||||
if (updatedConfig) {
|
||||
saveConfig(envConfig, sideBarBookKey, updatedConfig, settings);
|
||||
@@ -100,27 +104,16 @@ const Notebook: React.FC = ({}) => {
|
||||
};
|
||||
|
||||
const { handleMouseDown } = useDragBar(handleDragMove);
|
||||
const deleteNote = (note: BookNote) => {
|
||||
if (!sideBarBookKey) return;
|
||||
const config = getConfig(sideBarBookKey);
|
||||
if (!config) return;
|
||||
const { booknotes = [] } = config;
|
||||
const updatedNotes = booknotes.filter((item) => item.id !== note.id);
|
||||
const updatedConfig = updateBooknotes(sideBarBookKey, updatedNotes);
|
||||
if (updatedConfig) {
|
||||
saveConfig(envConfig, sideBarBookKey, updatedConfig, settings);
|
||||
}
|
||||
};
|
||||
|
||||
if (!sideBarBookKey) return null;
|
||||
|
||||
const config = getConfig(sideBarBookKey);
|
||||
const { booknotes: allNotes = [] } = config || {};
|
||||
const annotationNotes = allNotes
|
||||
.filter((note) => note.type === 'annotation' && note.note)
|
||||
.filter((note) => note.type === 'annotation' && note.note && !note.deletedAt)
|
||||
.sort((a, b) => b.createdAt - a.createdAt);
|
||||
const excerptNotes = allNotes
|
||||
.filter((note) => note.type === 'excerpt')
|
||||
.filter((note) => note.type === 'excerpt' && note.text && !note.deletedAt)
|
||||
.sort((a, b) => a.createdAt - b.createdAt);
|
||||
|
||||
return isNotebookVisible ? (
|
||||
@@ -176,7 +169,7 @@ const Notebook: React.FC = ({}) => {
|
||||
'btn btn-ghost settings-content hover:bg-transparent',
|
||||
'flex h-[1em] min-h-[1em] items-end p-0',
|
||||
)}
|
||||
onClick={deleteNote.bind(null, item)}
|
||||
onClick={handleEditNote.bind(null, item, true)}
|
||||
>
|
||||
<div className='align-bottom text-xs text-red-400'>Delete</div>
|
||||
</button>
|
||||
@@ -192,7 +185,7 @@ const Notebook: React.FC = ({}) => {
|
||||
)}
|
||||
</div>
|
||||
{(notebookNewAnnotation || notebookEditAnnotation) && (
|
||||
<NoteEditor onSave={handleSaveNote} onEdit={handleEditNote} />
|
||||
<NoteEditor onSave={handleSaveNote} onEdit={(item) => handleEditNote(item, false)} />
|
||||
)}
|
||||
<ul className=''>
|
||||
{annotationNotes.map((item, index) => (
|
||||
|
||||
@@ -39,8 +39,12 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = f
|
||||
const config = getConfig(bookKey);
|
||||
if (!config) return;
|
||||
const { booknotes = [] } = config;
|
||||
const updatedNotes = booknotes.filter((item) => item.id !== note.id);
|
||||
const updatedConfig = updateBooknotes(bookKey, updatedNotes);
|
||||
booknotes.forEach((item) => {
|
||||
if (item.id === note.id) {
|
||||
item.deletedAt = Date.now();
|
||||
}
|
||||
});
|
||||
const updatedConfig = updateBooknotes(bookKey, booknotes);
|
||||
if (updatedConfig) {
|
||||
saveConfig(envConfig, bookKey, updatedConfig, settings);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const BooknoteView: React.FC<{
|
||||
const { getConfig } = useBookDataStore();
|
||||
const config = getConfig(bookKey)!;
|
||||
const { booknotes: allNotes = [] } = config;
|
||||
const booknotes = allNotes.filter((note) => note.type === type);
|
||||
const booknotes = allNotes.filter((note) => note.type === type && !note.deletedAt);
|
||||
|
||||
const booknoteGroups: { [href: string]: BooknoteGroup } = {};
|
||||
for (const booknote of booknotes) {
|
||||
|
||||
@@ -13,15 +13,16 @@ export const useNotesSync = (bookKey: string) => {
|
||||
const bookHash = bookKey.split('-')[0]!;
|
||||
|
||||
useEffect(() => {
|
||||
if (!config || !user) return;
|
||||
if (!user) return;
|
||||
syncNotes([], bookHash, 'pull');
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const lastSyncTime = useRef<number>(0);
|
||||
const syncTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
useEffect(() => {
|
||||
if (!config || !user) return;
|
||||
|
||||
const getNewNotes = () => {
|
||||
if (!config?.location || !user) return [];
|
||||
const bookNotes = config.booknotes ?? [];
|
||||
const newNotes = bookNotes.filter(
|
||||
(note) => lastSyncedAtNotes < note.updatedAt || lastSyncedAtNotes < (note.deletedAt ?? 0),
|
||||
@@ -29,16 +30,23 @@ export const useNotesSync = (bookKey: string) => {
|
||||
newNotes.forEach((note) => {
|
||||
note.bookHash = bookHash;
|
||||
});
|
||||
return newNotes;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!config?.location || !user) return;
|
||||
const now = Date.now();
|
||||
const timeSinceLastSync = now - lastSyncTime.current;
|
||||
if (timeSinceLastSync > SYNC_NOTES_INTERVAL_SEC * 1000) {
|
||||
lastSyncTime.current = now;
|
||||
const newNotes = getNewNotes();
|
||||
syncNotes(newNotes, bookHash, 'both');
|
||||
} else {
|
||||
if (syncTimeoutRef.current) clearTimeout(syncTimeoutRef.current);
|
||||
syncTimeoutRef.current = setTimeout(
|
||||
() => {
|
||||
lastSyncTime.current = Date.now();
|
||||
const newNotes = getNewNotes();
|
||||
syncNotes(newNotes, bookHash, 'both');
|
||||
syncTimeoutRef.current = null;
|
||||
},
|
||||
@@ -49,7 +57,7 @@ export const useNotesSync = (bookKey: string) => {
|
||||
}, [config]);
|
||||
|
||||
useEffect(() => {
|
||||
if (syncedNotes?.length && config) {
|
||||
if (syncedNotes?.length && config?.location) {
|
||||
const newNotes = syncedNotes.filter((note) => note.bookHash === bookHash);
|
||||
if (!newNotes.length) return;
|
||||
const oldNotes = config.booknotes ?? [];
|
||||
|
||||
@@ -31,7 +31,7 @@ export const useProgressSync = (
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!config || !user) return;
|
||||
if (!user) return;
|
||||
const bookHash = bookKey.split('-')[0]!;
|
||||
syncConfigs([], bookHash, 'pull');
|
||||
return () => {
|
||||
@@ -43,7 +43,7 @@ export const useProgressSync = (
|
||||
const lastProgressSyncTime = useRef<number>(0);
|
||||
const syncTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
useEffect(() => {
|
||||
if (!config || !user) return;
|
||||
if (!config?.location || !user) return;
|
||||
const now = Date.now();
|
||||
const timeSinceLastSync = now - lastProgressSyncTime.current;
|
||||
if (configSynced.current && timeSinceLastSync > SYNC_PROGRESS_INTERVAL_SEC * 1000) {
|
||||
|
||||
@@ -59,13 +59,14 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
localStorage.setItem('user', JSON.stringify(newUser));
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
const logout = async () => {
|
||||
console.log('Logging out');
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
await supabase.auth.refreshSession();
|
||||
await supabase.auth.signOut();
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
supabase.auth.signOut();
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { supabase } from '@/utils/supabase';
|
||||
import { Book, BookConfig, BookNote, BookDataRecord } from '@/types/book';
|
||||
import { READEST_WEB_BASE_URL } from '@/services/constants';
|
||||
import { isWebAppPlatform } from '@/services/environment';
|
||||
|
||||
const BASE_URL = process.env['NODE_ENV'] === 'production' ? READEST_WEB_BASE_URL : '';
|
||||
// Develop Sync API only in development mode and web platform
|
||||
// with command `pnpm dev-web`
|
||||
const SYNC_API_ENDPOINT =
|
||||
process.env['NODE_ENV'] === 'development' && isWebAppPlatform()
|
||||
? '/api/sync'
|
||||
: `${READEST_WEB_BASE_URL}/api/sync`;
|
||||
|
||||
export type SyncType = 'books' | 'configs' | 'notes';
|
||||
export type SyncOp = 'push' | 'pull' | 'both';
|
||||
@@ -32,7 +38,7 @@ export class SyncClient {
|
||||
const token = await this.getAccessToken();
|
||||
if (!token) throw new Error('Not authenticated');
|
||||
|
||||
const url = `${BASE_URL}/api/sync?since=${encodeURIComponent(since)}&type=${type ?? ''}&book=${book ?? ''}`;
|
||||
const url = `${SYNC_API_ENDPOINT}?since=${encodeURIComponent(since)}&type=${type ?? ''}&book=${book ?? ''}`;
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
@@ -55,7 +61,7 @@ export class SyncClient {
|
||||
const token = await this.getAccessToken();
|
||||
if (!token) throw new Error('Not authenticated');
|
||||
|
||||
const res = await fetch(`${BASE_URL}/api/sync`, {
|
||||
const res = await fetch(SYNC_API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -101,7 +101,11 @@ export async function GET(req: NextRequest) {
|
||||
return NextResponse.json({ error: errorMsg }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json(results, { status: 200 });
|
||||
const response = NextResponse.json(results, { status: 200 });
|
||||
response.headers.set('Cache-Control', 'no-store');
|
||||
response.headers.set('Pragma', 'no-cache');
|
||||
response.headers.delete('ETag');
|
||||
return response;
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
const errorMessage = (error as PostgrestError).message || 'Unknown error';
|
||||
@@ -146,6 +150,8 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (!serverData) {
|
||||
// use server updated_at for new records
|
||||
dbRec.updated_at = new Date().toISOString();
|
||||
const { data: inserted, error: insertError } = await supabase
|
||||
.from(table)
|
||||
.insert(dbRec)
|
||||
@@ -167,6 +173,8 @@ export async function POST(req: NextRequest) {
|
||||
clientDeletedAt > serverDeletedAt || clientUpdatedAt > serverUpdatedAt;
|
||||
|
||||
if (clientIsNewer) {
|
||||
// use server updated_at for updated records
|
||||
dbRec.updated_at = new Date().toISOString();
|
||||
const { data: updated, error: updateError } = await supabase
|
||||
.from(table)
|
||||
.update(dbRec)
|
||||
|
||||
@@ -132,7 +132,7 @@ export abstract class BaseAppService implements AppService {
|
||||
const existingBook = books.filter((b) => b.hash === hash)[0];
|
||||
if (existingBook) {
|
||||
if (existingBook.deletedAt) {
|
||||
delete existingBook.deletedAt;
|
||||
existingBook.deletedAt = null;
|
||||
}
|
||||
existingBook.updatedAt = Date.now();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface Book {
|
||||
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
deletedAt?: number;
|
||||
deletedAt?: number | null;
|
||||
|
||||
lastUpdated?: number; // deprecated in favor of updatedAt
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export interface BookNote {
|
||||
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
deletedAt?: number;
|
||||
deletedAt?: number | null;
|
||||
}
|
||||
|
||||
export interface BookLayout {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const FILE_REVEAL_LABELS = {
|
||||
macos: 'Reveal in Finder',
|
||||
windows: 'Reveal in File Explorer',
|
||||
linux: 'Reveal in Folder',
|
||||
default: 'Reveal in Folder',
|
||||
};
|
||||
|
||||
export type FILE_REVEAL_PLATFORMS = keyof typeof FILE_REVEAL_LABELS;
|
||||
@@ -50,7 +50,7 @@ export const transformBookToDB = (book: unknown, userId: string): DBBook => {
|
||||
tags: tags && JSON.stringify(tags),
|
||||
created_at: new Date(createdAt).toISOString(),
|
||||
updated_at: new Date(updatedAt).toISOString(),
|
||||
deleted_at: deletedAt ? new Date(deletedAt).toISOString() : undefined,
|
||||
deleted_at: deletedAt ? new Date(deletedAt).toISOString() : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ export const transformBookFromDB = (dbBook: DBBook): Book => {
|
||||
tags: tags && JSON.parse(tags),
|
||||
createdAt: new Date(created_at!).getTime(),
|
||||
updatedAt: new Date(updated_at!).getTime(),
|
||||
deletedAt: deleted_at ? new Date(deleted_at!).getTime() : undefined,
|
||||
deletedAt: deleted_at ? new Date(deleted_at!).getTime() : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -87,7 +87,8 @@ export const transformBookNoteToDB = (bookNote: unknown, userId: string): DBBook
|
||||
note,
|
||||
created_at: new Date(createdAt).toISOString(),
|
||||
updated_at: new Date(updatedAt).toISOString(),
|
||||
deleted_at: deletedAt ? new Date(deletedAt).toISOString() : undefined,
|
||||
// note that only null deleted_at is updated to the database, undefined is not
|
||||
deleted_at: deletedAt ? new Date(deletedAt).toISOString() : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -106,6 +107,6 @@ export const transformBookNoteFromDB = (dbBookNote: DBBookNote): BookNote => {
|
||||
note,
|
||||
createdAt: new Date(created_at!).getTime(),
|
||||
updatedAt: new Date(updated_at!).getTime(),
|
||||
deletedAt: deleted_at ? new Date(deleted_at!).getTime() : undefined,
|
||||
deletedAt: deleted_at ? new Date(deleted_at!).getTime() : null,
|
||||
};
|
||||
};
|
||||
|
||||
Generated
+10
@@ -62,6 +62,9 @@ importers:
|
||||
'@tauri-apps/plugin-log':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
'@tauri-apps/plugin-opener':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@tauri-apps/plugin-os':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
@@ -773,6 +776,9 @@ packages:
|
||||
'@tauri-apps/plugin-log@2.2.0':
|
||||
resolution: {integrity: sha512-g6CsQAR1lsm5ABSZZxpM/iCn86GrMDTTlhj7GPkZkYBRSm3+WczfOAl7SV7HDn77tOKCzhZffwI5uHfRoHutrw==}
|
||||
|
||||
'@tauri-apps/plugin-opener@2.2.2':
|
||||
resolution: {integrity: sha512-E/XIHKqGV+FT8PDdkfMETmgPUxcR79Rk8USuzbadD/ZdvsKCfQR5q+6rpZC9zEnG2wzi9lVQM4D3xwrtGGIB8A==}
|
||||
|
||||
'@tauri-apps/plugin-os@2.2.0':
|
||||
resolution: {integrity: sha512-HszbCdbisMlu5QhCNAN8YIWyz2v33abAWha6+uvV2CKX8P5VSct/y+kEe22JeyqrxCnWlQ3DRx7s49Byg7/0EA==}
|
||||
|
||||
@@ -3454,6 +3460,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.1.1
|
||||
|
||||
'@tauri-apps/plugin-opener@2.2.2':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.1.1
|
||||
|
||||
'@tauri-apps/plugin-os@2.2.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.1.1
|
||||
|
||||
Reference in New Issue
Block a user