Skip to content

Commit 2ed632f

Browse files
authored
feat(html): better formatting for code & backport changes from deno-docs (#611)
1 parent 9f2b3cf commit 2ed632f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1127
-1220
lines changed

src/html/comrak_adapters.rs

Lines changed: 26 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -232,42 +232,24 @@ pub struct ToCEntry {
232232
pub anchor: String,
233233
}
234234

235-
#[derive(Debug)]
236-
pub struct ToCGroup {
237-
pub toc: Vec<ToCEntry>,
238-
pub id: Option<String>,
239-
}
240-
241235
#[derive(Clone)]
242236
pub struct HeadingToCAdapter {
243-
toc_groups: Arc<Mutex<Vec<ToCGroup>>>,
237+
toc: Arc<Mutex<Vec<ToCEntry>>>,
244238
anchorizer: Arc<Mutex<comrak::html::Anchorizer>>,
245239
offset: Arc<Mutex<u8>>,
246240
}
247241

248242
impl Default for HeadingToCAdapter {
249243
fn default() -> Self {
250244
Self {
251-
toc_groups: Arc::new(Mutex::new(vec![ToCGroup {
252-
toc: vec![],
253-
id: None,
254-
}])),
245+
toc: Arc::new(Mutex::new(vec![])),
255246
anchorizer: Arc::new(Mutex::new(Default::default())),
256247
offset: Arc::new(Mutex::new(0)),
257248
}
258249
}
259250
}
260251

261252
impl HeadingToCAdapter {
262-
pub fn create_group(&self, id: String) {
263-
let mut toc_groups = self.toc_groups.lock().unwrap();
264-
265-
toc_groups.push(ToCGroup {
266-
toc: vec![],
267-
id: Some(id),
268-
});
269-
}
270-
271253
pub fn anchorize(&self, content: String) -> String {
272254
let mut anchorizer = self.anchorizer.lock().unwrap();
273255
anchorizer.anchorize(content.clone())
@@ -279,19 +261,13 @@ impl HeadingToCAdapter {
279261
content: String,
280262
anchor: String,
281263
) -> String {
282-
let mut toc_groups = self.toc_groups.lock().unwrap();
264+
let mut toc = self.toc.lock().unwrap();
283265
let mut offset = self.offset.lock().unwrap();
284266

285267
*offset = level;
286268

287-
let toc_group = toc_groups.last_mut().unwrap();
288-
289-
if toc_group
290-
.toc
291-
.last()
292-
.map_or(true, |toc| toc.content != content)
293-
{
294-
toc_group.toc.push(ToCEntry {
269+
if toc.last().map_or(true, |toc| toc.content != content) {
270+
toc.push(ToCEntry {
295271
level,
296272
content,
297273
anchor: anchor.clone(),
@@ -301,64 +277,37 @@ impl HeadingToCAdapter {
301277
anchor
302278
}
303279

304-
pub fn into_toc(self) -> Vec<ToCGroup> {
305-
Arc::into_inner(self.toc_groups)
306-
.unwrap()
307-
.into_inner()
308-
.unwrap()
309-
}
310-
311280
pub fn render(self) -> Option<String> {
312-
let toc_groups = Arc::into_inner(self.toc_groups)
313-
.unwrap()
314-
.into_inner()
315-
.unwrap();
281+
let toc = Arc::into_inner(self.toc).unwrap().into_inner().unwrap();
316282

317-
if toc_groups.is_empty()
318-
|| (toc_groups.len() == 1 && toc_groups[0].toc.is_empty())
319-
{
283+
if toc.is_empty() {
320284
return None;
321285
}
322286

323-
let mut toc_content = vec![];
287+
let mut toc_content = vec!["<ul>".to_string()];
288+
let mut current_level = toc.first().unwrap().level;
324289

325-
for toc_group in toc_groups {
326-
if toc_group.toc.is_empty() {
327-
continue;
290+
for entry in toc {
291+
match current_level.cmp(&entry.level) {
292+
Ordering::Equal => {}
293+
Ordering::Less => {
294+
toc_content.push(r#"<li><ul>"#.to_string());
295+
current_level = entry.level;
296+
}
297+
Ordering::Greater => {
298+
toc_content.push("</ul></li>".to_string());
299+
current_level = entry.level;
300+
}
328301
}
329302

330303
toc_content.push(format!(
331-
r#"<ul{}>"#,
332-
toc_group
333-
.id
334-
.map(|id| format!(r#" id="{id}""#))
335-
.unwrap_or_default(),
304+
r##"<li><a href="#{}" title="{}">{}</a></li>"##,
305+
entry.anchor, entry.content, entry.content
336306
));
337-
338-
let mut current_level = 1;
339-
340-
for entry in toc_group.toc {
341-
match current_level.cmp(&entry.level) {
342-
Ordering::Equal => {}
343-
Ordering::Less => {
344-
toc_content.push(r#"<li><ul>"#.to_string());
345-
current_level = entry.level;
346-
}
347-
Ordering::Greater => {
348-
toc_content.push("</ul></li>".to_string());
349-
current_level = entry.level;
350-
}
351-
}
352-
353-
toc_content.push(format!(
354-
r##"<li><a href="#{}" title="{}">{}</a></li>"##,
355-
entry.anchor, entry.content, entry.content
356-
));
357-
}
358-
359-
toc_content.push(String::from("</ul>"));
360307
}
361308

309+
toc_content.push(String::from("</ul>"));
310+
362311
Some(toc_content.join(""))
363312
}
364313
}
@@ -376,8 +325,8 @@ impl HeadingAdapter for HeadingToCAdapter {
376325
let anchor = anchorizer.anchorize(heading.content.clone());
377326
writeln!(output, r#"<h{} id="{anchor}">"#, heading.level)?;
378327

379-
let mut lock = self.toc_groups.lock().unwrap();
380-
lock.last_mut().unwrap().toc.push(ToCEntry {
328+
let mut toc = self.toc.lock().unwrap();
329+
toc.push(ToCEntry {
381330
level: heading.level + *offset,
382331
content: heading.content.clone(),
383332
anchor,

src/html/jsdoc.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ fn render_node<'a>(
306306
pub struct MarkdownToHTMLOptions {
307307
pub summary: bool,
308308
pub summary_prefer_title: bool,
309+
pub no_toc: bool,
309310
}
310311

311312
pub fn markdown_to_html(
@@ -334,7 +335,9 @@ pub fn markdown_to_html(
334335
let mut plugins = comrak::Plugins::default();
335336
plugins.render.codefence_syntax_highlighter =
336337
Some(&render_ctx.ctx.highlight_adapter);
337-
plugins.render.heading_adapter = Some(&render_ctx.toc);
338+
if !render_options.no_toc {
339+
plugins.render.heading_adapter = Some(&render_ctx.toc);
340+
}
338341

339342
let md = parse_links(md, render_ctx);
340343

@@ -448,18 +451,24 @@ pub(crate) fn render_markdown_summary(
448451
MarkdownToHTMLOptions {
449452
summary: true,
450453
summary_prefer_title: true,
454+
no_toc: false,
451455
},
452456
)
453457
.unwrap_or_default()
454458
}
455459

456-
pub(crate) fn render_markdown(render_ctx: &RenderContext, md: &str) -> String {
460+
pub(crate) fn render_markdown(
461+
render_ctx: &RenderContext,
462+
md: &str,
463+
no_toc: bool,
464+
) -> String {
457465
markdown_to_html(
458466
render_ctx,
459467
md,
460468
MarkdownToHTMLOptions {
461469
summary: false,
462470
summary_prefer_title: false,
471+
no_toc,
463472
},
464473
)
465474
.unwrap_or_default()
@@ -477,6 +486,7 @@ pub(crate) fn jsdoc_body_to_html(
477486
MarkdownToHTMLOptions {
478487
summary,
479488
summary_prefer_title: true,
489+
no_toc: false,
480490
},
481491
)
482492
} else {
@@ -537,7 +547,8 @@ impl ExampleCtx {
537547
};
538548

539549
let markdown_title = render_markdown_summary(render_ctx, &title);
540-
let markdown_body = render_markdown(render_ctx, body.unwrap_or_default());
550+
let markdown_body =
551+
render_markdown(render_ctx, body.unwrap_or_default(), true);
541552

542553
ExampleCtx {
543554
anchor: AnchorCtx { id: id.to_string() },
@@ -571,6 +582,7 @@ impl ModuleDocCtx {
571582
Some(render_markdown(
572583
render_ctx,
573584
doc.as_deref().unwrap_or_default(),
585+
false,
574586
))
575587
} else {
576588
None
@@ -861,6 +873,7 @@ mod test {
861873
> foo
862874
>
863875
> bar"#,
876+
true,
864877
);
865878

866879
assert!(md.contains("foo"));
@@ -874,6 +887,7 @@ mod test {
874887
> foo
875888
>
876889
> bar"#,
890+
true,
877891
);
878892

879893
assert!(md.contains("foo"));

src/html/pages.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,23 @@ impl IndexCtx {
280280
render_ctx.toc.anchorize(title.clone()),
281281
);
282282

283-
let doc = ctx.category_docs.as_ref().and_then(|category_docs| {
284-
category_docs.get(&title).cloned().flatten()
285-
});
283+
let doc = ctx
284+
.category_docs
285+
.as_ref()
286+
.and_then(|category_docs| {
287+
category_docs.get(&title).cloned().flatten()
288+
})
289+
.and_then(|doc| {
290+
super::jsdoc::markdown_to_html(
291+
&render_ctx,
292+
&doc,
293+
super::jsdoc::MarkdownToHTMLOptions {
294+
summary: false,
295+
summary_prefer_title: false,
296+
no_toc: false,
297+
},
298+
)
299+
});
286300

287301
util::SectionCtx {
288302
header: SectionHeaderCtx {

src/html/parameters.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,13 @@ pub(crate) fn render_params(
99
) -> String {
1010
if params.is_empty() {
1111
String::new()
12-
} else if params.len() <= 3 {
13-
let items = params
14-
.iter()
15-
.enumerate()
16-
.map(|(i, element)| render_param(ctx, element, i))
17-
.collect::<Vec<String>>()
18-
.join("<span>, </span>");
19-
20-
format!("<span>{items}</span>")
12+
} else if params.len() == 1 {
13+
format!("<span>{}</span>", render_param(ctx, &params[0], 0))
2114
} else {
2215
let mut items = Vec::with_capacity(params.len());
2316

2417
for (i, def) in params.iter().enumerate() {
25-
items.push(format!("<div>{}</div>", render_param(ctx, def, i)));
18+
items.push(format!("<div>{},</div>", render_param(ctx, def, i)));
2619
}
2720

2821
let content = items.join("");

src/html/render_context.rs

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -265,27 +265,28 @@ impl<'ctx> RenderContext<'ctx> {
265265
});
266266
}
267267

268-
let (_, symbol_parts) = symbol.split('.').enumerate().fold(
269-
(vec![], vec![]),
270-
|(mut symbol_parts, mut breadcrumbs), (i, symbol_part)| {
271-
symbol_parts.push(symbol_part);
272-
let breadcrumb = BreadcrumbCtx {
273-
name: symbol_part.to_string(),
274-
href: self.ctx.resolve_path(
275-
self.current_resolve,
276-
UrlResolveKind::Symbol {
277-
file,
278-
symbol: &symbol_parts.join("."),
279-
},
280-
),
281-
is_symbol: true,
282-
is_first_symbol: i == 0,
283-
};
284-
breadcrumbs.push(breadcrumb);
285-
286-
(symbol_parts, breadcrumbs)
287-
},
288-
);
268+
let (_, symbol_parts) =
269+
split_with_brackets(symbol).into_iter().enumerate().fold(
270+
(vec![], vec![]),
271+
|(mut symbol_parts, mut breadcrumbs), (i, symbol_part)| {
272+
symbol_parts.push(symbol_part.clone());
273+
let breadcrumb = BreadcrumbCtx {
274+
name: symbol_part,
275+
href: self.ctx.resolve_path(
276+
self.current_resolve,
277+
UrlResolveKind::Symbol {
278+
file,
279+
symbol: &symbol_parts.join("."),
280+
},
281+
),
282+
is_symbol: true,
283+
is_first_symbol: i == 0,
284+
};
285+
breadcrumbs.push(breadcrumb);
286+
287+
(symbol_parts, breadcrumbs)
288+
},
289+
);
289290

290291
parts.extend(symbol_parts);
291292

@@ -297,6 +298,41 @@ impl<'ctx> RenderContext<'ctx> {
297298
}
298299
}
299300

301+
fn split_with_brackets(s: &str) -> Vec<String> {
302+
let mut result = Vec::new();
303+
let mut current = String::new();
304+
let mut bracket = false;
305+
306+
for c in s.chars() {
307+
if c == ']' {
308+
bracket = false;
309+
current.push(c);
310+
result.push(current.clone());
311+
current.clear();
312+
} else if c == '[' {
313+
bracket = true;
314+
if !current.is_empty() {
315+
result.push(current.clone());
316+
current.clear();
317+
}
318+
current.push(c);
319+
} else if c == '.' && !bracket {
320+
if !current.is_empty() {
321+
result.push(current.clone());
322+
current.clear();
323+
}
324+
} else {
325+
current.push(c);
326+
}
327+
}
328+
329+
if !current.is_empty() {
330+
result.push(current.clone());
331+
}
332+
333+
result
334+
}
335+
300336
fn get_current_imports(
301337
doc_nodes: &[DocNodeWithContext],
302338
) -> HashMap<String, String> {

0 commit comments

Comments
 (0)