syntax highlight + TOC scroll + other content parsing improvements

This commit is contained in:
NGPixel 2016-08-20 23:28:53 -04:00
parent 1ad03a3d1f
commit e94abf9466
13 changed files with 114 additions and 25 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
"use strict";jQuery(document).ready(function(e){e("a").smoothScroll({speed:"auto"})}); "use strict";jQuery(document).ready(function(e){e("a").smoothScroll({speed:400,offset:-20});new Sticky(".stickyscroll")});

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,10 @@
jQuery( document ).ready(function( $ ) { jQuery( document ).ready(function( $ ) {
$('a').smoothScroll({ $('a').smoothScroll({
speed: 'auto' speed: 400,
offset: -20
}); });
var sticky = new Sticky('.stickyscroll');
}); });

View File

@ -8,4 +8,4 @@ $warning: #f68b39;
@import './layout/_header'; @import './layout/_header';
@import './layout/_footer'; @import './layout/_footer';
@import './layout/_content';

View File

@ -0,0 +1,32 @@
.mkcontent {
h1 {
border-bottom: 1px dotted $grey-light;
padding-bottom: 4px;
font-weight: 400;
color: $grey-dark;
}
a.toc-anchor {
font-size: 80%;
color: $purple;
border-bottom: none;
}
.hljs {
padding: 0;
border-bottom: 1px solid $grey-light;
border-right: 1px solid $grey-light;
border-radius: 3px;
}
pre + p {
padding-top: 1em;
}
img.right {
float:right;
}
}

View File

