123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- "use strict";
- var Promise = require('bluebird'),
- md = require('markdown-it'),
- mdEmoji = require('markdown-it-emoji'),
- mdTaskLists = require('markdown-it-task-lists'),
- mdAbbr = require('markdown-it-abbr'),
- mdAnchor = require('markdown-it-toc-and-anchor').default,
- mdFootnote = require('markdown-it-footnote'),
- mdExternalLinks = require('markdown-it-external-links'),
- mdExpandTabs = require('markdown-it-expand-tabs'),
- mdAttrs = require('markdown-it-attrs'),
- hljs = require('highlight.js'),
- slug = require('slug'),
- cheerio = require('cheerio'),
- _ = require('lodash');
- // Load plugins
- var mkdown = md({
- html: true,
- linkify: true,
- typography: true,
- highlight(str, lang) {
- if (lang && hljs.getLanguage(lang)) {
- try {
- return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>';
- } catch (err) {
- return '<pre><code>' + str + '</code></pre>';
- }
- }
- return '<pre><code>' + str + '</code></pre>';
- }
- })
- .use(mdEmoji)
- .use(mdTaskLists)
- .use(mdAbbr)
- .use(mdAnchor, {
- tocClassName: 'toc',
- anchorClassName: 'toc-anchor'
- })
- .use(mdFootnote)
- .use(mdExternalLinks, {
- externalClassName: 'external-link',
- internalClassName: 'internal-link'
- })
- .use(mdExpandTabs, {
- tabWidth: 4
- })
- .use(mdAttrs);
- // Rendering rules
- mkdown.renderer.rules.emoji = function(token, idx) {
- return '<i class="twa twa-' + token[idx].markup + '"></i>';
- };
- /**
- * Parse markdown content and build TOC tree
- *
- * @param {(Function|string)} content Markdown content
- * @return {Array} TOC tree
- */
- const parseTree = (content) => {
- let tokens = md().parse(content, {});
- let tocArray = [];
- //-> Extract headings and their respective levels
- for (let i = 0; i < tokens.length; i++) {
- if (tokens[i].type !== "heading_close") {
- continue;
- }
- const heading = tokens[i - 1];
- const heading_close = tokens[i];
- if (heading.type === "inline") {
- let content = "";
- let anchor = "";
- if (heading.children && heading.children[0].type === "link_open") {
- content = heading.children[1].content;
- anchor = slug(content, {lower: true});
- } else {
- content = heading.content
- anchor = slug(heading.children.reduce((acc, t) => acc + t.content, ""), {lower: true});
- }
- tocArray.push({
- content,
- anchor,
- level: +heading_close.tag.substr(1, 1)
- });
- }
- }
- //-> Exclude levels deeper than 2
- _.remove(tocArray, (n) => { return n.level > 2; });
- //-> Build tree from flat array
- return _.reduce(tocArray, (tree, v) => {
- let treeLength = tree.length - 1;
- if(v.level < 2) {
- tree.push({
- content: v.content,
- anchor: v.anchor,
- nodes: []
- });
- } else {
- let lastNodeLevel = 1;
- let GetNodePath = (startPos) => {
- lastNodeLevel++;
- if(_.isEmpty(startPos)) {
- startPos = 'nodes';
- }
- if(lastNodeLevel === v.level) {
- return startPos;
- } else {
- return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes');
- }
- };
- let lastNodePath = GetNodePath();
- let lastNode = _.get(tree[treeLength], lastNodePath);
- if(lastNode) {
- lastNode.push({
- content: v.content,
- anchor: v.anchor,
- nodes: []
- });
- _.set(tree[treeLength], lastNodePath, lastNode);
- }
- }
- return tree;
- }, []);
- };
- /**
- * Parse markdown content to HTML
- *
- * @param {String} content Markdown content
- * @return {String} HTML formatted content
- */
- const parseContent = (content) => {
- let output = mkdown.render(content);
- let cr = cheerio.load(output);
- cr('table').addClass('table is-bordered is-striped is-narrow');
- output = cr.html();
- return output;
- };
- const parseMeta = (content) => {
- let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->','g');
- let results = {}, match;
- while(match = commentMeta.exec(content)) {
- results[_.toLower(match[1])] = _.trim(match[2]);
- }
- return results;
- };
- module.exports = {
- parse(content) {
- return {
- meta: parseMeta(content),
- html: parseContent(content),
- tree: parseTree(content)
- };
- },
- parseContent,
- parseMeta,
- parseTree
- };
|