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

Using SuperFences custom fences #618

Closed
wburningham opened this issue Nov 30, 2017 · 14 comments
Closed

Using SuperFences custom fences #618

wburningham opened this issue Nov 30, 2017 · 14 comments

Comments

@wburningham
Copy link

Description

I know I can enable SuperFences for enabled nested blocks. Is it possible to enable custom fences (like flow and sequence) using the mkdocs-material theme?

Expected behavior

Ideally a mkdoc.yml file with the following would enable a custom flow fence

markdown_extensions:
  - pymdownx.superfences:
    custom_fences:
      - name: flow
        class: uml-flowchart
        format: superfences.fence_code_format

This could either automatically include raphael.js and flowchart.js (required as documented in the SuperFences docs) or I could add them to custom_javascript.

Actual behavior

ERROR - Config value: 'markdown_extensions'. Error: Invalid Markdown Extensions configuration

Steps to reproduce the bug

  1. Use the config I have below
  2. run mkdocs build or mkdocs serve

Package versions

  • Python: Python 2.7.10
  • MkDocs: mkdocs, version 0.16.3
  • Material: Version: 1.12.2

Project configuration

site_name: Demo
theme: material

markdown_extensions:
  - pymdownx.superfences:
    custom_fences:
      - name: flow
        class: uml-flowchart
        format: superfences.fence_code_format

System information

  • OS: OSX 10.12.6
  • Browser: Chrome
@facelessuser
Copy link
Contributor

  1. By default, superfences is configured to use UML if desired already. If you are looking to trigger Material, it would make more sense to use a theme feature switch instead of the extension config, but I wonder if Material should even do this as that is exactly what extra_css is for etc.

  2. Your example config will not work the way you've specified format. It should be !!python/name:pymdownx.superfences.fence_code_format.

  3. Ideally, since UML requires a number of libraries, I personally think it is best to load it only on pages that you intend to have UML on. So I use Snippets (but there are other extensions that can do the same), as shown here, to include the libraries at the bottom of the page when I need them (see bottom of source file).

@wburningham
Copy link
Author

#1 ok
#2 I'm not following your syntax. Could you paste what the entire mkdoc.yml would look like?
#3 Did not know about snippets. I'll for sure use that to load the libraries.

@facelessuser
Copy link
Contributor

I'm not following your syntax. Could you paste what the entire mkdoc.yml would look like?

There is no reason to setup flow as it is already set up for you, but if you did need to setup your own, you have to remember that SuperFences takes a function references, not a string with the function name. And you have to reference it with the full import path.

So the function you specified up above superfences.fence_code_format is not the full path, but pymdownx.superfences.fence_code_format is. And the way you've specified in your yaml config is as a string, and as stated earlier, we need a function reference. PyYaml, (the yaml parser that MkDocs uses), allows you specify a function reference via !!python/name:. This tells PyYaml that the following is a Python object's name.

markdown_extensions:
  - pymdownx.superfences:
    custom_fences:
      - name: flow
        class: uml-flowchart
        format: !!python/name:pymdownx.superfences.fence_code_format

You can see examples in pymdownx-extensions document config file of me referencing functions for other extensions.

@wburningham
Copy link
Author

Perfect! Thanks for your help.

@facelessuser
Copy link
Contributor

Perfect! Thanks for your help.

No problem. Glad you got it working.

@harveyslash
Copy link

This doesnt seem to work for me.

I have tried the code in your demo:

st=>start: Start:>http://www.google.com[blank]
e=>end:>http://www.google.com
op1=>operation: My Operation
sub1=>subroutine: My Subroutine
cond=>condition: Yes
or No?:>http://www.google.com
io=>inputoutput: catch something...

st->op1->cond
cond(yes)->io->e
cond(no)->sub1(right)->op1

and this is what my yml looks like

- pymdownx.superfences:
      custom_fences:
          - name: flow
          - class: uml-flowchart
          - format: !!python/name:pymdownx.superfences.fence_code_format
extra_javascript:
  - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML'
  - 'https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js'
  - 'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js'
  - 'https://cdnjs.cloudflare.com/ajax/libs/js-sequence-diagrams/1.0.6/sequence-diagram-min.js'
  - 'https://cdnjs.cloudflare.com/ajax/libs/flowchart/1.6.5/flowchart.min.js'

@facelessuser
Copy link
Contributor

