Skip to content

Commit e3c1e38

Browse files
authored
feat(html): remove sidepanels and add document navigation (#569)
1 parent 5341200 commit e3c1e38

40 files changed

+1064
-3883
lines changed

src/html/comrak_adapters.rs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use comrak::adapters::HeadingAdapter;
77
use comrak::adapters::HeadingMeta;
88
use comrak::adapters::SyntaxHighlighterAdapter;
99
use comrak::nodes::Sourcepos;
10+
use std::cmp::Ordering;
1011
use std::collections::HashMap;
1112
use std::io::Write;
1213
use std::sync::Arc;
@@ -224,15 +225,75 @@ impl SyntaxHighlighterAdapter for HighlightAdapter {
224225
}
225226
}
226227

227-
#[derive(Default)]
228+
#[derive(Debug)]
229+
pub struct ToCEntry {
230+
pub level: u8,
231+
pub content: String,
232+
pub anchor: String,
233+
}
234+
235+
#[derive(Default, Clone)]
228236
pub struct HeadingToCAdapter {
229-
toc: Mutex<Vec<(u8, String, String)>>,
230-
anchorizer: Mutex<comrak::html::Anchorizer>,
237+
toc: Arc<Mutex<Vec<ToCEntry>>>,
238+
anchorizer: Arc<Mutex<comrak::html::Anchorizer>>,
239+
offset: Arc<Mutex<u8>>,
231240
}
232241

