Marked as 0.2.8
This commit is contained in:
53
public/js/common.js
Normal file
53
public/js/common.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,63 +63,202 @@ $(".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;
|
||||
|
||||
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();
|
||||
$(".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';
|
||||
});
|
||||
|
||||
var filtertags = [];
|
||||
$(".ui-use-tags").select2({
|
||||
placeholder: 'Use tags...',
|
||||
multiple: true,
|
||||
data: function () {
|
||||
return {
|
||||
results: filtertags
|
||||
};
|
||||
}
|
||||
});
|
||||
$('.select2-input').css('width', 'inherit');
|
||||
buildTagsFilter([]);
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
$('.search').keyup(function () {
|
||||
checkHistoryList();
|
||||
});
|
||||
|
||||
var source = $("#template").html();
|
||||
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",
|
||||
|
||||
@@ -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]);
|
||||
|
||||
8
public/js/fb.js
Normal file
8
public/js/fb.js
Normal file
@@ -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'));
|
||||
@@ -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');
|
||||
@@ -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 parseHistory(callback) {
|
||||
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(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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
9
public/js/pretty.js
Normal file
9
public/js/pretty.js
Normal file
@@ -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();
|
||||
327
public/js/syncscroll.js
Normal file
327
public/js/syncscroll.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user