mirror of
https://github.com/typst/typst
synced 2025-06-28 16:22:53 +08:00
Add internal URL type (#5074)
This commit is contained in:
parent
788ae10a07
commit
d94acd615e
@ -1,8 +1,7 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use ecow::EcoString;
|
|
||||||
use typst::layout::{Frame, FrameItem, Point, Position, Size};
|
use typst::layout::{Frame, FrameItem, Point, Position, Size};
|
||||||
use typst::model::{Destination, Document};
|
use typst::model::{Destination, Document, Url};
|
||||||
use typst::syntax::{FileId, LinkedNode, Side, Source, Span, SyntaxKind};
|
use typst::syntax::{FileId, LinkedNode, Side, Source, Span, SyntaxKind};
|
||||||
use typst::visualize::Geometry;
|
use typst::visualize::Geometry;
|
||||||
use typst::World;
|
use typst::World;
|
||||||
@ -13,7 +12,7 @@ pub enum Jump {
|
|||||||
/// Jump to a position in a source file.
|
/// Jump to a position in a source file.
|
||||||
Source(FileId, usize),
|
Source(FileId, usize),
|
||||||
/// Jump to an external URL.
|
/// Jump to an external URL.
|
||||||
Url(EcoString),
|
Url(Url),
|
||||||
/// Jump to a point on a page.
|
/// Jump to a point on a page.
|
||||||
Position(Position),
|
Position(Position),
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::diag::{warning, SourceResult};
|
use crate::diag::{warning, At, SourceResult};
|
||||||
use crate::eval::{Eval, Vm};
|
use crate::eval::{Eval, Vm};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Content, Label, NativeElement, Repr, Smart, Unlabellable, Value,
|
Content, Label, NativeElement, Repr, Smart, Unlabellable, Value,
|
||||||
@ -6,7 +6,7 @@ use crate::foundations::{
|
|||||||
use crate::math::EquationElem;
|
use crate::math::EquationElem;
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
EmphElem, EnumItem, HeadingElem, LinkElem, ListItem, ParbreakElem, RefElem,
|
EmphElem, EnumItem, HeadingElem, LinkElem, ListItem, ParbreakElem, RefElem,
|
||||||
StrongElem, Supplement, TermItem,
|
StrongElem, Supplement, TermItem, Url,
|
||||||
};
|
};
|
||||||
use crate::symbols::Symbol;
|
use crate::symbols::Symbol;
|
||||||
use crate::syntax::ast::{self, AstNode};
|
use crate::syntax::ast::{self, AstNode};
|
||||||
@ -195,7 +195,8 @@ impl Eval for ast::Link<'_> {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(LinkElem::from_url(self.get().clone()).pack())
|
let url = Url::new(self.get().clone()).at(self.span())?;
|
||||||
|
Ok(LinkElem::from_url(url).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ use crate::layout::{
|
|||||||
};
|
};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
|
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
|
||||||
|
Url,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
@ -741,8 +742,8 @@ impl<'a> Generator<'a> {
|
|||||||
|
|
||||||
/// Displays hayagriva's output as content for the citations and references.
|
/// Displays hayagriva's output as content for the citations and references.
|
||||||
fn display(&mut self, rendered: &hayagriva::Rendered) -> StrResult<Works> {
|
fn display(&mut self, rendered: &hayagriva::Rendered) -> StrResult<Works> {
|
||||||
let citations = self.display_citations(rendered);
|
let citations = self.display_citations(rendered)?;
|
||||||
let references = self.display_references(rendered);
|
let references = self.display_references(rendered)?;
|
||||||
let hanging_indent =
|
let hanging_indent =
|
||||||
rendered.bibliography.as_ref().is_some_and(|b| b.hanging_indent);
|
rendered.bibliography.as_ref().is_some_and(|b| b.hanging_indent);
|
||||||
Ok(Works { citations, references, hanging_indent })
|
Ok(Works { citations, references, hanging_indent })
|
||||||
@ -752,7 +753,7 @@ impl<'a> Generator<'a> {
|
|||||||
fn display_citations(
|
fn display_citations(
|
||||||
&mut self,
|
&mut self,
|
||||||
rendered: &hayagriva::Rendered,
|
rendered: &hayagriva::Rendered,
|
||||||
) -> HashMap<Location, SourceResult<Content>> {
|
) -> StrResult<HashMap<Location, SourceResult<Content>>> {
|
||||||
// Determine for each citation key where in the bibliography it is,
|
// Determine for each citation key where in the bibliography it is,
|
||||||
// so that we can link there.
|
// so that we can link there.
|
||||||
let mut links = HashMap::new();
|
let mut links = HashMap::new();
|
||||||
@ -779,7 +780,7 @@ impl<'a> Generator<'a> {
|
|||||||
Content::empty()
|
Content::empty()
|
||||||
} else {
|
} else {
|
||||||
let mut content =
|
let mut content =
|
||||||
renderer.display_elem_children(&citation.citation, &mut None);
|
renderer.display_elem_children(&citation.citation, &mut None)?;
|
||||||
|
|
||||||
if info.footnote {
|
if info.footnote {
|
||||||
content = FootnoteElem::with_content(content).pack();
|
content = FootnoteElem::with_content(content).pack();
|
||||||
@ -791,15 +792,16 @@ impl<'a> Generator<'a> {
|
|||||||
output.insert(info.location, Ok(content));
|
output.insert(info.location, Ok(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
output
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display the bibliography references.
|
/// Display the bibliography references.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn display_references(
|
fn display_references(
|
||||||
&self,
|
&self,
|
||||||
rendered: &hayagriva::Rendered,
|
rendered: &hayagriva::Rendered,
|
||||||
) -> Option<Vec<(Option<Content>, Content)>> {
|
) -> StrResult<Option<Vec<(Option<Content>, Content)>>> {
|
||||||
let rendered = rendered.bibliography.as_ref()?;
|
let Some(rendered) = &rendered.bibliography else { return Ok(None) };
|
||||||
|
|
||||||
// Determine for each citation key where it first occurred, so that we
|
// Determine for each citation key where it first occurred, so that we
|
||||||
// can link there.
|
// can link there.
|
||||||
@ -829,18 +831,22 @@ impl<'a> Generator<'a> {
|
|||||||
let backlink = location.variant(k + 1);
|
let backlink = location.variant(k + 1);
|
||||||
|
|
||||||
// Render the first field.
|
// Render the first field.
|
||||||
let mut prefix = item.first_field.as_ref().map(|elem| {
|
let mut prefix = item
|
||||||
let mut content = renderer.display_elem_child(elem, &mut None);
|
.first_field
|
||||||
if let Some(location) = first_occurrences.get(item.key.as_str()) {
|
.as_ref()
|
||||||
let dest = Destination::Location(*location);
|
.map(|elem| {
|
||||||
content = content.linked(dest);
|
let mut content = renderer.display_elem_child(elem, &mut None)?;
|
||||||
}
|
if let Some(location) = first_occurrences.get(item.key.as_str()) {
|
||||||
content
|
let dest = Destination::Location(*location);
|
||||||
});
|
content = content.linked(dest);
|
||||||
|
}
|
||||||
|
StrResult::Ok(content)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
// Render the main reference content.
|
// Render the main reference content.
|
||||||
let mut reference =
|
let mut reference =
|
||||||
renderer.display_elem_children(&item.content, &mut prefix);
|
renderer.display_elem_children(&item.content, &mut prefix)?;
|
||||||
|
|
||||||
// Attach a backlink to either the prefix or the reference so that
|
// Attach a backlink to either the prefix or the reference so that
|
||||||
// we can link to the bibliography entry.
|
// we can link to the bibliography entry.
|
||||||
@ -849,7 +855,7 @@ impl<'a> Generator<'a> {
|
|||||||
output.push((prefix, reference));
|
output.push((prefix, reference));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(output)
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -874,10 +880,14 @@ impl ElemRenderer<'_> {
|
|||||||
&self,
|
&self,
|
||||||
elems: &hayagriva::ElemChildren,
|
elems: &hayagriva::ElemChildren,
|
||||||
prefix: &mut Option<Content>,
|
prefix: &mut Option<Content>,
|
||||||
) -> Content {
|
) -> StrResult<Content> {
|
||||||
Content::sequence(
|
Ok(Content::sequence(
|
||||||
elems.0.iter().map(|elem| self.display_elem_child(elem, prefix)),
|
elems
|
||||||
)
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|elem| self.display_elem_child(elem, prefix))
|
||||||
|
.collect::<StrResult<Vec<_>>>()?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display a rendered hayagriva element.
|
/// Display a rendered hayagriva element.
|
||||||
@ -885,16 +895,16 @@ impl ElemRenderer<'_> {
|
|||||||
&self,
|
&self,
|
||||||
elem: &hayagriva::ElemChild,
|
elem: &hayagriva::ElemChild,
|
||||||
prefix: &mut Option<Content>,
|
prefix: &mut Option<Content>,
|
||||||
) -> Content {
|
) -> StrResult<Content> {
|
||||||
match elem {
|
Ok(match elem {
|
||||||
hayagriva::ElemChild::Text(formatted) => self.display_formatted(formatted),
|
hayagriva::ElemChild::Text(formatted) => self.display_formatted(formatted),
|
||||||
hayagriva::ElemChild::Elem(elem) => self.display_elem(elem, prefix),
|
hayagriva::ElemChild::Elem(elem) => self.display_elem(elem, prefix)?,
|
||||||
hayagriva::ElemChild::Markup(markup) => self.display_math(markup),
|
hayagriva::ElemChild::Markup(markup) => self.display_math(markup),
|
||||||
hayagriva::ElemChild::Link { text, url } => self.display_link(text, url),
|
hayagriva::ElemChild::Link { text, url } => self.display_link(text, url)?,
|
||||||
hayagriva::ElemChild::Transparent { cite_idx, format } => {
|
hayagriva::ElemChild::Transparent { cite_idx, format } => {
|
||||||
self.display_transparent(*cite_idx, format)
|
self.display_transparent(*cite_idx, format)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display a block-level element.
|
/// Display a block-level element.
|
||||||
@ -902,7 +912,7 @@ impl ElemRenderer<'_> {
|
|||||||
&self,
|
&self,
|
||||||
elem: &hayagriva::Elem,
|
elem: &hayagriva::Elem,
|
||||||
prefix: &mut Option<Content>,
|
prefix: &mut Option<Content>,
|
||||||
) -> Content {
|
) -> StrResult<Content> {
|
||||||
use citationberg::Display;
|
use citationberg::Display;
|
||||||
|
|
||||||
let block_level = matches!(elem.display, Some(Display::Block | Display::Indent));
|
let block_level = matches!(elem.display, Some(Display::Block | Display::Indent));
|
||||||
@ -911,7 +921,7 @@ impl ElemRenderer<'_> {
|
|||||||
let mut content = self.display_elem_children(
|
let mut content = self.display_elem_children(
|
||||||
&elem.children,
|
&elem.children,
|
||||||
if block_level { &mut suf_prefix } else { prefix },
|
if block_level { &mut suf_prefix } else { prefix },
|
||||||
);
|
)?;
|
||||||
|
|
||||||
if let Some(prefix) = suf_prefix {
|
if let Some(prefix) = suf_prefix {
|
||||||
const COLUMN_GUTTER: Em = Em::new(0.65);
|
const COLUMN_GUTTER: Em = Em::new(0.65);
|
||||||
@ -941,7 +951,7 @@ impl ElemRenderer<'_> {
|
|||||||
}
|
}
|
||||||
Some(Display::LeftMargin) => {
|
Some(Display::LeftMargin) => {
|
||||||
*prefix.get_or_insert_with(Default::default) += content;
|
*prefix.get_or_insert_with(Default::default) += content;
|
||||||
return Content::empty();
|
return Ok(Content::empty());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -953,7 +963,7 @@ impl ElemRenderer<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display math.
|
/// Display math.
|
||||||
@ -964,11 +974,11 @@ impl ElemRenderer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Display a link.
|
/// Display a link.
|
||||||
fn display_link(&self, text: &hayagriva::Formatted, url: &str) -> Content {
|
fn display_link(&self, text: &hayagriva::Formatted, url: &str) -> StrResult<Content> {
|
||||||
let dest = Destination::Url(url.into());
|
let dest = Destination::Url(Url::new(url)?);
|
||||||
LinkElem::new(dest.into(), self.display_formatted(text))
|
Ok(LinkElem::new(dest.into(), self.display_formatted(text))
|
||||||
.pack()
|
.pack()
|
||||||
.spanned(self.span)
|
.spanned(self.span))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display transparent pass-through content.
|
/// Display transparent pass-through content.
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::diag::{At, SourceResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Content, Label, Packed, Repr, Show, Smart, StyleChain,
|
cast, elem, Content, Label, Packed, Repr, Show, Smart, StyleChain,
|
||||||
@ -89,7 +91,7 @@ pub struct LinkElem {
|
|||||||
|
|
||||||
impl LinkElem {
|
impl LinkElem {
|
||||||
/// Create a link element from a URL with its bare text.
|
/// Create a link element from a URL with its bare text.
|
||||||
pub fn from_url(url: EcoString) -> Self {
|
pub fn from_url(url: Url) -> Self {
|
||||||
let body = body_from_url(&url);
|
let body = body_from_url(&url);
|
||||||
Self::new(LinkTarget::Dest(Destination::Url(url)), body)
|
Self::new(LinkTarget::Dest(Destination::Url(url)), body)
|
||||||
}
|
}
|
||||||
@ -112,13 +114,13 @@ impl Show for Packed<LinkElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn body_from_url(url: &EcoString) -> Content {
|
fn body_from_url(url: &Url) -> Content {
|
||||||
let mut text = url.as_str();
|
let mut text = url.as_str();
|
||||||
for prefix in ["mailto:", "tel:"] {
|
for prefix in ["mailto:", "tel:"] {
|
||||||
text = text.trim_start_matches(prefix);
|
text = text.trim_start_matches(prefix);
|
||||||
}
|
}
|
||||||
let shorter = text.len() < url.len();
|
let shorter = text.len() < url.len();
|
||||||
TextElem::packed(if shorter { text.into() } else { url.clone() })
|
TextElem::packed(if shorter { text.into() } else { (**url).clone() })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A target where a link can go.
|
/// A target where a link can go.
|
||||||
@ -148,13 +150,15 @@ impl From<Destination> for LinkTarget {
|
|||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Destination {
|
pub enum Destination {
|
||||||
/// A link to a URL.
|
/// A link to a URL.
|
||||||
Url(EcoString),
|
Url(Url),
|
||||||
/// A link to a point on a page.
|
/// A link to a point on a page.
|
||||||
Position(Position),
|
Position(Position),
|
||||||
/// An unresolved link to a location in the document.
|
/// An unresolved link to a location in the document.
|
||||||
Location(Location),
|
Location(Location),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Destination {}
|
||||||
|
|
||||||
impl Repr for Destination {
|
impl Repr for Destination {
|
||||||
fn repr(&self) -> EcoString {
|
fn repr(&self) -> EcoString {
|
||||||
eco_format!("{self:?}")
|
eco_format!("{self:?}")
|
||||||
@ -168,7 +172,41 @@ cast! {
|
|||||||
Self::Position(v) => v.into_value(),
|
Self::Position(v) => v.into_value(),
|
||||||
Self::Location(v) => v.into_value(),
|
Self::Location(v) => v.into_value(),
|
||||||
},
|
},
|
||||||
v: EcoString => Self::Url(v),
|
v: Url => Self::Url(v),
|
||||||
v: Position => Self::Position(v),
|
v: Position => Self::Position(v),
|
||||||
v: Location => Self::Location(v),
|
v: Location => Self::Location(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A uniform resource locator with a maximum length.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct Url(EcoString);
|
||||||
|
|
||||||
|
impl Url {
|
||||||
|
/// Create an URL from a string, checking the maximum length.
|
||||||
|
pub fn new(url: impl Into<EcoString>) -> StrResult<Self> {
|
||||||
|
let url = url.into();
|
||||||
|
if url.len() > 8000 {
|
||||||
|
bail!("URL is too long")
|
||||||
|
}
|
||||||
|
Ok(Self(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the underlying [`EcoString`].
|
||||||
|
pub fn into_inner(self) -> EcoString {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Url {
|
||||||
|
type Target = EcoString;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cast! {
|
||||||
|
Url,
|
||||||
|
self => self.0.into_value(),
|
||||||
|
v: EcoString => Self::new(v)?,
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user