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

Table of contents #545

Closed
mazing opened this issue Feb 16, 2015 · 12 comments
Closed

Table of contents #545

mazing opened this issue Feb 16, 2015 · 12 comments

Comments

@mazing
Copy link

mazing commented Feb 16, 2015

Is it possible to get a list of all headers in order to create a table of contents?

@paxidently
Copy link

Just define custom renderer. Like this:

var renderer = new marked.Renderer();

var toc = [];

renderer.heading = function(text, level, raw) {
    var anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w]+/g, '-');
    toc.push({
        anchor: anchor,
        level: level,
        text: text
    });
    return '<h'
        + level
        + ' id="'
        + anchor
        + '">'
        + text
        + '</h'
        + level
        + '>\n';
};

marked.setOptions({
    renderer: renderer
});

@anaran
Copy link

anaran commented Mar 9, 2015

Worked great for me, thanks!

  // See
  // https://github.com/chjj/marked/issues/545#issuecomment-74505539
  var toc = [];
  var renderer = (function() {
    var renderer = new marked.Renderer();
    renderer.heading = function(text, level, raw) {
      var anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w]+/g, '-');
      toc.push({
        anchor: anchor,
        level: level,
        text: text
      });
      return '<h'
      + level
      + ' id="'
      + anchor
      + '">'
      + text
      + '</h'
      + level
      + '>\n'
      + '<a href="#table-of-contents">Table of Contents<a>\n';
    };
    return renderer;
  })();

  marked.setOptions({
    renderer: renderer,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
  });

          var html = marked(request.response);
          var tocHTML = '<h1 id="table-of-contents">Table of Contents</h1>\n<ul>';
          toc.forEach(function (entry) {
            tocHTML += '<li><a href="#'+entry.anchor+'">'+entry.text+'<a></li>\n';
          });
          tocHTML += '</ul>\n';
          document.querySelector('#render_markdown').innerHTML = tocHTML + html;

@joshbruce
Copy link
Member

Closing as out of scope based on current focus.

See #956

@yzfdjzwl
Copy link

yzfdjzwl commented Feb 8, 2018

Those answers above, only support English, do not support Chinese.So I add some rules based on the example above:

var anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w\\u4e00-\\u9fa5]]+/g, '-');

Hope it can help we Chinese.

@joshbruce
Copy link
Member

@UziTech and @Feder1co5oave: Check the pseudo PR from @yzfdjzwl. This intrigues me. I know I made the comment in #1043 about possibly deprecating the header id functionality, but this seems intriguing considering how many times I've seen this type of thing in some of the issues.

@joshbruce
Copy link
Member

#1048

@ibreathebsb
Copy link

#1489

@watercoldyi
Copy link

Those answers above,do not support multi-level tree TOC. So I add some code based on the example above:

 
var toc = [];
var renderer = (function () {
    var renderer = new marked.Renderer();
    renderer.heading = function (text, level, raw) {
        var anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w\\u4e00-\\u9fa5]]+/g, '-');
        toc.push({
            anchor: anchor,
            level: level,
            text: text
        });
        return '<h'
            + level
            + ' id="'
            + anchor
            + '">'
            + text
            + '</h'
            + level
            + '>\n'
            + '<a href="#table-of-contents">Table of Contents<a>\n';
    };
    return renderer;
})();

marked.setOptions({
    renderer: renderer,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
});
function build(coll, k, level, ctx) {
    if (k >= coll.length || coll[k].level <= level) { return k; }
    var node = coll[k];
    ctx.push("<li><a href='#" + node.anchor + "'>" + node.text + "</a>");
    k++;
    var childCtx = [];
    k = build(coll, k, node.level, childCtx);
    if (childCtx.length > 0) {
        ctx.push("<ul>");
        childCtx.forEach(function (idm) {
            ctx.push(idm);
        });
        ctx.push("</ul>");
    }
    ctx.push("</li>");
    k = build(coll, k, level, ctx);
    return k;
}
var html = marked(data);
var ctx = [];
ctx.push('<h1 id="table-of-contents">Table of Contents</h1>\n<ul>');
build(toc, 0, 0, ctx);
ctx.push("</ul>");
document.querySelector('#content').innerHTML = ctx.join("") + html;

@jenstornell
Copy link

I've made a TOC solution where the javascript is less complicated but still supports nesting, at least visually.

toc

Javascript

I did not need clickable headings, but if you need it, look at the first comment in this issue.

const renderer = new marked.Renderer();
const r = {
  heading: renderer.heading.bind(renderer),
};

let toc = [];

