Skip to main content

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}