Update to move authorship calculation code to note model and support update authorship after making revision of docs
This commit is contained in:
parent
a5e6b5dd3b
commit
d23ced1fba
2 changed files with 177 additions and 101 deletions
|
@ -11,11 +11,16 @@ var shortId = require('shortid');
|
|||
var Sequelize = require("sequelize");
|
||||
var async = require('async');
|
||||
var moment = require('moment');
|
||||
var DiffMatchPatch = require('diff-match-patch');
|
||||
var dmp = new DiffMatchPatch();
|
||||
|
||||
// core
|
||||
var config = require("../config.js");
|
||||
var logger = require("../logger.js");
|
||||
|
||||
//ot
|
||||
var ot = require("../ot/index.js");
|
||||
|
||||
// permission types
|
||||
var permissionTypes = ["freely", "editable", "locked", "private"];
|
||||
|
||||
|
@ -115,6 +120,7 @@ module.exports = function (sequelize, DataTypes) {
|
|||
var fsModifiedTime = moment(fs.statSync(filePath).mtime);
|
||||
var dbModifiedTime = moment(note.lastchangeAt || note.createdAt);
|
||||
var body = fs.readFileSync(filePath, 'utf8');
|
||||
var contentLength = body.length;
|
||||
var title = Note.parseNoteTitle(body);
|
||||
body = LZString.compressToBase64(body);
|
||||
title = LZString.compressToBase64(title);
|
||||
|
@ -126,7 +132,20 @@ module.exports = function (sequelize, DataTypes) {
|
|||
}).then(function (note) {
|
||||
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
|
||||
if (err) return _callback(err, null);
|
||||
return callback(null, note.id);
|
||||
// update authorship on after making revision of docs
|
||||
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
|
||||
var operations = Note.transformPatchToOperations(patch, contentLength);
|
||||
var authorship = note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : [];
|
||||
for (var i = 0; i < operations.length; i++) {
|
||||
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
|
||||
}
|
||||
note.update({
|
||||
authorship: authorship
|
||||
}).then(function (note) {
|
||||
return callback(null, note.id);
|
||||
}).catch(function (err) {
|
||||
return _callback(err, null);
|
||||
});
|
||||
});
|
||||
}).catch(function (err) {
|
||||
return _callback(err, null);
|
||||
|
@ -247,6 +266,162 @@ module.exports = function (sequelize, DataTypes) {
|
|||
_meta.slideOptions = meta.slideOptions;
|
||||
}
|
||||
return _meta;
|
||||
},
|
||||
updateAuthorshipByOperation: function (operation, userId, authorships) {
|
||||
var index = 0;
|
||||
var timestamp = Date.now();
|
||||
for (var i = 0; i < operation.length; i++) {
|
||||
var op = operation[i];
|
||||
if (ot.TextOperation.isRetain(op)) {
|
||||
index += op;
|
||||
} else if (ot.TextOperation.isInsert(op)) {
|
||||
var opStart = index;
|
||||
var opEnd = index + op.length;
|
||||
var inserted = false;
|
||||
// authorship format: [userId, startPos, endPos, createdAt, updatedAt]
|
||||
if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
|
||||
else {
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
if (!inserted) {
|
||||
var nextAuthorship = authorships[j + 1] || -1;
|
||||
if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
|
||||
if (authorship[1] < opStart && authorship[2] > opStart) {
|
||||
// divide
|
||||
var postLength = authorship[2] - opStart;
|
||||
authorship[2] = opStart;
|
||||
authorship[4] = timestamp;
|
||||
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
|
||||
authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
|
||||
j += 2;
|
||||
inserted = true;
|
||||
} else if (authorship[1] >= opStart) {
|
||||
authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
|
||||
j += 1;
|
||||
inserted = true;
|
||||
} else if (authorship[2] <= opStart) {
|
||||
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
|
||||
j += 1;
|
||||
inserted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (authorship[1] >= opStart) {
|
||||
authorship[1] += op.length;
|
||||
authorship[2] += op.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
index += op.length;
|
||||
} else if (ot.TextOperation.isDelete(op)) {
|
||||
var opStart = index;
|
||||
var opEnd = index - op;
|
||||
if (operation.length == 1) {
|
||||
authorships = [];
|
||||
} else if (authorships.length > 0) {
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
|
||||
authorships.splice(j, 1);
|
||||
j -= 1;
|
||||
} else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
|
||||
authorship[2] += op;
|
||||
authorship[4] = timestamp;
|
||||
} else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
|
||||
authorship[2] = opStart;
|
||||
authorship[4] = timestamp;
|
||||
} else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
|
||||
authorship[1] = opEnd;
|
||||
authorship[4] = timestamp;
|
||||
}
|
||||
if (authorship[1] >= opEnd) {
|
||||
authorship[1] += op;
|
||||
authorship[2] += op;
|
||||
}
|
||||
}
|
||||
}
|
||||
index += op;
|
||||
}
|
||||
}
|
||||
// merge
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
for (var k = j + 1; k < authorships.length; k++) {
|
||||
var nextAuthorship = authorships[k];
|
||||
if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
|
||||
var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
|
||||
var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
|
||||
authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
|
||||
authorships.splice(k, 1);
|
||||
j -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// clear
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
if (!authorship[0]) {
|
||||
authorships.splice(j, 1);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
return authorships;
|
||||
},
|
||||
transformPatchToOperations: function (patch, contentLength) {
|
||||
var operations = [];
|
||||
if (patch.length > 0) {
|
||||
// calculate original content length
|
||||
for (var j = patch.length - 1; j >= 0; j--) {
|
||||
var p = patch[j];
|
||||
for (var i = 0; i < p.diffs.length; i++) {
|
||||
var diff = p.diffs[i];
|
||||
switch(diff[0]) {
|
||||
case 1: // insert
|
||||
contentLength -= diff[1].length;
|
||||
break;
|
||||
case -1: // delete
|
||||
contentLength += diff[1].length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// generate operations
|
||||
var bias = 0;
|
||||
var lengthBias = 0;
|
||||
for (var j = 0; j < patch.length; j++) {
|
||||
var operation = [];
|
||||
var p = patch[j];
|
||||
var currIndex = p.start1;
|
||||
var currLength = contentLength - bias;
|
||||
for (var i = 0; i < p.diffs.length; i++) {
|
||||
var diff = p.diffs[i];
|
||||
switch(diff[0]) {
|
||||
case 0: // retain
|
||||
if (i == 0) // first
|
||||
operation.push(currIndex + diff[1].length);
|
||||
else if (i != p.diffs.length - 1) // mid
|
||||
operation.push(diff[1].length);
|
||||
else // last
|
||||
operation.push(currLength + lengthBias - currIndex);
|
||||
currIndex += diff[1].length;
|
||||
break;
|
||||
case 1: // insert
|
||||
operation.push(diff[1]);
|
||||
lengthBias += diff[1].length;
|
||||
currIndex += diff[1].length;
|
||||
break;
|
||||
case -1: // delete
|
||||
operation.push(-diff[1].length);
|
||||
bias += diff[1].length;
|
||||
currIndex += diff[1].length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
operations.push(operation);
|
||||
}
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
},
|
||||
hooks: {
|
||||
|
|
101
lib/realtime.js
101
lib/realtime.js
|
@ -642,106 +642,7 @@ function operationCallback(socket, operation) {
|
|||
}
|
||||
}
|
||||
// save authorship
|
||||
var index = 0;
|
||||
var authorships = note.authorship;
|
||||
var timestamp = Date.now();
|
||||
for (var i = 0; i < operation.length; i++) {
|
||||
var op = operation[i];
|
||||
if (ot.TextOperation.isRetain(op)) {
|
||||
index += op;
|
||||
} else if (ot.TextOperation.isInsert(op)) {
|
||||
var opStart = index;
|
||||
var opEnd = index + op.length;
|
||||
var inserted = false;
|
||||
// authorship format: [userId, startPos, endPos, createdAt, updatedAt]
|
||||
if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
|
||||
else {
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
if (!inserted) {
|
||||
var nextAuthorship = authorships[j + 1] || -1;
|
||||
if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
|
||||
if (authorship[1] < opStart && authorship[2] > opStart) {
|
||||
// divide
|
||||
var postLength = authorship[2] - opStart;
|
||||
authorship[2] = opStart;
|
||||
authorship[4] = timestamp;
|
||||
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
|
||||
authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
|
||||
j += 2;
|
||||
inserted = true;
|
||||
} else if (authorship[1] >= opStart) {
|
||||
authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
|
||||
j += 1;
|
||||
inserted = true;
|
||||
} else if (authorship[2] <= opStart) {
|
||||
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
|
||||
j += 1;
|
||||
inserted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (authorship[1] >= opStart) {
|
||||
authorship[1] += op.length;
|
||||
authorship[2] += op.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
index += op.length;
|
||||
} else if (ot.TextOperation.isDelete(op)) {
|
||||
var opStart = index;
|
||||
var opEnd = index - op;
|
||||
if (operation.length == 1) {
|
||||
authorships = [];
|
||||
} else if (authorships.length > 0) {
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
|
||||
authorships.splice(j, 1);
|
||||
j -= 1;
|
||||
} else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
|
||||
authorship[2] += op;
|
||||
authorship[4] = timestamp;
|
||||
} else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
|
||||
authorship[2] = opStart;
|
||||
authorship[4] = timestamp;
|
||||
} else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
|
||||
authorship[1] = opEnd;
|
||||
authorship[4] = timestamp;
|
||||
}
|
||||
if (authorship[1] >= opEnd) {
|
||||
authorship[1] += op;
|
||||
authorship[2] += op;
|
||||
}
|
||||
}
|
||||
}
|
||||
index += op;
|
||||
}
|
||||
}
|
||||
// merge
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
for (var k = j + 1; k < authorships.length; k++) {
|
||||
var nextAuthorship = authorships[k];
|
||||
if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
|
||||
var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
|
||||
var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
|
||||
authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
|
||||
authorships.splice(k, 1);
|
||||
j -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// clear
|
||||
for (var j = 0; j < authorships.length; j++) {
|
||||
var authorship = authorships[j];
|
||||
if (!authorship[0]) {
|
||||
authorships.splice(j, 1);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
note.authorship = authorships;
|
||||
note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship);
|
||||
}
|
||||
|
||||
function connection(socket) {
|
||||
|
|
Loading…
Reference in a new issue