From 4a71f9b69d9b5b7a39d065762c62f9a9007674b7 Mon Sep 17 00:00:00 2001 From: maxrks777 Date: Mon, 30 Mar 2026 21:54:17 +0800 Subject: [PATCH] fix: set pasting image default folder & resolve macOS screenshot scaling issue & add settings shortcut Auto-scale 50% for Retina screenshots on macOS Add toggle in settings & keyboard shortcut (cmd+,/ctrl+,) Improve image quality with Lanczos3 scaling --- src-tauri/src/lib.rs | 93 ++++++++++++++++++++++++------ src/lib/MarkdownViewer.svelte | 4 ++ src/lib/components/Editor.svelte | 16 ++--- src/lib/components/Settings.svelte | 52 ++++++++++++----- src/lib/stores/settings.svelte.ts | 14 ++++- 5 files changed, 140 insertions(+), 39 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7ebf570..03c02e2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -491,7 +491,7 @@ fn clipboard_read_text() -> Result { } #[tauri::command] -fn clipboard_read_image() -> Result { +fn clipboard_read_image(macos_image_scaling: bool) -> Result { let mut clipboard = arboard::Clipboard::new().map_err(|e| e.to_string())?; let image = clipboard.get_image().map_err(|e| e.to_string())?; @@ -500,14 +500,73 @@ fn clipboard_read_image() -> Result { { let encoder = image::codecs::png::PngEncoder::new(&mut png_data); use image::ImageEncoder; - encoder - .write_image( - image.bytes.as_ref(), - image.width as u32, - image.height as u32, - image::ExtendedColorType::Rgba8, - ) - .map_err(|e| e.to_string())?; + + // Check if running on macOS and scale image if needed + #[cfg(target_os = "macos")] + { + if macos_image_scaling { + // Use image crate for high-quality scaling + use image::{DynamicImage, ImageBuffer, Rgba}; + + // Convert arboard Image to ImageBuffer + let mut img_buffer = ImageBuffer::new(image.width as u32, image.height as u32); + for (x, y, pixel) in img_buffer.enumerate_pixels_mut() { + let idx = (y * image.width as u32 + x) as usize * 4; + if idx + 3 < image.bytes.len() { + *pixel = Rgba([ + image.bytes[idx], + image.bytes[idx + 1], + image.bytes[idx + 2], + image.bytes[idx + 3] + ]); + } + } + + // Create DynamicImage + let dynamic_image = DynamicImage::ImageRgba8(img_buffer); + + // Resize with high-quality Lanczos3 filter + let resized = dynamic_image.resize( + (image.width / 2) as u32, + (image.height / 2) as u32, + image::imageops::FilterType::Lanczos3 + ); + + // Write the resized image + let resized_rgba = resized.to_rgba8(); + encoder + .write_image( + resized_rgba.as_raw(), + (image.width / 2) as u32, + (image.height / 2) as u32, + image::ExtendedColorType::Rgba8, + ) + .map_err(|e| e.to_string())?; + } else { + // Use original image if scaling is disabled + encoder + .write_image( + image.bytes.as_ref(), + image.width as u32, + image.height as u32, + image::ExtendedColorType::Rgba8, + ) + .map_err(|e| e.to_string())?; + } + } + + #[cfg(not(target_os = "macos"))] + { + // For other platforms, use the original image + encoder + .write_image( + image.bytes.as_ref(), + image.width as u32, + image.height as u32, + image::ExtendedColorType::Rgba8, + ) + .map_err(|e| e.to_string())?; + } } use base64::{engine::general_purpose, Engine as _}; @@ -515,8 +574,8 @@ fn clipboard_read_image() -> Result { } #[tauri::command] -fn save_image(parent_dir: String, filename: String, base64_data: String) -> Result { - let img_dir = Path::new(&parent_dir).join("img"); +fn save_image(parent_dir: String, filename: String, base64_data: String, image_directory: String) -> Result { + let img_dir = Path::new(&parent_dir).join(&image_directory); if !img_dir.exists() { fs::create_dir_all(&img_dir).map_err(|e| e.to_string())?; } @@ -537,12 +596,12 @@ fn save_image(parent_dir: String, filename: String, base64_data: String) -> Resu fs::write(&file_path, bytes).map_err(|e| e.to_string())?; - Ok(format!("img/{}", filename)) + Ok(format!("{}/{}", image_directory, filename)) } #[tauri::command] -fn copy_file_to_img(src_path: String, parent_dir: String) -> Result { - let img_dir = Path::new(&parent_dir).join("img"); +fn copy_file_to_img(src_path: String, parent_dir: String, image_directory: String) -> Result { + let img_dir = Path::new(&parent_dir).join(&image_directory); if !img_dir.exists() { fs::create_dir_all(&img_dir).map_err(|e| e.to_string())?; } @@ -569,7 +628,7 @@ fn copy_file_to_img(src_path: String, parent_dir: String) -> Result Result<(), String> { } #[tauri::command] -fn cleanup_empty_img_dir(parent_dir: String) -> Result<(), String> { - let img_dir = Path::new(&parent_dir).join("img"); +fn cleanup_empty_img_dir(parent_dir: String, image_directory: String) -> Result<(), String> { + let img_dir = Path::new(&parent_dir).join(&image_directory); if img_dir.exists() && img_dir.is_dir() { if fs::read_dir(&img_dir) .map_err(|e| e.to_string())? diff --git a/src/lib/MarkdownViewer.svelte b/src/lib/MarkdownViewer.svelte index 97cf2f3..1dde632 100644 --- a/src/lib/MarkdownViewer.svelte +++ b/src/lib/MarkdownViewer.svelte @@ -1603,6 +1603,10 @@ import { processMarkdownHtml } from './utils/markdown'; e.preventDefault(); zoomLevel = 100; } + if (cmdOrCtrl && key === ',') { + e.preventDefault(); + showSettings = !showSettings; + } } function pushScrollHistory() { diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index be08bb8..659cd1f 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -598,12 +598,12 @@ const last = managedImages[managedImages.length - 1]; if (!currentContent.includes(last.embed)) { managedImages.pop(); - const imgPath = `${last.parentDir}\\img\\${last.filename}`; - invoke("delete_file", { path: imgPath }) - .then(() => { - invoke("cleanup_empty_img_dir", { parentDir: last.parentDir }); - }) - .catch(console.error); + const imgPath = `${last.parentDir}/${settings.imageDirectory}/${last.filename}`; + invoke("delete_file", { path: imgPath }) + .then(() => { + invoke("cleanup_empty_img_dir", { parentDir: last.parentDir, imageDirectory: settings.imageDirectory }); + }) + .catch(console.error); } } }); @@ -695,7 +695,7 @@ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyV, async () => { try { // check for image in clipboard via Rust - const base64Image = await invoke("clipboard_read_image").catch(() => null) as string | null; + const base64Image = await invoke("clipboard_read_image", { macos_image_scaling: settings.macosImageScaling }).catch(() => null) as string | null; if (base64Image && tabManager.activeTab?.path) { const ext = "png"; // output of Rust command is always PNG const filename = `paste_${Date.now()}.${ext}`; @@ -708,6 +708,7 @@ parentDir, filename, base64Data: base64Image, + imageDirectory: settings.imageDirectory, })) as string; const escapedPath = relPath.replace(/ /g, "%20"); const embed = `![alt](${escapedPath})`; @@ -1004,6 +1005,7 @@ const relPath = (await invoke("copy_file_to_img", { srcPath: path, parentDir, + imageDirectory: settings.imageDirectory, })) as string; const escapedPath = relPath.replace(/ /g, "%20"); const embed = `![alt](${escapedPath})`; diff --git a/src/lib/components/Settings.svelte b/src/lib/components/Settings.svelte index b6fe612..49cae75 100644 --- a/src/lib/components/Settings.svelte +++ b/src/lib/components/Settings.svelte @@ -611,12 +611,36 @@
- + + +
+ +
+ + + Default: img +
+ + {#if settings.osType === 'macos'} +
+ + Reduce size by 50%
+ {/if} {/if} @@ -640,18 +664,18 @@ } .settings-modal { - background: var(--color-canvas-default); - border: 1px solid var(--color-border-default); - border-radius: 6px; - box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3); - width: 560px; - max-width: 90vw; - height: 420px; - display: flex; - flex-direction: column; - overflow: hidden; - font-family: var(--win-font); - } + background: var(--color-canvas-default); + border: 1px solid var(--color-border-default); + border-radius: 6px; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3); + width: 560px; + max-width: 90vw; + height: 420px; + display: flex; + flex-direction: column; + overflow: hidden; + font-family: var(--win-font); + } .settings-header { display: flex; diff --git a/src/lib/stores/settings.svelte.ts b/src/lib/stores/settings.svelte.ts index 2f947e0..538f72e 100644 --- a/src/lib/stores/settings.svelte.ts +++ b/src/lib/stores/settings.svelte.ts @@ -59,6 +59,8 @@ export class SettingsStore { pinnedToc = $state(false); tocSide = $state<'left' | 'right'>('left'); osType = $state('unknown'); + imageDirectory = $state('img'); + macosImageScaling = $state(true); editorFont = $state('Consolas'); editorFontSize = $state(14); @@ -89,6 +91,8 @@ export class SettingsStore { const savedEditorMaxWidth = localStorage.getItem('editor.maxWidth'); const savedPinnedToc = localStorage.getItem('editor.pinnedToc'); const savedTocSide = localStorage.getItem('editor.tocSide'); + const savedImageDirectory = localStorage.getItem('editor.imageDirectory'); + const savedMacosImageScaling = localStorage.getItem('editor.macosImageScaling'); const savedEditorFont = localStorage.getItem('editor.font'); const savedEditorFontSize = localStorage.getItem('editor.fontSize'); @@ -123,6 +127,8 @@ export class SettingsStore { if (savedEditorMaxWidth !== null) this.editorMaxWidth = parseFontSize(savedEditorMaxWidth, 80, 20, 500); if (savedPinnedToc !== null) this.pinnedToc = savedPinnedToc === 'true'; if (savedTocSide !== null) this.tocSide = savedTocSide as 'left' | 'right'; + if (savedImageDirectory !== null) this.imageDirectory = savedImageDirectory; + if (savedMacosImageScaling !== null) this.macosImageScaling = savedMacosImageScaling === 'true'; if (savedPreZenState !== null) { try { this.preZenState = JSON.parse(savedPreZenState); @@ -176,7 +182,9 @@ export class SettingsStore { localStorage.setItem('editor.startInEditor', String(this.startInEditor)); localStorage.setItem('editor.maxWidth', String(this.editorMaxWidth)); localStorage.setItem('editor.pinnedToc', String(this.pinnedToc)); - localStorage.setItem('editor.tocSide', this.tocSide); + localStorage.setItem('editor.tocSide', this.tocSide); + localStorage.setItem('editor.imageDirectory', this.imageDirectory); + localStorage.setItem('editor.macosImageScaling', String(this.macosImageScaling)); localStorage.setItem('editor.font', this.editorFont); localStorage.setItem('editor.fontSize', String(this.editorFontSize)); localStorage.setItem('preview.font', this.previewFont); @@ -289,6 +297,10 @@ export class SettingsStore { this.tocSide = this.tocSide === 'left' ? 'right' : 'left'; } + toggleMacosImageScaling() { + this.macosImageScaling = !this.macosImageScaling; + } + resetEditorMaxWidth() { this.editorMaxWidth = 80; }