support delete bookmarks
This commit is contained in:
@@ -7,9 +7,11 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
config_store,
|
config_store,
|
||||||
models::{
|
models::{
|
||||||
BrowserConfigListResponse, CleanupHistoryInput, CleanupHistoryResponse,
|
BookmarkRemovalRequest, BrowserConfigListResponse, CleanupHistoryInput,
|
||||||
CleanupHistoryResult, CreateCustomBrowserConfigInput, ExtensionInstallSourceSummary,
|
CleanupHistoryResponse, CleanupHistoryResult, CreateCustomBrowserConfigInput,
|
||||||
RemoveExtensionResult, RemoveExtensionsInput, RemoveExtensionsResponse, ScanResponse,
|
ExtensionInstallSourceSummary, RemoveBookmarkResult, RemoveBookmarksInput,
|
||||||
|
RemoveBookmarksResponse, RemoveExtensionResult, RemoveExtensionsInput,
|
||||||
|
RemoveExtensionsResponse, ScanResponse,
|
||||||
},
|
},
|
||||||
scanner,
|
scanner,
|
||||||
};
|
};
|
||||||
@@ -124,6 +126,35 @@ pub fn remove_extensions(
|
|||||||
Ok(RemoveExtensionsResponse { results })
|
Ok(RemoveExtensionsResponse { results })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn remove_bookmarks(
|
||||||
|
app: AppHandle,
|
||||||
|
input: RemoveBookmarksInput,
|
||||||
|
) -> Result<RemoveBookmarksResponse, String> {
|
||||||
|
let config = config_store::find_browser_config(&app, &input.browser_id)?;
|
||||||
|
let user_data_dir = PathBuf::from(&config.user_data_path);
|
||||||
|
|
||||||
|
if !user_data_dir.is_dir() {
|
||||||
|
return Err(format!(
|
||||||
|
"User data directory does not exist: {}",
|
||||||
|
user_data_dir.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for removal in input.removals {
|
||||||
|
for profile_id in &removal.profile_ids {
|
||||||
|
results.push(remove_bookmark_from_profile(
|
||||||
|
&user_data_dir.join(profile_id),
|
||||||
|
&removal,
|
||||||
|
profile_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RemoveBookmarksResponse { results })
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_browser_process(
|
fn spawn_browser_process(
|
||||||
executable_path: PathBuf,
|
executable_path: PathBuf,
|
||||||
user_data_dir: PathBuf,
|
user_data_dir: PathBuf,
|
||||||
@@ -298,6 +329,106 @@ fn remove_extension_from_profile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_bookmark_from_profile(
|
||||||
|
profile_path: &Path,
|
||||||
|
removal: &BookmarkRemovalRequest,
|
||||||
|
profile_id: &str,
|
||||||
|
) -> RemoveBookmarkResult {
|
||||||
|
if !profile_path.is_dir() {
|
||||||
|
return RemoveBookmarkResult {
|
||||||
|
url: removal.url.clone(),
|
||||||
|
profile_id: profile_id.to_string(),
|
||||||
|
removed_count: 0,
|
||||||
|
removed_files: Vec::new(),
|
||||||
|
skipped_files: Vec::new(),
|
||||||
|
error: Some(format!(
|
||||||
|
"Profile directory does not exist: {}",
|
||||||
|
profile_path.display()
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut removed_files = Vec::new();
|
||||||
|
let mut skipped_files = Vec::new();
|
||||||
|
|
||||||
|
let removed_backup = remove_bookmark_backups(profile_path).map_err(|error| RemoveBookmarkResult {
|
||||||
|
url: removal.url.clone(),
|
||||||
|
profile_id: profile_id.to_string(),
|
||||||
|
removed_count: 0,
|
||||||
|
removed_files: removed_files.clone(),
|
||||||
|
skipped_files: skipped_files.clone(),
|
||||||
|
error: Some(error),
|
||||||
|
});
|
||||||
|
let removed_backup = match removed_backup {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(result) => return result,
|
||||||
|
};
|
||||||
|
if removed_backup {
|
||||||
|
removed_files.push("Bookmarks.bak".to_string());
|
||||||
|
} else {
|
||||||
|
skipped_files.push("Bookmarks.bak".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(bookmarks_path) = resolve_bookmarks_path(profile_path) else {
|
||||||
|
return RemoveBookmarkResult {
|
||||||
|
url: removal.url.clone(),
|
||||||
|
profile_id: profile_id.to_string(),
|
||||||
|
removed_count: 0,
|
||||||
|
removed_files,
|
||||||
|
skipped_files,
|
||||||
|
error: Some(format!(
|
||||||
|
"Bookmarks file does not exist in {}",
|
||||||
|
profile_path.display()
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut document = match read_json_document(&bookmarks_path) {
|
||||||
|
Ok(document) => document,
|
||||||
|
Err(error) => {
|
||||||
|
return RemoveBookmarkResult {
|
||||||
|
url: removal.url.clone(),
|
||||||
|
profile_id: profile_id.to_string(),
|
||||||
|
removed_count: 0,
|
||||||
|
removed_files,
|
||||||
|
skipped_files,
|
||||||
|
error: Some(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let checksum_removed = document
|
||||||
|
.as_object_mut()
|
||||||
|
.and_then(|object| object.remove("checksum"))
|
||||||
|
.is_some();
|
||||||
|
let removed_count = remove_matching_bookmarks(&mut document, &removal.url);
|
||||||
|
|
||||||
|
if checksum_removed || removed_count > 0 {
|
||||||
|
if let Err(error) = write_json_document(&bookmarks_path, &document) {
|
||||||
|
return RemoveBookmarkResult {
|
||||||
|
url: removal.url.clone(),
|
||||||
|
profile_id: profile_id.to_string(),
|
||||||
|
removed_count: 0,
|
||||||
|
removed_files,
|
||||||
|
skipped_files,
|
||||||
|
error: Some(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
removed_files.push("Bookmarks".to_string());
|
||||||
|
} else {
|
||||||
|
skipped_files.push("Bookmarks".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveBookmarkResult {
|
||||||
|
url: removal.url.clone(),
|
||||||
|
profile_id: profile_id.to_string(),
|
||||||
|
removed_count,
|
||||||
|
removed_files,
|
||||||
|
skipped_files,
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_extension_from_secure_preferences(
|
fn remove_extension_from_secure_preferences(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
extension_id: &str,
|
extension_id: &str,
|
||||||
@@ -427,3 +558,69 @@ fn cleanup_sessions_directory(path: &Path) -> Result<bool, std::io::Error> {
|
|||||||
|
|
||||||
Ok(deleted_any)
|
Ok(deleted_any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_bookmark_backups(profile_path: &Path) -> Result<bool, String> {
|
||||||
|
let mut deleted_any = false;
|
||||||
|
for backup_name in ["Bookmarks.bak", "Bookmark.bak"] {
|
||||||
|
let backup_path = profile_path.join(backup_name);
|
||||||
|
if !backup_path.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fs::remove_file(&backup_path)
|
||||||
|
.map_err(|error| format!("Failed to delete {}: {error}", backup_path.display()))?;
|
||||||
|
deleted_any = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(deleted_any)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_bookmarks_path(profile_path: &Path) -> Option<PathBuf> {
|
||||||
|
["Bookmarks", "Bookmark"]
|
||||||
|
.into_iter()
|
||||||
|
.map(|name| profile_path.join(name))
|
||||||
|
.find(|path| path.is_file())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_matching_bookmarks(value: &mut Value, target_url: &str) -> usize {
|
||||||
|
match value {
|
||||||
|
Value::Object(object) => {
|
||||||
|
let mut removed_count = 0;
|
||||||
|
|
||||||
|
if let Some(children) = object.get_mut("children").and_then(Value::as_array_mut) {
|
||||||
|
let mut index = 0;
|
||||||
|
while index < children.len() {
|
||||||
|
let matches_url = children[index]
|
||||||
|
.as_object()
|
||||||
|
.map(|child| {
|
||||||
|
child.get("type").and_then(Value::as_str) == Some("url")
|
||||||
|
&& child.get("url").and_then(Value::as_str) == Some(target_url)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if matches_url {
|
||||||
|
children.remove(index);
|
||||||
|
removed_count += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
removed_count += remove_matching_bookmarks(&mut children[index], target_url);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, child) in object.iter_mut() {
|
||||||
|
if key == "children" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
removed_count += remove_matching_bookmarks(child, target_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
removed_count
|
||||||
|
}
|
||||||
|
Value::Array(array) => array
|
||||||
|
.iter_mut()
|
||||||
|
.map(|item| remove_matching_bookmarks(item, target_url))
|
||||||
|
.sum(),
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ pub fn run() {
|
|||||||
commands::delete_custom_browser_config,
|
commands::delete_custom_browser_config,
|
||||||
commands::open_browser_profile,
|
commands::open_browser_profile,
|
||||||
commands::cleanup_history_files,
|
commands::cleanup_history_files,
|
||||||
commands::remove_extensions
|
commands::remove_extensions,
|
||||||
|
commands::remove_bookmarks
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -122,6 +122,13 @@ pub struct RemoveExtensionsInput {
|
|||||||
pub removals: Vec<ExtensionRemovalRequest>,
|
pub removals: Vec<ExtensionRemovalRequest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RemoveBookmarksInput {
|
||||||
|
pub browser_id: String,
|
||||||
|
pub removals: Vec<BookmarkRemovalRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ExtensionRemovalRequest {
|
pub struct ExtensionRemovalRequest {
|
||||||
@@ -129,12 +136,25 @@ pub struct ExtensionRemovalRequest {
|
|||||||
pub profile_ids: Vec<String>,
|
pub profile_ids: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct BookmarkRemovalRequest {
|
||||||
|
pub url: String,
|
||||||
|
pub profile_ids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RemoveExtensionsResponse {
|
pub struct RemoveExtensionsResponse {
|
||||||
pub results: Vec<RemoveExtensionResult>,
|
pub results: Vec<RemoveExtensionResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RemoveBookmarksResponse {
|
||||||
|
pub results: Vec<RemoveBookmarkResult>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RemoveExtensionResult {
|
pub struct RemoveExtensionResult {
|
||||||
@@ -145,6 +165,17 @@ pub struct RemoveExtensionResult {
|
|||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RemoveBookmarkResult {
|
||||||
|
pub url: String,
|
||||||
|
pub profile_id: String,
|
||||||
|
pub removed_count: usize,
|
||||||
|
pub removed_files: Vec<String>,
|
||||||
|
pub skipped_files: Vec<String>,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AssociatedProfileSummary {
|
pub struct AssociatedProfileSummary {
|
||||||
|
|||||||
38
src/App.vue
38
src/App.vue
@@ -8,6 +8,14 @@ const {
|
|||||||
activeSection,
|
activeSection,
|
||||||
associatedProfilesModal,
|
associatedProfilesModal,
|
||||||
bookmarkSortKey,
|
bookmarkSortKey,
|
||||||
|
bookmarkDeleteBusy,
|
||||||
|
bookmarkModalSelectedProfileIds,
|
||||||
|
bookmarkRemovalConfirmBookmarkCount,
|
||||||
|
bookmarkRemovalConfirmProfileCount,
|
||||||
|
bookmarkRemovalError,
|
||||||
|
bookmarkRemovalResultOpen,
|
||||||
|
bookmarkRemovalResults,
|
||||||
|
bookmarkSelectedUrls,
|
||||||
browserConfigs,
|
browserConfigs,
|
||||||
browserMonogram,
|
browserMonogram,
|
||||||
browsers,
|
browsers,
|
||||||
@@ -16,6 +24,8 @@ const {
|
|||||||
configsLoading,
|
configsLoading,
|
||||||
createConfigForm,
|
createConfigForm,
|
||||||
createCustomBrowserConfig,
|
createCustomBrowserConfig,
|
||||||
|
deleteBookmarkFromAllProfiles,
|
||||||
|
deleteBookmarkFromProfile,
|
||||||
cleanupHistoryError,
|
cleanupHistoryError,
|
||||||
cleanupHistoryForProfile,
|
cleanupHistoryForProfile,
|
||||||
cleanupHistoryResults,
|
cleanupHistoryResults,
|
||||||
@@ -26,6 +36,8 @@ const {
|
|||||||
confirmHistoryCleanup,
|
confirmHistoryCleanup,
|
||||||
currentBrowser,
|
currentBrowser,
|
||||||
deleteCustomBrowserConfig,
|
deleteCustomBrowserConfig,
|
||||||
|
deleteSelectedBookmarkProfiles,
|
||||||
|
deleteSelectedBookmarks,
|
||||||
deleteExtensionFromAllProfiles,
|
deleteExtensionFromAllProfiles,
|
||||||
deleteExtensionFromProfile,
|
deleteExtensionFromProfile,
|
||||||
deleteSelectedExtensionProfiles,
|
deleteSelectedExtensionProfiles,
|
||||||
@@ -58,6 +70,8 @@ const {
|
|||||||
savingConfig,
|
savingConfig,
|
||||||
sectionCount,
|
sectionCount,
|
||||||
selectedBrowserId,
|
selectedBrowserId,
|
||||||
|
closeBookmarkRemovalConfirm,
|
||||||
|
closeBookmarkRemovalResult,
|
||||||
showBookmarkProfilesModal,
|
showBookmarkProfilesModal,
|
||||||
showExtensionProfilesModal,
|
showExtensionProfilesModal,
|
||||||
showPasswordSiteProfilesModal,
|
showPasswordSiteProfilesModal,
|
||||||
@@ -65,10 +79,15 @@ const {
|
|||||||
sortedExtensions,
|
sortedExtensions,
|
||||||
sortedPasswordSites,
|
sortedPasswordSites,
|
||||||
sortedProfiles,
|
sortedProfiles,
|
||||||
|
confirmBookmarkRemoval,
|
||||||
closeExtensionRemovalConfirm,
|
closeExtensionRemovalConfirm,
|
||||||
closeExtensionRemovalResult,
|
closeExtensionRemovalResult,
|
||||||
confirmExtensionRemoval,
|
confirmExtensionRemoval,
|
||||||
|
toggleAllBookmarks,
|
||||||
|
toggleAllBookmarkModalProfiles,
|
||||||
toggleAllExtensions,
|
toggleAllExtensions,
|
||||||
|
toggleBookmarkModalProfileSelection,
|
||||||
|
toggleBookmarkSelection,
|
||||||
toggleAllExtensionModalProfiles,
|
toggleAllExtensionModalProfiles,
|
||||||
toggleExtensionModalProfileSelection,
|
toggleExtensionModalProfileSelection,
|
||||||
toggleExtensionSelection,
|
toggleExtensionSelection,
|
||||||
@@ -147,6 +166,14 @@ const {
|
|||||||
:history-cleanup-result-open="historyCleanupResultOpen"
|
:history-cleanup-result-open="historyCleanupResultOpen"
|
||||||
:cleanup-history-error="cleanupHistoryError"
|
:cleanup-history-error="cleanupHistoryError"
|
||||||
:cleanup-history-results="cleanupHistoryResults"
|
:cleanup-history-results="cleanupHistoryResults"
|
||||||
|
:bookmark-selected-urls="bookmarkSelectedUrls"
|
||||||
|
:bookmark-modal-selected-profile-ids="bookmarkModalSelectedProfileIds"
|
||||||
|
:bookmark-delete-busy="bookmarkDeleteBusy"
|
||||||
|
:bookmark-removal-confirm-bookmark-count="bookmarkRemovalConfirmBookmarkCount"
|
||||||
|
:bookmark-removal-confirm-profile-count="bookmarkRemovalConfirmProfileCount"
|
||||||
|
:bookmark-removal-result-open="bookmarkRemovalResultOpen"
|
||||||
|
:bookmark-removal-error="bookmarkRemovalError"
|
||||||
|
:bookmark-removal-results="bookmarkRemovalResults"
|
||||||
:extension-selected-ids="extensionSelectedIds"
|
:extension-selected-ids="extensionSelectedIds"
|
||||||
:extension-modal-selected-profile-ids="extensionModalSelectedProfileIds"
|
:extension-modal-selected-profile-ids="extensionModalSelectedProfileIds"
|
||||||
:extension-delete-busy="extensionDeleteBusy"
|
:extension-delete-busy="extensionDeleteBusy"
|
||||||
@@ -169,6 +196,17 @@ const {
|
|||||||
@show-extension-profiles="showExtensionProfilesModal"
|
@show-extension-profiles="showExtensionProfilesModal"
|
||||||
@show-bookmark-profiles="showBookmarkProfilesModal"
|
@show-bookmark-profiles="showBookmarkProfilesModal"
|
||||||
@show-password-site-profiles="showPasswordSiteProfilesModal"
|
@show-password-site-profiles="showPasswordSiteProfilesModal"
|
||||||
|
@toggle-bookmark-selection="toggleBookmarkSelection"
|
||||||
|
@toggle-all-bookmarks="toggleAllBookmarks"
|
||||||
|
@delete-bookmark-from-all-profiles="deleteBookmarkFromAllProfiles"
|
||||||
|
@delete-selected-bookmarks="deleteSelectedBookmarks"
|
||||||
|
@toggle-bookmark-modal-profile-selection="toggleBookmarkModalProfileSelection"
|
||||||
|
@toggle-all-bookmark-modal-profiles="toggleAllBookmarkModalProfiles"
|
||||||
|
@delete-bookmark-from-profile="deleteBookmarkFromProfile"
|
||||||
|
@delete-selected-bookmark-profiles="deleteSelectedBookmarkProfiles"
|
||||||
|
@confirm-bookmark-removal="confirmBookmarkRemoval"
|
||||||
|
@close-bookmark-removal-confirm="closeBookmarkRemovalConfirm"
|
||||||
|
@close-bookmark-removal-result="closeBookmarkRemovalResult"
|
||||||
@toggle-extension-selection="toggleExtensionSelection"
|
@toggle-extension-selection="toggleExtensionSelection"
|
||||||
@toggle-all-extensions="toggleAllExtensions"
|
@toggle-all-extensions="toggleAllExtensions"
|
||||||
@delete-extension-from-all-profiles="deleteExtensionFromAllProfiles"
|
@delete-extension-from-all-profiles="deleteExtensionFromAllProfiles"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const allSelected = computed(
|
|||||||
sortedProfiles.value.length > 0 &&
|
sortedProfiles.value.length > 0 &&
|
||||||
sortedProfiles.value.every((profile) => selectedProfileIds.value.includes(profile.id)),
|
sortedProfiles.value.every((profile) => selectedProfileIds.value.includes(profile.id)),
|
||||||
);
|
);
|
||||||
|
const isSelectableMode = computed(() => props.isExtension || props.isBookmark);
|
||||||
|
|
||||||
function hasBookmarkPath(profile: ModalProfile): profile is BookmarkAssociatedProfileSummary {
|
function hasBookmarkPath(profile: ModalProfile): profile is BookmarkAssociatedProfileSummary {
|
||||||
return "bookmarkPath" in profile;
|
return "bookmarkPath" in profile;
|
||||||
@@ -67,7 +68,7 @@ function isSelected(profileId: string) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isExtension" class="modal-toolbar">
|
<div v-if="isSelectableMode" class="modal-toolbar">
|
||||||
<label class="toolbar-checkbox" :class="{ disabled: !sortedProfiles.length }">
|
<label class="toolbar-checkbox" :class="{ disabled: !sortedProfiles.length }">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -95,10 +96,18 @@ function isSelected(profileId: string) {
|
|||||||
|
|
||||||
<div class="modal-table">
|
<div class="modal-table">
|
||||||
<div class="modal-table-header modal-grid" :class="{ bookmark: isBookmark, extension: isExtension }">
|
<div class="modal-table-header modal-grid" :class="{ bookmark: isBookmark, extension: isExtension }">
|
||||||
<div v-if="isExtension" class="header-cell checkbox-cell">Pick</div>
|
<div v-if="isSelectableMode" class="header-cell checkbox-cell">Pick</div>
|
||||||
<div class="header-cell icon-cell">Avatar</div>
|
<div class="header-cell icon-cell">Avatar</div>
|
||||||
<button class="header-cell sortable" :class="{ active: sortKey === 'name' }" type="button" @click="sortKey = 'name'">Name</button>
|
<button class="header-cell sortable" :class="{ active: sortKey === 'name' }" type="button" @click="sortKey = 'name'">Name</button>
|
||||||
<button v-if="!isExtension" class="header-cell sortable" :class="{ active: sortKey === 'id' }" type="button" @click="sortKey = 'id'">Profile ID</button>
|
<button
|
||||||
|
v-if="!isExtension && !isBookmark"
|
||||||
|
class="header-cell sortable"
|
||||||
|
:class="{ active: sortKey === 'id' }"
|
||||||
|
type="button"
|
||||||
|
@click="sortKey = 'id'"
|
||||||
|
>
|
||||||
|
Profile ID
|
||||||
|
</button>
|
||||||
<div v-if="isExtension" class="header-cell">Source</div>
|
<div v-if="isExtension" class="header-cell">Source</div>
|
||||||
<div v-if="isBookmark" class="header-cell">Bookmark Path</div>
|
<div v-if="isBookmark" class="header-cell">Bookmark Path</div>
|
||||||
<div class="header-cell actions-cell">Action</div>
|
<div class="header-cell actions-cell">Action</div>
|
||||||
@@ -110,7 +119,7 @@ function isSelected(profileId: string) {
|
|||||||
class="modal-table-row modal-grid"
|
class="modal-table-row modal-grid"
|
||||||
:class="{ bookmark: isBookmark, extension: isExtension }"
|
:class="{ bookmark: isBookmark, extension: isExtension }"
|
||||||
>
|
>
|
||||||
<div v-if="isExtension" class="row-cell checkbox-cell">
|
<div v-if="isSelectableMode" class="row-cell checkbox-cell">
|
||||||
<label class="table-checkbox" :class="{ disabled: deleteBusy }">
|
<label class="table-checkbox" :class="{ disabled: deleteBusy }">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -136,9 +145,9 @@ function isSelected(profileId: string) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="row-cell primary-cell">
|
<div class="row-cell primary-cell">
|
||||||
<strong>{{ profile.name }}</strong>
|
<strong>{{ profile.name }}</strong>
|
||||||
<span v-if="isExtension" class="subtle-line">{{ profile.id }}</span>
|
<span v-if="isExtension || isBookmark" class="subtle-line">{{ profile.id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isExtension" class="row-cell">
|
<div v-if="!isExtension && !isBookmark" class="row-cell">
|
||||||
<span class="badge neutral">{{ profile.id }}</span>
|
<span class="badge neutral">{{ profile.id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isExtension && hasInstallSource(profile)" class="row-cell muted-cell">
|
<div v-if="isExtension && hasInstallSource(profile)" class="row-cell muted-cell">
|
||||||
@@ -161,7 +170,7 @@ function isSelected(profileId: string) {
|
|||||||
{{ isOpeningProfile(browserId, profile.id) ? "Opening..." : "Open" }}
|
{{ isOpeningProfile(browserId, profile.id) ? "Opening..." : "Open" }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="isExtension"
|
v-if="isSelectableMode"
|
||||||
class="danger-button inline-danger-button"
|
class="danger-button inline-danger-button"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="deleteBusy"
|
:disabled="deleteBusy"
|
||||||
@@ -234,7 +243,7 @@ function isSelected(profileId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-grid.bookmark {
|
.modal-grid.bookmark {
|
||||||
grid-template-columns: 56px minmax(140px, 0.9fr) 120px minmax(180px, 1fr) 110px;
|
grid-template-columns: 52px 56px minmax(180px, 0.78fr) minmax(180px, 1fr) 168px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-table-header.modal-grid.extension,
|
.modal-table-header.modal-grid.extension,
|
||||||
|
|||||||
151
src/components/browser-data/BookmarkRemovalModal.vue
Normal file
151
src/components/browser-data/BookmarkRemovalModal.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
import type { RemoveBookmarkResult } from "../../types/browser";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
mode: "confirm" | "result";
|
||||||
|
title: string;
|
||||||
|
bookmarkCount: number;
|
||||||
|
profileCount: number;
|
||||||
|
results: RemoveBookmarkResult[];
|
||||||
|
busy?: boolean;
|
||||||
|
generalError?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: [];
|
||||||
|
confirm: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const resultSummary = computed(() => {
|
||||||
|
const statusByUrl = new Map<string, boolean>();
|
||||||
|
|
||||||
|
for (const result of props.results) {
|
||||||
|
const previous = statusByUrl.get(result.url);
|
||||||
|
const succeeded = !result.error;
|
||||||
|
|
||||||
|
if (previous === undefined) {
|
||||||
|
statusByUrl.set(result.url, succeeded);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusByUrl.set(result.url, previous && succeeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let failedCount = 0;
|
||||||
|
for (const succeeded of statusByUrl.values()) {
|
||||||
|
if (succeeded) {
|
||||||
|
successCount += 1;
|
||||||
|
} else {
|
||||||
|
failedCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { successCount, failedCount };
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="modal-backdrop" @click.self="emit('close')">
|
||||||
|
<section class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
<button class="secondary-button" type="button" @click="emit('close')">Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="mode === 'confirm'">
|
||||||
|
<p class="modal-copy">
|
||||||
|
This will remove {{ bookmarkCount }} bookmark{{ bookmarkCount === 1 ? "" : "s" }} from
|
||||||
|
{{ profileCount }} profile{{ profileCount === 1 ? "" : "s" }}.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="secondary-button" type="button" @click="emit('close')">Cancel</button>
|
||||||
|
<button class="danger-button" type="button" :disabled="busy" @click="emit('confirm')">
|
||||||
|
{{ busy ? "Deleting..." : "Confirm Delete" }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<p v-if="generalError" class="result-banner error">{{ generalError }}</p>
|
||||||
|
<p class="modal-copy">
|
||||||
|
Successfully removed {{ resultSummary.successCount }} bookmark{{
|
||||||
|
resultSummary.successCount === 1 ? "" : "s"
|
||||||
|
}}. Failed to remove {{ resultSummary.failedCount }} bookmark{{
|
||||||
|
resultSummary.failedCount === 1 ? "" : "s"
|
||||||
|
}}.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="primary-button" type="button" @click="emit('close')">Close</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
background: rgba(15, 23, 42, 0.26);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
-webkit-backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card {
|
||||||
|
width: min(560px, 100%);
|
||||||
|
max-height: min(72vh, 820px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
box-shadow: 0 28px 70px rgba(15, 23, 42, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h3,
|
||||||
|
.modal-copy {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-copy {
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-banner {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 14px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-banner.error {
|
||||||
|
background: rgba(254, 242, 242, 0.96);
|
||||||
|
color: #b42318;
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,27 +1,88 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
import type { BookmarkSortKey, BookmarkSummary } from "../../types/browser";
|
import type { BookmarkSortKey, BookmarkSummary } from "../../types/browser";
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
bookmarks: BookmarkSummary[];
|
bookmarks: BookmarkSummary[];
|
||||||
sortKey: BookmarkSortKey;
|
sortKey: BookmarkSortKey;
|
||||||
|
selectedBookmarkUrls: string[];
|
||||||
|
deleteBusy: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
"update:sortKey": [value: BookmarkSortKey];
|
"update:sortKey": [value: BookmarkSortKey];
|
||||||
showProfiles: [url: string];
|
showProfiles: [url: string];
|
||||||
|
toggleBookmark: [url: string];
|
||||||
|
toggleAllBookmarks: [];
|
||||||
|
deleteBookmark: [url: string];
|
||||||
|
deleteSelected: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const allSelected = computed(
|
||||||
|
() =>
|
||||||
|
props.bookmarks.length > 0 &&
|
||||||
|
props.bookmarks.every((bookmark) => props.selectedBookmarkUrls.includes(bookmark.url)),
|
||||||
|
);
|
||||||
|
|
||||||
|
function isSelected(url: string) {
|
||||||
|
return props.selectedBookmarkUrls.includes(url);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="table-section">
|
<section class="table-section">
|
||||||
|
<div class="bookmarks-toolbar">
|
||||||
|
<label class="toolbar-checkbox" :class="{ disabled: !bookmarks.length }">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="native-checkbox"
|
||||||
|
:checked="allSelected"
|
||||||
|
:disabled="!bookmarks.length || deleteBusy"
|
||||||
|
@change="emit('toggleAllBookmarks')"
|
||||||
|
/>
|
||||||
|
<span class="custom-checkbox" :class="{ checked: allSelected }" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 16 16">
|
||||||
|
<path d="M3.5 8.2L6.4 11.1L12.5 4.9" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span>Select All</span>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
class="danger-button"
|
||||||
|
type="button"
|
||||||
|
:disabled="!selectedBookmarkUrls.length || deleteBusy"
|
||||||
|
@click="emit('deleteSelected')"
|
||||||
|
>
|
||||||
|
{{ deleteBusy ? "Deleting..." : `Delete Selected (${selectedBookmarkUrls.length})` }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="bookmarks.length" class="data-table">
|
<div v-if="bookmarks.length" class="data-table">
|
||||||
<div class="data-table-header bookmarks-grid">
|
<div class="data-table-header bookmarks-grid">
|
||||||
|
<div class="header-cell checkbox-cell">Pick</div>
|
||||||
<button class="header-cell sortable" :class="{ active: sortKey === 'title' }" type="button" @click="emit('update:sortKey', 'title')">Name</button>
|
<button class="header-cell sortable" :class="{ active: sortKey === 'title' }" type="button" @click="emit('update:sortKey', 'title')">Name</button>
|
||||||
<button class="header-cell sortable" :class="{ active: sortKey === 'url' }" type="button" @click="emit('update:sortKey', 'url')">URL</button>
|
<button class="header-cell sortable" :class="{ active: sortKey === 'url' }" type="button" @click="emit('update:sortKey', 'url')">URL</button>
|
||||||
<div class="header-cell actions-cell">Profiles</div>
|
<div class="header-cell actions-cell">Actions</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-table-body styled-scrollbar">
|
<div class="data-table-body styled-scrollbar">
|
||||||
<article v-for="bookmark in bookmarks" :key="bookmark.url" class="data-table-row bookmarks-grid">
|
<article v-for="bookmark in bookmarks" :key="bookmark.url" class="data-table-row bookmarks-grid">
|
||||||
|
<div class="row-cell checkbox-cell">
|
||||||
|
<label class="table-checkbox" :class="{ disabled: deleteBusy }">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="native-checkbox"
|
||||||
|
:checked="isSelected(bookmark.url)"
|
||||||
|
:disabled="deleteBusy"
|
||||||
|
@change="emit('toggleBookmark', bookmark.url)"
|
||||||
|
/>
|
||||||
|
<span class="custom-checkbox" :class="{ checked: isSelected(bookmark.url) }" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 16 16">
|
||||||
|
<path d="M3.5 8.2L6.4 11.1L12.5 4.9" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="row-cell primary-cell">
|
<div class="row-cell primary-cell">
|
||||||
<strong :title="bookmark.title">{{ bookmark.title }}</strong>
|
<strong :title="bookmark.title">{{ bookmark.title }}</strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,6 +92,14 @@ const emit = defineEmits<{
|
|||||||
<span>View</span>
|
<span>View</span>
|
||||||
<span class="badge neutral">{{ bookmark.profileIds.length }}</span>
|
<span class="badge neutral">{{ bookmark.profileIds.length }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="danger-button inline-danger-button"
|
||||||
|
type="button"
|
||||||
|
:disabled="deleteBusy"
|
||||||
|
@click="emit('deleteBookmark', bookmark.url)"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,11 +112,21 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.table-section {
|
.table-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bookmarks-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.data-table {
|
.data-table {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -68,7 +147,7 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
.bookmarks-grid {
|
.bookmarks-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(180px, 0.9fr) minmax(260px, 1.2fr) 154px;
|
grid-template-columns: 52px minmax(180px, 0.9fr) minmax(260px, 1.2fr) 250px;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -102,6 +181,79 @@ const emit = defineEmits<{
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbar-checkbox {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-checkbox.disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.native-checkbox {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-checkbox {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-checkbox.disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.34);
|
||||||
|
border-radius: 7px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(241, 245, 249, 0.92));
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.78),
|
||||||
|
0 4px 10px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox svg {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox path {
|
||||||
|
fill: none;
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-width: 2.2;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked {
|
||||||
|
border-color: rgba(47, 111, 237, 0.2);
|
||||||
|
background: linear-gradient(135deg, #2f6fed, #5aa1f7);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.22),
|
||||||
|
0 8px 18px rgba(47, 111, 237, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked path {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.data-table-row {
|
.data-table-row {
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
|
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
|
||||||
@@ -153,20 +305,32 @@ const emit = defineEmits<{
|
|||||||
.actions-cell {
|
.actions-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-cell {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-danger-button {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.bookmarks-grid {
|
.bookmarks-grid {
|
||||||
grid-template-columns: minmax(160px, 0.9fr) minmax(200px, 1fr) 148px;
|
grid-template-columns: 52px minmax(160px, 0.9fr) minmax(200px, 1fr) 236px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.bookmarks-grid {
|
.bookmarks-grid {
|
||||||
grid-template-columns: minmax(0, 1fr) 132px;
|
grid-template-columns: 52px minmax(0, 1fr) 152px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmarks-grid > :nth-child(2) {
|
.bookmarks-grid > :nth-child(3) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import type {
|
|||||||
ExtensionSortKey,
|
ExtensionSortKey,
|
||||||
CleanupHistoryResult,
|
CleanupHistoryResult,
|
||||||
ExtensionAssociatedProfileSummary,
|
ExtensionAssociatedProfileSummary,
|
||||||
|
RemoveBookmarkResult,
|
||||||
PasswordSiteSortKey,
|
PasswordSiteSortKey,
|
||||||
ProfileSortKey,
|
ProfileSortKey,
|
||||||
RemoveExtensionResult,
|
RemoveExtensionResult,
|
||||||
} from "../../types/browser";
|
} from "../../types/browser";
|
||||||
import AssociatedProfilesModal from "./AssociatedProfilesModal.vue";
|
import AssociatedProfilesModal from "./AssociatedProfilesModal.vue";
|
||||||
|
import BookmarkRemovalModal from "./BookmarkRemovalModal.vue";
|
||||||
import BookmarksList from "./BookmarksList.vue";
|
import BookmarksList from "./BookmarksList.vue";
|
||||||
import ExtensionRemovalModal from "./ExtensionRemovalModal.vue";
|
import ExtensionRemovalModal from "./ExtensionRemovalModal.vue";
|
||||||
import ExtensionsList from "./ExtensionsList.vue";
|
import ExtensionsList from "./ExtensionsList.vue";
|
||||||
@@ -38,6 +40,14 @@ defineProps<{
|
|||||||
historyCleanupResultOpen: boolean;
|
historyCleanupResultOpen: boolean;
|
||||||
cleanupHistoryError: string;
|
cleanupHistoryError: string;
|
||||||
cleanupHistoryResults: CleanupHistoryResult[];
|
cleanupHistoryResults: CleanupHistoryResult[];
|
||||||
|
bookmarkSelectedUrls: string[];
|
||||||
|
bookmarkModalSelectedProfileIds: string[];
|
||||||
|
bookmarkDeleteBusy: boolean;
|
||||||
|
bookmarkRemovalConfirmBookmarkCount: number;
|
||||||
|
bookmarkRemovalConfirmProfileCount: number;
|
||||||
|
bookmarkRemovalResultOpen: boolean;
|
||||||
|
bookmarkRemovalError: string;
|
||||||
|
bookmarkRemovalResults: RemoveBookmarkResult[];
|
||||||
extensionSelectedIds: string[];
|
extensionSelectedIds: string[];
|
||||||
extensionModalSelectedProfileIds: string[];
|
extensionModalSelectedProfileIds: string[];
|
||||||
extensionDeleteBusy: boolean;
|
extensionDeleteBusy: boolean;
|
||||||
@@ -61,6 +71,7 @@ defineProps<{
|
|||||||
isBookmark: boolean;
|
isBookmark: boolean;
|
||||||
isExtension?: boolean;
|
isExtension?: boolean;
|
||||||
extensionId?: string;
|
extensionId?: string;
|
||||||
|
bookmarkUrl?: string;
|
||||||
} | null;
|
} | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -74,6 +85,17 @@ const emit = defineEmits<{
|
|||||||
showExtensionProfiles: [extensionId: string];
|
showExtensionProfiles: [extensionId: string];
|
||||||
showBookmarkProfiles: [url: string];
|
showBookmarkProfiles: [url: string];
|
||||||
showPasswordSiteProfiles: [url: string];
|
showPasswordSiteProfiles: [url: string];
|
||||||
|
toggleBookmarkSelection: [url: string];
|
||||||
|
toggleAllBookmarks: [];
|
||||||
|
deleteBookmarkFromAllProfiles: [url: string];
|
||||||
|
deleteSelectedBookmarks: [];
|
||||||
|
toggleBookmarkModalProfileSelection: [profileId: string];
|
||||||
|
toggleAllBookmarkModalProfiles: [];
|
||||||
|
deleteBookmarkFromProfile: [profileId: string];
|
||||||
|
deleteSelectedBookmarkProfiles: [];
|
||||||
|
confirmBookmarkRemoval: [];
|
||||||
|
closeBookmarkRemovalConfirm: [];
|
||||||
|
closeBookmarkRemovalResult: [];
|
||||||
toggleExtensionSelection: [extensionId: string];
|
toggleExtensionSelection: [extensionId: string];
|
||||||
toggleAllExtensions: [];
|
toggleAllExtensions: [];
|
||||||
deleteExtensionFromAllProfiles: [extensionId: string];
|
deleteExtensionFromAllProfiles: [extensionId: string];
|
||||||
@@ -177,8 +199,14 @@ const emit = defineEmits<{
|
|||||||
v-else-if="activeSection === 'bookmarks'"
|
v-else-if="activeSection === 'bookmarks'"
|
||||||
:bookmarks="sortedBookmarks"
|
:bookmarks="sortedBookmarks"
|
||||||
:sort-key="bookmarkSortKey"
|
:sort-key="bookmarkSortKey"
|
||||||
|
:selected-bookmark-urls="bookmarkSelectedUrls"
|
||||||
|
:delete-busy="bookmarkDeleteBusy"
|
||||||
@update:sort-key="emit('update:bookmarkSortKey', $event)"
|
@update:sort-key="emit('update:bookmarkSortKey', $event)"
|
||||||
@show-profiles="emit('showBookmarkProfiles', $event)"
|
@show-profiles="emit('showBookmarkProfiles', $event)"
|
||||||
|
@toggle-bookmark="emit('toggleBookmarkSelection', $event)"
|
||||||
|
@toggle-all-bookmarks="emit('toggleAllBookmarks')"
|
||||||
|
@delete-bookmark="emit('deleteBookmarkFromAllProfiles', $event)"
|
||||||
|
@delete-selected="emit('deleteSelectedBookmarks')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PasswordSitesList
|
<PasswordSitesList
|
||||||
@@ -223,6 +251,29 @@ const emit = defineEmits<{
|
|||||||
@close="emit('closeHistoryCleanupResult')"
|
@close="emit('closeHistoryCleanupResult')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<BookmarkRemovalModal
|
||||||
|
v-if="bookmarkRemovalConfirmBookmarkCount > 0"
|
||||||
|
mode="confirm"
|
||||||
|
title="Confirm Bookmark Removal"
|
||||||
|
:bookmark-count="bookmarkRemovalConfirmBookmarkCount"
|
||||||
|
:profile-count="bookmarkRemovalConfirmProfileCount"
|
||||||
|
:results="[]"
|
||||||
|
:busy="bookmarkDeleteBusy"
|
||||||
|
@close="emit('closeBookmarkRemovalConfirm')"
|
||||||
|
@confirm="emit('confirmBookmarkRemoval')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BookmarkRemovalModal
|
||||||
|
v-if="bookmarkRemovalResultOpen"
|
||||||
|
mode="result"
|
||||||
|
title="Bookmark Removal Result"
|
||||||
|
:bookmark-count="0"
|
||||||
|
:profile-count="0"
|
||||||
|
:results="bookmarkRemovalResults"
|
||||||
|
:general-error="bookmarkRemovalError"
|
||||||
|
@close="emit('closeBookmarkRemovalResult')"
|
||||||
|
/>
|
||||||
|
|
||||||
<ExtensionRemovalModal
|
<ExtensionRemovalModal
|
||||||
v-if="extensionRemovalConfirmExtensions.length || extensionRemovalConfirmProfiles.length"
|
v-if="extensionRemovalConfirmExtensions.length || extensionRemovalConfirmProfiles.length"
|
||||||
mode="confirm"
|
mode="confirm"
|
||||||
@@ -254,15 +305,35 @@ const emit = defineEmits<{
|
|||||||
:browser-family-id="currentBrowser.browserFamilyId"
|
:browser-family-id="currentBrowser.browserFamilyId"
|
||||||
:is-bookmark="associatedProfilesModal.isBookmark"
|
:is-bookmark="associatedProfilesModal.isBookmark"
|
||||||
:is-extension="associatedProfilesModal.isExtension"
|
:is-extension="associatedProfilesModal.isExtension"
|
||||||
:selected-profile-ids="extensionModalSelectedProfileIds"
|
:selected-profile-ids="
|
||||||
:delete-busy="extensionDeleteBusy"
|
associatedProfilesModal.isExtension
|
||||||
|
? extensionModalSelectedProfileIds
|
||||||
|
: bookmarkModalSelectedProfileIds
|
||||||
|
"
|
||||||
|
:delete-busy="associatedProfilesModal.isExtension ? extensionDeleteBusy : bookmarkDeleteBusy"
|
||||||
:is-opening-profile="isOpeningProfile"
|
:is-opening-profile="isOpeningProfile"
|
||||||
@close="emit('closeAssociatedProfiles')"
|
@close="emit('closeAssociatedProfiles')"
|
||||||
@open-profile="(browserId, profileId) => emit('openProfile', browserId, profileId)"
|
@open-profile="(browserId, profileId) => emit('openProfile', browserId, profileId)"
|
||||||
@toggle-profile-selection="emit('toggleExtensionModalProfileSelection', $event)"
|
@toggle-profile-selection="
|
||||||
@toggle-all-profile-selection="emit('toggleAllExtensionModalProfiles')"
|
associatedProfilesModal.isExtension
|
||||||
@delete-profile="emit('deleteExtensionFromProfile', $event)"
|
? emit('toggleExtensionModalProfileSelection', $event)
|
||||||
@delete-selected-profiles="emit('deleteSelectedExtensionProfiles')"
|
: emit('toggleBookmarkModalProfileSelection', $event)
|
||||||
|
"
|
||||||
|
@toggle-all-profile-selection="
|
||||||
|
associatedProfilesModal.isExtension
|
||||||
|
? emit('toggleAllExtensionModalProfiles')
|
||||||
|
: emit('toggleAllBookmarkModalProfiles')
|
||||||
|
"
|
||||||
|
@delete-profile="
|
||||||
|
associatedProfilesModal.isExtension
|
||||||
|
? emit('deleteExtensionFromProfile', $event)
|
||||||
|
: emit('deleteBookmarkFromProfile', $event)
|
||||||
|
"
|
||||||
|
@delete-selected-profiles="
|
||||||
|
associatedProfilesModal.isExtension
|
||||||
|
? emit('deleteSelectedExtensionProfiles')
|
||||||
|
: emit('deleteSelectedBookmarkProfiles')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
BrowserConfigEntry,
|
BrowserConfigEntry,
|
||||||
BrowserConfigListResponse,
|
BrowserConfigListResponse,
|
||||||
BrowserView,
|
BrowserView,
|
||||||
|
BookmarkRemovalRequest,
|
||||||
CleanupHistoryInput,
|
CleanupHistoryInput,
|
||||||
CleanupHistoryResponse,
|
CleanupHistoryResponse,
|
||||||
CreateCustomBrowserConfigInput,
|
CreateCustomBrowserConfigInput,
|
||||||
@@ -20,6 +21,8 @@ import type {
|
|||||||
ExtensionSortKey,
|
ExtensionSortKey,
|
||||||
PasswordSiteSortKey,
|
PasswordSiteSortKey,
|
||||||
ProfileSortKey,
|
ProfileSortKey,
|
||||||
|
RemoveBookmarksInput,
|
||||||
|
RemoveBookmarksResponse,
|
||||||
RemoveExtensionsInput,
|
RemoveExtensionsInput,
|
||||||
RemoveExtensionsResponse,
|
RemoveExtensionsResponse,
|
||||||
ScanResponse,
|
ScanResponse,
|
||||||
@@ -56,11 +59,21 @@ export function useBrowserManager() {
|
|||||||
isBookmark: boolean;
|
isBookmark: boolean;
|
||||||
isExtension?: boolean;
|
isExtension?: boolean;
|
||||||
extensionId?: string;
|
extensionId?: string;
|
||||||
|
bookmarkUrl?: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const profileSortKey = ref<ProfileSortKey>("name");
|
const profileSortKey = ref<ProfileSortKey>("name");
|
||||||
const extensionSortKey = ref<ExtensionSortKey>("name");
|
const extensionSortKey = ref<ExtensionSortKey>("name");
|
||||||
const bookmarkSortKey = ref<BookmarkSortKey>("title");
|
const bookmarkSortKey = ref<BookmarkSortKey>("title");
|
||||||
const passwordSiteSortKey = ref<PasswordSiteSortKey>("domain");
|
const passwordSiteSortKey = ref<PasswordSiteSortKey>("domain");
|
||||||
|
const bookmarkSelectedUrls = ref<string[]>([]);
|
||||||
|
const bookmarkModalSelectedProfileIds = ref<string[]>([]);
|
||||||
|
const bookmarkDeleteBusy = ref(false);
|
||||||
|
const bookmarkRemovalError = ref("");
|
||||||
|
const bookmarkRemovalResults = ref<RemoveBookmarksResponse["results"]>([]);
|
||||||
|
const bookmarkRemovalResultOpen = ref(false);
|
||||||
|
const bookmarkRemovalConfirmRemovals = ref<BookmarkRemovalRequest[]>([]);
|
||||||
|
const bookmarkRemovalConfirmUrls = ref<string[]>([]);
|
||||||
|
const bookmarkRemovalConfirmProfileIds = ref<string[]>([]);
|
||||||
const extensionSelectedIds = ref<string[]>([]);
|
const extensionSelectedIds = ref<string[]>([]);
|
||||||
const extensionModalSelectedProfileIds = ref<string[]>([]);
|
const extensionModalSelectedProfileIds = ref<string[]>([]);
|
||||||
const extensionDeleteBusy = ref(false);
|
const extensionDeleteBusy = ref(false);
|
||||||
@@ -123,6 +136,14 @@ export function useBrowserManager() {
|
|||||||
cleanupHistorySelectedProfiles.value = [];
|
cleanupHistorySelectedProfiles.value = [];
|
||||||
cleanupHistoryResults.value = [];
|
cleanupHistoryResults.value = [];
|
||||||
cleanupHistoryError.value = "";
|
cleanupHistoryError.value = "";
|
||||||
|
bookmarkSelectedUrls.value = [];
|
||||||
|
bookmarkModalSelectedProfileIds.value = [];
|
||||||
|
bookmarkRemovalError.value = "";
|
||||||
|
bookmarkRemovalResults.value = [];
|
||||||
|
bookmarkRemovalResultOpen.value = false;
|
||||||
|
bookmarkRemovalConfirmRemovals.value = [];
|
||||||
|
bookmarkRemovalConfirmUrls.value = [];
|
||||||
|
bookmarkRemovalConfirmProfileIds.value = [];
|
||||||
extensionSelectedIds.value = [];
|
extensionSelectedIds.value = [];
|
||||||
extensionModalSelectedProfileIds.value = [];
|
extensionModalSelectedProfileIds.value = [];
|
||||||
extensionRemovalError.value = "";
|
extensionRemovalError.value = "";
|
||||||
@@ -339,11 +360,13 @@ export function useBrowserManager() {
|
|||||||
function showBookmarkProfilesModal(url: string) {
|
function showBookmarkProfilesModal(url: string) {
|
||||||
const bookmark = currentBrowser.value?.bookmarks.find((item) => item.url === url);
|
const bookmark = currentBrowser.value?.bookmarks.find((item) => item.url === url);
|
||||||
if (!bookmark || !currentBrowser.value) return;
|
if (!bookmark || !currentBrowser.value) return;
|
||||||
|
bookmarkModalSelectedProfileIds.value = [];
|
||||||
associatedProfilesModal.value = {
|
associatedProfilesModal.value = {
|
||||||
title: `${bookmark.title} Profiles`,
|
title: `${bookmark.title} Profiles`,
|
||||||
browserId: currentBrowser.value.browserId,
|
browserId: currentBrowser.value.browserId,
|
||||||
profiles: bookmark.profiles,
|
profiles: bookmark.profiles,
|
||||||
isBookmark: true,
|
isBookmark: true,
|
||||||
|
bookmarkUrl: url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,6 +533,201 @@ export function useBrowserManager() {
|
|||||||
function closeAssociatedProfilesModal() {
|
function closeAssociatedProfilesModal() {
|
||||||
associatedProfilesModal.value = null;
|
associatedProfilesModal.value = null;
|
||||||
extensionModalSelectedProfileIds.value = [];
|
extensionModalSelectedProfileIds.value = [];
|
||||||
|
bookmarkModalSelectedProfileIds.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBookmarkSelection(url: string) {
|
||||||
|
if (bookmarkSelectedUrls.value.includes(url)) {
|
||||||
|
bookmarkSelectedUrls.value = bookmarkSelectedUrls.value.filter((item) => item !== url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarkSelectedUrls.value = [...bookmarkSelectedUrls.value, url];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAllBookmarks() {
|
||||||
|
const bookmarkUrls = currentBrowser.value?.bookmarks.map((bookmark) => bookmark.url) ?? [];
|
||||||
|
const allSelected =
|
||||||
|
bookmarkUrls.length > 0 &&
|
||||||
|
bookmarkUrls.every((url) => bookmarkSelectedUrls.value.includes(url));
|
||||||
|
bookmarkSelectedUrls.value = allSelected ? [] : bookmarkUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBookmarkModalProfileSelection(profileId: string) {
|
||||||
|
if (bookmarkModalSelectedProfileIds.value.includes(profileId)) {
|
||||||
|
bookmarkModalSelectedProfileIds.value = bookmarkModalSelectedProfileIds.value.filter(
|
||||||
|
(id) => id !== profileId,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarkModalSelectedProfileIds.value = [...bookmarkModalSelectedProfileIds.value, profileId];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAllBookmarkModalProfiles() {
|
||||||
|
if (!associatedProfilesModal.value?.isBookmark) return;
|
||||||
|
const profileIds = associatedProfilesModal.value.profiles.map((profile) => profile.id);
|
||||||
|
const allSelected =
|
||||||
|
profileIds.length > 0 &&
|
||||||
|
profileIds.every((profileId) => bookmarkModalSelectedProfileIds.value.includes(profileId));
|
||||||
|
bookmarkModalSelectedProfileIds.value = allSelected ? [] : profileIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bookmarkRemovalConfirmBookmarkCount() {
|
||||||
|
return bookmarkRemovalConfirmUrls.value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bookmarkRemovalConfirmProfileCount() {
|
||||||
|
return bookmarkRemovalConfirmProfileIds.value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestBookmarkRemoval(removals: BookmarkRemovalRequest[]) {
|
||||||
|
if (!removals.length) return;
|
||||||
|
|
||||||
|
bookmarkRemovalConfirmRemovals.value = removals;
|
||||||
|
bookmarkRemovalConfirmUrls.value = [...new Set(removals.map((item) => item.url))];
|
||||||
|
bookmarkRemovalConfirmProfileIds.value = [...new Set(removals.flatMap((item) => item.profileIds))];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBookmarkRemovalConfirmState() {
|
||||||
|
bookmarkRemovalConfirmRemovals.value = [];
|
||||||
|
bookmarkRemovalConfirmUrls.value = [];
|
||||||
|
bookmarkRemovalConfirmProfileIds.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteBookmarkFromAllProfiles(url: string) {
|
||||||
|
const bookmark = currentBrowser.value?.bookmarks.find((item) => item.url === url);
|
||||||
|
if (!bookmark) return;
|
||||||
|
|
||||||
|
requestBookmarkRemoval([
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
profileIds: [...bookmark.profileIds],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelectedBookmarks() {
|
||||||
|
const browser = currentBrowser.value;
|
||||||
|
if (!browser || !bookmarkSelectedUrls.value.length) return;
|
||||||
|
|
||||||
|
const removals = browser.bookmarks
|
||||||
|
.filter((bookmark) => bookmarkSelectedUrls.value.includes(bookmark.url))
|
||||||
|
.map((bookmark) => ({
|
||||||
|
url: bookmark.url,
|
||||||
|
profileIds: [...bookmark.profileIds],
|
||||||
|
}));
|
||||||
|
requestBookmarkRemoval(removals);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteBookmarkFromProfile(profileId: string) {
|
||||||
|
const modal = associatedProfilesModal.value;
|
||||||
|
if (!modal?.isBookmark || !modal.bookmarkUrl) return;
|
||||||
|
|
||||||
|
requestBookmarkRemoval([
|
||||||
|
{
|
||||||
|
url: modal.bookmarkUrl,
|
||||||
|
profileIds: [profileId],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelectedBookmarkProfiles() {
|
||||||
|
const modal = associatedProfilesModal.value;
|
||||||
|
if (!modal?.isBookmark || !modal.bookmarkUrl || !bookmarkModalSelectedProfileIds.value.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBookmarkRemoval([
|
||||||
|
{
|
||||||
|
url: modal.bookmarkUrl,
|
||||||
|
profileIds: [...bookmarkModalSelectedProfileIds.value],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBookmarkRemovalConfirm() {
|
||||||
|
if (bookmarkDeleteBusy.value) return;
|
||||||
|
resetBookmarkRemovalConfirmState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBookmarkRemovalResult() {
|
||||||
|
bookmarkRemovalResultOpen.value = false;
|
||||||
|
bookmarkRemovalResults.value = [];
|
||||||
|
bookmarkRemovalError.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyBookmarkRemovalResults(results: RemoveBookmarksResponse["results"]) {
|
||||||
|
const browser = currentBrowser.value;
|
||||||
|
if (!browser) return;
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
|
if (result.error || result.removedCount === 0) continue;
|
||||||
|
const bookmark = browser.bookmarks.find((item) => item.url === result.url);
|
||||||
|
if (!bookmark) continue;
|
||||||
|
|
||||||
|
bookmark.profileIds = bookmark.profileIds.filter((id) => id !== result.profileId);
|
||||||
|
bookmark.profiles = bookmark.profiles.filter((profile) => profile.id !== result.profileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.bookmarks = browser.bookmarks.filter((bookmark) => bookmark.profileIds.length > 0);
|
||||||
|
browser.stats.bookmarkCount = browser.bookmarks.length;
|
||||||
|
|
||||||
|
bookmarkSelectedUrls.value = bookmarkSelectedUrls.value.filter((selectedUrl) =>
|
||||||
|
browser.bookmarks.some((bookmark) => bookmark.url === selectedUrl),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (associatedProfilesModal.value?.isBookmark) {
|
||||||
|
const currentBookmark = browser.bookmarks.find(
|
||||||
|
(bookmark) => bookmark.url === associatedProfilesModal.value?.bookmarkUrl,
|
||||||
|
);
|
||||||
|
if (!currentBookmark) {
|
||||||
|
associatedProfilesModal.value = null;
|
||||||
|
bookmarkModalSelectedProfileIds.value = [];
|
||||||
|
} else {
|
||||||
|
associatedProfilesModal.value = {
|
||||||
|
...associatedProfilesModal.value,
|
||||||
|
title: `${currentBookmark.title} Profiles`,
|
||||||
|
profiles: currentBookmark.profiles,
|
||||||
|
};
|
||||||
|
bookmarkModalSelectedProfileIds.value = bookmarkModalSelectedProfileIds.value.filter((id) =>
|
||||||
|
currentBookmark.profiles.some((profile) => profile.id === id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmBookmarkRemoval() {
|
||||||
|
const browser = currentBrowser.value;
|
||||||
|
const removals = bookmarkRemovalConfirmRemovals.value.map((item) => ({
|
||||||
|
url: item.url,
|
||||||
|
profileIds: [...item.profileIds],
|
||||||
|
}));
|
||||||
|
if (!browser || !removals.length) return;
|
||||||
|
|
||||||
|
bookmarkDeleteBusy.value = true;
|
||||||
|
bookmarkRemovalError.value = "";
|
||||||
|
bookmarkRemovalResults.value = [];
|
||||||
|
bookmarkRemovalResultOpen.value = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const input: RemoveBookmarksInput = {
|
||||||
|
browserId: browser.browserId,
|
||||||
|
removals,
|
||||||
|
};
|
||||||
|
const result = await invoke<RemoveBookmarksResponse>("remove_bookmarks", { input });
|
||||||
|
applyBookmarkRemovalResults(result.results);
|
||||||
|
bookmarkRemovalResults.value = result.results;
|
||||||
|
resetBookmarkRemovalConfirmState();
|
||||||
|
bookmarkRemovalResultOpen.value = true;
|
||||||
|
} catch (removeError) {
|
||||||
|
resetBookmarkRemovalConfirmState();
|
||||||
|
bookmarkRemovalError.value =
|
||||||
|
removeError instanceof Error ? removeError.message : "Failed to remove bookmarks.";
|
||||||
|
bookmarkRemovalResultOpen.value = true;
|
||||||
|
} finally {
|
||||||
|
bookmarkDeleteBusy.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleExtensionSelection(extensionId: string) {
|
function toggleExtensionSelection(extensionId: string) {
|
||||||
@@ -729,6 +947,14 @@ export function useBrowserManager() {
|
|||||||
activeSection,
|
activeSection,
|
||||||
associatedProfilesModal,
|
associatedProfilesModal,
|
||||||
bookmarkSortKey,
|
bookmarkSortKey,
|
||||||
|
bookmarkDeleteBusy,
|
||||||
|
bookmarkModalSelectedProfileIds,
|
||||||
|
bookmarkRemovalConfirmBookmarkCount: computed(bookmarkRemovalConfirmBookmarkCount),
|
||||||
|
bookmarkRemovalConfirmProfileCount: computed(bookmarkRemovalConfirmProfileCount),
|
||||||
|
bookmarkRemovalError,
|
||||||
|
bookmarkRemovalResultOpen,
|
||||||
|
bookmarkRemovalResults,
|
||||||
|
bookmarkSelectedUrls,
|
||||||
browserConfigs,
|
browserConfigs,
|
||||||
browserMonogram,
|
browserMonogram,
|
||||||
browsers,
|
browsers,
|
||||||
@@ -739,6 +965,10 @@ export function useBrowserManager() {
|
|||||||
createCustomBrowserConfig,
|
createCustomBrowserConfig,
|
||||||
currentBrowser,
|
currentBrowser,
|
||||||
deleteCustomBrowserConfig,
|
deleteCustomBrowserConfig,
|
||||||
|
deleteBookmarkFromAllProfiles,
|
||||||
|
deleteBookmarkFromProfile,
|
||||||
|
deleteSelectedBookmarkProfiles,
|
||||||
|
deleteSelectedBookmarks,
|
||||||
deleteExtensionFromAllProfiles,
|
deleteExtensionFromAllProfiles,
|
||||||
deleteExtensionFromProfile,
|
deleteExtensionFromProfile,
|
||||||
deleteSelectedExtensionProfiles,
|
deleteSelectedExtensionProfiles,
|
||||||
@@ -787,10 +1017,17 @@ export function useBrowserManager() {
|
|||||||
sortedProfiles,
|
sortedProfiles,
|
||||||
closeExtensionRemovalConfirm,
|
closeExtensionRemovalConfirm,
|
||||||
closeExtensionRemovalResult,
|
closeExtensionRemovalResult,
|
||||||
|
closeBookmarkRemovalConfirm,
|
||||||
|
closeBookmarkRemovalResult,
|
||||||
confirmExtensionRemoval,
|
confirmExtensionRemoval,
|
||||||
|
confirmBookmarkRemoval,
|
||||||
cleanupHistoryForProfile,
|
cleanupHistoryForProfile,
|
||||||
|
toggleAllBookmarks,
|
||||||
toggleAllExtensions,
|
toggleAllExtensions,
|
||||||
|
toggleAllBookmarkModalProfiles,
|
||||||
toggleAllExtensionModalProfiles,
|
toggleAllExtensionModalProfiles,
|
||||||
|
toggleBookmarkModalProfileSelection,
|
||||||
|
toggleBookmarkSelection,
|
||||||
toggleExtensionModalProfileSelection,
|
toggleExtensionModalProfileSelection,
|
||||||
toggleExtensionSelection,
|
toggleExtensionSelection,
|
||||||
toggleAllHistoryProfiles,
|
toggleAllHistoryProfiles,
|
||||||
|
|||||||
@@ -72,15 +72,29 @@ export type RemoveExtensionsInput = {
|
|||||||
removals: ExtensionRemovalRequest[];
|
removals: ExtensionRemovalRequest[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RemoveBookmarksInput = {
|
||||||
|
browserId: string;
|
||||||
|
removals: BookmarkRemovalRequest[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ExtensionRemovalRequest = {
|
export type ExtensionRemovalRequest = {
|
||||||
extensionId: string;
|
extensionId: string;
|
||||||
profileIds: string[];
|
profileIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BookmarkRemovalRequest = {
|
||||||
|
url: string;
|
||||||
|
profileIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type RemoveExtensionsResponse = {
|
export type RemoveExtensionsResponse = {
|
||||||
results: RemoveExtensionResult[];
|
results: RemoveExtensionResult[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RemoveBookmarksResponse = {
|
||||||
|
results: RemoveBookmarkResult[];
|
||||||
|
};
|
||||||
|
|
||||||
export type RemoveExtensionResult = {
|
export type RemoveExtensionResult = {
|
||||||
extensionId: string;
|
extensionId: string;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
@@ -89,6 +103,15 @@ export type RemoveExtensionResult = {
|
|||||||
error: string | null;
|
error: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RemoveBookmarkResult = {
|
||||||
|
url: string;
|
||||||
|
profileId: string;
|
||||||
|
removedCount: number;
|
||||||
|
removedFiles: string[];
|
||||||
|
skippedFiles: string[];
|
||||||
|
error: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type AssociatedProfileSummary = {
|
export type AssociatedProfileSummary = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user