summaryrefslogtreecommitdiff
path: root/public/js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js')
-rw-r--r--public/js/common.js53
-rw-r--r--public/js/cover.js245
-rw-r--r--public/js/extra.js6
-rw-r--r--public/js/fb.js8
-rw-r--r--public/js/ga.js14
-rw-r--r--public/js/history.js187
-rw-r--r--public/js/index.js419
-rw-r--r--public/js/pretty.js9
-rw-r--r--public/js/syncscroll.js327
9 files changed, 913 insertions, 355 deletions
diff --git a/public/js/common.js b/public/js/common.js
new file mode 100644
index 00000000..37591d36
--- /dev/null
+++ b/public/js/common.js
@@ -0,0 +1,53 @@
+//common
+var domain = 'change this';
+var checkAuth = false;
+var profile = null;
+var lastLoginState = getLoginState();
+var loginStateChangeEvent = null;
+
+function resetCheckAuth() {
+ checkAuth = false;
+}
+
+function setLoginState(bool) {
+ Cookies.set('loginstate', bool, {
+ expires: 14
+ });
+ if (loginStateChangeEvent && bool != lastLoginState)
+ loginStateChangeEvent();
+ lastLoginState = bool;
+}
+
+function getLoginState() {
+ return Cookies.get('loginstate') === "true";
+}
+
+function clearLoginState() {
+ Cookies.remove('loginstate');
+}
+
+function checkIfAuth(yesCallback, noCallback) {
+ var cookieLoginState = getLoginState();
+ if (!checkAuth || typeof cookieLoginState == 'undefined') {
+ $.get('/me')
+ .done(function (data) {
+ if (data && data.status == 'ok') {
+ profile = data;
+ yesCallback(profile);
+ setLoginState(true);
+ } else {
+ noCallback();
+ setLoginState(false);
+ }
+ })
+ .fail(function () {
+ noCallback();
+ setLoginState(false);
+ });
+ checkAuth = true;
+ } else if (cookieLoginState) {
+ yesCallback(profile);
+ } else {
+ noCallback();
+ }
+} \ No newline at end of file
diff --git a/public/js/cover.js b/public/js/cover.js
index fea51f6a..24ba605c 100644
--- a/public/js/cover.js
+++ b/public/js/cover.js
@@ -1,3 +1,47 @@
+var options = {
+ valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags'],
+ item: '<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">\
+ <span class="id" style="display:none;"></span>\
+ <a href="#">\
+ <div class="item">\
+ <div class="ui-history-close fa fa-close fa-fw"></div>\
+ <h4 class="text"></h4>\
+ <p><i class="fromNow"><i class="fa fa-clock-o"></i></i>\
+ <br>\
+ <i class="timestamp" style="display:none;"></i><i class="time"></i></p>\
+ <p class="tags"></p>\
+ </div>\
+ </a>\
+ </li>'
+};
+var historyList = new List('history', options);
+
+migrateHistoryFromTempCallback = pageInit;
+loginStateChangeEvent = pageInit;
+pageInit();
+
+function pageInit() {
+ checkIfAuth(
+ function (data) {
+ $('.ui-signin').hide();
+ $('.ui-or').hide();
+ $('.ui-welcome').show();
+ $('.ui-name').html(data.name);
+ $('.ui-signout').show();
+ $(".ui-history").click();
+ parseServerToHistory(historyList, parseHistoryCallback);
+ },
+ function () {
+ $('.ui-signin').slideDown();
+ $('.ui-or').slideDown();
+ $('.ui-welcome').hide();
+ $('.ui-name').html('');
+ $('.ui-signout').hide();
+ parseStorageToHistory(historyList, parseHistoryCallback);
+ }
+ );
+}
+
$(".masthead-nav li").click(function () {
$(this).siblings().removeClass("active");
$(this).addClass("active");
@@ -19,57 +63,166 @@ $(".ui-releasenotes").click(function () {
});
function checkHistoryList() {
- if ($("#history-list").children().length > 0)
+ if ($("#history-list").children().length > 0) {
$(".ui-nohistory").hide();
- else if ($("#history-list").children().length == 0) {
+ $(".ui-import-from-browser").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();
- }
+ getStorageHistory(function (data) {
+ if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) {
+ $(".ui-import-from-browser").slideDown();
+ }
+ });
}
}
-function parseHistoryCallback() {
+function parseHistoryCallback(list, notehistory) {
checkHistoryList();
+ list.sort('timestamp', {
+ order: "desc"
+ });
+ var filtertags = [];
+ $(".item").each(function (key, value) {
+ var a = $(this).closest("a");
+ var id = a.siblings("span").html();
+ var tagsEl = $(this).find(".tags");
+ var item = historyList.get('id', id);
+ if (item.length > 0 && item[0]) {
+ var values = item[0].values();
+ //parse link to element a
+ a.attr('href', '/' + values.id);
+ //parse tags
+ if (values.tags) {
+ var tags = values.tags;
+ if (tags.length > 0) {
+ var labels = [];
+ for (var j = 0; j < tags.length; j++) {
+ //push info filtertags if not found
+ var found = false;
+ if (filtertags.indexOf(tags[j]) != -1)
+ found = true;
+ if (!found)
+ filtertags.push(tags[j]);
+ //push into the item label
+ labels.push("<span class='label label-default'>" + tags[j] + "</span>");
+ }
+ tagsEl.html(labels.join(' '));
+ }
+ }
+ }
+ });
$(".ui-history-close").click(function (e) {
e.preventDefault();
- var id = $(this).closest("a").attr("href").split('/')[1];
+ var id = $(this).closest("a").siblings("span").html();
getHistory(function (notehistory) {
var newnotehistory = removeHistory(id, notehistory);
saveHistory(newnotehistory);
});
- $(this).closest("li").remove();
+ list.remove('id', id);
checkHistoryList();
});
+ buildTagsFilter(filtertags);
}
-var login = false;
+$(".ui-import-from-browser").click(function () {
+ saveStorageHistoryToServer(function () {
+ parseStorageToHistory(historyList, parseHistoryCallback);
+ });
+});
+
+$(".ui-save-history").click(function () {
+ getHistory(function (data) {
+ var history = JSON.stringify(data);
+ var blob = new Blob([history], {
+ type: "application/json;charset=utf-8"
+ });
+ saveAs(blob, 'hackmd_history_' + moment().format('YYYYMMDDHHmmss'));
+ });
+});
+
+$(".ui-open-history").bind("change", function (e) {
+ var files = e.target.files || e.dataTransfer.files;
+ var file = files[0];
+ var reader = new FileReader();
+ reader.onload = function () {
+ var notehistory = JSON.parse(reader.result);
+ //console.log(notehistory);
+ if (!reader.result) return;
+ getHistory(function (data) {
+ var mergedata = data.concat(notehistory);
+ mergedata = clearDuplicatedHistory(mergedata);
+ saveHistory(mergedata);
+ parseHistory(historyList, parseHistoryCallback);
+ });
+ $(".ui-open-history").replaceWith($(".ui-open-history").val('').clone(true));
+ };
+ reader.readAsText(file);
+});
+
+$(".ui-clear-history").click(function () {
+ saveHistory([]);
+ historyList.clear();
+ checkHistoryList();
+});
+
+$(".ui-refresh-history").click(function () {
+ resetCheckAuth();
+ historyList.clear();
+ parseHistory(historyList, parseHistoryCallback);
+});
+
+$(".ui-logout").click(function () {
+ clearLoginState();
+ location.href = '/logout';
+});
-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;
+var filtertags = [];
+$(".ui-use-tags").select2({
+ placeholder: 'Use tags...',
+ multiple: true,
+ data: function () {
+ return {
+ results: filtertags
+ };
}
-);
+});
+$('.select2-input').css('width', 'inherit');
+buildTagsFilter([]);
-parseHistory(parseHistoryCallback);
+function buildTagsFilter(tags) {
+ for (var i = 0; i < tags.length; i++)
+ tags[i] = {
+ id: i,
+ text: tags[i]
+ };
+ filtertags = tags;
+}
+$(".ui-use-tags").on('change', function () {
+ var tags = [];
+ var data = $(this).select2('data');
+ for (var i = 0; i < data.length; i++)
+ tags.push(data[i].text);
+ if (tags.length > 0) {
+ historyList.filter(function (item) {
+ var values = item.values();
+ if (!values.tags) return false;
+ var found = false;
+ for (var i = 0; i < tags.length; i++) {
+ if (values.tags.indexOf(tags[i]) != -1) {
+ found = true;
+ break;
+ }
+ }
+ return found;
+ });
+ } else {
+ historyList.filter();
+ }
+ checkHistoryList();
+});
-$(".ui-import-from-cookie").click(function () {
- saveCookieHistoryToServer(function() {
- parseCookieToHistory(parseHistoryCallback);
- $(".ui-import-from-cookie").hide();
- });
+$('.search').keyup(function () {
+ checkHistoryList();
});
var source = $("#template").html();
@@ -77,6 +230,36 @@ var template = Handlebars.compile(source);
var context = {
release: [
{
+ version: "0.2.8",
+ tag: "flame",
+ date: moment("201505151200", 'YYYYMMDDhhmm').fromNow(),
+ detail: [
+ {
+ title: "Features",
+ item: [
+ "+ Support drag-n-drop(exclude firefox) and paste image inline",
+ "+ Support tags filter in history",
+ "+ Support sublime-like shortcut keys"
+ ]
+ },
+ {
+ title: "Enhancements",
+ item: [
+ "* Adjust index description",
+ "* Adjust toolbar ui and view font",
+ "* Remove scroll sync delay and gain accuracy"
+ ]
+ },
+ {
+ title: "Fixes",
+ item: [
+ "* Partial update in the front and the end might not render properly",
+ "* Server not handle some editor events"
+ ]
+ }
+ ]
+ },
+ {
version: "0.2.7",
tag: "fuel",
date: moment("201505031200", 'YYYYMMDDhhmm').fromNow(),
diff --git a/public/js/extra.js b/public/js/extra.js
index 45833c89..05fa4704 100644
--- a/public/js/extra.js
+++ b/public/js/extra.js
@@ -28,6 +28,8 @@ function renderFilename(view) {
return filename;
}
+var viewAjaxCallback = null;
+
//dynamic event or object binding here
function finishView(view) {
//youtube
@@ -42,7 +44,7 @@ function finishView(view) {
.each(function (key, value) {
$.ajax({
type: 'GET',
- url: 'http://vimeo.com/api/v2/video/' + $(value).attr('videoid') + '.json',
+ url: '//vimeo.com/api/v2/video/' + $(value).attr('videoid') + '.json',
jsonp: 'callback',
dataType: 'jsonp',
success: function (data) {
@@ -54,7 +56,7 @@ function finishView(view) {
//gist
view.find("code[data-gist-id]").each(function(key, value) {
if($(value).children().length == 0)
- $(value).gist();
+ $(value).gist(viewAjaxCallback);
});
//emojify
emojify.run(view[0]);
diff --git a/public/js/fb.js b/public/js/fb.js
new file mode 100644
index 00000000..0bb7a466
--- /dev/null
+++ b/public/js/fb.js
@@ -0,0 +1,8 @@
+(function (d, s, id) {
+ var js, fjs = d.getElementsByTagName(s)[0];
+ if (d.getElementById(id)) return;
+ js = d.createElement(s);
+ js.id = id;
+ js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.3&appId=1436904003272070";
+ fjs.parentNode.insertBefore(js, fjs);
+}(document, 'script', 'facebook-jssdk')); \ No newline at end of file
diff --git a/public/js/ga.js b/public/js/ga.js
deleted file mode 100644
index 0fda2410..00000000
--- a/public/js/ga.js
+++ /dev/null
@@ -1,14 +0,0 @@
-(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
index c6deaa53..717a7ca4 100644
--- a/public/js/history.js
+++ b/public/js/history.js
@@ -1,16 +1,35 @@
-//common
-function checkIfAuth(yesCallback, noCallback) {
- $.get('/me')
- .done(function (data) {
- if (data && data.status == 'ok') {
- yesCallback(data);
- } else {
- noCallback();
- }
- })
- .fail(function () {
- noCallback();
- });
+var migrateHistoryFromTempCallback = null;
+
+migrateHistoryFromTemp();
+
+function migrateHistoryFromTemp() {
+ if (url('#tempid')) {
+ $.get('/temp', {
+ tempid: url('#tempid')
+ })
+ .done(function (data) {
+ if (data && data.temp) {
+ getStorageHistory(function (olddata) {
+ if (!olddata || olddata.length == 0) {
+ saveHistoryToStorage(JSON.parse(data.temp));
+ }
+ });
+ }
+ })
+ .always(function () {
+ var hash = location.hash.split('#')[1];
+ hash = hash.split('&');
+ for (var i = 0; i < hash.length; i++)
+ if (hash[i].indexOf('tempid') == 0) {
+ hash.splice(i, 1);
+ i--;
+ }
+ hash = hash.join('&');
+ location.hash = hash;
+ if (migrateHistoryFromTempCallback)
+ migrateHistoryFromTempCallback();
+ });
+ }
}
function saveHistory(notehistory) {
@@ -19,13 +38,20 @@ function saveHistory(notehistory) {
saveHistoryToServer(notehistory);
},
function () {
- saveHistoryToCookie(notehistory);
+ saveHistoryToStorage(notehistory);
}
);
}
+function saveHistoryToStorage(notehistory) {
+ if (store.enabled)
+ store.set('notehistory', JSON.stringify(notehistory));
+ else
+ saveHistoryToCookie(notehistory);
+}
+
function saveHistoryToCookie(notehistory) {
- $.cookie('notehistory', JSON.stringify(notehistory), {
+ Cookies.set('notehistory', notehistory, {
expires: 365
});
}
@@ -36,12 +62,29 @@ function saveHistoryToServer(notehistory) {
});
}
+function saveCookieHistoryToStorage(callback) {
+ store.set('notehistory', Cookies.get('notehistory'));
+ callback();
+}
+
+function saveStorageHistoryToServer(callback) {
+ var data = store.get('notehistory');
+ if (data) {
+ $.post('/history', {
+ history: data
+ })
+ .done(function (data) {
+ callback(data);
+ });
+ }
+}
+
function saveCookieHistoryToServer(callback) {
$.post('/history', {
- history: $.cookie('notehistory')
+ history: Cookies.get('notehistory')
})
.done(function (data) {
- callback();
+ callback(data);
});
}
@@ -58,7 +101,7 @@ function clearDuplicatedHistory(notehistory) {
if (!found)
newnotehistory.push(notehistory[i]);
}
- return notehistory;
+ return newnotehistory;
}
function addHistory(id, text, time, tags, notehistory) {
@@ -86,7 +129,7 @@ function writeHistory(view) {
writeHistoryToServer(view);
},
function () {
- writeHistoryToCookie(view);
+ writeHistoryToStorage(view);
}
);
}
@@ -113,7 +156,7 @@ function writeHistoryToServer(view) {
function writeHistoryToCookie(view) {
try {
- var notehistory = JSON.parse($.cookie('notehistory'));
+ var notehistory = Cookies.getJSON('notehistory');
} catch (err) {
var notehistory = [];
}
@@ -122,6 +165,22 @@ function writeHistoryToCookie(view) {
saveHistoryToCookie(newnotehistory);
}
+function writeHistoryToStorage(view) {
+ if (store.enabled) {
+ var data = store.get('notehistory');
+ if (data) {
+ if (typeof data == "string")
+ data = JSON.parse(data);
+ var notehistory = data;
+ } else
+ var notehistory = [];
+ var newnotehistory = generateHistory(view, notehistory);
+ saveHistoryToStorage(newnotehistory);
+ } else {
+ writeHistoryToCookie(view);
+ }
+}
+
function renderHistory(view) {
var title = renderFilename(view);
@@ -169,7 +228,7 @@ function getHistory(callback) {
getServerHistory(callback);
},
function () {
- getCookieHistory(callback);
+ getStorageHistory(callback);
}
);
}
@@ -187,70 +246,76 @@ function getServerHistory(callback) {
}
function getCookieHistory(callback) {
- callback(JSON.parse($.cookie('notehistory')));
+ callback(Cookies.getJSON('notehistory'));
+}
+
+function getStorageHistory(callback) {
+ if (store.enabled) {
+ var data = store.get('notehistory');
+ if (data) {
+ if (typeof data == "string")
+ data = JSON.parse(data);
+ callback(data);
+ } else
+ getCookieHistory(callback);
+ } else {
+ getCookieHistory(callback);
+ }
}
-function parseHistory(callback) {
+function parseHistory(list, callback) {
checkIfAuth(
function () {
- parseServerToHistory(callback);
+ parseServerToHistory(list, callback);
},
function () {
- parseCookieToHistory(callback);
+ parseStorageToHistory(list, callback);
}
);
}
-function parseServerToHistory(callback) {
+function parseServerToHistory(list, callback) {
$.get('/history')
.done(function (data) {
if (data.history) {
- //console.log(data.history);
- parseToHistory(data.history, callback);
+ parseToHistory(list, data.history, callback);
}
})
.fail(function () {
- parseCookieToHistory(callback);
+ parseCookieToHistory(list, callback);
});
}
-function parseCookieToHistory(callback) {
- var notehistory = JSON.parse($.cookie('notehistory'));
- parseToHistory(notehistory, callback);
+function parseCookieToHistory(list, callback) {
+ var notehistory = Cookies.getJSON('notehistory');
+ parseToHistory(list, notehistory, callback);
}
-function parseToHistory(notehistory, callback) {
- if (notehistory && notehistory.length > 0) {
- //console.log(notehistory);
+function parseStorageToHistory(list, callback) {
+ if (store.enabled) {
+ var data = store.get('notehistory');
+ if (data) {
+ if (typeof data == "string")
+ data = JSON.parse(data);
+ parseToHistory(list, data, callback);
+ } else
+ parseCookieToHistory(list, callback);
+ } else {
+ parseCookieToHistory(list, callback);
+ }
+}
+
+function parseToHistory(list, notehistory, callback) {
+ if (!callback) return;
+ else if (!list || !notehistory) callback(list, notehistory);
+ else if (notehistory && notehistory.length > 0) {
for (var i = 0; i < notehistory.length; i++) {
+ //parse time to timestamp and fromNow
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();
+ if (list.get('id', notehistory[i].id).length == 0)
+ list.add(notehistory[i]);
}
- $(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();
+ callback(list, notehistory);
} \ No newline at end of file
diff --git a/public/js/index.js b/public/js/index.js
index 73b4e594..331251c9 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -1,12 +1,10 @@
//constant vars
//settings
-var debug = false;
-var version = '0.2.7';
+var debug = true;
+var version = '0.2.8';
var doneTypingDelay = 400;
var finishChangeDelay = 400;
var cursorActivityDelay = 50;
-var syncScrollDelay = 50;
-var scrollAnimatePeriod = 100;
var cursorAnimatePeriod = 100;
var modeType = {
edit: {},
@@ -67,15 +65,20 @@ var lastInfo = {
};
//editor settings
-var editor = CodeMirror.fromTextArea(document.getElementById("textit"), {
+var textit = document.getElementById("textit");
+if (!textit) throw new Error("There was no textit area!");
+var editor = CodeMirror.fromTextArea(textit, {
mode: 'gfm',
+ keyMap: "sublime",
viewportMargin: 20,
styleActiveLine: true,
lineNumbers: true,
lineWrapping: true,
+ showCursorWhenSelecting: true,
theme: "monokai",
autofocus: true,
inputStyle: "textarea",
+ scrollbarStyle: "overlay",
matchBrackets: true,
autoCloseBrackets: true,
matchTags: {
@@ -89,6 +92,7 @@ var editor = CodeMirror.fromTextArea(document.getElementById("textit"), {
},
readOnly: true
});
+inlineAttachment.editors.codemirror4.attach(editor);
//ui vars
var ui = {
@@ -148,23 +152,33 @@ $(document).ready(function () {
changeMode(currentMode);
/* we need this only on touch devices */
if (isTouchDevice) {
- /* cache dom references */
- var $body = jQuery('body');
+ /* 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');
- });
+ .on('focus', 'textarea, input', function () {
+ $body.addClass('fixfixed');
+ })
+ .on('blur', 'textarea, input', function () {
+ $body.removeClass('fixfixed');
+ });
}
});
//when page resize
+var windowResizeDelay = 200;
+var windowResizeTimer = null;
$(window).resize(function () {
- checkResponsive();
+ clearTimeout(windowResizeTimer);
+ windowResizeTimer = setTimeout(function () {
+ windowResize();
+ }, windowResizeDelay);
});
+function windowResize() {
+ checkResponsive();
+ clearMap();
+ syncScrollToView();
+}
//768-792px have a gap
function checkResponsive() {
visibleXS = $(".visible-xs").is(":visible");
@@ -176,6 +190,10 @@ function checkResponsive() {
changeMode(modeType.edit);
else
changeMode(modeType.view);
+ if (visibleXS)
+ $('.CodeMirror').css('height', 'auto');
+ else
+ $('.CodeMirror').css('height', '');
}
function showStatus(type, num) {
@@ -220,7 +238,7 @@ function showStatus(type, num) {
}
function toggleMode() {
- switch(currentMode) {
+ switch (currentMode) {
case modeType.edit:
changeMode(modeType.view);
break;
@@ -297,26 +315,31 @@ var url = window.location.origin + '/' + noteId;
ui.toolbar.pretty.attr("href", url + "/pretty");
//download
//markdown
-ui.toolbar.download.markdown.click(function() {
+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"});
+ var blob = new Blob([markdown], {
+ type: "text/markdown;charset=utf-8"
+ });
saveAs(blob, filename);
});
//save to dropbox
-ui.toolbar.save.dropbox.click(function() {
+ui.toolbar.save.dropbox.click(function () {
var filename = renderFilename(ui.area.markdown) + '.md';
var options = {
files: [
- {'url': url + "/download", 'filename': filename}
+ {
+ 'url': url + "/download",
+ 'filename': filename
+ }
]
};
Dropbox.save(options);
});
//import from dropbox
-ui.toolbar.import.dropbox.click(function() {
+ui.toolbar.import.dropbox.click(function () {
var options = {
- success: function(files) {
+ success: function (files) {
ui.spinner.show();
var url = files[0].link;
importFromUrl(url);
@@ -328,64 +351,73 @@ ui.toolbar.import.dropbox.click(function() {
Dropbox.choose(options);
});
//import from clipboard
-ui.toolbar.import.clipboard.click(function() {
+ui.toolbar.import.clipboard.click(function () {
//na
});
//fix for wrong autofocus
-$('#clipboardModal').on('shown.bs.modal', function() {
+$('#clipboardModal').on('shown.bs.modal', function () {
$('#clipboardModal').blur();
});
-$("#clipboardModalClear").click(function() {
+$("#clipboardModalClear").click(function () {
$("#clipboardModalContent").html('');
});
-$("#clipboardModalConfirm").click(function() {
+$("#clipboardModalConfirm").click(function () {
var data = $("#clipboardModalContent").html();
- if(data) {
+ 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');
+ 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)) {
+ if (url == null) return;
+ if (!isValidURL(url)) {
alert('Not valid URL :(');
return;
}
$.ajax({
method: "GET",
url: url,
- success: function(data) {
+ success: function (data) {
parseToEditor(data);
},
- error: function() {
+ error: function () {
alert('Import failed :(');
},
- complete: function() {
+ 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;
+ 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
+ //mode
ui.toolbar.mode.click(function () {
toggleMode();
});
@@ -427,7 +459,7 @@ socket.on('version', function (data) {
});
socket.on('refresh', function (data) {
saveInfo();
-
+
var body = data.body;
body = LZString.decompressFromBase64(body);
if (body)
@@ -455,7 +487,7 @@ socket.on('refresh', function (data) {
if (editor.getOption('readOnly'))
editor.setOption('readOnly', false);
-
+
restoreInfo();
});
socket.on('change', function (data) {
@@ -470,51 +502,65 @@ 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++) {
+ $('.other-cursors').children().each(function (key, value) {
+ var found = false;
+ for (var i = 0; i < data.users.length; i++) {
+ var user = data.users[i];
+ if ($(this).attr('id') == user.id)
+ found = true;
+ }
+ if (!found)
+ $(this).remove();
+ });
+ for (var i = 0; i < data.users.length; i++) {
var user = data.users[i];
- if(user.id != socket.id)
+ if (user.id != socket.id)
buildCursor(user.id, user.color, user.cursor);
}
});
socket.on('cursor focus', function (data) {
- if(debug)
+ if (debug)
console.debug(data);
var cursor = $('#' + data.id);
- if(cursor.length > 0) {
+ if (cursor.length > 0) {
cursor.fadeIn();
} else {
- if(data.id != socket.id)
+ if (data.id != socket.id)
buildCursor(data.id, data.color, data.cursor);
}
});
socket.on('cursor activity', function (data) {
- if(debug)
+ if (debug)
console.debug(data);
- if(data.id != socket.id)
+ if (data.id != socket.id)
buildCursor(data.id, data.color, data.cursor);
});
socket.on('cursor blur', function (data) {
- if(debug)
+ if (debug)
console.debug(data);
var cursor = $('#' + data.id);
- if(cursor.length > 0) {
+ if (cursor.length > 0) {
cursor.fadeOut();
}
});
+
function emitUserStatus() {
checkIfAuth(
function (data) {
- socket.emit('user status', {login:true});
+ socket.emit('user status', {
+ login: true
+ });
},
function () {
- socket.emit('user status', {login:false});
+ socket.emit('user status', {
+ login: false
+ });
}
);
}
function buildCursor(id, color, pos) {
- if(!pos) return;
+ if (!pos) return;
if ($('.other-cursors').length <= 0) {
$("<div class='other-cursors'>").insertAfter('.CodeMirror-cursors');
}
@@ -535,7 +581,10 @@ function buildCursor(id, color, pos) {
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.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';
@@ -566,6 +615,7 @@ editor.on('cursorActivity', function (cm) {
clearTimeout(cursorActivityTimer);
cursorActivityTimer = setTimeout(cursorActivity, cursorActivityDelay);
});
+
function cursorActivity() {
socket.emit('cursor activity', editor.getCursor());
}
@@ -578,8 +628,9 @@ function saveInfo() {
var top = $(document.body).scrollTop();
switch (currentMode) {
case modeType.edit:
- lastInfo.edit.scroll.left = left;
- lastInfo.edit.scroll.top = top;
+ //lastInfo.edit.scroll.left = left;
+ //lastInfo.edit.scroll.top = top;
+ lastInfo.edit.scroll = editor.getScrollInfo();
break;
case modeType.view:
lastInfo.view.scroll.left = left;
@@ -603,8 +654,12 @@ function restoreInfo() {
switch (currentMode) {
case modeType.edit:
- $(document.body).scrollLeft(lastInfo.edit.scroll.left);
- $(document.body).scrollTop(lastInfo.edit.scroll.top);
+ //$(document.body).scrollLeft(lastInfo.edit.scroll.left);
+ //$(document.body).scrollTop(lastInfo.edit.scroll.top);
+ var left = lastInfo.edit.scroll.left;
+ var top = lastInfo.edit.scroll.top;
+ editor.scrollIntoView();
+ editor.scrollTo(left, top);
break;
case modeType.view:
$(document.body).scrollLeft(lastInfo.view.scroll.left);
@@ -652,9 +707,8 @@ function updateView() {
finishView(ui.area.view);
writeHistory(ui.area.markdown);
isDirty = false;
- // reset lines mapping cache on content update
- scrollMap = null;
emitUserStatus();
+ clearMap();
}
function partialUpdate(src, tar, des) {
@@ -702,7 +756,7 @@ function partialUpdate(src, tar, des) {
}
}
//tar end
- for (var i = 1; i <= tar.length; i++) {
+ for (var i = 1; i <= tar.length + 1; i++) {
var srcLength = src.length;
var tarLength = tar.length;
copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
@@ -715,7 +769,7 @@ function partialUpdate(src, tar, des) {
}
}
//src end
- for (var i = 1; i <= src.length; i++) {
+ for (var i = 1; i <= src.length + 1; i++) {
var srcLength = src.length;
var tarLength = tar.length;
copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
@@ -730,56 +784,75 @@ function partialUpdate(src, tar, des) {
//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)
+ 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)
+ if (debug)
console.log('overlap:' + overlap);
//show diff content
- if(debug) {
+ 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;
+ var repeatAdd = (start - srcEnd) < (start - tarEnd);
+ var repeatDiff = Math.abs(srcEnd - tarEnd) - 1;
+ //push new elements
+ var newElements = [];
+ if(srcEnd >= start) {
+ for (var j = start; j <= srcEnd; j++) {
+ if (!src[j]) continue;
+ newElements.push(src[j].outerHTML);
+ }
+ } else if(repeatAdd) {
+ for (var j = srcEnd - repeatDiff; j <= srcEnd; j++) {
+ if (!des[j]) continue;
+ newElements.push(des[j].outerHTML);
+ }
}
- if(newElements && des[start]) {
- $(newElements).insertBefore(des[start]);
- } else {
- $(newElements).insertAfter(des[des.length-1]);
+ //push remove elements
+ var removeElements = [];
+ if(tarEnd >= start) {
+ for (var j = start; j <= tarEnd; j++) {
+ if (!des[j]) continue;
+ removeElements.push(des[j]);
+ }
+ } else if(!repeatAdd) {
+ for (var j = start; j <= start + repeatDiff; j++) {
+ if (!des[j]) continue;
+ removeElements.push(des[j]);
+ }
}
- 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();
+ //add elements
+ if (debug) {
+ console.log('ADD ELEMENTS');
+ console.log(newElements.join('\n'));
}
- if(debug) {
- console.log(tarChanged);
- var srcChanged = "";
+ if (des[start])
+ $(newElements.join('')).insertBefore(des[start]);
+ else
+ $(newElements.join('')).insertAfter(des[start - 1]);
+ //remove elements
+ if (debug)
+ console.log('REMOVE ELEMENTS');
+ for (var j = 0; j < removeElements.length; j++) {
+ if (debug) {
+ console.log(removeElements[j].outerHTML);
+ }
+ if (removeElements[j])
+ removeElements[j].remove();
}
}
}
function cloneAndRemoveDataAttr(el) {
- if(!el) return;
+ if (!el) return;
var rawEl = $(el).clone(true)[0];
rawEl.removeAttribute('data-startline');
rawEl.removeAttribute('data-endline');
@@ -789,152 +862,4 @@ function cloneAndRemoveDataAttr(el) {
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/pretty.js b/public/js/pretty.js
new file mode 100644
index 00000000..33b97803
--- /dev/null
+++ b/public/js/pretty.js
@@ -0,0 +1,9 @@
+var raw = $(".markdown-body").text();
+var markdown = LZString.decompressFromBase64(raw);
+var result = postProcess(md.render(markdown));
+var markdown = $(".markdown-body");
+markdown.html(result);
+markdown.show();
+finishView(markdown);
+autoLinkify(markdown);
+scrollToHash(); \ No newline at end of file
diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js
new file mode 100644
index 00000000..3d97324c
--- /dev/null
+++ b/public/js/syncscroll.js
@@ -0,0 +1,327 @@
+//
+// 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.blockquote_open = function (tokens, idx /*, options, env */ ) {
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<blockquote class="part" data-startline="' + startline + '" data-endline="' + endline + '">\n';
+ }
+ return '<blockquote>\n';
+};
+
+md.renderer.rules.table_open = function (tokens, idx /*, options, env */ ) {
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<table class="part" data-startline="' + startline + '" data-endline="' + endline + '">\n';
+ }
+ return '<table>\n';
+};
+
+md.renderer.rules.bullet_list_open = function (tokens, idx /*, options, env */ ) {
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<ul class="part" data-startline="' + startline + '" data-endline="' + endline + '">\n';
+ }
+ return '<ul>\n';
+};
+
+md.renderer.rules.ordered_list_open = function (tokens, idx /*, options, env */ ) {
+ var token = tokens[idx];
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<ol class="part" data-startline="' + startline + '" data-endline="' + endline + '"' + (token.order > 1 ? ' start="' + token.order + '"' : '') + '>\n';
+ }
+ return '<ol' + (token.order > 1 ? ' start="' + token.order + '"' : '') + '>\n';
+};
+
+md.renderer.rules.link_open = function (tokens, idx /*, options, env */ ) {
+ var title = tokens[idx].title ? (' title="' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].title)) + '"') : '';
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<a class="part" data-startline="' + startline + '" data-endline="' + endline + '" href="' + Remarkable.utils.escapeHtml(tokens[idx].href) + '"' + title + '>';
+ }
+ return '<a href="' + Remarkable.utils.escapeHtml(tokens[idx].href) + '"' + title + '>';
+};
+
+md.renderer.rules.paragraph_open = function (tokens, idx) {
+ 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) {
+ 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 + '>';
+};
+
+md.renderer.rules.image = function (tokens, idx, options /*, env */ ) {
+ var src = ' src="' + Remarkable.utils.escapeHtml(tokens[idx].src) + '"';
+ var title = tokens[idx].title ? (' title="' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].title)) + '"') : '';
+ var alt = ' alt="' + (tokens[idx].alt ? Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].alt)) : '') + '"';
+ var suffix = options.xhtmlOut ? ' /' : '';
+ var image = $('<img' + src + alt + title + suffix + '>');
+ image[0].onload = function (e) {
+ if (viewAjaxCallback)
+ viewAjaxCallback();
+ };
+ return image[0].outerHTML;
+};
+
+md.renderer.rules.fence = function (tokens, idx, options, env, self) {
+ var token = tokens[idx];
+ var langClass = '';
+ var langPrefix = options.langPrefix;
+ var langName = '',
+ fenceName;
+ var highlighted;
+
+ if (token.params) {
+
+ //
+ // ```foo bar
+ //
+ // Try custom renderer "foo" first. That will simplify overwrite
+ // for diagrams, latex, and any other fenced block with custom look
+ //
+
+ fenceName = token.params.split(/\s+/g)[0];
+
+ if (Remarkable.utils.has(self.rules.fence_custom, fenceName)) {
+ return self.rules.fence_custom[fenceName](tokens, idx, options, env, self);
+ }
+
+ langName = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(Remarkable.utils.unescapeMd(fenceName)));
+ langClass = ' class="' + langPrefix + langName + '"';
+ }
+
+ if (options.highlight) {
+ highlighted = options.highlight(token.content, langName) || Remarkable.utils.escapeHtml(token.content);
+ } else {
+ highlighted = Remarkable.utils.escapeHtml(token.content);
+ }
+
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<pre class="part" data-startline="' + startline + '" data-endline="' + endline + '"><code' + langClass + '>' + highlighted + '</code></pre>' + md.renderer.getBreak(tokens, idx);
+ }
+
+ return '<pre><code' + langClass + '>' + highlighted + '</code></pre>' + md.renderer.getBreak(tokens, idx);
+};
+
+md.renderer.rules.code = function (tokens, idx /*, options, env */ ) {
+ if (tokens[idx].block) {
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<pre class="part" data-startline="' + startline + '" data-endline="' + endline + '"><code>' + Remarkable.utils.escapeHtml(tokens[idx].content) + '</code></pre>' + md.renderer.getBreak(tokens, idx);
+ }
+
+ return '<pre><code>' + Remarkable.utils.escapeHtml(tokens[idx].content) + '</code></pre>' + md.renderer.getBreak(tokens, idx);
+ }
+
+ if (tokens[idx].lines && tokens[idx].level === 0) {
+ var startline = tokens[idx].lines[0] + 1;
+ var endline = tokens[idx].lines[1];
+ return '<code class="part" data-startline="' + startline + '" data-endline="' + endline + '">' + Remarkable.utils.escapeHtml(tokens[idx].content) + '</code>';
+ }
+
+ return '<code>' + Remarkable.utils.escapeHtml(tokens[idx].content) + '</code>';
+};
+
+var viewScrolling = false;
+var viewScrollingDelay = 200;
+var viewScrollingTimer = null;
+
+editor.on('scroll', syncScrollToView);
+ui.area.view.on('scroll', function () {
+ viewScrolling = true;
+ clearTimeout(viewScrollingTimer);
+ viewScrollingTimer = setTimeout(function () {
+ viewScrolling = false;
+ }, viewScrollingDelay);
+});
+//editor.on('scroll', _.debounce(syncScrollToView, syncScrollDelay));
+//ui.area.view.on('scroll', _.debounce(syncScrollToEdit, 50));
+
+var scrollMap, lineHeightMap;
+
+viewAjaxCallback = clearMap;
+
+function clearMap() {
+ scrollMap = null;
+ lineHeightMap = null;
+}
+
+// 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 buildMap() {
+ var i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount,
+ acc, sourceLikeDiv, textarea = ui.area.codemirror,
+ wrap = $('.CodeMirror-wrap pre'),
+ _scrollMap;
+
+ sourceLikeDiv = $('<div />').css({
+ position: 'absolute',
+ visibility: 'hidden',
+ height: 'auto',
+ width: wrap.width(),
+ padding: wrap.css('padding'),
+ margin: wrap.css('margin'),
+ 'font-size': textarea.css('font-size'),
+ 'font-family': textarea.css('font-family'),
+ 'line-height': textarea.css('line-height'),
+ 'word-wrap': wrap.css('word-wrap'),
+ 'white-space': wrap.css('white-space'),
+ 'word-break': wrap.css('word-break')
+ }).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') - 1;
+ 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));
+ }
+
+ _scrollMap[0] = 0;
+
+ scrollMap = _scrollMap;
+ lineHeightMap = _lineHeightMap;
+}
+
+function getPartByEditorLineNo(lineNo) {
+ var part = null;
+ ui.area.markdown.find('.part').each(function (n, el) {
+ if (part) return;
+ var $el = $(el),
+ t = $el.data('startline') - 1,
+ f = $el.data('endline') - 1;
+ if (t === '' || f === '') {
+ return;
+ }
+ if (lineNo >= t && lineNo <= f) {
+ part = $el;
+ }
+ });
+ if (part)
+ return {
+ startline: part.data('startline') - 1,
+ endline: part.data('endline') - 1,
+ linediff: Math.abs(part.data('endline') - part.data('startline')) + 1,
+ element: part
+ };
+ else
+ return null;
+}
+
+function getEditorLineNoByTop(top) {
+ for (var i = 0; i < lineHeightMap.length; i++)
+ if (lineHeightMap[i] * editor.defaultTextHeight() > top)
+ return i;
+ return null;
+}
+
+function syncScrollToView(_lineNo) {
+ var lineNo, posTo;
+ var scrollInfo = editor.getScrollInfo();
+ if (!scrollMap || !lineHeightMap) {
+ buildMap();
+ }
+ if (typeof _lineNo != "number") {
+ var topDiffPercent, posToNextDiff;
+ var textHeight = editor.defaultTextHeight();
+ lineNo = Math.floor(scrollInfo.top / textHeight);
+ var lineCount = editor.lineCount();
+ var lastLineHeight = editor.getLineHandle(lineCount - 1).height;
+ //if reach last line, then scroll to end
+ if (scrollInfo.top + scrollInfo.clientHeight >= scrollInfo.height - lastLineHeight) {
+ posTo = ui.area.view[0].scrollHeight - ui.area.view.height();
+ } else {
+ topDiffPercent = (scrollInfo.top % textHeight) / textHeight;
+ posTo = scrollMap[lineNo];
+ posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent;
+ posTo += Math.floor(posToNextDiff);
+ }
+ } else {
+ if (viewScrolling) return;
+ posTo = scrollMap[lineHeightMap[_lineNo]];
+ }
+ var posDiff = Math.abs(ui.area.view.scrollTop() - posTo);
+ if (posDiff > scrollInfo.clientHeight / 5) {
+ var duration = posDiff / 50;
+ ui.area.view.stop(true).animate({
+ scrollTop: posTo
+ }, duration >= 50 ? duration : 100, "linear");
+ } else {
+ ui.area.view.stop(true).scrollTop(posTo);
+ }
+} \ No newline at end of file