Skip to content
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

Add complete JS example for UML #876

Closed
ofek opened this issue Apr 6, 2020 · 17 comments
Closed

Add complete JS example for UML #876

ofek opened this issue Apr 6, 2020 · 17 comments
Labels
C: docs Related to documentation. T: support Support request.

Comments

@ofek
Copy link
Sponsor Contributor

ofek commented Apr 6, 2020

Idea being I can put it in one file like before:

(function () {
    'use strict';

    /**
     * Targets special code or div blocks and converts them to UML.
     * @param {object} converter is the object that transforms the text to UML.
     * @param {string} className is the name of the class to target.
     * @param {object} settings is the settings for converter.
     * @return {void}
     */
    var uml = (function (converter, className, settings) {

      var getFromCode = function getFromCode(parent) {
        // Handles <pre><code>
        var text = "";
        for (var j = 0; j < parent.childNodes.length; j++) {
          var subEl = parent.childNodes[j];
          if (subEl.tagName.toLowerCase() === "code") {
            for (var k = 0; k < subEl.childNodes.length; k++) {
              var child = subEl.childNodes[k];
              var whitespace = /^\s*$/;
              if (child.nodeName === "#text" && !whitespace.test(child.nodeValue)) {
                text = child.nodeValue;
                break;
              }
            }
          }
        }
        return text;
      };

      var getFromDiv = function getFromDiv(parent) {
        // Handles <div>
        return parent.textContent || parent.innerText;
      };

      // Change body to whatever element your main Markdown content lives.
      var body = document.querySelectorAll("body");
      var blocks = document.querySelectorAll("pre." + className + ",div." + className

      // Is there a settings object?
      );var config = settings === void 0 ? {} : settings;

      // Find the UML source element and get the text
      for (var i = 0; i < blocks.length; i++) {
        var parentEl = blocks[i];
        var el = document.createElement("div");
        el.className = className;
        el.style.visibility = "hidden";
        el.style.position = "absolute";

        var text = parentEl.tagName.toLowerCase() === "pre" ? getFromCode(parentEl) : getFromDiv(parentEl);

        // Insert our new div at the end of our content to get general
        // typeset and page sizes as our parent might be `display:none`
        // keeping us from getting the right sizes for our SVG.
        // Our new div will be hidden via "visibility" and take no space
        // via `position: absolute`. When we are all done, use the
        // original node as a reference to insert our SVG back
        // into the proper place, and then make our SVG visible again.
        // Lastly, clean up the old node.
        body[0].appendChild(el);
        var diagram = converter.parse(text);
        diagram.drawSVG(el, config);
        el.style.visibility = "visible";
        el.style.position = "static";
        parentEl.parentNode.insertBefore(el, parentEl);
        parentEl.parentNode.removeChild(parentEl);
      }
    });

    (function () {
      var onReady = function onReady(fn) {
        if (document.addEventListener) {
          document.addEventListener("DOMContentLoaded", fn);
        } else {
          document.attachEvent("onreadystatechange", function () {
            if (document.readyState === "interactive") {
              fn();
            }
          });
        }
      };

      onReady(function () {
        if (typeof flowchart !== "undefined") {
          uml(flowchart, "uml-flowchart");
        }

        if (typeof Diagram !== "undefined") {
          uml(Diagram, "uml-sequence-diagram", { theme: "simple" });
        }
    });
})();

}());

but now your loader imports uml from a 2nd file and I think that requires a JS build tool?

@gir-bot gir-bot added the S: triage Issue needs triage. label Apr 6, 2020
@facelessuser
Copy link
Owner

It has always been in two separate files. I used to provide an example that was generated from the build system. It is generated in a non-minified form if you you run npm run serve from the project directory. Assuming you've run npm install prior:

