diff options
Diffstat (limited to 'public/js')
-rw-r--r-- | public/js/cover.js | 130 | ||||
-rw-r--r-- | public/js/extra.js | 293 | ||||
-rw-r--r-- | public/js/fb.js | 8 | ||||
-rw-r--r-- | public/js/history.js | 3 | ||||
-rw-r--r-- | public/js/index.js | 541 | ||||
-rw-r--r-- | public/js/pretty.js | 84 | ||||
-rw-r--r-- | public/js/syncscroll.js | 13 |
7 files changed, 878 insertions, 194 deletions
diff --git a/public/js/cover.js b/public/js/cover.js index 322768bf..49fcddeb 100644 --- a/public/js/cover.js +++ b/public/js/cover.js @@ -4,12 +4,17 @@ var options = { <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 class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>\ + <div class="content">\ + <h4 class="text"></h4>\ + <p>\ + <i><i class="fa fa-clock-o"></i> visit </i><i class="fromNow"></i>\ + <br>\ + <i class="timestamp" style="display:none;"></i>\ + <i class="time"></i>\ + </p>\ + <p class="tags"></p>\ + </div>\ </div>\ </a>\ </li>' @@ -114,16 +119,53 @@ function parseHistoryCallback(list, notehistory) { $(".ui-history-close").click(function (e) { e.preventDefault(); var id = $(this).closest("a").siblings("span").html(); + var value = list.get('id', id)[0].values(); + $('.ui-delete-modal-msg').text('Do you really want to delete below history?'); + $('.ui-delete-modal-item').html('<i class="fa fa-file-text"></i> ' + value.text + '<br><i class="fa fa-clock-o"></i> ' + value.time); + clearHistory = false; + deleteId = id; + }); + buildTagsFilter(filtertags); +} + +//auto update item fromNow every minutes +setInterval(updateItemFromNow, 60000); + +function updateItemFromNow() { + var items = $('.item').toArray(); + for (var i = 0; i < items.length; i++) { + var item = $(items[i]); + var timestamp = parseInt(item.find('.timestamp').text()); + item.find('.fromNow').text(moment(timestamp).fromNow()); + } +} + +var clearHistory = false; +var deleteId = null; + +function deleteHistory() { + if (clearHistory) { + saveHistory([]); + historyList.clear(); + checkHistoryList(); + } else { + if (!deleteId) return; getHistory(function (notehistory) { - var newnotehistory = removeHistory(id, notehistory); + var newnotehistory = removeHistory(deleteId, notehistory); saveHistory(newnotehistory); }); - list.remove('id', id); + historyList.remove('id', deleteId); checkHistoryList(); - }); - buildTagsFilter(filtertags); + } + $('.delete-modal').modal('hide'); + clearHistory = false; + deleteId = null; } +$(".ui-delete-modal-confirm").click(function () { + deleteHistory(); +}); + $(".ui-import-from-browser").click(function () { saveStorageHistoryToServer(function () { parseStorageToHistory(historyList, parseHistoryCallback); @@ -160,9 +202,10 @@ $(".ui-open-history").bind("change", function (e) { }); $(".ui-clear-history").click(function () { - saveHistory([]); - historyList.clear(); - checkHistoryList(); + $('.ui-delete-modal-msg').text('Do you really want to clear all history?'); + $('.ui-delete-modal-item').html('There is no turning back.'); + clearHistory = true; + deleteId = null; }); $(".ui-refresh-history").click(function () { @@ -230,6 +273,67 @@ var template = Handlebars.compile(source); var context = { release: [ { + version: "0.3.1", + tag: "clearsky", + date: moment("201506301600", 'YYYYMMDDhhmm').fromNow(), + detail: [ + { + title: "Features", + item: [ + "+ Added auto table of content", + "+ Added basic permission control", + "+ Added view count in share note" + ] + }, + { + title: "Enhancements", + item: [ + "* Toolbar now will hide in single view", + "* History time now will auto update", + "* Smooth scroll on anchor changed", + "* Updated video style" + ] + }, + { + title: "Fixes", + item: [ + "* Note might not clear when all users disconnect", + "* Blockquote tag not parsed properly", + "* History style not correct" + ] + } + ] + }, + { + version: "0.3.0", + tag: "sunrise", + date: moment("201506152400", 'YYYYMMDDhhmm').fromNow(), + detail: [ + { + title: "Enhancements", + item: [ + "* Used short url in share notes", + "* Added upload image button on toolbar", + "* Share notes are now SEO and mobile friendly", + "* Updated code block style", + "* Newline now will cause line breaks", + "* Image now will link out", + "* Used otk to avoid race condition", + "* Used hash to avoid data inconsistency", + "* Optimized server realtime script" + ] + }, + { + title: "Fixes", + item: [ + "* Composition input might lost or duplicated when other input involved", + "* Note title might not save properly", + "* Todo list not render properly" + ] + } + ] + }, + { version: "0.2.9", tag: "wildfire", date: moment("201505301400", 'YYYYMMDDhhmm').fromNow(), diff --git a/public/js/extra.js b/public/js/extra.js index 495c5677..f6d47647 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -1,43 +1,75 @@ +//auto update last change +var lastchangetime = null; +var lastchangeui = null; + +function updateLastChange() { + if (lastchangetime && lastchangeui) { + lastchangeui.html(' <i class="fa fa-clock-o"></i> change ' + moment(lastchangetime).fromNow()); + lastchangeui.attr('title', moment(lastchangetime).format('llll')); + } +} +setInterval(updateLastChange, 60000); + //get title function getTitle(view) { var h1s = view.find("h1"); var title = ""; - if (h1s.length > 0) { + if (h1s.length > 0) { title = h1s.first().text(); } else { title = null; } return title; } + //render title function renderTitle(view) { var title = getTitle(view); - if (title) { + if (title) { title += ' - HackMD'; } else { title = 'HackMD - Collaborative notes'; } return title; } + //render filename function renderFilename(view) { var filename = getTitle(view); - if (!filename) { + if (!filename) { filename = 'Untitled'; } return filename; } +function slugifyWithUTF8(text) { + var newText = S(text.toLowerCase()).trim().stripTags().dasherize().s; + newText = newText.replace(/([\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\[\\\]\^\`\{\|\}\~])/g, ''); + return newText; +} + var viewAjaxCallback = null; +//regex for blockquote +var spaceregex = /\s*/; +var notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/; +var coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/; +coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g"); +var nameregex = /\[name=(.*?)\]/; +var timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*?)\]/; +var nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g"); +nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g"); +timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g"); + //dynamic event or object binding here function finishView(view) { //youtube - view.find(".youtube").click(function () { - imgPlayiframe(this, '//www.youtube.com/embed/'); - }); + view.find(".youtube.raw").removeClass("raw") + .click(function () { + imgPlayiframe(this, '//www.youtube.com/embed/'); + }); //vimeo - view.find(".vimeo") + view.find(".vimeo.raw").removeClass("raw") .click(function () { imgPlayiframe(this, '//player.vimeo.com/video/'); }) @@ -54,35 +86,32 @@ function finishView(view) { }); }); //gist - view.find("code[data-gist-id]").each(function(key, value) { - if($(value).children().length == 0) + view.find("code[data-gist-id]").each(function (key, value) { + if ($(value).children().length == 0) $(value).gist(viewAjaxCallback); }); //emojify emojify.run(view[0]); //mathjax - var mathjaxdivs = view.find('.mathjax').toArray(); + var mathjaxdivs = view.find('.mathjax.raw').removeClass("raw").toArray(); try { for (var i = 0; i < mathjaxdivs.length; i++) { MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[i].innerHTML]); MathJax.Hub.Queue(viewAjaxCallback); - $(mathjaxdivs[i]).removeClass("mathjax"); } - } catch(err) { - } + } catch (err) {} //sequence diagram - var sequence = view.find(".sequence-diagram"); + var sequence = view.find(".sequence-diagram.raw").removeClass("raw"); try { sequence.sequenceDiagram({ theme: 'simple' }); sequence.parent().parent().replaceWith(sequence); - sequence.removeClass("sequence-diagram"); - } catch(err) { + } catch (err) { console.error(err); } //flowchart - var flow = view.find(".flow-chart"); + var flow = view.find(".flow-chart.raw").removeClass("raw"); flow.each(function (key, value) { try { var chart = flowchart.parse($(value).text()); @@ -94,26 +123,41 @@ function finishView(view) { 'font-family': "'Andale Mono', monospace" }); $(value).parent().parent().replaceWith(value); - $(value).removeClass("flow-chart"); - } catch(err) { + } catch (err) { console.error(err); } }); + //image href new window(emoji not included) + var images = view.find("p > img[src]:not([class])"); + images.each(function (key, value) { + var src = $(value).attr('src'); + var a = $('<a>'); + if (src) { + a.attr('href', src); + a.attr('target', "_blank"); + } + a.html($(value).clone()); + $(value).replaceWith(a); + }); + //blockquote + var blockquote = view.find("blockquote.raw").removeClass("raw"); + var blockquote_p = blockquote.find("p"); + blockquote_p.each(function (key, value) { + var html = $(value).html(); + html = html.replace(coloregex, '<span class="color" data-color="$1"></span>'); + html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>'); + html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>'); + html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>'); + $(value).html(html); + }); + var blockquote_color = blockquote.find(".color"); + blockquote_color.each(function (key, value) { + $(value).closest("blockquote").css('border-left-color', $(value).attr('data-color')); + }); //render title document.title = renderTitle(view); } -//regex for blockquote -var spaceregex = /\s*/; -var notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/; -var coloregex = /\[color=([#|\(|\)|\s|\,|\w]*)\]/; -coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g"); -var nameregex = /\[name=([-|_|\s|\w]*)\]/; -var timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*)\]/; -var nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g"); -nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g"); -timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g"); - //only static transform should be here function postProcess(code) { var result = $('<div>' + code + '</div>'); @@ -125,30 +169,105 @@ function postProcess(code) { return "<noiframe>" + $(this).html() + "</noiframe>" }); //todo list - var lis = result[0].getElementsByTagName('li'); + var lis = result.find('li.raw').removeClass("raw").sortByDepth().toArray(); for (var i = 0; i < lis.length; i++) { - var html = lis[i].innerHTML; - if (/^\s*\[[x ]\]\s+/.test(html)) { - lis[i].innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled>') - .replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled>'); + var li = lis[i]; + var html = $(li).clone()[0].innerHTML; + var p = $(li).children('p'); + if (p.length == 1) { + html = p.html(); + li = p[0]; + } + if (/^\s*\[[x ]\]\s*/.test(html)) { + li.innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled><label></label>') + .replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled><label></label>'); lis[i].setAttribute('class', 'task-list-item'); } } - //blockquote - var blockquote = result.find("blockquote"); - blockquote.each(function (key, value) { - var html = $(value).html(); - html = html.replace(coloregex, '<span class="color" data-color="$1"></span>'); - html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>'); - html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>'); - html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>'); - $(value).html(html); + return result; +} + +//jQuery sortByDepth +$.fn.sortByDepth = function () { + var ar = this.map(function () { + return { + length: $(this).parents().length, + elt: this + } + }).get(), + result = [], + i = ar.length; + ar.sort(function (a, b) { + return a.length - b.length; }); - var blockquotecolor = result.find("blockquote .color"); - blockquotecolor.each(function (key, value) { - $(value).closest("blockquote").css('border-left-color', $(value).attr('data-color')); + while (i--) { + result.push(ar[i].elt); + } + return $(result); +}; + +//remove hash +function removeHash() { + history.pushState("", document.title, window.location.pathname + window.location.search); +} + +//toc +function generateToc(id) { + var target = $('#' + id); + target.html(''); + new Toc('doc', { + 'level': 3, + 'top': -1, + 'class': 'toc', + 'targetId': id }); - return result; + if(target.text() == 'undefined') + target.html(''); + var backtotop = $('<a class="back-to-top" href="#">Back to top</a>'); + var gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>'); + backtotop.click(function (e) { + e.preventDefault(); + e.stopPropagation(); + if (scrollToTop) + scrollToTop(); + removeHash(); + }); + gotobottom.click(function (e) { + e.preventDefault(); + e.stopPropagation(); + if (scrollToBottom) + scrollToBottom(); + removeHash(); + }); + target.append(backtotop).append(gotobottom); +} + +//smooth all hash trigger scrolling +function smoothHashScroll() { + var hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray(); + for (var i = 0; i < hashElements.length; i++) { + var element = hashElements[i]; + var $element = $(element); + var hash = element.hash; + if (hash) { + $element.on('click', function (e) { + // store hash + var hash = this.hash; + if ($(hash).length <= 0) return; + // prevent default anchor click behavior + e.preventDefault(); + // animate + $('html, body').animate({ + scrollTop: $(hash).offset().top + }, 100, "linear", function () { + // when done, add hash to url + // (default click behaviour) + window.location.hash = hash; + }); + }); + $element.attr('smoothhashscroll', ''); + } + } } function setSizebyAttr(element, target) { @@ -168,10 +287,10 @@ function imgPlayiframe(element, src) { var anchorForId = function (id) { var anchor = document.createElement("a"); - anchor.className = "header-link"; + anchor.className = "header-link hidden-xs"; anchor.href = "#" + id; - anchor.innerHTML = "<span class=\"sr-only\">Permalink</span><i class=\"fa fa-link\"></i>"; - anchor.title = "Permalink"; + anchor.innerHTML = "<span class=\"sr-only\"></span><i class=\"fa fa-link\"></i>"; + anchor.title = id; return anchor; }; @@ -179,12 +298,14 @@ var linkifyAnchors = function (level, containingElement) { var headers = containingElement.getElementsByTagName("h" + level); for (var h = 0; h < headers.length; h++) { var header = headers[h]; - - if (typeof header.id == "undefined" || header.id == "") { - var id = S(header.innerHTML.toLowerCase()).trim().stripTags().dasherize().s; - header.id = encodeURIComponent(id); + if (header.getElementsByClassName("header-link").length == 0) { + if (typeof header.id == "undefined" || header.id == "") { + //to escape characters not allow in css and humanize + var id = slugifyWithUTF8(header.innerHTML); + header.id = id; + } + header.appendChild(anchorForId(header.id)); } - header.appendChild(anchorForId(header.id)); } }; @@ -207,10 +328,10 @@ function scrollToHash() { function highlightRender(code, lang) { if (!lang || /no(-?)highlight|plain|text/.test(lang)) return; - if(lang == 'sequence') { - return '<div class="sequence-diagram">' + code + '</div>'; - } else if(lang == 'flow') { - return '<div class="flow-chart">' + code + '</div>'; + if (lang == 'sequence') { + return '<div class="sequence-diagram raw">' + code + '</div>'; + } else if (lang == 'flow') { + return '<div class="flow-chart raw">' + code + '</div>'; } var reallang = lang.replace('=', ''); var languages = hljs.listLanguages(); @@ -238,10 +359,56 @@ emojify.setConfig({ var md = new Remarkable('full', { html: true, + breaks: true, + langPrefix: "", linkify: true, typographer: true, highlight: highlightRender }); +md.renderer.rules.list_item_open = function (/* tokens, idx, options, env */) { + return '<li class="raw">'; +}; +md.renderer.rules.blockquote_open = function (tokens, idx /*, options, env */ ) { + return '<blockquote class="raw">\n'; +}; +md.renderer.rules.hardbreak = function (tokens, idx, options /*, env */ ) { + return md.options.xhtmlOut ? '<br /><br />' : '<br><br>'; +}; +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.replace('=', '') + ' hljs"'; + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || Remarkable.utils.escapeHtml(token.content); + } else { + highlighted = Remarkable.utils.escapeHtml(token.content); + } + + return '<pre><code' + langClass + '>' + highlighted + '</code></pre>' + md.renderer.getBreak(tokens, idx); +}; //youtube var youtubePlugin = new Plugin( // regexp to match @@ -251,7 +418,7 @@ var youtubePlugin = new Plugin( function (match, utils) { var videoid = match[1]; if (!videoid) return; - var div = $('<div class="youtube"></div>'); + var div = $('<div class="youtube raw"></div>'); setSizebyAttr(div, div); div.attr('videoid', videoid); var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'; @@ -270,7 +437,7 @@ var vimeoPlugin = new Plugin( function (match, utils) { var videoid = match[1]; if (!videoid) return; - var div = $('<div class="vimeo"></div>'); + var div = $('<div class="vimeo raw"></div>'); setSizebyAttr(div, div); div.attr('videoid', videoid); var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'; @@ -298,7 +465,7 @@ var mathjaxPlugin = new Plugin( // this function will be called when something matches function (match, utils) { //var code = $(match).text(); - return '<span class="mathjax">' + match[0] + '</span>'; + return '<span class="mathjax raw">' + match[0] + '</span>'; } ); md.use(youtubePlugin); diff --git a/public/js/fb.js b/public/js/fb.js deleted file mode 100644 index 0bb7a466..00000000 --- a/public/js/fb.js +++ /dev/null @@ -1,8 +0,0 @@ -(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/history.js b/public/js/history.js index c5db94c5..15e46cc6 100644 --- a/public/js/history.js +++ b/public/js/history.js @@ -319,8 +319,9 @@ function parseToHistory(list, notehistory, callback) { 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].timestamp = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').valueOf(); notehistory[i].fromNow = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').fromNow(); + notehistory[i].time = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').format('llll'); if (list.get('id', notehistory[i].id).length == 0) list.add(notehistory[i]); } diff --git a/public/js/index.js b/public/js/index.js index 24b38f75..5f7e6992 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,16 +1,21 @@ //constant vars //settings var debug = false; -var version = '0.2.9'; +var version = '0.3.1'; var defaultTextHeight = 18; var viewportMargin = 20; var defaultExtraKeys = { + "Cmd-S": function () { + return CodeMirror.PASS + }, + "Ctrl-S": function () { + return CodeMirror.PASS + }, "Enter": "newlineAndIndentContinueMarkdownList" }; var idleTime = 300000; //5 mins -var doneTypingDelay = 400; var finishChangeDelay = 400; var cursorActivityDelay = 50; var cursorAnimatePeriod = 100; @@ -97,12 +102,27 @@ var supportExternals = [ search: 'gist' } ]; -var supportGenerals = [ +var supportBlockquoteTags = [ + { + text: '[name tag]', + search: '[]', + command: function () { + return '[name=' + personalInfo.name + ']'; + }, + }, { + text: '[time tag]', + search: '[]', command: function () { - return moment().format('llll'); + return '[time=' + moment().format('llll') + ']'; }, - search: 'time' + }, + { + text: '[color tag]', + search: '[]', + command: function () { + return '[color=' + personalInfo.color + ']'; + } } ]; var modeType = { @@ -131,6 +151,7 @@ var defaultMode = modeType.both; //global vars var loaded = false; +var needRefresh = false; var isDirty = false; var editShown = false; var visibleXS = false; @@ -192,7 +213,7 @@ var editor = CodeMirror.fromTextArea(textit, { extraKeys: defaultExtraKeys, readOnly: true }); -inlineAttachment.editors.codemirror4.attach(editor); +var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor); defaultTextHeight = parseInt($(".CodeMirror").css('line-height')); //ui vars @@ -203,7 +224,7 @@ var ui = { shortStatus: $(".ui-short-status"), status: $(".ui-status"), new: $(".ui-new"), - pretty: $(".ui-pretty"), + share: $(".ui-share"), download: { markdown: $(".ui-download-markdown") }, @@ -217,7 +238,24 @@ var ui = { mode: $(".ui-mode"), edit: $(".ui-edit"), view: $(".ui-view"), - both: $(".ui-both") + both: $(".ui-both"), + uploadImage: $(".ui-upload-image") + }, + infobar: { + lastchange: $(".ui-lastchange"), + permission: { + permission: $(".ui-permission"), + label: $(".ui-permission-label"), + freely: $(".ui-permission-freely"), + editable: $(".ui-permission-editable"), + locked: $(".ui-permission-locked") + } + }, + toc: { + toc: $('.ui-toc'), + affix: $('.ui-affix-toc'), + label: $('.ui-toc-label'), + dropdown: $('.ui-toc-dropdown') }, area: { edit: $(".ui-edit-area"), @@ -263,10 +301,16 @@ function idleStateChange() { updateOnlineStatus(); } -loginStateChangeEvent = function () { - location.reload(true); +function setNeedRefresh() { + $('#refreshModal').modal('show'); + needRefresh = true; + editor.setOption('readOnly', true); + socket.disconnect(); + showStatus(statusType.offline); } +loginStateChangeEvent = setNeedRefresh; + //visibility var wasFocus = false; Visibility.change(function (e, state) { @@ -315,6 +359,11 @@ $(document).ready(function () { $body.removeClass('fixfixed'); }); } + //showup + $().showUp('.navbar', { + upClass: 'navbar-hide', + downClass: 'navbar-show' + }); }); //when page resize var windowResizeDelay = 200; @@ -327,12 +376,38 @@ $(window).resize(function () { }); //when page unload $(window).unload(function () { - emitRefresh(); + emitUpdate(); }); +//when page hash change +window.onhashchange = locationHashChanged; + +function locationHashChanged(e) { + e.stopPropagation(); + e.preventDefault(); + if (currentMode != modeType.both) { + return; + } + var hashtarget = $("[id$='" + location.hash.substr(1) + "']"); + if (hashtarget.length > 0) { + var linenumber = hashtarget.attr('data-startline'); + if (linenumber) { + editor.setOption('viewportMargin', Infinity); + editor.setOption('viewportMargin', viewportMargin); + var t = editor.charCoords({ + line: linenumber, + ch: 0 + }, "local").top; + editor.scrollTo(null, t - defaultTextHeight * 1.2); + } + } +} + function windowResize() { checkResponsive(); checkEditorStyle(); + checkTocStyle(); + //refresh editor if (loaded) { editor.setOption('viewportMargin', Infinity); setTimeout(function () { @@ -373,6 +448,39 @@ function checkEditorStyle() { } } +function checkTocStyle() { + //toc right + var paddingRight = parseFloat(ui.area.markdown.css('padding-right')); + var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight)); + ui.toc.toc.css('right', right + 'px'); + //affix toc left + var newbool; + var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2; + //for ipad or wider device + if (rightMargin >= 133) { + newbool = true; + var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2; + var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin; + ui.toc.affix.css('left', left + 'px'); + } else { + newbool = false; + } + //toc scrollspy + ui.toc.toc.removeClass('scrollspy-body, scrollspy-view'); + ui.toc.affix.removeClass('scrollspy-body, scrollspy-view'); + if (currentMode != modeType.both && !newbool) { + ui.toc.toc.addClass('scrollspy-body'); + ui.toc.affix.addClass('scrollspy-body'); + } else { + ui.toc.toc.addClass('scrollspy-view'); + ui.toc.affix.addClass('scrollspy-body'); + } + if (newbool != enoughForAffixToc) { + enoughForAffixToc = newbool; + generateScrollspy(); + } +} + function showStatus(type, num) { currentStatus = type; var shortStatus = ui.toolbar.shortStatus; @@ -461,12 +569,21 @@ function changeMode(type) { } if (currentMode != modeType.view && visibleLG) { //editor.focus(); - editor.refresh(); + //editor.refresh(); } else { editor.getInputField().blur(); } - if (changeMode != modeType.edit) + if (currentMode == modeType.edit || currentMode == modeType.both) { + ui.toolbar.uploadImage.fadeIn(); + } else { + ui.toolbar.uploadImage.fadeOut(); + } + if (currentMode != modeType.edit) { + $(document.body).css('background-color', 'white'); updateView(); + } else { + $(document.body).css('background-color', ui.area.codemirror.css('background-color')); + } restoreInfo(); windowResize(); @@ -489,10 +606,9 @@ function changeMode(type) { } //button actions -var noteId = window.location.pathname.split('/')[1]; -var url = window.location.origin + '/' + noteId; -//pretty -ui.toolbar.pretty.attr("href", url + "/pretty"); +var url = window.location.pathname; +//share +ui.toolbar.share.attr("href", url + "/share"); //download //markdown ui.toolbar.download.markdown.click(function () { @@ -534,6 +650,73 @@ ui.toolbar.import.dropbox.click(function () { ui.toolbar.import.clipboard.click(function () { //na }); +//upload image +ui.toolbar.uploadImage.bind('change', function (e) { + var files = e.target.files || e.dataTransfer.files; + e.dataTransfer = {}; + e.dataTransfer.files = files; + inlineAttach.onDrop(e); +}); +//toc +ui.toc.dropdown.click(function (e) { + e.stopPropagation(); +}); + +function scrollToTop() { + if (currentMode == modeType.both) { + if (editor.getScrollInfo().top != 0) + editor.scrollTo(0, 0); + else + ui.area.view.animate({ + scrollTop: 0 + }, 100, "linear"); + } else { + $(document.body).animate({ + scrollTop: 0 + }, 100, "linear"); + } +} + +function scrollToBottom() { + if (currentMode == modeType.both) { + var scrollInfo = editor.getScrollInfo(); + var scrollHeight = scrollInfo.height; + if (scrollInfo.top != scrollHeight) + editor.scrollTo(0, scrollHeight * 2); + else + ui.area.view.animate({ + scrollTop: ui.area.view[0].scrollHeight + }, 100, "linear"); + } else { + $(document.body).animate({ + scrollTop: $(document.body)[0].scrollHeight + }, 100, "linear"); + } +} + +var enoughForAffixToc = true; + +//scrollspy +function generateScrollspy() { + $(document.body).scrollspy({ + target: '.scrollspy-body' + }); + ui.area.view.scrollspy({ + target: '.scrollspy-view' + }); + $(document.body).scrollspy('refresh'); + ui.area.view.scrollspy('refresh'); + if (enoughForAffixToc) { + ui.toc.toc.hide(); + ui.toc.affix.show(); + } else { + ui.toc.affix.hide(); + ui.toc.toc.show(); + } + $(document.body).scroll(); + ui.area.view.scroll(); +} + //fix for wrong autofocus $('#clipboardModal').on('shown.bs.modal', function () { $('#clipboardModal').blur(); @@ -549,6 +732,9 @@ $("#clipboardModalConfirm").click(function () { $("#clipboardModalContent").html(''); } }); +$('#refreshModalRefresh').click(function () { + location.reload(true); +}); function parseToEditor(data) { var parsed = toMarkdown(data); @@ -585,19 +771,20 @@ function importFromUrl(url) { } 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(); }); @@ -613,18 +800,63 @@ ui.toolbar.view.click(function () { ui.toolbar.both.click(function () { changeMode(modeType.both); }); +//permission +//freely +ui.infobar.permission.freely.click(function () { + updatePermission("freely"); +}); +//editable +ui.infobar.permission.editable.click(function () { + updatePermission("editable"); +}); +//locked +ui.infobar.permission.locked.click(function () { + updatePermission("locked"); +}); + +function updatePermission(_permission) { + if (_permission != permission) { + socket.emit('permission', _permission); + } +} + +function checkPermission() { + var label = null; + var title = null; + switch (permission) { + case "freely": + label = '<i class="fa fa-leaf"></i> Freely'; + title = "Anyone can edit"; + break; + case "editable": + label = '<i class="fa fa-pencil"></i> Editable'; + title = "Signed people can edit"; + break; + case "locked": + label = '<i class="fa fa-lock"></i> Locked'; + title = "Only owner can edit"; + break; + } + if (personalInfo.userid == owner) { + label += ' <i class="fa fa-caret-down"></i>'; + ui.infobar.permission.label.removeClass('disabled'); + } else { + ui.infobar.permission.label.addClass('disabled'); + } + ui.infobar.permission.label.html(label).attr('title', title); +} //socket.io actions var socket = io.connect(); //overwrite original event for checking login state var on = socket.on; socket.on = function () { - if (!checkLoginStateChanged()) + if (!checkLoginStateChanged() && !needRefresh) on.apply(socket, arguments); }; var emit = socket.emit; socket.emit = function () { - if (!checkLoginStateChanged()) + if (!checkLoginStateChanged() && !needRefresh) emit.apply(socket, arguments); }; socket.on('info', function (data) { @@ -653,17 +885,53 @@ socket.on('connect', function (data) { }); socket.on('version', function (data) { if (data != version) - location.reload(true); + setNeedRefresh(); +}); +socket.on('check', function (data) { + if (data.id == socket.id) { + lastchangetime = data.updatetime; + lastchangeui = ui.infobar.lastchange; + updateLastChange(); + return; + } + var currentHash = md5(LZString.compressToUTF16(editor.getValue())); + var hashMismatch = (currentHash != data.hash); + if (hashMismatch) + socket.emit('refresh'); + else { + lastchangetime = data.updatetime; + lastchangeui = ui.infobar.lastchange; + updateLastChange(); + } }); +socket.on('permission', function (data) { + permission = data.permission; + checkPermission(); +}); +var otk = null; +var owner = null; +var permission = null; socket.on('refresh', function (data) { + var currentHash = md5(LZString.compressToUTF16(editor.getValue())); + var hashMismatch = (currentHash != data.hash); saveInfo(); - var body = data.body; - body = LZString.decompressFromUTF16(body); - if (body) - editor.setValue(body); - else - editor.setValue(""); + otk = data.otk; + owner = data.owner; + permission = data.permission; + + if (hashMismatch) { + var body = data.body; + body = LZString.decompressFromUTF16(body); + if (body) + editor.setValue(body); + else + editor.setValue(""); + } + + lastchangetime = data.updatetime; + lastchangeui = ui.infobar.lastchange; + updateLastChange(); if (!loaded) { editor.clearHistory(); @@ -673,9 +941,19 @@ socket.on('refresh', function (data) { loaded = true; emitUserStatus(); //send first user status updateOnlineStatus(); //update first online status + setTimeout(function () { + //work around editor not refresh + editor.refresh(); + //work around cursor not refresh + for (var i = 0; i < onlineUsers.length; i++) { + buildCursor(onlineUsers[i]); + } + //work around might not scroll to hash + scrollToHash(); + }, 1); } else { //if current doc is equal to the doc before disconnect - if (LZString.compressToUTF16(editor.getValue()) !== data.body) + if (hashMismatch) editor.clearHistory(); else { if (lastInfo.history) @@ -684,21 +962,58 @@ socket.on('refresh', function (data) { lastInfo.history = null; } - updateView(); + if (hashMismatch) + updateView(); if (editor.getOption('readOnly')) editor.setOption('readOnly', false); restoreInfo(); + checkPermission(); }); + +var changeStack = []; +var changeBusy = false; + socket.on('change', function (data) { data = LZString.decompressFromUTF16(data); data = JSON.parse(data); - editor.replaceRange(data.text, data.from, data.to, "ignoreHistory"); - isDirty = true; - clearTimeout(finishChangeTimer); - finishChangeTimer = setTimeout(finishChange, finishChangeDelay); + changeStack.push(data); + if (!changeBusy) + executeChange(); }); + +function executeChange() { + if (changeStack.length > 0) { + changeBusy = true; + var data = changeStack.shift(); + if (data.otk != otk) { + var found = false; + for (var i = 0, l = changeStack.length; i < l; i++) { + if (changeStack[i].otk == otk) { + changeStack.unshift(data); + data = changeStack[i]; + found = true; + break; + } + } + if (!found) { + socket.emit('refresh'); + changeBusy = false; + return; + } + } + otk = data.nextotk; + if (data.id == personalInfo.id) + editor.replaceRange(data.text, data.from, data.to, 'self::' + data.origin); + else + editor.replaceRange(data.text, data.from, data.to, "ignoreHistory"); + executeChange(); + } else { + changeBusy = false; + } +} + socket.on('online users', function (data) { data = LZString.decompressFromUTF16(data); data = JSON.parse(data); @@ -795,7 +1110,7 @@ var onlineUserList = new List('online-user-list', options); var shortOnlineUserList = new List('short-online-user-list', options); function updateOnlineStatus() { - if (!loaded) return; + if (!loaded || !socket.connected) return; var _onlineUsers = deduplicateOnlineUsers(onlineUsers); showStatus(statusType.online, _onlineUsers.length); var items = onlineUserList.items; @@ -851,7 +1166,7 @@ function sortOnlineUserList(list) { var userbIsSelf = (userb.id == personalInfo.id || (userb.login && userb.userid == personalInfo.userid)); if (useraIsSelf && !userbIsSelf) { return -1; - } else if(!useraIsSelf && userbIsSelf) { + } else if (!useraIsSelf && userbIsSelf) { return 1; } else { if (usera.login && !userb.login) @@ -975,7 +1290,7 @@ function checkCursorTag(coord, ele) { var offsetTop = defaultTextHeight; if (width > 0 && height > 0) { if (left + width + offsetLeft > editorWidth - curosrtagMargin) { - offsetLeft = -(width + 4); + offsetLeft = -(width + 10); } if (top + height + offsetTop > Math.max(viewportHeight, editorHeight) + curosrtagMargin && top - height > curosrtagMargin) { offsetTop = -(height); @@ -1124,16 +1439,61 @@ function buildCursor(user) { editor.on('beforeChange', function (cm, change) { if (debug) console.debug(change); + var self = change.origin.split('self::'); + if (self.length == 2) { + change.origin = self[1]; + self = true; + } else { + self = false; + } + if (self) { + change.canceled = true; + } else { + var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) != -1); + if (!isIgnoreEmitEvent) { + switch (permission) { + case "freely": + //na + break; + case "editable": + if (!personalInfo.login) { + change.canceled = true; + $('.signin-modal').modal('show'); + } + break; + case "locked": + if (personalInfo.userid != owner) { + change.canceled = true; + $('.locked-modal').modal('show'); + } + break; + } + } + } }); + +var ignoreEmitEvents = ['setValue', 'ignoreHistory']; editor.on('change', function (i, op) { if (debug) console.debug(op); - if (op.origin != 'setValue' && op.origin != 'ignoreHistory') { - socket.emit('change', LZString.compressToUTF16(JSON.stringify(op))); + var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(op.origin) != -1); + if (!isIgnoreEmitEvent) { + var out = { + text: op.text, + from: op.from, + to: op.to, + origin: op.origin + }; + socket.emit('change', LZString.compressToUTF16(JSON.stringify(out))); } isDirty = true; - clearTimeout(doneTypingTimer); - doneTypingTimer = setTimeout(doneTyping, doneTypingDelay); + clearTimeout(finishChangeTimer); + finishChangeTimer = setTimeout(function () { + if (!isIgnoreEmitEvent) + finishChange(true); + else + finishChange(false); + }, finishChangeDelay); }); editor.on('focus', function (cm) { for (var i = 0; i < onlineUsers.length; i++) { @@ -1236,22 +1596,17 @@ function restoreInfo() { } //view actions -var doneTypingTimer = null; var finishChangeTimer = null; -var input = editor.getInputField(); -//user is "finished typing," do something -function doneTyping() { - emitRefresh(); - updateView(); -} -function finishChange() { +function finishChange(emit) { + if (emit) + emitUpdate(); updateView(); } -function emitRefresh() { +function emitUpdate() { var value = editor.getValue(); - socket.emit('refresh', LZString.compressToUTF16(value)); + socket.emit('update', LZString.compressToUTF16(value)); } var lastResult = null; @@ -1267,6 +1622,11 @@ function updateView() { updateDataAttrs(result, ui.area.markdown.children().toArray()); lastResult = $(result).clone(); finishView(ui.area.view); + autoLinkify(ui.area.view); + generateToc('toc'); + generateToc('toc-affix'); + generateScrollspy(); + smoothHashScroll(); writeHistory(ui.area.markdown); isDirty = false; clearMap(); @@ -1535,7 +1895,7 @@ $(editor.getInputField()) return '$1```' + lang + '=\n\n```'; }, done: function () { - editor.doc.cm.moveV(-1, "line"); + editor.doc.cm.execCommand("goLineUp"); }, context: function () { return isInCode; @@ -1556,12 +1916,19 @@ $(editor.getInputField()) return !isInCode; } }, - { //referral - match: /(^|\n|\s)(\!|\!|\[\])(\w*)$/, + { //blockquote personal info & general info + match: /(?:^|\n|\s)(\>.*)(\[\])(\w*)$/, search: function (term, callback) { - callback($.map(supportReferrals, function (referral) { - return referral.search.indexOf(term) === 0 ? referral.text : null; - })); + var list = []; + $.map(supportBlockquoteTags, function (blockquotetag) { + if (blockquotetag.search.indexOf(term) === 0) + list.push(blockquotetag.command()); + }); + $.map(supportReferrals, function (referral) { + if (referral.search.indexOf(term) === 0) + list.push(referral.text); + }) + callback(list); checkCursorMenu(); }, replace: function (value) { @@ -1571,11 +1938,11 @@ $(editor.getInputField()) return !isInCode; } }, - { //externals - match: /(^|\n|\s)\{\}(\w*)$/, + { //referral + match: /(^|\n|\s)(\!|\!|\[\])(\w*)$/, search: function (term, callback) { - callback($.map(supportExternals, function (external) { - return external.search.indexOf(term) === 0 ? external.text : null; + callback($.map(supportReferrals, function (referral) { + return referral.search.indexOf(term) === 0 ? referral.text : null; })); checkCursorMenu(); }, @@ -1586,36 +1953,16 @@ $(editor.getInputField()) return !isInCode; } }, - { //blockquote personal info & general info - match: /(^|\n|\s|\>.*)\[(\w*)=$/, - search: function (term, callback) { - var list = typeof personalInfo[term] != 'undefined' ? [personalInfo[term]] : []; - $.map(supportGenerals, function (general) { - if (general.search.indexOf(term) === 0) - list.push(general.command()); - }); - callback(list); - checkCursorMenu(); - }, - replace: function (value) { - return '$1[$2=' + value; - }, - context: function (text) { - return !isInCode; - } - }, - { //blockquote quick start tag - match: /(^.*(?!>)\n|)(\>\s{0,1})$/, + { //externals + match: /(^|\n|\s)\{\}(\w*)$/, search: function (term, callback) { - var self = '[name=' + personalInfo.name + '] [time=' + moment().format('llll') + '] [color=' + personalInfo.color + ']'; - callback([self]); + callback($.map(supportExternals, function (external) { + return external.search.indexOf(term) === 0 ? external.text : null; + })); checkCursorMenu(); }, - template: function (value) { - return '[Your name, time, color tags]'; - }, replace: function (value) { - return '$1$2' + value; + return '$1' + value; }, context: function (text) { return !isInCode; diff --git a/public/js/pretty.js b/public/js/pretty.js index 33b97803..6fff4d03 100644 --- a/public/js/pretty.js +++ b/public/js/pretty.js @@ -1,9 +1,81 @@ -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(); +var text = $('<textarea/>').html(markdown.html()).text(); +var result = postProcess(md.render(text)); +markdown.html(result.html()); +$(document.body).show(); finishView(markdown); autoLinkify(markdown); -scrollToHash();
\ No newline at end of file +generateToc('toc'); +generateToc('toc-affix'); +smoothHashScroll(); +lastchangetime = $('.ui-lastchange').text(); +lastchangeui = $('.ui-lastchange'); +updateLastChange(); +var url = window.location.pathname; +$('.ui-edit').attr('href', url + '/edit'); +var toc = $('.ui-toc'); +var tocAffix = $('.ui-affix-toc'); +var tocDropdown = $('.ui-toc-dropdown'); +//toc +tocDropdown.click(function (e) { + e.stopPropagation(); +}); + +var enoughForAffixToc = true; + +function generateScrollspy() { + $(document.body).scrollspy({ + target: '' + }); + $(document.body).scrollspy('refresh'); + if (enoughForAffixToc) { + toc.hide(); + tocAffix.show(); + } else { + tocAffix.hide(); + toc.show(); + } + $(document.body).scroll(); +} + +function windowResize() { + //toc right + var paddingRight = parseFloat(markdown.css('padding-right')); + var right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight)); + toc.css('right', right + 'px'); + //affix toc left + var newbool; + var rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2; + //for ipad or wider device + if (rightMargin >= 133) { + newbool = true; + var affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2; + var left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin; + tocAffix.css('left', left + 'px'); + } else { + newbool = false; + } + if (newbool != enoughForAffixToc) { + enoughForAffixToc = newbool; + generateScrollspy(); + } +} +$(window).resize(function () { + windowResize(); +}); +$(document).ready(function () { + windowResize(); + generateScrollspy(); +}); + +function scrollToTop() { + $(document.body).animate({ + scrollTop: 0 + }, 100, "linear"); +} + +function scrollToBottom() { + $(document.body).animate({ + scrollTop: $(document.body)[0].scrollHeight + }, 100, "linear"); +}
\ No newline at end of file diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js index 4dee0996..48829a4a 100644 --- a/public/js/syncscroll.js +++ b/public/js/syncscroll.js @@ -8,7 +8,7 @@ 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 class="raw part" data-startline="' + startline + '" data-endline="' + endline + '">\n'; } return '<blockquote>\n'; }; @@ -55,9 +55,9 @@ 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 tokens[idx].tight ? '' : '<p class="part" data-startline="' + startline + '" data-endline="' + endline + '">'; } - return ''; + return tokens[idx].tight ? '' : '<p>'; }; md.renderer.rules.heading_open = function (tokens, idx) { @@ -106,7 +106,7 @@ md.renderer.rules.fence = function (tokens, idx, options, env, self) { } langName = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(Remarkable.utils.unescapeMd(fenceName))); - langClass = ' class="' + langPrefix + langName + '"'; + langClass = ' class="' + langPrefix + langName.replace('=', '') + ' hljs"'; } if (options.highlight) { @@ -193,7 +193,8 @@ function buildMapInner(syncBack) { 'line-height': textarea.css('line-height'), 'word-wrap': wrap.css('word-wrap'), 'white-space': wrap.css('white-space'), - 'word-break': wrap.css('word-break') + 'word-break': wrap.css('word-break'), + 'tab-size': '38px' }).appendTo('body'); offset = ui.area.view.scrollTop() - ui.area.view.offset().top; @@ -313,7 +314,7 @@ function syncScrollToView(event, _lineNo) { var textHeight = editor.defaultTextHeight(); lineNo = Math.floor(scrollInfo.top / textHeight); //if reach bottom, then scroll to end - if (scrollInfo.top + scrollInfo.clientHeight >= scrollInfo.height - defaultTextHeight) { + if (scrollInfo.height > scrollInfo.clientHeight && scrollInfo.top + scrollInfo.clientHeight >= scrollInfo.height - defaultTextHeight) { posTo = ui.area.view[0].scrollHeight - ui.area.view.height(); } else { topDiffPercent = (scrollInfo.top % textHeight) / textHeight; |