mirror of https://github.com/xwiki-labs/cryptpad
See other users' cursor position
This commit is contained in:
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…
Reference in New Issue