Implemented sync merge
This commit is contained in:
parent
0e5f198270
commit
acebad8a65
@ -75,15 +75,78 @@ define([
|
||||
});
|
||||
|
||||
var merge = settings.conflictMode == 'merge';
|
||||
Provider.prototype.merge = function(localContent, remoteContent, localTitle, remoteTitle, localDiscussionList, remoteDiscussionList, syncAttributes) {
|
||||
Provider.prototype.syncMerge = function(fileDesc, syncAttributes, remoteContent, remoteTitle, remoteDiscussionListJSON) {
|
||||
var lineArray = [];
|
||||
var lineHash = {};
|
||||
|
||||
function linesToChars(text) {
|
||||
var chars = '';
|
||||
var lineArrayLength = lineArray.length;
|
||||
text.split('\n').forEach(function(line) {
|
||||
if(lineHash.hasOwnProperty(line)) {
|
||||
chars += String.fromCharCode(lineHash[line]);
|
||||
} else {
|
||||
chars += String.fromCharCode(lineArrayLength);
|
||||
lineHash[line] = lineArrayLength;
|
||||
lineArray[lineArrayLength++] = line;
|
||||
}
|
||||
});
|
||||
return chars;
|
||||
}
|
||||
|
||||
function moveComments(oldTextContent, newTextContent, discussionList) {
|
||||
var changes = diffMatchPatch.diff_main(oldTextContent, newTextContent);
|
||||
var updateDiscussionList = false;
|
||||
var startOffset = 0;
|
||||
changes.forEach(function(change) {
|
||||
var changeType = change[0];
|
||||
var changeText = change[1];
|
||||
if(changeType === 0) {
|
||||
startOffset += changeText.length;
|
||||
return;
|
||||
}
|
||||
var endOffset = startOffset;
|
||||
var diffOffset = changeText.length;
|
||||
if(changeType === -1) {
|
||||
endOffset += diffOffset;
|
||||
diffOffset = -diffOffset;
|
||||
}
|
||||
discussionList.forEach(function(discussion) {
|
||||
// selectionEnd
|
||||
if(discussion.selectionEnd >= endOffset) {
|
||||
discussion.selectionEnd += diffOffset;
|
||||
updateDiscussionList = true;
|
||||
}
|
||||
else if(discussion.selectionEnd > startOffset) {
|
||||
discussion.selectionEnd = startOffset;
|
||||
updateDiscussionList = true;
|
||||
}
|
||||
// selectionStart
|
||||
if(discussion.selectionStart >= endOffset) {
|
||||
discussion.selectionStart += diffOffset;
|
||||
updateDiscussionList = true;
|
||||
}
|
||||
else if(discussion.selectionStart > startOffset) {
|
||||
discussion.selectionStart = startOffset;
|
||||
updateDiscussionList = true;
|
||||
}
|
||||
});
|
||||
startOffset = endOffset;
|
||||
});
|
||||
return updateDiscussionList;
|
||||
}
|
||||
|
||||
var localContent = fileDesc.content;
|
||||
var localTitle = fileDesc.title;
|
||||
var localDiscussionListJSON = fileDesc.discussionListJSON;
|
||||
|
||||
// Local/Remote CRCs
|
||||
var localContentCRC = utils.crc32(localContent);
|
||||
var localTitleCRC = utils.crc32(localTitle);
|
||||
var localDiscussionListCRC = utils.crc32(localDiscussionList);
|
||||
var localDiscussionListCRC = utils.crc32(localDiscussionListJSON);
|
||||
var remoteContentCRC = utils.crc32(remoteContent);
|
||||
var remoteTitleCRC = utils.crc32(remoteTitle);
|
||||
var remoteDiscussionListCRC = utils.crc32(remoteDiscussionList);
|
||||
var remoteDiscussionListCRC = utils.crc32(remoteDiscussionListJSON);
|
||||
|
||||
// Check content
|
||||
var contentChanged = localContent != remoteContent;
|
||||
@ -99,7 +162,7 @@ define([
|
||||
var titleConflict = titleChanged && localTitleChanged && remoteTitleChanged;
|
||||
|
||||
// Check discussionList
|
||||
var discussionListChanged = localDiscussionList != remoteDiscussionList;
|
||||
var discussionListChanged = localDiscussionListJSON != remoteDiscussionListJSON;
|
||||
var localDiscussionListChanged = syncAttributes.discussionListCRC != localDiscussionListCRC;
|
||||
var remoteDiscussionListChanged = syncAttributes.discussionListCRC != remoteDiscussionListCRC;
|
||||
var discussionListConflict = discussionListChanged && localDiscussionListChanged && remoteDiscussionListChanged;
|
||||
@ -111,15 +174,79 @@ define([
|
||||
(titleConflict && syncAttributes.title === undefined) ||
|
||||
(discussionListConflict && syncAttributes.discussionList === undefined)
|
||||
) {
|
||||
fileMgr.createFile(localTitle + " (backup)", localContent);
|
||||
fileMgr.createFile(localTitle + " (backup)", localContent, localDiscussionListJSON);
|
||||
eventMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
|
||||
}
|
||||
else {
|
||||
if(contentConflict === true) {
|
||||
var patch = diffMatchPatch.patch_make(syncAttributes.content, localContent);
|
||||
var updateDiscussionList = remoteDiscussionListChanged;
|
||||
var localDiscussionList = fileDesc.discussionList;
|
||||
var remoteDiscussionList = JSON.parse(remoteDiscussionListJSON);
|
||||
var oldDiscussionList;
|
||||
var patch, delta;
|
||||
if(contentConflict) {
|
||||
// Patch content (line mode)
|
||||
var oldContentLines = linesToChars(syncAttributes.content);
|
||||
var localContentLines = linesToChars(localContent);
|
||||
var remoteContentLines = linesToChars(remoteContent);
|
||||
patch = diffMatchPatch.patch_make(oldContentLines, localContentLines);
|
||||
remoteContentLines = diffMatchPatch.patch_apply(patch, remoteContentLines)[0];
|
||||
var newContent = remoteContentLines.split('').map(function(char) {
|
||||
return lineArray[char.charCodeAt(0)];
|
||||
}).join('\n');
|
||||
|
||||
// Whether we take the local discussionList into account
|
||||
if(localDiscussionListChanged || !remoteDiscussionListChanged) {
|
||||
// Move local discussion according to content patch
|
||||
var localDiscussionArray = _.values(localDiscussionList);
|
||||
fileDesc.newDiscussion && localDiscussionArray.push(fileDesc.newDiscussion);
|
||||
updateDiscussionList |= moveComments(localContent, newContent, localDiscussionArray);
|
||||
}
|
||||
|
||||
if(remoteDiscussionListChanged) {
|
||||
// Move remote discussion according to content patch
|
||||
var remoteDiscussionArray = _.values(remoteDiscussionList);
|
||||
moveComments(remoteContent, newContent, remoteDiscussionArray);
|
||||
|
||||
if(localDiscussionListChanged) {
|
||||
// Patch remote discussionList with local modifications
|
||||
oldDiscussionList = JSON.parse(syncAttributes.discussionList);
|
||||
delta = jsonDiffPatch.diff(oldDiscussionList, localDiscussionList);
|
||||
jsonDiffPatch.patch(remoteDiscussionList, delta);
|
||||
}
|
||||
}
|
||||
else {
|
||||
remoteDiscussionList = localDiscussionList;
|
||||
}
|
||||
remoteContent = newContent;
|
||||
}
|
||||
else if(discussionListConflict) {
|
||||
// Patch remote discussionList with local modifications
|
||||
oldDiscussionList = JSON.parse(syncAttributes.discussionList);
|
||||
delta = jsonDiffPatch.diff(oldDiscussionList, localDiscussionList);
|
||||
jsonDiffPatch.patch(remoteDiscussionList, delta);
|
||||
}
|
||||
if(titleConflict) {
|
||||
// Patch title
|
||||
patch = diffMatchPatch.patch_make(syncAttributes.title, localTitle);
|
||||
remoteTitle = diffMatchPatch.patch_apply(patch, remoteTitle)[0];
|
||||
}
|
||||
}
|
||||
|
||||
if(titleChanged && remoteTitleChanged) {
|
||||
fileDesc.title = remoteTitle;
|
||||
eventMgr.onTitleChanged(fileDesc);
|
||||
eventMgr.onMessage('"' + localTitle + '" has been renamed to "' + remoteTitle + '" on ' + this.providerName + '.');
|
||||
}
|
||||
if(contentChanged && remoteContentChanged === true) {
|
||||
if(fileMgr.currentFile === fileDesc) {
|
||||
document.getElementById('wmd-input').setValueSilently(remoteContent);
|
||||
}
|
||||
else {
|
||||
fileDesc.content = remoteContent;
|
||||
eventMgr.onContentChanged(fileDesc, remoteContent);
|
||||
eventMgr.onMessage('"' + remoteTitle + '" has been updated from ' + this.providerName + '.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Provider;
|
||||
|
@ -64,15 +64,23 @@ define([
|
||||
});
|
||||
|
||||
var contentObserver;
|
||||
var isWatching = false;
|
||||
function noWatch(cb) {
|
||||
if(isWatching === true) {
|
||||
contentObserver.disconnect();
|
||||
isWatching = false;
|
||||
cb();
|
||||
isWatching = true;
|
||||
contentObserver.observe(editor.contentElt, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
});
|
||||
}
|
||||
else {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
var diffMatchPatch = new diff_match_patch();
|
||||
var jsonDiffPatch = jsondiffpatch.create({
|
||||
@ -147,10 +155,7 @@ define([
|
||||
// Update editor
|
||||
noWatch(function() {
|
||||
if(previousTextContent != state.content) {
|
||||
inputElt.value = state.content;
|
||||
fileDesc.content = state.content;
|
||||
eventMgr.onContentChanged(fileDesc, state.content);
|
||||
previousTextContent = state.content;
|
||||
inputElt.setValueSilently(state.content);
|
||||
}
|
||||
inputElt.setSelectionStartEnd(selectionStart, selectionEnd);
|
||||
var discussionListJSON = fileDesc.discussionListJSON;
|
||||
@ -501,6 +506,7 @@ define([
|
||||
editor.$marginElt = $(editor.marginElt);
|
||||
|
||||
contentObserver = new MutationObserver(checkContentChange);
|
||||
isWatching = true;
|
||||
contentObserver.observe(editor.contentElt, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
@ -549,6 +555,18 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
inputElt.setValueSilently = function(value) {
|
||||
noWatch(function() {
|
||||
if(!/\n$/.test(value)) {
|
||||
value += '\n';
|
||||
}
|
||||
inputElt.value = value;
|
||||
fileDesc.content = value;
|
||||
previousTextContent = value;
|
||||
eventMgr.onContentChanged(fileDesc, value);
|
||||
});
|
||||
};
|
||||
|
||||
Object.defineProperty(inputElt, 'selectionStart', {
|
||||
get: function () {
|
||||
return selectionStart;
|
||||
|
@ -40,8 +40,16 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var fileDesc;
|
||||
markdownSectionParser.onFileSelected = function(fileDescParam) {
|
||||
fileDesc = fileDescParam;
|
||||
};
|
||||
|
||||
var sectionCounter = 0;
|
||||
function parseFileContent(fileDesc, content) {
|
||||
function parseFileContent(fileDescParam, content) {
|
||||
if(fileDescParam !== fileDesc) {
|
||||
return;
|
||||
}
|
||||
var frontMatter = (fileDesc.frontMatter || {})._frontMatter || '';
|
||||
var text = content.substring(frontMatter.length);
|
||||
var tmpText = text + "\n\n";
|
||||
|
@ -18,7 +18,10 @@ define([
|
||||
|
||||
var regex = /^(\s*-{3}\s*\n([\w\W]+?)\n\s*-{3}\s*?\n)?([\w\W]*)$/;
|
||||
|
||||
function parseFrontMatter(fileDesc, content) {
|
||||
function parseFrontMatter(fileDescParam, content) {
|
||||
if(fileDescParam !== fileDesc) {
|
||||
return;
|
||||
}
|
||||
var results = regex.exec(content);
|
||||
var yaml = results[2];
|
||||
|
||||
|
@ -671,10 +671,10 @@ define([
|
||||
];
|
||||
utils.crc32 = function(str) {
|
||||
var n = 0, crc = -1;
|
||||
for ( var i = 0; i < str.length; i++) {
|
||||
n = (crc ^ str.charCodeAt(i)) & 0xFF;
|
||||
str.split('').forEach(function(char) {
|
||||
n = (crc ^ char.charCodeAt(0)) & 0xFF;
|
||||
crc = (crc >>> 8) ^ mHash[n];
|
||||
}
|
||||
});
|
||||
crc = crc ^ (-1);
|
||||
if(crc < 0) {
|
||||
crc = 0xFFFFFFFF + crc + 1;
|
||||
@ -688,7 +688,7 @@ define([
|
||||
cb();
|
||||
}
|
||||
console.log('Run 10,000 times in ' + (Date.now() - startTime) + 'ms');
|
||||
}
|
||||
};
|
||||
|
||||
return utils;
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user