mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart,
|
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::introspection::Locatable;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Alignment, BlockBody, BlockElem, Em, HElem, PadElem, Spacing, VElem,
|
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};
|
use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem};
|
||||||
|
|
||||||
/// Displays a quote alongside an optional attribution.
|
/// 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> {
|
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let mut realized = self.body.clone();
|
let mut realized = self.body.clone();
|
||||||
let block = self.block(styles);
|
let block = self.block(styles);
|
||||||
|
let html = TargetElem::target_in(styles).is_html();
|
||||||
|
|
||||||
if self.quotes(styles) == Smart::Custom(true) || !block {
|
if self.quotes(styles) == Smart::Custom(true) || !block {
|
||||||
let quotes = SmartQuotes::get(
|
let quotes = SmartQuotes::get(
|
||||||
@ -171,50 +173,65 @@ impl Show for Packed<QuoteElem> {
|
|||||||
let Depth(depth) = QuoteElem::depth_in(styles);
|
let Depth(depth) = QuoteElem::depth_in(styles);
|
||||||
let double = depth % 2 == 0;
|
let double = depth % 2 == 0;
|
||||||
|
|
||||||
|
if !html {
|
||||||
// Add zero-width weak spacing to make the quotes "sticky".
|
// Add zero-width weak spacing to make the quotes "sticky".
|
||||||
let hole = HElem::hole().pack();
|
let hole = HElem::hole().pack();
|
||||||
|
realized = Content::sequence([hole.clone(), realized, hole]);
|
||||||
|
}
|
||||||
realized = Content::sequence([
|
realized = Content::sequence([
|
||||||
TextElem::packed(quotes.open(double)),
|
TextElem::packed(quotes.open(double)),
|
||||||
hole.clone(),
|
|
||||||
realized,
|
realized,
|
||||||
hole,
|
|
||||||
TextElem::packed(quotes.close(double)),
|
TextElem::packed(quotes.close(double)),
|
||||||
])
|
])
|
||||||
.styled(QuoteElem::set_depth(Depth(1)));
|
.styled(QuoteElem::set_depth(Depth(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let attribution = self.attribution(styles);
|
||||||
|
|
||||||
if block {
|
if block {
|
||||||
realized = BlockElem::new()
|
realized = if html {
|
||||||
.with_body(Some(BlockBody::Content(realized)))
|
let mut elem = HtmlElem::new(tag::blockquote).with_body(Some(realized));
|
||||||
.pack()
|
if let Some(Attribution::Content(attribution)) = attribution {
|
||||||
.spanned(self.span());
|
if let Some(link) = attribution.to_packed::<LinkElem>() {
|
||||||
|
if let LinkTarget::Dest(Destination::Url(url)) = &link.dest {
|
||||||
if let Some(attribution) = self.attribution(styles).as_ref() {
|
elem = elem.with_attr(
|
||||||
let mut seq = vec![TextElem::packed('—'), SpaceElem::shared().clone()];
|
HtmlAttr::constant("cite"),
|
||||||
|
url.clone().into_inner(),
|
||||||
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()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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
|
if let Some(attribution) = attribution.as_ref() {
|
||||||
// quote.
|
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 gap = Spacing::Rel(Em::new(0.9).into());
|
||||||
let v = VElem::new(gap).with_weak(true).pack();
|
let v = VElem::new(gap).with_weak(true).pack();
|
||||||
realized += v + Content::sequence(seq).aligned(Alignment::END);
|
realized += v;
|
||||||
|
}
|
||||||
|
realized += Content::sequence(attribution).aligned(Alignment::END);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !html {
|
||||||
realized = PadElem::new(realized).pack();
|
realized = PadElem::new(realized).pack();
|
||||||
} else if let Some(Attribution::Label(label)) = self.attribution(styles) {
|
}
|
||||||
|
} else if let Some(Attribution::Label(label)) = attribution {
|
||||||
realized += SpaceElem::shared().clone()
|
realized += SpaceElem::shared().clone()
|
||||||
+ CiteElem::new(*label).pack().spanned(self.span());
|
+ 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.
|
// With custom quotes.
|
||||||
#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
|
#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
|
||||||
#quote[A #quote[nested] quote]
|
#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