mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
5.4 KiB
222 lines
5.4 KiB
9 years ago
|
"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'),
|
||
9 years ago
|
mdAnchor = require('markdown-it-anchor'),
|
||
9 years ago
|
mdFootnote = require('markdown-it-footnote'),
|
||
|
mdExternalLinks = require('markdown-it-external-links'),
|
||
|
mdExpandTabs = require('markdown-it-expand-tabs'),
|
||
9 years ago
|
mdAttrs = require('markdown-it-attrs'),
|
||
|
hljs = require('highlight.js'),
|
||
|
cheerio = require('cheerio'),
|
||
9 years ago
|
_ = require('lodash'),
|
||
|
mdRemove = require('remove-markdown');
|
||
9 years ago
|
|
||
|
// Load plugins
|
||
|
|
||
|
var mkdown = md({
|
||
|
html: true,
|
||
|
linkify: true,
|
||
9 years ago
|
typography: true,
|
||
9 years ago
|
highlight(str, lang) {
|
||
9 years ago
|
if (lang && hljs.getLanguage(lang)) {
|
||
|
try {
|
||
|
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>';
|
||
9 years ago
|
} catch (err) {
|
||
|
return '<pre><code>' + str + '</code></pre>';
|
||
|
}
|
||
9 years ago
|
}
|
||
9 years ago
|
return '<pre><code>' + str + '</code></pre>';
|
||
9 years ago
|
}
|
||
9 years ago
|
})
|
||
|
.use(mdEmoji)
|
||
|
.use(mdTaskLists)
|
||
|
.use(mdAbbr)
|
||
|
.use(mdAnchor, {
|
||
9 years ago
|
slugify: _.kebabCase,
|
||
|
permalink: true,
|
||
|
permalinkClass: 'toc-anchor',
|
||
|
permalinkSymbol: '#',
|
||
|
permalinkBefore: true
|
||
9 years ago
|
})
|
||
|
.use(mdFootnote)
|
||
|
.use(mdExternalLinks, {
|
||
|
externalClassName: 'external-link',
|
||
|
internalClassName: 'internal-link'
|
||
|
})
|
||
|
.use(mdExpandTabs, {
|
||
|
tabWidth: 4
|
||
9 years ago
|
})
|
||
|
.use(mdAttrs);
|
||
9 years ago
|
|
||
|
// Rendering rules
|
||
|
|
||
|
mkdown.renderer.rules.emoji = function(token, idx) {
|
||
9 years ago
|
return '<i class="twa twa-' + token[idx].markup + '"></i>';
|
||
9 years ago
|
};
|
||
|
|
||
9 years ago
|
/**
|
||
|
* Parse markdown content and build TOC tree
|
||
|
*
|
||
|
* @param {(Function|string)} content Markdown content
|
||
|
* @return {Array} TOC tree
|
||
|
*/
|
||
9 years ago
|
const parseTree = (content) => {
|
||
|
|
||
|
let tokens = md().parse(content, {});
|
||
|
let tocArray = [];
|
||
|
|
||
9 years ago
|
//-> Extract headings and their respective levels
|
||
|
|
||
9 years ago
|
for (let i = 0; i < tokens.length; i++) {
|
||
|
if (tokens[i].type !== "heading_close") {
|
||
9 years ago
|
continue;
|
||
9 years ago
|
}
|
||
|
|
||
9 years ago
|
const heading = tokens[i - 1];
|
||
|
const heading_close = tokens[i];
|
||
9 years ago
|
|
||
|
if (heading.type === "inline") {
|
||
9 years ago
|
let content = "";
|
||
|
let anchor = "";
|
||
|
if (heading.children && heading.children[0].type === "link_open") {
|
||
9 years ago
|
content = heading.children[1].content;
|
||
9 years ago
|
anchor = _.kebabCase(content);
|
||
9 years ago
|
} else {
|
||
9 years ago
|
content = heading.content
|
||
9 years ago
|
anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ""));
|
||
9 years ago
|
}
|
||
9 years ago
|
|
||
9 years ago
|
tocArray.push({
|
||
9 years ago
|
content,
|
||
|
anchor,
|
||
|
level: +heading_close.tag.substr(1, 1)
|
||
9 years ago
|
});
|
||
9 years ago
|
}
|
||
|
}
|
||
|
|
||
9 years ago
|
//-> Exclude levels deeper than 2
|
||
|
|
||
|
_.remove(tocArray, (n) => { return n.level > 2; });
|
||
|
|
||
|
//-> Build tree from flat array
|
||
|
|
||
9 years ago
|
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);
|
||
9 years ago
|
if(lastNode) {
|
||
|
lastNode.push({
|
||
|
content: v.content,
|
||
|
anchor: v.anchor,
|
||
|
nodes: []
|
||
|
});
|
||
|
_.set(tree[treeLength], lastNodePath, lastNode);
|
||
|
}
|
||
9 years ago
|
}
|
||
|
return tree;
|
||
|
}, []);
|
||
|
|
||
|
};
|
||
|
|
||
9 years ago
|
/**
|
||
|
* 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;
|
||
|
|
||
|
};
|
||
|
|
||
9 years ago
|
/**
|
||
|
* Parse meta-data tags from content
|
||
|
*
|
||
|
* @param {String} content Markdown content
|
||
|
* @return {Object} Properties found in the content and their values
|
||
|
*/
|
||
9 years ago
|
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;
|
||
|
|
||
|
};
|
||
|
|
||
9 years ago
|
module.exports = {
|
||
|
|
||
9 years ago
|
/**
|
||
|
* Parse content and return all data
|
||
|
*
|
||
|
* @param {String} content Markdown-formatted content
|
||
|
* @return {Object} Object containing meta, html and tree data
|
||
|
*/
|
||
9 years ago
|
parse(content) {
|
||
|
return {
|
||
9 years ago
|
meta: parseMeta(content),
|
||
9 years ago
|
html: parseContent(content),
|
||
9 years ago
|
tree: parseTree(content)
|
||
|
};
|
||
9 years ago
|
},
|
||
|
|
||
|
parseContent,
|
||
|
parseMeta,
|
||
9 years ago
|
parseTree,
|
||
|
|
||
|
/**
|
||
|
* Strips non-text elements from Markdown content
|
||
|
*
|
||
|
* @param {String} content Markdown-formatted content
|
||
|
* @return {String} Text-only version
|
||
|
*/
|
||
|
removeMarkdown(content) {
|
||
|
return mdRemove(_.chain(content)
|
||
|
.replace(/<!-- ?([a-zA-Z]+):(.*)-->/g, '')
|
||
|
.replace(/```[^`]+```/g, '')
|
||
|
.replace(/`[^`]+`/g, '')
|
||
|
.replace(new RegExp('(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?', 'g'), '')
|
||
|
.replace(/\r?\n|\r/g, ' ')
|
||
|
.deburr()
|
||
|
.toLower()
|
||
|
.replace(/(\b([^a-z]+)\b)/g, ' ')
|
||
|
.replace(/[^a-z]+/g, ' ')
|
||
|
.replace(/(\b(\w{1,2})\b(\W|$))/g, '')
|
||
|
.replace(/\s\s+/g, ' ')
|
||
|
.value()
|
||
|
);
|
||
|
}
|
||
9 years ago
|
|
||
|
};
|