summaryrefslogtreecommitdiff
path: root/public/js
diff options
context:
space:
mode:
authorWu Cheng-Han2015-06-01 18:04:25 +0800
committerWu Cheng-Han2015-06-01 18:04:25 +0800
commitf7f8c901f4bc39c3ed0a2bdfe1cbaa1ee6957999 (patch)
tree1bb09442906fa8e258c670c87491c64b6de27e68 /public/js
parent4e64583a0b6175d2c9a6729ffde1472dd55d389c (diff)
Marked as 0.2.9
Diffstat (limited to 'public/js')
-rw-r--r--public/js/common.js36
-rw-r--r--public/js/cover.js38
-rw-r--r--public/js/extra.js29
-rw-r--r--public/js/history.js18
-rw-r--r--public/js/index.js985
-rw-r--r--public/js/syncscroll.js56
6 files changed, 1036 insertions, 126 deletions
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
@@ -230,6 +230,44 @@ 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",
date: moment("201505151200", 'YYYYMMDDhhmm').fromNow(),
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 = $('<div>' + code + '</div>');
@@ -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, '<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 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] = "<div class='linenumber'>" + (i + 1) + "</div>";
}
var linegutter = "<div class='gutter'>" + linenumbers.join('\n') + "</div>";
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: '<li class="ui-user-item">\
+ <span class="id" style="display:none;"></span>\
+ <a href="#">\
+ <span class="pull-left"><i class="fa fa-square ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>\
+ </a>\
+ </li>'
+};
+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) {
$("<div class='other-cursors'>").insertAfter('.CodeMirror-cursors');
}
- if ($('#' + id).length <= 0) {
- var cursor = $('<div id="' + id + '" class="other-cursor">&nbsp;</div>');
- //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 = $('<div id="' + user.id + '" class="other-cursor" style="display:none;"></div>');
+ 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 = $('<div class="cursorbar">&nbsp;</div>');
+ cursorbar[0].style.height = defaultTextHeight + 'px';
+ cursorbar[0].style.borderLeft = '2px solid ' + user.color;
+
+ var icon = '<i class="fa ' + iconClass + '"></i>';
+
+ var cursortag = $('<div class="cursortag">' + icon + '&nbsp;<span class="name">' + user.name + '</span></div>');
+ //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) {
+ $("<div class='cursor-menu'>").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 '<img class="emoji" src="/vendor/emojify/images/' + value + '.png"></img> ' + 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 '<code>' + Remarkable.utils.escapeHtml(tokens[idx].content) + '</code>';
};
+//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