renderer.heading = (text, level, raw, slugger) => {
  toc.push({
    level: level,
    text: text
  });

  return r.heading(text, level, raw, slugger);
};

I use Vue so I output the data in a template like this.

<h3>Table of contents</h3>
<div class="list">
  <component class="h" v-for="h, index in toc" :is="'h' + h.level" :key="index" :title="'h' + h.level">
    {{ h.text }}
  </component>
</div>

SCSS

The nesting is made with pure CSS (SCSS). The different heading tags have different margins.

.list {
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    font-size: 13px;
    font-weight: normal;
    display: flex;

    &:before {
      width: 20px;
      min-width: 20px;
      display: block;
      opacity: .5;
    }
  }

  h1 {
    &:before {
      content: '1\00a0 ';
    }
  }

  h2 {
    &:before {
      content: '2\00a0 ';
      margin-right: 1rem;
    }
  }

  h3 {
    &:before {
      content: '3\00a0 ';
      margin-right: 2rem;
    }
  }

  h4 {
    &:before {
      content: '4\00a0 ';
      margin-right: 3rem;
    }
  }

  h5 {
    &:before {
      content: '5\00a0 ';
      margin-right: 4rem;
    }
  }

  h6 {
    &:before {
      content: '6\00a0 ';
      margin-right: 5rem;
    }
  }
}

@dinoox
Copy link

dinoox commented Jan 19, 2021

Worked great for me, thanks!

  // See
  // https://github.com/chjj/marked/issues/545#issuecomment-74505539
  var toc = [];
  var renderer = (function() {
    var renderer = new marked.Renderer();
    renderer.heading = function(text, level, raw) {
      var anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w]+/g, '-');
      toc.push({
        anchor: anchor,
        level: level,
        text: text
      });
      return '<h'
      + level
      + ' id="'
      + anchor
      + '">'
      + text
      + '</h'
      + level
      + '>\n'
      + '<a href="#table-of-contents">Table of Contents<a>\n';
    };
    return renderer;
  })();

  marked.setOptions({
    renderer: renderer,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
  });

          var html = marked(request.response);
          var tocHTML = '<h1 id="table-of-contents">Table of Contents</h1>\n<ul>';
          toc.forEach(function (entry) {
            tocHTML += '<li><a href="#'+entry.anchor+'">'+entry.text+'<a></li>\n';
          });
          tocHTML += '</ul>\n';
          document.querySelector('#render_markdown').innerHTML = tocHTML + html;

the last label a should be /a Otherwise, the following text parsing will be abnormal

@albfan
Copy link

albfan commented May 14, 2022

I usually don't need to change whole marked for table of contents (like sidebar with toc and all text rendered)

This is an example on how to apply this toc on fly. If you have a div content and a div table-content:

  var renderer = new marked.Renderer();

  var toc = [];
  var tableOfContentsId="table-of-contents";
  var tableOfContents="Table of contents";
  renderer.heading = function(text, level, raw) {
    var anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w]+/g, '-');
    toc.push({
      anchor: anchor,
      level: level,
      text: text
    });
    return '<h' + level + ' id="' + anchor + '">' + text + '</h' + level + '>\n'
      + '<a href="#'+tableOfContentsId+'">'+tableOfContents+'<a>\n';
  };

  var html = marked(contents, {
    renderer: renderer,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
  });
  var tocHTML = '<h1 id="'+tableOfContentsId+'">'+tableOfContents+'</h1>\n<ul>';
  toc.forEach(function (entry) {
    tocHTML += '<li><a href="#'+entry.anchor+'">'+entry.text+'<a></li>\n';
  });
  tocHTML += '</ul>\n';

  $('#table-content').html(tocHTML);
  $('#content').html(marked.parse(contents));

@CarterLi
Copy link

CarterLi commented Jul 5, 2022

Generate TOC with indentation

import { marked } from 'marked';

function createToc(mdText: string) {
  const stack = [document.createElement('ul')];
  for (const heading of marked.lexer(mdText).filter(x => x.type === 'heading') as marked.Tokens.Heading[]) {
    if (heading.depth < stack.length) {
      stack.length = heading.depth;
    } else {
      while (heading.depth > stack.length) {
        const ul = document.createElement('ul');
        stack.at(-1).append(ul);
        stack.push(ul);
      }
    }

    const prefix = marked.getDefaults().headerPrefix || '';
    const anchor = prefix + heading.text.replaceAll('.', '').replaceAll(' ', '-');
    stack.at(-1).insertAdjacentHTML('beforeend', `<li><a href="#${anchor}">${heading.text}</a></li>`);
  }
  return stack[0];
}

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

No branches or pull requests