feat(cheddar): Implement callout paragraphs
Implements support for tagging paragraphs that begin with a callout word (TODO, WARNING, QUESTION, TIP) with an additional `cheddar-*` class that makes it possible to render these callouts specially. This is currently not the nicest implementation, but it works.
This commit is contained in:
parent
d4e469508f
commit
2236d43ff7
1 changed files with 68 additions and 0 deletions
|
@ -1,6 +1,8 @@
|
|||
use comrak::arena_tree::Node;
|
||||
use comrak::nodes::{Ast, AstNode, NodeValue, NodeCodeBlock, NodeHtmlBlock};
|
||||
use comrak::{Arena, parse_document, format_html, ComrakOptions};
|
||||
use lazy_static::lazy_static;
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::BufRead;
|
||||
|
@ -122,6 +124,52 @@ fn highlight_code_block(code_block: &NodeCodeBlock) -> NodeValue {
|
|||
NodeValue::HtmlBlock(block)
|
||||
}
|
||||
|
||||
// Supported callout elements (which each have their own distinct rendering):
|
||||
enum Callout {
|
||||
Todo,
|
||||
Warning,
|
||||
Question,
|
||||
Tip,
|
||||
}
|
||||
|
||||
// Determine whether the first child of the supplied node contains a text that
|
||||
// should cause a callout section to be rendered.
|
||||
fn has_callout<'a>(node: &Node<'a, RefCell<Ast>>) -> Option<Callout> {
|
||||
match node.first_child().map(|c| c.data.borrow()) {
|
||||
Some(child) => match &child.value {
|
||||
NodeValue::Text(text) => {
|
||||
if text.starts_with("TODO".as_bytes()) {
|
||||
return Some(Callout::Todo)
|
||||
} else if text.starts_with("WARNING".as_bytes()) {
|
||||
return Some(Callout::Warning)
|
||||
} else if text.starts_with("QUESTION".as_bytes()) {
|
||||
return Some(Callout::Question)
|
||||
} else if text.starts_with("TIP".as_bytes()) {
|
||||
return Some(Callout::Tip)
|
||||
}
|
||||
|
||||
return None
|
||||
},
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_callout_paragraph(callout: Callout) -> NodeValue {
|
||||
let class = match callout {
|
||||
Callout::Todo => "cheddar-todo",
|
||||
Callout::Warning => "cheddar-warning",
|
||||
Callout::Question => "cheddar-question",
|
||||
Callout::Tip => "cheddar-tip",
|
||||
};
|
||||
|
||||
NodeValue::HtmlBlock(NodeHtmlBlock {
|
||||
block_type: 1,
|
||||
literal: format!("<p class=\"cheddar-callout {}\">", class).into_bytes(),
|
||||
})
|
||||
}
|
||||
|
||||
fn format_markdown() {
|
||||
let document = {
|
||||
let mut buffer = String::new();
|
||||
|
@ -134,12 +182,32 @@ fn format_markdown() {
|
|||
let arena = Arena::new();
|
||||
let root = parse_document(&arena, &document, &MD_OPTS);
|
||||
|
||||
// This node must exist with a lifetime greater than that of the parsed AST
|
||||
// in case that callouts are encountered (otherwise insertion into the tree
|
||||
// is not possible).
|
||||
let p_close = Node::new(RefCell::new(Ast {
|
||||
start_line: 0, // TODO(tazjin): hrmm
|
||||
content: vec![],
|
||||
open: false,
|
||||
last_line_blank: false,
|
||||
value: NodeValue::HtmlBlock(NodeHtmlBlock {
|
||||
block_type: 1,
|
||||
literal: "</p>".as_bytes().to_vec(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Syntax highlighting is implemented by traversing the arena and
|
||||
// replacing all code blocks with HTML blocks rendered by syntect.
|
||||
iter_nodes(root, &|node| {
|
||||
let mut ast = node.data.borrow_mut();
|
||||
let new = match &ast.value {
|
||||
NodeValue::CodeBlock(code) => Some(highlight_code_block(code)),
|
||||
NodeValue::Paragraph => if let Some(callout) = has_callout(node) {
|
||||
node.insert_after(&p_close);
|
||||
Some(format_callout_paragraph(callout))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue