');
console.warn(err);
}
});
//graphviz
var graphvizs = view.find("div.graphviz.raw").removeClass("raw");
function parseGraphviz(key, value) {
var $value = $(value);
var $ele = $(value).parent().parent();
var graphviz = Viz($value.text());
if (!graphviz) throw Error('viz.js output empty graph');
$value.html(graphviz);
$ele.addClass('graphviz');
$value.children().unwrap().unwrap();
}
graphvizs.each(function (key, value) {
try {
parseGraphviz(key, value);
} catch (err) {
// workaround for graphviz not recover from error
try {
parseGraphviz(key, value);
} catch (err) {
var $value = $(value);
$value.unwrap();
$value.parent().append('
' + err + '
');
console.warn(err);
}
}
});
//mermaid
var mermaids = view.find("div.mermaid.raw").removeClass("raw");
mermaids.each(function (key, value) {
try {
var $value = $(value);
var $ele = $(value).closest('pre');
var mermaidError = null;
mermaid.parseError = function (err, hash) {
mermaidError = err;
};
if (mermaidAPI.parse($value.text())) {
$ele.addClass('mermaid');
$ele.html($value.text());
mermaid.init(undefined, $ele);
} else {
throw new Error(mermaidError);
}
} catch (err) {
$value.unwrap();
$value.parent().append('
' + err + '
');
console.warn(err);
}
});
//image href new window(emoji not included)
var images = view.find("img.raw[src]").removeClass("raw");
images.each(function (key, value) {
// if it's already wrapped by link, then ignore
var $value = $(value);
$value[0].onload = function (e) {
if(viewAjaxCallback) viewAjaxCallback();
};
});
//blockquote
var blockquote = view.find("blockquote.raw").removeClass("raw");
var blockquote_p = blockquote.find("p");
blockquote_p.each(function (key, value) {
var html = $(value).html();
html = replaceExtraTags(html);
$(value).html(html);
});
//color tag in blockquote will change its left border color
var blockquote_color = blockquote.find(".color");
blockquote_color.each(function (key, value) {
$(value).closest("blockquote").css('border-left-color', $(value).attr('data-color'));
});
//slideshare
view.find("div.slideshare.raw").removeClass("raw")
.each(function (key, value) {
$.ajax({
type: 'GET',
url: '//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/' + $(value).attr('data-slideshareid') + '&format=json',
jsonp: 'callback',
dataType: 'jsonp',
success: function (data) {
var $html = $(data.html);
var iframe = $html.closest('iframe');
var caption = $html.closest('div');
var inner = $('').append(iframe);
var height = iframe.attr('height');
var width = iframe.attr('width');
var ratio = (height / width) * 100;
inner.css('padding-bottom', ratio + '%');
$(value).html(inner).append(caption);
if(viewAjaxCallback) viewAjaxCallback();
}
});
});
//speakerdeck
view.find("div.speakerdeck.raw").removeClass("raw")
.each(function (key, value) {
var url = 'https://speakerdeck.com/oembed.json?url=https%3A%2F%2Fspeakerdeck.com%2F' + encodeURIComponent($(value).attr('data-speakerdeckid'));
//use yql because speakerdeck not support jsonp
$.ajax({
url: 'https://query.yahooapis.com/v1/public/yql',
data: {
q: "select * from json where url ='" + url + "'",
format: "json"
},
dataType: "jsonp",
success: function (data) {
if (!data.query || !data.query.results) return;
var json = data.query.results.json;
var html = json.html;
var ratio = json.height / json.width;
$(value).html(html);
var iframe = $(value).children('iframe');
var src = iframe.attr('src');
if (src.indexOf('//') == 0)
iframe.attr('src', 'https:' + src);
var inner = $('').append(iframe);
var height = iframe.attr('height');
var width = iframe.attr('width');
var ratio = (height / width) * 100;
inner.css('padding-bottom', ratio + '%');
$(value).html(inner);
if(viewAjaxCallback) viewAjaxCallback();
}
});
});
//pdf
view.find("div.pdf.raw").removeClass("raw")
.each(function (key, value) {
var url = $(value).attr('data-pdfurl');
var inner = $('');
$(this).append(inner);
PDFObject.embed(url, inner, {
height: '400px'
});
});
//syntax highlighting
view.find("code.raw").removeClass("raw")
.each(function (key, value) {
var langDiv = $(value);
if (langDiv.length > 0) {
var reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim();
var codeDiv = langDiv.find('.code');
var code = "";
if (codeDiv.length > 0) code = codeDiv.html();
else code = langDiv.html();
if (!reallang) {
var result = {
value: code
};
} else if (reallang == "haskell" || reallang == "go" || reallang == "typescript" || reallang == "jsx") {
code = S(code).unescapeHTML().s;
var result = {
value: Prism.highlight(code, Prism.languages[reallang])
};
} else if (reallang == "tiddlywiki" || reallang == "mediawiki") {
code = S(code).unescapeHTML().s;
var result = {
value: Prism.highlight(code, Prism.languages.wiki)
};
} else {
code = S(code).unescapeHTML().s;
var languages = hljs.listLanguages();
if (languages.indexOf(reallang) == -1) {
var result = hljs.highlightAuto(code);
} else {
var result = hljs.highlight(reallang, code);
}
}
if (codeDiv.length > 0) codeDiv.html(result.value);
else langDiv.html(result.value);
}
});
//mathjax
var mathjaxdivs = view.find('span.mathjax.raw').removeClass("raw").toArray();
try {
if (mathjaxdivs.length > 1) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs]);
MathJax.Hub.Queue(viewAjaxCallback);
} else if (mathjaxdivs.length > 0) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[0]]);
MathJax.Hub.Queue(viewAjaxCallback);
}
} catch (err) {
console.warn(err);
}
//render title
document.title = renderTitle(view);
}
//only static transform should be here
function postProcess(code) {
var result = $('
' + code + '
');
//link should open in new window or tab
result.find('a:not([href^="#"]):not([target])').attr('target', '_blank');
//update continue line numbers
var linenumberdivs = result.find('.gutter.linenumber').toArray();
for (var i = 0; i < linenumberdivs.length; i++) {
if ($(linenumberdivs[i]).hasClass('continue')) {
var startnumber = linenumberdivs[i - 1] ? parseInt($(linenumberdivs[i - 1]).find('> span').last().attr('data-linenumber')) : 0;
$(linenumberdivs[i]).find('> span').each(function(key, value) {
$(value).attr('data-linenumber', startnumber + key + 1);
});
}
}
// show yaml meta paring error
if (md.metaError) {
var warning = result.find('div#meta-error');
if (warning && warning.length > 0) {
warning.text(md.metaError)
} else {
warning = $('
' + md.metaError + '
')
result.prepend(warning);
}
}
return result;
}
window.postProcess = postProcess;
function generateCleanHTML(view) {
var src = view.clone();
var eles = src.find('*');
//remove syncscroll parts
eles.removeClass('part');
src.find('*[class=""]').removeAttr('class');
eles.removeAttr('data-startline data-endline');
src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
//remove gist content
src.find("code[data-gist-id]").children().remove();
//disable todo list
src.find("input.task-list-item-checkbox").attr('disabled', '');
//replace emoji image path
src.find("img.emoji").each(function (key, value) {
var name = $(value).attr('alt');
name = name.substr(1);
name = name.slice(0, name.length - 1);
$(value).attr('src', 'https://www.tortue.me/emoji/' + name + '.png');
});
//replace video to iframe
src.find("div[data-videoid]").each(function (key, value) {
var id = $(value).attr('data-videoid');
var style = $(value).attr('style');
var url = null;
if ($(value).hasClass('youtube')) {
url = 'https://www.youtube.com/embed/';
} else if ($(value).hasClass('vimeo')) {
url = 'https://player.vimeo.com/video/';
}
if (url) {
var iframe = $('');
iframe.attr('src', url + id);
iframe.attr('style', style);
$(value).html(iframe);
}
});
return src;
}
function exportToRawHTML(view) {
var filename = renderFilename(ui.area.markdown) + '.html';
var src = generateCleanHTML(view);
$(src).find('a.anchor').remove();
var html = src[0].outerHTML;
var blob = new Blob([html], {
type: "text/html;charset=utf-8"
});
saveAs(blob, filename);
}
var common = require('./common.js');
//extract markdown body to html and compile to template
function exportToHTML(view) {
var title = renderTitle(ui.area.markdown);
var filename = renderFilename(ui.area.markdown) + '.html';
var src = generateCleanHTML(view);
//generate toc
var toc = $('#ui-toc').clone();
toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
var tocAffix = $('#ui-toc-affix').clone();
tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
//generate html via template
$.get(serverurl + '/build/html.min.css', function (css) {
$.get(serverurl + '/views/html.hbs', function (data) {
var template = Handlebars.compile(data);
var context = {
url: serverurl,
title: title,
css: css,
html: src[0].outerHTML,
'ui-toc': toc.html(),
'ui-toc-affix': tocAffix.html(),
lang: (md && md.meta && md.meta.lang) ? 'lang="' + md.meta.lang + '"' : null,
dir: (md && md.meta && md.meta.dir) ? 'dir="' + md.meta.dir + '"' : null
};
var html = template(context);
// console.log(html);
var blob = new Blob([html], {
type: "text/html;charset=utf-8"
});
saveAs(blob, filename);
});
});
}
//jQuery sortByDepth
$.fn.sortByDepth = function () {
var ar = this.map(function () {
return {
length: $(this).parents().length,
elt: this
}
}).get(),
result = [],
i = ar.length;
ar.sort(function (a, b) {
return a.length - b.length;
});
while (i--) {
result.push(ar[i].elt);
}
return $(result);
};
function toggleTodoEvent(e) {
var startline = $(this).closest('li').attr('data-startline') - 1;
var line = editor.getLine(startline);
var matches = line.match(/^[>\s]*[\-\+\*]\s\[([x ])\]/);
if (matches && matches.length >= 2) {
var checked = null;
if (matches[1] == 'x')
checked = true;
else if (matches[1] == ' ')
checked = false;
var replacements = matches[0].match(/(^[>\s]*[\-\+\*]\s\[)([x ])(\])/);
editor.replaceRange(checked ? ' ' : 'x', {
line: startline,
ch: replacements[1].length
}, {
line: startline,
ch: replacements[1].length + 1
}, '+input');
}
}
//remove hash
function removeHash() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}
var tocExpand = false;
function checkExpandToggle() {
var toc = $('.ui-toc-dropdown .toc');
var toggle = $('.expand-toggle');
if (!tocExpand) {
toc.removeClass('expand');
toggle.text('Expand all');
} else {
toc.addClass('expand');
toggle.text('Collapse all');
}
}
//toc
function generateToc(id) {
var target = $('#' + id);
target.html('');
new Toc('doc', {
'level': 3,
'top': -1,
'class': 'toc',
'ulClass': 'nav',
'targetId': id,
'process': getHeaderContent
});
if (target.text() == 'undefined')
target.html('');
var tocMenu = $('Expand all');
var backtotop = $('Back to top');
var gotobottom = $('Go to bottom');
checkExpandToggle();
toggle.click(function (e) {
e.preventDefault();
e.stopPropagation();
tocExpand = !tocExpand;
checkExpandToggle();
});
backtotop.click(function (e) {
e.preventDefault();
e.stopPropagation();
if (scrollToTop)
scrollToTop();
removeHash();
});
gotobottom.click(function (e) {
e.preventDefault();
e.stopPropagation();
if (scrollToBottom)
scrollToBottom();
removeHash();
});
tocMenu.append(toggle).append(backtotop).append(gotobottom);
target.append(tocMenu);
}
//smooth all hash trigger scrolling
function smoothHashScroll() {
var hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray();
for (var i = 0; i < hashElements.length; i++) {
var element = hashElements[i];
var $element = $(element);
var hash = element.hash;
if (hash) {
$element.on('click', function (e) {
// store hash
var hash = decodeURIComponent(this.hash);
// escape special characters in jquery selector
var $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, "\\$1"));
// return if no element been selected
if ($hash.length <= 0) return;
// prevent default anchor click behavior
e.preventDefault();
// animate
$('body, html').stop(true, true).animate({
scrollTop: $hash.offset().top
}, 100, "linear", function () {
// when done, add hash to url
// (default click behaviour)
window.location.hash = hash;
});
});
$element.attr('smoothhashscroll', '');
}
}
}
function imgPlayiframe(element, src) {
if (!$(element).attr("data-videoid")) return;
var iframe = $("");
$(iframe).attr("src", src + $(element).attr("data-videoid") + '?autoplay=1');
$(element).find('img').css('visibility', 'hidden');
$(element).append(iframe);
}
var anchorForId = function (id) {
var anchor = document.createElement("a");
anchor.className = "anchor hidden-xs";
anchor.href = "#" + id;
anchor.innerHTML = "";
anchor.title = id;
return anchor;
};
var linkifyAnchors = function (level, containingElement) {
var headers = containingElement.getElementsByTagName("h" + level);
for (var h = 0; h < headers.length; h++) {
var header = headers[h];
if (header.getElementsByClassName("anchor").length == 0) {
if (typeof header.id == "undefined" || header.id == "") {
//to escape characters not allow in css and humanize
var id = slugifyWithUTF8(getHeaderContent(header));
header.id = id;
}
header.insertBefore(anchorForId(header.id), header.firstChild);
}
}
};
function autoLinkify(view) {
var contentBlock = view[0];
if (!contentBlock) {
return;
}
for (var level = 1; level <= 6; level++) {
linkifyAnchors(level, contentBlock);
}
}
function getHeaderContent(header) {
var headerHTML = $(header).clone();
headerHTML.find('.MathJax_Preview').remove();
headerHTML.find('.MathJax').remove();
return headerHTML[0].innerHTML;
}
function deduplicatedHeaderId(view) {
var headers = view.find(':header.raw').removeClass('raw').toArray();
for (var i = 0; i < headers.length; i++) {
var id = $(headers[i]).attr('id');
if (!id) continue;
var duplicatedHeaders = view.find(':header[id="' + id + '"]').toArray();
for (var j = 0; j < duplicatedHeaders.length; j++) {
if (duplicatedHeaders[j] != headers[i]) {
var newId = id + j;
var $duplicatedHeader = $(duplicatedHeaders[j]);
$duplicatedHeader.attr('id', newId);
var $headerLink = $duplicatedHeader.find('> .header-link');
$headerLink.attr('href', '#' + newId);
$headerLink.attr('title', newId);
}
}
}
}
function renderTOC(view) {
var tocs = view.find('.toc').toArray();
for (var i = 0; i < tocs.length; i++) {
var toc = $(tocs[i]);
var id = 'toc' + i;
toc.attr('id', id);
var target = $('#' + id);
target.html('');
new Toc('doc', {
'level': 3,
'top': -1,
'class': 'toc',
'targetId': id,
'process': getHeaderContent
});
if (target.text() == 'undefined')
target.html('');
target.replaceWith(target.html());
}
}
function scrollToHash() {
var hash = location.hash;
location.hash = "";
location.hash = hash;
}
function highlightRender(code, lang) {
if (!lang || /no(-?)highlight|plain|text/.test(lang))
return;
code = S(code).escapeHTML().s
if (lang == 'sequence') {
return '
' + code + '
';
} else if (lang == 'flow') {
return '
' + code + '
';
} else if (lang == 'graphviz') {
return '
' + code + '
';
} else if (lang == 'mermaid') {
return '
' + code + '
';
}
var result = {
value: code
};
var showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang);
if (showlinenumbers) {
var startnumber = 1;
var matches = lang.match(/\=(\d+)$/);
if (matches)
startnumber = parseInt(matches[1]);
var lines = result.value.split('\n');
var linenumbers = [];
for (var i = 0; i < lines.length - 1; i++) {
linenumbers[i] = "";
}
var continuelinenumber = /\=\+$/.test(lang);
var linegutter = "
\n';
};
/* Defined regex markdown it plugins */
var Plugin = require('markdown-it-regexp');
//youtube
var youtubePlugin = new Plugin(
// regexp to match
/{%youtube\s*([\d\D]*?)\s*%}/,
// this function will be called when something matches
function (match, utils) {
var videoid = match[1];
if (!videoid) return;
var div = $('');
div.attr('data-videoid', videoid);
var thumbnail_src = '//img.youtube.com/vi/' + videoid + '/hqdefault.jpg';
var image = '';
div.append(image);
var icon = '';
div.append(icon);
return div[0].outerHTML;
}
);
//vimeo
var vimeoPlugin = new Plugin(
// regexp to match
/{%vimeo\s*([\d\D]*?)\s*%}/,
// this function will be called when something matches
function (match, utils) {
var videoid = match[1];
if (!videoid) return;
var div = $('');
div.attr('data-videoid', videoid);
var icon = '';
div.append(icon);
return div[0].outerHTML;
}
);
//gist
var gistPlugin = new Plugin(
// regexp to match
/{%gist\s*([\d\D]*?)\s*%}/,
// this function will be called when something matches
function (match, utils) {
var gistid = match[1];
var code = '';
return code;
}
);
//TOC
var tocPlugin = new Plugin(
// regexp to match
/^\[TOC\]$/i,
// this function will be called when something matches
function (match, utils) {
return '';
}
);
//slideshare
var slidesharePlugin = new Plugin(
// regexp to match
/{%slideshare\s*([\d\D]*?)\s*%}/,
// this function will be called when something matches
function (match, utils) {
var slideshareid = match[1];
var div = $('');
div.attr('data-slideshareid', slideshareid);
return div[0].outerHTML;
}
);
//speakerdeck
var speakerdeckPlugin = new Plugin(
// regexp to match
/{%speakerdeck\s*([\d\D]*?)\s*%}/,
// this function will be called when something matches
function (match, utils) {
var speakerdeckid = match[1];
var div = $('');
div.attr('data-speakerdeckid', speakerdeckid);
return div[0].outerHTML;
}
);
//pdf
var pdfPlugin = new Plugin(
// regexp to match
/{%pdf\s*([\d\D]*?)\s*%}/,
// this function will be called when something matches
function (match, utils) {
var pdfurl = match[1];
if (!isValidURL(pdfurl)) return match[0];
var div = $('');
div.attr('data-pdfurl', pdfurl);
return div[0].outerHTML;
}
);
//yaml meta, from https://github.com/eugeneware/remarkable-meta
function get(state, line) {
var pos = state.bMarks[line];
var max = state.eMarks[line];
return state.src.substr(pos, max - pos);
}
function meta(state, start, end, silent) {
if (start !== 0 || state.blkIndent !== 0) return false;
if (state.tShift[start] < 0) return false;
if (!get(state, start).match(/^---$/)) return false;
var data = [];
for (var line = start + 1; line < end; line++) {
var str = get(state, line);
if (str.match(/^(\.{3}|-{3})$/)) break;
if (state.tShift[line] < 0) break;
data.push(str);
}
if (line >= end) return false;
try {
md.meta = jsyaml.safeLoad(data.join('\n')) || {};
delete md.metaError;
} catch(err) {
md.metaError = err;
console.warn(err);
return false;
}
state.line = line + 1;
return true;
}
function metaPlugin(md) {
md.meta = md.meta || {};
md.block.ruler.before('code', 'meta', meta, {
alt: []
});
}
md.use(metaPlugin);
md.use(youtubePlugin);
md.use(vimeoPlugin);
md.use(gistPlugin);
md.use(tocPlugin);
md.use(slidesharePlugin);
md.use(speakerdeckPlugin);
md.use(pdfPlugin);
module.exports = {
md: md,
updateLastChange: updateLastChange,
postProcess: postProcess,
finishView: finishView,
autoLinkify: autoLinkify,
deduplicatedHeaderId: deduplicatedHeaderId,
renderTOC: renderTOC,
renderTitle: renderTitle,
renderFilename: renderFilename,
renderTags: renderTags,
isValidURL: isValidURL,
generateToc: generateToc,
smoothHashScroll: smoothHashScroll,
scrollToHash: scrollToHash,
updateLastChangeUser: updateLastChangeUser,
updateOwner: updateOwner,
parseMeta: parseMeta,
exportToHTML: exportToHTML,
exportToRawHTML: exportToRawHTML
};