From f7f8c901f4bc39c3ed0a2bdfe1cbaa1ee6957999 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 1 Jun 2015 18:04:25 +0800 Subject: Marked as 0.2.9 --- public/js/common.js | 36 +- public/js/cover.js | 38 ++ public/js/extra.js | 29 +- public/js/history.js | 18 +- public/js/index.js | 985 +++++++++++++++++++++++++++++++++++++++++++----- public/js/syncscroll.js | 56 ++- 6 files changed, 1036 insertions(+), 126 deletions(-) (limited to 'public/js') diff --git a/public/js/common.js b/public/js/common.js index 37591d36..e6928e98 100644 --- a/public/js/common.js +++ b/public/js/common.js @@ -3,38 +3,62 @@ var domain = 'change this'; var checkAuth = false; var profile = null; var lastLoginState = getLoginState(); +var lastUserId = getUserId(); var loginStateChangeEvent = null; function resetCheckAuth() { checkAuth = false; } -function setLoginState(bool) { +function setLoginState(bool, id) { Cookies.set('loginstate', bool, { expires: 14 }); - if (loginStateChangeEvent && bool != lastLoginState) - loginStateChangeEvent(); + if (id) { + Cookies.set('userid', id, { + expires: 14 + }); + } else { + Cookies.remove('userid'); + } lastLoginState = bool; + lastUserId = id; + checkLoginStateChanged(); +} + +function checkLoginStateChanged() { + if (getLoginState() != lastLoginState || getUserId() != lastUserId) { + if(loginStateChangeEvent) + loginStateChangeEvent(); + return true; + } else { + return false; + } } function getLoginState() { return Cookies.get('loginstate') === "true"; } +function getUserId() { + return Cookies.get('userid'); +} + function clearLoginState() { Cookies.remove('loginstate'); } function checkIfAuth(yesCallback, noCallback) { var cookieLoginState = getLoginState(); + if (checkLoginStateChanged()) + checkAuth = false; if (!checkAuth || typeof cookieLoginState == 'undefined') { $.get('/me') .done(function (data) { if (data && data.status == 'ok') { profile = data; yesCallback(profile); - setLoginState(true); + setLoginState(true, data.id); } else { noCallback(); setLoginState(false); @@ -43,8 +67,10 @@ function checkIfAuth(yesCallback, noCallback) { .fail(function () { noCallback(); setLoginState(false); + }) + .always(function () { + checkAuth = true; }); - checkAuth = true; } else if (cookieLoginState) { yesCallback(profile); } else { diff --git a/public/js/cover.js b/public/js/cover.js index 24ba605c..322768bf 100644 --- a/public/js/cover.js +++ b/public/js/cover.js @@ -229,6 +229,44 @@ var source = $("#template").html(); var template = Handlebars.compile(source); var context = { release: [ + { + version: "0.2.9", + tag: "wildfire", + date: moment("201505301400", 'YYYYMMDDhhmm').fromNow(), + detail: [ + { + title: "Features", + item: [ + "+ Support text auto complete", + "+ Support cursor tag and random last name", + "+ Support online user list", + "+ Support show user info in blockquote" + ] + }, + { + title: "Enhancements", + item: [ + "* Added more code highlighting support", + "* Added more continue list support", + "* Adjust menu and history filter UI for better UX", + "* Adjust sync scoll animte to gain performance", + "* Change compression method of dynamic data", + "* Optimized render script" + ] + }, + { + title: "Fixes", + item: [ + "* Access history fallback might get wrong", + "* Sync scroll not accurate", + "* Sync scroll reach bottom range too much", + "* Detect login state change not accurate", + "* Detect editor focus not accurate", + "* Server not handle some editor events" + ] + } + ] + }, { version: "0.2.8", tag: "flame", diff --git a/public/js/extra.js b/public/js/extra.js index 05fa4704..495c5677 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -65,6 +65,7 @@ function finishView(view) { 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) { @@ -101,6 +102,18 @@ function finishView(view) { //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 = $('
' + code + '
'); @@ -121,6 +134,20 @@ function postProcess(code) { 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, ''); + html = html.replace(nameandtimeregex, ' $1 $2'); + html = html.replace(nameregex, ' $1'); + html = html.replace(timeregex, ' $1'); + $(value).html(html); + }); + var blockquotecolor = result.find("blockquote .color"); + blockquotecolor.each(function (key, value) { + $(value).closest("blockquote").css('border-left-color', $(value).attr('data-color')); + }); return result; } @@ -195,7 +222,7 @@ function highlightRender(code, lang) { if (/\=$/.test(lang)) { var lines = result.value.split('\n'); var linenumbers = []; - for (var i = 0; i < lines.length; i++) { + for (var i = 0; i < lines.length - 1; i++) { linenumbers[i] = "
" + (i + 1) + "
"; } var linegutter = "
" + linenumbers.join('\n') + "
"; diff --git a/public/js/history.js b/public/js/history.js index 717a7ca4..c5db94c5 100644 --- a/public/js/history.js +++ b/public/js/history.js @@ -47,7 +47,7 @@ function saveHistoryToStorage(notehistory) { if (store.enabled) store.set('notehistory', JSON.stringify(notehistory)); else - saveHistoryToCookie(notehistory); + saveHistoryToStorage(notehistory); } function saveHistoryToCookie(notehistory) { @@ -146,11 +146,14 @@ function writeHistoryToServer(view) { } catch (err) { var notehistory = []; } + if(!notehistory) + notehistory = []; + var newnotehistory = generateHistory(view, notehistory); saveHistoryToServer(newnotehistory); }) .fail(function () { - writeHistoryToCookie(view); + writeHistoryToStorage(view); }); } @@ -160,7 +163,9 @@ function writeHistoryToCookie(view) { } catch (err) { var notehistory = []; } - + if(!notehistory) + notehistory = []; + var newnotehistory = generateHistory(view, notehistory); saveHistoryToCookie(newnotehistory); } @@ -174,6 +179,9 @@ function writeHistoryToStorage(view) { var notehistory = data; } else var notehistory = []; + if(!notehistory) + notehistory = []; + var newnotehistory = generateHistory(view, notehistory); saveHistoryToStorage(newnotehistory); } else { @@ -241,7 +249,7 @@ function getServerHistory(callback) { } }) .fail(function () { - getCookieHistory(callback); + getStorageHistory(callback); }); } @@ -282,7 +290,7 @@ function parseServerToHistory(list, callback) { } }) .fail(function () { - parseCookieToHistory(list, callback); + parseStorageToHistory(list, callback); }); } diff --git a/public/js/index.js b/public/js/index.js index 331251c9..24b38f75 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,11 +1,110 @@ //constant vars //settings -var debug = true; -var version = '0.2.8'; +var debug = false; +var version = '0.2.9'; + +var defaultTextHeight = 18; +var viewportMargin = 20; +var defaultExtraKeys = { + "Enter": "newlineAndIndentContinueMarkdownList" +}; + +var idleTime = 300000; //5 mins var doneTypingDelay = 400; var finishChangeDelay = 400; var cursorActivityDelay = 50; var cursorAnimatePeriod = 100; +var supportCodeModes = ['javascript', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'coffeescript', 'yaml', 'jade', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile']; +var supportHeaders = [ + { + text: '# h1', + search: '#' + }, + { + text: '## h2', + search: '##' + }, + { + text: '### h3', + search: '###' + }, + { + text: '#### h4', + search: '####' + }, + { + text: '##### h5', + search: '#####' + }, + { + text: '###### h6', + search: '######' + }, + { + text: '###### tags: `example`', + search: '###### tags:' + } +]; +var supportReferrals = [ + { + text: '[reference link]', + search: '[]' + }, + { + text: '[reference]: url "title"', + search: '[]:' + }, + { + text: '[^footnote link]', + search: '[^]' + }, + { + text: '[^footnote reference]: url "title"', + search: '[^]:' + }, + { + text: '^[inline footnote]', + search: '^[]' + }, + { + text: '[link text][reference]', + search: '[][]' + }, + { + text: '[link text](url "title")', + search: '[]()' + }, + { + text: '![image text][reference]', + search: '![][]' + }, + { + text: '![image text](url "title")', + search: '![]()' + } +]; +var supportExternals = [ + { + text: '{%youtube youtubeid %}', + search: 'youtube' + }, + { + text: '{%vimeo vimeoid %}', + search: 'vimeo' + }, + { + text: '{%gist gistid %}', + search: 'gist' + } +]; +var supportGenerals = [ + { + command: function () { + return moment().format('llll'); + }, + search: 'time' + } +]; var modeType = { edit: {}, view: {}, @@ -18,7 +117,7 @@ var statusType = { fa: "fa-wifi" }, online: { - msg: "ONLINE: ", + msg: "ONLINE", label: "label-primary", fa: "fa-users" }, @@ -63,6 +162,8 @@ var lastInfo = { }, history: null }; +var personalInfo = {}; +var onlineUsers = []; //editor settings var textit = document.getElementById("textit"); @@ -70,15 +171,16 @@ if (!textit) throw new Error("There was no textit area!"); var editor = CodeMirror.fromTextArea(textit, { mode: 'gfm', keyMap: "sublime", - viewportMargin: 20, + viewportMargin: viewportMargin, styleActiveLine: true, lineNumbers: true, lineWrapping: true, showCursorWhenSelecting: true, + indentUnit: 4, + indentWithTabs: true, + continueComments: "Enter", theme: "monokai", - autofocus: true, inputStyle: "textarea", - scrollbarStyle: "overlay", matchBrackets: true, autoCloseBrackets: true, matchTags: { @@ -87,12 +189,11 @@ var editor = CodeMirror.fromTextArea(textit, { autoCloseTags: true, foldGutter: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], - extraKeys: { - "Enter": "newlineAndIndentContinueMarkdownList" - }, + extraKeys: defaultExtraKeys, readOnly: true }); inlineAttachment.editors.codemirror4.attach(editor); +defaultTextHeight = parseInt($(".CodeMirror").css('line-height')); //ui vars var ui = { @@ -146,9 +247,59 @@ var opts = { left: '50%' // Left position relative to parent }; var spinner = new Spinner(opts).spin(ui.spinner[0]); + +//idle +var idle = new Idle({ + onAway: idleStateChange, + onAwayBack: idleStateChange, + awayTimeout: idleTime +}); +ui.area.codemirror.on('touchstart', function () { + idle.onActive(); +}); + +function idleStateChange() { + emitUserStatus(); + updateOnlineStatus(); +} + +loginStateChangeEvent = function () { + location.reload(true); +} + +//visibility +var wasFocus = false; +Visibility.change(function (e, state) { + var hidden = Visibility.hidden(); + if (hidden) { + if (editorHasFocus()) { + wasFocus = true; + editor.getInputField().blur(); + } + } else { + if (wasFocus) { + editor.focus(); + wasFocus = false; + } + } +}); + //when page ready $(document).ready(function () { + idle.checkAway(); checkResponsive(); + //if in smaller screen, we don't need advanced scrollbar + var scrollbarStyle; + if (visibleXS) { + scrollbarStyle = 'native'; + } else { + scrollbarStyle = 'overlay'; + } + if (scrollbarStyle != editor.getOption('scrollbarStyle')) { + editor.setOption('scrollbarStyle', scrollbarStyle); + clearMap(); + } + checkEditorStyle(); changeMode(currentMode); /* we need this only on touch devices */ if (isTouchDevice) { @@ -174,26 +325,52 @@ $(window).resize(function () { windowResize(); }, windowResizeDelay); }); +//when page unload +$(window).unload(function () { + emitRefresh(); +}); + function windowResize() { checkResponsive(); - clearMap(); - syncScrollToView(); + checkEditorStyle(); + if (loaded) { + editor.setOption('viewportMargin', Infinity); + setTimeout(function () { + clearMap(); + syncScrollToView(); + editor.setOption('viewportMargin', viewportMargin); + }, windowResizeDelay); + } +} + +function editorHasFocus() { + return $(editor.getInputField()).is(":focus"); } + //768-792px have a gap function checkResponsive() { visibleXS = $(".visible-xs").is(":visible"); visibleSM = $(".visible-sm").is(":visible"); visibleMD = $(".visible-md").is(":visible"); visibleLG = $(".visible-lg").is(":visible"); + if (visibleXS && currentMode == modeType.both) - if (editor.hasFocus()) + if (editorHasFocus()) changeMode(modeType.edit); else changeMode(modeType.view); - if (visibleXS) - $('.CodeMirror').css('height', 'auto'); - else - $('.CodeMirror').css('height', ''); + + emitUserStatus(); +} + +function checkEditorStyle() { + var scrollbarStyle = editor.getOption('scrollbarStyle'); + if (scrollbarStyle == 'overlay' || currentMode == modeType.both) { + ui.area.codemirror.css('height', ''); + } else if (scrollbarStyle == 'native') { + ui.area.codemirror.css('height', 'auto'); + $('.CodeMirror-gutters').css('height', $('.CodeMirror-sizer').height()); + } } function showStatus(type, num) { @@ -217,8 +394,8 @@ function showStatus(type, num) { case statusType.online: label.addClass(statusType.online.label); fa.addClass(statusType.online.fa); - shortMsg = " " + num; - msg = statusType.online.msg + num; + shortMsg = num; + msg = num + " " + statusType.online.msg; break; case statusType.offline: label.addClass(statusType.offline.label); @@ -255,6 +432,7 @@ function changeMode(type) { saveInfo(); if (type) currentMode = type; + checkEditorStyle(); var responsiveClass = "col-lg-6 col-md-6 col-sm-6"; var scrollClass = "ui-scrollable"; ui.area.codemirror.removeClass(scrollClass); @@ -282,7 +460,7 @@ function changeMode(type) { break; } if (currentMode != modeType.view && visibleLG) { - editor.focus(); + //editor.focus(); editor.refresh(); } else { editor.getInputField().blur(); @@ -291,6 +469,8 @@ function changeMode(type) { updateView(); restoreInfo(); + windowResize(); + ui.toolbar.both.removeClass("active"); ui.toolbar.edit.removeClass("active"); ui.toolbar.view.removeClass("active"); @@ -436,6 +616,17 @@ ui.toolbar.both.click(function () { //socket.io actions var socket = io.connect(); +//overwrite original event for checking login state +var on = socket.on; +socket.on = function () { + if (!checkLoginStateChanged()) + on.apply(socket, arguments); +}; +var emit = socket.emit; +socket.emit = function () { + if (!checkLoginStateChanged()) + emit.apply(socket, arguments); +}; socket.on('info', function (data) { console.error(data); location.href = "./404.html"; @@ -449,7 +640,14 @@ socket.on('disconnect', function (data) { if (!editor.getOption('readOnly')) editor.setOption('readOnly', true); }); +socket.on('reconnect', function (data) { + //sync back any change in offline + emitUserStatus(true); + cursorActivity(); + socket.emit('online users'); +}); socket.on('connect', function (data) { + personalInfo['id'] = socket.id; showStatus(statusType.connected); socket.emit('version'); }); @@ -461,7 +659,7 @@ socket.on('refresh', function (data) { saveInfo(); var body = data.body; - body = LZString.decompressFromBase64(body); + body = LZString.decompressFromUTF16(body); if (body) editor.setValue(body); else @@ -473,8 +671,11 @@ socket.on('refresh', function (data) { ui.content.fadeIn(); changeMode(); loaded = true; + emitUserStatus(); //send first user status + updateOnlineStatus(); //update first online status } else { - if (LZString.compressToBase64(editor.getValue()) !== data.body) + //if current doc is equal to the doc before disconnect + if (LZString.compressToUTF16(editor.getValue()) !== data.body) editor.clearHistory(); else { if (lastInfo.history) @@ -491,7 +692,7 @@ socket.on('refresh', function (data) { restoreInfo(); }); socket.on('change', function (data) { - data = LZString.decompressFromBase64(data); + data = LZString.decompressFromUTF16(data); data = JSON.parse(data); editor.replaceRange(data.text, data.from, data.to, "ignoreHistory"); isDirty = true; @@ -499,9 +700,12 @@ socket.on('change', function (data) { finishChangeTimer = setTimeout(finishChange, finishChangeDelay); }); socket.on('online users', function (data) { + data = LZString.decompressFromUTF16(data); + data = JSON.parse(data); if (debug) console.debug(data); - showStatus(statusType.online, data.count); + onlineUsers = data.users; + updateOnlineStatus(); $('.other-cursors').children().each(function (key, value) { var found = false; for (var i = 0; i < data.users.length; i++) { @@ -510,85 +714,409 @@ socket.on('online users', function (data) { found = true; } if (!found) - $(this).remove(); + $(this).stop(true).fadeOut("normal", function () { + $(this).remove(); + }); }); for (var i = 0; i < data.users.length; i++) { var user = data.users[i]; if (user.id != socket.id) - buildCursor(user.id, user.color, user.cursor); + buildCursor(user); + else + personalInfo = user; + } +}); +socket.on('user status', function (data) { + if (debug) + console.debug(data); + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == data.id) { + onlineUsers[i] = data; + } } + updateOnlineStatus(); + if (data.id != socket.id) + buildCursor(data); }); socket.on('cursor focus', function (data) { if (debug) console.debug(data); + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == data.id) { + onlineUsers[i].cursor = data; + } + } + if (data.id != socket.id) + buildCursor(data); + //force show var cursor = $('#' + data.id); if (cursor.length > 0) { - cursor.fadeIn(); - } else { - if (data.id != socket.id) - buildCursor(data.id, data.color, data.cursor); + cursor.stop(true).fadeIn(); } }); socket.on('cursor activity', function (data) { if (debug) console.debug(data); + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == data.id) { + onlineUsers[i].cursor = data; + } + } if (data.id != socket.id) - buildCursor(data.id, data.color, data.cursor); + buildCursor(data); }); socket.on('cursor blur', function (data) { if (debug) console.debug(data); + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == data.id) { + onlineUsers[i].cursor = null; + } + } + if (data.id != socket.id) + buildCursor(data); + //force hide var cursor = $('#' + data.id); if (cursor.length > 0) { - cursor.fadeOut(); + cursor.stop(true).fadeOut(); } }); -function emitUserStatus() { - checkIfAuth( - function (data) { - socket.emit('user status', { - login: true - }); - }, - function () { - socket.emit('user status', { - login: false - }); +var options = { + valueNames: ['id', 'name'], + item: '
  • \ + \ + \ + \ + \ +
  • ' +}; +var onlineUserList = new List('online-user-list', options); +var shortOnlineUserList = new List('short-online-user-list', options); + +function updateOnlineStatus() { + if (!loaded) return; + var _onlineUsers = deduplicateOnlineUsers(onlineUsers); + showStatus(statusType.online, _onlineUsers.length); + var items = onlineUserList.items; + //update or remove current list items + for (var i = 0; i < items.length; i++) { + var found = false; + var foundindex = null; + for (var j = 0; j < _onlineUsers.length; j++) { + if (items[i].values().id == _onlineUsers[j].id) { + foundindex = j; + found = true; + break; + } + } + var id = items[i].values().id; + if (found) { + onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]); + shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]); + } else { + onlineUserList.remove('id', id); + shortOnlineUserList.remove('id', id); + } + } + //add not in list items + for (var i = 0; i < _onlineUsers.length; i++) { + var found = false; + for (var j = 0; j < items.length; j++) { + if (items[j].values().id == _onlineUsers[i].id) { + found = true; + break; + } } - ); + if (!found) { + onlineUserList.add(_onlineUsers[i]); + shortOnlineUserList.add(_onlineUsers[i]); + } + } + //sorting + sortOnlineUserList(onlineUserList); + sortOnlineUserList(shortOnlineUserList); + //render list items + renderUserStatusList(onlineUserList); + renderUserStatusList(shortOnlineUserList); +} + +function sortOnlineUserList(list) { + //sort order by isSelf, login state, idle state, alphabet name, color brightness + list.sort('', { + sortFunction: function (a, b) { + var usera = a.values(); + var userb = b.values(); + var useraIsSelf = (usera.id == personalInfo.id || (usera.login && usera.userid == personalInfo.userid)); + var userbIsSelf = (userb.id == personalInfo.id || (userb.login && userb.userid == personalInfo.userid)); + if (useraIsSelf && !userbIsSelf) { + return -1; + } else if(!useraIsSelf && userbIsSelf) { + return 1; + } else { + if (usera.login && !userb.login) + return -1; + else if (!usera.login && userb.login) + return 1; + else { + if (!usera.idle && userb.idle) + return -1; + else if (usera.idle && !userb.idle) + return 1; + else { + if (usera.name.toLowerCase() < userb.name.toLowerCase()) { + return -1; + } else if (usera.name.toLowerCase() > userb.name.toLowerCase()) { + return 1; + } else { + if (usera.color.toLowerCase() < userb.color.toLowerCase()) + return -1; + else if (usera.color.toLowerCase() > userb.color.toLowerCase()) + return 1; + else + return 0; + } + } + } + } + } + }); } -function buildCursor(id, color, pos) { - if (!pos) return; +function renderUserStatusList(list) { + var items = list.items; + for (var j = 0; j < items.length; j++) { + var item = items[j]; + var userstatus = $(item.elm).find('.ui-user-status'); + var usericon = $(item.elm).find('.ui-user-icon'); + usericon.css('color', item.values().color); + userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle'); + if (item.values().idle) + userstatus.addClass('ui-user-status-idle'); + else + userstatus.addClass('ui-user-status-online'); + } +} + +function deduplicateOnlineUsers(list) { + var _onlineUsers = []; + for (var i = 0; i < list.length; i++) { + var user = $.extend({}, list[i]); + if (!user.userid) + _onlineUsers.push(user); + else { + var found = false; + for (var j = 0; j < _onlineUsers.length; j++) { + if (_onlineUsers[j].userid == user.userid) { + //keep self color when login + if (user.id == personalInfo.id) { + _onlineUsers[j].color = user.color; + } + //keep idle state if any of self client not idle + if (!user.idle) { + _onlineUsers[j].idle = user.idle; + } + found = true; + break; + } + } + if (!found) + _onlineUsers.push(user); + } + } + return _onlineUsers; +} + +var userStatusCache = null; + +function emitUserStatus(force) { + if (!loaded) return; + var type = null; + if (visibleXS) + type = 'xs'; + else if (visibleSM) + type = 'sm'; + else if (visibleMD) + type = 'md'; + else if (visibleLG) + type = 'lg'; + + personalInfo['idle'] = idle.isAway; + personalInfo['type'] = type; + + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == personalInfo.id) { + onlineUsers[i] = personalInfo; + } + } + + var userStatus = { + idle: idle.isAway, + type: type + }; + + if (force || JSON.stringify(userStatus) != JSON.stringify(userStatusCache)) { + socket.emit('user status', userStatus); + userStatusCache = userStatus; + } +} + +function checkCursorTag(coord, ele) { + var curosrtagMargin = 60; + var viewport = editor.getViewport(); + var viewportHeight = (viewport.to - viewport.from) * editor.defaultTextHeight(); + var editorWidth = ui.area.codemirror.width(); + var editorHeight = ui.area.codemirror.height(); + var width = ele.width(); + var height = ele.height(); + var left = coord.left; + var top = coord.top; + var offsetLeft = -3; + var offsetTop = defaultTextHeight; + if (width > 0 && height > 0) { + if (left + width + offsetLeft > editorWidth - curosrtagMargin) { + offsetLeft = -(width + 4); + } + if (top + height + offsetTop > Math.max(viewportHeight, editorHeight) + curosrtagMargin && top - height > curosrtagMargin) { + offsetTop = -(height); + } + } + ele[0].style.left = offsetLeft + 'px'; + ele[0].style.top = offsetTop + 'px'; +} + +function buildCursor(user) { + if (!user.cursor) return; + var coord = editor.charCoords(user.cursor, 'windows'); + coord.left = coord.left < 4 ? 4 : coord.left; + coord.top = coord.top < 0 ? 0 : coord.top; + var iconClass = 'fa-user'; + switch (user.type) { + case 'xs': + iconClass = 'fa-mobile'; + break; + case 'sm': + iconClass = 'fa-tablet'; + break; + case 'md': + iconClass = 'fa-desktop'; + break; + case 'lg': + iconClass = 'fa-desktop'; + break; + } if ($('.other-cursors').length <= 0) { $("
    ").insertAfter('.CodeMirror-cursors'); } - if ($('#' + id).length <= 0) { - var cursor = $('
     
    '); - //console.debug(pos); - cursor.attr('data-line', pos.line); - cursor.attr('data-ch', pos.ch); - var coord = editor.charCoords(pos, 'windows'); + if ($('#' + user.id).length <= 0) { + var cursor = $(''); + cursor.attr('data-line', user.cursor.line); + cursor.attr('data-ch', user.cursor.ch); + cursor.attr('data-offset-left', 0); + cursor.attr('data-offset-top', 0); + + var cursorbar = $('
     
    '); + cursorbar[0].style.height = defaultTextHeight + 'px'; + cursorbar[0].style.borderLeft = '2px solid ' + user.color; + + var icon = ''; + + var cursortag = $('
    ' + icon + ' ' + user.name + '
    '); + //cursortag[0].style.background = color; + cursortag[0].style.color = user.color; + + cursor.attr('data-mode', 'state'); + cursor.hover( + function () { + if (cursor.attr('data-mode') == 'hover') + cursortag.stop(true).fadeIn("fast"); + }, + function () { + if (cursor.attr('data-mode') == 'hover') + cursortag.stop(true).fadeOut("fast"); + }); + + function switchMode(ele) { + if (ele.attr('data-mode') == 'state') + ele.attr('data-mode', 'hover'); + else if (ele.attr('data-mode') == 'hover') + ele.attr('data-mode', 'state'); + } + + function switchTag(ele) { + if (ele.css('display') === 'none') + ele.stop(true).fadeIn("fast"); + else + ele.stop(true).fadeOut("fast"); + } + var hideCursorTagDelay = 2000; + var hideCursorTagTimer = null; + + function hideCursorTag() { + if (cursor.attr('data-mode') == 'hover') + cursortag.fadeOut("fast"); + } + cursor.on('touchstart', function (e) { + var display = cursortag.css('display'); + cursortag.stop(true).fadeIn("fast"); + clearTimeout(hideCursorTagTimer); + hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay); + if (display === 'none') { + e.preventDefault(); + e.stopPropagation(); + } + }); + cursortag.on('mousedown touchstart', function (e) { + if (cursor.attr('data-mode') == 'state') + switchTag(cursortag); + switchMode(cursor); + e.preventDefault(); + e.stopPropagation(); + }); + + cursor.append(cursorbar); + cursor.append(cursortag); + cursor[0].style.left = coord.left + 'px'; cursor[0].style.top = coord.top + 'px'; - cursor[0].style.height = '18px'; - cursor[0].style.borderLeft = '2px solid ' + color; $('.other-cursors').append(cursor); - cursor.hide().fadeIn(); + + if (!user.idle) + cursor.stop(true).fadeIn(); + + checkCursorTag(coord, cursortag); } else { - var cursor = $('#' + id); - cursor.attr('data-line', pos.line); - cursor.attr('data-ch', pos.ch); - var coord = editor.charCoords(pos, 'windows'); - cursor.stop(true).css('opacity', 1).animate({ - "left": coord.left, - "top": coord.top - }, cursorAnimatePeriod); - //cursor[0].style.left = coord.left + 'px'; - //cursor[0].style.top = coord.top + 'px'; - cursor[0].style.height = '18px'; - cursor[0].style.borderLeft = '2px solid ' + color; + var cursor = $('#' + user.id); + cursor.attr('data-line', user.cursor.line); + cursor.attr('data-ch', user.cursor.ch); + + var cursorbar = cursor.find('.cursorbar'); + cursorbar[0].style.height = defaultTextHeight + 'px'; + cursorbar[0].style.borderLeft = '2px solid ' + user.color; + + var cursortag = cursor.find('.cursortag'); + cursortag.find('i').removeClass().addClass('fa').addClass(iconClass); + cursortag.find(".name").text(user.name); + + if (cursor.css('display') === 'none') { + cursor[0].style.left = coord.left + 'px'; + cursor[0].style.top = coord.top + 'px'; + } else { + cursor.animate({ + "left": coord.left, + "top": coord.top + }, { + duration: cursorAnimatePeriod, + queue: false + }); + } + + if (user.idle && cursor.css('display') !== 'none') + cursor.stop(true).fadeOut(); + else if (!user.idle && cursor.css('display') === 'none') + cursor.stop(true).fadeIn(); + + checkCursorTag(coord, cursortag); } } @@ -601,13 +1129,19 @@ editor.on('change', function (i, op) { if (debug) console.debug(op); if (op.origin != 'setValue' && op.origin != 'ignoreHistory') { - socket.emit('change', LZString.compressToBase64(JSON.stringify(op))); + socket.emit('change', LZString.compressToUTF16(JSON.stringify(op))); } isDirty = true; clearTimeout(doneTypingTimer); doneTypingTimer = setTimeout(doneTyping, doneTypingDelay); }); editor.on('focus', function (cm) { + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == personalInfo.id) { + onlineUsers[i].cursor = editor.getCursor(); + } + } + personalInfo['cursor'] = editor.getCursor(); socket.emit('cursor focus', editor.getCursor()); }); var cursorActivityTimer = null; @@ -617,20 +1151,38 @@ editor.on('cursorActivity', function (cm) { }); function cursorActivity() { - socket.emit('cursor activity', editor.getCursor()); + if (editorHasFocus() && !Visibility.hidden()) { + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == personalInfo.id) { + onlineUsers[i].cursor = editor.getCursor(); + } + } + personalInfo['cursor'] = editor.getCursor(); + socket.emit('cursor activity', editor.getCursor()); + } } editor.on('blur', function (cm) { + for (var i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id == personalInfo.id) { + onlineUsers[i].cursor = null; + } + } + personalInfo['cursor'] = null; socket.emit('cursor blur'); }); function saveInfo() { + var scrollbarStyle = editor.getOption('scrollbarStyle'); var left = $(document.body).scrollLeft(); var top = $(document.body).scrollTop(); switch (currentMode) { case modeType.edit: - //lastInfo.edit.scroll.left = left; - //lastInfo.edit.scroll.top = top; - lastInfo.edit.scroll = editor.getScrollInfo(); + if (scrollbarStyle == 'native') { + lastInfo.edit.scroll.left = left; + lastInfo.edit.scroll.top = top; + } else { + lastInfo.edit.scroll = editor.getScrollInfo(); + } break; case modeType.view: lastInfo.view.scroll.left = left; @@ -647,6 +1199,7 @@ function saveInfo() { } function restoreInfo() { + var scrollbarStyle = editor.getOption('scrollbarStyle'); if (lastInfo.needRestore) { var line = lastInfo.edit.cursor.line; var ch = lastInfo.edit.cursor.ch; @@ -654,12 +1207,15 @@ function restoreInfo() { switch (currentMode) { case modeType.edit: - //$(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); + if (scrollbarStyle == 'native') { + $(document.body).scrollLeft(lastInfo.edit.scroll.left); + $(document.body).scrollTop(lastInfo.edit.scroll.top); + } else { + 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); @@ -685,15 +1241,19 @@ var finishChangeTimer = null; var input = editor.getInputField(); //user is "finished typing," do something function doneTyping() { + emitRefresh(); updateView(); - var value = editor.getValue(); - socket.emit('refresh', LZString.compressToBase64(value)); } function finishChange() { updateView(); } +function emitRefresh() { + var value = editor.getValue(); + socket.emit('refresh', LZString.compressToUTF16(value)); +} + var lastResult = null; function updateView() { @@ -703,12 +1263,22 @@ function updateView() { //ui.area.markdown.html(result); //finishView(ui.area.markdown); partialUpdate(result, lastResult, ui.area.markdown.children().toArray()); - lastResult = $(result).clone(true); + if (result && lastResult && result.length != lastResult.length) + updateDataAttrs(result, ui.area.markdown.children().toArray()); + lastResult = $(result).clone(); finishView(ui.area.view); writeHistory(ui.area.markdown); isDirty = false; - emitUserStatus(); clearMap(); + buildMap(); +} + +function updateDataAttrs(src, des) { + //sync data attr startline and endline + for (var i = 0; i < src.length; i++) { + copyAttribute(src[i], des[i], 'data-startline'); + copyAttribute(src[i], des[i], 'data-endline'); + } } function partialUpdate(src, tar, des) { @@ -733,8 +1303,8 @@ function partialUpdate(src, tar, des) { var end = 0; //find diff start position for (var i = 0; i < tar.length; i++) { - copyAttribute(src[i], des[i], 'data-startline'); - copyAttribute(src[i], des[i], 'data-endline'); + //copyAttribute(src[i], des[i], 'data-startline'); + //copyAttribute(src[i], des[i], 'data-endline'); var rawSrc = cloneAndRemoveDataAttr(src[i]); var rawTar = cloneAndRemoveDataAttr(tar[i]); if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { @@ -746,8 +1316,8 @@ function partialUpdate(src, tar, des) { var srcEnd = 0; var tarEnd = 0; for (var i = 0; i < src.length; i++) { - copyAttribute(src[i], des[i], 'data-startline'); - copyAttribute(src[i], des[i], 'data-endline'); + //copyAttribute(src[i], des[i], 'data-startline'); + //copyAttribute(src[i], des[i], 'data-endline'); var rawSrc = cloneAndRemoveDataAttr(src[i]); var rawTar = cloneAndRemoveDataAttr(tar[i]); if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { @@ -759,8 +1329,8 @@ function partialUpdate(src, tar, des) { 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'); - copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); + //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); + //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]); var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]); if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { @@ -772,8 +1342,8 @@ function partialUpdate(src, tar, des) { 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'); - copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); + //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); + //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]); var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]); if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) { @@ -805,12 +1375,12 @@ function partialUpdate(src, tar, des) { var repeatDiff = Math.abs(srcEnd - tarEnd) - 1; //push new elements var newElements = []; - if(srcEnd >= start) { + if (srcEnd >= start) { for (var j = start; j <= srcEnd; j++) { if (!src[j]) continue; newElements.push(src[j].outerHTML); } - } else if(repeatAdd) { + } else if (repeatAdd) { for (var j = srcEnd - repeatDiff; j <= srcEnd; j++) { if (!des[j]) continue; newElements.push(des[j].outerHTML); @@ -818,12 +1388,12 @@ function partialUpdate(src, tar, des) { } //push remove elements var removeElements = []; - if(tarEnd >= start) { + if (tarEnd >= start) { for (var j = start; j <= tarEnd; j++) { if (!des[j]) continue; removeElements.push(des[j]); } - } else if(!repeatAdd) { + } else if (!repeatAdd) { for (var j = start; j <= start + repeatDiff; j++) { if (!des[j]) continue; removeElements.push(des[j]); @@ -853,7 +1423,7 @@ function partialUpdate(src, tar, des) { function cloneAndRemoveDataAttr(el) { if (!el) return; - var rawEl = $(el).clone(true)[0]; + var rawEl = $(el).clone()[0]; rawEl.removeAttribute('data-startline'); rawEl.removeAttribute('data-endline'); return rawEl; @@ -862,4 +1432,229 @@ function cloneAndRemoveDataAttr(el) { function copyAttribute(src, des, attr) { if (src && src.getAttribute(attr) && des) des.setAttribute(attr, src.getAttribute(attr)); -} \ No newline at end of file +} + +if ($('.cursor-menu').length <= 0) { + $("
    ").insertAfter('.CodeMirror-cursors'); +} + +var upSideDown = false; +var menuMargin = 60; + +function checkCursorMenu() { + var dropdown = $('.cursor-menu .dropdown-menu'); + var cursor = editor.getCursor(); + var scrollInfo = editor.getScrollInfo(); + if (!dropdown.hasClass('other-cursor')) + dropdown.addClass('other-cursor'); + dropdown.attr('data-line', cursor.line); + dropdown.attr('data-ch', cursor.ch); + var coord = editor.charCoords({ + line: cursor.line, + ch: cursor.ch + }, 'windows'); + var viewport = editor.getViewport(); + var viewportHeight = (viewport.to - viewport.from) * editor.defaultTextHeight(); + var editorWidth = ui.area.codemirror.width(); + var editorHeight = ui.area.codemirror.height(); + var width = dropdown.width(); + var height = dropdown.height(); + var left = coord.left; + var top = coord.top; + var offsetLeft = 0; + var offsetTop = defaultTextHeight; + if (left + width + offsetLeft > editorWidth - menuMargin) + offsetLeft = -(left + width - editorWidth + menuMargin); + if (top + height + offsetTop > Math.max(viewportHeight, editorHeight) + menuMargin && top - height > menuMargin) { + offsetTop = -(height + defaultTextHeight); + upSideDown = true; + } else { + upSideDown = false; + } + dropdown.attr('data-offset-left', offsetLeft); + dropdown.attr('data-offset-top', offsetTop); + dropdown[0].style.left = left + offsetLeft + 'px'; + dropdown[0].style.top = top + offsetTop + 'px'; +} + +var isInCode = false; + +function check(text) { + var cursor = editor.getCursor(); + text = []; + for (var i = 0; i < cursor.line; i++) + text.push(editor.getLine(i)); + text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch); + //console.log(text); + var match; + match = text.match(/`{3,}/g); + if (match && match.length % 2) { + isInCode = true; + } else { + match = text.match(/`/g); + if (match && match.length % 2) { + isInCode = true; + } else { + isInCode = false; + } + } +} + +$(editor.getInputField()) + .textcomplete([ + { // emoji strategy + match: /(?:^|\n|)\B:([\-+\w]*)$/, + search: function (term, callback) { + callback($.map(emojify.emojiNames, function (emoji) { + return emoji.indexOf(term) === 0 ? emoji : null; + })); + checkCursorMenu(); + }, + template: function (value) { + return ' ' + value; + }, + replace: function (value) { + return ':' + value + ':'; + }, + index: 1, + context: function (text) { + check(text); + return !isInCode; + } + }, + { // Code block language strategy + langs: supportCodeModes, + match: /(^|\n)```(\w*)$/, + search: function (term, callback) { + callback($.map(this.langs, function (lang) { + return lang.indexOf(term) === 0 ? lang : null; + })); + checkCursorMenu(); + }, + replace: function (lang) { + return '$1```' + lang + '=\n\n```'; + }, + done: function () { + editor.doc.cm.moveV(-1, "line"); + }, + context: function () { + return isInCode; + } + }, + { //header + match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/, + search: function (term, callback) { + callback($.map(supportHeaders, function (header) { + return header.search.indexOf(term) === 0 ? header.text : null; + })); + checkCursorMenu(); + }, + replace: function (value) { + return '$1' + value; + }, + context: function (text) { + return !isInCode; + } + }, + { //referral + match: /(^|\n|\s)(\!|\!|\[\])(\w*)$/, + search: function (term, callback) { + callback($.map(supportReferrals, function (referral) { + return referral.search.indexOf(term) === 0 ? referral.text : null; + })); + checkCursorMenu(); + }, + replace: function (value) { + return '$1' + value; + }, + context: function (text) { + return !isInCode; + } + }, + { //externals + match: /(^|\n|\s)\{\}(\w*)$/, + search: function (term, callback) { + callback($.map(supportExternals, function (external) { + return external.search.indexOf(term) === 0 ? external.text : null; + })); + checkCursorMenu(); + }, + replace: function (value) { + return '$1' + value; + }, + context: function (text) { + 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})$/, + search: function (term, callback) { + var self = '[name=' + personalInfo.name + '] [time=' + moment().format('llll') + '] [color=' + personalInfo.color + ']'; + callback([self]); + checkCursorMenu(); + }, + template: function (value) { + return '[Your name, time, color tags]'; + }, + replace: function (value) { + return '$1$2' + value; + }, + context: function (text) { + return !isInCode; + } + } +], { + appendTo: $('.cursor-menu') + }) + .on({ + 'textComplete:select': function (e, value, strategy) { + //NA + }, + 'textComplete:show': function (e) { + checkCursorMenu(); + $(this).data('autocompleting', true); + editor.setOption("extraKeys", { + "Up": function () { + return CodeMirror.PASS; + }, + "Right": function () { + editor.doc.cm.execCommand("goCharRight"); + }, + "Down": function () { + return CodeMirror.PASS; + }, + "Left": function () { + editor.doc.cm.execCommand("goCharLeft"); + }, + "Enter": function () { + return CodeMirror.PASS; + }, + "Backspace": function () { + editor.doc.cm.execCommand("delCharBefore"); + checkCursorMenu(); + } + }); + }, + 'textComplete:hide': function (e) { + $(this).data('autocompleting', false); + editor.setOption("extraKeys", defaultExtraKeys); + } + }); \ No newline at end of file diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js index 3d97324c..4dee0996 100644 --- a/public/js/syncscroll.js +++ b/public/js/syncscroll.js @@ -144,10 +144,14 @@ md.renderer.rules.code = function (tokens, idx /*, options, env */ ) { return '' + Remarkable.utils.escapeHtml(tokens[idx].content) + ''; }; +//var editorScrollThrottle = 100; +var buildMapThrottle = 100; + var viewScrolling = false; var viewScrollingDelay = 200; var viewScrollingTimer = null; +//editor.on('scroll', _.throttle(syncScrollToView, editorScrollThrottle)); editor.on('scroll', syncScrollToView); ui.area.view.on('scroll', function () { viewScrolling = true; @@ -168,10 +172,12 @@ function clearMap() { lineHeightMap = null; } +var buildMap = _.throttle(buildMapInner, buildMapThrottle); + // 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() { +function buildMapInner(syncBack) { var i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, sourceLikeDiv, textarea = ui.area.codemirror, wrap = $('.CodeMirror-wrap pre'), @@ -182,8 +188,6 @@ function buildMap() { 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'), @@ -198,21 +202,23 @@ function buildMap() { _lineHeightMap = []; acc = 0; - editor.getValue().split('\n').forEach(function (str) { + var lines = editor.getValue().split('\n'); + for (i = 0; i < lines.length; i++) { + var str = lines[i]; var h, lh; _lineHeightMap.push(acc); if (str.length === 0) { acc++; - return; + continue; } 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; @@ -224,9 +230,10 @@ function buildMap() { nonEmptyList.push(0); _scrollMap[0] = 0; - ui.area.markdown.find('.part').each(function (n, el) { - var $el = $(el), - t = $el.data('startline') - 1; + var parts = ui.area.markdown.find('.part').toArray(); + for (i = 0; i < parts.length; i++) { + var $el = $(parts[i]), + t = $el.attr('data-startline') - 1; if (t === '') { return; } @@ -235,7 +242,7 @@ function buildMap() { nonEmptyList.push(t); } _scrollMap[t] = Math.round($el.offset().top + offset); - }); + } nonEmptyList.push(linesCount); _scrollMap[linesCount] = ui.area.view[0].scrollHeight; @@ -256,6 +263,9 @@ function buildMap() { scrollMap = _scrollMap; lineHeightMap = _lineHeightMap; + + if(loaded && syncBack) + syncScrollToView(); } function getPartByEditorLineNo(lineNo) { @@ -290,20 +300,20 @@ function getEditorLineNoByTop(top) { return null; } -function syncScrollToView(_lineNo) { +function syncScrollToView(event, _lineNo) { + if (currentMode != modeType.both) return; var lineNo, posTo; var scrollInfo = editor.getScrollInfo(); if (!scrollMap || !lineHeightMap) { - buildMap(); + buildMap(true); + return; } - if (typeof _lineNo != "number") { + if (!_lineNo) { 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) { + //if reach bottom, then scroll to end + if (scrollInfo.top + scrollInfo.clientHeight >= scrollInfo.height - defaultTextHeight) { posTo = ui.area.view[0].scrollHeight - ui.area.view.height(); } else { topDiffPercent = (scrollInfo.top % textHeight) / textHeight; @@ -316,12 +326,18 @@ function syncScrollToView(_lineNo) { posTo = scrollMap[lineHeightMap[_lineNo]]; } var posDiff = Math.abs(ui.area.view.scrollTop() - posTo); + var duration = posDiff / 50; + ui.area.view.stop(true, true).animate({ + scrollTop: posTo + }, duration >= 100 ? duration : 100, "linear"); + /* if (posDiff > scrollInfo.clientHeight / 5) { var duration = posDiff / 50; - ui.area.view.stop(true).animate({ + ui.area.view.stop(true, true).animate({ scrollTop: posTo - }, duration >= 50 ? duration : 100, "linear"); + }, duration >= 100 ? duration : 100, "linear"); } else { - ui.area.view.stop(true).scrollTop(posTo); + ui.area.view.stop(true, true).scrollTop(posTo); } + */ } \ No newline at end of file -- cgit v1.2.3