(function () {
  'use strict';

  /**
   * Targets special code or div blocks and converts them to UML.
   * @param {string} className is the name of the class to target.
   * @return {void}
   */
  var uml = (function (className) {
    var getFromCode = function getFromCode(parent) {
      // Handles <pre><code> text extraction.
      var text = "";

      for (var j = 0; j < parent.childNodes.length; j++) {
        var subEl = parent.childNodes[j];

        if (subEl.tagName.toLowerCase() === "code") {
          for (var k = 0; k < subEl.childNodes.length; k++) {
            var child = subEl.childNodes[k];
            var whitespace = /^\s*$/;

            if (child.nodeName === "#text" && !whitespace.test(child.nodeValue)) {
              text = child.nodeValue;
              break;
            }
          }
        }
      }

      return text;
    }; // Find all of our Mermaid sources and render them.


    var blocks = document.querySelectorAll("pre.".concat(className));

    var _loop = function _loop(i) {
      var parentEl = blocks[i]; // Create a temporary element with the typeset and size we desire.
      // Insert it at the end of our parent to render the SVG.

      var temp = document.createElement("div");
      temp.style.visibility = "hidden";
      temp.style.width = "100%";
      temp.style.minWidth = "100%";
      temp.style.fontSize = "16px";
      parentEl.parentNode.appendChild(temp);
      mermaid.mermaidAPI.render("_mermaind_".concat(i), getFromCode(parentEl), function (content) {
        var el = document.createElement("div");
        el.className = className;
        el.innerHTML = content;
        var child = el.childNodes[0]; // Some mermaid items have no height assigned, fix this for sane sizes. Mainly for state diagrams.
        //
        // Notes (as of Mermaid 8.4.8):
        // - Gantt: width is always relative to the parent, if you have a small parent, the chart will be squashed.
        //   Can't help it.
        // - Pie: These charts have no default height or width. Good luck pinning them down to a reasonable size.
        // - Git: The render portion is agnostic to the size of the parent element. But padding of the SVG is relative
        //   to the parent element. You will never find a happy size.
        // - State/Class: These two are rendered with references internally in the SVG that certain elements link to.
        //   The problem is that they link to internal elements that use the same IDs that are in other sibling SVG
        //   of these types. All other diagrams use unique IDs. So if the first of these gets hidden due to being in an
        //   inactive tab, or hidden under a closed details element the browser will not be able to find the ID as it
        //   only searches for the first. This will cause certain visual elements (arrow heads etc.) to disappear.

        if (!child.hasAttribute("height") && child.hasAttribute("width")) {
          child.setAttribute("height", temp.childNodes[0].getBoundingClientRect().height);
        } // Insert the render where we want it and remove the original text source.
        // Mermaid will clean up the temporary element.


        parentEl.parentNode.insertBefore(el, parentEl);
        parentEl.parentNode.removeChild(parentEl);
      }, temp);
    };

    for (var i = 0; i < blocks.length; i++) {
      _loop(i);
    }
  });

  (function () {
    var onReady = function onReady(fn) {
      if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", fn);
        document.addEventListener("DOMContentSwitch", fn);
      } else {
        document.attachEvent("onreadystatechange", function () {
          if (document.readyState === "interactive") {
            fn();
          }
        });
      }
    };

    onReady(function () {
      if (typeof mermaid !== "undefined") {
        uml("mermaid");
      }
    });
  })();

}());

It usually requires some cleanup, because comments don't always align right, and you get some oddities like the whole _loop function. I'm trying to steer people away from assuming I offer support for UML. It is possible for the user to support UML, which the example illustrates

@facelessuser
Copy link
Owner

We can add the full example, but when I do these things, I often forget to update them once I change them. I figured it was easier to link them.

@ofek
Copy link
Sponsor Contributor Author

ofek commented Apr 6, 2020

Ah, so it's ok to use the generated js file here? https://github.com/facelessuser/pymdown-extensions/tree/master/docs/theme/assets/pymdownx-extras

That makes it render!!! Though it doesn't look great:

Capture

@facelessuser
Copy link
Owner

Because I turn off themes: https://github.com/facelessuser/pymdown-extensions/blob/master/docs/src/markdown/_snippets/uml.txt#L5. Remove that line from your config and you will use the default theme. Remember, I disable theme embedding and then provide my own CSS.

@facelessuser
Copy link
Owner

@gir-bot remove S: triage
@gir-bot add T: support, C: docs