@harveyslash, you are going to have to read the documentation as I feel like it is explained in pretty good depth there. One thing it mentions is that even after adding the required libraries, you need to have a loader script, and then I give and example loader script: https://facelessuser.github.io/pymdown-extensions/extensions/superfences/#uml-diagram-example.

Also, if you read through this post, I mention that by default, flow is already setup up along with sequence, so you don't need to manually set it up yourself. What you do need to do is provide the required libraries and a script that targets the flow and sequence blocks and passes the the content through the libraries.

Please read through this post and pymdown-extensions' documentation as most of your question should be answered. If you have a specific question about the information covered in the documentation, or feel there is wrong or incomplete information, please open an issue over at pymdown-extensions, and it can be discussed over there.

@harveyslash
Copy link

I am not sure about what you mean by already set up.
I did try to follow the set up (sorry, I am not familiar with this).

I created a js file like this:

(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 article to whatever element your main Markdown content lives.
  var article = document.querySelectorAll("article");
  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
    // typset 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 `poistion: 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 visilbe again.
    // Lastly, clean up the old node.
    );
    article[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" });
    }
  });
})();

}());

Then I reference it in my yml like this:

extra_javascript:
  - 'js/myjs.js'
  - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML'
  - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML'
  - 'https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js'
  - 'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js'
  - 'https://cdnjs.cloudflare.com/ajax/libs/js-sequence-diagrams/1.0.6/sequence-diagram-min.js'
  - 'https://cdnjs.cloudflare.com/ajax/libs/flowchart/1.6.5/flowchart.min.js'

My config looks like :

- pymdownx.superfences:
      custom_fences:
        - name: flow
        - class: uml-flowchart
        - format: !!python/name:pymdownx.superfences.fence_code_format

It still does not show up.
Sorry if it sounds stupid to you

@harveyslash
Copy link

It turns out that there is a syntax error in the example.

This made it work.

(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 article to whatever element your main Markdown content lives.
  var article = document.querySelectorAll("article");
  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
    // typset 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 `poistion: 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 visilbe again.
    // Lastly, clean up the old node.
    ;
    article[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" });
    }
  });
})();

}());

@facelessuser
Copy link
Contributor

Okay, so it looks like I had a stray ) in the example. I'll fix that.

What I'm actually using is this: https://github.com/facelessuser/pymdown-extensions/blob/master/docs/src/js/uml.js, which gets converted to normal JS. Most likely I ran it through a converter, cleaned it up, and when I made changes to the original source, I quickly patched the example and made the mistake.

Anyways, thanks for catching that, I'll fix it in the docs.

@harveyslash
Copy link

I also noticed that in case of wide sequence diagrams, the diagram just overflows , like so:

screen shot 2017-12-24 at 11 20 33 am

Should I open an issue for this in the repo?

@facelessuser
Copy link
Contributor

This has nothing to do with SuperFences. The 3rd party libraries render the diagrams, Material handles the styling of them. If the diagram is large, yes it will overflow. At the very least, it should be scrollable. If it isn't, then maybe Material needs to update the styling. I do know that I am personally adding these additional styles to UML: https://github.com/facelessuser/pymdown-extensions/blob/master/docs/src/scss/_uml.scss. Maybe that will help. I added them at one point to fix an issue I had, but I am not sure if they are still relevant.

If you are concerned that the loader script is doing something wrong, feel free to write a better one. My intention is to provide an example that works, which it does. I am not specifically maintaining a JavaScript loader library, but a pull request to provide a better one is fine if someone wants to do the work. With that said though, I am using the example albeit in a slightly different form, but functional it is the same, and I haven't noticed any issues with it.

I would play with your UML styling to get what you want. If you find there is some additional styling that Material needs to style them better, then I'd create a styling issue here or a pull request. Maybe the additional styling I use will help. I've been meaning to look into that area more on the Material side, but I support a bunch of repos, and priority pulls me away from things like this at times.

@facelessuser
Copy link
Contributor

Updated doc example to fix typo and made the example a little more generic.

@usstq
Copy link

usstq commented Sep 1, 2021

To make the config work on my side, I have to remove the '-' prepending 'class' & 'format'

- pymdownx.superfences:
      custom_fences:
        - name: flow
          class: uml-flowchart
          format: !!python/name:pymdownx.superfences.fence_code_format

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants