-
-
Notifications
You must be signed in to change notification settings - Fork 150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create custom HTML formatters. #540
base: main
Are you sure you want to change the base?
Conversation
Run on Sat Feb 22 02:06:27 UTC 2025 |
Looking pretty good! I think we're going to need to have the context and the node available within the formatter in order to be able to do certain overrides. It doesn't look like that's exposed at the moment. |
Love it, you've really separated out the macro, it's getting pretty clean. |
OK! Keep in mind you may not actually always need the node itself (i.e. the I've updated this to now expose a variety of things, depending on the names (!) of the captures. I've also added the ability to suppress children from being rendered, so you can completely take control of a subtree. The updated example shows off all the captures available: create_formatter!(CustomFormatter, {
NodeValue::Emph => |output, entering| {
if entering {
output.write_all(b"<i>")?;
} else {
output.write_all(b"</i>")?;
}
},
NodeValue::Strong => |context, entering| {
use std::io::Write;
context.write_all(if entering { b"<b>" } else { b"</b>" })?;
},
NodeValue::Image(ref nl) => |output, node, entering, suppress_children| {
assert!(node.data.borrow().sourcepos == (3, 1, 3, 18).into());
if entering {
output.write_all(nl.url.to_uppercase().as_bytes())?;
*suppress_children = true;
}
},
}); |
Looks good. I'm frazzled for the night. I'll have a chance to look more closely on Sun. But really, I think it's about ready to go. |
👍 I'll finish off the documentation; let me know, do try taking it for an actual spin (using a Git branch dependency) before we move to merge! |
I've already got one in progress 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kivikakk this is really good! Really appreciate you working on this and fleshing it out. I just had a couple minor comments.
I picked overriding NodeValue::Heading
because I actually need to modify it to add aria-describedby
to give better assistance support.
9403862
to
aac8059
Compare
aac8059
to
78467cc
Compare
@kivikakk this is awesome! It's working well for me. Really appreciate you knocking this out 😍 Here's the code I'm usingThis is just about moving the text of the header inside the pub fn markdown_to_html_with_plugins(md: &str, options: &Options, plugins: &Plugins) -> String {
let arena = Arena::new();
let root = parse_document(&arena, md, options);
let mut bw = BufWriter::new(Vec::new());
CustomFormatter::format_document_with_plugins(root, options, &mut bw, plugins).unwrap();
String::from_utf8(bw.into_inner().unwrap()).unwrap()
}
create_formatter!(CustomFormatter, {
NodeValue::Heading(_) => |context, node, entering| {
return render_heading(context, node, entering);
},
});
fn render_heading<'a>(
context: &mut Context,
node: &'a AstNode<'a>,
entering: bool,
) -> io::Result<ChildRendering> {
let NodeValue::Heading(ref nch) = node.data.borrow().value else {
panic!()
};
match context.plugins.render.heading_adapter {
None => {
if entering {
context.cr()?;
write!(context, "<h{}", nch.level)?;
render_sourcepos(context, node)?;
context.write_all(b">")?;
if let Some(ref prefix) = context.options.extension.header_ids {
let mut text_content = Vec::with_capacity(20);
collect_text(node, &mut text_content);
let mut id = String::from_utf8(text_content).unwrap();
id = context.anchorizer.anchorize(id);
write!(
context,
"<a href=\"#{}\" aria-hidden=\"true\" class=\"anchor\" id=\"{}{}\">",
id, prefix, id
)?;
}
} else {
if let Some(ref _prefix) = context.options.extension.header_ids {
write!(context, "</a>")?;
}
writeln!(context, "</h{}>", nch.level)?;
}
Ok(ChildRendering::HTML)
}
Some(_adapter) => { format_node_default(context, node, entering) }
}
} I think this is ready to go! 🚀 |
o/ @digitalmoksha
See
examples/custom_formatter.rs
for what this looks like when being used from a different crate.I've refactored some things out, added the new
html::Context
struct (which contains everythingHtmlFormatter
itself used to), movedWriteWithLast
's functionality into that, addedhtml::RenderMode
(instead of theplain
boolean).Still need to document newly public things!
Let me know your thoughts.