From b53f8e5961078de14e315661a1ef9812217d99c6 Mon Sep 17 00:00:00 2001 From: vanzan01 Date: Sun, 20 Jul 2025 10:18:25 +0800 Subject: [PATCH 1/7] fix: Add macOS file association support using RunEvent::Opened - Implement proper macOS file opening via RunEvent::Opened event - Add OpenedFileState to store file paths from OS file associations - Add get_opened_file command to retrieve files opened via "Open With" - Handle both file:// URLs and direct file paths from macOS - Maintain fallback support for command line arguments on other platforms - Add dual event system (direct emission + state-based retrieval) - Include proper file path validation for security This fixes the issue where double-clicking markdown files on macOS would launch the app but not open the file. The solution uses Tauri's RunEvent::Opened which captures the NSApplication delegate events that macOS sends when files are opened via file associations. --- src-tauri/src/lib.rs | 94 ++++++++++++++++++++++++++++++++++++++++++-- src/main.js | 40 +++++++++---------- 2 files changed, 111 insertions(+), 23 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ec7dd58..6047662 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,7 +4,7 @@ use std::env; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use notify::{Watcher, RecommendedWatcher, RecursiveMode, Event, EventKind}; -use tauri::{AppHandle, Emitter}; +use tauri::{AppHandle, Emitter, Manager, RunEvent}; use syntect::parsing::SyntaxSet; use syntect::highlighting::ThemeSet; use syntect::html::highlighted_html_for_string; @@ -20,6 +20,29 @@ const MAX_HTML_SIZE: usize = 100 * 1024 * 1024; // 100MB HTML limit for temp fil // Global state for file watcher type WatcherState = Arc>>; +// App state to store file opened via "Open With" on macOS +#[derive(Default)] +struct OpenedFileState { + file_path: Arc>>, +} + +impl OpenedFileState { + fn set_file(&self, path: String) { + let mut file_path = self.file_path.lock().unwrap(); + *file_path = Some(path); + } + + fn get_file(&self) -> Option { + let file_path = self.file_path.lock().unwrap(); + file_path.clone() + } + + fn clear_file(&self) { + let mut file_path = self.file_path.lock().unwrap(); + *file_path = None; + } +} + // Security utilities for temp file handling fn create_secure_temp_file(content: &str) -> Result { // Basic size validation only @@ -386,6 +409,16 @@ fn get_launch_args() -> Vec { env::args().collect() } +#[tauri::command] +fn get_opened_file(state: tauri::State) -> Option { + let file = state.get_file(); + if file.is_some() { + // Clear the file after retrieving it so it's only opened once + state.clear_file(); + } + file +} + #[tauri::command] fn export_html(content: String, title: String) -> Result { let html_template = format!(r#" @@ -664,23 +697,78 @@ async fn save_temp_html_and_open(html_content: String) -> Result<(), String> { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let watcher_state: WatcherState = Arc::new(Mutex::new(None)); + let opened_file_state = OpenedFileState::default(); tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .manage(watcher_state) + .manage(opened_file_state) .invoke_handler(tauri::generate_handler![ greet, parse_markdown, read_markdown_file, get_launch_args, + get_opened_file, start_watching_file, stop_watching_file, export_html, read_file_content, save_temp_html_and_open ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .setup(|app| { + // Check command line args during setup (fallback for other platforms) + let setup_args = env::args().collect::>(); + + // Check for markdown files in args + for arg in setup_args.iter().skip(1) { + if arg.ends_with(".md") || arg.ends_with(".markdown") || + arg.ends_with(".mdown") || arg.ends_with(".mkd") { + // For command line arguments, store in the opened file state + let opened_file_state = app.state::(); + opened_file_state.set_file(arg.clone()); + break; + } + } + + Ok(()) + }) + .build(tauri::generate_context!()) + .expect("error while building tauri application") + .run(|app_handle, event| { + match event { + RunEvent::Opened { urls } => { + // Find the first markdown file in the opened URLs + for url in urls { + // Convert URL to string and handle file:// URLs + let url_str = url.as_str(); + let file_path = if url_str.starts_with("file://") { + url_str.trim_start_matches("file://").to_string() + } else { + url_str.to_string() + }; + + if file_path.ends_with(".md") || file_path.ends_with(".markdown") || + file_path.ends_with(".mdown") || file_path.ends_with(".mkd") { + // Validate the file path for security + if let Ok(validated_path) = validate_file_path(&file_path) { + let validated_str = validated_path.to_string_lossy().to_string(); + + // Store the opened file in app state + let opened_file_state = app_handle.state::(); + opened_file_state.set_file(validated_str.clone()); + + // Also try to emit the event to the frontend if it's ready + let _ = app_handle.emit("file-opened-via-os", &validated_str); + } + break; + } + } + } + _ => { + // Handle other events as needed + } + } + }); } diff --git a/src/main.js b/src/main.js index c4c8faf..dffb410 100644 --- a/src/main.js +++ b/src/main.js @@ -1176,18 +1176,25 @@ async function loadMarkdownFile(filePath) { async function checkLaunchArgs() { try { + // First check if there's a file opened via "Open With" (macOS RunEvent::Opened) + const openedFile = await invoke('get_opened_file'); + if (openedFile) { + await loadMarkdownFile(openedFile); + return true; + } + + // Fallback: check command line arguments (for other platforms or direct execution) const args = await invoke('get_launch_args'); - console.log('Launch args:', args); // Look for markdown file in arguments (skip first arg which is the executable) for (let i = 1; i < args.length; i++) { const arg = args[i]; if (arg.match(/\.(md|markdown|mdown|mkd)$/i)) { - console.log('Found markdown file in args:', arg); await loadMarkdownFile(arg); return true; } } + return false; } catch (error) { console.error('Error checking launch args:', error); @@ -2158,24 +2165,6 @@ async function getOriginalMarkdownContent() { } window.addEventListener("DOMContentLoaded", async () => { - console.log('🚀 DOM Content Loaded'); - console.log('🔍 Checking Tauri availability...'); - console.log('window.__TAURI__:', !!window.__TAURI__); - console.log('window.__TAURI__.event:', !!window.__TAURI__?.event); - console.log('window.__TAURI__.window:', !!window.__TAURI__?.window); - console.log('window.__TAURI__.webview:', !!window.__TAURI__?.webview); - console.log('window.__TAURI__.core:', !!window.__TAURI__?.core); - - // Debug library availability - console.log('🔍 Checking libraries...'); - console.log('DOMPurify:', typeof DOMPurify !== 'undefined'); - console.log('mermaid:', typeof mermaid !== 'undefined'); - console.log('saveAs:', typeof saveAs !== 'undefined'); - console.log('window.docxReady:', window.docxReady); - console.log('window.generateDocxFromMarkdown:', typeof window.generateDocxFromMarkdown !== 'undefined'); - console.log('docx library:', typeof docx !== 'undefined'); - console.log('highlight.js:', typeof hljs !== 'undefined'); - console.log('window.highlightJsReady:', window.highlightJsReady); // Get DOM elements openFileBtn = document.querySelector('#open-file-btn'); @@ -2283,6 +2272,17 @@ window.addEventListener("DOMContentLoaded", async () => { handleFileChange(event.payload); }); + // Listen for file opened via OS "Open With" events + await listen('file-opened-via-os', async (event) => { + const filePath = event.payload; + if (filePath) { + // Small delay to ensure UI is ready + setTimeout(async () => { + await loadMarkdownFile(filePath); + }, 100); + } + }); + // Check for file associations (launch arguments) const foundFile = await checkLaunchArgs(); From 9dfb36a2a8ea929c022a092ee594cca49fe704ec Mon Sep 17 00:00:00 2001 From: vanzan01 Date: Sun, 20 Jul 2025 10:22:50 +0800 Subject: [PATCH 2/7] ci: Add cross-platform test workflow for branch validation - Test builds on Ubuntu, Windows, and macOS - Runs on pushes and pull requests - Validates no regressions across platforms --- .github/workflows/test.yml | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..30c674f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: Test Build + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test-build: + strategy: + matrix: + platform: [ubuntu-20.04, windows-latest, macos-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install dependencies (Ubuntu only) + if: matrix.platform == 'ubuntu-20.04' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Install npm dependencies + run: npm ci + + - name: Build Tauri app + run: npm run tauri build + + - name: Test basic functionality + run: | + echo "Build completed successfully for ${{ matrix.platform }}" + # Could add more specific tests here \ No newline at end of file From 896b77192e3258415bd9ec0357389214bc72beda Mon Sep 17 00:00:00 2001 From: vanzan01 Date: Sun, 20 Jul 2025 10:24:56 +0800 Subject: [PATCH 3/7] fix: Update test workflow to run on feature branches - Add 'fix/*' and 'feature/*' branch patterns to push triggers - Enables CI validation on feature branches before PR creation --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30c674f..e1a8a1f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [ main ] push: - branches: [ main ] + branches: [ main, 'fix/*', 'feature/*' ] jobs: test-build: From 8b962c8d8c01938d206e95b414a7acbd0edffe22 Mon Sep 17 00:00:00 2001 From: vanzan01 Date: Sun, 20 Jul 2025 10:35:55 +0800 Subject: [PATCH 4/7] fix: Make RunEvent::Opened macOS/iOS specific with conditional compilation - Add #[cfg(any(target_os = "macos", target_os = "ios"))] to RunEvent::Opened handler - Fixes Windows/Linux build failure where RunEvent::Opened variant doesn't exist - Maintains cross-platform compatibility while enabling macOS file association fix --- src-tauri/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6047662..bb19644 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -738,6 +738,8 @@ pub fn run() { .expect("error while building tauri application") .run(|app_handle, event| { match event { + // RunEvent::Opened is only available on macOS and iOS + #[cfg(any(target_os = "macos", target_os = "ios"))] RunEvent::Opened { urls } => { // Find the first markdown file in the opened URLs for url in urls { From 0abbcfa2c6ef7c577f47a729db766360c45c19e1 Mon Sep 17 00:00:00 2001 From: vanzan01 Date: Sun, 20 Jul 2025 10:52:17 +0800 Subject: [PATCH 5/7] fix: Remove compiler warnings for cross-platform compatibility - Make RunEvent import conditional for macOS/iOS only - Prefix unused app_handle parameter with underscore - Ensures clean builds across all platforms --- src-tauri/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index bb19644..a585ec5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,7 +4,9 @@ use std::env; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use notify::{Watcher, RecommendedWatcher, RecursiveMode, Event, EventKind}; -use tauri::{AppHandle, Emitter, Manager, RunEvent}; +use tauri::{AppHandle, Emitter, Manager}; +#[cfg(any(target_os = "macos", target_os = "ios"))] +use tauri::RunEvent; use syntect::parsing::SyntaxSet; use syntect::highlighting::ThemeSet; use syntect::html::highlighted_html_for_string; @@ -736,11 +738,12 @@ pub fn run() { }) .build(tauri::generate_context!()) .expect("error while building tauri application") - .run(|app_handle, event| { + .run(|_app_handle, event| { match event { // RunEvent::Opened is only available on macOS and iOS #[cfg(any(target_os = "macos", target_os = "ios"))] RunEvent::Opened { urls } => { + let app_handle = _app_handle; // Find the first markdown file in the opened URLs for url in urls { // Convert URL to string and handle file:// URLs From dad83a62d53a64464eef338663d5d4702793df07 Mon Sep 17 00:00:00 2001 From: vanzan01 Date: Sun, 20 Jul 2025 10:52:57 +0800 Subject: [PATCH 6/7] fix: Update test workflow to match build targets (Windows + macOS only) - Remove Ubuntu from test matrix to match production build.yml - Add fail-fast: false for better visibility of platform-specific issues - Aligns test workflow with actual release targets --- .github/workflows/test.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1a8a1f..6c53907 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,8 +9,9 @@ on: jobs: test-build: strategy: + fail-fast: false matrix: - platform: [ubuntu-20.04, windows-latest, macos-latest] + platform: [windows-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 @@ -24,11 +25,6 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable - - name: Install dependencies (Ubuntu only) - if: matrix.platform == 'ubuntu-20.04' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - name: Install npm dependencies run: npm ci From 62305a6d1bfe9cf3a2ff09e608f9487d0e2cadab Mon Sep 17 00:00:00 2001 From: vanzan01 Date: Sun, 20 Jul 2025 11:14:14 +0800 Subject: [PATCH 7/7] feat: Add artifact uploads to test workflow for bundle testing - Upload Windows and macOS bundles as artifacts - 7-day retention for testing branch builds - Enables downloading and testing actual installers --- .github/workflows/test.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c53907..4dfa29d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,23 @@ jobs: - name: Build Tauri app run: npm run tauri build - - name: Test basic functionality - run: | - echo "Build completed successfully for ${{ matrix.platform }}" - # Could add more specific tests here \ No newline at end of file + - name: Upload Windows artifacts + if: matrix.platform == 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: test-windows-artifacts + path: | + src-tauri/target/release/bundle/msi/*.msi + src-tauri/target/release/bundle/nsis/*.exe + src-tauri/target/release/markdown-viewer.exe + retention-days: 7 + + - name: Upload macOS artifacts + if: matrix.platform == 'macos-latest' + uses: actions/upload-artifact@v4 + with: + name: test-macos-artifacts + path: | + src-tauri/target/release/bundle/dmg/*.dmg + src-tauri/target/release/bundle/macos/*.app + retention-days: 7 \ No newline at end of file