Implement "Copy only" feature for menu items to enhance user control over clipboard operations and update related settings

This commit is contained in:
Sergey Kurdin 2025-06-13 11:36:49 -04:00
parent cc77ab3ddf
commit 0af0554cc9
6 changed files with 127 additions and 21 deletions

View File

@ -0,0 +1,5 @@
---
'pastebar-app-ui': patch
---
Added user preference to copy only menu items to clipboard without auto-pasting

View File

@ -145,6 +145,7 @@ function App() {
clipTextMinLength: settings.clipTextMinLength?.valueInt,
clipTextMaxLength: settings.clipTextMaxLength?.valueInt,
isImageCaptureDisabled: settings.isImageCaptureDisabled?.valueBool,
isMenuItemCopyOnlyEnabled: settings.isMenuItemCopyOnlyEnabled?.valueBool,
isAutoClearSettingsEnabled: settings.isAutoClearSettingsEnabled?.valueBool,
autoClearSettingsDuration: settings.autoClearSettingsDuration?.valueInt,
autoClearSettingsDurationType:

View File

@ -1,6 +1,7 @@
App restart required: App restart required
Application Starts with Main Window Hidden: Application Starts with Main Window Hidden
Change: Change
Copy only from menu items: Copy only from menu items
Display navbar items only when the mouse hovers over the navigation bar to minimize visible UI elements: Display navbar items only when the mouse hovers over the navigation bar to minimize visible UI elements
Global System OS Hotkeys: Global System OS Hotkeys
Hide Collections Navbar: Hide Collections Navbar
@ -22,3 +23,5 @@ Show collections menu on the navbar: Show collections menu on the navbar
Show navbar elements on hover only: Show navbar elements on hover only
Show/Hide Main App Window: Show/Hide Main App Window
Show/Hide Quick Paste Window: Show/Hide Quick Paste Window
? When enabled, clicking menu items will only copy content to clipboard instead of auto-pasting. This gives you more control over when and where content is pasted.
: When enabled, clicking menu items will only copy content to clipboard instead of auto-pasting. This gives you more control over when and where content is pasted.

View File

@ -11,7 +11,7 @@ import {
uiStoreAtom,
} from '~/store'
import { useAtomValue } from 'jotai'
import { ChevronDown, ChevronUp, MessageSquare, MessageSquareDashed } from 'lucide-react'
import { MessageSquare, MessageSquareDashed } from 'lucide-react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
@ -71,6 +71,8 @@ export default function UserPreferences() {
hotKeysShowHideQuickPasteWindow,
setHotKeysShowHideMainAppWindow,
setHotKeysShowHideQuickPasteWindow,
isMenuItemCopyOnlyEnabled,
setIsMenuItemCopyOnlyEnabled,
} = useAtomValue(settingsStoreAtom)
const { setFontSize, fontSize, setIsSwapPanels, isSwapPanels, returnRoute, isMacOSX } =
@ -529,6 +531,32 @@ export default function UserPreferences() {
</Card>
</Box>
<Box className="animate-in fade-in max-w-xl mt-4">
<Card
className={`${
!isMenuItemCopyOnlyEnabled && '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('Copy only from menu items', { ns: 'settings2' })}
</CardTitle>
<Switch
checked={isMenuItemCopyOnlyEnabled}
className="ml-auto"
onCheckedChange={() => {
setIsMenuItemCopyOnlyEnabled(!isMenuItemCopyOnlyEnabled)
}}
/>
</CardHeader>
<CardContent>
<Text className="text-sm text-muted-foreground">
{t('When enabled, clicking menu items will only copy content to clipboard instead of auto-pasting. This gives you more control over when and where content is pasted.', { ns: 'settings2' })}
</Text>
</CardContent>
</Card>
</Box>
<Box className="animate-in fade-in max-w-xl mt-4">
<Card
className={`${

View File

@ -86,6 +86,7 @@ type Settings = {
clipTextMinLength: number
clipTextMaxLength: number
isImageCaptureDisabled: boolean
isMenuItemCopyOnlyEnabled: boolean
}
type Constants = {
@ -161,6 +162,7 @@ export interface SettingsStoreState {
setIsHideCollectionsOnNavBar: (isEnabled: boolean) => void
setIsShowNavBarItemsOnHoverOnly: (isEnabled: boolean) => void
setIsImageCaptureDisabled: (isEnabled: boolean) => void
setIsMenuItemCopyOnlyEnabled: (isEnabled: boolean) => void
hashPassword: (pass: string) => Promise<string>
isNotTourCompletedOrSkipped: (tourName: string) => boolean
verifyPassword: (pass: string, hash: string) => Promise<boolean>
@ -246,6 +248,7 @@ const initialState: SettingsStoreState & Settings = {
clipTextMinLength: 0,
clipTextMaxLength: 5000,
isImageCaptureDisabled: false,
isMenuItemCopyOnlyEnabled: false,
CONST: {
APP_DETECT_LANGUAGES_SUPPORTED: [],
},
@ -303,6 +306,7 @@ const initialState: SettingsStoreState & Settings = {
setClipTextMinLength: () => {},
setClipTextMaxLength: () => {},
setIsImageCaptureDisabled: () => {},
setIsMenuItemCopyOnlyEnabled: () => {},
initConstants: () => {},
setAppDataDir: () => {}, // Keep if used for other general app data
setCustomDbPath: () => {},
@ -375,7 +379,8 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
name === 'isHistoryEnabled' ||
name === 'userSelectedLanguage' ||
name === 'isAppLocked' ||
name === 'isImageCaptureDisabled'
name === 'isImageCaptureDisabled' ||
name === 'isMenuItemCopyOnlyEnabled'
) {
invoke('build_system_menu')
}
@ -611,6 +616,9 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
setIsImageCaptureDisabled: async (isEnabled: boolean) => {
return get().updateSetting('isImageCaptureDisabled', isEnabled)
},
setIsMenuItemCopyOnlyEnabled: async (isEnabled: boolean) => {
return get().updateSetting('isMenuItemCopyOnlyEnabled', isEnabled)
},
isNotTourCompletedOrSkipped: (tourName: string) => {
const { appToursCompletedList, appToursSkippedList } = get()
return (

View File

@ -718,10 +718,24 @@ async fn main() {
debug_output(|| {
println!("system tray received a click on item id{:?} ", item_id);
});
let w = app.get_window("main").unwrap();
let state: tauri::State<DbItems> = app.state::<DbItems>();
let db_items_state = state.0.lock().unwrap();
// Get the copy-only setting
let app_settings = app.state::<Mutex<HashMap<String, Setting>>>();
let settings_map = app_settings.lock().unwrap();
let is_copy_only = settings_map
.get("isMenuItemCopyOnlyEnabled")
.and_then(|setting| setting.value_bool)
.unwrap_or(false);
debug_output(|| {
println!("Looking for item with item_id: {:?}", item_id);
println!("is_copy_only: {:?}", is_copy_only);
});
let item_opt = db_items_state.iter().find(|&item| item.item_id == item_id);
if let Some(item) = item_opt {
@ -743,26 +757,54 @@ async fn main() {
// 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 img_data =
std::fs::read(&absolute_path).expect("Failed to read image from path");
let base64_image = base64::encode(&img_data);
write_image_to_clipboard(base64_image).expect("Failed to write image to clipboard");
}
if item.is_link.unwrap_or(false) {
let url = item.value.as_deref().unwrap_or("");
let _ = opener::open(ensure_url_or_email_prefix(url))
.map_err(|e| format!("Failed to open url: {}", e));
if is_copy_only {
// Copy URL to clipboard instead of opening it
debug_output(|| {
println!("Copying URL to clipboard: {}", url);
});
manager
.write_text(url)
.expect("failed to write to clipboard");
} else {
let _ = opener::open(ensure_url_or_email_prefix(url))
.map_err(|e| format!("Failed to open url: {}", e));
}
} else if item.is_path.unwrap_or(false) {
let path = item.value.as_deref().unwrap_or("");
let _ = opener::open(path).map_err(|e| format!("Failed to open path: {}", e));
if is_copy_only {
// Copy path to clipboard instead of opening it
debug_output(|| {
println!("Copying path to clipboard: {}", path);
});
manager
.write_text(path)
.expect("failed to write to clipboard");
} else {
let _ = opener::open(path).map_err(|e| format!("Failed to open path: {}", e));
}
} else {
if item.value.as_deref().unwrap_or("").is_empty() {
debug_output(|| {
println!("Copying item name to clipboard: {}", &item.name);
});
manager
.write_text(&item.name)
.expect("failed to write to clipboard");
} else if let Some(ref item_value) = item.value {
let text_to_copy = remove_special_bbcode_tags(item_value);
debug_output(|| {
println!("Copying item value to clipboard: {}", text_to_copy);
});
manager
.write_text(remove_special_bbcode_tags(item_value))
.write_text(text_to_copy)
.expect("failed to write to clipboard");
}
}
@ -782,12 +824,15 @@ async fn main() {
return true;
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
if query_accessibility_permissions() {
VKey.press_paste();
} else {
w.show().unwrap();
w.emit("macosx-permissions-modal", "show").unwrap();
// Only auto-paste if not in copy-only mode
if !is_copy_only {
#[cfg(any(target_os = "windows", target_os = "macos"))]
if query_accessibility_permissions() {
VKey.press_paste();
} else {
w.show().unwrap();
w.emit("macosx-permissions-modal", "show").unwrap();
}
}
w.emit("execMenuItemById", item_id).unwrap();
@ -801,11 +846,23 @@ async fn main() {
let delay = if cfg!(target_os = "windows") { 3 } else { 0 };
rt.block_on(async {
copy_paste_clip_item_from_menu(app_clone, item_id_string, delay).await;
if is_copy_only {
// For copy-only mode, use copy function instead of copy-paste
clipboard_commands::copy_clip_item(app_clone, item_id_string, true).await;
} else {
copy_paste_clip_item_from_menu(app_clone, item_id_string, delay).await;
}
});
});
}
} else {
debug_output(|| {
println!(
"Item not found in db_items_state, checking recent history for: {:?}",
item_id
);
});
let recent_history_state: tauri::State<DbRecentHistoryItems> =
app.state::<DbRecentHistoryItems>();
let db_recent_history_items_state = recent_history_state.0.lock().unwrap();
@ -838,7 +895,8 @@ async fn main() {
// 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 img_data =
std::fs::read(&absolute_path).expect("Failed to read image from path");
let base64_image = base64::encode(&img_data);
write_image_to_clipboard(base64_image).expect("Failed to write image to clipboard");
@ -867,12 +925,15 @@ async fn main() {
return true;
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
if query_accessibility_permissions() {
VKey.press_paste();
} else {
w.show().unwrap();
w.emit("macosx-permissions-modal", "show").unwrap();
// Only auto-paste if not in copy-only mode
if !is_copy_only {
#[cfg(any(target_os = "windows", target_os = "macos"))]
if query_accessibility_permissions() {
VKey.press_paste();
} else {
w.show().unwrap();
w.emit("macosx-permissions-modal", "show").unwrap();
}
}
w.emit("execMenuItemById", item_id).unwrap();