markdown/preview.rs
1//! Who describes a link.
2//!
3//! `markdown` fetches nothing. Resolving a URL's Open Graph data is an HTTP
4//! request and an HTML parse, neither of which a component library has any
5//! business carrying, and `wasm32-unknown-unknown` would spell both differently
6//! anyway. Installed once at boot like the highlighter and read at paint: the
7//! app answers from its own cache and notifies when a fetch lands.
8
9use gpui::{AnyElement, App, Global, SharedString, Window};
10
11/// What a bookmark paints beyond the URL it already has.
12///
13/// Every field is optional because a preview arrives in pieces, and a card
14/// holding none of them still shows its host.
15#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct Preview {
17 pub title: Option<SharedString>,
18 pub description: Option<SharedString>,
19 pub image: Option<SharedString>,
20 pub icon: Option<SharedString>,
21 /// The footer's identity where the host is not the most specific one — a
22 /// repository, a subreddit. Which path names a *unit* is the app's
23 /// knowledge, not this crate's.
24 pub label: Option<SharedString>,
25 /// An app's own mark for a link it owns, painted in the text colour where
26 /// a favicon would go. A chip with one opens no hover card.
27 pub glyph: Option<ui::icons::Icon>,
28}
29
30/// The card an app paints for a link it owns, in place of the bookmark — see
31/// [`crate::AppExt::set_link_card`]. `None` for a link it leaves to the web
32/// preview.
33///
34/// The element sits inside [`crate::render::PAINTED_CONTEXT`]. A press it does
35/// not stop reaches the editor.
36pub type LinkCard = fn(url: &str, form: crate::Form, &mut Window, &mut App) -> Option<AnyElement>;
37
38struct InstalledCard(LinkCard);
39
40impl Global for InstalledCard {}
41
42pub(crate) fn set_link_card(cx: &mut App, card: LinkCard) {
43 cx.set_global(InstalledCard(card));
44}
45
46pub(crate) fn card(
47 url: &str,
48 form: crate::Form,
49 window: &mut Window,
50 cx: &mut App,
51) -> Option<AnyElement> {
52 let card = cx.try_global::<InstalledCard>()?.0;
53 card(url, form, window, cx)
54}
55
56/// `None` for a URL the caller has nothing for *yet*: the card paints its host
57/// and repaints when the answer arrives.
58///
59/// `cx` is how the answer is found: a cache lives in the app, so a bare call
60/// with only the URL would have to reach for a `static` to read its own.
61/// Shared rather than exclusive because this is asked at paint — the fetch it
62/// misses belongs to whatever notices the `None`, not to the paint that
63/// returned it.
64pub type LinkPreview = fn(url: &str, cx: &App) -> Option<Preview>;
65
66struct Installed(LinkPreview);
67
68impl Global for Installed {}
69
70/// `cx.set_link_preview(my_previews)` — call once at boot. Without
71/// it a bookmark shows its host and its URL, which is what a link looks like
72/// before anyone has resolved it.
73pub(crate) fn set_link_preview(cx: &mut App, preview: LinkPreview) {
74 cx.set_global(Installed(preview));
75}
76
77pub(crate) fn of(cx: &App, url: &str) -> Option<Preview> {
78 // Copied out before the call: a preview reads its own globals off the same
79 // `cx` this borrows.
80 let preview = cx.try_global::<Installed>()?.0;
81 preview(url, cx)
82}
83
84/// The host, without its `www.` — all a card can say about a URL nobody has
85/// resolved.
86pub(crate) fn host(url: &str) -> &str {
87 let after = url.split_once("://").map_or(url, |(_, rest)| rest);
88 let host = after.split(['/', '?', '#']).next().unwrap_or(after);
89 host.strip_prefix("www.").unwrap_or(host)
90}