UNPKG

verb-toc

Version:

Verb plugin that adds middleware for creating and injecting a table of contents into a markdown document.

136 lines (112 loc) 3.38 kB
/*! * verb-toc <https://github.com/jonschlinkert/verb-toc> * * Copyright (c) 2016, Jon Schlinkert. * Licensed under the MIT License. */ 'use strict'; var isObject = require('isobject'); var extend = require('extend-shallow'); var toc = require('markdown-toc'); module.exports = function(app) { app.postLayout(/\.md/, createToc('postLayout', app)); app.postRender(/\.md/, createToc('postRender', app)); app.postRender(/\.md/, createToc('preRender', app)); app.preWrite(/\.md/, injectToc(app)); }; function createToc(method, app) { return function(file, next) { var opts = createOpts(app, file); if (method !== opts.toc.method) { next(null, file); return; } if (opts.toc.render !== true) { next(null, file); return; } opts.linkify = opts.linkify || function(tok, name, slug, tocOpts) { if (/^[{<]%?/.test(tok.content)) { var view = app.view('temp' + file.extname, {content: tok.content}); app.compile(view, opts); name = tok.content = view.fn(app.cache.data); } toc.linkify(tok, name, slug, tocOpts); return tok; }; file.toc = toc(file.content, opts); if (app.hasListeners('toc')) { app.emit('toc', file, next); return; } next(null, file); }; }; function injectToc(app) { return function(file, next) { var opts = createOpts(app, file); var str = file.contents.toString(); if (opts.toc.method === 'preWrite') { file.toc = toc(file.content, opts); } var tocString = (file.toc && file.toc.content) ? file.toc.content : ''; var min = opts.toc.minLevels; // does the TOC have the minimum expected levels to render? if (typeof min === 'number') { opts.toc.render = hasMinimumLevels(tocString, min); } if (tocString === '' || opts.toc.render !== true) { str = str.replace(/^#+ (TOC|table of contents)/gmi, ''); tocString = ''; } else { tocString += opts.toc.footer || ''; } // don't render toc comments in backticks str = str.replace(/(?!`)<!-- toc -->(?!`)/g, tocString); // fix escaped code comments (used as macros) str = str.split('<!!--').join('<!--'); str = str.replace(/\n{2,}/g, '\n\n'); file.contents = new Buffer(str); next(null, file); }; } function createOpts(app, file) { var opts = extend({toc: {}}, app.options, file.options, file.data); if (typeof opts.toc === 'string') { if (opts.toc === 'collapsible') { opts.toc = { method: 'postLayout', collapsible: true }; } else { opts.toc = { method: opts.toc }; } } if (typeof opts.toc === 'boolean') { opts.toc = { render: opts.toc }; } if (!isObject(opts.toc)) { opts.toc = {}; } if (typeof opts.toc.method === 'string' || opts.toc.match || typeof opts.toc.minLevels === 'number') { opts.toc.render = true; } else { opts.toc.method = 'postLayout'; } opts = extend({}, opts, opts.toc); return opts; } function hasMinimumLevels(str, min) { var lines = str.split('\n'); var len = lines.length; var max = 0; while (len--) { var line = lines[len]; var ws = /^\s+/.exec(line); if (!ws) continue; var wlen = ws[0].length; if (wlen > max) { max = wlen; } } return max >= min; } module.exports.createToc = createToc; module.exports.injectToc = injectToc;