feat(template): themed blockquotes, references, chapter-based numbering for textbook

- Add themed blockquote styles (课堂小实验, 关键洞察, etc.) via quote block
- Add #references() wrapper for bibliography formatting
- Implement chapter-based figure/table numbering (1-1, 2-1, ...)
- Start each chapter on a new page (except first)
- Increase list spacing to match long-form reading rhythm
- Fix first-line indent to apply on all paragraphs
This commit is contained in:
Zhengshou Lai
2026-05-08 13:53:31 +08:00
parent 9eafd7577f
commit 11f5635c94
+84 -5
View File
@@ -5,6 +5,51 @@
#import "@preview/codly:1.3.0": *
#import "@preview/showybox:2.0.4": showybox
// ── helpers ──────────────────────────────────────────────────────────────
#let content-to-string(content) = {
if content.has("text") {
content.text
} else if content.has("children") {
content.children.map(content-to-string).join("")
} else if content.has("body") {
content-to-string(content.body)
} else if content == [ ] {
" "
} else {
""
}
}
// ── blockquote themes ────────────────────────────────────────────────────
#let blockquote-themes = (
("课堂小实验", rgb("#F0FDF4"), rgb("#16A34A")),
("关键洞察", rgb("#FFFBEB"), rgb("#D97706")),
("工程警示", rgb("#FEF2F2"), rgb("#DC2626")),
("前沿瞭望", rgb("#F3E8FF"), rgb("#9333EA")),
("历史视角", rgb("#FEF3C7"), rgb("#B45309")),
("你知道吗", rgb("#ECFEFF"), rgb("#0891B2")),
)
#let themed-blockquote(body) = {
let body-str = content-to-string(body)
let theme = blockquote-themes.find(t => body-str.contains(t.at(0)))
let (fill-color, stroke-color) = if theme != none {
(theme.at(1), theme.at(2))
} else {
(rgb("#F1F5F9"), rgb("#64748B"))
}
block(
fill: fill-color,
stroke: (left: 4pt + stroke-color),
inset: (x: 14pt, y: 10pt),
radius: (right: 4pt),
spacing: 1.2em,
body,
)
}
#let primary = rgb("#1F4E79")
#let secondary = rgb("#2C3E50")
#let accent = rgb("#3467A8")
@@ -92,6 +137,20 @@
table(..new_children, ..kwargs)
}
// Reference-list block: smaller text, hanging indent, tighter rhythm.
// Wrap a 参考文献 / References section's body via `#references[ ... ]`.
#let references(body) = block({
set text(size: 9.5pt)
set par(
justify: true,
leading: 0.7em,
spacing: 0.6em,
first-line-indent: 0pt,
hanging-indent: 1.5em,
)
body
})
#let textbook(
title: none,
subtitle: none,
@@ -124,7 +183,7 @@
justify: true,
leading: 0.86em,
spacing: 1.2em,
first-line-indent: 2em,
first-line-indent: (amount: 2em, all: true),
)
show: codly-init
@@ -140,12 +199,29 @@
show: great-theorems-init
set heading(numbering: "1.1")
// Same 11pt body as default: match list rhythm to default template
set list(indent: 1.2em, body-indent: 0.45em, spacing: 0.62em)
set enum(indent: 1.2em, body-indent: 0.45em, spacing: 0.62em)
// 按章编号:每章开始时重置图片/表格计数器
set figure(numbering: (..nums) => {
let h = counter(heading).get()
let ch = if h.len() > 0 { h.first() } else { 1 }
numbering("1-1", ch, nums.pos().first())
})
// List rhythm: spacing ≥ leading (0.86em), slightly looser than body for long-form reading
set list(indent: 1.2em, body-indent: 0.45em, spacing: 0.98em)
set enum(indent: 1.2em, body-indent: 0.45em, spacing: 0.98em)
// 每章从新页开始(第一章除外,因为前面已有 pagebreak)
let first-chapter = state("first-chapter", true)
// Vertical rhythm matches manual (long-form); font sizes differ
show heading.where(level: 1): it => {
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
context if not first-chapter.get() {
pagebreak()
}
first-chapter.update(false)
v(1.45em)
text(size: 18pt, weight: "bold", fill: primary, it)
v(0.55em)
@@ -163,6 +239,9 @@
v(0.4em)
}
// Blockquote → themed block
show quote.where(block: true): it => themed-blockquote(it.body)
set page(
paper: "a4",
margin: (top: 2.5cm, bottom: 2.5cm, left: 2.5cm, right: 2.5cm),