diff options
Diffstat (limited to 'public/js')
-rw-r--r-- | public/js/common.js | 53 | ||||
-rw-r--r-- | public/js/cover.js | 245 | ||||
-rw-r--r-- | public/js/extra.js | 6 | ||||
-rw-r--r-- | public/js/fb.js | 8 | ||||
-rw-r--r-- | public/js/ga.js | 14 | ||||
-rw-r--r-- | public/js/history.js | 187 | ||||
-rw-r--r-- | public/js/index.js | 419 | ||||
-rw-r--r-- | public/js/pretty.js | 9 | ||||
-rw-r--r-- | public/js/syncscroll.js | 327 |
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 |