diff --git a/.gitignore b/.gitignore index 82d4746..976b3b2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,8 +23,7 @@ nul data/ # Editor directories and files -.vscode/* -!.vscode/extensions.json +.vscode/ .idea .DS_Store *.suo @@ -32,3 +31,21 @@ data/ *.njsproj *.sln *.sw? + +# AI/LLM tooling +.claude/ +.cursor/ +.copilot/ +.codeium/ +.tabnine/ +.aider* +.continue/ + +# Trash +trash/ + +# Docs +docs/ + +# This file +.gitignore diff --git a/public/tauri.svg b/public/tauri.svg deleted file mode 100644 index 31b62c9..0000000 --- a/public/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 905df4b..9f560a2 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3703,6 +3703,21 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc61e4822b8f74d68278e09161d3e3fdd1b14b9eb781e24edccaabf10c420e8c" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.18", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + [[package]] name = "tauri-runtime" version = "2.10.0" @@ -4281,7 +4296,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vesper" -version = "0.1.0" +version = "1.0.1" dependencies = [ "serde", "serde_json", @@ -4290,6 +4305,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-fs", "tauri-plugin-opener", + "tauri-plugin-single-instance", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4894863..1a4b42c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,3 +22,6 @@ tauri-plugin-fs = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +tauri-plugin-single-instance = "2" + diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7068e58..e3cb705 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -39,6 +39,31 @@ fn save_window_state(state: &WindowState) { } } +#[derive(Serialize, Clone)] +struct CliFile { + path: String, + content: String, +} + +/// Try to read a markdown file from the given path string. +fn read_md_file(file_arg: &str) -> Option { + let path = PathBuf::from(file_arg); + if !path.is_file() { return None; } + let ext = path.extension()?.to_str()?.to_lowercase(); + if ext != "md" && ext != "markdown" && ext != "txt" { return None; } + let content = fs::read_to_string(&path).ok()?; + Some(CliFile { + path: path.to_string_lossy().to_string(), + content, + }) +} + +/// Returns the file path + content if the app was launched with a .md/.markdown/.txt argument. +#[tauri::command] +fn get_cli_file() -> Option { + env::args().nth(1).and_then(|a| read_md_file(&a)) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let data_dir = get_data_dir(); @@ -51,9 +76,23 @@ pub fn run() { ); tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, args, _cwd| { + // Second instance launched with a file — send it to the existing window + if let Some(file_arg) = args.get(1) { + if let Some(cli_file) = read_md_file(file_arg) { + let _ = app.emit("open-file", cli_file); + } + } + // Focus the existing window + if let Some(window) = app.get_webview_window("main") { + let _ = window.unminimize(); + let _ = window.set_focus(); + } + })) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) + .invoke_handler(tauri::generate_handler![get_cli_file]) .setup(|app| { let state = load_window_state(); let window = app.get_webview_window("main").unwrap(); @@ -64,35 +103,12 @@ pub fn run() { )); } if let (Some(w), Some(h)) = (state.width, state.height) { - let _ = window.set_size(tauri::Size::Physical( - tauri::PhysicalSize::new(w, h), - )); + let _ = window.set_size(tauri::Size::Physical(tauri::PhysicalSize::new(w, h))); } if let Some(true) = state.maximized { let _ = window.maximize(); } - // If launched with a file argument (e.g. double-clicking a .md file), - // emit the path to the frontend so it can open it as a tab. - let args: Vec = env::args().collect(); - if let Some(file_arg) = args.get(1) { - let path = PathBuf::from(file_arg); - if path.is_file() { - if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - let ext_lower = ext.to_lowercase(); - if ext_lower == "md" || ext_lower == "markdown" || ext_lower == "txt" { - let path_str = path.to_string_lossy().to_string(); - let win = window.clone(); - // Emit after a short delay so the frontend has time to set up listeners - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_millis(500)); - let _ = win.emit("open-file", path_str); - }); - } - } - } - } - Ok(()) }) .on_window_event(|window, event| { diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test-markdown.md b/test-markdown.md deleted file mode 100644 index 0906ff9..0000000 --- a/test-markdown.md +++ /dev/null @@ -1,332 +0,0 @@ -# Markdown Combinations Test - -This file tests all possible combinations of markdown elements. - ---- - -## 1. Lists with Text Formatting - -### Bold in List - -- **Bold item 1** -- **Bold item 2** with more text -- **Bold item 3** and *italic* and `code` - -### Italic in List - -- *Italic item 1* -- *Italic item 2* with text -- *Italic* and **bold** together - -### Code in List - -- `inline code` item -- Code with `const x = 1` variable -- Command `npm install` - -### Mixed Formatting in List - -- **Bold** and *italic* and `code` all together -- ***Bold italic*** with normal text -- ~~Strikethrough~~ and **bold** -- ==Highlighted== and *italic* - ---- - -## 2. Headings in Lists - -- ### Heading 3 in list -- #### Heading 4 in list -- ##### Heading 5 in list - -1. ### Heading 3 in ordered list -2. #### Heading 4 in ordered list -3. ##### Heading 5 in ordered list - ---- - -## 3. Blockquotes with Lists - -> ### Blockquote with list: -> - Item 1 -> - Item 2 -> - Item 3 - -> **Bold blockquote** with list: -> 1. First -> 2. Second -> 3. Third - -> *Italic blockquote* with `code`: -> - Item with `inline code` -> - Another with **bold** - ---- - -## 4. Lists in Blockquotes - -> This is a blockquote with a nested list: -> - First level -> - Second level -> - Third level -> - Fourth level -> - Back to first - -> Another blockquote with mixed content: -> - **Bold item** -> - *Italic item* -> - `Code item` -> - [Link item](https://example.com) - ---- - -## 5. Links in Various Elements - -### Links in Lists - -- [Link](https://example.com) in list -- [Link with title](https://example.com "Title") in list -- [Reference link][ref] in list - -[ref]: https://example.com - -### Links in Blockquotes - -> [Link in blockquote](https://example.com) -> **[Bold link](https://example.com)** -> *[[Italic link](https://example.com)* - -### Links in Code - -- `Link` in list -- `[link](url)` as code - ---- - -## 6. Images in Various Elements - -### Images in Lists - -- ![Image](https://via.placeholder.com/100x50.png) in list -- ![Alt text](https://via.placeholder.com/100x50.png "Image title") - -### Images in Blockquotes - -> ![Image in blockquote](https://via.placeholder.com/100x50.png) - ---- - -## 7. Code Blocks with Formatting - -### Code Block with Bold/Italic - -``` -**This is bold in code** -*This is italic in code* -`inline code in code block` -``` - -### Code Block Inside List - -- ``` - // Code block in list - const x = 1; - ``` -- Another item - ---- - -## 8. Tables with Formatting - -| Header | **Bold Header** | *Italic Header* | -|--------|-----------------|-----------------| -| Cell | **Bold Cell** | *Italic Cell* | -| `Code` | **Bold** + `code` | *Italic* + `code` | - ---- - -## 9. Nested Lists with All Formatting - -- Level 1 **bold** - - Level 2 *italic* - - Level 3 `code` - - Level 4 ~~strikethrough~~ - - Level 5 ==highlight== - -1. Ordered 1 **bold** - 1. Ordered 1.1 *italic* - 1. Ordered 1.1.1 `code` - ---- - -## 10. Complex Combinations - -### Blockquote > List > Blockquote - -> - Item 1 -> > Nested blockquote -> > With **bold** -> - Item 2 - -### List > Blockquote > List - -- Item 1 - > Blockquote inside list - > With *italic* -- Item 2 - -### List > Code > List - -- Item 1 - ``` - // Code block nested - const x = 1; - ``` -- Item 2 - -### Task List with Formatting - -- [x] **Completed** task with *italic* -- [ ] *Incomplete* with `code` -- [x] ==Highlighted== and **bold** and *italic* - ---- - -## 11. Heading + List Combinations - -### Heading followed by formatted list - -#### Subheading - -- **Bold item** -- *Italic item* -- `Code item` - -### Multiple headings in sequence - -## Section 1 - -- Item 1 -- Item 2 - -## Section 2 - -1. Ordered 1 -2. Ordered 2 - -### Heading within list (edge case) - -- Normal item -- #### Not a heading (just text) -- Normal item - ---- - -## 12. Links + Code + Bold + Italic combinations - -- **[Bold link](https://example.com)** -- *[Italic link](https://example.com)* -- [`Code link`](https://example.com) -- **[Link](https://example.com)** with `code` and *italic* -- ***[All combined](https://example.com)*** with `more code` - ---- - -## 13. Tables + Lists combinations - -| Table | With | Formatting | -|-------|------|-----------| -| - List in table | - Another | - Item | -| **Bold** | *Italic* | `Code` | - -- Table below list: - -| Col1 | Col2 | -|------|------| -| A | B | - -- List below table: - - | Col1 | Col2 | - |------|------| - | C | D | - ---- - -## 14. All Elements in One Paragraph - -This is a paragraph with **bold**, *italic*, ***bold italic***, `inline code`, [a link](https://example.com), ![an image](https://via.placeholder.com/50x20.png), ~~strikethrough~~, ==highlight==, and ^superscript^, all in one paragraph. It's quite long and should test how your renderer handles multiple formatting elements combined. - ---- - -## 15. Edge Case: Empty Elements - -- -- Item after empty - -> -> Empty blockquote - ---- - -## 16. Deep Nesting - -1. Level 1 - - Level 2 - - Level 3 - - Level 4 - - Level 5 - - Level 6 - - **Deep bold** - - *Deep italic* - - `Deep code` - ---- - -## 17. Code Blocks with Language and Formatting - -```javascript -// JavaScript with **fake bold** (just text) -const greet = (name) => { - return `Hello, ${name}!`; // Template literal -}; -``` - -```python -# Python with *italic* comments -def hello(): - """Docstring with **bold**""" - return "Hello" -``` - -```html - -
-

Paragraph with *italic*

-
-``` - ---- - -## 18. Final Complex Example - -> ### Blockquote Heading -> -> This is a blockquote that contains: -> -> - A **bold** list item -> - An *italic* list item -> - A `code` list item -> - A [link](https://example.com) item -> -> And then continues with more text that has **bold**, *italic*, and `code` formatting. -> -> It also has a nested list: -> -> 1. First nested **bold** item -> 2. Second nested *italic* item -> 3. Third nested `code` item - ---- - -This covers virtually every possible combination of markdown elements!