233242
impl HeadingToCAdapter {
234-
pub fn into_toc(self) -> Vec<(u8, String, String)> {
235-
self.toc.into_inner().unwrap()
243+
pub fn add_entry(&self, level: u8, content: String) -> String {
244+
let mut lock = self.toc.lock().unwrap();
245+
let mut anchorizer = self.anchorizer.lock().unwrap();
246+
let mut offset = self.offset.lock().unwrap();
247+
248+
let anchor = anchorizer.anchorize(content.clone());
249+
*offset = level;
250+
251+
lock.push(ToCEntry {
252+
level,
253+
content,
254+
anchor: anchor.clone(),
255+
});
256+
257+
anchor
258+
}
259+
260+
pub fn into_toc(self) -> Vec<ToCEntry> {
261+
Arc::into_inner(self.toc).unwrap().into_inner().unwrap()
262+
}
263+
264+
pub fn render(self) -> Option<String> {
265+
let toc = Arc::into_inner(self.toc).unwrap().into_inner().unwrap();
266+
267+
if toc.is_empty() {
268+
return None;
269+
}
270+
271+
let mut toc_content = vec![String::from(r#"<ul>"#)];
272+
273+
let mut current_level = 1;
274+
275+
for entry in toc {
276+
match current_level.cmp(&entry.level) {
277+
Ordering::Equal => {}
278+
Ordering::Less => {
279+
toc_content.push(r#"<li><ul>"#.to_string());
280+
current_level = entry.level;
281+
}
282+
Ordering::Greater => {
283+
toc_content.push("</ul></li>".to_string());
284+
current_level = entry.level;
285+
}
286+
}
287+
288+
toc_content.push(format!(
289+
r##"<li><a href="#{}" title="{}">{}</a></li>"##,
290+
entry.anchor, entry.content, entry.content
291+
));
292+
}
293+
294+
toc_content.push(String::from("</ul>"));
295+
296+
Some(toc_content.join(""))
236297
}
237298
}
238299

@@ -244,12 +305,17 @@ impl HeadingAdapter for HeadingToCAdapter {
244305
_sourcepos: Option<Sourcepos>,
245306
) -> std::io::Result<()> {
246307
let mut anchorizer = self.anchorizer.lock().unwrap();
308+
let offset = self.offset.lock().unwrap();
247309

248310
let anchor = anchorizer.anchorize(heading.content.clone());
249311
writeln!(output, r#"<h{} id="{anchor}">"#, heading.level)?;
250312

251313
let mut lock = self.toc.lock().unwrap();
252-
lock.push((heading.level, heading.content.clone(), anchor));
314+
lock.push(ToCEntry {
315+
level: heading.level + *offset,
316+
content: heading.content.clone(),
317+
anchor,
318+
});
253319

254320
Ok(())
255321
}

src/html/jsdoc.rs

Lines changed: 12 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use super::render_context::RenderContext;
22
use super::util::*;
3-
use crate::html::usage::UsagesCtx;
43
use crate::html::ShortPath;
54
use crate::js_doc::JsDoc;
65
use crate::js_doc::JsDocTag;
@@ -13,7 +12,6 @@ use comrak::Arena;
1312
use serde::Serialize;
1413
use std::borrow::Cow;
1514
use std::cell::RefCell;
16-
use std::cmp::Ordering;
1715

1816
#[cfg(feature = "ammonia")]
1917
use crate::html::comrak_adapters::URLRewriter;
@@ -247,23 +245,16 @@ fn render_node<'a>(
247245
String::from_utf8(bw.into_inner().unwrap()).unwrap()
248246
}
249247

250-
#[derive(Debug, Serialize, Clone, Default)]
251-
pub struct Markdown {
252-
pub html: String,
253-
pub toc: Option<String>,
254-
}
255-
256248
pub struct MarkdownToHTMLOptions {
257249
pub summary: bool,
258250
pub summary_prefer_title: bool,
259-
pub render_toc: bool,
260251
}
261252

262253
pub fn markdown_to_html(
263254
render_ctx: &RenderContext,
264255
md: &str,
265256
render_options: MarkdownToHTMLOptions,
266-
) -> Option<Markdown> {
257+
) -> Option<String> {
267258
// TODO(bartlomieju): this should be initialized only once
268259
let mut options = comrak::Options::default();
269260
options.extension.autolink = true;
@@ -285,9 +276,7 @@ pub fn markdown_to_html(
285276
let mut plugins = comrak::Plugins::default();
286277
plugins.render.codefence_syntax_highlighter =
287278
Some(&render_ctx.ctx.highlight_adapter);
288-
let heading_adapter =
289-
crate::html::comrak_adapters::HeadingToCAdapter::default();
290-
plugins.render.heading_adapter = Some(&heading_adapter);
279+
plugins.render.heading_adapter = Some(&render_ctx.toc);
291280

292281
let md = parse_links(md, render_ctx);
293282

@@ -323,7 +312,7 @@ pub fn markdown_to_html(
323312
let mut ammonia_builder = ammonia::Builder::default();
324313

325314
ammonia_builder
326-
.add_tags(["video", "button", "svg", "path"])
315+
.add_tags(["video", "button", "svg", "path", "rect"])
327316
.add_generic_attributes(["id", "align"])
328317
.add_tag_attributes("button", ["data-copy"])
329318
.add_tag_attributes(
@@ -353,6 +342,7 @@ pub fn markdown_to_html(
353342
"stroke-linejoin",
354343
],
355344
)
345+
.add_tag_attributes("rect", ["x", "y", "width", "height", "fill"])
356346
.add_tag_attributes("video", ["src", "controls"])
357347
.add_allowed_classes("pre", ["highlight"])
358348
.add_allowed_classes("button", ["context_button"])
@@ -387,46 +377,7 @@ pub fn markdown_to_html(
387377
html = ammonia_builder.clean(&html).to_string();
388378
}
389379

390-
let toc = if render_options.render_toc {
391-
let toc = heading_adapter.into_toc();
392-
393-
if toc.is_empty() {
394-
None
395-
} else {
396-
let mut toc_content = vec![String::from(r#"<nav class="toc"><ul>"#)];
397-
398-
let mut current_level = 1;
399-
400-
for (level, heading, anchor) in toc {
401-
match current_level.cmp(&level) {
402-
Ordering::Equal => {}
403-
Ordering::Less => {
404-
toc_content.push(r#"<li><ul>"#.to_string());
405-
current_level = level;
406-
}
407-
Ordering::Greater => {
408-
toc_content.push("</ul></li>".to_string());
409-
current_level = level;
410-
}
411-
}
412-
413-
toc_content.push(format!(
414-
r##"<li><a href="#{anchor}" title="{heading}">{heading}</a></li>"##
415-
));
416-
}
417-
418-
toc_content.push(String::from("</ul></nav>"));
419-
420-
Some(toc_content.join(""))
421-
}
422-
} else {
423-
None
424-
};
425-
426-
Some(Markdown {
427-
html: format!(r#"<div class="{class_name} flex-1">{html}</div>"#),
428-
toc,
429-
})
380+
Some(format!(r#"<div class="{class_name}">{html}</div>"#))
430381
}
431382

432383
pub(crate) fn render_markdown_summary(
@@ -439,11 +390,9 @@ pub(crate) fn render_markdown_summary(
439390
MarkdownToHTMLOptions {
440391
summary: true,
441392
summary_prefer_title: false,
442-
render_toc: false,
443393
},
444394
)
445395
.unwrap_or_default()
446-
.html
447396
}
448397

449398
pub(crate) fn render_markdown(render_ctx: &RenderContext, md: &str) -> String {
@@ -453,11 +402,9 @@ pub(crate) fn render_markdown(render_ctx: &RenderContext, md: &str) -> String {
453402
MarkdownToHTMLOptions {
454403
summary: false,
455404
summary_prefer_title: false,
456-
render_toc: false,
457405
},
458406
)
459407
.unwrap_or_default()
460-
.html
461408
}
462409

463410
pub(crate) fn jsdoc_body_to_html(
@@ -472,10 +419,8 @@ pub(crate) fn jsdoc_body_to_html(
472419
MarkdownToHTMLOptions {
473420
summary,
474421
summary_prefer_title: false,
475-
render_toc: false,
476422
},
477423
)
478-
.map(|markdown| markdown.html)
479424
} else {
480425
None
481426
}
@@ -503,6 +448,7 @@ pub(crate) fn jsdoc_examples(
503448

504449
if !examples.is_empty() {
505450
Some(SectionCtx::new(
451+
ctx,
506452
"Examples",
507453
SectionContentCtx::Example(examples),
508454
))
@@ -547,8 +493,6 @@ impl ExampleCtx {
547493
#[derive(Debug, Serialize, Clone, Default)]
548494
pub struct ModuleDocCtx {
549495
pub deprecated: Option<String>,
550-
pub usages: Option<UsagesCtx>,
551-
pub toc: Option<String>,
552496
pub sections: super::SymbolContentCtx,
553497
}
554498

@@ -560,7 +504,7 @@ impl ModuleDocCtx {
560504

561505
let mut sections = Vec::with_capacity(7);
562506

563-
let (deprecated, html, toc) = if let Some(node) = module_doc_nodes
507+
let (deprecated, html) = if let Some(node) = module_doc_nodes
564508
.iter()
565509
.find(|n| n.kind == DocNodeKind::ModuleDoc)
566510
{
@@ -579,26 +523,11 @@ impl ModuleDocCtx {
579523
sections.push(examples);
580524
}
581525

582-
let (html, toc) = if let Some(markdown) =
583-
node.js_doc.doc.as_ref().and_then(|doc| {
584-
markdown_to_html(
585-
render_ctx,
586-
doc,
587-
MarkdownToHTMLOptions {
588-
summary: false,
589-
summary_prefer_title: false,
590-
render_toc: true,
591-
},
592-
)
593-
}) {
594-
(Some(markdown.html), markdown.toc)
595-
} else {
596-
(None, None)
597-
};
526+
let html = jsdoc_body_to_html(render_ctx, &node.js_doc, false);
598527

599-
(deprecated, html, toc)
528+
(deprecated, html)
600529
} else {
601-
(None, None, None)
530+
(None, None)
602531
};
603532

604533
if !short_path.is_main {
@@ -612,7 +541,8 @@ impl ModuleDocCtx {
612541
.map(|(title, nodes)| {
613542
(
614543
SectionHeaderCtx {
615-
title,
544+
title: title.clone(),
545+
anchor: AnchorCtx { id: title },
616546
href: None,
617547
doc: None,
618548
},
@@ -625,8 +555,6 @@ impl ModuleDocCtx {
625555

626556
Self {
627557
deprecated,
628-
usages: UsagesCtx::new(render_ctx, &[]),
629-
toc,
630558
sections: super::SymbolContentCtx {
631559
id: "module_doc".to_string(),
632560
docs: html,

0 commit comments

Comments
 (0)