Merge branch 'main' into backup-restore-system
This commit is contained in:
commit
1aa8e5d28c
5
.changeset/calm-bags-learn.md
Normal file
5
.changeset/calm-bags-learn.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'pastebar-app-ui': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added option to disable capturing and storing images from clipboard
|
5
.changeset/tasty-walls-pull.md
Normal file
5
.changeset/tasty-walls-pull.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'pastebar-app-ui': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Added custom data location to store application data in a folder of your choice instead of the default location.
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,6 +12,7 @@ safelist.txt
|
|||||||
clipboard-images/**/*
|
clipboard-images/**/*
|
||||||
clip-images/**/*
|
clip-images/**/*
|
||||||
src-tauri/http-cacache/**/*
|
src-tauri/http-cacache/**/*
|
||||||
|
pastebar_settings.yaml
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
packages/dist-ui/**/*
|
packages/dist-ui/**/*
|
||||||
|
131
custom_db_location_plan.md
Normal file
131
custom_db_location_plan.md
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# Plan: Implement Custom Data Location Feature
|
||||||
|
|
||||||
|
This document outlines the plan to implement the feature allowing users to specify a custom location for the PasteBar application's data.
|
||||||
|
|
||||||
|
## 1. Goals
|
||||||
|
|
||||||
|
* Allow users to specify a custom parent directory for application data via the settings UI.
|
||||||
|
* The application will create and manage a `pastebar-data` subdirectory within the user-specified location.
|
||||||
|
* This `pastebar-data` directory will contain the database file (`pastebar-db.data`), the `clip-images` folder, and the `clipboard-images` folder.
|
||||||
|
* Provide options to either **move** the existing data, **copy** it, or **use the new location without moving/copying**.
|
||||||
|
* Ensure the application uses the data from the new location after a restart.
|
||||||
|
* Handle potential errors gracefully and inform the user.
|
||||||
|
* Update the application state and backend configuration accordingly.
|
||||||
|
|
||||||
|
## 2. Backend (Rust - `src-tauri`)
|
||||||
|
|
||||||
|
### 2.1. Configuration (`user_settings_service.rs`)
|
||||||
|
|
||||||
|
* The `UserConfig` struct's `custom_db_path: Option<String>` will now be repurposed to store the path to the **user-selected parent directory**. The application logic will handle appending the `/pastebar-data/` segment. This requires no change to the struct itself, only to how the path is interpreted.
|
||||||
|
|
||||||
|
### 2.2. Path Logic (`db.rs` and new helpers)
|
||||||
|
|
||||||
|
* We will introduce new helper functions to consistently resolve data paths, whether default or custom.
|
||||||
|
* `get_data_dir() -> PathBuf`: This will be the core helper. It checks for a `custom_db_path` in the settings.
|
||||||
|
* If present, it returns `PathBuf::from(custom_path)`.
|
||||||
|
* If `None`, it returns the default application data directory.
|
||||||
|
* `get_db_path()`: This function will be refactored to use `get_data_dir().join("pastebar-db.data")`.
|
||||||
|
* `get_clip_images_dir()`: A new helper that returns `get_data_dir().join("clip-images")`.
|
||||||
|
* `get_clipboard_images_dir()`: A new helper that returns `get_data_dir().join("clipboard-images")`.
|
||||||
|
|
||||||
|
### 2.3. New & Updated Tauri Commands (`user_settings_command.rs`)
|
||||||
|
|
||||||
|
* **`cmd_validate_custom_db_path(path: String) -> Result<bool, String>`**
|
||||||
|
* **No change in purpose.** This command will still check if the user-selected directory is valid and writable.
|
||||||
|
* **`cmd_check_custom_data_path(path: String) -> Result<PathStatus, String>`**
|
||||||
|
* A new command to check the status of a selected directory. It returns one of the following statuses: `Empty`, `NotEmpty`, `IsPastebarDataAndNotEmpty`.
|
||||||
|
* **`cmd_set_and_relocate_data(new_parent_dir_path: String, operation: String) -> Result<String, String>`** (renamed from `set_and_relocate_db`)
|
||||||
|
* `new_parent_dir_path`: The new directory path selected by the user.
|
||||||
|
* `operation`: Either "move", "copy", or "none".
|
||||||
|
* **Updated Steps:**
|
||||||
|
1. Get the source paths:
|
||||||
|
* Current DB file path.
|
||||||
|
* Current `clip-images` directory path.
|
||||||
|
* Current `clipboard-images` directory path.
|
||||||
|
2. Define the new data directory: `let new_data_dir = Path::new(&new_parent_dir_path);`
|
||||||
|
3. Create the new data directory: `fs::create_dir_all(&new_data_dir)`.
|
||||||
|
4. Perform file/directory operations for each item (DB file, `clip-images` dir, `clipboard-images` dir):
|
||||||
|
* If "move": `fs::rename(source, destination)`.
|
||||||
|
* If "copy": `fs::copy` for the file, and a recursive copy function for the directories.
|
||||||
|
* If "none", do nothing.
|
||||||
|
* Handle cases where source items might not exist (e.g., `clip-images` folder hasn't been created yet) by skipping them gracefully.
|
||||||
|
5. If successful, call `user_settings_service::set_custom_db_path(&new_parent_dir_path)`.
|
||||||
|
6. Return a success or error message.
|
||||||
|
|
||||||
|
* **`cmd_revert_to_default_data_location() -> Result<String, String>`** (renamed and simplified)
|
||||||
|
* **Updated Steps:**
|
||||||
|
1. Call `user_settings_service::remove_custom_db_path()` to clear the custom data path setting.
|
||||||
|
2. Return a success message indicating the setting has been removed.
|
||||||
|
|
||||||
|
## 3. Frontend (React)
|
||||||
|
|
||||||
|
* The UI has been updated to refer to "Custom Application Data Location" instead of "Custom Database Location".
|
||||||
|
* A third radio button option, "Use new location", has been added.
|
||||||
|
* The `handleBrowse` function now calls the `cmd_check_custom_data_path` command to analyze the selected directory and prompts the user accordingly.
|
||||||
|
* The `settingsStore.ts` has been updated to support the "none" operation.
|
||||||
|
|
||||||
|
## 4. User Interaction Flow (Mermaid Diagram)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph User Flow
|
||||||
|
A[User navigates to User Preferences] --> B{Custom Data Path Set?};
|
||||||
|
B -- Yes --> C[Display Current Custom Path];
|
||||||
|
B -- No --> D[Display Current Path: Default];
|
||||||
|
|
||||||
|
C --> E[Show "Revert to Default" Button];
|
||||||
|
D --> F[User Selects New Parent Directory];
|
||||||
|
F --> G{Path Status?};
|
||||||
|
G -- Empty --> H[Set Path];
|
||||||
|
G -- Not Empty --> I{Confirm "pastebar-data" subfolder};
|
||||||
|
I -- Yes --> J[Append "pastebar-data" to path];
|
||||||
|
J --> H;
|
||||||
|
I -- No --> H;
|
||||||
|
G -- Is 'pastebar-data' and Not Empty --> K[Alert user existing data will be used];
|
||||||
|
K --> H;
|
||||||
|
H --> L[User Selects Operation: Move/Copy/None];
|
||||||
|
L --> M[User Clicks "Apply and Restart"];
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Backend Logic
|
||||||
|
M --> N[Frontend calls `cmd_set_and_relocate_data`];
|
||||||
|
N -- Success --> O[1. Create new data dir if needed];
|
||||||
|
O --> P[2. Move/Copy/Skip data];
|
||||||
|
P --> Q[3. Update `custom_db_path` in settings];
|
||||||
|
Q --> R[Show Success Toast & Relaunch App];
|
||||||
|
N -- Error --> S[Show Error Toast];
|
||||||
|
|
||||||
|
E --> T[User Clicks "Revert"];
|
||||||
|
T --> U[Frontend calls `cmd_revert_to_default_data_location`];
|
||||||
|
U -- Success --> V[Move/Copy data back to default app dir & clear setting];
|
||||||
|
V --> W[Show Success Toast & Relaunch App];
|
||||||
|
U -- Error --> X[Show Error Toast];
|
||||||
|
end
|
||||||
|
|
||||||
|
D -- "Browse..." --> F;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Implementation Summary
|
||||||
|
|
||||||
|
The following changes have been implemented:
|
||||||
|
|
||||||
|
* **`packages/pastebar-app-ui/src/pages/settings/UserPreferences.tsx`**:
|
||||||
|
* Renamed "Custom Database Location" to "Custom Application Data Location".
|
||||||
|
* Added a third radio button for the "Use new location" option.
|
||||||
|
* Updated the `handleBrowse` function to call the new `cmd_check_custom_data_path` command and handle the different path statuses with user prompts.
|
||||||
|
* **`packages/pastebar-app-ui/src/store/settingsStore.ts`**:
|
||||||
|
* Updated the `applyCustomDbPath` function to accept the "none" operation.
|
||||||
|
* Updated the `revertToDefaultDbPath` function to call the renamed backend command.
|
||||||
|
* **`src-tauri/src/commands/user_settings_command.rs`**:
|
||||||
|
* Added the `cmd_check_custom_data_path` command.
|
||||||
|
* Renamed `cmd_set_and_relocate_db` to `cmd_set_and_relocate_data` and updated its logic to handle the "none" operation and the new data directory structure.
|
||||||
|
* Renamed `cmd_revert_to_default_db_location` to `cmd_revert_to_default_data_location` and updated its logic.
|
||||||
|
* **`src-tauri/src/db.rs`**:
|
||||||
|
* Refactored the `get_data_dir` function to no longer automatically append `pastebar-data`.
|
||||||
|
* Added `get_clip_images_dir` and `get_clipboard_images_dir` helper functions.
|
||||||
|
* **`src-tauri/src/main.rs`**:
|
||||||
|
* Registered the new and renamed commands in the `invoke_handler`.
|
||||||
|
* **`src-tauri/Cargo.toml`**:
|
||||||
|
* Added the `fs_extra` dependency for recursive directory copying.
|
||||||
|
* **`src-tauri/src/services/items_service.rs`** and **`src-tauri/src/services/history_service.rs`**:
|
||||||
|
* Updated to use the new `get_clip_images_dir` and `get_clipboard_images_dir` helper functions.
|
210
package-lock.json
generated
210
package-lock.json
generated
@ -135,7 +135,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.27.1",
|
"@changesets/cli": "^2.27.1",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
"@tauri-apps/cli": "^1.6.0",
|
"@tauri-apps/cli": "^1.6.3",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/codemirror": "^5.60.15",
|
"@types/codemirror": "^5.60.15",
|
||||||
@ -7000,10 +7000,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli": {
|
"node_modules/@tauri-apps/cli": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.6.3.tgz",
|
||||||
"integrity": "sha512-DBBpBl6GhTzm8ImMbKkfaZ4fDTykWrC7Q5OXP4XqD91recmDEn2LExuvuiiS3HYe7uP8Eb5B9NPHhqJb+Zo7qQ==",
|
"integrity": "sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": ">=7.5.2"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"tauri": "tauri.js"
|
"tauri": "tauri.js"
|
||||||
},
|
},
|
||||||
@ -7015,26 +7019,27 @@
|
|||||||
"url": "https://opencollective.com/tauri"
|
"url": "https://opencollective.com/tauri"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tauri-apps/cli-darwin-arm64": "1.6.0",
|
"@tauri-apps/cli-darwin-arm64": "1.6.3",
|
||||||
"@tauri-apps/cli-darwin-x64": "1.6.0",
|
"@tauri-apps/cli-darwin-x64": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf": "1.6.0",
|
"@tauri-apps/cli-linux-arm-gnueabihf": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-arm64-gnu": "1.6.0",
|
"@tauri-apps/cli-linux-arm64-gnu": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-arm64-musl": "1.6.0",
|
"@tauri-apps/cli-linux-arm64-musl": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-x64-gnu": "1.6.0",
|
"@tauri-apps/cli-linux-x64-gnu": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-x64-musl": "1.6.0",
|
"@tauri-apps/cli-linux-x64-musl": "1.6.3",
|
||||||
"@tauri-apps/cli-win32-arm64-msvc": "1.6.0",
|
"@tauri-apps/cli-win32-arm64-msvc": "1.6.3",
|
||||||
"@tauri-apps/cli-win32-ia32-msvc": "1.6.0",
|
"@tauri-apps/cli-win32-ia32-msvc": "1.6.3",
|
||||||
"@tauri-apps/cli-win32-x64-msvc": "1.6.0"
|
"@tauri-apps/cli-win32-x64-msvc": "1.6.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.3.tgz",
|
||||||
"integrity": "sha512-SNRwUD9nqGxY47mbY1CGTt/jqyQOU7Ps7Mx/mpgahL0FVUDiCEY/5L9QfEPPhEgccgcelEVn7i6aQHIkHyUtCA==",
|
"integrity": "sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@ -7044,13 +7049,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.3.tgz",
|
||||||
"integrity": "sha512-g2/uDR/eeH2arvuawA4WwaEOqv/7jDO/ZLNI3JlBjP5Pk8GGb3Kdy0ro1xQzF94mtk2mOnOXa4dMgAet4sUJ1A==",
|
"integrity": "sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@ -7060,13 +7066,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.3.tgz",
|
||||||
"integrity": "sha512-EVwf4oRkQyG8BpSrk0gqO7oA0sDM2MdNDtJpMfleYFEgCxLIOGZKNqaOW3M7U+0Y4qikmG3TtRK+ngc8Ymtrjg==",
|
"integrity": "sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -7076,13 +7083,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.3.tgz",
|
||||||
"integrity": "sha512-YdpY17cAySrhK9dX4BUVEmhAxE2o+6skIEFg8iN/xrDwRxhaNPI9I80YXPatUTX54Kx55T5++25VJG9+3iw83A==",
|
"integrity": "sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -7092,13 +7100,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.3.tgz",
|
||||||
"integrity": "sha512-4U628tuf2U8pMr4tIBJhEkrFwt+46dwhXrDlpdyWSZtnop5RJAVKHODm0KbWns4xGKfTW1F3r6sSv+2ZxLcISA==",
|
"integrity": "sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -7108,13 +7117,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.3.tgz",
|
||||||
"integrity": "sha512-AKRzp76fVUaJyXj5KRJT9bJyhwZyUnRQU0RqIRqOtZCT5yr6qGP8rjtQ7YhCIzWrseBlOllc3Qvbgw3Yl0VQcA==",
|
"integrity": "sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -7124,13 +7134,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.3.tgz",
|
||||||
"integrity": "sha512-0edIdq6aMBTaRMIXddHfyAFL361JqulLLd2Wi2aoOie7DkQ2MYh6gv3hA7NB9gqFwNIGE+xtJ4BkXIP2tSGPlg==",
|
"integrity": "sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -7140,13 +7151,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.3.tgz",
|
||||||
"integrity": "sha512-QwWpWk4ubcwJ1rljsRAmINgB2AwkyzZhpYbalA+MmzyYMREcdXWGkyixWbRZgqc6fEWEBmq5UG73qz5eBJiIKg==",
|
"integrity": "sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@ -7156,13 +7168,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.3.tgz",
|
||||||
"integrity": "sha512-Vtw0yxO9+aEFuhuxQ57ALG43tjECopRimRuKGbtZYDCriB/ty5TrT3QWMdy0dxBkpDTu3Rqsz30sbDzw6tlP3Q==",
|
"integrity": "sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@ -7172,13 +7185,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.3.tgz",
|
||||||
"integrity": "sha512-h54FHOvGi7+LIfRchzgZYSCHB1HDlP599vWXQQJ/XnwJY+6Rwr2E5bOe/EhqoG8rbGkfK0xX3KPAvXPbUlmggg==",
|
"integrity": "sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@ -7187,6 +7201,19 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/cli/node_modules/semver": {
|
||||||
|
"version": "7.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tootallnate/once": {
|
"node_modules/@tootallnate/once": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||||
@ -22361,90 +22388,99 @@
|
|||||||
"integrity": "sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg=="
|
"integrity": "sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg=="
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli": {
|
"@tauri-apps/cli": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.6.3.tgz",
|
||||||
"integrity": "sha512-DBBpBl6GhTzm8ImMbKkfaZ4fDTykWrC7Q5OXP4XqD91recmDEn2LExuvuiiS3HYe7uP8Eb5B9NPHhqJb+Zo7qQ==",
|
"integrity": "sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@tauri-apps/cli-darwin-arm64": "1.6.0",
|
"@tauri-apps/cli-darwin-arm64": "1.6.3",
|
||||||
"@tauri-apps/cli-darwin-x64": "1.6.0",
|
"@tauri-apps/cli-darwin-x64": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf": "1.6.0",
|
"@tauri-apps/cli-linux-arm-gnueabihf": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-arm64-gnu": "1.6.0",
|
"@tauri-apps/cli-linux-arm64-gnu": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-arm64-musl": "1.6.0",
|
"@tauri-apps/cli-linux-arm64-musl": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-x64-gnu": "1.6.0",
|
"@tauri-apps/cli-linux-x64-gnu": "1.6.3",
|
||||||
"@tauri-apps/cli-linux-x64-musl": "1.6.0",
|
"@tauri-apps/cli-linux-x64-musl": "1.6.3",
|
||||||
"@tauri-apps/cli-win32-arm64-msvc": "1.6.0",
|
"@tauri-apps/cli-win32-arm64-msvc": "1.6.3",
|
||||||
"@tauri-apps/cli-win32-ia32-msvc": "1.6.0",
|
"@tauri-apps/cli-win32-ia32-msvc": "1.6.3",
|
||||||
"@tauri-apps/cli-win32-x64-msvc": "1.6.0"
|
"@tauri-apps/cli-win32-x64-msvc": "1.6.3",
|
||||||
|
"semver": ">=7.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "7.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-darwin-arm64": {
|
"@tauri-apps/cli-darwin-arm64": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.3.tgz",
|
||||||
"integrity": "sha512-SNRwUD9nqGxY47mbY1CGTt/jqyQOU7Ps7Mx/mpgahL0FVUDiCEY/5L9QfEPPhEgccgcelEVn7i6aQHIkHyUtCA==",
|
"integrity": "sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-darwin-x64": {
|
"@tauri-apps/cli-darwin-x64": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.3.tgz",
|
||||||
"integrity": "sha512-g2/uDR/eeH2arvuawA4WwaEOqv/7jDO/ZLNI3JlBjP5Pk8GGb3Kdy0ro1xQzF94mtk2mOnOXa4dMgAet4sUJ1A==",
|
"integrity": "sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf": {
|
"@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.3.tgz",
|
||||||
"integrity": "sha512-EVwf4oRkQyG8BpSrk0gqO7oA0sDM2MdNDtJpMfleYFEgCxLIOGZKNqaOW3M7U+0Y4qikmG3TtRK+ngc8Ymtrjg==",
|
"integrity": "sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-linux-arm64-gnu": {
|
"@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.3.tgz",
|
||||||
"integrity": "sha512-YdpY17cAySrhK9dX4BUVEmhAxE2o+6skIEFg8iN/xrDwRxhaNPI9I80YXPatUTX54Kx55T5++25VJG9+3iw83A==",
|
"integrity": "sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-linux-arm64-musl": {
|
"@tauri-apps/cli-linux-arm64-musl": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.3.tgz",
|
||||||
"integrity": "sha512-4U628tuf2U8pMr4tIBJhEkrFwt+46dwhXrDlpdyWSZtnop5RJAVKHODm0KbWns4xGKfTW1F3r6sSv+2ZxLcISA==",
|
"integrity": "sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-linux-x64-gnu": {
|
"@tauri-apps/cli-linux-x64-gnu": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.3.tgz",
|
||||||
"integrity": "sha512-AKRzp76fVUaJyXj5KRJT9bJyhwZyUnRQU0RqIRqOtZCT5yr6qGP8rjtQ7YhCIzWrseBlOllc3Qvbgw3Yl0VQcA==",
|
"integrity": "sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-linux-x64-musl": {
|
"@tauri-apps/cli-linux-x64-musl": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.3.tgz",
|
||||||
"integrity": "sha512-0edIdq6aMBTaRMIXddHfyAFL361JqulLLd2Wi2aoOie7DkQ2MYh6gv3hA7NB9gqFwNIGE+xtJ4BkXIP2tSGPlg==",
|
"integrity": "sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-win32-arm64-msvc": {
|
"@tauri-apps/cli-win32-arm64-msvc": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.3.tgz",
|
||||||
"integrity": "sha512-QwWpWk4ubcwJ1rljsRAmINgB2AwkyzZhpYbalA+MmzyYMREcdXWGkyixWbRZgqc6fEWEBmq5UG73qz5eBJiIKg==",
|
"integrity": "sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-win32-ia32-msvc": {
|
"@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.3.tgz",
|
||||||
"integrity": "sha512-Vtw0yxO9+aEFuhuxQ57ALG43tjECopRimRuKGbtZYDCriB/ty5TrT3QWMdy0dxBkpDTu3Rqsz30sbDzw6tlP3Q==",
|
"integrity": "sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@tauri-apps/cli-win32-x64-msvc": {
|
"@tauri-apps/cli-win32-x64-msvc": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.3.tgz",
|
||||||
"integrity": "sha512-h54FHOvGi7+LIfRchzgZYSCHB1HDlP599vWXQQJ/XnwJY+6Rwr2E5bOe/EhqoG8rbGkfK0xX3KPAvXPbUlmggg==",
|
"integrity": "sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
@ -149,7 +149,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.27.1",
|
"@changesets/cli": "^2.27.1",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
"@tauri-apps/cli": "^1.6.0",
|
"@tauri-apps/cli": "^1.6.3",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/codemirror": "^5.60.15",
|
"@types/codemirror": "^5.60.15",
|
||||||
|
@ -107,6 +107,11 @@ function App() {
|
|||||||
|
|
||||||
settingsStore.initSettings({
|
settingsStore.initSettings({
|
||||||
appDataDir: import.meta.env.TAURI_DEBUG ? appDevDataDir : appDataDir,
|
appDataDir: import.meta.env.TAURI_DEBUG ? appDevDataDir : appDataDir,
|
||||||
|
// Initialize new DB path settings for type conformity; actual value loaded by loadInitialCustomDbPath
|
||||||
|
customDbPath: null,
|
||||||
|
isCustomDbPathValid: null,
|
||||||
|
customDbPathError: null,
|
||||||
|
dbRelocationInProgress: false,
|
||||||
appLastUpdateVersion: settings.appLastUpdateVersion?.valueText,
|
appLastUpdateVersion: settings.appLastUpdateVersion?.valueText,
|
||||||
appLastUpdateDate: settings.appLastUpdateDate?.valueText,
|
appLastUpdateDate: settings.appLastUpdateDate?.valueText,
|
||||||
isHideMacOSDockIcon: settings.isHideMacOSDockIcon?.valueBool,
|
isHideMacOSDockIcon: settings.isHideMacOSDockIcon?.valueBool,
|
||||||
@ -139,6 +144,7 @@ function App() {
|
|||||||
isHistoryEnabled: settings.isHistoryEnabled?.valueBool,
|
isHistoryEnabled: settings.isHistoryEnabled?.valueBool,
|
||||||
clipTextMinLength: settings.clipTextMinLength?.valueInt,
|
clipTextMinLength: settings.clipTextMinLength?.valueInt,
|
||||||
clipTextMaxLength: settings.clipTextMaxLength?.valueInt,
|
clipTextMaxLength: settings.clipTextMaxLength?.valueInt,
|
||||||
|
isImageCaptureDisabled: settings.isImageCaptureDisabled?.valueBool,
|
||||||
isAutoClearSettingsEnabled: settings.isAutoClearSettingsEnabled?.valueBool,
|
isAutoClearSettingsEnabled: settings.isAutoClearSettingsEnabled?.valueBool,
|
||||||
autoClearSettingsDuration: settings.autoClearSettingsDuration?.valueInt,
|
autoClearSettingsDuration: settings.autoClearSettingsDuration?.valueInt,
|
||||||
autoClearSettingsDurationType:
|
autoClearSettingsDurationType:
|
||||||
@ -187,6 +193,8 @@ function App() {
|
|||||||
settingsStore.initConstants({
|
settingsStore.initConstants({
|
||||||
APP_DETECT_LANGUAGES_SUPPORTED: appDetectLanguageSupport,
|
APP_DETECT_LANGUAGES_SUPPORTED: appDetectLanguageSupport,
|
||||||
})
|
})
|
||||||
|
// Load the actual custom DB path after basic settings are initialized
|
||||||
|
settingsStore.loadInitialCustomDbPath()
|
||||||
type().then(osType => {
|
type().then(osType => {
|
||||||
if (osType === 'Windows_NT' && settings.copyPasteDelay?.valueInt === 0) {
|
if (osType === 'Windows_NT' && settings.copyPasteDelay?.valueInt === 0) {
|
||||||
settingsStore.updateSetting('copyPasteDelay', 2)
|
settingsStore.updateSetting('copyPasteDelay', 2)
|
||||||
@ -424,6 +432,9 @@ function App() {
|
|||||||
if (name === 'isHistoryEnabled') {
|
if (name === 'isHistoryEnabled') {
|
||||||
settingsStore.updateSetting('isHistoryEnabled', Boolean(value_bool))
|
settingsStore.updateSetting('isHistoryEnabled', Boolean(value_bool))
|
||||||
}
|
}
|
||||||
|
if (name === 'isImageCaptureDisabled') {
|
||||||
|
settingsStore.updateSetting('isImageCaptureDisabled', Boolean(value_bool))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const listenToMenuUnlisten = listen('menu:add_first_menu_item', () => {
|
const listenToMenuUnlisten = listen('menu:add_first_menu_item', () => {
|
||||||
|
@ -191,6 +191,8 @@ export function NavBar() {
|
|||||||
setIsShowDisabledCollectionsOnNavBarMenu,
|
setIsShowDisabledCollectionsOnNavBarMenu,
|
||||||
isShowNavBarItemsOnHoverOnly,
|
isShowNavBarItemsOnHoverOnly,
|
||||||
isHideCollectionsOnNavBar,
|
isHideCollectionsOnNavBar,
|
||||||
|
isImageCaptureDisabled,
|
||||||
|
setIsImageCaptureDisabled,
|
||||||
} = useAtomValue(settingsStoreAtom)
|
} = useAtomValue(settingsStoreAtom)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -642,6 +644,23 @@ export function NavBar() {
|
|||||||
<Shortcut keys="CTRL+A" />
|
<Shortcut keys="CTRL+A" />
|
||||||
</MenubarShortcut>
|
</MenubarShortcut>
|
||||||
</MenubarCheckboxItem>
|
</MenubarCheckboxItem>
|
||||||
|
<MenubarCheckboxItem
|
||||||
|
checked={!isImageCaptureDisabled}
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault()
|
||||||
|
setIsImageCaptureDisabled(!isImageCaptureDisabled)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
className={`mr-2 ${
|
||||||
|
isImageCaptureDisabled
|
||||||
|
? 'text-slate-900/50'
|
||||||
|
: 'text-slate-800'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t('Enable Image Capture', { ns: 'history' })}
|
||||||
|
</Text>
|
||||||
|
</MenubarCheckboxItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem
|
<MenubarItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -24,6 +24,7 @@ Are you sure you want to delete?: Are you sure you want to delete?
|
|||||||
Are you sure?: Are you sure?
|
Are you sure?: Are you sure?
|
||||||
Attach History Window: Attach History Window
|
Attach History Window: Attach History Window
|
||||||
Back: Back
|
Back: Back
|
||||||
|
Browse...: Browse...
|
||||||
Build on {{buildDate}}: Build on {{buildDate}}
|
Build on {{buildDate}}: Build on {{buildDate}}
|
||||||
Cancel: Cancel
|
Cancel: Cancel
|
||||||
Cancel Reset: Cancel Reset
|
Cancel Reset: Cancel Reset
|
||||||
|
@ -11,6 +11,7 @@ Confirm Clear All History: Confirm Clear All History
|
|||||||
Do you really want to remove ALL clipboard history items?: Do you really want to remove ALL clipboard history items?
|
Do you really want to remove ALL clipboard history items?: Do you really want to remove ALL clipboard history items?
|
||||||
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}
|
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}
|
||||||
Enable Capture History: Enable Capture History
|
Enable Capture History: Enable Capture History
|
||||||
|
Enable Image Capture: Enable Image Capture
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: App Filters
|
App Filters: App Filters
|
||||||
Audio: Audio
|
Audio: Audio
|
||||||
|
@ -12,6 +12,11 @@ Application UI Color Theme: Application UI Color Theme
|
|||||||
Application UI Fonts Scale: Application UI Fonts Scale
|
Application UI Fonts Scale: Application UI Fonts Scale
|
||||||
Application UI Language: Application UI Language
|
Application UI Language: Application UI Language
|
||||||
Applications listed below will not have their copy to clipboard action captured in clipboard history. Case insensitive.: Applications listed below will not have their copy to clipboard action captured in clipboard history. Case insensitive.
|
Applications listed below will not have their copy to clipboard action captured in clipboard history. Case insensitive.: Applications listed below will not have their copy to clipboard action captured in clipboard history. Case insensitive.
|
||||||
|
Apply and Restart: Apply and Restart
|
||||||
|
Are you sure you want to go back to the default data folder? This will remove the custom location setting.: Are you sure you want to go back to the default data folder? This will remove the custom location setting.
|
||||||
|
Are you sure you want to revert to the default database location? The application will restart.: Are you sure you want to revert to the default database location? The application will restart.
|
||||||
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: Are you sure you want to set "{{path}}" as the new data folder? The application will restart.
|
||||||
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.
|
||||||
Auto Disable History Capture when Screen Unlocked: Auto Disable History Capture when Screen Unlocked
|
Auto Disable History Capture when Screen Unlocked: Auto Disable History Capture when Screen Unlocked
|
||||||
Auto Lock Application Screen on User Inactivity: Auto Lock Application Screen on User Inactivity
|
Auto Lock Application Screen on User Inactivity: Auto Lock Application Screen on User Inactivity
|
||||||
Auto Lock Screen on User Inactivity: Auto Lock Screen on User Inactivity
|
Auto Lock Screen on User Inactivity: Auto Lock Screen on User Inactivity
|
||||||
@ -30,25 +35,42 @@ Auto-delete clipboard history after: Auto-delete clipboard history after
|
|||||||
Back: Back
|
Back: Back
|
||||||
Capture History: Capture History
|
Capture History: Capture History
|
||||||
Capture History Text Length Limits: Capture History Text Length Limits
|
Capture History Text Length Limits: Capture History Text Length Limits
|
||||||
|
Change Custom Data Folder...: Change Custom Data Folder...
|
||||||
|
Change Directory...: Change Directory...
|
||||||
|
Change Selected Folder...: Change Selected Folder...
|
||||||
Change the application UI font size scale: Change the application UI font size scale
|
Change the application UI font size scale: Change the application UI font size scale
|
||||||
Change the application UI language: Change the application UI language
|
Change the application UI language: Change the application UI language
|
||||||
Change the application user interface color theme: Change the application user interface color theme
|
Change the application user interface color theme: Change the application user interface color theme
|
||||||
Change the application user interface font size scale: Change the application user interface font size scale
|
Change the application user interface font size scale: Change the application user interface font size scale
|
||||||
Change the application user interface language: Change the application user interface language
|
Change the application user interface language: Change the application user interface language
|
||||||
|
Changing the database location requires an application restart to take effect.: Changing the database location requires an application restart to take effect.
|
||||||
Clip Notes Popup Maximum Dimensions: Clip Notes Popup Maximum Dimensions
|
Clip Notes Popup Maximum Dimensions: Clip Notes Popup Maximum Dimensions
|
||||||
Clipboard History Settings: Clipboard History Settings
|
Clipboard History Settings: Clipboard History Settings
|
||||||
'Complete details:': 'Complete details:'
|
'Complete details:': 'Complete details:'
|
||||||
Configure settings to automatically delete clipboard history items after a specified duration.: Configure settings to automatically delete clipboard history items after a specified duration.
|
Configure settings to automatically delete clipboard history items after a specified duration.: Configure settings to automatically delete clipboard history items after a specified duration.
|
||||||
|
ConfirmRevertToDefaultDbPathMessage: Are you sure you want to revert to the default data location? Your application data will be moved to the default path.
|
||||||
|
Copy data: Copy data
|
||||||
|
Copy database file: Copy database file
|
||||||
Create a preview card on link hover in the clipboard history. This allows you to preview the link before opening or pasting it.: Create a preview card on link hover in the clipboard history. This allows you to preview the link before opening or pasting it.
|
Create a preview card on link hover in the clipboard history. This allows you to preview the link before opening or pasting it.: Create a preview card on link hover in the clipboard history. This allows you to preview the link before opening or pasting it.
|
||||||
Create an unlimited number of collections to organize your clips and menus.: Create an unlimited number of collections to organize your clips and menus.
|
Create an unlimited number of collections to organize your clips and menus.: Create an unlimited number of collections to organize your clips and menus.
|
||||||
|
Current data folder: Current data folder
|
||||||
|
Current data location: Current data location
|
||||||
|
Current database location: Current database location
|
||||||
Custom: Custom
|
Custom: Custom
|
||||||
|
Custom Application Data Location: Custom Application Data Location
|
||||||
|
Custom Database Location: Custom Database Location
|
||||||
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: Custom data location is active. You can change it or revert to the default location. Changes require an application restart.
|
||||||
Custom themes: Custom themes
|
Custom themes: Custom themes
|
||||||
Decrease UI Font Size: Decrease UI Font Size
|
Decrease UI Font Size: Decrease UI Font Size
|
||||||
|
Default: Default
|
||||||
|
Disable Image Capture: Disable Image Capture
|
||||||
|
Disable capturing and storing images from clipboard: Disable capturing and storing images from clipboard
|
||||||
? Display clipboard history capture toggle on the locked application screen. This allows you to control history capture settings directly from the lock screen.
|
? Display clipboard history capture toggle on the locked application screen. This allows you to control history capture settings directly from the lock screen.
|
||||||
: Display clipboard history capture toggle on the locked application screen. This allows you to control history capture settings directly from the lock screen.
|
: Display clipboard history capture toggle on the locked application screen. This allows you to control history capture settings directly from the lock screen.
|
||||||
Display disabled collections name on the navigation bar collections menu: Display disabled collections name on the navigation bar collections menu
|
Display disabled collections name on the navigation bar collections menu: Display disabled collections name on the navigation bar collections menu
|
||||||
Display disabled collections name on the navigation bar under collections menu: Display disabled collections name on the navigation bar under collections menu
|
Display disabled collections name on the navigation bar under collections menu: Display disabled collections name on the navigation bar under collections menu
|
||||||
Display full name of selected collection on the navigation bar: Display full name of selected collection on the navigation bar
|
Display full name of selected collection on the navigation bar: Display full name of selected collection on the navigation bar
|
||||||
|
Do you want to attempt to move the database file from "{{customPath}}" back to the default location?: Do you want to attempt to move the database file from "{{customPath}}" back to the default location?
|
||||||
Drag and drop to prioritize languages for detection. The higher a language is in the list, the higher its detection priority.: Drag and drop to prioritize languages for detection. The higher a language is in the list, the higher its detection priority.
|
Drag and drop to prioritize languages for detection. The higher a language is in the list, the higher its detection priority.: Drag and drop to prioritize languages for detection. The higher a language is in the list, the higher its detection priority.
|
||||||
Email: Email
|
Email: Email
|
||||||
Email is not valid: Email is not valid
|
Email is not valid: Email is not valid
|
||||||
@ -61,11 +83,12 @@ Enable auto lock the application screen after a certain period of inactivity, to
|
|||||||
Enable auto lock the application screen when user not active: Enable auto lock the application screen when user not active
|
Enable auto lock the application screen when user not active: Enable auto lock the application screen when user not active
|
||||||
Enable auto trim spaces on history capture: Enable auto trim spaces on history capture
|
Enable auto trim spaces on history capture: Enable auto trim spaces on history capture
|
||||||
Enable auto update on capture: Enable auto update on capture
|
Enable auto update on capture: Enable auto update on capture
|
||||||
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: Enable custom data location to store application data in a directory of your choice instead of the default location.
|
||||||
Enable history capture: Enable history capture
|
Enable history capture: Enable history capture
|
||||||
Enable programming language detection: Enable programming language detection
|
Enable programming language detection: Enable programming language detection
|
||||||
Enable screen unlock requirement on app launch for enhanced security, safeguarding data from unauthorized access.: Enable screen unlock requirement on app launch for enhanced security, safeguarding data from unauthorized access.
|
Enable screen unlock requirement on app launch for enhanced security, safeguarding data from unauthorized access.: Enable screen unlock requirement on app launch for enhanced security, safeguarding data from unauthorized access.
|
||||||
Enhance security by automatically locking the application screen after a set period of user inactivity.: Enhance security by automatically locking the application screen after a set period of user inactivity.
|
|
||||||
Enter Passcode length: Enter Passcode length
|
Enter Passcode length: Enter Passcode length
|
||||||
|
Enter new directory path or leave empty for default on next revert: Enter new directory path or leave empty for default on next revert
|
||||||
Enter recovery password to reset passcode.: Enter recovery password to reset passcode.
|
Enter recovery password to reset passcode.: Enter recovery password to reset passcode.
|
||||||
Enter your <strong>{{screenLockPassCodeLength}} digits</strong> passcode: Enter your <strong>{{screenLockPassCodeLength}} digits</strong> passcode
|
Enter your <strong>{{screenLockPassCodeLength}} digits</strong> passcode: Enter your <strong>{{screenLockPassCodeLength}} digits</strong> passcode
|
||||||
Entered Passcode is invalid: Entered Passcode is invalid
|
Entered Passcode is invalid: Entered Passcode is invalid
|
||||||
@ -73,12 +96,16 @@ Excluded Apps List: Excluded Apps List
|
|||||||
Execute Web Requests: Execute Web Requests
|
Execute Web Requests: Execute Web Requests
|
||||||
Execute terminal or shell commands directly from PasteBar clip and copy the results to the clipboard.: Execute terminal or shell commands directly from PasteBar clip and copy the results to the clipboard.
|
Execute terminal or shell commands directly from PasteBar clip and copy the results to the clipboard.: Execute terminal or shell commands directly from PasteBar clip and copy the results to the clipboard.
|
||||||
'Expires:': 'Expires:'
|
'Expires:': 'Expires:'
|
||||||
|
Failed to revert to default database location.: Failed to revert to default database location.
|
||||||
|
Failed to select directory: Failed to select directory
|
||||||
Forgot Passcode ? Enter your recovery password to reset the passcode.: Forgot Passcode ? Enter your recovery password to reset the passcode.
|
Forgot Passcode ? Enter your recovery password to reset the passcode.: Forgot Passcode ? Enter your recovery password to reset the passcode.
|
||||||
Forgot passcode ?: Forgot passcode ?
|
Forgot passcode ?: Forgot passcode ?
|
||||||
Forgot?: Forgot?
|
Forgot?: Forgot?
|
||||||
Forgot? Reset using Password: Forgot? Reset using Password
|
Forgot? Reset using Password: Forgot? Reset using Password
|
||||||
Get priority email support from us to resolve any issues or questions you may have about PasteBar.: Get priority email support from us to resolve any issues or questions you may have about PasteBar.
|
Get priority email support from us to resolve any issues or questions you may have about PasteBar.: Get priority email support from us to resolve any issues or questions you may have about PasteBar.
|
||||||
'Hint: {{screenLockRecoveryPasswordMasked}}': 'Hint: {{screenLockRecoveryPasswordMasked}}'
|
'Hint: {{screenLockRecoveryPasswordMasked}}': 'Hint: {{screenLockRecoveryPasswordMasked}}'
|
||||||
|
? If a database file already exists at the default location, do you want to overwrite it? Choosing "Cancel" will skip moving the file if an existing file is found.
|
||||||
|
: If a database file already exists at the default location, do you want to overwrite it? Choosing "Cancel" will skip moving the file if an existing file is found.
|
||||||
Incorrect passcode.: Incorrect passcode.
|
Incorrect passcode.: Incorrect passcode.
|
||||||
Increase UI Font Size: Increase UI Font Size
|
Increase UI Font Size: Increase UI Font Size
|
||||||
Issued: Issued
|
Issued: Issued
|
||||||
@ -95,8 +122,13 @@ Medium: Medium
|
|||||||
Minimal 4 digits: Minimal 4 digits
|
Minimal 4 digits: Minimal 4 digits
|
||||||
Minimize Window: Minimize Window
|
Minimize Window: Minimize Window
|
||||||
Minimum number of lines to trigger detection: Minimum number of lines to trigger detection
|
Minimum number of lines to trigger detection: Minimum number of lines to trigger detection
|
||||||
|
Move data: Move data
|
||||||
|
Move database file: Move database file
|
||||||
Name: Name
|
Name: Name
|
||||||
|
New Data Directory Path: New Data Directory Path
|
||||||
|
New Database Directory Path: New Database Directory Path
|
||||||
Open Security Settings: Open Security Settings
|
Open Security Settings: Open Security Settings
|
||||||
|
Operation when applying new path: Operation when applying new path
|
||||||
Passcode digits remaining: Passcode digits remaining
|
Passcode digits remaining: Passcode digits remaining
|
||||||
Passcode is locked.: Passcode is locked.
|
Passcode is locked.: Passcode is locked.
|
||||||
Passcode is not set: Passcode is not set
|
Passcode is not set: Passcode is not set
|
||||||
@ -132,17 +164,27 @@ Require Screen Unlock at Application Start: Require Screen Unlock at Application
|
|||||||
? Require screen unlock at application launch to enhance security. This setting ensures that only authorized users can access the application, protecting your data from unauthorized access right from the start.
|
? Require screen unlock at application launch to enhance security. This setting ensures that only authorized users can access the application, protecting your data from unauthorized access right from the start.
|
||||||
: Require screen unlock at application launch to enhance security. This setting ensures that only authorized users can access the application, protecting your data from unauthorized access right from the start.
|
: Require screen unlock at application launch to enhance security. This setting ensures that only authorized users can access the application, protecting your data from unauthorized access right from the start.
|
||||||
Reset Font Size: Reset Font Size
|
Reset Font Size: Reset Font Size
|
||||||
|
Revert to Default: Revert to Default
|
||||||
|
Revert to Default and Restart: Revert to Default and Restart
|
||||||
Run Terminal or Shell Commands: Run Terminal or Shell Commands
|
Run Terminal or Shell Commands: Run Terminal or Shell Commands
|
||||||
Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard.: Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard.
|
Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard.: Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard.
|
||||||
? 'Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard. '
|
? 'Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard. '
|
||||||
: 'Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard. '
|
: 'Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard. '
|
||||||
Security: Security
|
Security: Security
|
||||||
Security Settings: Security Settings
|
Security Settings: Security Settings
|
||||||
|
Select Custom Data Folder...: Select Custom Data Folder...
|
||||||
|
Select Data Folder: Select Data Folder
|
||||||
|
Select Data Folder...: Select Data Folder...
|
||||||
|
Select a custom location to store your application data instead of the default location.: Select a custom location to store your application data instead of the default location.
|
||||||
|
Selected data folder: Selected data folder
|
||||||
|
Selected new data folder: Selected new data folder
|
||||||
|
Selected new data folder for change: Selected new data folder for change
|
||||||
Send HTTP requests to web APIs or services and copy the response data to the clipboard.: Send HTTP requests to web APIs or services and copy the response data to the clipboard.
|
Send HTTP requests to web APIs or services and copy the response data to the clipboard.: Send HTTP requests to web APIs or services and copy the response data to the clipboard.
|
||||||
Sensitive words or sentences listed below will automatically be masked if found in the copied text. Case insensitive.: Sensitive words or sentences listed below will automatically be masked if found in the copied text. Case insensitive.
|
Sensitive words or sentences listed below will automatically be masked if found in the copied text. Case insensitive.: Sensitive words or sentences listed below will automatically be masked if found in the copied text. Case insensitive.
|
||||||
Set a passcode to unlock the locked screen and protect your data from unauthorized access.: Set a passcode to unlock the locked screen and protect your data from unauthorized access.
|
Set a passcode to unlock the locked screen and protect your data from unauthorized access.: Set a passcode to unlock the locked screen and protect your data from unauthorized access.
|
||||||
? Set a recovery password to easily reset your lock screen passcode if forgotten. Your password will be securely stored in your device's OS storage.
|
? Set a recovery password to easily reset your lock screen passcode if forgotten. Your password will be securely stored in your device's OS storage.
|
||||||
: Set a recovery password to easily reset your lock screen passcode if forgotten. Your password will be securely stored in your device's OS storage.
|
: Set a recovery password to easily reset your lock screen passcode if forgotten. Your password will be securely stored in your device's OS storage.
|
||||||
|
Setting a custom database location requires an application restart to take effect.: Setting a custom database location requires an application restart to take effect.
|
||||||
Settings: Settings
|
Settings: Settings
|
||||||
Show Clipboard History Capture Control on Lock Screen: Show Clipboard History Capture Control on Lock Screen
|
Show Clipboard History Capture Control on Lock Screen: Show Clipboard History Capture Control on Lock Screen
|
||||||
Show Disable History Capture When Screen Locked: Show Disable History Capture When Screen Locked
|
Show Disable History Capture When Screen Locked: Show Disable History Capture When Screen Locked
|
||||||
@ -158,6 +200,10 @@ Swap Panels Layout: Swap Panels Layout
|
|||||||
Switch the layout position of panels in Clipboard History and Paste Menu views: Switch the layout position of panels in Clipboard History and Paste Menu views
|
Switch the layout position of panels in Clipboard History and Paste Menu views: Switch the layout position of panels in Clipboard History and Paste Menu views
|
||||||
Thank you again for using PasteBar.: Thank you again for using PasteBar.
|
Thank you again for using PasteBar.: Thank you again for using PasteBar.
|
||||||
Thank you for testing! 🙌: Thank you for testing! 🙌
|
Thank you for testing! 🙌: Thank you for testing! 🙌
|
||||||
|
? The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?
|
||||||
|
: The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?
|
||||||
|
The selected folder is not empty. Do you want to create a "pastebar-data" subfolder to store the data?: The selected folder is not empty. Do you want to create a "pastebar-data" subfolder to store the data?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: This folder already contains PasteBar data. The application will use this existing data after restart.
|
||||||
? This option lets you control the display and timing of hover notes on clips. You can choose to show notes instantly or with a delay to prevent unintended popups.
|
? This option lets you control the display and timing of hover notes on clips. You can choose to show notes instantly or with a delay to prevent unintended popups.
|
||||||
: This option lets you control the display and timing of hover notes on clips. You can choose to show notes instantly or with a delay to prevent unintended popups.
|
: This option lets you control the display and timing of hover notes on clips. You can choose to show notes instantly or with a delay to prevent unintended popups.
|
||||||
? This option lets you customize the maximum width and height of the popup that displays clip notes, ensuring it fits comfortably within your desired size.
|
? This option lets you customize the maximum width and height of the popup that displays clip notes, ensuring it fits comfortably within your desired size.
|
||||||
@ -177,8 +223,10 @@ Unlimited Collections: Unlimited Collections
|
|||||||
Unlimited Tabs per Collection: Unlimited Tabs per Collection
|
Unlimited Tabs per Collection: Unlimited Tabs per Collection
|
||||||
Unlimited paste history: Unlimited paste history
|
Unlimited paste history: Unlimited paste history
|
||||||
Use Password: Use Password
|
Use Password: Use Password
|
||||||
|
Use new location: Use new location
|
||||||
User Preferences: User Preferences
|
User Preferences: User Preferences
|
||||||
Web Scraping and Parsing: Web Scraping and Parsing
|
Web Scraping and Parsing: Web Scraping and Parsing
|
||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.
|
||||||
|
none: none
|
||||||
passcode reset: passcode reset
|
passcode reset: passcode reset
|
||||||
password reset: password reset
|
password reset: password reset
|
||||||
|
@ -11,6 +11,7 @@ Confirm Clear All History: Confirmar Borrar Todo el Historial
|
|||||||
Do you really want to remove ALL clipboard history items?: ¿Realmente quieres eliminar TODOS los elementos del historial?
|
Do you really want to remove ALL clipboard history items?: ¿Realmente quieres eliminar TODOS los elementos del historial?
|
||||||
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: ¿Quieres eliminar los elementos del historial más antiguos que {{olderThen}} {{durationType}}?
|
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: ¿Quieres eliminar los elementos del historial más antiguos que {{olderThen}} {{durationType}}?
|
||||||
Enable Capture History: Habilitar Captura de Historial
|
Enable Capture History: Habilitar Captura de Historial
|
||||||
|
Enable Image Capture: Habilitar Captura de Imágenes
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: Filtros de App
|
App Filters: Filtros de App
|
||||||
Audio: Audio
|
Audio: Audio
|
||||||
|
@ -178,4 +178,39 @@ User Preferences: Preferencias de Usuario
|
|||||||
Web Scraping and Parsing: Web Scraping y Análisis
|
Web Scraping and Parsing: Web Scraping y Análisis
|
||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Las palabras o frases listadas abajo no serán capturadas en el historial del portapapeles si se encuentran en el texto copiado. No distingue mayúsculas y minúsculas.
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Las palabras o frases listadas abajo no serán capturadas en el historial del portapapeles si se encuentran en el texto copiado. No distingue mayúsculas y minúsculas.
|
||||||
passcode reset: restablecimiento de código
|
passcode reset: restablecimiento de código
|
||||||
password reset: restablecimiento de contraseña
|
password reset: restablecimiento de contraseña
|
||||||
|
Apply and Restart: Aplicar y Reiniciar
|
||||||
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: ¿Estás seguro de que quieres establecer "{{path}}" como la nueva carpeta de datos? La aplicación se reiniciará.
|
||||||
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: ¿Estás seguro de que quieres {{operation}} la base de datos a "{{path}}"? La aplicación se reiniciará.
|
||||||
|
Change Custom Data Folder...: Cambiar Carpeta de Datos Personalizada...
|
||||||
|
Change Selected Folder...: Cambiar Carpeta Seleccionada...
|
||||||
|
Changing the database location requires an application restart to take effect.: Cambiar la ubicación de la base de datos requiere reiniciar la aplicación para que surta efecto.
|
||||||
|
ConfirmRevertToDefaultDbPathMessage: ¿Estás seguro de que quieres revertir a la ubicación de datos predeterminada? Los datos de tu aplicación se moverán a la ruta predeterminada.
|
||||||
|
Copy data: Copiar datos
|
||||||
|
Current data folder: Carpeta de datos actual
|
||||||
|
Custom Application Data Location: Ubicación de Datos de Aplicación Personalizada
|
||||||
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: La ubicación de datos personalizada está activa. Puedes cambiarla o revertir a la ubicación predeterminada. Los cambios requieren reiniciar la aplicación.
|
||||||
|
Default: Predeterminado
|
||||||
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: Habilitar ubicación de datos personalizada para almacenar datos de la aplicación en un directorio de tu elección en lugar de la ubicación predeterminada.
|
||||||
|
Failed to apply custom database location.: Error al aplicar la ubicación de base de datos personalizada.
|
||||||
|
Failed to create directory. Please check permissions and try again.: Error al crear directorio. Por favor verifica los permisos e intenta de nuevo.
|
||||||
|
Failed to revert to default database location.: Error al revertir a la ubicación de base de datos predeterminada.
|
||||||
|
Found existing "pastebar-data" folder. The application will use this folder to store data.: Se encontró una carpeta "pastebar-data" existente. La aplicación usará esta carpeta para almacenar datos.
|
||||||
|
Invalid directory selected.: Directorio seleccionado inválido.
|
||||||
|
Operation when applying new path: Operación al aplicar nueva ruta
|
||||||
|
Please select a directory first.: Por favor selecciona un directorio primero.
|
||||||
|
Please select a new directory different from the current one.: Por favor selecciona un directorio nuevo diferente al actual.
|
||||||
|
Revert to Default: Revertir a Predeterminado
|
||||||
|
Select Data Folder: Seleccionar Carpeta de Datos
|
||||||
|
Select Data Folder...: Seleccionar Carpeta de Datos...
|
||||||
|
Select a custom location to store your application data instead of the default location.: Selecciona una ubicación personalizada para almacenar los datos de tu aplicación en lugar de la ubicación predeterminada.
|
||||||
|
Selected data folder: Carpeta de datos seleccionada
|
||||||
|
Selected new data folder for change: Nueva carpeta de datos seleccionada para cambio
|
||||||
|
Setting a custom database location requires an application restart to take effect.: Establecer una ubicación de base de datos personalizada requiere reiniciar la aplicación para que surta efecto.
|
||||||
|
The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?: La carpeta seleccionada no está vacía y no contiene archivos de datos de PasteBar. ¿Quieres crear una subcarpeta "pastebar-data" para almacenar los datos?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: Esta carpeta ya contiene datos de PasteBar. La aplicación usará estos datos existentes después del reinicio.
|
||||||
|
Use new location: Usar nueva ubicación
|
||||||
|
An error occurred during directory processing.: Ocurrió un error durante el procesamiento del directorio.
|
||||||
|
An error occurred during directory selection or setup.: Ocurrió un error durante la selección o configuración del directorio.
|
||||||
|
Disable Image Capture: Deshabilitar Captura de Imágenes
|
||||||
|
Disable capturing and storing images from clipboard: Deshabilitar la captura y almacenamiento de imágenes del portapapeles
|
@ -11,6 +11,7 @@ Confirm Clear All History: Confirmer l'effacement de tout l'historique
|
|||||||
Do you really want to remove ALL clipboard history items?: Voulez-vous vraiment supprimer TOUS les éléments de l'historique du presse-papier ?
|
Do you really want to remove ALL clipboard history items?: Voulez-vous vraiment supprimer TOUS les éléments de l'historique du presse-papier ?
|
||||||
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Voulez-vous supprimer les éléments de l'historique du presse-papier antérieurs à {{olderThen}} {{durationType}}
|
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Voulez-vous supprimer les éléments de l'historique du presse-papier antérieurs à {{olderThen}} {{durationType}}
|
||||||
Enable Capture History: Activer la capture de l'historique
|
Enable Capture History: Activer la capture de l'historique
|
||||||
|
Enable Image Capture: Activer la capture d'images
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: Filtres d'applications
|
App Filters: Filtres d'applications
|
||||||
Audio: Audio
|
Audio: Audio
|
||||||
|
@ -182,3 +182,38 @@ Web Scraping and Parsing: Extraction et analyse web
|
|||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Les mots ou phrases énumérés ci-dessous ne seront pas capturés dans l'historique du presse-papiers s'ils se trouvent dans le texte copié. Insensible à la casse
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Les mots ou phrases énumérés ci-dessous ne seront pas capturés dans l'historique du presse-papiers s'ils se trouvent dans le texte copié. Insensible à la casse
|
||||||
passcode reset: réinitialisation code d'accès
|
passcode reset: réinitialisation code d'accès
|
||||||
password reset: réinitialisation mot de passe
|
password reset: réinitialisation mot de passe
|
||||||
|
Apply and Restart: Appliquer et redémarrer
|
||||||
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: Êtes-vous sûr de vouloir définir "{{path}}" comme nouveau dossier de données ? L'application va redémarrer.
|
||||||
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: Êtes-vous sûr de vouloir {{operation}} la base de données vers "{{path}}" ? L'application va redémarrer.
|
||||||
|
Change Custom Data Folder...: Changer le dossier de données personnalisé...
|
||||||
|
Change Selected Folder...: Changer le dossier sélectionné...
|
||||||
|
Changing the database location requires an application restart to take effect.: Le changement de l'emplacement de la base de données nécessite un redémarrage de l'application pour prendre effet.
|
||||||
|
ConfirmRevertToDefaultDbPathMessage: Êtes-vous sûr de vouloir revenir à l'emplacement de données par défaut ? Vos données d'application seront déplacées vers le chemin par défaut.
|
||||||
|
Copy data: Copier les données
|
||||||
|
Current data folder: Dossier de données actuel
|
||||||
|
Custom Application Data Location: Emplacement de données d'application personnalisé
|
||||||
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: L'emplacement de données personnalisé est actif. Vous pouvez le modifier ou revenir à l'emplacement par défaut. Les modifications nécessitent un redémarrage de l'application.
|
||||||
|
Default: Par défaut
|
||||||
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: Activer l'emplacement de données personnalisé pour stocker les données d'application dans un répertoire de votre choix au lieu de l'emplacement par défaut.
|
||||||
|
Failed to apply custom database location.: Échec de l'application de l'emplacement de base de données personnalisé.
|
||||||
|
Failed to create directory. Please check permissions and try again.: Échec de la création du répertoire. Veuillez vérifier les autorisations et réessayer.
|
||||||
|
Failed to revert to default database location.: Échec du retour à l'emplacement de base de données par défaut.
|
||||||
|
Found existing "pastebar-data" folder. The application will use this folder to store data.: Dossier "pastebar-data" existant trouvé. L'application utilisera ce dossier pour stocker les données.
|
||||||
|
Invalid directory selected.: Répertoire sélectionné invalide.
|
||||||
|
Operation when applying new path: Opération lors de l'application du nouveau chemin
|
||||||
|
Please select a directory first.: Veuillez d'abord sélectionner un répertoire.
|
||||||
|
Please select a new directory different from the current one.: Veuillez sélectionner un nouveau répertoire différent de l'actuel.
|
||||||
|
Revert to Default: Revenir au défaut
|
||||||
|
Select Data Folder: Sélectionner le dossier de données
|
||||||
|
Select Data Folder...: Sélectionner le dossier de données...
|
||||||
|
Select a custom location to store your application data instead of the default location.: Sélectionnez un emplacement personnalisé pour stocker vos données d'application au lieu de l'emplacement par défaut.
|
||||||
|
Selected data folder: Dossier de données sélectionné
|
||||||
|
Selected new data folder for change: Nouveau dossier de données sélectionné pour modification
|
||||||
|
Setting a custom database location requires an application restart to take effect.: La définition d'un emplacement de base de données personnalisé nécessite un redémarrage de l'application pour prendre effet.
|
||||||
|
The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?: Le dossier sélectionné n'est pas vide et ne contient pas de fichiers de données PasteBar. Voulez-vous créer un sous-dossier "pastebar-data" pour stocker les données ?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: Ce dossier contient déjà des données PasteBar. L'application utilisera ces données existantes après le redémarrage.
|
||||||
|
Use new location: Utiliser le nouvel emplacement
|
||||||
|
An error occurred during directory processing.: Une erreur s'est produite lors du traitement du répertoire.
|
||||||
|
An error occurred during directory selection or setup.: Une erreur s'est produite lors de la sélection ou configuration du répertoire.
|
||||||
|
Disable Image Capture: Désactiver la Capture d'Images
|
||||||
|
Disable capturing and storing images from clipboard: Désactiver la capture et le stockage d'images depuis le presse-papiers
|
||||||
|
@ -11,6 +11,7 @@ Confirm Clear All History: Conferma Cancellazione di Tutta la Cronologia
|
|||||||
Do you really want to remove ALL clipboard history items?: Vuoi davvero rimuovere TUTTI gli elementi della cronologia degli appunti?
|
Do you really want to remove ALL clipboard history items?: Vuoi davvero rimuovere TUTTI gli elementi della cronologia degli appunti?
|
||||||
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Vuoi rimuovere gli elementi della cronologia degli appunti più vecchi di {{olderThen}} {{durationType}}
|
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Vuoi rimuovere gli elementi della cronologia degli appunti più vecchi di {{olderThen}} {{durationType}}
|
||||||
Enable Capture History: Abilita Cattura Cronologia
|
Enable Capture History: Abilita Cattura Cronologia
|
||||||
|
Enable Image Capture: Abilita Cattura Immagini
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: Filtri App
|
App Filters: Filtri App
|
||||||
Audio: Audio
|
Audio: Audio
|
||||||
|
@ -181,3 +181,38 @@ Web Scraping and Parsing: Web Scraping e Parsing
|
|||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Le parole o frasi elencate di seguito non verranno catturate nella cronologia degli appunti se trovate nel testo copiato. Non sensibile alle maiuscole/minuscole.
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Le parole o frasi elencate di seguito non verranno catturate nella cronologia degli appunti se trovate nel testo copiato. Non sensibile alle maiuscole/minuscole.
|
||||||
passcode reset: reset del codice di accesso
|
passcode reset: reset del codice di accesso
|
||||||
password reset: reset della password
|
password reset: reset della password
|
||||||
|
Apply and Restart: Applica e Riavvia
|
||||||
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: Sei sicuro di voler impostare "{{path}}" come nuova cartella dati? L'applicazione si riavvierà.
|
||||||
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: Sei sicuro di voler {{operation}} il database in "{{path}}"? L'applicazione si riavvierà.
|
||||||
|
Change Custom Data Folder...: Cambia Cartella Dati Personalizzata...
|
||||||
|
Change Selected Folder...: Cambia Cartella Selezionata...
|
||||||
|
Changing the database location requires an application restart to take effect.: Cambiare la posizione del database richiede un riavvio dell'applicazione per avere effetto.
|
||||||
|
ConfirmRevertToDefaultDbPathMessage: Sei sicuro di voler tornare alla posizione dati predefinita? I dati della tua applicazione verranno spostati nel percorso predefinito.
|
||||||
|
Copy data: Copia dati
|
||||||
|
Current data folder: Cartella dati corrente
|
||||||
|
Custom Application Data Location: Posizione Dati Applicazione Personalizzata
|
||||||
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: La posizione dati personalizzata è attiva. Puoi cambiarla o tornare alla posizione predefinita. Le modifiche richiedono un riavvio dell'applicazione.
|
||||||
|
Default: Predefinito
|
||||||
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: Abilita posizione dati personalizzata per memorizzare i dati dell'applicazione in una directory di tua scelta invece della posizione predefinita.
|
||||||
|
Failed to apply custom database location.: Impossibile applicare la posizione database personalizzata.
|
||||||
|
Failed to create directory. Please check permissions and try again.: Impossibile creare la directory. Controlla i permessi e riprova.
|
||||||
|
Failed to revert to default database location.: Impossibile tornare alla posizione database predefinita.
|
||||||
|
Found existing "pastebar-data" folder. The application will use this folder to store data.: Trovata cartella "pastebar-data" esistente. L'applicazione userà questa cartella per memorizzare i dati.
|
||||||
|
Invalid directory selected.: Directory selezionata non valida.
|
||||||
|
Operation when applying new path: Operazione quando si applica il nuovo percorso
|
||||||
|
Please select a directory first.: Seleziona prima una directory.
|
||||||
|
Please select a new directory different from the current one.: Seleziona una nuova directory diversa da quella corrente.
|
||||||
|
Revert to Default: Torna al Predefinito
|
||||||
|
Select Data Folder: Seleziona Cartella Dati
|
||||||
|
Select Data Folder...: Seleziona Cartella Dati...
|
||||||
|
Select a custom location to store your application data instead of the default location.: Seleziona una posizione personalizzata per memorizzare i dati della tua applicazione invece della posizione predefinita.
|
||||||
|
Selected data folder: Cartella dati selezionata
|
||||||
|
Selected new data folder for change: Nuova cartella dati selezionata per il cambio
|
||||||
|
Setting a custom database location requires an application restart to take effect.: Impostare una posizione database personalizzata richiede un riavvio dell'applicazione per avere effetto.
|
||||||
|
The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?: La cartella selezionata non è vuota e non contiene file dati di PasteBar. Vuoi creare una sottocartella "pastebar-data" per memorizzare i dati?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: Questa cartella contiene già dati di PasteBar. L'applicazione userà questi dati esistenti dopo il riavvio.
|
||||||
|
Use new location: Usa nuova posizione
|
||||||
|
An error occurred during directory processing.: Si è verificato un errore durante l'elaborazione della directory.
|
||||||
|
An error occurred during directory selection or setup.: Si è verificato un errore durante la selezione o configurazione della directory.
|
||||||
|
Disable Image Capture: Disabilita Cattura Immagini
|
||||||
|
Disable capturing and storing images from clipboard: Disabilita la cattura e memorizzazione di immagini dagli appunti
|
||||||
|
@ -12,6 +12,7 @@ Do you really want to remove ALL clipboard history items?: Вы действит
|
|||||||
Do you want to remove all recent clipboard history older than {{olderThen}} {{durationType}}: Вы хотите удалить всю недавнюю историю буфера обмена за период {{olderThen}} {{durationType}}
|
Do you want to remove all recent clipboard history older than {{olderThen}} {{durationType}}: Вы хотите удалить всю недавнюю историю буфера обмена за период {{olderThen}} {{durationType}}
|
||||||
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Вы хотите удалить историю буфера обмена старше чем {{olderThen}} {{durationType}}
|
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Вы хотите удалить историю буфера обмена старше чем {{olderThen}} {{durationType}}
|
||||||
Enable Capture History: Включить захват истории
|
Enable Capture History: Включить захват истории
|
||||||
|
Enable Image Capture: Включить захват изображений
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: Фильтры приложений
|
App Filters: Фильтры приложений
|
||||||
Audio: Аудио
|
Audio: Аудио
|
||||||
|
@ -165,3 +165,38 @@ Web Scraping and Parsing: Веб-скрейпинг и анализ
|
|||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Слова или предложения, перечисленные ниже, не будут захватываться в историю буфера обмена, если они будут найдены в скопированном тексте. Без учета регистра.
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Слова или предложения, перечисленные ниже, не будут захватываться в историю буфера обмена, если они будут найдены в скопированном тексте. Без учета регистра.
|
||||||
passcode reset: сброс кода доступа
|
passcode reset: сброс кода доступа
|
||||||
password reset: сброс пароля
|
password reset: сброс пароля
|
||||||
|
Apply and Restart: Применить и перезапустить
|
||||||
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: Вы уверены, что хотите установить "{{path}}" как новую папку данных? Приложение будет перезапущено.
|
||||||
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: Вы уверены, что хотите {{operation}} базу данных в "{{path}}"? Приложение будет перезапущено.
|
||||||
|
Change Custom Data Folder...: Изменить пользовательскую папку данных...
|
||||||
|
Change Selected Folder...: Изменить выбранную папку...
|
||||||
|
Changing the database location requires an application restart to take effect.: Изменение расположения базы данных требует перезапуска приложения для вступления в силу.
|
||||||
|
ConfirmRevertToDefaultDbPathMessage: Вы уверены, что хотите вернуться к расположению данных по умолчанию? Данные вашего приложения будут перемещены в путь по умолчанию.
|
||||||
|
Copy data: Копировать данные
|
||||||
|
Current data folder: Текущая папка данных
|
||||||
|
Custom Application Data Location: Пользовательское расположение данных приложения
|
||||||
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: Пользовательское расположение данных активно. Вы можете изменить его или вернуться к расположению по умолчанию. Изменения требуют перезапуска приложения.
|
||||||
|
Default: По умолчанию
|
||||||
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: Включить пользовательское расположение данных для хранения данных приложения в выбранной вами директории вместо расположения по умолчанию.
|
||||||
|
Failed to apply custom database location.: Не удалось применить пользовательское расположение базы данных.
|
||||||
|
Failed to create directory. Please check permissions and try again.: Не удалось создать директорию. Проверьте разрешения и попробуйте снова.
|
||||||
|
Failed to revert to default database location.: Не удалось вернуться к расположению базы данных по умолчанию.
|
||||||
|
Found existing "pastebar-data" folder. The application will use this folder to store data.: Найдена существующая папка "pastebar-data". Приложение будет использовать эту папку для хранения данных.
|
||||||
|
Invalid directory selected.: Выбрана недопустимая директория.
|
||||||
|
Operation when applying new path: Операция при применении нового пути
|
||||||
|
Please select a directory first.: Пожалуйста, сначала выберите директорию.
|
||||||
|
Please select a new directory different from the current one.: Пожалуйста, выберите новую директорию, отличную от текущей.
|
||||||
|
Revert to Default: Вернуться к умолчанию
|
||||||
|
Select Data Folder: Выбрать папку данных
|
||||||
|
Select Data Folder...: Выбрать папку данных...
|
||||||
|
Select a custom location to store your application data instead of the default location.: Выберите пользовательское расположение для хранения данных приложения вместо расположения по умолчанию.
|
||||||
|
Selected data folder: Выбранная папка данных
|
||||||
|
Selected new data folder for change: Выбранная новая папка данных для изменения
|
||||||
|
Setting a custom database location requires an application restart to take effect.: Установка пользовательского расположения базы данных требует перезапуска приложения для вступления в силу.
|
||||||
|
The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?: Выбранная папка не пуста и не содержит файлов данных PasteBar. Хотите создать подпапку "pastebar-data" для хранения данных?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: Эта папка уже содержит данные PasteBar. Приложение будет использовать эти существующие данные после перезапуска.
|
||||||
|
Use new location: Использовать новое расположение
|
||||||
|
An error occurred during directory processing.: Произошла ошибка при обработке директории.
|
||||||
|
An error occurred during directory selection or setup.: Произошла ошибка при выборе или настройке директории.
|
||||||
|
Disable Image Capture: Отключить Захват Изображений
|
||||||
|
Disable capturing and storing images from clipboard: Отключить захват и сохранение изображений из буфера обмена
|
||||||
|
@ -11,6 +11,7 @@ Confirm Clear All History: Tüm Geçmişi Temizle'yi Onayla
|
|||||||
Do you really want to remove ALL clipboard history items?: Gerçekten TÜM pano geçmişi öğelerini kaldırmak istiyormusunuz?
|
Do you really want to remove ALL clipboard history items?: Gerçekten TÜM pano geçmişi öğelerini kaldırmak istiyormusunuz?
|
||||||
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Şu tarihten önceki pano geçmişi öğelerini kaldırmak ister misiniz? {{olderThen}} {{durationType}}
|
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Şu tarihten önceki pano geçmişi öğelerini kaldırmak ister misiniz? {{olderThen}} {{durationType}}
|
||||||
Enable Capture History: Yakalama Geçmişini Etkinleştir
|
Enable Capture History: Yakalama Geçmişini Etkinleştir
|
||||||
|
Enable Image Capture: Görüntü Yakalamayı Etkinleştir
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: Uygulama Filtreleri
|
App Filters: Uygulama Filtreleri
|
||||||
Audio: Ses
|
Audio: Ses
|
||||||
|
@ -182,3 +182,38 @@ Web Scraping and Parsing: Web Kazıma ve Ayrıştırma
|
|||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Aşağıda listelenen kelimeler veya cümleler kopyalanan metinde bulunursa pano geçmişine kaydedilmeyecektir. Büyük/küçük harf duyarlı değildir.
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Aşağıda listelenen kelimeler veya cümleler kopyalanan metinde bulunursa pano geçmişine kaydedilmeyecektir. Büyük/küçük harf duyarlı değildir.
|
||||||
passcode reset: parolayı sıfırla
|
passcode reset: parolayı sıfırla
|
||||||
password reset: şifreyi sıfırla
|
password reset: şifreyi sıfırla
|
||||||
|
Apply and Restart: Uygula ve Yeniden Başlat
|
||||||
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: '"{{path}}" yeni veri klasörü olarak ayarlamak istediğinizden emin misiniz? Uygulama yeniden başlayacak.'
|
||||||
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: 'Veritabanını "{{path}}" konumuna {{operation}} yapmak istediğinizden emin misiniz? Uygulama yeniden başlayacak.'
|
||||||
|
Change Custom Data Folder...: Özel Veri Klasörünü Değiştir...
|
||||||
|
Change Selected Folder...: Seçili Klasörü Değiştir...
|
||||||
|
Changing the database location requires an application restart to take effect.: Veritabanı konumunu değiştirmek, etkili olması için uygulamanın yeniden başlatılmasını gerektirir.
|
||||||
|
ConfirmRevertToDefaultDbPathMessage: Varsayılan veri konumuna geri dönmek istediğinizden emin misiniz? Uygulama verileriniz varsayılan yola taşınacak.
|
||||||
|
Copy data: Veriyi kopyala
|
||||||
|
Current data folder: Mevcut veri klasörü
|
||||||
|
Custom Application Data Location: Özel Uygulama Veri Konumu
|
||||||
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: Özel veri konumu aktif. Değiştirebilir veya varsayılan konuma geri dönebilirsiniz. Değişiklikler uygulama yeniden başlatma gerektirir.
|
||||||
|
Default: Varsayılan
|
||||||
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: Uygulama verilerini varsayılan konum yerine seçtiğiniz bir dizinde depolamak için özel veri konumunu etkinleştirin.
|
||||||
|
Failed to apply custom database location.: Özel veritabanı konumu uygulanamadı.
|
||||||
|
Failed to create directory. Please check permissions and try again.: Dizin oluşturulamadı. Lütfen izinleri kontrol edin ve tekrar deneyin.
|
||||||
|
Failed to revert to default database location.: Varsayılan veritabanı konumuna geri dönülemedi.
|
||||||
|
Found existing "pastebar-data" folder. The application will use this folder to store data.: Mevcut "pastebar-data" klasörü bulundu. Uygulama verileri depolamak için bu klasörü kullanacak.
|
||||||
|
Invalid directory selected.: Geçersiz dizin seçildi.
|
||||||
|
Operation when applying new path: Yeni yol uygulanırken işlem
|
||||||
|
Please select a directory first.: Lütfen önce bir dizin seçin.
|
||||||
|
Please select a new directory different from the current one.: Lütfen mevcut olandan farklı yeni bir dizin seçin.
|
||||||
|
Revert to Default: Varsayılana Dön
|
||||||
|
Select Data Folder: Veri Klasörü Seç
|
||||||
|
Select Data Folder...: Veri Klasörü Seç...
|
||||||
|
Select a custom location to store your application data instead of the default location.: Uygulama verilerinizi varsayılan konum yerine depolamak için özel bir konum seçin.
|
||||||
|
Selected data folder: Seçili veri klasörü
|
||||||
|
Selected new data folder for change: Değişiklik için seçili yeni veri klasörü
|
||||||
|
Setting a custom database location requires an application restart to take effect.: Özel veritabanı konumu ayarlamak, etkili olması için uygulama yeniden başlatması gerektirir.
|
||||||
|
The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?: Seçili klasör boş değil ve PasteBar veri dosyalarını içermiyor. Verileri depolamak için "pastebar-data" alt klasörü oluşturmak istiyor musunuz?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: Bu klasör zaten PasteBar verileri içeriyor. Uygulama yeniden başlatıldıktan sonra mevcut verileri kullanacak.
|
||||||
|
Use new location: Yeni konumu kullan
|
||||||
|
An error occurred during directory processing.: Dizin işleme sırasında bir hata oluştu.
|
||||||
|
An error occurred during directory selection or setup.: Dizin seçimi veya kurulumu sırasında bir hata oluştu.
|
||||||
|
Disable Image Capture: Görüntü Yakalamayı Devre Dışı Bırak
|
||||||
|
Disable capturing and storing images from clipboard: Panodan görüntü yakalama ve depolamayı devre dışı bırak
|
||||||
|
@ -12,6 +12,7 @@ Do you really want to remove ALL clipboard history items?: Ви дійсно х
|
|||||||
Do you want to remove all recent clipboard history older than {{olderThen}} {{durationType}}: Ви хочете видалити всю нещодавню історію буфера обміну за період {{olderThen}} {{durationType}}
|
Do you want to remove all recent clipboard history older than {{olderThen}} {{durationType}}: Ви хочете видалити всю нещодавню історію буфера обміну за період {{olderThen}} {{durationType}}
|
||||||
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Ви хочете видалити історію буфера обміну старішу за {{olderThen}} {{durationType}}
|
Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}: Ви хочете видалити історію буфера обміну старішу за {{olderThen}} {{durationType}}
|
||||||
Enable Capture History: Увімкнути захоплення історії
|
Enable Capture History: Увімкнути захоплення історії
|
||||||
|
Enable Image Capture: Увімкнути захоплення зображень
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: Фільтри додатків
|
App Filters: Фільтри додатків
|
||||||
Audio: Аудіо
|
Audio: Аудіо
|
||||||
|
@ -165,28 +165,38 @@ Web Scraping and Parsing: Веб-скрейпінг та аналіз
|
|||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Слова або речення, перелічені нижче, не будуть захоплюватися в історію буфера обміну, якщо вони будуть знайдені в скопійованому тексті. Без урахування регістру.
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Слова або речення, перелічені нижче, не будуть захоплюватися в історію буфера обміну, якщо вони будуть знайдені в скопійованому тексті. Без урахування регістру.
|
||||||
passcode reset: скидання коду доступу
|
passcode reset: скидання коду доступу
|
||||||
password reset: скидання пароля
|
password reset: скидання пароля
|
||||||
Backup and Restore: Резервне копіювання та відновлення
|
Apply and Restart: Застосувати та перезапустити
|
||||||
Create Backup: Створити резервну копію
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: Ви впевнені, що хочете встановити "{{path}}" як нову папку даних? Додаток буде перезапущено.
|
||||||
Include images in backup: Включити зображення в резервну копію
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: Ви впевнені, що хочете {{operation}} базу даних до "{{path}}"? Додаток буде перезапущено.
|
||||||
Backup Now: Створити резервну копію зараз
|
Change Custom Data Folder...: Змінити користувацьку папку даних...
|
||||||
Restore Data: Відновити дані
|
Change Selected Folder...: Змінити вибрану папку...
|
||||||
Restore from File...: Відновити з файлу...
|
Changing the database location requires an application restart to take effect.: Зміна розташування бази даних вимагає перезапуску додатка для набрання чинності.
|
||||||
Select backup file: Вибрати файл резервної копії
|
ConfirmRevertToDefaultDbPathMessage: Ви впевнені, що хочете повернутися до розташування даних за замовчуванням? Дані вашого додатка будуть переміщені за шляхом за замовчуванням.
|
||||||
Available Backups: Доступні резервні копії
|
Copy data: Копіювати дані
|
||||||
Total backup space: "{{size}}": Загальний розмір резервних копій: {{size}}
|
Current data folder: Поточна папка даних
|
||||||
No backups found: Резервні копії не знайдені
|
Custom Application Data Location: Користувацьке розташування даних додатка
|
||||||
Restore: Відновити
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: Користувацьке розташування даних активне. Ви можете змінити його або повернутися до розташування за замовчуванням. Зміни вимагають перезапуску додатка.
|
||||||
Delete: Видалити
|
Default: За замовчуванням
|
||||||
Create a backup of your data?: Створити резервну копію ваших даних?
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: Увімкнути користувацьке розташування даних для зберігання даних додатка в обраній вами директорії замість розташування за замовчуванням.
|
||||||
Backup created successfully: Резервну копію успішно створено
|
Failed to apply custom database location.: Не вдалося застосувати користувацьке розташування бази даних.
|
||||||
Move to another location?: Перемістити в інше місце?
|
Failed to create directory. Please check permissions and try again.: Не вдалося створити директорію. Перевірте дозволи та спробуйте знову.
|
||||||
This will replace all current data. Are you sure?: Це замінить всі поточні дані. Ви впевнені?
|
Failed to revert to default database location.: Не вдалося повернутися до розташування бази даних за замовчуванням.
|
||||||
Restore from "{{filename}}"? This will replace all current data.: Відновити з {{filename}}? Це замінить всі поточні дані.
|
Found existing "pastebar-data" folder. The application will use this folder to store data.: Знайдено існуючу папку "pastebar-data". Додаток буде використовувати цю папку для зберігання даних.
|
||||||
Delete this backup? This action cannot be undone.: Видалити цю резервну копію? Цю дію неможливо скасувати.
|
Invalid directory selected.: Вибрано недійсну директорію.
|
||||||
Restore completed. The application will restart.: Відновлення завершено. Додаток перезапуститься.
|
Operation when applying new path: Операція при застосуванні нового шляху
|
||||||
Creating backup...: Створення резервної копії...
|
Please select a directory first.: Будь ласка, спочатку виберіть директорію.
|
||||||
Restoring backup...: Відновлення резервної копії...
|
Please select a new directory different from the current one.: Будь ласка, виберіть нову директорію, відмінну від поточної.
|
||||||
Backup deleted successfully: Резервну копію успішно видалено
|
Revert to Default: Повернутися до замовчування
|
||||||
Failed to delete backup: Не вдалося видалити резервну копію
|
Select Data Folder: Вибрати папку даних
|
||||||
Invalid backup file: Недійсний файл резервної копії
|
Select Data Folder...: Вибрати папку даних...
|
||||||
The selected file is not a valid PasteBar backup: Вибраний файл не є дійсною резервною копією PasteBar
|
Select a custom location to store your application data instead of the default location.: Виберіть користувацьке розташування для зберігання даних вашого додатка замість розташування за замовчуванням.
|
||||||
|
Selected data folder: Вибрана папка даних
|
||||||
|
Selected new data folder for change: Вибрана нова папка даних для зміни
|
||||||
|
Setting a custom database location requires an application restart to take effect.: Встановлення користувацького розташування бази даних вимагає перезапуску додатка для набрання чинності.
|
||||||
|
The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?: Вибрана папка не порожня і не містить файлів даних PasteBar. Хочете створити підпапку "pastebar-data" для зберігання даних?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: Ця папка вже містить дані PasteBar. Додаток буде використовувати ці існуючі дані після перезапуску.
|
||||||
|
Use new location: Використовувати нове розташування
|
||||||
|
An error occurred during directory processing.: Сталася помилка під час обробки директорії.
|
||||||
|
An error occurred during directory selection or setup.: Сталася помилка під час вибору або налаштування директорії.
|
||||||
|
Disable Image Capture: Вимкнути Захоплення Зображень
|
||||||
|
Disable capturing and storing images from clipboard: Вимкнути захоплення та збереження зображень з буферу обміну
|
||||||
|
@ -11,6 +11,7 @@ Confirm Clear All History: 确认清除所有历史
|
|||||||
Do you really want to remove ALL clipboard history items?: 确定要删除所有剪贴板历史记录吗?
|
Do you really want to remove ALL clipboard history items?: 确定要删除所有剪贴板历史记录吗?
|
||||||
"Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}": 您想删除早于 {{olderThen}} {{durationType}} 的剪贴板历史项目吗
|
"Do you want to remove clipboard history items older than {{olderThen}} {{durationType}}": 您想删除早于 {{olderThen}} {{durationType}} 的剪贴板历史项目吗
|
||||||
Enable Capture History: 启用捕获历史
|
Enable Capture History: 启用捕获历史
|
||||||
|
Enable Image Capture: 启用图像捕获
|
||||||
Filters:
|
Filters:
|
||||||
App Filters: 应用筛选器
|
App Filters: 应用筛选器
|
||||||
Audio: 音频
|
Audio: 音频
|
||||||
|
@ -181,4 +181,39 @@ User Preferences: 用户偏好设置
|
|||||||
Web Scraping and Parsing: 网页抓取和解析
|
Web Scraping and Parsing: 网页抓取和解析
|
||||||
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: 如果在复制的文本中找到以下列出的词或句子,将不会被记录在剪贴板历史中。不区分大小写。
|
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: 如果在复制的文本中找到以下列出的词或句子,将不会被记录在剪贴板历史中。不区分大小写。
|
||||||
passcode reset: 密码重置
|
passcode reset: 密码重置
|
||||||
password reset: 密码重置
|
password reset: 密码重置
|
||||||
|
Apply and Restart: 应用并重启
|
||||||
|
Are you sure you want to set "{{path}}" as the new data folder? The application will restart.: 您确定要将"{{path}}"设置为新的数据文件夹吗?应用程序将重新启动。
|
||||||
|
Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.: 您确定要将数据库{{operation}}到"{{path}}"吗?应用程序将重新启动。
|
||||||
|
Change Custom Data Folder...: 更改自定义数据文件夹...
|
||||||
|
Change Selected Folder...: 更改选定文件夹...
|
||||||
|
Changing the database location requires an application restart to take effect.: 更改数据库位置需要重新启动应用程序才能生效。
|
||||||
|
ConfirmRevertToDefaultDbPathMessage: 您确定要恢复到默认数据位置吗?您的应用程序数据将移动到默认路径。
|
||||||
|
Copy data: 复制数据
|
||||||
|
Current data folder: 当前数据文件夹
|
||||||
|
Custom Application Data Location: 自定义应用程序数据位置
|
||||||
|
Custom data location is active. You can change it or revert to the default location. Changes require an application restart.: 自定义数据位置已激活。您可以更改它或恢复到默认位置。更改需要重新启动应用程序。
|
||||||
|
Default: 默认
|
||||||
|
Enable custom data location to store application data in a directory of your choice instead of the default location.: 启用自定义数据位置,将应用程序数据存储在您选择的目录中,而不是默认位置。
|
||||||
|
Failed to apply custom database location.: 应用自定义数据库位置失败。
|
||||||
|
Failed to create directory. Please check permissions and try again.: 创建目录失败。请检查权限并重试。
|
||||||
|
Failed to revert to default database location.: 恢复到默认数据库位置失败。
|
||||||
|
Found existing "pastebar-data" folder. The application will use this folder to store data.: 找到现有的"pastebar-data"文件夹。应用程序将使用此文件夹存储数据。
|
||||||
|
Invalid directory selected.: 选择的目录无效。
|
||||||
|
Operation when applying new path: 应用新路径时的操作
|
||||||
|
Please select a directory first.: 请先选择一个目录。
|
||||||
|
Please select a new directory different from the current one.: 请选择与当前不同的新目录。
|
||||||
|
Revert to Default: 恢复到默认
|
||||||
|
Select Data Folder: 选择数据文件夹
|
||||||
|
Select Data Folder...: 选择数据文件夹...
|
||||||
|
Select a custom location to store your application data instead of the default location.: 选择一个自定义位置来存储应用程序数据,而不是默认位置。
|
||||||
|
Selected data folder: 选定的数据文件夹
|
||||||
|
Selected new data folder for change: 选定的新数据文件夹用于更改
|
||||||
|
Setting a custom database location requires an application restart to take effect.: 设置自定义数据库位置需要重新启动应用程序才能生效。
|
||||||
|
The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?: 选定的文件夹不为空且不包含PasteBar数据文件。您想创建一个"pastebar-data"子文件夹来存储数据吗?
|
||||||
|
This folder already contains PasteBar data. The application will use this existing data after restart.: 此文件夹已包含PasteBar数据。应用程序将在重启后使用这些现有数据。
|
||||||
|
Use new location: 使用新位置
|
||||||
|
An error occurred during directory processing.: 目录处理期间发生错误。
|
||||||
|
An error occurred during directory selection or setup.: 目录选择或设置期间发生错误。
|
||||||
|
Disable Image Capture: 禁用图像捕获
|
||||||
|
Disable capturing and storing images from clipboard: 禁用从剪贴板捕获和存储图像
|
@ -87,6 +87,8 @@ export default function ClipboardHistorySettings() {
|
|||||||
setClipTextMinLength,
|
setClipTextMinLength,
|
||||||
setClipTextMaxLength,
|
setClipTextMaxLength,
|
||||||
setIsHistoryEnabled,
|
setIsHistoryEnabled,
|
||||||
|
isImageCaptureDisabled,
|
||||||
|
setIsImageCaptureDisabled,
|
||||||
isHistoryAutoUpdateOnCaputureEnabled,
|
isHistoryAutoUpdateOnCaputureEnabled,
|
||||||
setIsHistoryAutoTrimOnCaputureEnabled,
|
setIsHistoryAutoTrimOnCaputureEnabled,
|
||||||
isHistoryAutoTrimOnCaputureEnabled,
|
isHistoryAutoTrimOnCaputureEnabled,
|
||||||
@ -411,6 +413,35 @@ export default function ClipboardHistorySettings() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box className="max-w-xl animate-in fade-in mt-4">
|
||||||
|
<Card
|
||||||
|
className={`${
|
||||||
|
!isImageCaptureDisabled &&
|
||||||
|
'opacity-80 bg-gray-100 dark:bg-gray-900/80'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1">
|
||||||
|
<CardTitle className="animate-in fade-in text-md font-medium w-full">
|
||||||
|
{t('Disable Image Capture', { ns: 'settings' })}
|
||||||
|
</CardTitle>
|
||||||
|
<Switch
|
||||||
|
checked={isImageCaptureDisabled}
|
||||||
|
className="ml-auto"
|
||||||
|
onCheckedChange={() => {
|
||||||
|
setIsImageCaptureDisabled(!isImageCaptureDisabled)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Text className="text-sm text-muted-foreground">
|
||||||
|
{t('Disable capturing and storing images from clipboard', {
|
||||||
|
ns: 'settings',
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box className="mt-4 max-w-xl animate-in fade-in">
|
<Box className="mt-4 max-w-xl animate-in fade-in">
|
||||||
<Card
|
<Card
|
||||||
className={`${
|
className={`${
|
||||||
|
@ -0,0 +1,799 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { dialog, invoke } from '@tauri-apps/api'
|
||||||
|
import { join } from '@tauri-apps/api/path'
|
||||||
|
import { settingsStore, settingsStoreAtom } from '~/store'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
import { Icons } from '~/components/icons'
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
Flex,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextNormal,
|
||||||
|
} from '~/components/ui'
|
||||||
|
|
||||||
|
export default function CustomDatabaseLocationSettings() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const {
|
||||||
|
customDbPath,
|
||||||
|
customDbPathError: storeCustomDbPathError,
|
||||||
|
dbRelocationInProgress,
|
||||||
|
validateCustomDbPath,
|
||||||
|
applyCustomDbPath,
|
||||||
|
revertToDefaultDbPath,
|
||||||
|
relaunchApp,
|
||||||
|
} = useAtomValue(settingsStoreAtom)
|
||||||
|
|
||||||
|
// States for the "Change" functionality within CardContent
|
||||||
|
const [selectedPathForChangeDialog, setSelectedPathForChangeDialog] = useState<
|
||||||
|
string | null
|
||||||
|
>(null)
|
||||||
|
const [dbOperationForChangeDialog, setDbOperationForChangeDialog] = useState<
|
||||||
|
'copy' | 'none'
|
||||||
|
>('none')
|
||||||
|
const [isApplyingChange, setIsApplyingChange] = useState(false)
|
||||||
|
// isRevertingPath state is effectively handled by isProcessing when called from CardContent's revert button
|
||||||
|
|
||||||
|
// General states
|
||||||
|
const [isProcessing, setIsProcessing] = useState(false) // For toggle switch operations and CardContent button operations
|
||||||
|
const [operationError, setOperationError] = useState<string | null>(null)
|
||||||
|
const [validationErrorForChangeDialog, setValidationErrorForChangeDialog] = useState<
|
||||||
|
string | null
|
||||||
|
>(null)
|
||||||
|
|
||||||
|
// Local state to control the expanded/collapsed state of the setup section when no custom path is set
|
||||||
|
const [isSetupSectionExpanded, setIsSetupSectionExpanded] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Reset "Change" dialog state if customDbPath changes (e.g., reverted or set)
|
||||||
|
setValidationErrorForChangeDialog(null)
|
||||||
|
setSelectedPathForChangeDialog(null)
|
||||||
|
setDbOperationForChangeDialog('none')
|
||||||
|
// Reset setup section expansion when custom path is set/unset
|
||||||
|
if (customDbPath) {
|
||||||
|
setIsSetupSectionExpanded(false)
|
||||||
|
}
|
||||||
|
}, [customDbPath])
|
||||||
|
|
||||||
|
// Effect to react to validation errors from the store for the "Change" dialog
|
||||||
|
useEffect(() => {
|
||||||
|
const state = settingsStore.getState()
|
||||||
|
if (
|
||||||
|
selectedPathForChangeDialog &&
|
||||||
|
storeCustomDbPathError &&
|
||||||
|
!state.isCustomDbPathValid
|
||||||
|
) {
|
||||||
|
setValidationErrorForChangeDialog(storeCustomDbPathError)
|
||||||
|
} else if (state.isCustomDbPathValid) {
|
||||||
|
// Clear validation error if store says path is valid (e.g. after successful validation for the selectedPathForChangeDialog)
|
||||||
|
setValidationErrorForChangeDialog(null)
|
||||||
|
}
|
||||||
|
}, [storeCustomDbPathError, selectedPathForChangeDialog])
|
||||||
|
|
||||||
|
// Renamed from handleBrowse, used by "Change" button in CardContent
|
||||||
|
const handleBrowseForChangeDialog = async () => {
|
||||||
|
setOperationError(null)
|
||||||
|
setValidationErrorForChangeDialog(null)
|
||||||
|
try {
|
||||||
|
const selected = await dialog.open({
|
||||||
|
directory: true,
|
||||||
|
multiple: false,
|
||||||
|
title: t('Select Data Folder', { ns: 'settings' }),
|
||||||
|
})
|
||||||
|
if (typeof selected === 'string') {
|
||||||
|
const status: any = await invoke('cmd_check_custom_data_path', {
|
||||||
|
pathStr: selected,
|
||||||
|
})
|
||||||
|
let finalPath = selected
|
||||||
|
|
||||||
|
if (status === 'HasPastebarDataSubfolder') {
|
||||||
|
// Use existing pastebar-data subfolder
|
||||||
|
finalPath = await join(selected, 'pastebar-data')
|
||||||
|
await dialog.message(
|
||||||
|
t(
|
||||||
|
'Found existing "pastebar-data" folder. The application will use this folder to store data.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (status === 'NotEmpty') {
|
||||||
|
const confirmSubfolder = await dialog.confirm(
|
||||||
|
t(
|
||||||
|
'The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (confirmSubfolder) {
|
||||||
|
finalPath = await join(selected, 'pastebar-data')
|
||||||
|
// Create the directory after user confirmation
|
||||||
|
try {
|
||||||
|
await invoke('cmd_create_directory', { pathStr: finalPath })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create pastebar-data directory:', error)
|
||||||
|
setOperationError(
|
||||||
|
t('Failed to create directory. Please check permissions and try again.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (status === 'IsPastebarDataAndNotEmpty') {
|
||||||
|
await dialog.message(
|
||||||
|
t(
|
||||||
|
'This folder already contains PasteBar data. The application will use this existing data after restart.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedPathForChangeDialog(finalPath)
|
||||||
|
if (finalPath !== customDbPath) {
|
||||||
|
await validateCustomDbPath(finalPath) // Validation result will be reflected in storeCustomDbPathError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling directory selection for change:', error)
|
||||||
|
setOperationError(
|
||||||
|
t('An error occurred during directory processing.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renamed from handleApply, used by "Apply and Restart" button in CardContent
|
||||||
|
const handleApplyChangeDialog = async () => {
|
||||||
|
if (!selectedPathForChangeDialog || selectedPathForChangeDialog === customDbPath) {
|
||||||
|
setOperationError(
|
||||||
|
t('Please select a new directory different from the current one.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIsApplyingChange(true)
|
||||||
|
setIsProcessing(true) // General processing state
|
||||||
|
setOperationError(null)
|
||||||
|
setValidationErrorForChangeDialog(null)
|
||||||
|
|
||||||
|
// Ensure validation is re-checked or use existing validation state
|
||||||
|
await validateCustomDbPath(selectedPathForChangeDialog)
|
||||||
|
const currentStoreState = settingsStore.getState()
|
||||||
|
|
||||||
|
if (!currentStoreState.isCustomDbPathValid) {
|
||||||
|
setValidationErrorForChangeDialog(
|
||||||
|
currentStoreState.customDbPathError ||
|
||||||
|
t('Invalid directory selected.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
setIsApplyingChange(false)
|
||||||
|
setIsProcessing(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let confirmMessage: string
|
||||||
|
if (dbOperationForChangeDialog === 'none') {
|
||||||
|
confirmMessage = t(
|
||||||
|
'Are you sure you want to set "{{path}}" as the new data folder? The application will restart.',
|
||||||
|
{
|
||||||
|
ns: 'settings',
|
||||||
|
path: selectedPathForChangeDialog,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
confirmMessage = t(
|
||||||
|
'Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.',
|
||||||
|
{
|
||||||
|
ns: 'settings',
|
||||||
|
operation: t(dbOperationForChangeDialog, { ns: 'settings' }),
|
||||||
|
path: selectedPathForChangeDialog,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = await dialog.confirm(confirmMessage)
|
||||||
|
if (confirmed) {
|
||||||
|
try {
|
||||||
|
await applyCustomDbPath(selectedPathForChangeDialog, dbOperationForChangeDialog)
|
||||||
|
relaunchApp()
|
||||||
|
} catch (error: any) {
|
||||||
|
// Attempt rollback on failure
|
||||||
|
try {
|
||||||
|
await revertToDefaultDbPath()
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore rollback errors
|
||||||
|
}
|
||||||
|
setOperationError(
|
||||||
|
error.message ||
|
||||||
|
t('Failed to apply custom database location.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setIsApplyingChange(false)
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsApplyingChange(false)
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal function for reverting, called by toggle or button
|
||||||
|
const revertToDefaultWithConfirmationInternal = async () => {
|
||||||
|
setOperationError(null)
|
||||||
|
const confirmed = await dialog.confirm(
|
||||||
|
t('ConfirmRevertToDefaultDbPathMessage', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return false // Indicates cancellation
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsProcessing(true)
|
||||||
|
try {
|
||||||
|
await revertToDefaultDbPath()
|
||||||
|
relaunchApp()
|
||||||
|
return true // Indicates success
|
||||||
|
} catch (error: any) {
|
||||||
|
setOperationError(
|
||||||
|
error.message ||
|
||||||
|
t('Failed to revert to default database location.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
return false // Indicates failure
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by "Revert to Default" button in CardContent
|
||||||
|
const handleRevertFromContent = async () => {
|
||||||
|
await revertToDefaultWithConfirmationInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle initial setup path selection
|
||||||
|
const handleSetupPathSelection = async () => {
|
||||||
|
setOperationError(null)
|
||||||
|
setValidationErrorForChangeDialog(null)
|
||||||
|
try {
|
||||||
|
const selected = await dialog.open({
|
||||||
|
directory: true,
|
||||||
|
multiple: false,
|
||||||
|
title: t('Select Data Folder', { ns: 'settings' }),
|
||||||
|
})
|
||||||
|
if (typeof selected === 'string') {
|
||||||
|
const status: any = await invoke('cmd_check_custom_data_path', {
|
||||||
|
pathStr: selected,
|
||||||
|
})
|
||||||
|
let finalPath = selected
|
||||||
|
|
||||||
|
if (status === 'HasPastebarDataSubfolder') {
|
||||||
|
// Use existing pastebar-data subfolder
|
||||||
|
finalPath = await join(selected, 'pastebar-data')
|
||||||
|
await dialog.message(
|
||||||
|
t(
|
||||||
|
'Found existing "pastebar-data" folder. The application will use this folder to store data.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (status === 'NotEmpty') {
|
||||||
|
const confirmSubfolder = await dialog.confirm(
|
||||||
|
t(
|
||||||
|
'The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (confirmSubfolder) {
|
||||||
|
finalPath = await join(selected, 'pastebar-data')
|
||||||
|
// Create the directory after user confirmation
|
||||||
|
try {
|
||||||
|
await invoke('cmd_create_directory', { pathStr: finalPath })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create pastebar-data directory:', error)
|
||||||
|
setOperationError(
|
||||||
|
t('Failed to create directory. Please check permissions and try again.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (status === 'IsPastebarDataAndNotEmpty') {
|
||||||
|
await dialog.message(
|
||||||
|
t(
|
||||||
|
'This folder already contains PasteBar data. The application will use this existing data after restart.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedPathForChangeDialog(finalPath)
|
||||||
|
await validateCustomDbPath(finalPath) // Validation result will be reflected in storeCustomDbPathError
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling directory selection for setup:', error)
|
||||||
|
setOperationError(
|
||||||
|
t('An error occurred during directory processing.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle applying the initial setup
|
||||||
|
const handleApplySetup = async () => {
|
||||||
|
if (!selectedPathForChangeDialog) {
|
||||||
|
setOperationError(t('Please select a directory first.', { ns: 'settings' }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIsApplyingChange(true)
|
||||||
|
setIsProcessing(true)
|
||||||
|
setOperationError(null)
|
||||||
|
setValidationErrorForChangeDialog(null)
|
||||||
|
|
||||||
|
// Ensure validation is re-checked
|
||||||
|
await validateCustomDbPath(selectedPathForChangeDialog)
|
||||||
|
const currentStoreState = settingsStore.getState()
|
||||||
|
|
||||||
|
if (!currentStoreState.isCustomDbPathValid) {
|
||||||
|
setValidationErrorForChangeDialog(
|
||||||
|
currentStoreState.customDbPathError ||
|
||||||
|
t('Invalid directory selected.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
setIsApplyingChange(false)
|
||||||
|
setIsProcessing(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let confirmMessage: string
|
||||||
|
if (dbOperationForChangeDialog === 'none') {
|
||||||
|
confirmMessage = t(
|
||||||
|
'Are you sure you want to set "{{path}}" as the new data folder? The application will restart.',
|
||||||
|
{
|
||||||
|
ns: 'settings',
|
||||||
|
path: selectedPathForChangeDialog,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
confirmMessage = t(
|
||||||
|
'Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.',
|
||||||
|
{
|
||||||
|
ns: 'settings',
|
||||||
|
operation: t(dbOperationForChangeDialog, { ns: 'settings' }),
|
||||||
|
path: selectedPathForChangeDialog,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = await dialog.confirm(confirmMessage)
|
||||||
|
if (confirmed) {
|
||||||
|
try {
|
||||||
|
await applyCustomDbPath(selectedPathForChangeDialog, dbOperationForChangeDialog)
|
||||||
|
relaunchApp()
|
||||||
|
} catch (error: any) {
|
||||||
|
try {
|
||||||
|
await revertToDefaultDbPath()
|
||||||
|
} catch (_) {}
|
||||||
|
setOperationError(
|
||||||
|
error.message ||
|
||||||
|
t('Failed to apply custom database location.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setIsApplyingChange(false)
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsApplyingChange(false)
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to choose and set custom path, called when toggle is turned ON
|
||||||
|
const chooseAndSetCustomPath = async () => {
|
||||||
|
setOperationError(null)
|
||||||
|
setIsProcessing(true)
|
||||||
|
let pathSuccessfullySet = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const selected = await dialog.open({
|
||||||
|
directory: true,
|
||||||
|
multiple: false,
|
||||||
|
title: t('Select Data Folder', { ns: 'settings' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof selected === 'string') {
|
||||||
|
let finalPath = selected
|
||||||
|
const status: any = await invoke('cmd_check_custom_data_path', {
|
||||||
|
pathStr: selected,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (status === 'HasPastebarDataSubfolder') {
|
||||||
|
// Use existing pastebar-data subfolder
|
||||||
|
finalPath = await join(selected, 'pastebar-data')
|
||||||
|
await dialog.message(
|
||||||
|
t(
|
||||||
|
'Found existing "pastebar-data" folder. The application will use this folder to store data.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (status === 'NotEmpty') {
|
||||||
|
const confirmSubfolder = await dialog.confirm(
|
||||||
|
t(
|
||||||
|
'The selected folder is not empty and does not contain PasteBar data files. Do you want to create a "pastebar-data" subfolder to store the data?',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (confirmSubfolder) {
|
||||||
|
finalPath = await join(selected, 'pastebar-data')
|
||||||
|
// Create the directory after user confirmation
|
||||||
|
try {
|
||||||
|
await invoke('cmd_create_directory', { pathStr: finalPath })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create pastebar-data directory:', error)
|
||||||
|
setOperationError(
|
||||||
|
t('Failed to create directory. Please check permissions and try again.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
setIsProcessing(false)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsProcessing(false)
|
||||||
|
return false // User cancelled subfolder creation
|
||||||
|
}
|
||||||
|
} else if (status === 'IsPastebarDataAndNotEmpty') {
|
||||||
|
await dialog.message(
|
||||||
|
t(
|
||||||
|
'This folder already contains PasteBar data. The application will use this existing data after restart.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await validateCustomDbPath(finalPath)
|
||||||
|
const currentStoreState = settingsStore.getState()
|
||||||
|
|
||||||
|
if (!currentStoreState.isCustomDbPathValid) {
|
||||||
|
setOperationError(
|
||||||
|
currentStoreState.customDbPathError ||
|
||||||
|
t('Invalid directory selected.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
setIsProcessing(false)
|
||||||
|
return false // Validation failed
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = await dialog.confirm(
|
||||||
|
t(
|
||||||
|
'Are you sure you want to set "{{path}}" as the new data folder? The application will restart.',
|
||||||
|
{ ns: 'settings', path: finalPath }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
try {
|
||||||
|
await applyCustomDbPath(finalPath, 'none') // 'none' for initial setup
|
||||||
|
relaunchApp()
|
||||||
|
pathSuccessfullySet = true // Path will be set by store, app restarts
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
await revertToDefaultDbPath()
|
||||||
|
} catch (_) {}
|
||||||
|
setOperationError(
|
||||||
|
(error as any).message ||
|
||||||
|
t('Failed to apply custom database location.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error choosing custom DB path:', error)
|
||||||
|
setOperationError(
|
||||||
|
t('An error occurred during directory selection or setup.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
return pathSuccessfullySet // This return might not be directly used if app restarts
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleToggle = async (checked: boolean) => {
|
||||||
|
if (!customDbPath) {
|
||||||
|
// If no custom path is set, toggle the setup section expansion
|
||||||
|
setIsSetupSectionExpanded(checked)
|
||||||
|
if (!checked) {
|
||||||
|
// When closing, reset any setup-related states
|
||||||
|
setOperationError(null)
|
||||||
|
setSelectedPathForChangeDialog(null)
|
||||||
|
setDbOperationForChangeDialog('none')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If customDbPath is already set, the toggle is disabled so this won't be called
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoading = dbRelocationInProgress || isProcessing || isApplyingChange
|
||||||
|
const currentPathDisplay = customDbPath || t('Default', { ns: 'settings' })
|
||||||
|
const isPathUnchangedForChangeDialog = selectedPathForChangeDialog === customDbPath
|
||||||
|
|
||||||
|
// Determine switch state: ON if custom path is set OR if setup section is expanded
|
||||||
|
const isSwitchChecked = !!customDbPath || isSetupSectionExpanded
|
||||||
|
// Disable switch only when loading or when custom path is already set (to prevent toggling off)
|
||||||
|
const isSwitchDisabled = isLoading || !!customDbPath
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className="animate-in fade-in max-w-xl mt-4">
|
||||||
|
<Card
|
||||||
|
className={`${
|
||||||
|
!customDbPath && !isSetupSectionExpanded
|
||||||
|
? 'opacity-80 bg-gray-100 dark:bg-gray-900/80'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1">
|
||||||
|
<CardTitle className="animate-in fade-in text-md font-medium">
|
||||||
|
{t('Custom Application Data Location', { ns: 'settings' })}
|
||||||
|
</CardTitle>
|
||||||
|
{!customDbPath && (
|
||||||
|
<Switch
|
||||||
|
checked={isSwitchChecked}
|
||||||
|
onCheckedChange={handleToggle}
|
||||||
|
disabled={isSwitchDisabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
{!!customDbPath ? ( // Content visible only if customDbPath is set
|
||||||
|
<CardContent>
|
||||||
|
<Text className="text-sm text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
'Custom data location is active. You can change it or revert to the default location. Changes require an application restart.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
<div>
|
||||||
|
<Text className="text-sm text-muted-foreground">
|
||||||
|
{t('Current data folder', { ns: 'settings' })}:
|
||||||
|
</Text>
|
||||||
|
<Text className="text-sm font-semibold text-foreground mt-1 break-all">
|
||||||
|
{currentPathDisplay}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedPathForChangeDialog && (
|
||||||
|
<div className="p-3 bg-blue-50 dark:bg-blue-950/20 rounded-md border">
|
||||||
|
<Text className="text-sm font-medium text-blue-900 dark:text-blue-100">
|
||||||
|
{t('Selected new data folder for change', { ns: 'settings' })}:{' '}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-sm text-blue-700 dark:text-blue-200 break-all">
|
||||||
|
{selectedPathForChangeDialog}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{validationErrorForChangeDialog && (
|
||||||
|
<Text className="text-sm text-red-500">
|
||||||
|
{validationErrorForChangeDialog}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Flex className="gap-3">
|
||||||
|
<Button
|
||||||
|
onClick={handleBrowseForChangeDialog}
|
||||||
|
disabled={isLoading}
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1 h-10"
|
||||||
|
>
|
||||||
|
{dbRelocationInProgress && !isApplyingChange && !isProcessing ? (
|
||||||
|
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
|
{t('Change Custom Data Folder...', { ns: 'settings' })}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleRevertFromContent}
|
||||||
|
disabled={isLoading}
|
||||||
|
variant="secondary"
|
||||||
|
className="flex-1 h-10 bg-yellow-500 hover:bg-yellow-600 dark:bg-yellow-600 dark:hover:bg-yellow-700 text-white border-yellow-500 dark:border-yellow-600"
|
||||||
|
>
|
||||||
|
{isProcessing && !isApplyingChange ? (
|
||||||
|
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
|
{t('Revert to Default', { ns: 'settings' })}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{selectedPathForChangeDialog && (
|
||||||
|
<Flex className="items-center space-x-4">
|
||||||
|
<Text className="text-sm">
|
||||||
|
{t('Operation when applying new path', { ns: 'settings' })}:
|
||||||
|
</Text>
|
||||||
|
<label className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="dbOperationForChange"
|
||||||
|
value="none"
|
||||||
|
checked={dbOperationForChangeDialog === 'none'}
|
||||||
|
onChange={() => setDbOperationForChangeDialog('none')}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="form-radio accent-primary"
|
||||||
|
/>
|
||||||
|
<TextNormal size="sm">
|
||||||
|
{t('Use new location', { ns: 'settings' })}
|
||||||
|
</TextNormal>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="dbOperationForChange"
|
||||||
|
value="copy"
|
||||||
|
checked={dbOperationForChangeDialog === 'copy'}
|
||||||
|
onChange={() => setDbOperationForChangeDialog('copy')}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="form-radio accent-primary"
|
||||||
|
/>
|
||||||
|
<TextNormal size="sm">
|
||||||
|
{t('Copy data', { ns: 'settings' })}
|
||||||
|
</TextNormal>
|
||||||
|
</label>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{operationError && (
|
||||||
|
<Text className="text-sm text-red-500">{operationError}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedPathForChangeDialog && (
|
||||||
|
<Button
|
||||||
|
onClick={handleApplyChangeDialog}
|
||||||
|
disabled={
|
||||||
|
isLoading ||
|
||||||
|
!selectedPathForChangeDialog ||
|
||||||
|
isPathUnchangedForChangeDialog ||
|
||||||
|
!!validationErrorForChangeDialog
|
||||||
|
}
|
||||||
|
className="w-full h-10"
|
||||||
|
>
|
||||||
|
{isApplyingChange ? (
|
||||||
|
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
|
{t('Apply and Restart', { ns: 'settings' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Text className="text-xs text-muted-foreground pt-2">
|
||||||
|
{t(
|
||||||
|
'Changing the database location requires an application restart to take effect.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
) : isSetupSectionExpanded ? (
|
||||||
|
// Setup content visible when no custom path is set but section is expanded
|
||||||
|
<CardContent>
|
||||||
|
<Text className="text-sm text-muted-foreground mb-4">
|
||||||
|
{t(
|
||||||
|
'Select a custom location to store your application data instead of the default location.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{selectedPathForChangeDialog && (
|
||||||
|
<div className="p-3 bg-blue-50 dark:bg-blue-950/20 rounded-md border">
|
||||||
|
<Text className="text-sm font-medium text-blue-900 dark:text-blue-100">
|
||||||
|
{t('Selected data folder', { ns: 'settings' })}:{' '}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-sm text-blue-700 dark:text-blue-200 break-all">
|
||||||
|
{selectedPathForChangeDialog}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{validationErrorForChangeDialog && (
|
||||||
|
<Text className="text-sm text-red-500">
|
||||||
|
{validationErrorForChangeDialog}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleSetupPathSelection}
|
||||||
|
disabled={isLoading}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full h-10"
|
||||||
|
>
|
||||||
|
{isProcessing && !isApplyingChange ? (
|
||||||
|
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
|
{selectedPathForChangeDialog
|
||||||
|
? t('Change Selected Folder...', { ns: 'settings' })
|
||||||
|
: t('Select Data Folder...', { ns: 'settings' })}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{selectedPathForChangeDialog && (
|
||||||
|
<Flex className="items-center space-x-4">
|
||||||
|
<Text className="text-sm">
|
||||||
|
{t('Operation when applying new path', { ns: 'settings' })}:
|
||||||
|
</Text>
|
||||||
|
<label className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="dbOperationForSetup"
|
||||||
|
value="none"
|
||||||
|
checked={dbOperationForChangeDialog === 'none'}
|
||||||
|
onChange={() => setDbOperationForChangeDialog('none')}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="form-radio accent-primary"
|
||||||
|
/>
|
||||||
|
<TextNormal size="sm">
|
||||||
|
{t('Use new location', { ns: 'settings' })}
|
||||||
|
</TextNormal>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="dbOperationForSetup"
|
||||||
|
value="copy"
|
||||||
|
checked={dbOperationForChangeDialog === 'copy'}
|
||||||
|
onChange={() => setDbOperationForChangeDialog('copy')}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="form-radio accent-primary"
|
||||||
|
/>
|
||||||
|
<TextNormal size="sm">
|
||||||
|
{t('Copy data', { ns: 'settings' })}
|
||||||
|
</TextNormal>
|
||||||
|
</label>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{operationError && (
|
||||||
|
<Text className="text-sm text-red-500">{operationError}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedPathForChangeDialog && (
|
||||||
|
<Button
|
||||||
|
onClick={handleApplySetup}
|
||||||
|
disabled={
|
||||||
|
isLoading ||
|
||||||
|
!selectedPathForChangeDialog ||
|
||||||
|
!!validationErrorForChangeDialog
|
||||||
|
}
|
||||||
|
className="w-full h-10"
|
||||||
|
>
|
||||||
|
{isApplyingChange ? (
|
||||||
|
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
|
{t('Apply and Restart', { ns: 'settings' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Text className="text-xs text-muted-foreground pt-2">
|
||||||
|
{t(
|
||||||
|
'Setting a custom database location requires an application restart to take effect.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
// Default collapsed state when no custom path is set
|
||||||
|
<CardContent>
|
||||||
|
<Text className="text-sm text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
'Enable custom data location to store application data in a directory of your choice instead of the default location.',
|
||||||
|
{ ns: 'settings' }
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</CardContent>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
@ -10,8 +10,9 @@ import {
|
|||||||
themeStoreAtom,
|
themeStoreAtom,
|
||||||
uiStoreAtom,
|
uiStoreAtom,
|
||||||
} from '~/store'
|
} from '~/store'
|
||||||
|
import CustomDatabaseLocationSettings from './CustomDatabaseLocationSettings'
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
import { MessageSquare, MessageSquareDashed } from 'lucide-react'
|
import { ChevronDown, ChevronUp, MessageSquare, MessageSquareDashed } from 'lucide-react'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
@ -83,7 +84,7 @@ export default function UserPreferences() {
|
|||||||
if (theme !== mode) {
|
if (theme !== mode) {
|
||||||
setMode(theme)
|
setMode(theme)
|
||||||
}
|
}
|
||||||
}, [theme])
|
}, [theme, mode, setMode]) // Added mode and setMode to dependency array
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
invoke('is_autostart_enabled').then(isEnabled => {
|
invoke('is_autostart_enabled').then(isEnabled => {
|
||||||
@ -107,6 +108,8 @@ export default function UserPreferences() {
|
|||||||
setQuickPasteHotkey(hotKeysShowHideQuickPasteWindow)
|
setQuickPasteHotkey(hotKeysShowHideQuickPasteWindow)
|
||||||
}
|
}
|
||||||
}, [hotKeysShowHideMainAppWindow, hotKeysShowHideQuickPasteWindow])
|
}, [hotKeysShowHideMainAppWindow, hotKeysShowHideQuickPasteWindow])
|
||||||
|
// Removed mainAppHotkey, quickPasteHotkey from local state dependencies in the original thought process,
|
||||||
|
// as they are set inside this effect. The effect correctly depends on props.
|
||||||
|
|
||||||
const handleKeyDown = (
|
const handleKeyDown = (
|
||||||
event: KeyboardEvent | React.KeyboardEvent<HTMLInputElement>,
|
event: KeyboardEvent | React.KeyboardEvent<HTMLInputElement>,
|
||||||
@ -274,6 +277,10 @@ export default function UserPreferences() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* ------------- Custom Database Location Settings Card ------------- */}
|
||||||
|
<CustomDatabaseLocationSettings />
|
||||||
|
{/* ------------------------------------------------------------------ */}
|
||||||
|
|
||||||
<Box className="animate-in fade-in max-w-xl mt-4">
|
<Box className="animate-in fade-in max-w-xl mt-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-col items-start justify-between space-y-0 pb-1">
|
<CardHeader className="flex flex-col items-start justify-between space-y-0 pb-1">
|
||||||
|
@ -26,7 +26,11 @@ import {
|
|||||||
type Settings = {
|
type Settings = {
|
||||||
appLastUpdateVersion: string
|
appLastUpdateVersion: string
|
||||||
appLastUpdateDate: string
|
appLastUpdateDate: string
|
||||||
appDataDir: string
|
appDataDir: string // This might be the old customDbPath or a general app data dir. We'll add a specific one.
|
||||||
|
customDbPath: string | null // Path to the custom database directory
|
||||||
|
isCustomDbPathValid: boolean | null // Validation status of the entered customDbPath
|
||||||
|
customDbPathError: string | null // Error message if validation fails or operation fails
|
||||||
|
dbRelocationInProgress: boolean // True if a DB move/copy/revert operation is ongoing
|
||||||
isAppReady: boolean
|
isAppReady: boolean
|
||||||
isClipNotesHoverCardsEnabled: boolean
|
isClipNotesHoverCardsEnabled: boolean
|
||||||
clipNotesHoverCardsDelayMS: number
|
clipNotesHoverCardsDelayMS: number
|
||||||
@ -81,6 +85,7 @@ type Settings = {
|
|||||||
isScreenLockPassCodeRequireOnStart: boolean
|
isScreenLockPassCodeRequireOnStart: boolean
|
||||||
clipTextMinLength: number
|
clipTextMinLength: number
|
||||||
clipTextMaxLength: number
|
clipTextMaxLength: number
|
||||||
|
isImageCaptureDisabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Constants = {
|
type Constants = {
|
||||||
@ -88,6 +93,11 @@ type Constants = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsStoreState {
|
export interface SettingsStoreState {
|
||||||
|
setCustomDbPath: (path: string | null) => void
|
||||||
|
validateCustomDbPath: (path: string) => Promise<void>
|
||||||
|
applyCustomDbPath: (newPath: string, operation: 'copy' | 'none') => Promise<string>
|
||||||
|
revertToDefaultDbPath: () => Promise<string>
|
||||||
|
loadInitialCustomDbPath: () => Promise<void>
|
||||||
setIsHistoryEnabled: (isHistoryEnabled: boolean) => void
|
setIsHistoryEnabled: (isHistoryEnabled: boolean) => void
|
||||||
setIsHistoryAutoUpdateOnCaputureEnabled: (
|
setIsHistoryAutoUpdateOnCaputureEnabled: (
|
||||||
isHistoryAutoUpdateOnCaputureEnabled: boolean
|
isHistoryAutoUpdateOnCaputureEnabled: boolean
|
||||||
@ -120,7 +130,7 @@ export interface SettingsStoreState {
|
|||||||
setAppToursCompletedList: (words: string[]) => void
|
setAppToursCompletedList: (words: string[]) => void
|
||||||
setAppToursSkippedList: (words: string[]) => void
|
setAppToursSkippedList: (words: string[]) => void
|
||||||
setHistoryDetectLanguagesPrioritizedList: (words: string[]) => void
|
setHistoryDetectLanguagesPrioritizedList: (words: string[]) => void
|
||||||
setAppDataDir: (appDataDir: string) => void
|
setAppDataDir: (appDataDir: string) => void // Keep if used for other general app data
|
||||||
setIsAutoCloseOnCopyPaste: (isEnabled: boolean) => void
|
setIsAutoCloseOnCopyPaste: (isEnabled: boolean) => void
|
||||||
setClipNotesHoverCardsDelayMS: (delay: number) => void
|
setClipNotesHoverCardsDelayMS: (delay: number) => void
|
||||||
setClipNotesMaxWidth: (width: number) => void
|
setClipNotesMaxWidth: (width: number) => void
|
||||||
@ -150,6 +160,7 @@ export interface SettingsStoreState {
|
|||||||
setIsKeepMainWindowClosedOnRestartEnabled: (isEnabled: boolean) => void
|
setIsKeepMainWindowClosedOnRestartEnabled: (isEnabled: boolean) => void
|
||||||
setIsHideCollectionsOnNavBar: (isEnabled: boolean) => void
|
setIsHideCollectionsOnNavBar: (isEnabled: boolean) => void
|
||||||
setIsShowNavBarItemsOnHoverOnly: (isEnabled: boolean) => void
|
setIsShowNavBarItemsOnHoverOnly: (isEnabled: boolean) => void
|
||||||
|
setIsImageCaptureDisabled: (isEnabled: boolean) => void
|
||||||
hashPassword: (pass: string) => Promise<string>
|
hashPassword: (pass: string) => Promise<string>
|
||||||
isNotTourCompletedOrSkipped: (tourName: string) => boolean
|
isNotTourCompletedOrSkipped: (tourName: string) => boolean
|
||||||
verifyPassword: (pass: string, hash: string) => Promise<boolean>
|
verifyPassword: (pass: string, hash: string) => Promise<boolean>
|
||||||
@ -175,7 +186,11 @@ const initialState: SettingsStoreState & Settings = {
|
|||||||
appLastUpdateVersion: '0.0.1',
|
appLastUpdateVersion: '0.0.1',
|
||||||
appLastUpdateDate: '',
|
appLastUpdateDate: '',
|
||||||
isAppReady: false,
|
isAppReady: false,
|
||||||
appDataDir: '',
|
appDataDir: '', // Default app data dir if needed for other things
|
||||||
|
customDbPath: null,
|
||||||
|
isCustomDbPathValid: null,
|
||||||
|
customDbPathError: null,
|
||||||
|
dbRelocationInProgress: false,
|
||||||
isHistoryEnabled: true,
|
isHistoryEnabled: true,
|
||||||
isFirstRun: true,
|
isFirstRun: true,
|
||||||
historyDetectLanguagesEnabledList: [],
|
historyDetectLanguagesEnabledList: [],
|
||||||
@ -230,6 +245,7 @@ const initialState: SettingsStoreState & Settings = {
|
|||||||
isFirstRunAfterUpdate: false,
|
isFirstRunAfterUpdate: false,
|
||||||
clipTextMinLength: 0,
|
clipTextMinLength: 0,
|
||||||
clipTextMaxLength: 5000,
|
clipTextMaxLength: 5000,
|
||||||
|
isImageCaptureDisabled: false,
|
||||||
CONST: {
|
CONST: {
|
||||||
APP_DETECT_LANGUAGES_SUPPORTED: [],
|
APP_DETECT_LANGUAGES_SUPPORTED: [],
|
||||||
},
|
},
|
||||||
@ -286,8 +302,14 @@ const initialState: SettingsStoreState & Settings = {
|
|||||||
setIsShowNavBarItemsOnHoverOnly: () => {},
|
setIsShowNavBarItemsOnHoverOnly: () => {},
|
||||||
setClipTextMinLength: () => {},
|
setClipTextMinLength: () => {},
|
||||||
setClipTextMaxLength: () => {},
|
setClipTextMaxLength: () => {},
|
||||||
|
setIsImageCaptureDisabled: () => {},
|
||||||
initConstants: () => {},
|
initConstants: () => {},
|
||||||
setAppDataDir: () => {},
|
setAppDataDir: () => {}, // Keep if used for other general app data
|
||||||
|
setCustomDbPath: () => {},
|
||||||
|
validateCustomDbPath: async () => {},
|
||||||
|
applyCustomDbPath: async () => '',
|
||||||
|
revertToDefaultDbPath: async () => '',
|
||||||
|
loadInitialCustomDbPath: async () => {},
|
||||||
updateSetting: () => {},
|
updateSetting: () => {},
|
||||||
setIsFirstRun: () => {},
|
setIsFirstRun: () => {},
|
||||||
setAppLastUpdateVersion: () => {},
|
setAppLastUpdateVersion: () => {},
|
||||||
@ -352,7 +374,8 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
|
|||||||
if (
|
if (
|
||||||
name === 'isHistoryEnabled' ||
|
name === 'isHistoryEnabled' ||
|
||||||
name === 'userSelectedLanguage' ||
|
name === 'userSelectedLanguage' ||
|
||||||
name === 'isAppLocked'
|
name === 'isAppLocked' ||
|
||||||
|
name === 'isImageCaptureDisabled'
|
||||||
) {
|
) {
|
||||||
invoke('build_system_menu')
|
invoke('build_system_menu')
|
||||||
}
|
}
|
||||||
@ -585,6 +608,9 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
|
|||||||
setClipTextMaxLength: async (length: number) => {
|
setClipTextMaxLength: async (length: number) => {
|
||||||
return get().updateSetting('clipTextMaxLength', length)
|
return get().updateSetting('clipTextMaxLength', length)
|
||||||
},
|
},
|
||||||
|
setIsImageCaptureDisabled: async (isEnabled: boolean) => {
|
||||||
|
return get().updateSetting('isImageCaptureDisabled', isEnabled)
|
||||||
|
},
|
||||||
isNotTourCompletedOrSkipped: (tourName: string) => {
|
isNotTourCompletedOrSkipped: (tourName: string) => {
|
||||||
const { appToursCompletedList, appToursSkippedList } = get()
|
const { appToursCompletedList, appToursSkippedList } = get()
|
||||||
return (
|
return (
|
||||||
@ -738,10 +764,77 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
|
|||||||
availableVersionDateISO.value = null
|
availableVersionDateISO.value = null
|
||||||
},
|
},
|
||||||
initConstants: (CONST: Constants) => set(() => ({ CONST })),
|
initConstants: (CONST: Constants) => set(() => ({ CONST })),
|
||||||
setAppDataDir: (appDataDir: string) =>
|
setAppDataDir: (
|
||||||
|
appDataDir: string // Keep if used for other general app data
|
||||||
|
) =>
|
||||||
set(() => ({
|
set(() => ({
|
||||||
appDataDir,
|
appDataDir,
|
||||||
})),
|
})),
|
||||||
|
// Actions for custom DB path
|
||||||
|
setCustomDbPath: (path: string | null) =>
|
||||||
|
set({ customDbPath: path, isCustomDbPathValid: null, customDbPathError: null }),
|
||||||
|
loadInitialCustomDbPath: async () => {
|
||||||
|
try {
|
||||||
|
const path = await invoke('cmd_get_custom_db_path')
|
||||||
|
set({ customDbPath: path as string | null })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load initial custom DB path:', error)
|
||||||
|
set({ customDbPathError: 'Failed to load custom DB path setting.' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateCustomDbPath: async (path: string) => {
|
||||||
|
set({
|
||||||
|
dbRelocationInProgress: true,
|
||||||
|
customDbPathError: null,
|
||||||
|
isCustomDbPathValid: null,
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await invoke('cmd_validate_custom_db_path', { pathStr: path })
|
||||||
|
set({ isCustomDbPathValid: true, dbRelocationInProgress: false })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Custom DB path validation failed:', error)
|
||||||
|
set({
|
||||||
|
isCustomDbPathValid: false,
|
||||||
|
customDbPathError: error as string,
|
||||||
|
dbRelocationInProgress: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applyCustomDbPath: async (newPath: string, operation: 'copy' | 'none') => {
|
||||||
|
set({ dbRelocationInProgress: true, customDbPathError: null })
|
||||||
|
try {
|
||||||
|
const message = await invoke('cmd_set_and_relocate_data', {
|
||||||
|
newParentDirPath: newPath,
|
||||||
|
operation,
|
||||||
|
})
|
||||||
|
set({
|
||||||
|
customDbPath: newPath,
|
||||||
|
isCustomDbPathValid: true,
|
||||||
|
dbRelocationInProgress: false,
|
||||||
|
})
|
||||||
|
return message as string
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to apply custom DB path:', error)
|
||||||
|
set({ customDbPathError: error as string, dbRelocationInProgress: false })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
revertToDefaultDbPath: async () => {
|
||||||
|
set({ dbRelocationInProgress: true, customDbPathError: null })
|
||||||
|
try {
|
||||||
|
const message = await invoke('cmd_revert_to_default_data_location')
|
||||||
|
set({
|
||||||
|
customDbPath: null,
|
||||||
|
isCustomDbPathValid: null,
|
||||||
|
dbRelocationInProgress: false,
|
||||||
|
})
|
||||||
|
return message as string
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to revert to default DB path:', error)
|
||||||
|
set({ customDbPathError: error as string, dbRelocationInProgress: false })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
setAppLastUpdateVersion: (appLastUpdateVersion: string) => {
|
setAppLastUpdateVersion: (appLastUpdateVersion: string) => {
|
||||||
return get().updateSetting('appLastUpdateVersion', appLastUpdateVersion)
|
return get().updateSetting('appLastUpdateVersion', appLastUpdateVersion)
|
||||||
},
|
},
|
||||||
|
File diff suppressed because one or more lines are too long
2470
src-tauri/Cargo.lock
generated
2470
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ edition = "2021"
|
|||||||
rust-version = "1.75.0"
|
rust-version = "1.75.0"
|
||||||
|
|
||||||
[build-dependencies.tauri-build]
|
[build-dependencies.tauri-build]
|
||||||
version = "1.4"
|
version = "1.5"
|
||||||
features = []
|
features = []
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
@ -16,6 +16,7 @@ winapi = { version = "0.3", features = ["winuser", "windef"] }
|
|||||||
winreg = "0.52.0"
|
winreg = "0.52.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
fs_extra = "1.3.0"
|
||||||
fns = "0"
|
fns = "0"
|
||||||
mouse_position = "0.1.4"
|
mouse_position = "0.1.4"
|
||||||
keyring = "2.3.2"
|
keyring = "2.3.2"
|
||||||
@ -40,7 +41,7 @@ reqwest = "0.11.12"
|
|||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.66"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tauri = { version = "1.7.1", features = [ "api-all", "system-tray", "icon-png", "clipboard", "updater", "reqwest-client"] }
|
tauri = { version = "1.8.3", features = [ "api-all", "system-tray", "icon-png", "clipboard", "updater", "reqwest-client"] }
|
||||||
tokio = { version = "1.28.2", features = ["full"] }
|
tokio = { version = "1.28.2", features = ["full"] }
|
||||||
#single-instance = "0.3.3"
|
#single-instance = "0.3.3"
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ chrono = { version = "0.4.24", features = ["serde"] }
|
|||||||
uuid = "1.3.1"
|
uuid = "1.3.1"
|
||||||
once_cell = "1.7.0"
|
once_cell = "1.7.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
arboard = "3.2.1"
|
arboard = "3.5.0"
|
||||||
image = "0.24.9"
|
image = "0.24.9"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
base64 = "0.22.0"
|
base64 = "0.22.0"
|
||||||
@ -90,7 +91,7 @@ zip = "0.6"
|
|||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
macos-accessibility-client = { git = "https://github.com/kurdin/macos-accessibility-client", branch = "master", version = "0.0.1" }
|
macos-accessibility-client = { git = "https://github.com/kurdin/macos-accessibility-client", branch = "master", version = "0.0.1" }
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
cocoa = "0.25.0"
|
cocoa = "0.26.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
inputbotlinux = { path = "libs/inputbotlinux" }
|
inputbotlinux = { path = "libs/inputbotlinux" }
|
||||||
|
@ -22,6 +22,7 @@ use active_win_pos_rs::get_active_window;
|
|||||||
use crate::cron_jobs;
|
use crate::cron_jobs;
|
||||||
use crate::models::Setting;
|
use crate::models::Setting;
|
||||||
use crate::services::history_service;
|
use crate::services::history_service;
|
||||||
|
use crate::services::utils::debug_output;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LanguageDetectOptions {
|
pub struct LanguageDetectOptions {
|
||||||
@ -230,6 +231,19 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Ok(image_binary) = clipboard_manager.get_image_binary() {
|
} else if let Ok(image_binary) = clipboard_manager.get_image_binary() {
|
||||||
|
// Check if image capturing is disabled
|
||||||
|
let is_image_capture_disabled = settings_map
|
||||||
|
.get("isImageCaptureDisabled")
|
||||||
|
.and_then(|s| s.value_bool)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if is_image_capture_disabled {
|
||||||
|
debug_output(|| {
|
||||||
|
println!("Image capturing is disabled, skipping image capture!");
|
||||||
|
});
|
||||||
|
return CallbackResult::Next;
|
||||||
|
}
|
||||||
|
|
||||||
let mut is_app_excluded = false;
|
let mut is_app_excluded = false;
|
||||||
|
|
||||||
if let Some(setting) = settings_map.get("isExclusionAppListEnabled") {
|
if let Some(setting) = settings_map.get("isExclusionAppListEnabled") {
|
||||||
@ -299,58 +313,7 @@ impl ClipboardManager {
|
|||||||
clipboard.set_text(text).map_err(|err| err.to_string())
|
clipboard.set_text(text).map_err(|err| err.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_image(&self) -> Result<String, String> {
|
// write_image function remains unchanged as it's writing, not reading
|
||||||
let mut clipboard = Clipboard::new().unwrap();
|
|
||||||
let image = clipboard.get_image().map_err(|err| err.to_string())?;
|
|
||||||
let tmp_dir = tempfile::Builder::new()
|
|
||||||
.prefix("clipboard-img")
|
|
||||||
.tempdir()
|
|
||||||
.map_err(|err| err.to_string())?;
|
|
||||||
let fname = tmp_dir.path().join("clipboard-img.png");
|
|
||||||
|
|
||||||
let image2: RgbaImage = ImageBuffer::from_raw(
|
|
||||||
image.width.try_into().unwrap(),
|
|
||||||
image.height.try_into().unwrap(),
|
|
||||||
image.bytes.into_owned(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
image2.save(fname.clone()).map_err(|err| err.to_string())?;
|
|
||||||
let mut file = File::open(fname.clone()).unwrap();
|
|
||||||
let mut buffer = vec![];
|
|
||||||
file.read_to_end(&mut buffer).unwrap();
|
|
||||||
let base64_str = general_purpose::STANDARD_NO_PAD.encode(buffer);
|
|
||||||
Ok(base64_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_image_binary(&self) -> Result<ImageData, String> {
|
|
||||||
let mut clipboard = Clipboard::new().unwrap();
|
|
||||||
let image_data = clipboard.get_image().map_err(|err| err.to_string())?;
|
|
||||||
|
|
||||||
Ok(image_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_image_binary(&self) -> Result<Vec<u8>, String> {
|
|
||||||
let mut clipboard = Clipboard::new().unwrap();
|
|
||||||
let image = clipboard.get_image().map_err(|err| err.to_string())?;
|
|
||||||
let tmp_dir = tempfile::Builder::new()
|
|
||||||
.prefix("clipboard-img")
|
|
||||||
.tempdir()
|
|
||||||
.map_err(|err| err.to_string())?;
|
|
||||||
let fname = tmp_dir.path().join("clipboard-img.png");
|
|
||||||
|
|
||||||
let image2: RgbaImage = ImageBuffer::from_raw(
|
|
||||||
image.width.try_into().unwrap(),
|
|
||||||
image.height.try_into().unwrap(),
|
|
||||||
image.bytes.into_owned(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
image2.save(fname.clone()).map_err(|err| err.to_string())?;
|
|
||||||
let mut file = File::open(fname.clone()).unwrap();
|
|
||||||
let mut buffer = vec![];
|
|
||||||
file.read_to_end(&mut buffer).unwrap();
|
|
||||||
Ok(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_image(&self, base64_image: String) -> Result<(), String> {
|
pub fn write_image(&self, base64_image: String) -> Result<(), String> {
|
||||||
let mut clipboard = Clipboard::new().unwrap();
|
let mut clipboard = Clipboard::new().unwrap();
|
||||||
let decoded = general_purpose::STANDARD_NO_PAD
|
let decoded = general_purpose::STANDARD_NO_PAD
|
||||||
@ -373,6 +336,139 @@ impl ClipboardManager {
|
|||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_image(&self) -> Result<String, String> {
|
||||||
|
let mut clipboard = Clipboard::new().unwrap();
|
||||||
|
let image = clipboard.get_image().map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Handle stride alignment
|
||||||
|
let bytes_per_pixel = 4; // RGBA
|
||||||
|
let expected_bytes_per_row = image.width * bytes_per_pixel;
|
||||||
|
let actual_bytes_per_row = image.bytes.len() / image.height;
|
||||||
|
|
||||||
|
let cleaned_bytes = if actual_bytes_per_row != expected_bytes_per_row {
|
||||||
|
// Remove stride padding
|
||||||
|
let mut cleaned = Vec::with_capacity(expected_bytes_per_row * image.height);
|
||||||
|
|
||||||
|
for row in 0..image.height {
|
||||||
|
let row_start = row * actual_bytes_per_row;
|
||||||
|
let row_end = row_start + expected_bytes_per_row;
|
||||||
|
cleaned.extend_from_slice(&image.bytes[row_start..row_end]);
|
||||||
|
}
|
||||||
|
cleaned
|
||||||
|
} else {
|
||||||
|
image.bytes.into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create image from cleaned bytes
|
||||||
|
let image2: RgbaImage = ImageBuffer::from_raw(
|
||||||
|
image.width.try_into().unwrap(),
|
||||||
|
image.height.try_into().unwrap(),
|
||||||
|
cleaned_bytes,
|
||||||
|
)
|
||||||
|
.ok_or_else(|| "Failed to create image from raw bytes".to_string())?;
|
||||||
|
|
||||||
|
// Save to temporary file and encode as base64
|
||||||
|
let tmp_dir = tempfile::Builder::new()
|
||||||
|
.prefix("clipboard-img")
|
||||||
|
.tempdir()
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let fname = tmp_dir.path().join("clipboard-img.png");
|
||||||
|
|
||||||
|
image2.save(&fname).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let mut file = File::open(&fname).map_err(|err| err.to_string())?;
|
||||||
|
let mut buffer = vec![];
|
||||||
|
file
|
||||||
|
.read_to_end(&mut buffer)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let base64_str = general_purpose::STANDARD_NO_PAD.encode(buffer);
|
||||||
|
Ok(base64_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_image_binary(&self) -> Result<ImageData, String> {
|
||||||
|
let mut clipboard = Clipboard::new().unwrap();
|
||||||
|
let image_data = clipboard.get_image().map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Only check for stride alignment on Windows
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
let bytes_per_pixel = 4; // RGBA
|
||||||
|
let expected_bytes_per_row = image_data.width * bytes_per_pixel;
|
||||||
|
let actual_bytes_per_row = image_data.bytes.len() / image_data.height;
|
||||||
|
|
||||||
|
if actual_bytes_per_row != expected_bytes_per_row {
|
||||||
|
// We have stride padding, need to remove it
|
||||||
|
let mut cleaned_bytes = Vec::with_capacity(expected_bytes_per_row * image_data.height);
|
||||||
|
|
||||||
|
for row in 0..image_data.height {
|
||||||
|
let row_start = row * actual_bytes_per_row;
|
||||||
|
let row_end = row_start + expected_bytes_per_row;
|
||||||
|
cleaned_bytes.extend_from_slice(&image_data.bytes[row_start..row_end]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ImageData {
|
||||||
|
width: image_data.width,
|
||||||
|
height: image_data.height,
|
||||||
|
bytes: Cow::Owned(cleaned_bytes),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For macOS, Linux, and Windows without padding, return as-is
|
||||||
|
Ok(image_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function 2: Returns Vec<u8> of PNG file data
|
||||||
|
pub fn read_image_binary(&self) -> Result<Vec<u8>, String> {
|
||||||
|
let mut clipboard = Clipboard::new().unwrap();
|
||||||
|
let image = clipboard.get_image().map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Handle stride alignment
|
||||||
|
let bytes_per_pixel = 4; // RGBA
|
||||||
|
let expected_bytes_per_row = image.width * bytes_per_pixel;
|
||||||
|
let actual_bytes_per_row = image.bytes.len() / image.height;
|
||||||
|
|
||||||
|
let cleaned_bytes = if actual_bytes_per_row != expected_bytes_per_row {
|
||||||
|
// Remove stride padding
|
||||||
|
let mut cleaned = Vec::with_capacity(expected_bytes_per_row * image.height);
|
||||||
|
|
||||||
|
for row in 0..image.height {
|
||||||
|
let row_start = row * actual_bytes_per_row;
|
||||||
|
let row_end = row_start + expected_bytes_per_row;
|
||||||
|
cleaned.extend_from_slice(&image.bytes[row_start..row_end]);
|
||||||
|
}
|
||||||
|
cleaned
|
||||||
|
} else {
|
||||||
|
image.bytes.into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create image from cleaned bytes
|
||||||
|
let image2: RgbaImage = ImageBuffer::from_raw(
|
||||||
|
image.width.try_into().unwrap(),
|
||||||
|
image.height.try_into().unwrap(),
|
||||||
|
cleaned_bytes,
|
||||||
|
)
|
||||||
|
.ok_or_else(|| "Failed to create image from raw bytes".to_string())?;
|
||||||
|
|
||||||
|
// Save to temporary file and read back
|
||||||
|
let tmp_dir = tempfile::Builder::new()
|
||||||
|
.prefix("clipboard-img")
|
||||||
|
.tempdir()
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let fname = tmp_dir.path().join("clipboard-img.png");
|
||||||
|
|
||||||
|
image2.save(&fname).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let mut file = File::open(&fname).map_err(|err| err.to_string())?;
|
||||||
|
let mut buffer = vec![];
|
||||||
|
file
|
||||||
|
.read_to_end(&mut buffer)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the plugin.
|
/// Initializes the plugin.
|
||||||
|
@ -130,11 +130,15 @@ pub fn copy_history_item(app_handle: AppHandle, history_id: String) -> String {
|
|||||||
|
|
||||||
if let (Some(true), Some(false)) = (history_item.is_image, history_item.is_link) {
|
if let (Some(true), Some(false)) = (history_item.is_image, history_item.is_link) {
|
||||||
let base64_image = match history_item.image_path_full_res {
|
let base64_image = match history_item.image_path_full_res {
|
||||||
Some(path) => match std::fs::read(&path) {
|
Some(path) => {
|
||||||
Ok(img_data) => base64::encode(&img_data),
|
// Convert relative path to absolute path
|
||||||
Err(e) => {
|
let absolute_path = crate::db::to_absolute_image_path(&path);
|
||||||
eprintln!("Failed to read image from path: {}", e);
|
match std::fs::read(&absolute_path) {
|
||||||
IMAGE_NOT_FOUND_BASE64.to_string()
|
Ok(img_data) => base64::encode(&img_data),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to read image from path: {}", e);
|
||||||
|
IMAGE_NOT_FOUND_BASE64.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => IMAGE_NOT_FOUND_BASE64.to_string(),
|
None => IMAGE_NOT_FOUND_BASE64.to_string(),
|
||||||
@ -412,11 +416,15 @@ pub async fn copy_clip_item(
|
|||||||
}
|
}
|
||||||
} else if let (Some(true), Some(false)) = (item.is_image, item.is_link) {
|
} else if let (Some(true), Some(false)) = (item.is_image, item.is_link) {
|
||||||
let base64_image = match item.image_path_full_res {
|
let base64_image = match item.image_path_full_res {
|
||||||
Some(path) => match std::fs::read(&path) {
|
Some(path) => {
|
||||||
Ok(img_data) => base64::encode(&img_data),
|
// Convert relative path to absolute path
|
||||||
Err(e) => {
|
let absolute_path = crate::db::to_absolute_image_path(&path);
|
||||||
eprintln!("Failed to read image from path: {}", e);
|
match std::fs::read(&absolute_path) {
|
||||||
IMAGE_NOT_FOUND_BASE64.to_string()
|
Ok(img_data) => base64::encode(&img_data),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to read image from path: {}", e);
|
||||||
|
IMAGE_NOT_FOUND_BASE64.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => IMAGE_NOT_FOUND_BASE64.to_string(),
|
None => IMAGE_NOT_FOUND_BASE64.to_string(),
|
||||||
|
@ -272,7 +272,9 @@ pub async fn save_to_file_history_item(
|
|||||||
} else if let (Some(image_path), Some(true)) =
|
} else if let (Some(image_path), Some(true)) =
|
||||||
(history_item.image_path_full_res, history_item.is_image)
|
(history_item.image_path_full_res, history_item.is_image)
|
||||||
{
|
{
|
||||||
img_data = Some(std::fs::read(&image_path).map_err(|e| e.to_string())?);
|
// Convert relative path to absolute path
|
||||||
|
let absolute_path = crate::db::to_absolute_image_path(&image_path);
|
||||||
|
img_data = Some(std::fs::read(&absolute_path).map_err(|e| e.to_string())?);
|
||||||
} else if let Some(true) = history_item.is_link {
|
} else if let Some(true) = history_item.is_link {
|
||||||
if let Some(image_url) = &history_item.value {
|
if let Some(image_url) = &history_item.value {
|
||||||
let parsed_url = Url::parse(&ensure_url_prefix(image_url)).map_err(|e| e.to_string())?;
|
let parsed_url = Url::parse(&ensure_url_prefix(image_url)).map_err(|e| e.to_string())?;
|
||||||
|
@ -538,7 +538,9 @@ pub async fn save_to_file_clip_item(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let (Some(image_path), Some(true)) = (item.image_path_full_res, item.is_image) {
|
} else if let (Some(image_path), Some(true)) = (item.image_path_full_res, item.is_image) {
|
||||||
img_data = Some(std::fs::read(&image_path).map_err(|e| e.to_string())?);
|
// Convert relative path to absolute path
|
||||||
|
let absolute_path = crate::db::to_absolute_image_path(&image_path);
|
||||||
|
img_data = Some(std::fs::read(&absolute_path).map_err(|e| e.to_string())?);
|
||||||
} else if let Some(true) = item.is_link {
|
} else if let Some(true) = item.is_link {
|
||||||
if let Some(image_url) = &item.value {
|
if let Some(image_url) = &item.value {
|
||||||
let parsed_url = Url::parse(&ensure_url_prefix(image_url)).map_err(|e| e.to_string())?;
|
let parsed_url = Url::parse(&ensure_url_prefix(image_url)).map_err(|e| e.to_string())?;
|
||||||
|
@ -10,3 +10,4 @@ pub(crate) mod security_commands;
|
|||||||
pub(crate) mod shell_commands;
|
pub(crate) mod shell_commands;
|
||||||
pub(crate) mod tabs_commands;
|
pub(crate) mod tabs_commands;
|
||||||
pub(crate) mod translations_commands;
|
pub(crate) mod translations_commands;
|
||||||
|
pub(crate) mod user_settings_command;
|
||||||
|
258
src-tauri/src/commands/user_settings_command.rs
Normal file
258
src-tauri/src/commands/user_settings_command.rs
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
use serde_yaml::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use tauri::command;
|
||||||
|
|
||||||
|
use crate::db::{
|
||||||
|
get_clip_images_dir, get_clipboard_images_dir, get_data_dir, get_db_path, get_default_data_dir,
|
||||||
|
get_default_db_path_string,
|
||||||
|
};
|
||||||
|
use crate::services::user_settings_service::{
|
||||||
|
self as user_settings_service, get_all_settings, get_custom_db_path, get_setting,
|
||||||
|
remove_custom_db_path, remove_setting, set_custom_db_path, set_setting,
|
||||||
|
};
|
||||||
|
use fs_extra::dir::{copy, CopyOptions};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn rollback_moves(items: &[(PathBuf, PathBuf)]) {
|
||||||
|
for (src, dest) in items.iter().rev() {
|
||||||
|
if dest.exists() {
|
||||||
|
if dest.is_dir() {
|
||||||
|
let mut options = CopyOptions::new();
|
||||||
|
options.overwrite = true;
|
||||||
|
let _ = copy(dest, src, &options);
|
||||||
|
let _ = fs::remove_dir_all(dest);
|
||||||
|
} else {
|
||||||
|
let _ = fs::copy(dest, src);
|
||||||
|
let _ = fs::remove_file(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub enum PathStatus {
|
||||||
|
Empty,
|
||||||
|
NotEmpty,
|
||||||
|
IsPastebarDataAndNotEmpty,
|
||||||
|
HasPastebarDataSubfolder,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the status of a given path.
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_check_custom_data_path(path_str: String) -> Result<PathStatus, String> {
|
||||||
|
let path = Path::new(&path_str);
|
||||||
|
if !path.exists() || !path.is_dir() {
|
||||||
|
return Ok(PathStatus::Empty); // Treat non-existent paths as empty for this purpose
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the selected path itself is named "pastebar-data"
|
||||||
|
if path.file_name().and_then(|n| n.to_str()) == Some("pastebar-data") {
|
||||||
|
if path.read_dir().map_err(|e| e.to_string())?.next().is_some() {
|
||||||
|
return Ok(PathStatus::IsPastebarDataAndNotEmpty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if directory contains PasteBar database files directly
|
||||||
|
let has_dev_db = path.join("local.pastebar-db.data").exists();
|
||||||
|
let has_prod_db = path.join("pastebar-db.data").exists();
|
||||||
|
|
||||||
|
if has_dev_db || has_prod_db {
|
||||||
|
return Ok(PathStatus::IsPastebarDataAndNotEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's a "pastebar-data" subfolder in the selected directory
|
||||||
|
let pastebar_data_subfolder = path.join("pastebar-data");
|
||||||
|
if pastebar_data_subfolder.exists() && pastebar_data_subfolder.is_dir() {
|
||||||
|
// Check if the pastebar-data subfolder contains database files
|
||||||
|
let has_dev_db_in_subfolder = pastebar_data_subfolder
|
||||||
|
.join("local.pastebar-db.data")
|
||||||
|
.exists();
|
||||||
|
let has_prod_db_in_subfolder = pastebar_data_subfolder.join("pastebar-db.data").exists();
|
||||||
|
|
||||||
|
if has_dev_db_in_subfolder || has_prod_db_in_subfolder {
|
||||||
|
return Ok(PathStatus::IsPastebarDataAndNotEmpty);
|
||||||
|
} else {
|
||||||
|
return Ok(PathStatus::HasPastebarDataSubfolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.read_dir().map_err(|e| e.to_string())?.next().is_some() {
|
||||||
|
return Ok(PathStatus::NotEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PathStatus::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current `custom_db_path` (if any).
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_get_custom_db_path() -> Option<String> {
|
||||||
|
get_custom_db_path()
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmd_set_custom_db_path is now part of cmd_set_and_relocate_db
|
||||||
|
// cmd_remove_custom_db_path is now part of cmd_revert_to_default_db_location
|
||||||
|
|
||||||
|
/// Creates a directory at the specified path.
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_create_directory(path_str: String) -> Result<(), String> {
|
||||||
|
let path = Path::new(&path_str);
|
||||||
|
fs::create_dir_all(&path)
|
||||||
|
.map_err(|e| format!("Failed to create directory {}: {}", path.display(), e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates if the provided path is a writable directory.
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_validate_custom_db_path(path_str: String) -> Result<bool, String> {
|
||||||
|
let input_path = PathBuf::from(&path_str);
|
||||||
|
|
||||||
|
if input_path
|
||||||
|
.components()
|
||||||
|
.any(|c| matches!(c, std::path::Component::ParentDir))
|
||||||
|
{
|
||||||
|
return Err("Path traversal not allowed".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = if input_path.exists() {
|
||||||
|
input_path
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(|e| format!("Invalid path: {}", e))?
|
||||||
|
} else {
|
||||||
|
input_path
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.exists() && !path.is_dir() {
|
||||||
|
return Err(format!("Path {} is not a directory.", path.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check writability by trying to create a temporary file
|
||||||
|
let temp_file_path = path.join(".tmp_pastebar_writable_check");
|
||||||
|
match fs::File::create(&temp_file_path) {
|
||||||
|
Ok(_) => {
|
||||||
|
fs::remove_file(&temp_file_path)
|
||||||
|
.map_err(|e| format!("Failed to remove temporary check file: {}", e))?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
Err(e) => Err(format!("Directory {} is not writable: {}", path_str, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the custom data path, and moves/copies the data directory.
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_set_and_relocate_data(
|
||||||
|
new_parent_dir_path: String,
|
||||||
|
operation: String,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let current_data_dir = get_data_dir();
|
||||||
|
let new_data_dir = PathBuf::from(&new_parent_dir_path);
|
||||||
|
|
||||||
|
fs::create_dir_all(&new_data_dir)
|
||||||
|
.map_err(|e| format!("Failed to create new data directory: {}", e))?;
|
||||||
|
|
||||||
|
let items_to_relocate = vec!["pastebar-db.data", "clip-images", "clipboard-images"];
|
||||||
|
|
||||||
|
let mut moved_items: Vec<(PathBuf, PathBuf)> = Vec::new();
|
||||||
|
|
||||||
|
for item_name in items_to_relocate {
|
||||||
|
let source_path = current_data_dir.join(item_name);
|
||||||
|
let dest_path = new_data_dir.join(item_name);
|
||||||
|
|
||||||
|
if !source_path.exists() {
|
||||||
|
println!(
|
||||||
|
"Source item {} does not exist, skipping.",
|
||||||
|
source_path.display()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match operation.as_str() {
|
||||||
|
"move" => {
|
||||||
|
if source_path.is_dir() {
|
||||||
|
let mut options = CopyOptions::new();
|
||||||
|
options.overwrite = true;
|
||||||
|
copy(&source_path, &dest_path, &options)
|
||||||
|
.map_err(|e| format!("Failed to copy directory: {}", e))?;
|
||||||
|
if let Err(e) = fs::remove_dir_all(&source_path) {
|
||||||
|
let _ = fs_extra::dir::remove(&dest_path);
|
||||||
|
rollback_moves(&moved_items);
|
||||||
|
return Err(format!("Failed to remove original directory: {}", e));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs::copy(&source_path, &dest_path).map_err(|e| format!("Failed to copy file: {}", e))?;
|
||||||
|
if let Err(e) = fs::remove_file(&source_path) {
|
||||||
|
let _ = fs::remove_file(&dest_path);
|
||||||
|
rollback_moves(&moved_items);
|
||||||
|
return Err(format!("Failed to remove original file: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moved_items.push((source_path.clone(), dest_path.clone()));
|
||||||
|
}
|
||||||
|
"copy" => {
|
||||||
|
if source_path.is_dir() {
|
||||||
|
let mut options = CopyOptions::new();
|
||||||
|
options.overwrite = true;
|
||||||
|
copy(&source_path, &dest_path, &options)
|
||||||
|
.map_err(|e| format!("Failed to copy directory: {}", e))?;
|
||||||
|
} else {
|
||||||
|
fs::copy(&source_path, &dest_path).map_err(|e| format!("Failed to copy file: {}", e))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"none" => {
|
||||||
|
// Do nothing, just switch to the new location
|
||||||
|
}
|
||||||
|
_ => return Err("Invalid operation specified. Use 'move', 'copy', or 'none'.".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user_settings_service::set_custom_db_path(&new_parent_dir_path)?;
|
||||||
|
crate::db::reinitialize_connection_pool();
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"Data successfully {} to {}. Please restart the application.",
|
||||||
|
operation,
|
||||||
|
new_data_dir.display()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the custom data path setting.
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_revert_to_default_data_location() -> Result<String, String> {
|
||||||
|
// Simply remove the custom database path setting
|
||||||
|
remove_custom_db_path()?;
|
||||||
|
crate::db::reinitialize_connection_pool();
|
||||||
|
|
||||||
|
Ok("Custom database location setting removed successfully.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all key-value pairs from the `data` map.
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_get_all_settings() -> HashMap<String, Value> {
|
||||||
|
get_all_settings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a single setting by key (from the `data` map).
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_get_setting(key: String) -> Option<Value> {
|
||||||
|
get_setting(&key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert (or update) a setting in the `data` map.
|
||||||
|
/// `value_yaml` is a string containing valid YAML (e.g. `"true"`, `"42"`, `"some string"`).
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_set_setting(key: String, value_yaml: String) -> Result<(), String> {
|
||||||
|
// If your front end only sends strings,
|
||||||
|
// you could store them directly as `Value::String(value_yaml)`.
|
||||||
|
// But here we parse the YAML so you can handle booleans, numbers, etc.
|
||||||
|
match serde_yaml::from_str::<Value>(&value_yaml) {
|
||||||
|
Ok(val) => set_setting(&key, val),
|
||||||
|
Err(e) => Err(format!("Failed to parse YAML string: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a setting by key from the `data` map.
|
||||||
|
#[command]
|
||||||
|
pub fn cmd_remove_setting(key: String) -> Result<(), String> {
|
||||||
|
remove_setting(&key)
|
||||||
|
}
|
@ -4,12 +4,14 @@ use serde::Serialize;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::RwLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use diesel::connection::SimpleConnection;
|
use diesel::connection::SimpleConnection;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::r2d2 as diesel_r2d2;
|
use diesel::r2d2 as diesel_r2d2;
|
||||||
|
|
||||||
|
use crate::services::user_settings_service::load_user_config;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
|
|
||||||
// use diesel::connection::{set_default_instrumentation, Instrumentation, InstrumentationEvent};
|
// use diesel::connection::{set_default_instrumentation, Instrumentation, InstrumentationEvent};
|
||||||
@ -38,7 +40,7 @@ pub struct ConnectionOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref DB_POOL_CONNECTION: Pool = init_connection_pool();
|
pub static ref DB_POOL_CONNECTION: RwLock<Pool> = RwLock::new(init_connection_pool());
|
||||||
}
|
}
|
||||||
|
|
||||||
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
|
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
|
||||||
@ -61,7 +63,7 @@ impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
|
pub fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
|
||||||
const VERBATIM_PREFIX: &str = r#"\\?\"#;
|
const VERBATIM_PREFIX: &str = r#"\\?\"#;
|
||||||
let p = p.as_ref().display().to_string();
|
let p = p.as_ref().display().to_string();
|
||||||
if p.starts_with(VERBATIM_PREFIX) {
|
if p.starts_with(VERBATIM_PREFIX) {
|
||||||
@ -91,6 +93,12 @@ fn init_connection_pool() -> Pool {
|
|||||||
.expect("Failed to create db pool.")
|
.expect("Failed to create db pool.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reinitialize_connection_pool() {
|
||||||
|
let new_pool = init_connection_pool();
|
||||||
|
let mut pool_lock = DB_POOL_CONNECTION.write().unwrap();
|
||||||
|
*pool_lock = new_pool;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(app: &mut tauri::App) {
|
pub fn init(app: &mut tauri::App) {
|
||||||
let config = app.config().clone();
|
let config = app.config().clone();
|
||||||
|
|
||||||
@ -181,6 +189,8 @@ pub fn establish_pool_db_connection(
|
|||||||
});
|
});
|
||||||
|
|
||||||
DB_POOL_CONNECTION
|
DB_POOL_CONNECTION
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
.get()
|
.get()
|
||||||
.unwrap_or_else(|_| panic!("Error connecting to db pool"))
|
.unwrap_or_else(|_| panic!("Error connecting to db pool"))
|
||||||
}
|
}
|
||||||
@ -216,40 +226,163 @@ fn db_file_exists() -> bool {
|
|||||||
Path::new(&db_path).exists()
|
Path::new(&db_path).exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_db_path() -> String {
|
/// Returns the base directory for application data.
|
||||||
if cfg!(debug_assertions) {
|
/// This will be a `pastebar-data` subdirectory if a custom path is set.
|
||||||
let app_dir = APP_CONSTANTS.get().unwrap().app_dev_data_dir.clone();
|
pub fn get_data_dir() -> PathBuf {
|
||||||
let path = if cfg!(target_os = "macos") {
|
let user_config = load_user_config();
|
||||||
format!(
|
if let Some(custom_path_str) = user_config.custom_db_path {
|
||||||
"{}/local.pastebar-db.data",
|
PathBuf::from(custom_path_str)
|
||||||
adjust_canonicalization(app_dir)
|
|
||||||
)
|
|
||||||
} else if cfg!(target_os = "windows") {
|
|
||||||
format!(
|
|
||||||
"{}\\local.pastebar-db.data",
|
|
||||||
adjust_canonicalization(app_dir)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"{}/local.pastebar-db.data",
|
|
||||||
adjust_canonicalization(app_dir)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
path
|
|
||||||
} else {
|
} else {
|
||||||
|
get_default_data_dir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the default application data directory.
|
||||||
|
pub fn get_default_data_dir() -> PathBuf {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
APP_CONSTANTS.get().unwrap().app_dev_data_dir.clone()
|
||||||
|
} else {
|
||||||
|
APP_CONSTANTS.get().unwrap().app_data_dir.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_db_path() -> String {
|
||||||
|
let filename = if cfg!(debug_assertions) {
|
||||||
|
"local.pastebar-db.data"
|
||||||
|
} else {
|
||||||
|
"pastebar-db.data"
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_path = get_data_dir().join(filename);
|
||||||
|
db_path.to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to the `clip-images` directory.
|
||||||
|
pub fn get_clip_images_dir() -> PathBuf {
|
||||||
|
get_data_dir().join("clip-images")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to the `clipboard-images` directory.
|
||||||
|
pub fn get_clipboard_images_dir() -> PathBuf {
|
||||||
|
get_data_dir().join("clipboard-images")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the default database file path as a string.
|
||||||
|
pub fn get_default_db_path_string() -> String {
|
||||||
|
let db_path = get_default_data_dir().join("pastebar-db.data");
|
||||||
|
db_path.to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an absolute image path to a relative path with {{base_folder}} placeholder
|
||||||
|
pub fn to_relative_image_path(absolute_path: &str) -> String {
|
||||||
|
let data_dir = get_data_dir();
|
||||||
|
let data_dir_str = data_dir.to_string_lossy();
|
||||||
|
|
||||||
|
if absolute_path.starts_with(&data_dir_str.as_ref()) {
|
||||||
|
// Remove the data directory prefix and replace with placeholder
|
||||||
|
let relative_path = absolute_path
|
||||||
|
.strip_prefix(&data_dir_str.as_ref())
|
||||||
|
.unwrap_or(absolute_path)
|
||||||
|
.trim_start_matches('/')
|
||||||
|
.trim_start_matches('\\');
|
||||||
|
format!("{{{{base_folder}}}}/{}", relative_path)
|
||||||
|
} else {
|
||||||
|
// If path doesn't start with data dir, return as is
|
||||||
|
absolute_path.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a relative image path with {{base_folder}} placeholder to absolute path
|
||||||
|
pub fn to_absolute_image_path(relative_path: &str) -> String {
|
||||||
|
if relative_path.starts_with("{{base_folder}}") {
|
||||||
|
let data_dir = get_data_dir();
|
||||||
|
let path_without_placeholder = relative_path
|
||||||
|
.strip_prefix("{{base_folder}}")
|
||||||
|
.unwrap_or(relative_path)
|
||||||
|
.trim_start_matches('/')
|
||||||
|
.trim_start_matches('\\');
|
||||||
|
data_dir
|
||||||
|
.join(path_without_placeholder)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
} else {
|
||||||
|
// If path doesn't have placeholder, return as is
|
||||||
|
relative_path.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_access_or_create(db_path: &str) -> bool {
|
||||||
|
let path = std::path::Path::new(db_path);
|
||||||
|
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
if let Err(e) = std::fs::create_dir_all(parent) {
|
||||||
|
eprintln!(
|
||||||
|
"Failed to create parent directory '{}': {}",
|
||||||
|
parent.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match std::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)
|
||||||
|
{
|
||||||
|
Ok(_file) => true,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to open custom DB path '{}': {}", db_path, e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_file_path() -> PathBuf {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
let app_dir = APP_CONSTANTS
|
||||||
|
.get()
|
||||||
|
.expect("APP_CONSTANTS not initialized")
|
||||||
|
.app_dev_data_dir
|
||||||
|
.clone();
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
PathBuf::from(format!(
|
||||||
|
"{}/pastebar_settings.yaml",
|
||||||
|
adjust_canonicalization(app_dir)
|
||||||
|
))
|
||||||
|
} else if cfg!(target_os = "windows") {
|
||||||
|
PathBuf::from(format!(
|
||||||
|
"{}\\pastebar_settings.yaml",
|
||||||
|
adjust_canonicalization(app_dir)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
PathBuf::from(format!(
|
||||||
|
"{}/pastebar_settings.yaml",
|
||||||
|
adjust_canonicalization(app_dir)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Release mode
|
||||||
let app_data_dir = APP_CONSTANTS.get().unwrap().app_data_dir.clone();
|
let app_data_dir = APP_CONSTANTS.get().unwrap().app_data_dir.clone();
|
||||||
let data_dir = app_data_dir.as_path();
|
let data_dir = app_data_dir.as_path();
|
||||||
|
|
||||||
let path = if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
format!("{}/pastebar-db.data", adjust_canonicalization(data_dir))
|
PathBuf::from(format!(
|
||||||
|
"{}/pastebar_settings.yaml",
|
||||||
|
adjust_canonicalization(data_dir)
|
||||||
|
))
|
||||||
} else if cfg!(target_os = "windows") {
|
} else if cfg!(target_os = "windows") {
|
||||||
format!("{}\\pastebar-db.data", adjust_canonicalization(data_dir))
|
PathBuf::from(format!(
|
||||||
|
"{}\\pastebar_settings.yaml",
|
||||||
|
adjust_canonicalization(data_dir)
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
format!("{}/pastebar-db.data", adjust_canonicalization(data_dir))
|
PathBuf::from(format!(
|
||||||
};
|
"{}/pastebar_settings.yaml",
|
||||||
|
adjust_canonicalization(data_dir)
|
||||||
path
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,8 @@ use commands::security_commands;
|
|||||||
use commands::shell_commands;
|
use commands::shell_commands;
|
||||||
use commands::tabs_commands;
|
use commands::tabs_commands;
|
||||||
use commands::translations_commands;
|
use commands::translations_commands;
|
||||||
|
use commands::user_settings_command;
|
||||||
|
|
||||||
use db::AppConstants;
|
use db::AppConstants;
|
||||||
use mouse_position::mouse_position::Mouse;
|
use mouse_position::mouse_position::Mouse;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -739,7 +741,9 @@ async fn main() {
|
|||||||
None => return (),
|
None => return (),
|
||||||
};
|
};
|
||||||
|
|
||||||
let img_data = std::fs::read(&image_path).expect("Failed to read image from path");
|
// Convert relative path to absolute path
|
||||||
|
let absolute_path = db::to_absolute_image_path(&image_path);
|
||||||
|
let img_data = std::fs::read(&absolute_path).expect("Failed to read image from path");
|
||||||
let base64_image = base64::encode(&img_data);
|
let base64_image = base64::encode(&img_data);
|
||||||
|
|
||||||
write_image_to_clipboard(base64_image).expect("Failed to write image to clipboard");
|
write_image_to_clipboard(base64_image).expect("Failed to write image to clipboard");
|
||||||
@ -832,7 +836,9 @@ async fn main() {
|
|||||||
None => return (),
|
None => return (),
|
||||||
};
|
};
|
||||||
|
|
||||||
let img_data = std::fs::read(&image_path).expect("Failed to read image from path");
|
// Convert relative path to absolute path
|
||||||
|
let absolute_path = db::to_absolute_image_path(&image_path);
|
||||||
|
let img_data = std::fs::read(&absolute_path).expect("Failed to read image from path");
|
||||||
let base64_image = base64::encode(&img_data);
|
let base64_image = base64::encode(&img_data);
|
||||||
|
|
||||||
write_image_to_clipboard(base64_image).expect("Failed to write image to clipboard");
|
write_image_to_clipboard(base64_image).expect("Failed to write image to clipboard");
|
||||||
@ -1189,6 +1195,18 @@ async fn main() {
|
|||||||
security_commands::verify_os_password,
|
security_commands::verify_os_password,
|
||||||
security_commands::delete_os_password,
|
security_commands::delete_os_password,
|
||||||
security_commands::get_stored_os_password,
|
security_commands::get_stored_os_password,
|
||||||
|
user_settings_command::cmd_get_custom_db_path,
|
||||||
|
// user_settings_command::cmd_set_custom_db_path, // Replaced by cmd_set_and_relocate_db
|
||||||
|
// user_settings_command::cmd_remove_custom_db_path, // Replaced by cmd_revert_to_default_db_location
|
||||||
|
user_settings_command::cmd_create_directory,
|
||||||
|
user_settings_command::cmd_validate_custom_db_path,
|
||||||
|
user_settings_command::cmd_check_custom_data_path,
|
||||||
|
user_settings_command::cmd_set_and_relocate_data,
|
||||||
|
user_settings_command::cmd_revert_to_default_data_location,
|
||||||
|
user_settings_command::cmd_get_all_settings,
|
||||||
|
user_settings_command::cmd_get_setting,
|
||||||
|
user_settings_command::cmd_set_setting,
|
||||||
|
user_settings_command::cmd_remove_setting,
|
||||||
open_osx_accessibility_preferences,
|
open_osx_accessibility_preferences,
|
||||||
check_osx_accessibility_preferences,
|
check_osx_accessibility_preferences,
|
||||||
open_path_or_app,
|
open_path_or_app,
|
||||||
|
@ -331,3 +331,21 @@ pub struct Tabs {
|
|||||||
pub tab_layout_split: i32,
|
pub tab_layout_split: i32,
|
||||||
pub tab_is_protected: bool,
|
pub tab_is_protected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
/// Transforms the image_path_full_res field from relative to absolute path for frontend consumption
|
||||||
|
pub fn transform_image_path_for_frontend(&mut self) {
|
||||||
|
if let Some(ref mut path) = self.image_path_full_res {
|
||||||
|
*path = crate::db::to_absolute_image_path(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipboardHistory {
|
||||||
|
/// Transforms the image_path_full_res field from relative to absolute path for frontend consumption
|
||||||
|
pub fn transform_image_path_for_frontend(&mut self) {
|
||||||
|
if let Some(ref mut path) = self.image_path_full_res {
|
||||||
|
*path = crate::db::to_absolute_image_path(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -361,9 +361,15 @@ pub fn get_active_collection_with_menu_items() -> Result<CollectionWithItems, Er
|
|||||||
))
|
))
|
||||||
.load::<AssociatedMenu>(connection)?;
|
.load::<AssociatedMenu>(connection)?;
|
||||||
|
|
||||||
|
// Transform image paths for frontend consumption
|
||||||
|
let mut transformed_items = associated_items;
|
||||||
|
for item in &mut transformed_items {
|
||||||
|
item.transform_image_path_for_frontend();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(CollectionWithItems {
|
Ok(CollectionWithItems {
|
||||||
collection,
|
collection,
|
||||||
items: associated_items,
|
items: transformed_items,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,9 +453,15 @@ pub fn get_active_collection_with_clips() -> Result<CollectionWithClips, Error>
|
|||||||
))
|
))
|
||||||
.load::<AssociatedClips>(connection);
|
.load::<AssociatedClips>(connection);
|
||||||
|
|
||||||
|
// Transform image paths for frontend consumption
|
||||||
|
let mut transformed_clips = associated_clips?;
|
||||||
|
for clip in &mut transformed_clips {
|
||||||
|
clip.transform_image_path_for_frontend();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(CollectionWithClips {
|
Ok(CollectionWithClips {
|
||||||
collection,
|
collection,
|
||||||
clips: associated_clips?,
|
clips: transformed_clips,
|
||||||
tabs: collection_tabs,
|
tabs: collection_tabs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -740,3 +752,21 @@ pub fn create_default_board_item(
|
|||||||
|
|
||||||
Ok(new_item.item_id)
|
Ok(new_item.item_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AssociatedClips {
|
||||||
|
/// Transforms the image_path_full_res field from relative to absolute path for frontend consumption
|
||||||
|
pub fn transform_image_path_for_frontend(&mut self) {
|
||||||
|
if let Some(ref mut path) = self.image_path_full_res {
|
||||||
|
*path = crate::db::to_absolute_image_path(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssociatedMenu {
|
||||||
|
/// Transforms the image_path_full_res field from relative to absolute path for frontend consumption
|
||||||
|
pub fn transform_image_path_for_frontend(&mut self) {
|
||||||
|
if let Some(ref mut path) = self.image_path_full_res {
|
||||||
|
*path = crate::db::to_absolute_image_path(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use crate::db::APP_CONSTANTS;
|
use crate::db::{self, APP_CONSTANTS};
|
||||||
use crate::schema::clipboard_history;
|
use crate::schema::clipboard_history;
|
||||||
use crate::schema::clipboard_history::dsl::*;
|
use crate::schema::clipboard_history::dsl::*;
|
||||||
use crate::schema::link_metadata;
|
use crate::schema::link_metadata;
|
||||||
@ -187,6 +187,13 @@ impl ClipboardHistoryWithMetaData {
|
|||||||
process_history_item(&mut history_with_metadata, auto_mask_words_list);
|
process_history_item(&mut history_with_metadata, auto_mask_words_list);
|
||||||
history_with_metadata
|
history_with_metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transforms the image_path_full_res field from relative to absolute path for frontend consumption
|
||||||
|
pub fn transform_image_path_for_frontend(&mut self) {
|
||||||
|
if let Some(ref mut path) = self.image_path_full_res {
|
||||||
|
*path = crate::db::to_absolute_image_path(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_source_apps_list() -> Result<Vec<Option<String>>, Error> {
|
pub fn get_source_apps_list() -> Result<Vec<Option<String>>, Error> {
|
||||||
@ -226,11 +233,7 @@ pub fn add_clipboard_history_from_image(
|
|||||||
let _history_id = nanoid!().to_string();
|
let _history_id = nanoid!().to_string();
|
||||||
let folder_name = &_history_id[..3];
|
let folder_name = &_history_id[..3];
|
||||||
|
|
||||||
let base_dir = if cfg!(debug_assertions) {
|
let base_dir = db::get_clipboard_images_dir();
|
||||||
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
|
|
||||||
} else {
|
|
||||||
&APP_CONSTANTS.get().unwrap().app_data_dir
|
|
||||||
};
|
|
||||||
|
|
||||||
let (_image_width, _image_height) = image.dimensions();
|
let (_image_width, _image_height) = image.dimensions();
|
||||||
|
|
||||||
@ -279,16 +282,21 @@ pub fn add_clipboard_history_from_image(
|
|||||||
))
|
))
|
||||||
.execute(connection);
|
.execute(connection);
|
||||||
} else {
|
} else {
|
||||||
let folder_path = base_dir.join("clipboard-images").join(folder_name);
|
let folder_path = base_dir.join(folder_name);
|
||||||
ensure_dir_exists(&folder_path);
|
ensure_dir_exists(&folder_path);
|
||||||
|
|
||||||
let image_file_name = folder_path.join(format!("{}.png", &_history_id));
|
let image_file_name = folder_path.join(format!("{}.png", &_history_id));
|
||||||
let _ = image.save(&image_file_name);
|
let _ = image.save(&image_file_name);
|
||||||
|
|
||||||
|
// Convert absolute path to relative path before storing
|
||||||
|
let relative_image_path = image_file_name.to_str()
|
||||||
|
.map(|path| db::to_relative_image_path(path))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let new_history = create_new_history(
|
let new_history = create_new_history(
|
||||||
_history_id,
|
_history_id,
|
||||||
_image_data_low_res,
|
_image_data_low_res,
|
||||||
image_file_name,
|
PathBuf::from(relative_image_path),
|
||||||
image_hash_string,
|
image_hash_string,
|
||||||
_preview_height.try_into().unwrap(),
|
_preview_height.try_into().unwrap(),
|
||||||
_image_height.try_into().unwrap(),
|
_image_height.try_into().unwrap(),
|
||||||
@ -565,19 +573,26 @@ pub fn get_pinned_clipboard_histories(
|
|||||||
auto_mask_words_list: Vec<String>,
|
auto_mask_words_list: Vec<String>,
|
||||||
) -> Result<Vec<ClipboardHistoryWithMetaData>, Error> {
|
) -> Result<Vec<ClipboardHistoryWithMetaData>, Error> {
|
||||||
let connection = &mut establish_pool_db_connection();
|
let connection = &mut establish_pool_db_connection();
|
||||||
let histories = clipboard_history
|
let query_results = clipboard_history
|
||||||
.left_join(
|
.left_join(
|
||||||
link_metadata_dsl.on(link_metadata::history_id.eq(clipboard_history::history_id.nullable())),
|
link_metadata_dsl.on(link_metadata::history_id.eq(clipboard_history::history_id.nullable())),
|
||||||
)
|
)
|
||||||
.limit(20)
|
.limit(20)
|
||||||
.filter(is_pinned.eq(true))
|
.filter(is_pinned.eq(true))
|
||||||
.load::<(ClipboardHistory, Option<LinkMetadata>)>(connection)?
|
.load::<(ClipboardHistory, Option<LinkMetadata>)>(connection)?;
|
||||||
|
|
||||||
|
let mut histories: Vec<ClipboardHistoryWithMetaData> = query_results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(history, link_metadata)| {
|
.map(|(history, link_metadata)| {
|
||||||
ClipboardHistoryWithMetaData::from(history, link_metadata, &auto_mask_words_list)
|
ClipboardHistoryWithMetaData::from(history, link_metadata, &auto_mask_words_list)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Transform image paths for frontend consumption
|
||||||
|
for history in &mut histories {
|
||||||
|
history.transform_image_path_for_frontend();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(histories)
|
Ok(histories)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,30 +606,42 @@ pub fn get_clipboard_histories(
|
|||||||
|
|
||||||
let connection = &mut establish_pool_db_connection();
|
let connection = &mut establish_pool_db_connection();
|
||||||
|
|
||||||
let histories: Vec<ClipboardHistoryWithMetaData> = clipboard_history
|
let query_results = clipboard_history
|
||||||
.left_join(
|
.left_join(
|
||||||
link_metadata_dsl.on(link_metadata::history_id.eq(clipboard_history::history_id.nullable())),
|
link_metadata_dsl.on(link_metadata::history_id.eq(clipboard_history::history_id.nullable())),
|
||||||
)
|
)
|
||||||
.order(updated_date.desc())
|
.order(updated_date.desc())
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.load::<(ClipboardHistory, Option<LinkMetadata>)>(connection)?
|
.load::<(ClipboardHistory, Option<LinkMetadata>)>(connection)?;
|
||||||
|
|
||||||
|
let mut histories: Vec<ClipboardHistoryWithMetaData> = query_results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(history, link_metadata)| {
|
.map(|(history, link_metadata)| {
|
||||||
ClipboardHistoryWithMetaData::from(history, link_metadata, &auto_mask_words_list)
|
ClipboardHistoryWithMetaData::from(history, link_metadata, &auto_mask_words_list)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Transform image paths for frontend consumption
|
||||||
|
for history in &mut histories {
|
||||||
|
history.transform_image_path_for_frontend();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(histories)
|
Ok(histories)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clipboard_history_by_id(history_id_value: &String) -> Option<ClipboardHistory> {
|
pub fn get_clipboard_history_by_id(history_id_value: &String) -> Option<ClipboardHistory> {
|
||||||
let connection = &mut establish_pool_db_connection();
|
let connection = &mut establish_pool_db_connection();
|
||||||
|
|
||||||
clipboard_history
|
let mut result = clipboard_history
|
||||||
.find(history_id_value)
|
.find(history_id_value)
|
||||||
.first::<ClipboardHistory>(connection)
|
.first::<ClipboardHistory>(connection)
|
||||||
.ok()
|
.ok()?;
|
||||||
|
|
||||||
|
// Transform image path for frontend consumption
|
||||||
|
result.transform_image_path_for_frontend();
|
||||||
|
|
||||||
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_clipboard_history_older_than(age: Duration) -> Result<String, diesel::result::Error> {
|
pub fn delete_clipboard_history_older_than(age: Duration) -> Result<String, diesel::result::Error> {
|
||||||
@ -712,13 +739,7 @@ pub fn delete_recent_clipboard_history(
|
|||||||
pub fn delete_all_clipboard_histories() -> String {
|
pub fn delete_all_clipboard_histories() -> String {
|
||||||
let connection = &mut establish_pool_db_connection();
|
let connection = &mut establish_pool_db_connection();
|
||||||
|
|
||||||
let base_dir = if cfg!(debug_assertions) {
|
let folder_path = db::get_clipboard_images_dir();
|
||||||
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
|
|
||||||
} else {
|
|
||||||
&APP_CONSTANTS.get().unwrap().app_data_dir
|
|
||||||
};
|
|
||||||
|
|
||||||
let folder_path = base_dir.join("clipboard-images");
|
|
||||||
|
|
||||||
let _ = remove_dir_if_exists(&folder_path);
|
let _ = remove_dir_if_exists(&folder_path);
|
||||||
|
|
||||||
@ -877,15 +898,22 @@ pub fn find_clipboard_histories_by_value_or_filter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let histories = query_builder
|
let query_results = query_builder
|
||||||
.order(updated_date.desc())
|
.order(updated_date.desc())
|
||||||
.limit(max_results)
|
.limit(max_results)
|
||||||
.load::<(ClipboardHistory, Option<LinkMetadata>)>(connection)?
|
.load::<(ClipboardHistory, Option<LinkMetadata>)>(connection)?;
|
||||||
|
|
||||||
|
let mut histories: Vec<ClipboardHistoryWithMetaData> = query_results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(history, link_metadata)| {
|
.map(|(history, link_metadata)| {
|
||||||
ClipboardHistoryWithMetaData::from(history, link_metadata, &auto_mask_words_list)
|
ClipboardHistoryWithMetaData::from(history, link_metadata, &auto_mask_words_list)
|
||||||
})
|
})
|
||||||
.collect::<Vec<ClipboardHistoryWithMetaData>>();
|
.collect();
|
||||||
|
|
||||||
|
// Transform image paths for frontend consumption
|
||||||
|
for history in &mut histories {
|
||||||
|
history.transform_image_path_for_frontend();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(histories)
|
Ok(histories)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::db::APP_CONSTANTS;
|
use crate::db::{self, APP_CONSTANTS};
|
||||||
use crate::models::models::UpdatedItemData;
|
use crate::models::models::UpdatedItemData;
|
||||||
use crate::models::Item;
|
use crate::models::Item;
|
||||||
use crate::services::utils::debug_output;
|
use crate::services::utils::debug_output;
|
||||||
@ -102,7 +102,11 @@ pub fn get_item_by_id(item_id: String) -> Result<Item, String> {
|
|||||||
.first::<Item>(connection);
|
.first::<Item>(connection);
|
||||||
|
|
||||||
match found_item {
|
match found_item {
|
||||||
Ok(item) => Ok(item),
|
Ok(mut item) => {
|
||||||
|
// Transform image path for frontend consumption
|
||||||
|
item.transform_image_path_for_frontend();
|
||||||
|
Ok(item)
|
||||||
|
},
|
||||||
Err(e) => Err(format!("Item not found: {}", e)),
|
Err(e) => Err(format!("Item not found: {}", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,13 +513,7 @@ pub fn add_image_to_item(item_id: &str, image_full_path: &str) -> Result<String,
|
|||||||
|
|
||||||
let is_svg = extension == "svg";
|
let is_svg = extension == "svg";
|
||||||
|
|
||||||
let base_dir = if cfg!(debug_assertions) {
|
let folder_path = db::get_clip_images_dir().join(&item_id[..3]);
|
||||||
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
|
|
||||||
} else {
|
|
||||||
&APP_CONSTANTS.get().unwrap().app_data_dir
|
|
||||||
};
|
|
||||||
|
|
||||||
let folder_path = base_dir.join("clip-images").join(&item_id[..3]);
|
|
||||||
ensure_dir_exists(&folder_path);
|
ensure_dir_exists(&folder_path);
|
||||||
let new_image_path = folder_path.join(format!("{}.{}", item_id, extension));
|
let new_image_path = folder_path.join(format!("{}.{}", item_id, extension));
|
||||||
|
|
||||||
@ -544,9 +542,13 @@ pub fn add_image_to_item(item_id: &str, image_full_path: &str) -> Result<String,
|
|||||||
|
|
||||||
let connection = &mut establish_pool_db_connection();
|
let connection = &mut establish_pool_db_connection();
|
||||||
|
|
||||||
|
// Convert absolute path to relative path before storing
|
||||||
|
let relative_image_path = new_image_path.to_str()
|
||||||
|
.map(|path| db::to_relative_image_path(path));
|
||||||
|
|
||||||
diesel::update(items.find(item_id))
|
diesel::update(items.find(item_id))
|
||||||
.set((
|
.set((
|
||||||
image_path_full_res.eq(new_image_path.to_str()),
|
image_path_full_res.eq(relative_image_path),
|
||||||
is_image.eq(true),
|
is_image.eq(true),
|
||||||
image_height.eq(_image_height),
|
image_height.eq(_image_height),
|
||||||
image_width.eq(_image_width),
|
image_width.eq(_image_width),
|
||||||
@ -636,13 +638,7 @@ pub fn save_item_image_from_history_item(
|
|||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let folder_name = &item_id[..3];
|
let folder_name = &item_id[..3];
|
||||||
|
|
||||||
let base_dir = if cfg!(debug_assertions) {
|
let folder_path = db::get_clip_images_dir().join(folder_name);
|
||||||
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
|
|
||||||
} else {
|
|
||||||
&APP_CONSTANTS.get().unwrap().app_data_dir
|
|
||||||
};
|
|
||||||
|
|
||||||
let folder_path = base_dir.join("clip-images").join(folder_name);
|
|
||||||
ensure_dir_exists(&folder_path);
|
ensure_dir_exists(&folder_path);
|
||||||
|
|
||||||
let clip_image_file_name = folder_path.join(format!("{}.png", &item_id));
|
let clip_image_file_name = folder_path.join(format!("{}.png", &item_id));
|
||||||
@ -664,7 +660,11 @@ pub fn save_item_image_from_history_item(
|
|||||||
e.to_string()
|
e.to_string()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(clip_image_file_name.to_str().unwrap().to_string())
|
// Return relative path instead of absolute path
|
||||||
|
let relative_path = clip_image_file_name.to_str()
|
||||||
|
.map(|path| db::to_relative_image_path(path))
|
||||||
|
.unwrap_or_default();
|
||||||
|
Ok(relative_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload_image_file_to_item_id(
|
pub fn upload_image_file_to_item_id(
|
||||||
@ -684,13 +684,7 @@ pub fn upload_image_file_to_item_id(
|
|||||||
|
|
||||||
let file_name = format!("{}.{}", item_id, extension);
|
let file_name = format!("{}.{}", item_id, extension);
|
||||||
|
|
||||||
let base_dir = if cfg!(debug_assertions) {
|
let folder_path = db::get_clip_images_dir().join(&item_id[..3]);
|
||||||
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
|
|
||||||
} else {
|
|
||||||
&APP_CONSTANTS.get().unwrap().app_data_dir
|
|
||||||
};
|
|
||||||
|
|
||||||
let folder_path = base_dir.join("clip-images").join(&item_id[..3]);
|
|
||||||
ensure_dir_exists(&folder_path);
|
ensure_dir_exists(&folder_path);
|
||||||
let image_path = folder_path.join(&file_name);
|
let image_path = folder_path.join(&file_name);
|
||||||
|
|
||||||
@ -723,9 +717,13 @@ pub fn upload_image_file_to_item_id(
|
|||||||
let _image_hash_string = chrono::Utc::now().timestamp_millis().to_string();
|
let _image_hash_string = chrono::Utc::now().timestamp_millis().to_string();
|
||||||
let connection = &mut establish_pool_db_connection();
|
let connection = &mut establish_pool_db_connection();
|
||||||
|
|
||||||
|
// Convert absolute path to relative path before storing
|
||||||
|
let relative_image_path = image_path.to_str()
|
||||||
|
.map(|path| db::to_relative_image_path(path));
|
||||||
|
|
||||||
let _ = diesel::update(items.find(item_id))
|
let _ = diesel::update(items.find(item_id))
|
||||||
.set((
|
.set((
|
||||||
image_path_full_res.eq(image_path.to_str()),
|
image_path_full_res.eq(relative_image_path),
|
||||||
image_data_url.eq(_image_data_url),
|
image_data_url.eq(_image_data_url),
|
||||||
image_height.eq(_image_height),
|
image_height.eq(_image_height),
|
||||||
image_width.eq(_image_width),
|
image_width.eq(_image_width),
|
||||||
|
@ -7,4 +7,5 @@ pub mod settings_service;
|
|||||||
pub mod shell_service;
|
pub mod shell_service;
|
||||||
pub mod tabs_service;
|
pub mod tabs_service;
|
||||||
pub mod translations;
|
pub mod translations;
|
||||||
|
pub mod user_settings_service;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
@ -39,7 +39,11 @@ pub fn insert_or_update_setting_by_name(
|
|||||||
setting: &Setting,
|
setting: &Setting,
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let connection = &mut DB_POOL_CONNECTION.get().unwrap();
|
let connection = &mut DB_POOL_CONNECTION
|
||||||
|
.read()
|
||||||
|
.expect("Failed to acquire read lock on DB_POOL_CONNECTION")
|
||||||
|
.get()
|
||||||
|
.expect("Failed to get a database connection from the pool");
|
||||||
|
|
||||||
match settings
|
match settings
|
||||||
.filter(name.eq(&setting.name))
|
.filter(name.eq(&setting.name))
|
||||||
|
99
src-tauri/src/services/user_settings_service.rs
Normal file
99
src-tauri/src/services/user_settings_service.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_yaml;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::db::get_config_file_path;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct UserConfig {
|
||||||
|
/// The custom DB path, if user specified one.
|
||||||
|
pub custom_db_path: Option<String>,
|
||||||
|
|
||||||
|
/// General-purpose key-value settings.
|
||||||
|
#[serde(default)]
|
||||||
|
pub data: HashMap<String, serde_yaml::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_user_config() -> UserConfig {
|
||||||
|
let path = get_config_file_path();
|
||||||
|
if !path.exists() {
|
||||||
|
return UserConfig::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
match std::fs::read_to_string(&path) {
|
||||||
|
Ok(contents) => match serde_yaml::from_str::<UserConfig>(&contents) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error parsing user config YAML: {:#}", e);
|
||||||
|
UserConfig::default()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error reading user config file: {:#}", e);
|
||||||
|
UserConfig::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save the `UserConfig` back to `pastebar_settings.yaml`.
|
||||||
|
pub fn save_user_config(cfg: &UserConfig) -> Result<(), String> {
|
||||||
|
let path = get_config_file_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)
|
||||||
|
.map_err(|e| format!("Failed to create config directory: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml_str =
|
||||||
|
serde_yaml::to_string(cfg).map_err(|e| format!("Failed to serialize config to YAML: {}", e))?;
|
||||||
|
std::fs::write(&path, yaml_str).map_err(|e| format!("Failed to write config file: {}", e))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Custom DB Path Methods
|
||||||
|
// ===========================
|
||||||
|
|
||||||
|
/// Get the current `custom_db_path` (if any).
|
||||||
|
pub fn get_custom_db_path() -> Option<String> {
|
||||||
|
load_user_config().custom_db_path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert or update the `custom_db_path`.
|
||||||
|
pub fn set_custom_db_path(new_path: &str) -> Result<(), String> {
|
||||||
|
let mut config = load_user_config();
|
||||||
|
config.custom_db_path = Some(new_path.to_string());
|
||||||
|
save_user_config(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove (clear) the `custom_db_path`.
|
||||||
|
pub fn remove_custom_db_path() -> Result<(), String> {
|
||||||
|
let mut config = load_user_config();
|
||||||
|
config.custom_db_path = None;
|
||||||
|
save_user_config(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Key–Value data Methods
|
||||||
|
// ===========================
|
||||||
|
|
||||||
|
pub fn get_setting(key: &str) -> Option<serde_yaml::Value> {
|
||||||
|
let config = load_user_config();
|
||||||
|
config.data.get(key).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_setting(key: &str, value: serde_yaml::Value) -> Result<(), String> {
|
||||||
|
let mut config = load_user_config();
|
||||||
|
config.data.insert(key.to_string(), value);
|
||||||
|
save_user_config(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_setting(key: &str) -> Result<(), String> {
|
||||||
|
let mut config = load_user_config();
|
||||||
|
config.data.remove(key);
|
||||||
|
save_user_config(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_settings() -> HashMap<String, serde_yaml::Value> {
|
||||||
|
load_user_config().data
|
||||||
|
}
|
@ -39,11 +39,32 @@ impl<R: Runtime> WindowToolBar for Window<R> {
|
|||||||
let miniaturize = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
let miniaturize = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||||
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||||
|
|
||||||
window
|
// Check if standard buttons exist
|
||||||
.standardWindowButton_(NSWindowButton::NSWindowDocumentIconButton)
|
if close.is_null() || miniaturize.is_null() || zoom.is_null() {
|
||||||
.setCanHide_(cocoa::base::YES);
|
eprintln!("Warning: Window buttons are null, skipping traffic light positioning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let title_bar_container_view = close.superview().superview();
|
let document_icon = window.standardWindowButton_(NSWindowButton::NSWindowDocumentIconButton);
|
||||||
|
if !document_icon.is_null() {
|
||||||
|
let mut doc_rect: NSRect = NSView::frame(document_icon);
|
||||||
|
doc_rect.origin.x = -200.0; // Move it off-screen
|
||||||
|
// document_icon.setFrameOrigin(doc_rect.origin);
|
||||||
|
// document_icon.setCanHide_(cocoa::base::YES);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check superviews exist
|
||||||
|
let superview = close.superview();
|
||||||
|
if superview.is_null() {
|
||||||
|
eprintln!("Warning: Close button superview is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title_bar_container_view = superview.superview();
|
||||||
|
if title_bar_container_view.is_null() {
|
||||||
|
eprintln!("Warning: Title bar container view is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let close_rect: NSRect = msg_send![close, frame];
|
let close_rect: NSRect = msg_send![close, frame];
|
||||||
let button_height = close_rect.size.height;
|
let button_height = close_rect.size.height;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user