See other users' cursor position
This commit is contained in:
parent
60e7011adc
commit
1ba80a344b
@ -9,6 +9,13 @@
|
|||||||
@color: @colortheme_code-color
|
@color: @colortheme_code-color
|
||||||
);
|
);
|
||||||
|
|
||||||
|
.cp-codemirror-cursor {
|
||||||
|
border-left: 2px solid red;
|
||||||
|
}
|
||||||
|
.cp-codemirror-selection {
|
||||||
|
background-color: rgba(255,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
|||||||
@ -301,6 +301,13 @@ define([
|
|||||||
return content;
|
return content;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
framework.onCursorUpdate(CodeMirror.setRemoteCursor);
|
||||||
|
framework.setCursorGetter(CodeMirror.getCursor);
|
||||||
|
editor.on('cursorActivity', function () {
|
||||||
|
if (editor._noCursorUpdate) { console.log('ok'); return; }
|
||||||
|
framework.updateCursor();
|
||||||
|
});
|
||||||
|
|
||||||
framework.onEditableChange(function () {
|
framework.onEditableChange(function () {
|
||||||
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
|
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
|
||||||
});
|
});
|
||||||
|
|||||||
@ -624,6 +624,14 @@ define([
|
|||||||
};
|
};
|
||||||
messenger.onEvent = Util.mkEvent();
|
messenger.onEvent = Util.mkEvent();
|
||||||
|
|
||||||
|
// Cursor
|
||||||
|
var cursor = common.cursor = {};
|
||||||
|
cursor.execCommand = function (data, cb) {
|
||||||
|
postMessage("CURSOR_COMMAND", data, cb);
|
||||||
|
};
|
||||||
|
cursor.onEvent = Util.mkEvent();
|
||||||
|
|
||||||
|
|
||||||
// Pad RPC
|
// Pad RPC
|
||||||
var pad = common.padRpc = {};
|
var pad = common.padRpc = {};
|
||||||
pad.joinPad = function (data) {
|
pad.joinPad = function (data) {
|
||||||
@ -1049,6 +1057,8 @@ define([
|
|||||||
},
|
},
|
||||||
// Chat
|
// Chat
|
||||||
CHAT_EVENT: common.messenger.onEvent.fire,
|
CHAT_EVENT: common.messenger.onEvent.fire,
|
||||||
|
// Cursor
|
||||||
|
CURSOR_EVENT: common.cursor.onEvent.fire,
|
||||||
// Pad
|
// Pad
|
||||||
PAD_READY: common.padRpc.onReadyEvent.fire,
|
PAD_READY: common.padRpc.onReadyEvent.fire,
|
||||||
PAD_MESSAGE: common.padRpc.onMessageEvent.fire,
|
PAD_MESSAGE: common.padRpc.onMessageEvent.fire,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ define([
|
|||||||
'/common/common-realtime.js',
|
'/common/common-realtime.js',
|
||||||
'/common/common-messaging.js',
|
'/common/common-messaging.js',
|
||||||
'/common/common-messenger.js',
|
'/common/common-messenger.js',
|
||||||
|
'/common/outer/cursor.js',
|
||||||
'/common/outer/chainpad-netflux-worker.js',
|
'/common/outer/chainpad-netflux-worker.js',
|
||||||
'/common/outer/network-config.js',
|
'/common/outer/network-config.js',
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
@ -20,7 +21,7 @@ define([
|
|||||||
'/bower_components/nthen/index.js',
|
'/bower_components/nthen/index.js',
|
||||||
'/bower_components/saferphore/index.js',
|
'/bower_components/saferphore/index.js',
|
||||||
], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
|
], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
|
||||||
CpNfWorker, NetConfig, AppConfig,
|
Cursor, CpNfWorker, NetConfig, AppConfig,
|
||||||
Crypto, ChainPad, Listmap, nThen, Saferphore) {
|
Crypto, ChainPad, Listmap, nThen, Saferphore) {
|
||||||
var Store = {};
|
var Store = {};
|
||||||
|
|
||||||
@ -904,6 +905,15 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cursor
|
||||||
|
|
||||||
|
Store.cursor = {
|
||||||
|
execCommand: function (clientId, data, cb) {
|
||||||
|
if (!store.cursor) { return void cb ({error: 'Cursor channel is disabled'}); }
|
||||||
|
store.cursor.execCommand(clientId, data, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
/////////////////////// PAD //////////////////////////////////////
|
/////////////////////// PAD //////////////////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
@ -1200,14 +1210,15 @@ define([
|
|||||||
var messengerEventClients = [];
|
var messengerEventClients = [];
|
||||||
|
|
||||||
var dropChannel = function (chanId) {
|
var dropChannel = function (chanId) {
|
||||||
|
store.messenger.leavePad(chanId);
|
||||||
|
store.cursor.leavePad(chanId);
|
||||||
|
|
||||||
if (!Store.channels[chanId]) { return; }
|
if (!Store.channels[chanId]) { return; }
|
||||||
|
|
||||||
if (Store.channels[chanId].cpNf) {
|
if (Store.channels[chanId].cpNf) {
|
||||||
Store.channels[chanId].cpNf.stop();
|
Store.channels[chanId].cpNf.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
store.messenger.leavePad(chanId);
|
|
||||||
|
|
||||||
delete Store.channels[chanId];
|
delete Store.channels[chanId];
|
||||||
};
|
};
|
||||||
Store._removeClient = function (clientId) {
|
Store._removeClient = function (clientId) {
|
||||||
@ -1219,6 +1230,7 @@ define([
|
|||||||
if (messengerIdx !== -1) {
|
if (messengerIdx !== -1) {
|
||||||
messengerEventClients.splice(messengerIdx, 1);
|
messengerEventClients.splice(messengerIdx, 1);
|
||||||
}
|
}
|
||||||
|
store.cursor.removeClient(clientId);
|
||||||
Object.keys(Store.channels).forEach(function (chanId) {
|
Object.keys(Store.channels).forEach(function (chanId) {
|
||||||
var chanIdx = Store.channels[chanId].clients.indexOf(clientId);
|
var chanIdx = Store.channels[chanId].clients.indexOf(clientId);
|
||||||
if (chanIdx !== -1) {
|
if (chanIdx !== -1) {
|
||||||
@ -1291,6 +1303,16 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var loadCursor = function () {
|
||||||
|
store.cursor = Cursor.init(store, function (ev, data, clients) {
|
||||||
|
clients.forEach(function (cId) {
|
||||||
|
postMessage(cId, 'CURSOR_EVENT', {
|
||||||
|
ev: ev,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
/////////////////////// Init /////////////////////////////////////
|
/////////////////////// Init /////////////////////////////////////
|
||||||
@ -1378,6 +1400,7 @@ define([
|
|||||||
userObject.fixFiles();
|
userObject.fixFiles();
|
||||||
loadSharedFolders(waitFor);
|
loadSharedFolders(waitFor);
|
||||||
loadMessenger();
|
loadMessenger();
|
||||||
|
loadCursor();
|
||||||
}).nThen(function () {
|
}).nThen(function () {
|
||||||
var requestLogin = function () {
|
var requestLogin = function () {
|
||||||
broadcast([], "REQUEST_LOGIN");
|
broadcast([], "REQUEST_LOGIN");
|
||||||
|
|||||||
@ -62,6 +62,8 @@ define([
|
|||||||
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
|
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
|
||||||
// Chat
|
// Chat
|
||||||
CHAT_COMMAND: Store.messenger.execCommand,
|
CHAT_COMMAND: Store.messenger.execCommand,
|
||||||
|
// Cursor
|
||||||
|
CURSOR_COMMAND: Store.cursor.execCommand,
|
||||||
// Pad
|
// Pad
|
||||||
SEND_PAD_MSG: Store.sendPadMsg,
|
SEND_PAD_MSG: Store.sendPadMsg,
|
||||||
JOIN_PAD: Store.joinPad,
|
JOIN_PAD: Store.joinPad,
|
||||||
|
|||||||
@ -55,6 +55,7 @@ define([
|
|||||||
|
|
||||||
var create = function (options, cb) {
|
var create = function (options, cb) {
|
||||||
var evContentUpdate = Util.mkEvent();
|
var evContentUpdate = Util.mkEvent();
|
||||||
|
var evCursorUpdate = Util.mkEvent();
|
||||||
var evEditableStateChange = Util.mkEvent();
|
var evEditableStateChange = Util.mkEvent();
|
||||||
var evOnReady = Util.mkEvent(true);
|
var evOnReady = Util.mkEvent(true);
|
||||||
var evOnDefaultContentNeeded = Util.mkEvent();
|
var evOnDefaultContentNeeded = Util.mkEvent();
|
||||||
@ -68,6 +69,7 @@ define([
|
|||||||
var cpNfInner;
|
var cpNfInner;
|
||||||
var readOnly;
|
var readOnly;
|
||||||
var title;
|
var title;
|
||||||
|
var cursor;
|
||||||
var toolbar;
|
var toolbar;
|
||||||
var state = STATE.DISCONNECTED;
|
var state = STATE.DISCONNECTED;
|
||||||
var firstConnection = true;
|
var firstConnection = true;
|
||||||
@ -90,6 +92,7 @@ define([
|
|||||||
var textContentGetter;
|
var textContentGetter;
|
||||||
var titleRecommender = function () { return false; };
|
var titleRecommender = function () { return false; };
|
||||||
var contentGetter = function () { return UNINITIALIZED; };
|
var contentGetter = function () { return UNINITIALIZED; };
|
||||||
|
var cursorGetter;
|
||||||
var normalize0 = function (x) { return x; };
|
var normalize0 = function (x) { return x; };
|
||||||
|
|
||||||
var normalize = function (x) {
|
var normalize = function (x) {
|
||||||
@ -326,6 +329,11 @@ define([
|
|||||||
evOnReady.fire(newPad);
|
evOnReady.fire(newPad);
|
||||||
|
|
||||||
common.openPadChat(onLocal);
|
common.openPadChat(onLocal);
|
||||||
|
if (!readOnly && cursorGetter) {
|
||||||
|
common.openCursorChannel(onLocal);
|
||||||
|
cursor = common.createCursor();
|
||||||
|
cursor.onCursorUpdate(evCursorUpdate.fire);
|
||||||
|
}
|
||||||
|
|
||||||
UI.removeLoadingScreen(emitResize);
|
UI.removeLoadingScreen(emitResize);
|
||||||
|
|
||||||
@ -638,6 +646,15 @@ define([
|
|||||||
// in the pad when requested by the framework.
|
// in the pad when requested by the framework.
|
||||||
setContentGetter: function (cg) { contentGetter = cg; },
|
setContentGetter: function (cg) { contentGetter = cg; },
|
||||||
|
|
||||||
|
// Set the function providing the cursor position when request by the framework.
|
||||||
|
setCursorGetter: function (cg) { cursorGetter = cg; },
|
||||||
|
onCursorUpdate: evCursorUpdate.reg,
|
||||||
|
updateCursor: function () {
|
||||||
|
if (cursor && cursorGetter) {
|
||||||
|
cursor.updateCursor(cursorGetter());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Set a text content supplier, this is a function which will give a text
|
// Set a text content supplier, this is a function which will give a text
|
||||||
// representation of the pad content if a text analyzer is configured
|
// representation of the pad content if a text analyzer is configured
|
||||||
setTextContentGetter: function (tcg) { textContentGetter = tcg; },
|
setTextContentGetter: function (tcg) { textContentGetter = tcg; },
|
||||||
|
|||||||
@ -45,6 +45,7 @@ define([
|
|||||||
return new Blob([ content ], { type: 'text/plain;charset=utf-8' });
|
return new Blob([ content ], { type: 'text/plain;charset=utf-8' });
|
||||||
};
|
};
|
||||||
module.setValueAndCursor = function (editor, oldDoc, remoteDoc) {
|
module.setValueAndCursor = function (editor, oldDoc, remoteDoc) {
|
||||||
|
editor._noCursorUpdate = true;
|
||||||
var scroll = editor.getScrollInfo();
|
var scroll = editor.getScrollInfo();
|
||||||
//get old cursor here
|
//get old cursor here
|
||||||
var oldCursor = {};
|
var oldCursor = {};
|
||||||
@ -59,6 +60,7 @@ define([
|
|||||||
return TextCursor.transformCursor(oldCursor[attr], ops);
|
return TextCursor.transformCursor(oldCursor[attr], ops);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
editor._noCursorUpdate = false;
|
||||||
if(selects[0] === selects[1]) {
|
if(selects[0] === selects[1]) {
|
||||||
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
||||||
}
|
}
|
||||||
@ -374,6 +376,56 @@ define([
|
|||||||
updateIndentSettings();
|
updateIndentSettings();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exp.getCursor = function () {
|
||||||
|
var doc = canonicalize(editor.getValue());
|
||||||
|
var cursor = {};
|
||||||
|
cursor.selectionStart = cursorToPos(editor.getCursor('from'), doc);
|
||||||
|
cursor.selectionEnd = cursorToPos(editor.getCursor('to'), doc);
|
||||||
|
return cursor;
|
||||||
|
};
|
||||||
|
|
||||||
|
var makeCursor = function (id) {
|
||||||
|
if (document.getElementById(id)) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
return $('<span>', {
|
||||||
|
'id': id,
|
||||||
|
'class': 'cp-codemirror-cursor'
|
||||||
|
})[0];
|
||||||
|
};
|
||||||
|
var marks = {};
|
||||||
|
exp.setRemoteCursor = function (data) {
|
||||||
|
if (data.leave) {
|
||||||
|
$('.cp-codemirror-cursor[id^='+data.id+']').each(function (i, el) {
|
||||||
|
var id = $(el).attr('id');
|
||||||
|
if (marks[id]) {
|
||||||
|
marks[id].clear();
|
||||||
|
delete marks[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = data.id;
|
||||||
|
var cursor = data.cursor;
|
||||||
|
var doc = canonicalize(editor.getValue());
|
||||||
|
|
||||||
|
if (marks[id]) {
|
||||||
|
marks[id].clear();
|
||||||
|
delete marks[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.selectionStart === cursor.selectionEnd) {
|
||||||
|
var cursorPosS = posToCursor(cursor.selectionStart, doc);
|
||||||
|
var el = makeCursor(id);
|
||||||
|
marks[id] = editor.setBookmark(cursorPosS, { widget: el });
|
||||||
|
} else {
|
||||||
|
var pos1 = posToCursor(cursor.selectionStart, doc);
|
||||||
|
var pos2 = posToCursor(cursor.selectionEnd, doc);
|
||||||
|
marks[id] = editor.markText(pos1, pos2, { className: 'cp-codemirror-selection' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return exp;
|
return exp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -792,6 +792,22 @@ define([
|
|||||||
cfg.addRpc(sframeChan, Cryptpad, Utils);
|
cfg.addRpc(sframeChan, Cryptpad, Utils);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sframeChan.on('Q_CURSOR_OPENCHANNEL', function (data, cb) {
|
||||||
|
Cryptpad.cursor.execCommand({
|
||||||
|
cmd: 'INIT_CURSOR',
|
||||||
|
data: {
|
||||||
|
channel: data,
|
||||||
|
secret: secret
|
||||||
|
}
|
||||||
|
}, cb);
|
||||||
|
});
|
||||||
|
Cryptpad.cursor.onEvent.reg(function (data) {
|
||||||
|
sframeChan.event('EV_CURSOR_EVENT', data);
|
||||||
|
});
|
||||||
|
sframeChan.on('Q_CURSOR_COMMAND', function (data, cb) {
|
||||||
|
Cryptpad.cursor.execCommand(data, cb);
|
||||||
|
});
|
||||||
|
|
||||||
if (cfg.messaging) {
|
if (cfg.messaging) {
|
||||||
Notifier.getPermission();
|
Notifier.getPermission();
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ define([
|
|||||||
'/common/sframe-common-history.js',
|
'/common/sframe-common-history.js',
|
||||||
'/common/sframe-common-file.js',
|
'/common/sframe-common-file.js',
|
||||||
'/common/sframe-common-codemirror.js',
|
'/common/sframe-common-codemirror.js',
|
||||||
|
'/common/sframe-common-cursor.js',
|
||||||
'/common/metadata-manager.js',
|
'/common/metadata-manager.js',
|
||||||
|
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
@ -31,6 +32,7 @@ define([
|
|||||||
History,
|
History,
|
||||||
File,
|
File,
|
||||||
CodeMirror,
|
CodeMirror,
|
||||||
|
Cursor,
|
||||||
MetadataMgr,
|
MetadataMgr,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
CommonRealtime,
|
CommonRealtime,
|
||||||
@ -106,6 +108,9 @@ define([
|
|||||||
// Title module
|
// Title module
|
||||||
funcs.createTitle = callWithCommon(Title.create);
|
funcs.createTitle = callWithCommon(Title.create);
|
||||||
|
|
||||||
|
// Cursor
|
||||||
|
funcs.createCursor = callWithCommon(Cursor.create);
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
funcs.uploadFile = callWithCommon(File.uploadFile);
|
funcs.uploadFile = callWithCommon(File.uploadFile);
|
||||||
funcs.createFileManager = callWithCommon(File.create);
|
funcs.createFileManager = callWithCommon(File.create);
|
||||||
@ -180,6 +185,24 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var cursorChannel;
|
||||||
|
funcs.getCursorChannel = function () {
|
||||||
|
return cursorChannel;
|
||||||
|
};
|
||||||
|
funcs.openCursorChannel = function (saveChanges) {
|
||||||
|
var md = JSON.parse(JSON.stringify(ctx.metadataMgr.getMetadata()));
|
||||||
|
var channel = md.cursor || Hash.createChannelId();
|
||||||
|
if (!md.cursor) {
|
||||||
|
md.cursor = channel;
|
||||||
|
ctx.metadataMgr.updateMetadata(md);
|
||||||
|
setTimeout(saveChanges);
|
||||||
|
}
|
||||||
|
cursorChannel = channel;
|
||||||
|
ctx.sframeChan.query('Q_CURSOR_OPENCHANNEL', channel, function (err, obj) {
|
||||||
|
if (err || (obj && obj.error)) { console.error(err || (obj && obj.error)); }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// CodeMirror
|
// CodeMirror
|
||||||
funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create);
|
funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create);
|
||||||
|
|
||||||
|
|||||||
@ -158,6 +158,11 @@ define({
|
|||||||
'Q_CHAT_COMMAND': true,
|
'Q_CHAT_COMMAND': true,
|
||||||
'Q_CHAT_OPENPADCHAT': true,
|
'Q_CHAT_OPENPADCHAT': true,
|
||||||
|
|
||||||
|
// Cursor
|
||||||
|
'EV_CURSOR_EVENT': true,
|
||||||
|
'Q_CURSOR_COMMAND': true,
|
||||||
|
'Q_CURSOR_OPENCHANNEL': true,
|
||||||
|
|
||||||
// Put one or more entries to the localStore which will go in localStorage.
|
// Put one or more entries to the localStore which will go in localStorage.
|
||||||
'EV_LOCALSTORE_PUT': true,
|
'EV_LOCALSTORE_PUT': true,
|
||||||
// Put one entry in the parent sessionStorage
|
// Put one entry in the parent sessionStorage
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user