feat(markdown): intercept links in MarkdownPane — relative files navigate in Files tab, external URLs open in browser
This commit is contained in:
parent
91aa711ef3
commit
cd438c2cf3
3 changed files with 75 additions and 3 deletions
|
|
@ -552,6 +552,19 @@ async fn remote_pty_kill(state: State<'_, AppState>, machine_id: String, id: Str
|
|||
state.remote_manager.pty_kill(&machine_id, &id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn open_url(url: String) -> Result<(), String> {
|
||||
// Only allow http/https URLs
|
||||
if !url.starts_with("http://") && !url.starts_with("https://") {
|
||||
return Err("Only http/https URLs are allowed".into());
|
||||
}
|
||||
std::process::Command::new("xdg-open")
|
||||
.arg(&url)
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to open URL: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
// Force dark GTK theme for native dialogs (file chooser, etc.)
|
||||
|
|
@ -616,6 +629,7 @@ pub fn run() {
|
|||
project_agent_state_load,
|
||||
cli_get_group,
|
||||
pick_directory,
|
||||
open_url,
|
||||
frontend_log,
|
||||
])
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@
|
|||
filePath: string;
|
||||
paneId: string;
|
||||
onExit?: () => void;
|
||||
onNavigate?: (absolutePath: string) => void;
|
||||
}
|
||||
|
||||
let { filePath, paneId, onExit }: Props = $props();
|
||||
let { filePath, paneId, onExit, onNavigate }: Props = $props();
|
||||
|
||||
let renderedHtml = $state('');
|
||||
let error = $state('');
|
||||
|
|
@ -72,13 +73,59 @@
|
|||
unlisten?.();
|
||||
unwatchFile(paneId).catch(() => {});
|
||||
});
|
||||
|
||||
function handleLinkClick(event: MouseEvent) {
|
||||
const anchor = (event.target as HTMLElement).closest('a');
|
||||
if (!anchor) return;
|
||||
|
||||
const href = anchor.getAttribute('href');
|
||||
if (!href) return;
|
||||
|
||||
// Anchor links — scroll within page
|
||||
if (href.startsWith('#')) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// External URLs — open in system browser
|
||||
if (/^https?:\/\//.test(href)) {
|
||||
import('@tauri-apps/api/core').then(({ invoke }) => {
|
||||
invoke('open_url', { url: href }).catch(() => {
|
||||
// Fallback: do nothing (no shell plugin)
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Relative file link — resolve against current file's directory
|
||||
if (onNavigate) {
|
||||
const dir = filePath.replace(/\/[^/]*$/, '');
|
||||
const resolved = resolveRelativePath(dir, href);
|
||||
onNavigate(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRelativePath(base: string, relative: string): string {
|
||||
// Strip any anchor or query from the link
|
||||
const cleanRelative = relative.split('#')[0].split('?')[0];
|
||||
const parts = base.split('/');
|
||||
for (const segment of cleanRelative.split('/')) {
|
||||
if (segment === '..') {
|
||||
parts.pop();
|
||||
} else if (segment !== '.' && segment !== '') {
|
||||
parts.push(segment);
|
||||
}
|
||||
}
|
||||
return parts.join('/');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="markdown-pane">
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
{:else}
|
||||
<div class="markdown-pane-scroll">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="markdown-pane-scroll" onclick={handleLinkClick}>
|
||||
<div class="markdown-body">
|
||||
{@html renderedHtml}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,17 @@
|
|||
loadFiles(cwd);
|
||||
});
|
||||
|
||||
function handleNavigate(absolutePath: string) {
|
||||
// If the file is in our discovered list, select it directly
|
||||
const match = files.find(f => f.path === absolutePath);
|
||||
if (match) {
|
||||
selectedPath = absolutePath;
|
||||
} else {
|
||||
// File not in sidebar — set it directly (MarkdownPane handles loading)
|
||||
selectedPath = absolutePath;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFiles(dir: string) {
|
||||
loading = true;
|
||||
try {
|
||||
|
|
@ -58,7 +69,7 @@
|
|||
|
||||
<main class="doc-content">
|
||||
{#if selectedPath}
|
||||
<MarkdownPane paneId="pf-{projectName}" filePath={selectedPath} />
|
||||
<MarkdownPane paneId="pf-{projectName}" filePath={selectedPath} onNavigate={handleNavigate} />
|
||||
{:else}
|
||||
<div class="state-msg full">Select a file</div>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue