require('prismjs/themes/prism.css');
var Prism = require('prismjs');
require('prismjs/components/prism-wiki');
require('prismjs/components/prism-haskell');
require('prismjs/components/prism-go');
require('prismjs/components/prism-typescript');
require('prismjs/components/prism-jsx');
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;
}
// render tags
function renderTags(view) {
var tags = [];
var rawtags = [];
if (md && md.meta && md.meta.tags && (typeof md.meta.tags == "string" || typeof md.meta.tags == "number")) {
var metaTags = ('' + md.meta.tags).split(',');
for (var i = 0; i < metaTags.length; i++) {
var text = metaTags[i].trim();
if (text) rawtags.push(text);
}
} else {
view.find('h6').each(function (key, value) {
if (/^tags/gmi.test($(value).text())) {
var codes = $(value).find("code");
for (var i = 0; i < codes.length; i++) {
var text = codes[i].innerHTML.trim();
if (text) rawtags.push(text);
}
}
});
}
for (var i = 0; i < rawtags.length; i++) {
var found = false;
for (var j = 0; j < tags.length; j++) {
if (tags[j] == rawtags[i]) {
found = true;
break;
}
}
if (!found)
tags.push(rawtags[i]);
}
return tags;
}
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;
}
}
window.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);
});
//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) {
$value.unwrap();
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) {
$value.unwrap();
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) {
$value.unwrap();
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 {
$value.unwrap();
console.warn(mermaidError);
}
} catch (err) {
$value.unwrap();
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 = $('
'
+ 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 = '
';
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')) || {};
} 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,
renderTags: renderTags,
isValidURL: isValidURL,
generateToc: generateToc,
smoothHashScroll: smoothHashScroll,
scrollToHash: scrollToHash,
updateLastChangeUser: updateLastChangeUser,
updateOwner: updateOwner,
parseMeta: parseMeta,
exportToHTML: exportToHTML,
exportToRawHTML: exportToRawHTML
};