summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
Diffstat (limited to 'library')
-rw-r--r--library/src/meta/bibliography.rs3
-rw-r--r--library/src/meta/link.rs89
-rw-r--r--library/src/meta/reference.rs16
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());
}