mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Support quotes in HTML output (#5673)
This commit is contained in:
parent
e61cd6fb9e
commit
b3fb6c2326
@ -2,13 +2,14 @@ use crate::diag::SourceResult;
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart,
|
||||
StyleChain, Styles,
|
||||
StyleChain, Styles, TargetElem,
|
||||
};
|
||||
use crate::html::{tag, HtmlAttr, HtmlElem};
|
||||
use crate::introspection::Locatable;
|
||||
use crate::layout::{
|
||||
Alignment, BlockBody, BlockElem, Em, HElem, PadElem, Spacing, VElem,
|
||||
};
|
||||
use crate::model::{CitationForm, CiteElem};
|
||||
use crate::model::{CitationForm, CiteElem, Destination, LinkElem, LinkTarget};
|
||||
use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem};
|
||||
|
||||
/// Displays a quote alongside an optional attribution.
|
||||
@ -158,6 +159,7 @@ impl Show for Packed<QuoteElem> {
|
||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
||||
let mut realized = self.body.clone();
|
||||
let block = self.block(styles);
|
||||
let html = TargetElem::target_in(styles).is_html();
|
||||
|
||||
if self.quotes(styles) == Smart::Custom(true) || !block {
|
||||
let quotes = SmartQuotes::get(
|
||||
@ -171,50 +173,65 @@ impl Show for Packed<QuoteElem> {
|
||||
let Depth(depth) = QuoteElem::depth_in(styles);
|
||||
let double = depth % 2 == 0;
|
||||
|
||||
// Add zero-width weak spacing to make the quotes "sticky".
|
||||
let hole = HElem::hole().pack();
|
||||
if !html {
|
||||
// Add zero-width weak spacing to make the quotes "sticky".
|
||||
let hole = HElem::hole().pack();
|
||||
realized = Content::sequence([hole.clone(), realized, hole]);
|
||||
}
|
||||
realized = Content::sequence([
|
||||
TextElem::packed(quotes.open(double)),
|
||||
hole.clone(),
|
||||
realized,
|
||||
hole,
|
||||
TextElem::packed(quotes.close(double)),
|
||||
])
|
||||
.styled(QuoteElem::set_depth(Depth(1)));
|
||||
}
|
||||
|
||||
let attribution = self.attribution(styles);
|
||||
|
||||
if block {
|
||||
realized = BlockElem::new()
|
||||
.with_body(Some(BlockBody::Content(realized)))
|
||||
.pack()
|
||||
.spanned(self.span());
|
||||
|
||||
if let Some(attribution) = self.attribution(styles).as_ref() {
|
||||
let mut seq = vec![TextElem::packed('—'), SpaceElem::shared().clone()];
|
||||
|
||||
match attribution {
|
||||
Attribution::Content(content) => {
|
||||
seq.push(content.clone());
|
||||
}
|
||||
Attribution::Label(label) => {
|
||||
seq.push(
|
||||
CiteElem::new(*label)
|
||||
.with_form(Some(CitationForm::Prose))
|
||||
.pack()
|
||||
.spanned(self.span()),
|
||||
);
|
||||
realized = if html {
|
||||
let mut elem = HtmlElem::new(tag::blockquote).with_body(Some(realized));
|
||||
if let Some(Attribution::Content(attribution)) = attribution {
|
||||
if let Some(link) = attribution.to_packed::<LinkElem>() {
|
||||
if let LinkTarget::Dest(Destination::Url(url)) = &link.dest {
|
||||
elem = elem.with_attr(
|
||||
HtmlAttr::constant("cite"),
|
||||
url.clone().into_inner(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
elem.pack()
|
||||
} else {
|
||||
BlockElem::new().with_body(Some(BlockBody::Content(realized))).pack()
|
||||
}
|
||||
.spanned(self.span());
|
||||
|
||||
// Use v(0.9em, weak: true) bring the attribution closer to the
|
||||
// quote.
|
||||
let gap = Spacing::Rel(Em::new(0.9).into());
|
||||
let v = VElem::new(gap).with_weak(true).pack();
|
||||
realized += v + Content::sequence(seq).aligned(Alignment::END);
|
||||
if let Some(attribution) = attribution.as_ref() {
|
||||
let attribution = match attribution {
|
||||
Attribution::Content(content) => content.clone(),
|
||||
Attribution::Label(label) => CiteElem::new(*label)
|
||||
.with_form(Some(CitationForm::Prose))
|
||||
.pack()
|
||||
.spanned(self.span()),
|
||||
};
|
||||
let attribution =
|
||||
[TextElem::packed('—'), SpaceElem::shared().clone(), attribution];
|
||||
|
||||
if !html {
|
||||
// Use v(0.9em, weak: true) to bring the attribution closer
|
||||
// to the quote.
|
||||
let gap = Spacing::Rel(Em::new(0.9).into());
|
||||
let v = VElem::new(gap).with_weak(true).pack();
|
||||
realized += v;
|
||||
}
|
||||
realized += Content::sequence(attribution).aligned(Alignment::END);
|
||||
}
|
||||
|
||||
realized = PadElem::new(realized).pack();
|
||||
} else if let Some(Attribution::Label(label)) = self.attribution(styles) {
|
||||
if !html {
|
||||
realized = PadElem::new(realized).pack();
|
||||
}
|
||||
} else if let Some(Attribution::Label(label)) = attribution {
|
||||
realized += SpaceElem::shared().clone()
|
||||
+ CiteElem::new(*label).pack().spanned(self.span());
|
||||
}
|
||||
|
15
tests/ref/html/quote-attribution-link.html
Normal file
15
tests/ref/html/quote-attribution-link.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<blockquote cite="https://typst.app/home">
|
||||
Compose papers faster
|
||||
</blockquote>
|
||||
<p>
|
||||
— <a href="https://typst.app/home">typst.com</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
12
tests/ref/html/quote-nesting-html.html
Normal file
12
tests/ref/html/quote-nesting-html.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
When you said that “he surely meant that ‘she intended to say “I'm sorry”’”, I was quite confused.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
21
tests/ref/html/quote-plato.html
Normal file
21
tests/ref/html/quote-plato.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<blockquote>
|
||||
… ἔοικα γοῦν τούτου γε σμικρῷ τινι αὐτῷ τούτῳ σοφώτερος εἶναι, ὅτι ἃ μὴ οἶδα οὐδὲ οἴομαι εἰδέναι.
|
||||
</blockquote>
|
||||
<p>
|
||||
— Plato
|
||||
</p>
|
||||
<blockquote>
|
||||
… I seem, then, in just this little thing to be wiser than this man at any rate, that what I do not know I do not think I know either.
|
||||
</blockquote>
|
||||
<p>
|
||||
— from the Henry Cary literal translation of 1897
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -84,3 +84,26 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum].
|
||||
// With custom quotes.
|
||||
#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
|
||||
#quote[A #quote[nested] quote]
|
||||
|
||||
--- quote-plato html ---
|
||||
#set quote(block: true)
|
||||
|
||||
#quote(attribution: [Plato])[
|
||||
... ἔοικα γοῦν τούτου γε σμικρῷ τινι αὐτῷ τούτῳ σοφώτερος εἶναι, ὅτι
|
||||
ἃ μὴ οἶδα οὐδὲ οἴομαι εἰδέναι.
|
||||
]
|
||||
#quote(attribution: [from the Henry Cary literal translation of 1897])[
|
||||
... I seem, then, in just this little thing to be wiser than this man at
|
||||
any rate, that what I do not know I do not think I know either.
|
||||
]
|
||||
|
||||
--- quote-nesting-html html ---
|
||||
When you said that #quote[he surely meant that #quote[she intended to say #quote[I'm sorry]]], I was quite confused.
|
||||
|
||||
--- quote-attribution-link html ---
|
||||
#quote(
|
||||
block: true,
|
||||
attribution: link("https://typst.app/home")[typst.com]
|
||||
)[
|
||||
Compose papers faster
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user