Implemented sync merge
This commit is contained in:
parent
0e5f198270
commit
acebad8a65
@ -75,15 +75,78 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
var merge = settings.conflictMode == 'merge';
|
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
|
// Local/Remote CRCs
|
||||||
var localContentCRC = utils.crc32(localContent);
|
var localContentCRC = utils.crc32(localContent);
|
||||||
var localTitleCRC = utils.crc32(localTitle);
|
var localTitleCRC = utils.crc32(localTitle);
|
||||||
var localDiscussionListCRC = utils.crc32(localDiscussionList);
|
var localDiscussionListCRC = utils.crc32(localDiscussionListJSON);
|
||||||
var remoteContentCRC = utils.crc32(remoteContent);
|
var remoteContentCRC = utils.crc32(remoteContent);
|
||||||
var remoteTitleCRC = utils.crc32(remoteTitle);
|
var remoteTitleCRC = utils.crc32(remoteTitle);
|
||||||
var remoteDiscussionListCRC = utils.crc32(remoteDiscussionList);
|
var remoteDiscussionListCRC = utils.crc32(remoteDiscussionListJSON);
|
||||||
|
|
||||||
// Check content
|
// Check content
|
||||||
var contentChanged = localContent != remoteContent;
|
var contentChanged = localContent != remoteContent;
|
||||||
@ -99,7 +162,7 @@ define([
|
|||||||
var titleConflict = titleChanged && localTitleChanged && remoteTitleChanged;
|
var titleConflict = titleChanged && localTitleChanged && remoteTitleChanged;
|
||||||
|
|
||||||
// Check discussionList
|
// Check discussionList
|
||||||
var discussionListChanged = localDiscussionList != remoteDiscussionList;
|
var discussionListChanged = localDiscussionListJSON != remoteDiscussionListJSON;
|
||||||
var localDiscussionListChanged = syncAttributes.discussionListCRC != localDiscussionListCRC;
|
var localDiscussionListChanged = syncAttributes.discussionListCRC != localDiscussionListCRC;
|
||||||
var remoteDiscussionListChanged = syncAttributes.discussionListCRC != remoteDiscussionListCRC;
|
var remoteDiscussionListChanged = syncAttributes.discussionListCRC != remoteDiscussionListCRC;
|
||||||
var discussionListConflict = discussionListChanged && localDiscussionListChanged && remoteDiscussionListChanged;
|
var discussionListConflict = discussionListChanged && localDiscussionListChanged && remoteDiscussionListChanged;
|
||||||
@ -111,15 +174,79 @@ define([
|
|||||||
(titleConflict && syncAttributes.title === undefined) ||
|
(titleConflict && syncAttributes.title === undefined) ||
|
||||||
(discussionListConflict && syncAttributes.discussionList === 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.');
|
eventMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(contentConflict === true) {
|
var updateDiscussionList = remoteDiscussionListChanged;
|
||||||
var patch = diffMatchPatch.patch_make(syncAttributes.content, localContent);
|
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;
|
return Provider;
|
||||||
|
@ -64,15 +64,23 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
var contentObserver;
|
var contentObserver;
|
||||||
|
var isWatching = false;
|
||||||
function noWatch(cb) {
|
function noWatch(cb) {
|
||||||
|
if(isWatching === true) {
|
||||||
contentObserver.disconnect();
|
contentObserver.disconnect();
|
||||||
|
isWatching = false;
|
||||||
cb();
|
cb();
|
||||||
|
isWatching = true;
|
||||||
contentObserver.observe(editor.contentElt, {
|
contentObserver.observe(editor.contentElt, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
characterData: true
|
characterData: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var diffMatchPatch = new diff_match_patch();
|
var diffMatchPatch = new diff_match_patch();
|
||||||
var jsonDiffPatch = jsondiffpatch.create({
|
var jsonDiffPatch = jsondiffpatch.create({
|
||||||
@ -147,10 +155,7 @@ define([
|
|||||||
// Update editor
|
// Update editor
|
||||||
noWatch(function() {
|
noWatch(function() {
|
||||||
if(previousTextContent != state.content) {
|
if(previousTextContent != state.content) {
|
||||||
inputElt.value = state.content;
|
inputElt.setValueSilently(state.content);
|
||||||
fileDesc.content = state.content;
|
|
||||||
eventMgr.onContentChanged(fileDesc, state.content);
|
|
||||||
previousTextContent = state.content;
|
|
||||||
}
|
}
|
||||||
inputElt.setSelectionStartEnd(selectionStart, selectionEnd);
|
inputElt.setSelectionStartEnd(selectionStart, selectionEnd);
|
||||||
var discussionListJSON = fileDesc.discussionListJSON;
|
var discussionListJSON = fileDesc.discussionListJSON;
|
||||||
@ -501,6 +506,7 @@ define([
|
|||||||
editor.$marginElt = $(editor.marginElt);
|
editor.$marginElt = $(editor.marginElt);
|
||||||
|
|
||||||
contentObserver = new MutationObserver(checkContentChange);
|
contentObserver = new MutationObserver(checkContentChange);
|
||||||
|
isWatching = true;
|
||||||
contentObserver.observe(editor.contentElt, {
|
contentObserver.observe(editor.contentElt, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: 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', {
|
Object.defineProperty(inputElt, 'selectionStart', {
|
||||||
get: function () {
|
get: function () {
|
||||||
return selectionStart;
|
return selectionStart;
|
||||||
@ -573,7 +591,7 @@ define([
|
|||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
inputElt.setSelectionStartEnd = function (start, end) {
|
inputElt.setSelectionStartEnd = function(start, end) {
|
||||||
selectionStart = start;
|
selectionStart = start;
|
||||||
selectionEnd = end;
|
selectionEnd = end;
|
||||||
fileDesc.editorStart = selectionStart;
|
fileDesc.editorStart = selectionStart;
|
||||||
|
@ -40,8 +40,16 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var fileDesc;
|
||||||
|
markdownSectionParser.onFileSelected = function(fileDescParam) {
|
||||||
|
fileDesc = fileDescParam;
|
||||||
|
};
|
||||||
|
|
||||||
var sectionCounter = 0;
|
var sectionCounter = 0;
|
||||||
function parseFileContent(fileDesc, content) {
|
function parseFileContent(fileDescParam, content) {
|
||||||
|
if(fileDescParam !== fileDesc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var frontMatter = (fileDesc.frontMatter || {})._frontMatter || '';
|
var frontMatter = (fileDesc.frontMatter || {})._frontMatter || '';
|
||||||
var text = content.substring(frontMatter.length);
|
var text = content.substring(frontMatter.length);
|
||||||
var tmpText = text + "\n\n";
|
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]*)$/;
|
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 results = regex.exec(content);
|
||||||
var yaml = results[2];
|
var yaml = results[2];
|
||||||
|
|
||||||
|
@ -671,10 +671,10 @@ define([
|
|||||||
];
|
];
|
||||||
utils.crc32 = function(str) {
|
utils.crc32 = function(str) {
|
||||||
var n = 0, crc = -1;
|
var n = 0, crc = -1;
|
||||||
for ( var i = 0; i < str.length; i++) {
|
str.split('').forEach(function(char) {
|
||||||
n = (crc ^ str.charCodeAt(i)) & 0xFF;
|
n = (crc ^ char.charCodeAt(0)) & 0xFF;
|
||||||
crc = (crc >>> 8) ^ mHash[n];
|
crc = (crc >>> 8) ^ mHash[n];
|
||||||
}
|
});
|
||||||
crc = crc ^ (-1);
|
crc = crc ^ (-1);
|
||||||
if(crc < 0) {
|
if(crc < 0) {
|
||||||
crc = 0xFFFFFFFF + crc + 1;
|
crc = 0xFFFFFFFF + crc + 1;
|
||||||
@ -688,7 +688,7 @@ define([
|
|||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
console.log('Run 10,000 times in ' + (Date.now() - startTime) + 'ms');
|
console.log('Run 10,000 times in ' + (Date.now() - startTime) + 'ms');
|
||||||
}
|
};
|
||||||
|
|
||||||
return utils;
|
return utils;
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user