markdown preview: Allow clicking on image to navigate to source location (#21630)

Follow up to #21082

Similar to checkboxes, you can now click on the image to navigate to the
source location, cmd-clicking opens the url in the browser.


https://github.com/user-attachments/assets/edaaa580-9d8f-490b-a4b3-d6ffb21f197c


Release Notes:

- N/A
This commit is contained in:
Bennet Bo Fenner 2024-12-06 18:31:58 +01:00 committed by GitHub
parent bffdc55d63
commit b4f59284a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 78 additions and 22 deletions

1
Cargo.lock generated
View file

@ -7421,6 +7421,7 @@ dependencies = [
"settings", "settings",
"theme", "theme",
"ui", "ui",
"util",
"workspace", "workspace",
] ]

View file

@ -28,6 +28,7 @@ pulldown-cmark.workspace = true
settings.workspace = true settings.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true
workspace.workspace = true workspace.workspace = true
[dev-dependencies] [dev-dependencies]

View file

@ -7,8 +7,8 @@ use crate::markdown_elements::{
use gpui::{ use gpui::{
div, img, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element, div, img, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
ElementId, HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke, Length, ElementId, HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke, Length,
Modifiers, ParentElement, Resource, SharedString, Styled, StyledText, TextStyle, WeakView, Modifiers, ParentElement, Render, Resource, SharedString, Styled, StyledText, TextStyle, View,
WindowContext, WeakView, WindowContext,
}; };
use settings::Settings; use settings::Settings;
use std::{ use std::{
@ -18,9 +18,10 @@ use std::{
}; };
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings}; use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
use ui::{ use ui::{
h_flex, relative, v_flex, Checkbox, Clickable, FluentBuilder, IconButton, IconName, IconSize, h_flex, relative, tooltip_container, v_flex, Checkbox, Clickable, Color, FluentBuilder,
InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, StyledImage, IconButton, IconName, IconSize, InteractiveElement, Label, LabelCommon, LabelSize, LinkPreview,
Tooltip, VisibleOnHover, Selection, StatefulInteractiveElement, StyledExt, StyledImage, ViewContext, VisibleOnHover,
VisualContext as _,
}; };
use workspace::Workspace; use workspace::Workspace;
@ -206,15 +207,7 @@ fn render_markdown_list_item(
) )
.hover(|s| s.cursor_pointer()) .hover(|s| s.cursor_pointer())
.tooltip(|cx| { .tooltip(|cx| {
let secondary_modifier = Keystroke { InteractiveMarkdownElementTooltip::new(None, "toggle checkbox", cx).into()
key: "".to_string(),
modifiers: Modifiers::secondary_key(),
key_char: None,
};
Tooltip::text(
format!("{}-click to toggle the checkbox", secondary_modifier),
cx,
)
}) })
.into_any_element(), .into_any_element(),
}; };
@ -513,6 +506,7 @@ fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext)
let image_element = div() let image_element = div()
.id(element_id) .id(element_id)
.cursor_pointer()
.child(img(ImageSource::Resource(image_resource)).with_fallback({ .child(img(ImageSource::Resource(image_resource)).with_fallback({
let alt_text = image.alt_text.clone(); let alt_text = image.alt_text.clone();
{ {
@ -521,18 +515,31 @@ fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext)
})) }))
.tooltip({ .tooltip({
let link = image.link.clone(); let link = image.link.clone();
move |cx| LinkPreview::new(&link.to_string(), cx) move |cx| {
InteractiveMarkdownElementTooltip::new(
Some(link.to_string()),
"open image",
cx,
)
.into()
}
}) })
.on_click({ .on_click({
let workspace = workspace_clone.clone(); let workspace = workspace_clone.clone();
let link = image.link.clone(); let link = image.link.clone();
move |_event, window_cx| match &link { move |_, cx| {
Link::Web { url } => window_cx.open_url(url), if cx.modifiers().secondary() {
Link::Path { path, .. } => { match &link {
if let Some(workspace) = &workspace { Link::Web { url } => cx.open_url(url),
_ = workspace.update(window_cx, |workspace, cx| { Link::Path { path, .. } => {
workspace.open_abs_path(path.clone(), false, cx).detach(); if let Some(workspace) = &workspace {
}); _ = workspace.update(cx, |workspace, cx| {
workspace
.open_abs_path(path.clone(), false, cx)
.detach();
});
}
}
} }
} }
} }
@ -550,3 +557,50 @@ fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement {
let rule = div().w_full().h(px(2.)).bg(cx.border_color); let rule = div().w_full().h(px(2.)).bg(cx.border_color);
div().pt_3().pb_3().child(rule).into_any() div().pt_3().pb_3().child(rule).into_any()
} }
struct InteractiveMarkdownElementTooltip {
tooltip_text: Option<SharedString>,
action_text: String,
}
impl InteractiveMarkdownElementTooltip {
pub fn new(
tooltip_text: Option<String>,
action_text: &str,
cx: &mut WindowContext,
) -> View<Self> {
let tooltip_text = tooltip_text.map(|t| util::truncate_and_trailoff(&t, 50).into());
cx.new_view(|_| Self {
tooltip_text,
action_text: action_text.to_string(),
})
}
}
impl Render for InteractiveMarkdownElementTooltip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
tooltip_container(cx, |el, _| {
let secondary_modifier = Keystroke {
modifiers: Modifiers::secondary_key(),
..Default::default()
};
el.child(
v_flex()
.gap_1()
.when_some(self.tooltip_text.clone(), |this, text| {
this.child(Label::new(text).size(LabelSize::Small))
})
.child(
Label::new(format!(
"{}-click to {}",
secondary_modifier, self.action_text
))
.size(LabelSize::Small)
.color(Color::Muted),
),
)
})
}
}