summaryrefslogtreecommitdiff
path: root/public/js
diff options
context:
space:
mode:
authorWu Cheng-Han2015-05-04 15:53:29 +0800
committerWu Cheng-Han2015-05-04 15:53:29 +0800
commit4b0ca55eb79e963523eb6c8197825e9e8ae904e2 (patch)
tree574f3923af77b37b41dbf1b00bcd7827ef724a28 /public/js
parent61eb11d23c65c9e5c493c67d055f785cbec139e2 (diff)
First commit, version 0.2.7
Diffstat (limited to 'public/js')
-rw-r--r--public/js/cover.js311
-rw-r--r--public/js/extra.js278
-rw-r--r--public/js/ga.js14
-rw-r--r--public/js/history.js256
-rw-r--r--public/js/index.js940
-rw-r--r--public/js/unused.js45
6 files changed, 1844 insertions, 0 deletions
diff --git a/public/js/cover.js b/public/js/cover.js
new file mode 100644
index 00000000..fea51f6a
--- /dev/null
+++ b/public/js/cover.js
@@ -0,0 +1,311 @@
+$(".masthead-nav li").click(function () {
+ $(this).siblings().removeClass("active");
+ $(this).addClass("active");
+});
+
+$(".ui-home").click(function () {
+ $(".section").hide();
+ $("#home").fadeIn();
+});
+
+$(".ui-history").click(function () {
+ $(".section").hide();
+ $("#history").fadeIn();
+});
+
+$(".ui-releasenotes").click(function () {
+ $(".section").hide();
+ $("#releasenotes").fadeIn();
+});
+
+function checkHistoryList() {
+ if ($("#history-list").children().length > 0)
+ $(".ui-nohistory").hide();
+ else if ($("#history-list").children().length == 0) {
+ $(".ui-nohistory").slideDown();
+ var cookienotehistory = JSON.parse($.cookie('notehistory'));
+ if (login && cookienotehistory && cookienotehistory.length > 0) {
+ $(".ui-import-from-cookie").slideDown();
+ }
+ }
+}
+
+function parseHistoryCallback() {
+ checkHistoryList();
+ $(".ui-history-close").click(function (e) {
+ e.preventDefault();
+ var id = $(this).closest("a").attr("href").split('/')[1];
+ getHistory(function (notehistory) {
+ var newnotehistory = removeHistory(id, notehistory);
+ saveHistory(newnotehistory);
+ });
+ $(this).closest("li").remove();
+ checkHistoryList();
+ });
+}
+
+var login = false;
+
+checkIfAuth(
+ function (data) {
+ $('.ui-signin').hide();
+ $('.ui-or').hide();
+ $('.ui-welcome').show();
+ $('.ui-name').html(data.name);
+ $('.ui-signout').show();
+ $(".ui-history").click();
+ login = true;
+ },
+ function () {
+ $('.ui-signin').slideDown();
+ $('.ui-or').slideDown();
+ login = false;
+ }
+);
+
+parseHistory(parseHistoryCallback);
+
+$(".ui-import-from-cookie").click(function () {
+ saveCookieHistoryToServer(function() {
+ parseCookieToHistory(parseHistoryCallback);
+ $(".ui-import-from-cookie").hide();
+ });
+});
+
+var source = $("#template").html();
+var template = Handlebars.compile(source);
+var context = {
+ release: [
+ {
+ version: "0.2.7",
+ tag: "fuel",
+ date: moment("201505031200", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support facebook, twitter, github, dropbox login",
+ "+ Support own history"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "* Adjust history ui",
+ "* Upgrade realtime package",
+ "* Upgrade editor package, now support composition input better"
+ ]
+ },
+ {
+ title: "Fixes",
+ item: [
+ "* Partial update might not render properly",
+ "* Cursor focus might not at correct position"
+ ]
+ }
+ ]
+ },
+ {
+ version: "0.2.6",
+ tag: "zippo",
+ date: moment("201504241600", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support sync scroll",
+ "+ Support partial update"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "* Added feedback ui",
+ "* Adjust animations and delays",
+ "* Adjust editor viewportMargin for performance",
+ "* Adjust emit refresh event occasion",
+ "* Added editor fallback fonts",
+ "* Index page auto focus at history if valid"
+ ]
+ },
+ {
+ title: "Fixes",
+ item: [
+ "* Server might not disconnect client properly",
+ "* Resume connection might restore wrong info"
+ ]
+ }
+ ]
+ },
+ {
+ version: "0.2.5",
+ tag: "lightning",
+ date: moment("201504142110", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support import from dropbox and clipboard",
+ "+ Support more code highlighting",
+ "+ Support mathjax, sequence diagram and flow chart"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "* Adjust toolbar and layout style",
+ "* Adjust mobile layout style",
+ "* Adjust history layout style",
+ "* Server using heartbeat to gain accuracy of online users"
+ ]
+ },
+ {
+ title: "Fixes",
+ item: [
+ "* Virtual keyboard might broken the navbar",
+ "* Adjust editor viewportMargin for preloading content"
+ ]
+ }
+ ]
+ },
+ {
+ version: "0.2.4",
+ tag: "flint",
+ date: moment("201504101240", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support save to dropbox",
+ "+ Show other users' cursor with light color"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "* Adjust toolbar layout style for future"
+ ]
+ },
+ {
+ title: "Fixes",
+ item: [
+ "* Title might not render properly",
+ "* Code border style might not show properly",
+ "* Server might not connect concurrent client properly"
+ ]
+ }
+ ]
+ },
+ {
+ version: "0.2.3",
+ tag: "light",
+ date: moment("201504062030", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support youtube, vimeo",
+ "+ Support gist",
+ "+ Added quick link in pretty",
+ "+ Added font-smoothing style"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "* Change the rendering engine to remarkable",
+ "* Adjust view, todo list layout style for UX",
+ "+ Added responsive layout check",
+ "+ Auto reload if client version mismatch",
+ "+ Keep history stack after reconnect if nothing changed",
+ "+ Added features page"
+ ]
+ },
+ {
+ title: "Fixes",
+ item: [
+ "* Closetags auto input might not have proper origin",
+ "* Autofocus on editor only if it's on desktop",
+ "+ Prevent using real script and iframe tags",
+ "* Sorting in history by time not percise"
+ ]
+ }
+ ]
+ },
+ {
+ version: "0.2.2",
+ tag: "fire",
+ date: moment("201503272110", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support smartLists, smartypants",
+ "+ Support line number on code block",
+ "+ Support tags and search or sort history"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "+ Added delay on socket change",
+ "+ Updated markdown-body width to match github style",
+ "+ Socket changes now won't add to editor's history",
+ "* Reduce redundant server events"
+ ]
+ },
+ {
+ title: "Fixes",
+ item: [
+ "* Toolbar links might get wrong",
+ "* Wrong action redirections"
+ ]
+ }
+ ]
+ },
+ {
+ version: "0.2.1",
+ tag: "spark",
+ date: moment("201503171340", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support github-like todo-list",
+ "+ Support emoji"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "+ Added more effects on transition",
+ "+ Reduced rendering delay",
+ "+ Auto close and match brackets",
+ "+ Auto close and match tags",
+ "+ Added code fold and fold gutters",
+ "+ Added continue listing of markdown"
+ ]
+ }
+ ]
+ },
+ {
+ version: "0.2.0",
+ tag: "launch-day",
+ date: moment("201503142020", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Markdown editor",
+ "+ Preview html",
+ "+ Realtime collaborate",
+ "+ Cross-platformed",
+ "+ Recently used history"
+ ]
+ }
+ ]
+ }
+ ]
+};
+var html = template(context);
+$("#releasenotes").html(html); \ No newline at end of file
diff --git a/public/js/extra.js b/public/js/extra.js
new file mode 100644
index 00000000..45833c89
--- /dev/null
+++ b/public/js/extra.js
@@ -0,0 +1,278 @@
+//get title
+function getTitle(view) {
+ var h1s = view.find("h1");
+ var title = "";
+ 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 notes';
+ }
+ return title;
+}
+//render filename
+function renderFilename(view) {
+ var filename = getTitle(view);
+ if (!filename) {
+ filename = 'Untitled';
+ }
+ return filename;
+}
+
+//dynamic event or object binding here
+function finishView(view) {
+ //youtube
+ view.find(".youtube").click(function () {
+ imgPlayiframe(this, '//www.youtube.com/embed/');
+ });
+ //vimeo
+ view.find(".vimeo")
+ .click(function () {
+ imgPlayiframe(this, '//player.vimeo.com/video/');
+ })
+ .each(function (key, value) {
+ $.ajax({
+ type: 'GET',
+ url: 'http://vimeo.com/api/v2/video/' + $(value).attr('videoid') + '.json',
+ jsonp: 'callback',
+ dataType: 'jsonp',
+ success: function (data) {
+ var thumbnail_src = data[0].thumbnail_large;
+ $(value).css('background-image', 'url(' + thumbnail_src + ')');
+ }
+ });
+ });
+ //gist
+ view.find("code[data-gist-id]").each(function(key, value) {
+ if($(value).children().length == 0)
+ $(value).gist();
+ });
+ //emojify
+ emojify.run(view[0]);
+ //mathjax
+ var mathjaxdivs = view.find('.mathjax').toArray();
+ try {
+ for (var i = 0; i < mathjaxdivs.length; i++) {
+ MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[i].innerHTML]);
+ $(mathjaxdivs[i]).removeClass("mathjax");
+ }
+ } catch(err) {
+ }
+ //sequence diagram
+ var sequence = view.find(".sequence-diagram");
+ try {
+ sequence.sequenceDiagram({
+ theme: 'simple'
+ });
+ sequence.parent().parent().replaceWith(sequence);
+ sequence.removeClass("sequence-diagram");
+ } catch(err) {
+ console.error(err);
+ }
+ //flowchart
+ var flow = view.find(".flow-chart");
+ flow.each(function (key, value) {
+ try {
+ var chart = flowchart.parse($(value).text());
+ $(value).html('');
+ chart.drawSVG(value, {
+ 'line-width': 2,
+ 'fill': 'none',
+ 'font-size': '16px',
+ 'font-family': "'Andale Mono', monospace"
+ });
+ $(value).parent().parent().replaceWith(value);
+ $(value).removeClass("flow-chart");
+ } catch(err) {
+ console.error(err);
+ }
+ });
+ //render title
+ document.title = renderTitle(view);
+}
+//only static transform should be here
+function postProcess(code) {
+ var result = $('<div>' + code + '</div>');
+ //prevent XSS
+ result.find("script").replaceWith(function () {
+ return "<noscript>" + $(this).html() + "</noscript>"
+ });
+ result.find("iframe").replaceWith(function () {
+ return "<noiframe>" + $(this).html() + "</noiframe>"
+ });
+ //todo list
+ var lis = result[0].getElementsByTagName('li');
+ for (var i = 0; i < lis.length; i++) {
+ var html = lis[i].innerHTML;
+ if (/^\s*\[[x ]\]\s+/.test(html)) {
+ lis[i].innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled>')
+ .replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled>');
+ lis[i].setAttribute('class', 'task-list-item');
+ }
+ }
+ return result;
+}
+
+function setSizebyAttr(element, target) {
+ var width = $(element).attr("width") ? $(element).attr("width") : '100%';
+ var height = $(element).attr("height") ? $(element).attr("height") : '360px';
+ $(target).width(width);
+ $(target).height(height);
+}
+
+function imgPlayiframe(element, src) {
+ if (!$(element).attr("videoid")) return;
+ var iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>");
+ $(iframe).attr("src", src + $(element).attr("videoid") + '?autoplay=1');
+ setSizebyAttr(element, iframe);
+ $(element).html(iframe);
+}
+
+var anchorForId = function (id) {
+ var anchor = document.createElement("a");
+ anchor.className = "header-link";
+ anchor.href = "#" + id;
+ anchor.innerHTML = "<span class=\"sr-only\">Permalink</span><i class=\"fa fa-link\"></i>";
+ anchor.title = "Permalink";
+ 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 (typeof header.id == "undefined" || header.id == "") {
+ var id = S(header.innerHTML.toLowerCase()).trim().stripTags().dasherize().s;
+ header.id = encodeURIComponent(id);
+ }
+ header.appendChild(anchorForId(header.id));
+ }
+};
+
+function autoLinkify(view) {
+ var contentBlock = view[0];
+ if (!contentBlock) {
+ return;
+ }
+ for (var level = 1; level <= 6; level++) {
+ linkifyAnchors(level, contentBlock);
+ }
+};
+
+function scrollToHash() {
+ var hash = location.hash;
+ location.hash = "";
+ location.hash = hash;
+}
+
+function highlightRender(code, lang) {
+ if (!lang || /no(-?)highlight|plain|text/.test(lang))
+ return;
+ if(lang == 'sequence') {
+ return '<div class="sequence-diagram">' + code + '</div>';
+ } else if(lang == 'flow') {
+ return '<div class="flow-chart">' + code + '</div>';
+ }
+ var reallang = lang.replace('=', '');
+ var languages = hljs.listLanguages();
+ if (languages.indexOf(reallang) == -1) {
+ var result = hljs.highlightAuto(code);
+ } else {
+ var result = hljs.highlight(reallang, code);
+ }
+ if (/\=$/.test(lang)) {
+ var lines = result.value.split('\n');
+ var linenumbers = [];
+ for (var i = 0; i < lines.length; i++) {
+ linenumbers[i] = "<div class='linenumber'>" + (i + 1) + "</div>";
+ }
+ var linegutter = "<div class='gutter'>" + linenumbers.join('\n') + "</div>";
+ result.value = "<div class='wrapper'>" + linegutter + "<div class='code'>" + result.value + "</div></div>";
+ }
+ return result.value;
+}
+
+emojify.setConfig({
+ img_dir: '/vendor/emojify/images',
+ ignore_emoticons: true
+});
+
+var md = new Remarkable('full', {
+ html: true,
+ linkify: true,
+ typographer: true,
+ highlight: highlightRender
+});
+//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 class="youtube"></div>');
+ setSizebyAttr(div, div);
+ div.attr('videoid', videoid);
+ var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
+ div.append(icon);
+ var thumbnail_src = '//img.youtube.com/vi/' + videoid + '/hqdefault.jpg';
+ div.css('background-image', 'url(' + thumbnail_src + ')');
+ 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 class="vimeo"></div>');
+ setSizebyAttr(div, div);
+ div.attr('videoid', videoid);
+ var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
+ 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 = '<code data-gist-id="' + gistid + '"/>';
+ return code;
+ }
+);
+//mathjax
+var mathjaxPlugin = new Plugin(
+ // regexp to match
+ /^\$\$\n([\d\D]*?)\n\$\$$|\$([\d\D]*?)\$/,
+
+ // this function will be called when something matches
+ function (match, utils) {
+ //var code = $(match).text();
+ return '<span class="mathjax">' + match[0] + '</span>';
+ }
+);
+md.use(youtubePlugin);
+md.use(vimeoPlugin);
+md.use(gistPlugin);
+md.use(mathjaxPlugin); \ No newline at end of file
diff --git a/public/js/ga.js b/public/js/ga.js
new file mode 100644
index 00000000..0fda2410
--- /dev/null
+++ b/public/js/ga.js
@@ -0,0 +1,14 @@
+(function (i, s, o, g, r, a, m) {
+ i['GoogleAnalyticsObject'] = r;
+ i[r] = i[r] || function () {
+ (i[r].q = i[r].q || []).push(arguments)
+ }, i[r].l = 1 * new Date();
+ a = s.createElement(o),
+ m = s.getElementsByTagName(o)[0];
+ a.async = 1;
+ a.src = g;
+ m.parentNode.insertBefore(a, m)
+})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
+
+ga('create', 'get your self one', 'auto');
+ga('send', 'pageview'); \ No newline at end of file
diff --git a/public/js/history.js b/public/js/history.js
new file mode 100644
index 00000000..c6deaa53
--- /dev/null
+++ b/public/js/history.js
@@ -0,0 +1,256 @@
+//common
+function checkIfAuth(yesCallback, noCallback) {
+ $.get('/me')
+ .done(function (data) {
+ if (data && data.status == 'ok') {
+ yesCallback(data);
+ } else {
+ noCallback();
+ }
+ })
+ .fail(function () {
+ noCallback();
+ });
+}
+
+function saveHistory(notehistory) {
+ checkIfAuth(
+ function () {
+ saveHistoryToServer(notehistory);
+ },
+ function () {
+ saveHistoryToCookie(notehistory);
+ }
+ );
+}
+
+function saveHistoryToCookie(notehistory) {
+ $.cookie('notehistory', JSON.stringify(notehistory), {
+ expires: 365
+ });
+}
+
+function saveHistoryToServer(notehistory) {
+ $.post('/history', {
+ history: JSON.stringify(notehistory)
+ });
+}
+
+function saveCookieHistoryToServer(callback) {
+ $.post('/history', {
+ history: $.cookie('notehistory')
+ })
+ .done(function (data) {
+ callback();
+ });
+}
+
+function clearDuplicatedHistory(notehistory) {
+ var newnotehistory = [];
+ for (var i = 0; i < notehistory.length; i++) {
+ var found = false;
+ for (var j = 0; j < newnotehistory.length; j++) {
+ if (notehistory[i].id == newnotehistory[j].id) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ newnotehistory.push(notehistory[i]);
+ }
+ return notehistory;
+}
+
+function addHistory(id, text, time, tags, notehistory) {
+ notehistory.push({
+ id: id,
+ text: text,
+ time: time,
+ tags: tags
+ });
+ return notehistory;
+}
+
+function removeHistory(id, notehistory) {
+ for (var i = 0; i < notehistory.length; i++) {
+ if (notehistory[i].id == id)
+ notehistory.splice(i, 1);
+ }
+ return notehistory;
+}
+
+//used for inner
+function writeHistory(view) {
+ checkIfAuth(
+ function () {
+ writeHistoryToServer(view);
+ },
+ function () {
+ writeHistoryToCookie(view);
+ }
+ );
+}
+
+function writeHistoryToServer(view) {
+ $.get('/history')
+ .done(function (data) {
+ try {
+ if (data.history) {
+ var notehistory = data.history;
+ } else {
+ var notehistory = [];
+ }
+ } catch (err) {
+ var notehistory = [];
+ }
+ var newnotehistory = generateHistory(view, notehistory);
+ saveHistoryToServer(newnotehistory);
+ })
+ .fail(function () {
+ writeHistoryToCookie(view);
+ });
+}
+
+function writeHistoryToCookie(view) {
+ try {
+ var notehistory = JSON.parse($.cookie('notehistory'));
+ } catch (err) {
+ var notehistory = [];
+ }
+
+ var newnotehistory = generateHistory(view, notehistory);
+ saveHistoryToCookie(newnotehistory);
+}
+
+function renderHistory(view) {
+ var title = renderFilename(view);
+
+ var tags = [];
+ var rawtags = [];
+ 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++)
+ rawtags.push(codes[i]);
+ }
+ });
+ for (var i = 0; i < rawtags.length; i++) {
+ var found = false;
+ for (var j = 0; j < tags.length; j++) {
+ if (tags[j] == rawtags[i].innerHTML) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ tags.push(rawtags[i].innerHTML);
+ }
+ //console.debug(tags);
+ return {
+ id: location.pathname.split('/')[1],
+ text: title,
+ time: moment().format('MMMM Do YYYY, h:mm:ss a'),
+ tags: tags
+ };
+}
+
+function generateHistory(view, notehistory) {
+ var info = renderHistory(view);
+ notehistory = clearDuplicatedHistory(notehistory);
+ notehistory = removeHistory(info.id, notehistory);
+ notehistory = addHistory(info.id, info.text, info.time, info.tags, notehistory);
+ return notehistory;
+}
+
+//used for outer
+function getHistory(callback) {
+ checkIfAuth(
+ function () {
+ getServerHistory(callback);
+ },
+ function () {
+ getCookieHistory(callback);
+ }
+ );
+}
+
+function getServerHistory(callback) {
+ $.get('/history')
+ .done(function (data) {
+ if (data.history) {
+ callback(data.history);
+ }
+ })
+ .fail(function () {
+ getCookieHistory(callback);
+ });
+}
+
+function getCookieHistory(callback) {
+ callback(JSON.parse($.cookie('notehistory')));
+}
+
+function parseHistory(callback) {
+ checkIfAuth(
+ function () {
+ parseServerToHistory(callback);
+ },
+ function () {
+ parseCookieToHistory(callback);
+ }
+ );
+}
+
+function parseServerToHistory(callback) {
+ $.get('/history')
+ .done(function (data) {
+ if (data.history) {
+ //console.log(data.history);
+ parseToHistory(data.history, callback);
+ }
+ })
+ .fail(function () {
+ parseCookieToHistory(callback);
+ });
+}
+
+function parseCookieToHistory(callback) {
+ var notehistory = JSON.parse($.cookie('notehistory'));
+ parseToHistory(notehistory, callback);
+}
+
+function parseToHistory(notehistory, callback) {
+ if (notehistory && notehistory.length > 0) {
+ //console.log(notehistory);
+ for (var i = 0; i < notehistory.length; i++) {
+ notehistory[i].timestamp = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').unix();
+ notehistory[i].fromNow = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').fromNow();
+ }
+ $(notehistory).each(function (key, value) {
+ var close = "<div class='ui-history-close fa fa-close fa-fw'></div>";
+ var text = "<h4 class='text'>" + value.text + "</h2>";
+ var timestamp = "<i class='timestamp' style='display:none;'>" + value.timestamp + "</i>";
+ var fromNow = "<i class='fromNow'><i class='fa fa-clock-o'></i> " + value.fromNow + "</i>";
+ var time = "<i class='time'>" + value.time + "</i>";
+ var tags = "";
+ if (value.tags) {
+ var labels = [];
+ for (var j = 0; j < value.tags.length; j++)
+ labels.push("<span class='label label-default'>" + value.tags[j] + "</span>");
+ tags = "<p class='tags'>" + labels.join(" ") + "</p>";
+ }
+ var li = "<li class='col-xs-12 col-sm-6 col-md-6 col-lg-6'><a href='" + "./" + value.id + "'><div class='item'>" + close + text + '<p>' + fromNow + '<br>' + timestamp + time + '</p>' + tags + "</div></a></li>"
+ //console.debug(li);
+ $("#history-list").append(li);
+ });
+ }
+
+ var options = {
+ valueNames: ['text', 'timestamp', 'fromNow', 'time', 'tags']
+ };
+ var historyList = new List('history', options);
+ historyList.sort('timestamp', {
+ order: "desc"
+ });
+ callback();
+} \ No newline at end of file
diff --git a/public/js/index.js b/public/js/index.js
new file mode 100644
index 00000000..73b4e594
--- /dev/null
+++ b/public/js/index.js
@@ -0,0 +1,940 @@
+//constant vars
+//settings
+var debug = false;
+var version = '0.2.7';
+var doneTypingDelay = 400;
+var finishChangeDelay = 400;
+var cursorActivityDelay = 50;
+var syncScrollDelay = 50;
+var scrollAnimatePeriod = 100;
+var cursorAnimatePeriod = 100;
+var modeType = {
+ edit: {},
+ view: {},
+ both: {}
+}
+var statusType = {
+ connected: {
+ msg: "CONNECTED",
+ label: "label-warning",
+ fa: "fa-wifi"
+ },
+ online: {
+ msg: "ONLINE: ",
+ label: "label-primary",
+ fa: "fa-users"
+ },
+ offline: {
+ msg: "OFFLINE",
+ label: "label-danger",
+ fa: "fa-plug"
+ }
+}
+var defaultMode = modeType.both;
+
+//global vars
+var loaded = false;
+var isDirty = false;
+var editShown = false;
+var visibleXS = false;
+var visibleSM = false;
+var visibleMD = false;
+var visibleLG = false;
+var isTouchDevice = 'ontouchstart' in document.documentElement;
+var currentMode = defaultMode;
+var currentStatus = statusType.offline;
+var lastInfo = {
+ needRestore: false,
+ cursor: null,
+ scroll: null,
+ edit: {
+ scroll: {
+ left: null,
+ top: null
+ },
+ cursor: {
+ line: null,
+ ch: null
+ }
+ },
+ view: {
+ scroll: {
+ left: null,
+ top: null
+ }
+ },
+ history: null
+};
+
+//editor settings
+var editor = CodeMirror.fromTextArea(document.getElementById("textit"), {
+ mode: 'gfm',
+ viewportMargin: 20,
+ styleActiveLine: true,
+ lineNumbers: true,
+ lineWrapping: true,
+ theme: "monokai",
+ autofocus: true,
+ inputStyle: "textarea",
+ matchBrackets: true,
+ autoCloseBrackets: true,
+ matchTags: {
+ bothTags: true
+ },
+ autoCloseTags: true,
+ foldGutter: true,
+ gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
+ extraKeys: {
+ "Enter": "newlineAndIndentContinueMarkdownList"
+ },
+ readOnly: true
+});
+
+//ui vars
+var ui = {
+ spinner: $(".ui-spinner"),
+ content: $(".ui-content"),
+ toolbar: {
+ shortStatus: $(".ui-short-status"),
+ status: $(".ui-status"),
+ new: $(".ui-new"),
+ pretty: $(".ui-pretty"),
+ download: {
+ markdown: $(".ui-download-markdown")
+ },
+ save: {
+ dropbox: $(".ui-save-dropbox")
+ },
+ import: {
+ dropbox: $(".ui-import-dropbox"),
+ clipboard: $(".ui-import-clipboard")
+ },
+ mode: $(".ui-mode"),
+ edit: $(".ui-edit"),
+ view: $(".ui-view"),
+ both: $(".ui-both")
+ },
+ area: {
+ edit: $(".ui-edit-area"),
+ view: $(".ui-view-area"),
+ codemirror: $(".ui-edit-area .CodeMirror"),
+ markdown: $(".ui-view-area .markdown-body")
+ }
+};
+
+//page actions
+var opts = {
+ lines: 11, // The number of lines to draw
+ length: 20, // The length of each line
+ width: 2, // The line thickness
+ radius: 30, // The radius of the inner circle
+ corners: 0, // Corner roundness (0..1)
+ rotate: 0, // The rotation offset
+ direction: 1, // 1: clockwise, -1: counterclockwise
+ color: '#000', // #rgb or #rrggbb or array of colors
+ speed: 1.1, // Rounds per second
+ trail: 60, // Afterglow percentage
+ shadow: false, // Whether to render a shadow
+ hwaccel: true, // Whether to use hardware acceleration
+ className: 'spinner', // The CSS class to assign to the spinner
+ zIndex: 2e9, // The z-index (defaults to 2000000000)
+ top: '50%', // Top position relative to parent
+ left: '50%' // Left position relative to parent
+};
+var spinner = new Spinner(opts).spin(ui.spinner[0]);
+//when page ready
+$(document).ready(function () {
+ checkResponsive();
+ changeMode(currentMode);
+ /* we need this only on touch devices */
+ if (isTouchDevice) {
+ /* cache dom references */
+ var $body = jQuery('body');
+
+ /* bind events */
+ $(document)
+ .on('focus', 'textarea, input', function() {
+ $body.addClass('fixfixed');
+ })
+ .on('blur', 'textarea, input', function() {
+ $body.removeClass('fixfixed');
+ });
+ }
+});
+//when page resize
+$(window).resize(function () {
+ checkResponsive();
+});
+//768-792px have a gap
+function checkResponsive() {
+ visibleXS = $(".visible-xs").is(":visible");
+ visibleSM = $(".visible-sm").is(":visible");
+ visibleMD = $(".visible-md").is(":visible");
+ visibleLG = $(".visible-lg").is(":visible");
+ if (visibleXS && currentMode == modeType.both)
+ if (editor.hasFocus())
+ changeMode(modeType.edit);
+ else
+ changeMode(modeType.view);
+}
+
+function showStatus(type, num) {
+ currentStatus = type;
+ var shortStatus = ui.toolbar.shortStatus;
+ var status = ui.toolbar.status;
+ var label = $('<span class="label"></span>');
+ var fa = $('<i class="fa"></i>');
+ var msg = "";
+ var shortMsg = "";
+
+ shortStatus.html("");
+ status.html("");
+
+ switch (currentStatus) {
+ case statusType.connected:
+ label.addClass(statusType.connected.label);
+ fa.addClass(statusType.connected.fa);
+ msg = statusType.connected.msg;
+ break;
+ case statusType.online:
+ label.addClass(statusType.online.label);
+ fa.addClass(statusType.online.fa);
+ shortMsg = " " + num;
+ msg = statusType.online.msg + num;
+ break;
+ case statusType.offline:
+ label.addClass(statusType.offline.label);
+ fa.addClass(statusType.offline.fa);
+ msg = statusType.offline.msg;
+ break;
+ }
+
+ label.append(fa);
+ var shortLabel = label.clone();
+
+ shortLabel.append(" " + shortMsg);
+ shortStatus.append(shortLabel);
+
+ label.append(" " + msg);
+ status.append(label);
+}
+
+function toggleMode() {
+ switch(currentMode) {
+ case modeType.edit:
+ changeMode(modeType.view);
+ break;
+ case modeType.view:
+ changeMode(modeType.edit);
+ break;
+ case modeType.both:
+ changeMode(modeType.view);
+ break;
+ }
+}
+
+function changeMode(type) {
+ saveInfo();
+ if (type)
+ currentMode = type;
+ var responsiveClass = "col-lg-6 col-md-6 col-sm-6";
+ var scrollClass = "ui-scrollable";
+ ui.area.codemirror.removeClass(scrollClass);
+ ui.area.edit.removeClass(responsiveClass);
+ ui.area.view.removeClass(scrollClass);
+ ui.area.view.removeClass(responsiveClass);
+ switch (currentMode) {
+ case modeType.edit:
+ ui.area.edit.show();
+ ui.area.view.hide();
+ if (!editShown) {
+ editor.refresh();
+ editShown = true;
+ }
+ break;
+ case modeType.view:
+ ui.area.edit.hide();
+ ui.area.view.show();
+ break;
+ case modeType.both:
+ ui.area.codemirror.addClass(scrollClass);
+ ui.area.edit.addClass(responsiveClass).show();
+ ui.area.view.addClass(scrollClass);
+ ui.area.view.addClass(responsiveClass).show();
+ break;
+ }
+ if (currentMode != modeType.view && visibleLG) {
+ editor.focus();
+ editor.refresh();
+ } else {
+ editor.getInputField().blur();
+ }
+ if (changeMode != modeType.edit)
+ updateView();
+ restoreInfo();
+
+ ui.toolbar.both.removeClass("active");
+ ui.toolbar.edit.removeClass("active");
+ ui.toolbar.view.removeClass("active");
+ var modeIcon = ui.toolbar.mode.find('i');
+ modeIcon.removeClass('fa-toggle-on').removeClass('fa-toggle-off');
+ if (ui.area.edit.is(":visible") && ui.area.view.is(":visible")) { //both
+ ui.toolbar.both.addClass("active");
+ modeIcon.addClass('fa-eye');
+ } else if (ui.area.edit.is(":visible")) { //edit
+ ui.toolbar.edit.addClass("active");
+ modeIcon.addClass('fa-toggle-off');
+ } else if (ui.area.view.is(":visible")) { //view
+ ui.toolbar.view.addClass("active");
+ modeIcon.addClass('fa-toggle-on');
+ }
+}
+
+//button actions
+var noteId = window.location.pathname.split('/')[1];
+var url = window.location.origin + '/' + noteId;
+//pretty
+ui.toolbar.pretty.attr("href", url + "/pretty");
+//download
+//markdown
+ui.toolbar.download.markdown.click(function() {
+ var filename = renderFilename(ui.area.markdown) + '.md';
+ var markdown = editor.getValue();
+ var blob = new Blob([markdown], {type: "text/markdown;charset=utf-8"});
+ saveAs(blob, filename);
+});
+//save to dropbox
+ui.toolbar.save.dropbox.click(function() {
+ var filename = renderFilename(ui.area.markdown) + '.md';
+ var options = {
+ files: [
+ {'url': url + "/download", 'filename': filename}
+ ]
+ };
+ Dropbox.save(options);
+});
+//import from dropbox
+ui.toolbar.import.dropbox.click(function() {
+ var options = {
+ success: function(files) {
+ ui.spinner.show();
+ var url = files[0].link;
+ importFromUrl(url);
+ },
+ linkType: "direct",
+ multiselect: false,
+ extensions: ['.md', '.html']
+ };
+ Dropbox.choose(options);
+});
+//import from clipboard
+ui.toolbar.import.clipboard.click(function() {
+ //na
+});
+//fix for wrong autofocus
+$('#clipboardModal').on('shown.bs.modal', function() {
+ $('#clipboardModal').blur();
+});
+$("#clipboardModalClear").click(function() {
+ $("#clipboardModalContent").html('');
+});
+$("#clipboardModalConfirm").click(function() {
+ var data = $("#clipboardModalContent").html();
+ if(data) {
+ parseToEditor(data);
+ $('#clipboardModal').modal('hide');
+ $("#clipboardModalContent").html('');
+ }
+});
+function parseToEditor(data) {
+ var parsed = toMarkdown(data);
+ if(parsed)
+ editor.replaceRange(parsed, {line:0, ch:0}, {line:editor.lastLine(), ch:editor.lastLine().length}, '+input');
+}
+function importFromUrl(url) {
+ //console.log(url);
+ if(url == null) return;
+ if(!isValidURL(url)) {
+ alert('Not valid URL :(');
+ return;
+ }
+ $.ajax({
+ method: "GET",
+ url: url,
+ success: function(data) {
+ parseToEditor(data);
+ },
+ error: function() {
+ alert('Import failed :(');
+ },
+ complete: function() {
+ ui.spinner.hide();
+ }
+ });
+}
+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;
+ }
+}
+//mode
+ui.toolbar.mode.click(function () {
+ toggleMode();
+});
+//edit
+ui.toolbar.edit.click(function () {
+ changeMode(modeType.edit);
+});
+//view
+ui.toolbar.view.click(function () {
+ changeMode(modeType.view);
+});
+//both
+ui.toolbar.both.click(function () {
+ changeMode(modeType.both);
+});
+
+//socket.io actions
+var socket = io.connect();
+socket.on('info', function (data) {
+ console.error(data);
+ location.href = "./404.html";
+});
+socket.on('disconnect', function (data) {
+ showStatus(statusType.offline);
+ if (loaded) {
+ saveInfo();
+ lastInfo.history = editor.getHistory();
+ }
+ if (!editor.getOption('readOnly'))
+ editor.setOption('readOnly', true);
+});
+socket.on('connect', function (data) {
+ showStatus(statusType.connected);
+ socket.emit('version');
+});
+socket.on('version', function (data) {
+ if (data != version)
+ location.reload(true);
+});
+socket.on('refresh', function (data) {
+ saveInfo();
+
+ var body = data.body;
+ body = LZString.decompressFromBase64(body);
+ if (body)
+ editor.setValue(body);
+ else
+ editor.setValue("");
+
+ if (!loaded) {
+ editor.clearHistory();
+ ui.spinner.hide();
+ ui.content.fadeIn();
+ changeMode();
+ loaded = true;
+ } else {
+ if (LZString.compressToBase64(editor.getValue()) !== data.body)
+ editor.clearHistory();
+ else {
+ if (lastInfo.history)
+ editor.setHistory(lastInfo.history);
+ }
+ lastInfo.history = null;
+ }
+
+ updateView();
+
+ if (editor.getOption('readOnly'))
+ editor.setOption('readOnly', false);
+
+ restoreInfo();
+});
+socket.on('change', function (data) {
+ data = LZString.decompressFromBase64(data);
+ data = JSON.parse(data);
+ editor.replaceRange(data.text, data.from, data.to, "ignoreHistory");
+ isDirty = true;
+ clearTimeout(finishChangeTimer);
+ finishChangeTimer = setTimeout(finishChange, finishChangeDelay);
+});
+socket.on('online users', function (data) {
+ if (debug)
+ console.debug(data);
+ showStatus(statusType.online, data.count);
+ $('.other-cursors').html('');
+ for(var i = 0; i < data.users.length; i++) {
+ var user = data.users[i];
+ if(user.id != socket.id)
+ buildCursor(user.id, user.color, user.cursor);
+ }
+});
+socket.on('cursor focus', function (data) {
+ if(debug)
+ console.debug(data);
+ var cursor = $('#' + data.id);
+ if(cursor.length > 0) {
+ cursor.fadeIn();
+ } else {
+ if(data.id != socket.id)
+ buildCursor(data.id, data.color, data.cursor);
+ }
+});
+socket.on('cursor activity', function (data) {
+ if(debug)
+ console.debug(data);
+ if(data.id != socket.id)
+ buildCursor(data.id, data.color, data.cursor);
+});
+socket.on('cursor blur', function (data) {
+ if(debug)
+ console.debug(data);
+ var cursor = $('#' + data.id);
+ if(cursor.length > 0) {
+ cursor.fadeOut();
+ }
+});
+function emitUserStatus() {
+ checkIfAuth(
+ function (data) {
+ socket.emit('user status', {login:true});
+ },
+ function () {
+ socket.emit('user status', {login:false});
+ }
+ );
+}
+
+function buildCursor(id, color, pos) {
+ if(!pos) return;
+ if ($('.other-cursors').length <= 0) {
+ $("<div class='other-cursors'>").insertAfter('.CodeMirror-cursors');
+ }
+ if ($('#' + id).length <= 0) {
+ var cursor = $('<div id="' + id + '" class="other-cursor">&nbsp;</div>');
+ //console.debug(pos);
+ cursor.attr('data-line', pos.line);
+ cursor.attr('data-ch', pos.ch);
+ var coord = editor.charCoords(pos, 'windows');
+ cursor[0].style.left = coord.left + 'px';
+ cursor[0].style.top = coord.top + 'px';
+ cursor[0].style.height = '18px';
+ cursor[0].style.borderLeft = '2px solid ' + color;
+ $('.other-cursors').append(cursor);
+ cursor.hide().fadeIn();
+ } else {
+ var cursor = $('#' + id);
+ cursor.attr('data-line', pos.line);
+ cursor.attr('data-ch', pos.ch);
+ var coord = editor.charCoords(pos, 'windows');
+ cursor.stop(true).css('opacity', 1).animate({"left":coord.left, "top":coord.top}, cursorAnimatePeriod);
+ //cursor[0].style.left = coord.left + 'px';
+ //cursor[0].style.top = coord.top + 'px';
+ cursor[0].style.height = '18px';
+ cursor[0].style.borderLeft = '2px solid ' + color;
+ }
+}
+
+//editor actions
+editor.on('beforeChange', function (cm, change) {
+ if (debug)
+ console.debug(change);
+});
+editor.on('change', function (i, op) {
+ if (debug)
+ console.debug(op);
+ if (op.origin != 'setValue' && op.origin != 'ignoreHistory') {
+ socket.emit('change', LZString.compressToBase64(JSON.stringify(op)));
+ }
+ isDirty = true;
+ clearTimeout(doneTypingTimer);
+ doneTypingTimer = setTimeout(doneTyping, doneTypingDelay);
+});
+editor.on('focus', function (cm) {
+ socket.emit('cursor focus', editor.getCursor());
+});
+var cursorActivityTimer = null;
+editor.on('cursorActivity', function (cm) {
+ clearTimeout(cursorActivityTimer);
+ cursorActivityTimer = setTimeout(cursorActivity, cursorActivityDelay);
+});
+function cursorActivity() {
+ socket.emit('cursor activity', editor.getCursor());
+}
+editor.on('blur', function (cm) {
+ socket.emit('cursor blur');
+});
+
+function saveInfo() {
+ var left = $(document.body).scrollLeft();
+ var top = $(document.body).scrollTop();
+ switch (currentMode) {
+ case modeType.edit:
+ lastInfo.edit.scroll.left = left;
+ lastInfo.edit.scroll.top = top;
+ break;
+ case modeType.view:
+ lastInfo.view.scroll.left = left;
+ lastInfo.view.scroll.top = top;
+ break;
+ case modeType.both:
+ lastInfo.edit.scroll = editor.getScrollInfo();
+ lastInfo.view.scroll.left = ui.area.view.scrollLeft();
+ lastInfo.view.scroll.top = ui.area.view.scrollTop();
+ break;
+ }
+ lastInfo.edit.cursor = editor.getCursor();
+ lastInfo.needRestore = true;
+}
+
+function restoreInfo() {
+ if (lastInfo.needRestore) {
+ var line = lastInfo.edit.cursor.line;
+ var ch = lastInfo.edit.cursor.ch;
+ editor.setCursor(line, ch);
+
+ switch (currentMode) {
+ case modeType.edit:
+ $(document.body).scrollLeft(lastInfo.edit.scroll.left);
+ $(document.body).scrollTop(lastInfo.edit.scroll.top);
+ break;
+ case modeType.view:
+ $(document.body).scrollLeft(lastInfo.view.scroll.left);
+ $(document.body).scrollTop(lastInfo.view.scroll.top);
+ break;
+ case modeType.both:
+ var left = lastInfo.edit.scroll.left;
+ var top = lastInfo.edit.scroll.top;
+ editor.scrollIntoView();
+ editor.scrollTo(left, top);
+ ui.area.view.scrollLeft(lastInfo.view.scroll.left);
+ ui.area.view.scrollTop(lastInfo.view.scroll.top);
+ break;
+ }
+
+ lastInfo.needRestore = false;
+ }
+}
+
+//view actions
+var doneTypingTimer = null;
+var finishChangeTimer = null;
+var input = editor.getInputField();
+//user is "finished typing," do something
+function doneTyping() {
+ updateView();
+ var value = editor.getValue();
+ socket.emit('refresh', LZString.compressToBase64(value));
+}
+
+function finishChange() {
+ updateView();
+}
+
+var lastResult = null;
+
+function updateView() {
+ if (currentMode == modeType.edit || !isDirty) return;
+ var value = editor.getValue();
+ var result = postProcess(md.render(value)).children().toArray();
+ //ui.area.markdown.html(result);
+ //finishView(ui.area.markdown);
+ partialUpdate(result, lastResult, ui.area.markdown.children().toArray());
+ lastResult = $(result).clone(true);
+ finishView(ui.area.view);
+ writeHistory(ui.area.markdown);
+ isDirty = false;
+ // reset lines mapping cache on content update
+ scrollMap = null;
+ emitUserStatus();
+}
+
+function partialUpdate(src, tar, des) {
+ if (!src || src.length == 0 || !tar || tar.length == 0 || !des || des.length == 0) {
+ ui.area.markdown.html(src);
+ return;
+ }
+ if (src.length == tar.length) { //same length
+ for (var i = 0; i < src.length; i++) {
+ copyAttribute(src[i], des[i], 'data-startline');
+ copyAttribute(src[i], des[i], 'data-endline');
+ var rawSrc = cloneAndRemoveDataAttr(src[i]);
+ var rawTar = cloneAndRemoveDataAttr(tar[i]);
+ if (rawSrc.outerHTML != rawTar.outerHTML) {
+ //console.log(rawSrc);
+ //console.log(rawTar);
+ $(des[i]).replaceWith(src[i]);
+ }
+ }
+ } else { //diff length
+ var start = 0;
+ var end = 0;
+ //find diff start position
+ for (var i = 0; i < tar.length; i++) {
+ copyAttribute(src[i], des[i], 'data-startline');
+ copyAttribute(src[i], des[i], 'data-endline');
+ var rawSrc = cloneAndRemoveDataAttr(src[i]);
+ var rawTar = cloneAndRemoveDataAttr(tar[i]);
+ if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
+ start = i;
+ break;
+ }
+ }
+ //find diff end position
+ var srcEnd = 0;
+ var tarEnd = 0;
+ for (var i = 0; i < src.length; i++) {
+ copyAttribute(src[i], des[i], 'data-startline');
+ copyAttribute(src[i], des[i], 'data-endline');
+ var rawSrc = cloneAndRemoveDataAttr(src[i]);
+ var rawTar = cloneAndRemoveDataAttr(tar[i]);
+ if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
+ start = i;
+ break;
+ }
+ }
+ //tar end
+ for (var i = 1; i <= tar.length; i++) {
+ var srcLength = src.length;
+ var tarLength = tar.length;
+ copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
+ copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
+ var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
+ var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
+ if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
+ tarEnd = tar.length - i;
+ break;
+ }
+ }
+ //src end
+ for (var i = 1; i <= src.length; i++) {
+ var srcLength = src.length;
+ var tarLength = tar.length;
+ copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
+ copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
+ var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
+ var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
+ if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
+ srcEnd = src.length - i;
+ break;
+ }
+ }
+ //check if tar end overlap tar start
+ var overlap = 0;
+ for (var i = start; i >= 0; i--) {
+ var rawTarStart = cloneAndRemoveDataAttr(tar[i-1]);
+ var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd+1+start-i]);
+ if(rawTarStart && rawTarEnd && rawTarStart.outerHTML == rawTarEnd.outerHTML)
+ overlap++;
+ else
+ break;
+ }
+ if(debug)
+ console.log('overlap:' + overlap);
+ //show diff content
+ if(debug) {
+ console.log('start:' + start);
+ console.log('tarEnd:' + tarEnd);
+ console.log('srcEnd:' + srcEnd);
+ console.log('des[start]:' + des[start]);
+ }
+ tarEnd += overlap;
+ srcEnd += overlap;
+ //add new element
+ var newElements = "";
+ for (var j = start; j <= srcEnd; j++) {
+ if(debug)
+ srcChanged += src[j].outerHTML;
+ newElements += src[j].outerHTML;
+ }
+ if(newElements && des[start]) {
+ $(newElements).insertBefore(des[start]);
+ } else {
+ $(newElements).insertAfter(des[des.length-1]);
+ }
+ if(debug)
+ console.log(srcChanged);
+ //remove old element
+ if(debug)
+ var tarChanged = "";
+ for (var j = start; j <= tarEnd; j++) {
+ if(debug)
+ tarChanged += tar[j].outerHTML;
+ if(des[j])
+ des[j].remove();
+ }
+ if(debug) {
+ console.log(tarChanged);
+ var srcChanged = "";
+ }
+ }
+}
+
+function cloneAndRemoveDataAttr(el) {
+ if(!el) return;
+ var rawEl = $(el).clone(true)[0];
+ rawEl.removeAttribute('data-startline');
+ rawEl.removeAttribute('data-endline');
+ return rawEl;
+}
+
+function copyAttribute(src, des, attr) {
+ if (src && src.getAttribute(attr) && des)
+ des.setAttribute(attr, src.getAttribute(attr));
+}
+
+//
+// Inject line numbers for sync scroll. Notes:
+//
+// - We track only headings and paragraphs on first level. That's enougth.
+// - Footnotes content causes jumps. Level limit filter it automatically.
+//
+md.renderer.rules.paragraph_open = function (tokens, idx) {
+ var line;
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<p class="part" data-startline="' + startline + '" data-endline="' + endline + '">';
+ }
+ return '';
+};
+
+md.renderer.rules.heading_open = function (tokens, idx) {
+ var line;
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<h' + tokens[idx].hLevel + ' class="part" data-startline="' + startline + '" data-endline="' + endline + '">';
+ }
+ return '<h' + tokens[idx].hLevel + '>';
+};
+
+editor.on('scroll', _.debounce(syncScrollToView, syncScrollDelay));
+//ui.area.view.on('scroll', _.debounce(syncScrollToEdit, 50));
+var scrollMap;
+// Build offsets for each line (lines can be wrapped)
+// That's a bit dirty to process each line everytime, but ok for demo.
+// Optimizations are required only for big texts.
+function buildScrollMap() {
+ var i, offset, nonEmptyList, pos, a, b, lineHeightMap, linesCount,
+ acc, sourceLikeDiv, textarea = ui.area.codemirror,
+ _scrollMap;
+
+ sourceLikeDiv = $('<div />').css({
+ position: 'absolute',
+ visibility: 'hidden',
+ height: 'auto',
+ width: editor.getScrollInfo().clientWidth,
+ 'font-size': textarea.css('font-size'),
+ 'font-family': textarea.css('font-family'),
+ 'line-height': textarea.css('line-height'),
+ 'white-space': textarea.css('white-space')
+ }).appendTo('body');
+
+ offset = ui.area.view.scrollTop() - ui.area.view.offset().top;
+ _scrollMap = [];
+ nonEmptyList = [];
+ lineHeightMap = [];
+
+ acc = 0;
+ editor.getValue().split('\n').forEach(function (str) {
+ var h, lh;
+
+ lineHeightMap.push(acc);
+
+ if (str.length === 0) {
+ acc++;
+ return;
+ }
+
+ sourceLikeDiv.text(str);
+ h = parseFloat(sourceLikeDiv.css('height'));
+ lh = parseFloat(sourceLikeDiv.css('line-height'));
+ acc += Math.round(h / lh);
+ });
+ sourceLikeDiv.remove();
+ lineHeightMap.push(acc);
+ linesCount = acc;
+
+ for (i = 0; i < linesCount; i++) {
+ _scrollMap.push(-1);
+ }
+
+ nonEmptyList.push(0);
+ _scrollMap[0] = 0;
+
+ ui.area.markdown.find('.part').each(function (n, el) {
+ var $el = $(el),
+ t = $el.data('startline');
+ if (t === '') {
+ return;
+ }
+ t = lineHeightMap[t];
+ if (t !== 0) {
+ nonEmptyList.push(t);
+ }
+ _scrollMap[t] = Math.round($el.offset().top + offset);
+ });
+
+ nonEmptyList.push(linesCount);
+ _scrollMap[linesCount] = ui.area.view[0].scrollHeight;
+
+ pos = 0;
+ for (i = 1; i < linesCount; i++) {
+ if (_scrollMap[i] !== -1) {
+ pos++;
+ continue;
+ }
+
+ a = nonEmptyList[pos];
+ b = nonEmptyList[pos + 1];
+ _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a));
+ }
+
+ return _scrollMap;
+}
+
+function syncScrollToView() {
+ var lineNo, posTo;
+ var scrollInfo = editor.getScrollInfo();
+ if (!scrollMap) {
+ scrollMap = buildScrollMap();
+ }
+ lineNo = Math.floor(scrollInfo.top / editor.defaultTextHeight());
+ posTo = scrollMap[lineNo];
+ ui.area.view.stop(true).animate({scrollTop: posTo}, scrollAnimatePeriod);
+}
+
+function syncScrollToEdit() {
+ var lineNo, posTo;
+ if (!scrollMap) {
+ scrollMap = buildScrollMap();
+ }
+ var top = ui.area.view.scrollTop();
+ lineNo = closestIndex(top, scrollMap);
+ posTo = lineNo * editor.defaultTextHeight();
+ editor.scrollTo(0, posTo);
+}
+
+function closestIndex(num, arr) {
+ var curr = arr[0];
+ var index = 0;
+ var diff = Math.abs(num - curr);
+ for (var val = 0; val < arr.length; val++) {
+ var newdiff = Math.abs(num - arr[val]);
+ if (newdiff < diff) {
+ diff = newdiff;
+ curr = arr[val];
+ index = val;
+ }
+ }
+ return index;
+} \ No newline at end of file
diff --git a/public/js/unused.js b/public/js/unused.js
new file mode 100644
index 00000000..4ff5b280
--- /dev/null
+++ b/public/js/unused.js
@@ -0,0 +1,45 @@
+
+ //parse Youtube
+ result.find(".youtube").each(function (key, value) {
+ if (!$(value).attr('videoid')) return;
+ setSizebyAttr(this, this);
+ var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
+ $(this).append(icon);
+ var videoid = $(value).attr('videoid');
+ var thumbnail_src = '//img.youtube.com/vi/' + videoid + '/hqdefault.jpg';
+ $(value).css('background-image', 'url(' + thumbnail_src + ')');
+ $(this).click(function () {
+ imgPlayiframe(this, '//www.youtube.com/embed/');
+ });
+ });
+ //parse vimeo
+ result.find(".vimeo").each(function (key, value) {
+ if (!$(value).attr('videoid')) return;
+ setSizebyAttr(this, this);
+ var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
+ $(this).append(icon);
+ var videoid = $(value).attr('videoid');
+ $.ajax({
+ type: 'GET',
+ url: 'http://vimeo.com/api/v2/video/' + videoid + '.json',
+ jsonp: 'callback',
+ dataType: 'jsonp',
+ success: function (data) {
+ var thumbnail_src = data[0].thumbnail_large;
+ $(value).css('background-image', 'url(' + thumbnail_src + ')');
+ }
+ });
+ $(this).click(function () {
+ imgPlayiframe(this, '//player.vimeo.com/video/');
+ });
+ });
+ //todo list
+ var lis = result[0].getElementsByTagName('li');
+ for (var i = 0; i < lis.length; i++) {
+ var html = lis[i].innerHTML;
+ if (/^\s*\[[x ]\]\s*/.test(html)) {
+ lis[i].innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled>')
+ .replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled>');
+ lis[i].setAttribute('class', 'task-list-item');
+ }
+ } \ No newline at end of file