summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamelmore <[email protected]>2026-01-27 08:11:06 -0600
committeradamelmore <[email protected]>2026-01-27 08:43:36 -0600
commit3297e5230e86b67c4013f8133aefd2c9d47cc464 (patch)
treec0be37796c271bb7a0f4c6e65a2ad805bc56252d
parentc3d8d2b27fb4c10aec99295483d4c34e6eedc780 (diff)
downloadopencode-3297e5230e86b67c4013f8133aefd2c9d47cc464.tar.gz
opencode-3297e5230e86b67c4013f8133aefd2c9d47cc464.zip
fix(app): open markdown links in external browser
-rw-r--r--packages/desktop/src-tauri/src/markdown.rs47
1 files changed, 45 insertions, 2 deletions
diff --git a/packages/desktop/src-tauri/src/markdown.rs b/packages/desktop/src-tauri/src/markdown.rs
index a2a53b222..c3ca73857 100644
--- a/packages/desktop/src-tauri/src/markdown.rs
+++ b/packages/desktop/src-tauri/src/markdown.rs
@@ -1,4 +1,43 @@
-use comrak::{markdown_to_html, Options};
+use comrak::{create_formatter, parse_document, Arena, Options, html::ChildRendering, nodes::NodeValue};
+use std::fmt::Write;
+
+create_formatter!(ExternalLinkFormatter, {
+ NodeValue::Link(ref nl) => |context, node, entering| {
+ let skip = context.options.parse.relaxed_autolinks
+ && node.parent().is_some_and(|p| comrak::node_matches!(p, NodeValue::Link(..)));
+ if skip {
+ return Ok(ChildRendering::HTML);
+ }
+
+ if entering {
+ context.write_str("<a")?;
+ comrak::html::render_sourcepos(context, node)?;
+
+ context.write_str(" href=\"")?;
+ let url = &nl.url;
+ if context.options.render.r#unsafe || !comrak::html::dangerous_url(url) {
+ if let Some(rewriter) = &context.options.extension.link_url_rewriter {
+ context.escape_href(&rewriter.to_html(url))?;
+ } else {
+ context.escape_href(url)?;
+ }
+ }
+ context.write_str("\"")?;
+
+ if !nl.title.is_empty() {
+ context.write_str(" title=\"")?;
+ context.escape(&nl.title)?;
+ context.write_str("\"")?;
+ }
+
+ context.write_str(
+ " class=\"external-link\" target=\"_blank\" rel=\"noopener noreferrer\">",
+ )?;
+ } else {
+ context.write_str("</a>")?;
+ }
+ },
+});
pub fn parse_markdown(input: &str) -> String {
let mut options = Options::default();
@@ -8,7 +47,11 @@ pub fn parse_markdown(input: &str) -> String {
options.extension.autolink = true;
options.render.r#unsafe = true;
- markdown_to_html(input, &options)
+ let arena = Arena::new();
+ let doc = parse_document(&arena, input, &options);
+ let mut html = String::new();
+ ExternalLinkFormatter::format_document(doc, &options, &mut html).unwrap_or_default();
+ html
}
#[tauri::command]