diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-29 20:08:53 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-29 20:10:23 +0200 |
| commit | 72fb155403801216e244ab1df784ccd2a29c54cb (patch) | |
| tree | 820001268f86d7f9e43d60120db54124241fd62e /library | |
| parent | 621922bb35657d002b123913dd507e35a178f4c2 (diff) | |
Link to label
Diffstat (limited to 'library')
| -rw-r--r-- | library/src/meta/bibliography.rs | 3 | ||||
| -rw-r--r-- | library/src/meta/link.rs | 89 | ||||
| -rw-r--r-- | library/src/meta/reference.rs | 16 |
3 files changed, 74 insertions, 34 deletions
diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs index 2a0a96b1..c9532915 100644 --- a/library/src/meta/bibliography.rs +++ b/library/src/meta/bibliography.rs @@ -614,7 +614,8 @@ fn format_display_string( Formatting::Bold => content.strong(), Formatting::Italic => content.emph(), Formatting::Link(link) => { - LinkElem::new(Destination::Url(link.as_str().into()), content).pack() + LinkElem::new(Destination::Url(link.as_str().into()).into(), content) + .pack() } }; } diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index d8cb779a..135ce0af 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -13,6 +13,7 @@ use crate::text::{Hyphenate, TextElem}; /// #show link: underline /// /// https://example.com \ +/// /// #link("https://example.com") \ /// #link("https://example.com")[ /// See example.com @@ -25,7 +26,7 @@ use crate::text::{Hyphenate, TextElem}; /// /// Display: Link /// Category: meta -#[element(Show, Finalize)] +#[element(Show)] pub struct LinkElem { /// The destination the link points to. /// @@ -34,33 +35,42 @@ pub struct LinkElem { /// omitted, the email address or phone number will be the link's body, /// without the scheme. /// - /// - To link to another part of the document, `dest` can take one of two - /// forms: A [`location`]($func/locate) or a dictionary with a `page` key - /// of type `integer` and `x` and `y` coordinates of type `length`. Pages - /// are counted from one, and the coordinates are relative to the page's - /// top left corner. + /// - To link to another part of the document, `dest` can take one of three + /// forms: + /// - A [label]($func/label) attached to an element. If you also want + /// automatic text for the link based on the element, consider using + /// a [reference]($func/ref) instead. + /// + /// - A [location]($func/locate) resulting from a [`locate`]($func/locate) + /// call or [`query`]($func/query). + /// + /// - A dictionary with a `page` key of type [integer]($type/integer) and + /// `x` and `y` coordinates of type [length]($type/length). Pages are + /// counted from one, and the coordinates are relative to the page's top + /// left corner. /// /// ```example + /// = Introduction <intro> /// #link("mailto:hello@typst.app") \ + /// #link(<intro>)[Go to intro] \ /// #link((page: 1, x: 0pt, y: 0pt))[ /// Go to top /// ] /// ``` #[required] #[parse( - let dest = args.expect::<Destination>("destination")?; + let dest = args.expect::<LinkTarget>("destination")?; dest.clone() )] - pub dest: Destination, + pub dest: LinkTarget, - /// How the link is represented. + /// The content that should become a link. /// - /// The content that should become a link. If `dest` is an URL string, the - /// parameter can be omitted. In this case, the URL will be shown as the - /// link. + /// If `dest` is an URL string, the parameter can be omitted. In this case, + /// the URL will be shown as the link. #[required] #[parse(match &dest { - Destination::Url(url) => match args.eat()? { + LinkTarget::Dest(Destination::Url(url)) => match args.eat()? { Some(body) => body, None => body_from_url(url), }, @@ -73,21 +83,28 @@ impl LinkElem { /// Create a link element from a URL with its bare text. pub fn from_url(url: EcoString) -> Self { let body = body_from_url(&url); - Self::new(Destination::Url(url), body) + Self::new(LinkTarget::Dest(Destination::Url(url)), body) } } impl Show for LinkElem { - fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { - Ok(self.body()) - } -} + fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { + let body = self.body(); + let dest = match self.dest() { + LinkTarget::Dest(dest) => dest, + LinkTarget::Label(label) => { + if !vt.introspector.init() { + return Ok(body); + } + + let elem = vt.introspector.query_label(&label).at(self.span())?; + Destination::Location(elem.location().unwrap()) + } + }; -impl Finalize for LinkElem { - fn finalize(&self, realized: Content, _: StyleChain) -> Content { - realized - .linked(self.dest()) - .styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))) + Ok(body + .linked(dest) + .styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false))))) } } @@ -99,3 +116,29 @@ fn body_from_url(url: &EcoString) -> Content { let shorter = text.len() < url.len(); TextElem::packed(if shorter { text.into() } else { url.clone() }) } + +/// A target where a link can go. +#[derive(Debug, Clone)] +pub enum LinkTarget { + Dest(Destination), + Label(Label), +} + +cast_from_value! { + LinkTarget, + v: Destination => Self::Dest(v), + v: Label => Self::Label(v), +} + +cast_to_value! { + v: LinkTarget => match v { + LinkTarget::Dest(v) => v.into(), + LinkTarget::Label(v) => v.into(), + } +} + +impl From<Destination> for LinkTarget { + fn from(dest: Destination) -> Self { + Self::Dest(dest) + } +} diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 14246436..0b317db0 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -11,6 +11,9 @@ use crate::text::TextElem; /// /// Reference syntax can also be used to [cite]($func/cite) from a bibliography. /// +/// If you just want to link to a labelled element and not get an automatic +/// textual reference, consider using the [`link`]($func/link) function instead. +/// /// # Example /// ```example /// #set heading(numbering: "1.") @@ -93,24 +96,17 @@ impl Show for RefElem { } let target = self.target(); - let matches = vt.introspector.query(Selector::Label(self.target())); + let elem = vt.introspector.query_label(&self.target()); if BibliographyElem::has(vt, &target.0) { - if !matches.is_empty() { + if elem.is_ok() { bail!(self.span(), "label occurs in the document and its bibliography"); } return Ok(self.to_citation(styles).pack()); } - let [elem] = matches.as_slice() else { - bail!(self.span(), if matches.is_empty() { - "label does not exist in the document" - } else { - "label occurs multiple times in the document" - }); - }; - + let elem = elem.at(self.span())?; if !elem.can::<dyn Locatable>() { bail!(self.span(), "cannot reference {}", elem.func().name()); } |
