Add TOC(Table of content) to marked

Markdown has always been parsed using the marked library, and now I want to add TOC functionality to it.
Using marked to parse markdown text is very simple, such as: marked (markdown Text) can get the parsed html content.

Initialize marked

import library

var marked = require('marked');

Rewrite renderer.heading

tocObj is introduced later, and the returned header contains anchors for positioning. Of course, you can also write the anchor directly in the h tag. I use a single a tag here to solve the problem that when the site has a fixed navigation head, the title is covered when locating the anchor, such as: How to make the page position move down a short distance after clicking on the website with anchor

var renderer = new marked.Renderer();
renderer.heading = function(text, level, raw) {
  var anchor = tocObj.add(text, level);
  return `<a id=${anchor} class="anchor-fix"></a><h${level}>${text}</h${level}>\n`;
};

Setting parameters

highlight is what I use to handle code highlighting, which is not considered here.

marked.setOptions({
    renderer: renderer,
    highlight: function(code) {
      return require("highlight.js").highlightAuto(code).value;
    },
});

Save marked parsed header information

In renderer.heading, tocObj is used to save the text and level of the H tag and return a production anchor, text is the content of the tag, level is several levels of title, such as: h1,h2,h3, etc.

Generating html code for toc

With header information, toc can be generated. toc is generated according to the hierarchy of h tag. It is the same as the directory tree, not a simple list.

The data we're currently getting is this: the number is level, the title is text, and they're stored in an array in order.

Title 1
 Title 1
  h2 secondary title
  h2 secondary title
    Title 3 of h3
  h2 secondary title
 Title 1

Now you need to convert this data into html tags

In the case of UL and li tags, there exists the nesting of UL in ul, as follows:

<ul>
  <li>First level title</li>
  <li>First level title</li>
  <ul>
    <li>Two level title</li>
    <li>Two level title</li>
    <ul>
      <li>Three level title</li>
    </ul>
    <li>Two level title</li>
  </ul>
  <li>First level title</li>
</ul>

Conversion code

const tocObj = { 
  add: function(text, level) {
    var anchor = `#toc${level}${++this.index}`;
    this.toc.push({ anchor: anchor, level: level, text: text });
    return anchor;
  },
  // The nested ul,li, level is the nested level of ul. 1 is the outermost layer.
  // <ul>
  //   <li></li>
  //   <ul>
  //     <li></li>
  //   </ul>
  //   <li></li>
  // </ul>
  toHTML: function() {
    let levelStack = [];
    let result = '';
    const addStartUL = () => { result += '<ul>'; };
    const addEndUL = () => { result += '</ul>\n'; };
    const addLI = (anchor, text) => { result += '<li><a href="#'+anchor+'">'+text+'<a></li>\n'; };

    this.toc.forEach(function (item) {
      let levelIndex = levelStack.indexOf(item.level);
      // If the ul tag for the level is not found, put li in the new ul
      if (levelIndex === -1) {
        levelStack.unshift(item.level);
        addStartUL();
        addLI(item.anchor, item.text);
      } // The ul tag of the corresponding level is found, and the li is placed directly under the ul at the top of the stack.
      else if (levelIndex === 0) {
        addLI(item.anchor, item.text);
      } // The ul tag of the corresponding level is found, but not at the top of the stack. All previous levels need to be out of the stack and labeled closed. Finally, a new li is added.
      else {
        while (levelIndex--) {
          levelStack.shift();
          addEndUL();
        }
        addLI(item.anchor, item.text);
      }
    });
    // If there is level in the stack, all the stacks are labeled closed.
    while(levelStack.length) {
      levelStack.shift();
      addEndUL();
    }
    // Clean up previous data for next use
    this.toc = [];
    this.index = 0;
    return result;
  },
  toc: [], 
  index: 0
};

The final code implementation

post represents the article object. Content is markdown content. When marked is invoked, the title information of the article is written into tocObj in renderer.heading. Finally, the html code of toc can be generated by calling toHTML.

post2html: function(post) {
  if (post) {
    post.content = marked(post.content);
    post.toc = tocObj.toHTML();
  }
}

Anchor location offset css

20px is the height covered by navigation bar

.anchor-fix {
    display: block;
    height: 20px; /*same height as header*/
    margin-top: -20px; /*same height as header*/
    visibility: hidden;
}

Tags: Attribute xml

Posted on Wed, 19 Dec 2018 01:42:04 -0800 by dv_evan