diff --git a/package.json b/package.json index 3dddf08..33a873f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "helixnotes", "private": true, "license": "AGPL-3.0-or-later", - "version": "1.0.5", + "version": "1.0.6", "type": "module", "scripts": { "dev": "vite dev", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9156e63..6378dd4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1778,7 +1778,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helixnotes" -version = "1.0.4" +version = "1.0.6" dependencies = [ "chrono", "dirs", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f35ab67..4540c6f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helixnotes" -version = "1.0.4" +version = "1.0.6" description = "Local-first markdown note-taking app" authors = ["HelixNotes"] license = "AGPL-3.0-or-later" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d4072e0..ba767c1 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "HelixNotes", - "version": "1.0.5", + "version": "1.0.6", "identifier": "com.helixnotes.app", "build": { "frontendDist": "../build", diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 29fb357..e8b719f 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -63,6 +63,17 @@ let highlightDropdown = $state(false); let alignDropdown = $state(false); let insertDropdown = $state(false); + + function closeAllDropdowns() { + headingDropdown = false; + colorDropdown = false; + highlightDropdown = false; + alignDropdown = false; + insertDropdown = false; + tablePickerOpen = false; + } + + let anyDropdownOpen = $derived(headingDropdown || colorDropdown || highlightDropdown || alignDropdown || insertDropdown || tablePickerOpen); let editorState = $state(0); // AI @@ -367,6 +378,9 @@ closeSlashMenu(); } + // Track whether the user just typed a slash (vs cursor moving into existing text) + let slashTypedByUser = false; + function closeSlashMenu() { slashMenu = null; slashSelectedIndex = 0; @@ -396,6 +410,13 @@ return; } + // Only open the menu if the user typed the slash, or the menu is already open + // This prevents triggering when clicking/arrowing into existing paths like /usr/local/bin + if (!slashMenu && !slashTypedByUser) { + return; + } + slashTypedByUser = false; + const query = match[2]; const slashOffset = textBefore.length - match[0].length + (match[1].length); // position of "/" const from = resolvedFrom.start() + slashOffset; @@ -422,6 +443,12 @@ new Plugin({ key: new PluginKey('slashCommands'), props: { + handleTextInput: (_view, _from, _to, text) => { + if (text === '/') { + slashTypedByUser = true; + } + return false; + }, handleKeyDown: (_view, event) => { if (!slashMenu) return false; if (slashTablePicker) { @@ -1168,7 +1195,15 @@ case 'underline': text = `${text}`; break; case 'subscript': text = `~${text}~`; break; case 'superscript': text = `^${text}^`; break; - case 'highlight': text = `==${text}==`; break; + case 'highlight': { + const color = mark.attrs?.color; + if (color) { + text = `${text}`; + } else { + text = `==${text}==`; + } + break; + } case 'link': text = `[${text}](${mark.attrs.href})`; break; case 'wikiLink': text = `[[${mark.attrs.title || text}]]`; break; } @@ -1331,6 +1366,7 @@ md = md.replace(/(.*?)<\/sub>/gi, '~$1~'); md = md.replace(/(.*?)<\/sup>/gi, '^$1^'); md = md.replace(/(.*?)<\/code>/gi, '`$1`'); + md = md.replace(/(.*?)<\/mark>/gi, '$2'); md = md.replace(/(.*?)<\/mark>/gi, '==$1=='); md = md.replace(/]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => { return content @@ -1438,8 +1474,10 @@ }); // Pre-process: convert task list syntax before markdown-it (it doesn't know TipTap's format) - src = src.replace(/^- \[x\]\s+(.+)$/gm, '- $1'); - src = src.replace(/^- \[ \]\s+(.+)$/gm, '- $1'); + src = src.replace(/^- \[x\][^\S\n]+(.+)$/gm, '- $1'); + src = src.replace(/^- \[x\][^\S\n]*$/gm, '-  '); + src = src.replace(/^- \[ \][^\S\n]+(.+)$/gm, '- $1'); + src = src.replace(/^- \[ \][^\S\n]*$/gm, '-  '); // Run markdown-it (single-pass parser — handles headings, bold, italic, strike, code, blockquote, lists, links, images, hr, tables, raw HTML) let html = mdit.render(src); @@ -1481,6 +1519,19 @@ } }); + // Close formatting dropdowns when clicking outside the formatting bar + $effect(() => { + if (!anyDropdownOpen) return; + function onClickAway(e: MouseEvent) { + const bar = document.querySelector('.editor-formatting-bar'); + if (bar && !bar.contains(e.target as Node)) { + closeAllDropdowns(); + } + } + document.addEventListener('mousedown', onClickAway); + return () => document.removeEventListener('mousedown', onClickAway); + }); + // React to activeNotePath changes from external sources (e.g. search panel) $effect(() => { const path = $activeNotePath; @@ -2653,37 +2704,37 @@
{#if insertDropdown}
e.stopPropagation()}>
@@ -2695,7 +2746,7 @@
{#if headingDropdown} @@ -2713,22 +2764,22 @@
{#if colorDropdown} @@ -2756,49 +2807,49 @@
{#if tablePickerOpen} @@ -2825,7 +2876,7 @@
@@ -2833,7 +2884,7 @@
{#if highlightDropdown} @@ -2859,10 +2910,10 @@
@@ -2871,32 +2922,32 @@
{#if alignDropdown}
e.stopPropagation()}>
@@ -4433,9 +4484,11 @@ } :global(.tiptap-wrapper .tiptap mark) { - padding: 1px 3px; + padding: 0px 5px 2px; border-radius: 3px; color: #333 !important; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; } :global(.dark .tiptap-wrapper .tiptap mark) {