@ -11,7 +11,7 @@ router.get('/', (req, res) => {
var Promise = require('bluebird'); var Promise = require('bluebird');
var fs = Promise.promisifyAll(require("fs")); var fs = Promise.promisifyAll(require("fs"));
fs.readFileAsync("repo/Gollum.md", "utf8").then(function(contents) { fs.readFileAsync("repo/Home.md", "utf8").then(function(contents) {
let pageData = mark.parse(contents); let pageData = mark.parse(contents);
if(!pageData.title) { if(!pageData.title) {
pageData.title = 'Gollum'; pageData.title = 'Gollum';

View File

@ -22,7 +22,8 @@ var paths = {
'./node_modules/lodash/lodash.min.js', './node_modules/lodash/lodash.min.js',
'./node_modules/jquery/dist/jquery.min.js', './node_modules/jquery/dist/jquery.min.js',
'./node_modules/vue/dist/vue.min.js', './node_modules/vue/dist/vue.min.js',
'./node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js' './node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
'./node_modules/sticky-js/dist/sticky.min.js'
], ],
scriptapps: [ scriptapps: [
'./client/js/components/*.js', './client/js/components/*.js',
@ -32,7 +33,8 @@ var paths = {
'./client/js/**/*.js' './client/js/**/*.js'
], ],
csslibs: [ csslibs: [
'./node_modules/font-awesome/css/font-awesome.min.css' './node_modules/font-awesome/css/font-awesome.min.css',
'./node_modules/highlight.js/styles/default.css'
], ],
cssapps: [ cssapps: [
'./client/scss/app.scss' './client/scss/app.scss'

View File

@ -9,7 +9,10 @@ var Promise = require('bluebird'),
mdFootnote = require('markdown-it-footnote'), mdFootnote = require('markdown-it-footnote'),
mdExternalLinks = require('markdown-it-external-links'), mdExternalLinks = require('markdown-it-external-links'),
mdExpandTabs = require('markdown-it-expand-tabs'), mdExpandTabs = require('markdown-it-expand-tabs'),
mdAttrs = require('markdown-it-attrs'),
hljs = require('highlight.js'),
slug = require('slug'), slug = require('slug'),
cheerio = require('cheerio'),
_ = require('lodash'); _ = require('lodash');
// Load plugins // Load plugins
@ -17,7 +20,15 @@ var Promise = require('bluebird'),
var mkdown = md({ var mkdown = md({
html: true, html: true,
linkify: true, linkify: true,
typography: true typography: true,
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>';
} catch (__) {}
}
return '<pre class="hljs"><code>' + hljs.highlightAuto(str).value + '</code></pre>';
}
}) })
.use(mdEmoji) .use(mdEmoji)
.use(mdTaskLists) .use(mdTaskLists)
@ -33,7 +44,8 @@ var mkdown = md({
}) })
.use(mdExpandTabs, { .use(mdExpandTabs, {
tabWidth: 4 tabWidth: 4
}); })
.use(mdAttrs);
// Rendering rules // Rendering rules
@ -41,13 +53,23 @@ mkdown.renderer.rules.emoji = function(token, idx) {
return '<i class="twa twa-' + token[idx].markup + '"></i>'; return '<i class="twa twa-' + token[idx].markup + '"></i>';
}; };
// Parse markdown headings tree mkdown.inline.ruler.push('internal_link', (state) => {
});
/**
* Parse markdown content and build TOC tree
*
* @param {(Function|string)} content Markdown content
* @return {Array} TOC tree
*/
const parseTree = (content) => { const parseTree = (content) => {
let tokens = md().parse(content, {}); let tokens = md().parse(content, {});
let tocArray = []; let tocArray = [];
//-> Extract headings and their respective levels
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
if (tokens[i].type !== "heading_close") { if (tokens[i].type !== "heading_close") {
continue continue
@ -75,6 +97,12 @@ const parseTree = (content) => {
} }
} }
//-> Exclude levels deeper than 2
_.remove(tocArray, (n) => { return n.level > 2; });
//-> Build tree from flat array
return _.reduce(tocArray, (tree, v) => { return _.reduce(tocArray, (tree, v) => {
let treeLength = tree.length - 1; let treeLength = tree.length - 1;
if(v.level < 2) { if(v.level < 2) {
@ -98,6 +126,7 @@ const parseTree = (content) => {
}; };
let lastNodePath = GetNodePath(); let lastNodePath = GetNodePath();
let lastNode = _.get(tree[treeLength], lastNodePath); let lastNode = _.get(tree[treeLength], lastNodePath);
if(lastNode) {
lastNode.push({ lastNode.push({
content: v.content, content: v.content,
anchor: v.anchor, anchor: v.anchor,
@ -105,16 +134,34 @@ const parseTree = (content) => {
}); });
_.set(tree[treeLength], lastNodePath, lastNode); _.set(tree[treeLength], lastNodePath, lastNode);
} }
}
return tree; 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;
};
module.exports = { module.exports = {
parse(content) { parse(content) {
return { return {
html: mkdown.render(content), html: parseContent(content),
tree: parseTree(content) tree: parseTree(content)
}; };
} }

View File

@ -35,6 +35,7 @@
"bluebird": "^3.4.1", "bluebird": "^3.4.1",
"body-parser": "^1.15.2", "body-parser": "^1.15.2",
"bulma": "^0.1.2", "bulma": "^0.1.2",
"cheerio": "^0.20.0",
"compression": "^1.6.2", "compression": "^1.6.2",
"connect-flash": "^0.1.1", "connect-flash": "^0.1.1",
"connect-redis": "^3.1.0", "connect-redis": "^3.1.0",
@ -44,6 +45,7 @@
"express-brute-redis": "0.0.1", "express-brute-redis": "0.0.1",
"express-session": "^1.14.0", "express-session": "^1.14.0",
"express-validator": "^2.20.8", "express-validator": "^2.20.8",
"highlight.js": "^9.6.0",
"i18next": "^3.4.1", "i18next": "^3.4.1",
"i18next-express-middleware": "^1.0.1", "i18next-express-middleware": "^1.0.1",
"i18next-node-fs-backend": "^0.1.2", "i18next-node-fs-backend": "^0.1.2",
@ -53,6 +55,7 @@
"markdown-it": "^7.0.1", "markdown-it": "^7.0.1",
"markdown-it-abbr": "^1.0.3", "markdown-it-abbr": "^1.0.3",
"markdown-it-anchor": "^2.5.0", "markdown-it-anchor": "^2.5.0",
"markdown-it-attrs": "^0.6.3",
"markdown-it-emoji": "^1.2.0", "markdown-it-emoji": "^1.2.0",
"markdown-it-expand-tabs": "^1.0.11", "markdown-it-expand-tabs": "^1.0.11",
"markdown-it-external-links": "0.0.5", "markdown-it-external-links": "0.0.5",
@ -71,6 +74,7 @@
"serve-favicon": "^2.3.0", "serve-favicon": "^2.3.0",
"simplemde": "^1.11.2", "simplemde": "^1.11.2",
"slug": "^0.9.1", "slug": "^0.9.1",
"sticky-js": "^1.0.5",
"twemoji-awesome": "^1.0.4", "twemoji-awesome": "^1.0.4",
"validator": "^5.5.0", "validator": "^5.5.0",
"validator-as-promised": "^1.0.2", "validator-as-promised": "^1.0.2",

View File

@ -1,5 +1,5 @@
nav.nav.has-shadow nav.nav.has-shadow.stickyscroll
.nav-left .nav-left
a.nav-item.is-brand(href='/') a.nav-item.is-brand(href='/')
img(src='/favicons/android-icon-96x96.png', alt='Wiki') img(src='/favicons/android-icon-96x96.png', alt='Wiki')

View File

@ -25,18 +25,19 @@ block content
a(href='/') Home a(href='/') Home
li li
a(href='/account') Account a(href='/account') Account
.box .box.stickyscroll(data-margin-top=70)
aside.menu(style= { 'min-width': '200px' }) aside.menu(style= { 'min-width': '200px' })
p.menu-label p.menu-label
| Contents | Contents
ul.menu-list ul.menu-list
a(href='#root', title='Start') Start
+tocMenu(pageData.tree) +tocMenu(pageData.tree)
.column .column
h1.title= pageData.title h1.title#title= pageData.title
h2.subtitle if pageData.subtitle
| Primary bold subtitle h2.subtitle= pageData.subtitle
.content .content.mkcontent
!= pageData.html != pageData.html