Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,12 @@ pub fn run() {
.item(&PredefinedMenuItem::copy(app, None)?)
.item(&PredefinedMenuItem::paste(app, None)?)
.item(&PredefinedMenuItem::select_all(app, None)?)
.separator()
.item(
&MenuItemBuilder::with_id("menu-edit-find", "Find…")
.accelerator("CmdOrCtrl+F")
.build(app)?,
)
.build()?;

let window_submenu = SubmenuBuilder::new(app, "Window")
Expand Down Expand Up @@ -1139,7 +1145,10 @@ pub fn run() {

if id == "check-updates" {
let _ = window.emit("menu-check-updates", ());
} else if id == "menu-app-quit" || id.starts_with("menu-file-") {
} else if id == "menu-app-quit"
|| id.starts_with("menu-file-")
|| id.starts_with("menu-edit-")
{
let _ = window.emit(id, ());
}
})
Expand Down
58 changes: 57 additions & 1 deletion src/lib/MarkdownViewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import ContextMenu, { type ContextMenuItem } from './components/ContextMenu.svelte';
import Toc from './components/Toc.svelte';
import Toast from './components/Toast.svelte';
import FindBar from './components/FindBar.svelte';
import { exportAsHtml as _exportHtml, exportAsPdf } from './utils/export';
import ZoomOverlay from './components/ZoomOverlay.svelte';
import { processMarkdownHtml } from './utils/markdown';
Expand Down Expand Up @@ -76,9 +77,28 @@ import { t } from './utils/i18n.js';
undo: () => void;
redo: () => void;
revealHeader: (text: string) => void;
triggerFind: () => void;
} | null>(null);
let liveMode = $state(false);

let findOpen = $state(false);
let findBar = $state<{ reapply: () => void; clearHighlights: () => void } | null>(null);

// Decide where Cmd/Ctrl+F should land based on what's visible and where
// focus is. Used by both the JS keydown handler (Win/Linux + macOS in-page
// shortcut) and the macOS native menu listener (which fires Cmd+F via the
// Edit menu accelerator and bypasses the JS keydown path).
function triggerFindAction() {
const active = document.activeElement as Node | null;
const editorHasFocus = !!editorPaneEl && !!active && editorPaneEl.contains(active);
const previewVisible = !isEditing || !!tabManager.activeTab?.isSplit;
if (editorHasFocus || !previewVisible) {
editorPane?.triggerFind?.();
} else if (markdownBody) {
findOpen = true;
}
}

let isDragging = $state(false);
let dragTarget = $state<'editor' | 'preview' | null>(null);
let editorPaneEl = $state<HTMLElement>();
Expand Down Expand Up @@ -331,6 +351,7 @@ import { t } from './utils/i18n.js';
$effect(() => {
const _ = tabManager.activeTabId;
showHome = false;
findOpen = false;
});

function processHighlights(root: Element) {
Expand Down Expand Up @@ -693,6 +714,16 @@ import { t } from './utils/i18n.js';
if (sanitizedHtml && markdownBody && !isEditing && hljs && renderMathInElement && mermaid) renderRichContent();
});

// Re-apply find highlights after the preview HTML is replaced. The
// `bind:innerHTML={sanitizedHtml}` on the article wipes the DOM on every
// edit/render pass; without this, highlights vanish until the user
// re-types in the find bar.
$effect(() => {
const _ = sanitizedHtml;
if (!findOpen || !findBar) return;
tick().then(() => findBar?.reapply());
});

$effect(() => {
// Depend on the ID and body existence to trigger restore
const id = tabManager.activeTabId;
Expand Down Expand Up @@ -2069,6 +2100,18 @@ import { t } from './utils/i18n.js';
e.preventDefault();
showSettings = !showSettings;
}
// Ctrl/Cmd+F: route to either Monaco's built-in find or the preview
// FindBar depending on focus and which panes are visible. We only
// preventDefault when we actually take the action ourselves —
// otherwise we let Monaco's own keybinding fire.
if (cmdOrCtrl && !e.shiftKey && !e.altKey && key === 'f') {
const active = document.activeElement as Node | null;
const editorHasFocus = !!editorPaneEl && !!active && editorPaneEl.contains(active);
if (!editorHasFocus) {
e.preventDefault();
triggerFindAction();
}
}
}

function pushScrollHistory() {
Expand Down Expand Up @@ -2283,6 +2326,11 @@ import { t } from './utils/i18n.js';
toggleEdit();
}),
);
unlisteners.push(
await listen('menu-edit-find', () => {
triggerFindAction();
}),
);
Comment thread
roniaxe marked this conversation as resolved.
unlisteners.push(
await listen('menu-tab-rename', async (event) => {
const tabId = event.payload as string;
Expand Down Expand Up @@ -2577,6 +2625,7 @@ import { t } from './utils/i18n.js';
{theme}
onSetTheme={(t) => (theme = t)}
onopenSettings={() => (showSettings = true)}
onfind={triggerFindAction}
oncloseTab={closeTabAndWindowIfLast} />
<div class="loading-screen">
<svg class="spinner" viewBox="0 0 50 50">
Expand Down Expand Up @@ -2620,6 +2669,7 @@ import { t } from './utils/i18n.js';
{theme}
onSetTheme={(t) => (theme = t)}
onopenSettings={() => (showSettings = true)}
onfind={triggerFindAction}
oncloseTab={closeTabAndWindowIfLast} />

<Settings show={showSettings} {theme} onSetTheme={(t) => (theme = t)} onclose={() => (showSettings = false)} />
Expand Down Expand Up @@ -2675,7 +2725,13 @@ import { t } from './utils/i18n.js';
class="pane viewer-pane"
class:active={!isEditing || isSplit}
style="flex: {isSplit ? 1 - tabManager.activeTab.splitRatio : (!isEditing) ? 1 : 0}">


<FindBar
bind:this={findBar}
bind:open={findOpen}
{markdownBody}
language={settings.language} />

<div class="viewer-content">
<article
bind:this={markdownBody}
Expand Down
6 changes: 6 additions & 0 deletions src/lib/components/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,12 @@
editor?.trigger("keyboard", "redo", null);
}

export const triggerFind = () => {
if (!editor) return;
editor.focus();
editor.getAction("actions.find")?.run();
}

export const getValue = () => editor?.getValue() || "";
export const setValue = (val: string) => editor?.setValue(val);
export const focus = () => editor?.focus();
Expand Down
Loading