pull/1/head
yflory 5 years ago
parent cba8f5fce6
commit 62fde59a89

@ -12,6 +12,8 @@ define([
'/common/TypingTests.js', '/common/TypingTests.js',
'/customize/messages.js', '/customize/messages.js',
'cm/lib/codemirror', 'cm/lib/codemirror',
'/bower_components/chainpad/chainpad.dist.js',
'css!cm/lib/codemirror.css', 'css!cm/lib/codemirror.css',
'css!cm/addon/dialog/dialog.css', 'css!cm/addon/dialog/dialog.css',
@ -54,7 +56,8 @@ define([
Visible, Visible,
TypingTest, TypingTest,
Messages, Messages,
CMeditor) CMeditor,
ChainPad)
{ {
window.CodeMirror = CMeditor; window.CodeMirror = CMeditor;
@ -292,6 +295,18 @@ define([
///////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////
var authorUid = function (existing) {
if (!Array.isArray(existing)) { existing = []; }
var n;
var i = 0;
while (!n || existing.indexOf(n) !== -1 && i++ < 1000) {
n = Math.floor(Math.random() * 1000000);
}
// If we can't find a valid number in 1000 iterations, use 0...
if (existing.indexOf(n) !== -1) { n = 0; }
return n;
};
var andThen2 = function (editor, CodeMirror, framework, isPresentMode) { var andThen2 = function (editor, CodeMirror, framework, isPresentMode) {
var common = framework._.sfCommon; var common = framework._.sfCommon;
@ -300,7 +315,7 @@ define([
var previewPane = mkPreviewPane(editor, CodeMirror, framework, isPresentMode); var previewPane = mkPreviewPane(editor, CodeMirror, framework, isPresentMode);
var markdownTb = mkMarkdownTb(editor, framework); var markdownTb = mkMarkdownTb(editor, framework);
var $removeAuthorColorsButton = framework._.sfCommon.createButton('removeauthorcolors', true, {icon: 'fa-paint-brush', title: 'Autorenfarben entfernen'}); var $removeAuthorColorsButton = framework._.sfCommon.createButton('removeauthorcolors', true, {icon: 'fa-paint-brush', title: 'Autorenfarben entfernen'}); // XXX
framework._.toolbar.$rightside.append($removeAuthorColorsButton); framework._.toolbar.$rightside.append($removeAuthorColorsButton);
$removeAuthorColorsButton.click(function() { $removeAuthorColorsButton.click(function() {
var selfrom = editor.getCursor("from"); var selfrom = editor.getCursor("from");
@ -309,6 +324,8 @@ define([
editor.getAllMarks().forEach(function (marker) { editor.getAllMarks().forEach(function (marker) {
marker.clear(); marker.clear();
}); });
authormarks.authors = {};
authormarks.marks = [];
} else { } else {
editor.findMarks(selfrom, selto).forEach(function (marker) { editor.findMarks(selfrom, selto).forEach(function (marker) {
marker.clear(); marker.clear();
@ -317,8 +334,39 @@ define([
framework.localChange(); framework.localChange();
}); });
var authormarks = {
marks: [],
authors: {}
};
var authormarksLocal = []; var authormarksLocal = [];
var authormarksUpdate = []; var myAuthorId = 0;
var MARK_OPACITY = 90;
var addMark = function (from, to, uid) {
var author = authormarks.authors[uid] || {};
editor.markText(from, to, {
inclusiveLeft: uid === myAuthorId,
inclusiveRight: uid === myAuthorId,
css: "background-color: " + author.color + MARK_OPACITY,
attributes: {
'data-type': 'authormark',
'data-uid': uid
}
});
};
var sortMarks = function (a, b) {
if (!Array.isArray(b)) { return -1; }
if (!Array.isArray(a)) { return 1; }
// Check line
if (a[1] < b[1]) { return -1; }
if (a[1] > b[1]) { return 1; }
// Same line: check start offset
if (a[2] < b[2]) { return -1; }
if (a[2] > b[2]) { return 1; }
return 0;
};
var $print = $('#cp-app-code-print'); var $print = $('#cp-app-code-print');
var $content = $('#cp-app-code-preview-content'); var $content = $('#cp-app-code-preview-content');
@ -342,24 +390,54 @@ define([
CodeMirror.configureTheme(common); CodeMirror.configureTheme(common);
} }
// get user color for author marks var checkAuthors = function (userDoc) {
var authorcolor = framework._.sfCommon.getMetadataMgr().getUserData().color; var chainpad = framework._.cpNfInner.chainpad;
var authorcolor_r = parseInt("0x" + authorcolor.slice(1,3)); var authDoc = JSON.parse(chainpad.getAuthDoc() || '{}');
var authorcolor_g = parseInt("0x" + authorcolor.slice(3,5)); if (!authDoc.content || !userDoc.content) { return; }
var authorcolor_b = parseInt("0x" + authorcolor.slice(5,7)); if (!authormarks || !Array.isArray(authormarks.marks)) { return; }
var authorcolor_min = Math.min(authorcolor_r, authorcolor_g, authorcolor_b); var oldDoc = CodeMirror.canonicalize(editor.getValue());
var theirOps = ChainPad.Diff.diff(oldDoc, userDoc.content);
// set minimal brightness for author marks and calculate color var myOps = ChainPad.Diff.diff(authDoc.content, userDoc.content);
var tarMinColorVal = 180; // If I have uncommited content when receiving a remote patch, and they have
if (authorcolor_min < tarMinColorVal) { // pushed content to the same line as me, I need to update all the authormarks
var facColor = (255-tarMinColorVal)/(255-authorcolor_min); // after their changes to push them by the length of the text I added
authorcolor_r = Math.floor(255-facColor*(255-authorcolor_r)); var changed = false;
authorcolor_g = Math.floor(255-facColor*(255-authorcolor_g)); console.log(JSON.stringify(authDoc.authormarks));
authorcolor_b = Math.floor(255-facColor*(255-authorcolor_b)); console.log(JSON.stringify(authormarks));
authorcolor = "#" + authorcolor_r.toString(16) + authorcolor_g.toString(16) + authorcolor_b.toString(16); console.warn(myOps);
} console.warn(theirOps);
myOps.forEach(function (op) {
//// var pos = SFCodeMirror.posToCursor(op.offset, authDoc.content);
var size = (op.toInsert.length - op.toRemove);
// If the remote change includes an operation on the same line,
// fix the offsets and continue to my next operation
// NOTE: we need to fix all the marks that are **after** the change with
// the bigger offset
theirOps.some(function (_op) {
var _pos = SFCodeMirror.posToCursor(_op.offset, oldDoc);
if (_pos.line !== pos.line) { return; }
var ch = Math.max(_pos.ch, pos.ch);
// Get the marks from this line and check offsets after the change
authormarks.marks.forEach(function (array) {
if (array[1] !== pos.line) { return; }
// Move the end position if it's on the same line and after the change
if (!array[4] && array[3] >= ch) {
array[3] += size;
changed = true;
}
// Move the start position if it's after the change
if (array[2] >= ch) {
array[2] += size;
changed = true;
}
});
return true;
});
});
framework.localChange();
};
framework.onContentUpdate(function (newContent) { framework.onContentUpdate(function (newContent) {
var highlightMode = newContent.highlightMode; var highlightMode = newContent.highlightMode;
@ -367,10 +445,20 @@ define([
CodeMirror.setMode(highlightMode, evModeChange.fire); CodeMirror.setMode(highlightMode, evModeChange.fire);
} }
// author marks will be updated in onChange-Handler if (newContent.authormarks) {
authormarksUpdate = newContent.authormarks; authormarks = newContent.authormarks;
if (!authormarks.marks) { authormarks.marks = []; }
if (!authormarks.authors) { authormarks.authors = {}; }
}
var chainpad = framework._.cpNfInner.chainpad;
var ops = ChainPad.Diff.diff(chainpad.getAuthDoc(), chainpad.getUserDoc());
if (ops.length) {
console.error(ops);
}
checkAuthors(newContent);
CodeMirror.contentUpdate(newContent, authormarksUpdate, authormarksLocal); CodeMirror.contentUpdate(newContent); //, authormarks.marks, authormarksLocal);
previewPane.draw(); previewPane.draw();
}); });
@ -380,32 +468,53 @@ define([
content.highlightMode = CodeMirror.highlightMode; content.highlightMode = CodeMirror.highlightMode;
previewPane.draw(); previewPane.draw();
var colorlist = content.colorlist || {};
// get author marks // get author marks
var authormarks = []; var authors = authormarks.authors || {};
var colorlist = []; var _marks = [];
var previous;
editor.getAllMarks().forEach(function (mark) { editor.getAllMarks().forEach(function (mark) {
var pos = mark.find(); var pos = mark.find();
var css = mark.css; var attributes = mark.attributes || {};
if (pos !== undefined && css !== undefined) { if (!pos || attributes['data-type'] !== 'authormark') { return; }
var color = css.replace("background-color:", "").trim();
var colorIndex = colorlist.indexOf(color); var uid = attributes['data-uid'] || 0;
if (colorIndex === -1) { var author = authors[uid] || {};
colorlist.push(color);
colorIndex = colorlist.length-1; // Check if we need to merge
} if (previous && previous.data && previous.data[0] === uid) {
if (pos.from.line === pos.to.line) { if (previous.pos.to.line === pos.from.line
if ((pos.from.ch + 1) === pos.to.ch) { && previous.pos.to.ch === pos.from.ch) {
authormarks.push([colorIndex, pos.from.line, pos.from.ch]); // Merge the marks
} else { previous.mark.clear();
authormarks.push([colorIndex, pos.from.line, pos.from.ch, pos.to.ch]); mark.clear();
} addMark(previous.pos.from, pos.to, uid);
} else { // Remove the data for the previous one
authormarks.push([colorIndex, pos.from.line, pos.from.ch, pos.to.line, pos.to.ch]); _marks.pop();
// Update the position to create the new data
pos.from = previous.pos.from;
} }
} }
var array = [uid, pos.from.line, pos.from.ch];
if (pos.from.line === pos.to.line && pos.to.ch > (pos.from.ch+1)) {
// If there is more than 1 character, add the "to" character
array.push(pos.to.ch);
} else if (pos.from.line !== pos.to.line) {
// If the mark is on more than one line, add the "to" line data
Array.prototype.push.apply(array, [pos.to.line, pos.to.ch]);
}
_marks.push(array);
previous = {
pos: pos,
mark: mark,
data: array
};
}); });
content.authormarks = {marks: authormarks, colorlist: colorlist}; _marks.sort(sortMarks);
authormarksLocal = authormarks.slice(); content.authormarks = {marks: _marks, authors: authormarks.authors};
//authormarksLocal = _marks.slice();
return content; return content;
}); });
@ -428,6 +537,22 @@ define([
framework.setTitleRecommender(CodeMirror.getHeadingText); framework.setTitleRecommender(CodeMirror.getHeadingText);
var getMyAuthorId = function () {
var existing = Object.keys(authormarks.authors || {});
if (!common.isLoggedIn()) { return authorUid(existing); }
var userData = common.getMetadataMgr().getUserData();
var uid;
existing.some(function (id) {
var author = authormarks.authors[id] || {};
if (author.curvePublic !== userData.curvePublic) { return; }
uid = Number(id);
return true;
});
// XXX update my color?
return uid || authorUid(existing);
};
framework.onReady(function (newPad) { framework.onReady(function (newPad) {
editor.focus(); editor.focus();
@ -436,6 +561,9 @@ define([
//console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val()); //console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val());
} }
myAuthorId = getMyAuthorId();
console.warn(myAuthorId);
var fmConfig = { var fmConfig = {
dropArea: $('.CodeMirror'), dropArea: $('.CodeMirror'),
body: $('body'), body: $('body'),
@ -476,28 +604,79 @@ define([
}); });
editor.on('change', function( cm, change ) { editor.on('change', function( cm, change ) {
if (change.origin !== undefined && change.text !== undefined && (change.origin === "+input" || change.origin === "paste")) { if (change.text !== undefined && (change.origin === "+input" || change.origin === "paste")) {
// add new author mark if text is added. marks from removed text are removed automatically // add new author mark if text is added. marks from removed text are removed automatically
var to_ch_add;
// If my text is inside an existing mark:
// * if it's my mark, do nothing
// * if it's someone else's mark, break it
// We can only have one author mark at a given position, but there may be
// another mark (cursor selection...) at this position so we use ".some"
var toSplit, abort;
editor.findMarksAt(change.from).some(function (mark) {
if (!mark.attributes) { return; }
if (mark.attributes['data-type'] !== 'authormark') { return; }
if (mark.attributes['data-uid'] !== myAuthorId) {
toSplit = {
mark: mark,
uid: mark.attributes['data-uid']
};
} else {
// This is our mark: abort to avoid making a new one
abort = true;
}
return true;
});
if (abort) { return void framework.localChange(); }
// Add my data to the doc if it's missing
if (!authormarks.authors[myAuthorId]) {
var userData = common.getMetadataMgr().getUserData();
authormarks.authors[myAuthorId] = {
name: userData.name,
curvePublic: userData.curvePublic,
color: userData.color
}
}
var to_add = {
line: change.from.line + change.text.length-1,
};
if (change.text.length > 1) { if (change.text.length > 1) {
to_ch_add = change.text[change.text.length-1].length; // Multiple lines => take the length of the text added to the last line
to_add.ch = change.text[change.text.length-1].length;
} else { } else {
to_ch_add = change.from.ch + change.text[change.text.length-1].length; // Single line => use the "from" position and add the length of the text
to_add.ch = change.from.ch + change.text[change.text.length-1].length;
}
if (toSplit && toSplit.mark && typeof(toSplit.uid) !== "undefined") {
// Break the other user's mark if needed
var _pos = toSplit.mark.find();
toSplit.mark.clear();
addMark(_pos.from, change.from, toSplit.uid); // their mark, 1st part
addMark(change.from, to_add, myAuthorId); // my mark
addMark(to_add, _pos.to, toSplit.uid); // their mark, 2nd part
} else {
// Add my mark
addMark(change.from, to_add, myAuthorId);
} }
editor.markText({line: change.from.line, ch: change.from.ch}, {line: change.from.line + change.text.length-1, ch: to_ch_add}, {css: "background-color: " + authorcolor});
} else if (change.origin === "setValue") { } else if (change.origin === "setValue") {
// on remote update: remove all marks, add new marks // on remote update: remove all marks, add new marks
editor.getAllMarks().forEach(function (marker) { editor.getAllMarks().forEach(function (marker) {
marker.clear(); if (marker.attributes && marker.attributes['data-type'] === 'authormark') {
marker.clear();
}
}); });
authormarksUpdate.marks.forEach(function (mark) { authormarks.marks.forEach(function (mark) {
var from_line; var from_line;
var to_line; var to_line;
var from_ch; var from_ch;
var to_ch; var to_ch;
var colorIndex = mark[0]; var uid = mark[0];
if (authormarksUpdate.colorlist === undefined || (authormarksUpdate.colorlist.length < (colorIndex+1))) { return; } if (!authormarks.authors || !authormarks.authors[uid]) { return; }
var color = authormarksUpdate.colorlist[colorIndex]; var data = authormarks.authors[uid];
if (mark.length === 3) { if (mark.length === 3) {
from_line = mark[1]; from_line = mark[1];
to_line = mark[1]; to_line = mark[1];
@ -514,7 +693,11 @@ define([
from_ch = mark[2]; from_ch = mark[2];
to_ch = mark[4]; to_ch = mark[4];
} }
editor.markText({line: from_line, ch: from_ch}, {line: to_line, ch: to_ch}, {css: "background-color: " + color}); addMark({
line: from_line, ch: from_ch
}, {
line: to_line, ch: to_ch
}, uid);
}); });
} }
framework.localChange(); framework.localChange();

@ -48,7 +48,6 @@ define(['json.sortify'], function (Sortify) {
//title: meta.doc.defaultTitle, //title: meta.doc.defaultTitle,
type: meta.doc.type, type: meta.doc.type,
users: {}, users: {},
authors: {}
}; };
metadataLazyObj = JSON.parse(JSON.stringify(metadataObj)); metadataLazyObj = JSON.parse(JSON.stringify(metadataObj));
} }
@ -69,6 +68,10 @@ define(['json.sortify'], function (Sortify) {
} }
metadataObj.users = mdo; metadataObj.users = mdo;
// Clean old data
delete metadataObj.authors;
delete metadataLazyObj.authors;
// Always update the userlist in the lazy object, otherwise it may be outdated // Always update the userlist in the lazy object, otherwise it may be outdated
// and metadataMgr.updateMetadata() won't do anything, and so we won't push events // and metadataMgr.updateMetadata() won't do anything, and so we won't push events
// to the userlist UI ==> phantom viewers // to the userlist UI ==> phantom viewers
@ -96,27 +99,6 @@ define(['json.sortify'], function (Sortify) {
checkUpdate(lazy); checkUpdate(lazy);
}); });
}; };
var addAuthor = function () {
if (!meta.user || !meta.user.netfluxId || !priv || !priv.edPublic) { return; }
var authors = metadataObj.authors || {};
var old = Sortify(authors);
if (!authors[priv.edPublic]) {
authors[priv.edPublic] = {
nId: [meta.user.netfluxId],
name: meta.user.name
};
} else {
authors[priv.edPublic].name = meta.user.name;
if (authors[priv.edPublic].nId.indexOf(meta.user.netfluxId) === -1) {
authors[priv.edPublic].nId.push(meta.user.netfluxId);
}
}
if (Sortify(authors) !== old) {
metadataObj.authors = authors;
metadataLazyObj.authors = JSON.parse(JSON.stringify(authors));
change();
}
};
var netfluxId; var netfluxId;
var isReady = false; var isReady = false;
@ -225,7 +207,6 @@ define(['json.sortify'], function (Sortify) {
if (isReady) { return void f(); } if (isReady) { return void f(); }
readyHandlers.push(f); readyHandlers.push(f);
}, },
addAuthor: addAuthor,
}); });
}; };
return Object.freeze({ create: create }); return Object.freeze({ create: create });

@ -290,7 +290,7 @@ define([
} }
if (padChange && hasChanged(content)) { if (padChange && hasChanged(content)) {
cpNfInner.metadataMgr.addAuthor(); //cpNfInner.metadataMgr.addAuthor();
} }
oldContent = content; oldContent = content;

@ -12,7 +12,7 @@ define([
], function ($, Modes, Themes, Messages, UIElements, MT, Hash, Util, TextCursor, ChainPad) { ], function ($, Modes, Themes, Messages, UIElements, MT, Hash, Util, TextCursor, ChainPad) {
var module = {}; var module = {};
var cursorToPos = function(cursor, oldText) { var cursorToPos = module.cursorToPos = function(cursor, oldText) {
var cLine = cursor.line; var cLine = cursor.line;
var cCh = cursor.ch; var cCh = cursor.ch;
var pos = 0; var pos = 0;
@ -28,7 +28,7 @@ define([
return pos; return pos;
}; };
var posToCursor = function(position, newText) { var posToCursor = module.posToCursor = function(position, newText) {
var cursor = { var cursor = {
line: 0, line: 0,
ch: 0 ch: 0
@ -58,6 +58,7 @@ define([
editor.save(); editor.save();
var ops = ChainPad.Diff.diff(oldDoc, remoteDoc); var ops = ChainPad.Diff.diff(oldDoc, remoteDoc);
console.log(ops);
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) { var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
return TextCursor.transformCursor(oldCursor[attr], ops); return TextCursor.transformCursor(oldCursor[attr], ops);
}); });
@ -415,16 +416,16 @@ define([
///// /////
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); }; var canonicalize = exp.canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
exp.contentUpdate = function (newContent) {
exp.contentUpdate = function (newContent, authormarksUpdate, authormarksLocal) {
var oldDoc = canonicalize(editor.getValue()); var oldDoc = canonicalize(editor.getValue());
var remoteDoc = newContent.content; var remoteDoc = newContent.content;
// setValueAndCursor triggers onLocal, even if we don't make any change to the content // setValueAndCursor triggers onLocal, even if we don't make any change to the content
// and it may revert other changes (metadata) // and it may revert other changes (metadata)
if (oldDoc === remoteDoc && (authormarksUpdate === undefined || authormarksLocal === undefined || JSON.stringify(authormarksUpdate) === JSON.stringify(authormarksLocal))) { return; }
if (oldDoc === remoteDoc) { return; }
exp.setValueAndCursor(oldDoc, remoteDoc); exp.setValueAndCursor(oldDoc, remoteDoc);
}; };

Loading…
Cancel
Save