var hljs = require('highlight.js'); var PDFObject = require('pdfobject'); var S = require('string'); var saveAs = require('file-saver').saveAs; require('../vendor/md-toc'); //auto update last change window.createtime = null; window.lastchangetime = null; window.lastchangeui = { status: $(".ui-status-lastchange"), time: $(".ui-lastchange"), user: $(".ui-lastchangeuser"), nouser: $(".ui-no-lastchangeuser") } var ownerui = $(".ui-owner"); function updateLastChange() { if (!lastchangeui) return; if (createtime) { if (createtime && !lastchangetime) { lastchangeui.status.text('created'); } else { lastchangeui.status.text('changed'); } var time = lastchangetime || createtime; lastchangeui.time.html(moment(time).fromNow()); lastchangeui.time.attr('title', moment(time).format('llll')); } } setInterval(updateLastChange, 60000); window.lastchangeuser = null; window.lastchangeuserprofile = null; function updateLastChangeUser() { if (lastchangeui) { if (lastchangeuser && lastchangeuserprofile) { var icon = lastchangeui.user.children('i'); icon.attr('title', lastchangeuserprofile.name).tooltip('fixTitle'); if (lastchangeuserprofile.photo) icon.attr('style', 'background-image:url(' + lastchangeuserprofile.photo + ')'); lastchangeui.user.show(); lastchangeui.nouser.hide(); } else { lastchangeui.user.hide(); lastchangeui.nouser.show(); } } } window.owner = null; window.ownerprofile = null; function updateOwner() { if (ownerui) { if (owner && ownerprofile && owner !== lastchangeuser) { var icon = ownerui.children('i'); icon.attr('title', ownerprofile.name).tooltip('fixTitle'); var styleString = 'background-image:url(' + ownerprofile.photo + ')'; if (ownerprofile.photo && icon.attr('style') !== styleString) icon.attr('style', styleString); ownerui.show(); } else { ownerui.hide(); } } } //get title function getTitle(view) { var title = ""; if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) { title = md.meta.title; } else { var h1s = view.find("h1"); if (h1s.length > 0) { title = h1s.first().text(); } else { title = null; } } return title; } //render title function renderTitle(view) { var title = getTitle(view); if (title) { title += ' - HackMD'; } else { title = 'HackMD - Collaborative markdown notes'; } return title; } //render filename function renderFilename(view) { var filename = getTitle(view); if (!filename) { filename = 'Untitled'; } return filename; } function slugifyWithUTF8(text) { var newText = S(text.toLowerCase()).trim().stripTags().dasherize().s; newText = newText.replace(/([\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\[\\\]\^\`\{\|\}\~])/g, ''); return newText; } function isValidURL(str) { var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator if (!pattern.test(str)) { return false; } else { return true; } } //parse meta function parseMeta(md, edit, view, toc, tocAffix) { var lang = null; var dir = null; var breaks = true; if (md && md.meta) { var meta = md.meta; lang = meta.lang; dir = meta.dir; breaks = meta.breaks; } //text language if (lang && typeof lang == "string") { view.attr('lang', lang); toc.attr('lang', lang); tocAffix.attr('lang', lang); if (edit) edit.attr('lang', lang); } else { view.removeAttr('lang'); toc.removeAttr('lang'); tocAffix.removeAttr('lang'); if (edit) edit.removeAttr('lang', lang); } //text direction if (dir && typeof dir == "string") { view.attr('dir', dir); toc.attr('dir', dir); tocAffix.attr('dir', dir); } else { view.removeAttr('dir'); toc.removeAttr('dir'); tocAffix.removeAttr('dir'); } //breaks if (typeof breaks === 'boolean' && !breaks) { md.options.breaks = false; } else { md.options.breaks = true; } } var viewAjaxCallback = null; //regex for extra tags var spaceregex = /\s*/; var notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/; var coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/; coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g"); var nameregex = /\[name=(.*?)\]/; var timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*?)\]/; var nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g"); nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g"); timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g"); function replaceExtraTags(html) { html = html.replace(coloregex, ''); html = html.replace(nameandtimeregex, ' $1 $2'); html = html.replace(nameregex, ' $1'); html = html.replace(timeregex, ' $1'); return html; } if (typeof mermaid !== 'undefined' && mermaid) mermaid.startOnLoad = false; //dynamic event or object binding here function finishView(view) { //todo list var lis = view.find('li.raw').removeClass("raw").sortByDepth().toArray(); for (var i = 0; i < lis.length; i++) { var li = lis[i]; var html = $(li).clone()[0].innerHTML; var p = $(li).children('p'); if (p.length == 1) { html = p.html(); li = p[0]; } html = replaceExtraTags(html); li.innerHTML = html; var disabled = 'disabled'; if(typeof editor !== 'undefined' && havePermission()) disabled = ''; if (/^\s*\[[x ]\]\s*/.test(html)) { li.innerHTML = html.replace(/^\s*\[ \]\s*/, '') .replace(/^\s*\[x\]\s*/, ''); lis[i].setAttribute('class', 'task-list-item'); } if (typeof editor !== 'undefined' && havePermission()) $(li).find('input').change(toggleTodoEvent); //color tag in list will convert it to tag icon with color var tag_color = $(li).closest('ul').find(".color"); tag_color.each(function (key, value) { $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color')); }); } //youtube view.find("div.youtube.raw").removeClass("raw") .click(function () { imgPlayiframe(this, '//www.youtube.com/embed/'); }); //vimeo view.find("div.vimeo.raw").removeClass("raw") .click(function () { imgPlayiframe(this, '//player.vimeo.com/video/'); }) .each(function (key, value) { $.ajax({ type: 'GET', url: '//vimeo.com/api/v2/video/' + $(value).attr('data-videoid') + '.json', jsonp: 'callback', dataType: 'jsonp', success: function (data) { var thumbnail_src = data[0].thumbnail_large; var image = ''; $(value).prepend(image); if(viewAjaxCallback) viewAjaxCallback(); } }); }); //gist view.find("code[data-gist-id]").each(function (key, value) { if ($(value).children().length == 0) $(value).gist(viewAjaxCallback); }); //emojify try { emojify.run(view[0]); } catch (err) { console.warn(err); } //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) {} //sequence diagram var sequences = view.find("div.sequence-diagram.raw").removeClass("raw"); sequences.each(function (key, value) { try { var $value = $(value); var $ele = $(value).parent().parent(); var sequence = $value; sequence.sequenceDiagram({ theme: 'simple' }); $ele.addClass('sequence-diagram'); $value.children().unwrap().unwrap(); var svg = $ele.find('> svg'); svg[0].setAttribute('viewBox', '0 0 ' + svg.attr('width') + ' ' + svg.attr('height')); svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet'); } catch (err) { console.warn(err); } }); //flowchart var flow = view.find("div.flow-chart.raw").removeClass("raw"); flow.each(function (key, value) { try { var $value = $(value); var $ele = $(value).parent().parent(); var chart = flowchart.parse($value.text()); $value.html(''); chart.drawSVG(value, { 'line-width': 2, 'fill': 'none', 'font-size': '16px', 'font-family': "'Andale Mono', monospace" }); $ele.addClass('flow-chart'); $value.children().unwrap().unwrap(); } catch (err) { console.warn(err); } }); //graphviz var Viz = require("viz.js"); var graphvizs = view.find("div.graphviz.raw").removeClass("raw"); graphvizs.each(function (key, value) { try { var $value = $(value); var $ele = $(value).parent().parent(); var graphviz = Viz($value.text()); $value.html(graphviz); $ele.addClass('graphviz'); $value.children().unwrap().unwrap(); } catch (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 { console.warn(mermaidError); } } catch (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(); code = md.utils.unescapeAll(code); if (!reallang) { var result = { value: md.utils.escapeHtml(code) }; } else if (reallang == "tiddlywiki" || reallang == "mediawiki") { var result = { value: Prism.highlight(code, Prism.languages.wiki) }; } else { 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); } }); //render title document.title = renderTitle(view); } //only static transform should be here function postProcess(code) { var result = $(''
+ highlighted
+ '
\n';
};
/* Defined regex markdown it plugins */
require('script!../vendor/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\]$/,
// 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')) || {};
} catch(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,
generateToc: generateToc,
smoothHashScroll: smoothHashScroll,
scrollToHash: scrollToHash,
updateLastChangeUser: updateLastChangeUser,
updateOwner: updateOwner,
parseMeta: parseMeta,
exportToHTML: exportToHTML,
exportToRawHTML: exportToRawHTML
};