libhanzzok/core/plugin/heading/
toc.rs1use std::{collections::VecDeque, iter::once};
2
3use serde_hzdata::HzdataValue;
4
5use crate::{
6 api::BlockConstructorRule,
7 codegen::{Context, HtmlNode},
8 core::{
9 ast::{BlockConstructorForm, InlineObjectNode},
10 plugin::heading::heading_meta::{Heading, HeadingList},
11 },
12};
13
14pub struct TocBlockConstructorRule;
15
16impl BlockConstructorRule for TocBlockConstructorRule {
17 fn name(&self) -> String {
18 "table-of-contents".to_owned()
19 }
20
21 fn form(&self) -> crate::core::ast::BlockConstructorForm {
22 BlockConstructorForm::Basic
23 }
24
25 fn apply(
26 &self,
27 _context: &mut Context,
28 _main_text: Vec<InlineObjectNode>,
29 _param: Option<HzdataValue>,
30 _: Vec<Vec<InlineObjectNode>>,
31 ) -> HtmlNode {
32 HtmlNode::create_lazy(|context| {
33 let meta: HeadingList = context.load_meta_or_default("heading", "list");
34
35 let mut stack = VecDeque::new();
36 stack.push_back((
37 Heading {
38 name: Vec::new(),
39 depth: 0,
40 },
41 HtmlNode::create_tag_builder("ol"),
42 ));
43
44 for heading in meta.values {
45 if heading.depth > 3 {
46 continue;
47 }
48
49 let depth = stack.back().unwrap().0.depth;
50
51 if depth >= heading.depth {
52 loop {
53 let (pop_heading, mut pop) = stack.pop_back().unwrap();
54 let (back_heading, back) = stack.back_mut().unwrap();
55
56 back.append(HtmlNode::create_tag(
57 "li",
58 &pop_heading
59 .name
60 .into_iter()
61 .map(|node_ref| node_ref.load_from(context).clone())
62 .chain(once(pop.build()))
63 .collect::<Vec<_>>(),
64 ));
65
66 if back_heading.depth < heading.depth {
67 break;
68 }
69 }
70 }
71
72 let depth = stack.back().unwrap().0.depth;
73
74 for depth in depth..(heading.depth - 1) {
75 stack.push_back((
76 Heading {
77 name: Vec::new(),
78 depth,
79 },
80 HtmlNode::create_tag_builder("ol"),
81 ));
82 }
83
84 stack.push_back((heading, HtmlNode::create_tag_builder("ol")));
85 }
86
87 loop {
88 let (pop_heading, mut pop) = stack.pop_back().unwrap();
89 let (back_heading, back) = stack.back_mut().unwrap();
90
91 back.append(HtmlNode::create_tag(
92 "li",
93 &pop_heading
94 .name
95 .into_iter()
96 .map(|node_ref| node_ref.load_from(context).clone())
97 .chain(once(pop.build()))
98 .collect::<Vec<_>>(),
99 ));
100
101 if back_heading.depth <= 0 {
102 break;
103 }
104 }
105
106 HtmlNode::create_tag_builder("div")
107 .append_all(stack.pop_front().map(|(_, mut builder)| builder.build()))
108 .set_attr("class", "table-of-contents")
109 .build()
110 })
111 }
112}