Added Markdown TOC
This commit is contained in:
@ -3,6 +3,7 @@ import marked from 'marked';
|
|||||||
import sanitizedRenderer from 'marked-sanitized';
|
import sanitizedRenderer from 'marked-sanitized';
|
||||||
import highlight from 'highlight.js';
|
import highlight from 'highlight.js';
|
||||||
import emojify from './emojify';
|
import emojify from './emojify';
|
||||||
|
import toc from './toc';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
slug.defaults.mode = 'rfc3986';
|
slug.defaults.mode = 'rfc3986';
|
||||||
@ -24,8 +25,16 @@ renderer.heading = (text, level) => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: This is syncronous and can be costly
|
|
||||||
const convertToMarkdown = (text) => {
|
const convertToMarkdown = (text) => {
|
||||||
|
// Add TOC
|
||||||
|
text = toc.insert(text, {
|
||||||
|
slugify: (heading) => {
|
||||||
|
// FIXME: E.g. `&` gets messed up
|
||||||
|
const headingSlug = _.escape(slug(heading));
|
||||||
|
return headingSlug;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return marked.parse(emojify(text), {
|
return marked.parse(emojify(text), {
|
||||||
renderer,
|
renderer,
|
||||||
gfm: true,
|
gfm: true,
|
||||||
|
145
frontend/utils/toc/index.js
Normal file
145
frontend/utils/toc/index.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* marked-toc <https://github.com/jonschlinkert/marked-toc>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Jon Schlinkert, contributors.
|
||||||
|
* Licensed under the MIT license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var marked = require('marked');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var utils = require('./utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `toc`
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = toc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default template to use for generating
|
||||||
|
* a table of contents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var defaultTemplate = '<%= depth %><%= bullet %>[<%= heading %>](#<%= url %>)\n';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the table of contents object that
|
||||||
|
* will be used as context for the template.
|
||||||
|
*
|
||||||
|
* @param {String} `str`
|
||||||
|
* @param {Object} `options`
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function generate(str, options) {
|
||||||
|
var opts = _.extend({
|
||||||
|
firsth1: false,
|
||||||
|
blacklist: true,
|
||||||
|
omit: [],
|
||||||
|
maxDepth: 3,
|
||||||
|
slugify: function(text) {
|
||||||
|
return text; // Override this!
|
||||||
|
}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
var toc = '';
|
||||||
|
var tokens = marked.lexer(str);
|
||||||
|
var tocArray = [];
|
||||||
|
|
||||||
|
// Remove the very first h1, true by default
|
||||||
|
if(opts.firsth1 === false) {
|
||||||
|
tokens.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do any h1's still exist?
|
||||||
|
var h1 = _.some(tokens, {depth: 1});
|
||||||
|
|
||||||
|
tokens.filter(function (token) {
|
||||||
|
// Filter out everything but headings
|
||||||
|
if (token.type !== 'heading' || token.type === 'code') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we removed the first h1, we'll check to see if other h1's
|
||||||
|
// exist. If none exist, then we unindent the rest of the TOC
|
||||||
|
if(!h1) {
|
||||||
|
token.depth = token.depth - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store original text and create an id for linking
|
||||||
|
token.heading = opts.strip ? utils.strip(token.text, opts) : token.text;
|
||||||
|
|
||||||
|
// Create a "slugified" id for linking
|
||||||
|
token.id = opts.slugify(token.text);
|
||||||
|
|
||||||
|
// Omit headings with these strings
|
||||||
|
var omissions = ['Table of Contents', 'TOC', 'TABLE OF CONTENTS'];
|
||||||
|
var omit = _.union([], opts.omit, omissions);
|
||||||
|
|
||||||
|
if (utils.isMatch(omit, token.heading)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).forEach(function (h) {
|
||||||
|
|
||||||
|
if(h.depth > opts.maxDepth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bullet = Array.isArray(opts.bullet)
|
||||||
|
? opts.bullet[(h.depth - 1) % opts.bullet.length]
|
||||||
|
: opts.bullet;
|
||||||
|
|
||||||
|
var data = _.extend({}, opts.data, {
|
||||||
|
depth : new Array((h.depth - 1) * 2 + 1).join(' '),
|
||||||
|
bullet : bullet ? bullet : '* ',
|
||||||
|
heading: h.heading,
|
||||||
|
url : h.id
|
||||||
|
});
|
||||||
|
|
||||||
|
tocArray.push(data);
|
||||||
|
toc += _.template(opts.template || defaultTemplate)(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: tocArray,
|
||||||
|
toc: opts.strip
|
||||||
|
? utils.strip(toc, opts)
|
||||||
|
: toc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toc
|
||||||
|
*/
|
||||||
|
|
||||||
|
function toc(str, options) {
|
||||||
|
return generate(str, options).toc;
|
||||||
|
}
|
||||||
|
|
||||||
|
toc.raw = function(str, options) {
|
||||||
|
return generate(str, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
toc.insert = function(content, options) {
|
||||||
|
var start = '<!-- toc -->';
|
||||||
|
var stop = '<!-- tocstop -->';
|
||||||
|
var re = /<!-- toc -->([\s\S]+?)<!-- tocstop -->/;
|
||||||
|
|
||||||
|
// remove the existing TOC
|
||||||
|
content = content.replace(re, start);
|
||||||
|
|
||||||
|
// generate new TOC
|
||||||
|
var newtoc = '\n\n'
|
||||||
|
+ start + '\n\n'
|
||||||
|
+ toc(content, options) + '\n'
|
||||||
|
+ stop + '\n';
|
||||||
|
|
||||||
|
// If front-matter existed, put it back
|
||||||
|
return content.replace(start, newtoc);
|
||||||
|
};
|
75
frontend/utils/toc/utils.js
Normal file
75
frontend/utils/toc/utils.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* marked-toc <https://github.com/jonschlinkert/marked-toc>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Jon Schlinkert, contributors.
|
||||||
|
* Licensed under the MIT license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
|
var utils = module.exports = {};
|
||||||
|
|
||||||
|
|
||||||
|
utils.arrayify = function(arr) {
|
||||||
|
return !Array.isArray(arr) ? [arr] : arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
utils.escapeRegex = function(re) {
|
||||||
|
return re.replace(/(\[|\]|\(|\)|\/|\.|\^|\$|\*|\+|\?)/g, '\\$1');
|
||||||
|
};
|
||||||
|
|
||||||
|
utils.isDest = function(dest) {
|
||||||
|
return !dest || dest === 'undefined' || typeof dest === 'object';
|
||||||
|
};
|
||||||
|
|
||||||
|
utils.isMatch = function (keys, str) {
|
||||||
|
keys = utils.arrayify(keys);
|
||||||
|
keys = (keys.length > 0) ? keys.join('|') : '.*';
|
||||||
|
|
||||||
|
// Escape certain characters, like '[', '('
|
||||||
|
var k = utils.escapeRegex(String(keys));
|
||||||
|
|
||||||
|
// Build up the regex to use for replacement patterns
|
||||||
|
var re = new RegExp('(?:' + k + ')', 'g');
|
||||||
|
if (String(str).match(re)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
utils.sanitize = function(src) {
|
||||||
|
src = src.replace(/(\s*\[!|(?:\[.+ →\]\()).+/g, '');
|
||||||
|
src = src.replace(/\s*\*\s*\[\].+/g, '');
|
||||||
|
return src;
|
||||||
|
};
|
||||||
|
|
||||||
|
utils.slugify = function(str) {
|
||||||
|
str = str.replace(/\/\//g, '-');
|
||||||
|
str = str.replace(/\//g, '-');
|
||||||
|
str = str.replace(/\./g, '-');
|
||||||
|
str = _.str.slugify(str);
|
||||||
|
str = str.replace(/^-/, '');
|
||||||
|
str = str.replace(/-$/, '');
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip certain words from headings. These can be
|
||||||
|
* overridden. Might seem strange but it makes
|
||||||
|
* sense in context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var omit = ['grunt', 'helper', 'handlebars-helper', 'mixin', 'filter', 'assemble-contrib', 'assemble'];
|
||||||
|
|
||||||
|
utils.strip = function (name, options) {
|
||||||
|
var opts = _.extend({}, options);
|
||||||
|
if(opts.omit === false) {omit = [];}
|
||||||
|
var exclusions = _.union(omit, utils.arrayify(opts.strip || []));
|
||||||
|
var re = new RegExp('^(?:' + exclusions.join('|') + ')[-_]?', 'g');
|
||||||
|
return name.replace(re, '');
|
||||||
|
};
|
Reference in New Issue
Block a user