See other users' cursor position

pull/1/head
yflory 6 years ago
parent 60e7011adc
commit 1ba80a344b

@ -9,6 +9,13 @@
@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;
flex-flow: column;
max-height: 100%;

@ -301,6 +301,13 @@ define([
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 () {
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
});

@ -624,6 +624,14 @@ define([
};
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
var pad = common.padRpc = {};
pad.joinPad = function (data) {
@ -1049,6 +1057,8 @@ define([
},
// Chat
CHAT_EVENT: common.messenger.onEvent.fire,
// Cursor
CURSOR_EVENT: common.cursor.onEvent.fire,
// Pad
PAD_READY: common.padRpc.onReadyEvent.fire,
PAD_MESSAGE: common.padRpc.onMessageEvent.fire,

@ -10,6 +10,7 @@ define([
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/common-messenger.js',
'/common/outer/cursor.js',
'/common/outer/chainpad-netflux-worker.js',
'/common/outer/network-config.js',
'/customize/application_config.js',
@ -20,7 +21,7 @@ define([
'/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js',
], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
CpNfWorker, NetConfig, AppConfig,
Cursor, CpNfWorker, NetConfig, AppConfig,
Crypto, ChainPad, Listmap, nThen, Saferphore) {
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 //////////////////////////////////////
//////////////////////////////////////////////////////////////////
@ -1200,14 +1210,15 @@ define([
var messengerEventClients = [];
var dropChannel = function (chanId) {
store.messenger.leavePad(chanId);
store.cursor.leavePad(chanId);
if (!Store.channels[chanId]) { return; }
if (Store.channels[chanId].cpNf) {
Store.channels[chanId].cpNf.stop();
}
store.messenger.leavePad(chanId);
delete Store.channels[chanId];
};
Store._removeClient = function (clientId) {
@ -1219,6 +1230,7 @@ define([
if (messengerIdx !== -1) {
messengerEventClients.splice(messengerIdx, 1);
}
store.cursor.removeClient(clientId);
Object.keys(Store.channels).forEach(function (chanId) {
var chanIdx = Store.channels[chanId].clients.indexOf(clientId);
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 /////////////////////////////////////
@ -1378,6 +1400,7 @@ define([
userObject.fixFiles();
loadSharedFolders(waitFor);
loadMessenger();
loadCursor();
}).nThen(function () {
var requestLogin = function () {
broadcast([], "REQUEST_LOGIN");

@ -62,6 +62,8 @@ define([
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
// Chat
CHAT_COMMAND: Store.messenger.execCommand,
// Cursor
CURSOR_COMMAND: Store.cursor.execCommand,
// Pad
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,

@ -55,6 +55,7 @@ define([
var create = function (options, cb) {
var evContentUpdate = Util.mkEvent();
var evCursorUpdate = Util.mkEvent();
var evEditableStateChange = Util.mkEvent();
var evOnReady = Util.mkEvent(true);
var evOnDefaultContentNeeded = Util.mkEvent();
@ -68,6 +69,7 @@ define([
var cpNfInner;
var readOnly;
var title;
var cursor;
var toolbar;
var state = STATE.DISCONNECTED;
var firstConnection = true;
@ -90,6 +92,7 @@ define([
var textContentGetter;
var titleRecommender = function () { return false; };
var contentGetter = function () { return UNINITIALIZED; };
var cursorGetter;
var normalize0 = function (x) { return x; };
var normalize = function (x) {
@ -326,6 +329,11 @@ define([
evOnReady.fire(newPad);
common.openPadChat(onLocal);
if (!readOnly && cursorGetter) {
common.openCursorChannel(onLocal);
cursor = common.createCursor();
cursor.onCursorUpdate(evCursorUpdate.fire);
}
UI.removeLoadingScreen(emitResize);
@ -638,6 +646,15 @@ define([
// in the pad when requested by the framework.
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
// representation of the pad content if a text analyzer is configured
setTextContentGetter: function (tcg) { textContentGetter = tcg; },

@ -45,6 +45,7 @@ define([
return new Blob([ content ], { type: 'text/plain;charset=utf-8' });
};
module.setValueAndCursor = function (editor, oldDoc, remoteDoc) {
editor._noCursorUpdate = true;
var scroll = editor.getScrollInfo();
//get old cursor here
var oldCursor = {};
@ -59,6 +60,7 @@ define([
return TextCursor.transformCursor(oldCursor[attr], ops);
});
editor._noCursorUpdate = false;
if(selects[0] === selects[1]) {
editor.setCursor(posToCursor(selects[0], remoteDoc));
}
@ -374,6 +376,56 @@ define([
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;
};

@ -792,6 +792,22 @@ define([
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) {
Notifier.getPermission();

@ -9,6 +9,7 @@ define([
'/common/sframe-common-history.js',
'/common/sframe-common-file.js',
'/common/sframe-common-codemirror.js',
'/common/sframe-common-cursor.js',
'/common/metadata-manager.js',
'/customize/application_config.js',
@ -31,6 +32,7 @@ define([
History,
File,
CodeMirror,
Cursor,
MetadataMgr,
AppConfig,
CommonRealtime,
@ -106,6 +108,9 @@ define([
// Title module
funcs.createTitle = callWithCommon(Title.create);
// Cursor
funcs.createCursor = callWithCommon(Cursor.create);
// Files
funcs.uploadFile = callWithCommon(File.uploadFile);
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
funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create);

@ -158,6 +158,11 @@ define({
'Q_CHAT_COMMAND': 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.
'EV_LOCALSTORE_PUT': true,
// Put one entry in the parent sessionStorage

Loading…
Cancel
Save