rust-lang / mdBook

Create book from markdown files. Like Gitbook but implemented in Rust
https://rust-lang.github.io/mdBook/
Mozilla Public License 2.0
17.25k stars 1.58k forks source link

provide mermaidjs rendering #762

Open rbuckland opened 5 years ago

rbuckland commented 5 years ago

MermaidJS provides and excellent set of "features" for diagramming. I would like to provide this feature, similar to MathJAX into mdBook as an optional feature.

I am after some guidance regarding modularity.

  1. how do I cater for renderers such as epub or PDF ?
  2. at a high level, which way is it preferred to be integrated. With my quick hacking, I suspect it is with the hbs_renderer.rs (adding html.mermaidjs_support) flags for example.

Guidance will be appreciated.

mattico commented 5 years ago

I think this could be implemented as a preprocessor. It could transform something like this:

```mermaidjs
chart goes here
```

into this

<div class="mermaid">
chart goes here
</div>

The mermaidjs script tag could be added with output.html.additional-js, or perhaps an additional-js facility could be added onto the preprocessor itself somehow.

badboy commented 5 years ago

I built this as a preprocessor: https://github.com/badboy/mdbook-mermaid Would this be considered to be included in mdbook directly?

Michael-F-Bryan commented 5 years ago

I think this would be better written as a custom preprocessor, using a similar mechanism to custom renderers (e.g. the CmdRenderer). That way you can provide mdbook-mermaid as its own binary on crates.io and it won't be bound to mdbook's release cycle.

... Of course users can't yet provide their own preprocessors for mdbook to run, so we'd need to implement. You can wrap mdbook like @badboy has done, but that's not as nice as mdbook being able to shell out to a mdbook-mermaid preprocessor and interact with it via stdin/stdout.

badboy commented 5 years ago

That’s exactly the issue I’m facing right now. I built 2 different preprocessors, but that also means I needed to build a mdbook clone that adds these 2 preprocessors. Going forward it would be nice to either have a mechanism to use external preprocessors or to include more preprocessors in mdbook itself.

Michael-F-Bryan commented 5 years ago

I wrote up some support for custom preprocessors. The idea is mdbook will shell out to the provided program, writing the inputs to stdin as JSON and reading the processed book from stdout. It's the same approach we use for custom renderers and should compose a lot better :slightly_smiling_face:

It'd be awesome if someone could review the PR and see if the example is intuitive enough.

sytsereitsma commented 5 years ago

You do not strictly need a preprocessor for this, it works with a javascript hack too. From my notes:

Mermaid preprocessor

You can easily use mermaid by making the following changes to your book.toml:

[output.html]
additional-css = ["mermaid.css"]
additional-js = ["mermaid.min.js", "mermaid-init.js"]

Where:

patchMermaidCodeElementClass(); mermaid.initialize({startOnLoad:true});

The stylesheet borks the rendering of the TOC though. To fix this remove the opacity of the '.section' definition (line 95 in version 3cea267 dated 2 Oct 2018):
```css
.section {
  stroke: none;
  /*opacity: 0.2;*/
}
THeK3nger commented 3 years ago

I am here with a small update of @sytsereitsma's snippet. I had a problem with the original code on some browser because elements dynamically change when you modify the className of one of its elements. As a consequence I was not able to render multiple mermaid diagrams in a single page.

I have updated the code by duplicating elements to avoid side-effect:

// mdBook creates <code> elements with the class 'language-mermaid hljs' whenever you
// define a mermaid code block.
// The mermaid javascript parser looks for elements with a class name 'mermaid'.
// So simply change the class name of the elements to 'mermaid' to make everything work.
function patchMermaidCodeElementClass() {
    var elements = document.getElementsByClassName("language-mermaid");
    Array.from(elements).forEach(element => {
        if (element.tagName.toLowerCase() == "code") {
            element.className = "mermaid";
        }
    });

}

patchMermaidCodeElementClass();
mermaid.initialize({startOnLoad:true});