@gir-bot gir-bot added C: docs Related to documentation. T: support Support request. and removed S: triage Issue needs triage. labels Apr 6, 2020
@ofek
Copy link
Sponsor Contributor Author

ofek commented Apr 6, 2020

You're my hero, thank you! Please consider making an official Mermaid superfence/option 😄

@ofek ofek closed this as completed Apr 6, 2020
@facelessuser
Copy link
Owner

I mean it's just a custom fence option, the rest the user needs to provide (CSS, JS, etc.). It just doesn't make sense as I'd have to package all of that and inject it into the HTML output, which is really not the direction the extensions are meant to go in. That should be controlled by the user. I should just be exporting HTML and providing recommendations of what you could do with it, which is what I do 🙂 .

@ofek
Copy link
Sponsor Contributor Author

ofek commented Apr 7, 2020

Sorry to bother, but something isn't loading correctly

  1. Go to https://datadoghq.dev/integrations-core/base/about/
  2. Quickly press . (period) or n 4 times which should put you on https://datadoghq.dev/integrations-core/ddev/layers/
  3. Mermaid didn't load

https://github.com/DataDog/integrations-core/blob/hack-a-doc/mkdocs.yml

@facelessuser
Copy link
Owner

Instant mode is experimental, and while following recommendations, I was able to get it generally working, their are reasons I'm not personally running instant mode.

@ofek
Copy link
Sponsor Contributor Author

ofek commented Apr 7, 2020

Ah, I see. I'll disable that for now, again! Should I bring this up at mkdocs-material?

@facelessuser
Copy link
Owner

Sure. While I appreciate what instant mode is trying to do, I have no intentions of using it until I can reliably have content work in it. I haven't had time to invest in trying to figure out how best to tweak things.

@ofek
Copy link
Sponsor Contributor Author

ofek commented Jan 25, 2021

Has the instant mode improved enough yet?

@facelessuser
Copy link
Owner

@ofek, I don't use instant mode, so I'm not sure as I haven't spent any time to verify how easy/hard it is to get it working there.

With that said, I @squidfunk did integrate most of what I recommend in MkDocs Material as an experimental feature (not sure if it is in his insider's release or not), but wrapping content in shadow DOMs has not been implemented. What this means is your ID space will get polluted with Mermaid IDs and if you embed UML under details or tabs, when content is hidden you may see things like arrows disappear in visible diagrams. I won't go into details here, but suffice it to say, wrapping the content in shadow DOMs fixes the issue. @squidfunk has mentioned he plans to add support though, so once that is done, it is reasonable to assume that Mermaid will work great auto-magically with Material, even instant mode.

@squidfunk
Copy link
Sponsor Contributor

squidfunk commented Jan 25, 2021

The experimental Mermaid integration already works with instant loading and even dark mode:

Ohne.Titel.mp4

Try it here

I'll address the ShadowDOM issues when I managed to finish my current ongoing refactoring. It shouldn't impact you if you only have one graph of each type per page, IIRC. Nonetheless, it's still experimental.

@facelessuser
Copy link
Owner

Some issues may cross some diagrams, but basically, if things like arrowheads disappear, or something like that when one diagram is hidden, you are hitting the issue that can be fixed via the shadow DOM wrapper.

@beanaroo
Copy link

@ofek I'm currently using the fence_div_format approach and I was having trouble with diagrams not rendering when switching pages with Instant Loading enabled on the material theme.

I found I had to include a listener for 'DOMContentSwitch'. My extra_js has:

mermaid.mermaidAPI.initialize({
  startOnLoad: false,
  theme: 'dark',
  ...
});

function renderMermaid() {
  mermaid.init(undefined, document.querySelectorAll(".mermaid"));
}

document.addEventListener('DOMContentSwitch', renderMermaid)

renderMermaid()

I'm not using the custom loader with shadow DOM at the moment.

@facelessuser
Copy link
Owner

Yes, the custom loader I provide also adds DOMContentSwitch, as does Mkdocs Material, so in theory both solutions should work, I just don't use Instant right now so I cannot confirm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C: docs Related to documentation. T: support Support request.
Projects
None yet
Development

No branches or pull requests

5 participants