Fixed sync merge
This commit is contained in:
parent
4ae6e540d4
commit
b543e85779
@ -72,9 +72,6 @@ define([
|
|||||||
objectHash: function(obj) {
|
objectHash: function(obj) {
|
||||||
return JSON.stringify(obj);
|
return JSON.stringify(obj);
|
||||||
},
|
},
|
||||||
arrays: {
|
|
||||||
detectMove: false,
|
|
||||||
},
|
|
||||||
textDiff: {
|
textDiff: {
|
||||||
minLength: 9999999
|
minLength: 9999999
|
||||||
}
|
}
|
||||||
@ -82,25 +79,75 @@ define([
|
|||||||
|
|
||||||
var merge = settings.conflictMode == 'merge';
|
var merge = settings.conflictMode == 'merge';
|
||||||
Provider.prototype.syncMerge = function(fileDesc, syncAttributes, remoteContent, remoteTitle, remoteDiscussionListJSON) {
|
Provider.prototype.syncMerge = function(fileDesc, syncAttributes, remoteContent, remoteTitle, remoteDiscussionListJSON) {
|
||||||
var lineArray = [];
|
|
||||||
var lineHash = {};
|
|
||||||
|
|
||||||
function linesToChars(text) {
|
function cleanupDiffs(diffs) {
|
||||||
var chars = '';
|
var result = [];
|
||||||
var lineArrayLength = lineArray.length;
|
var removeDiff = [-1, ''];
|
||||||
text.split('\n').forEach(function(line) {
|
var addDiff = [1, ''];
|
||||||
if(lineHash.hasOwnProperty(line)) {
|
var distance = 20;
|
||||||
chars += String.fromCharCode(lineHash[line]);
|
diffs.forEach(function(diff) {
|
||||||
} else {
|
var diffType = diff[0];
|
||||||
chars += String.fromCharCode(lineArrayLength);
|
var diffText = diff[1];
|
||||||
lineHash[line] = lineArrayLength;
|
if(diffType === 0) {
|
||||||
lineArray[lineArrayLength++] = line;
|
if(diffText.length > distance) {
|
||||||
|
if(removeDiff[1] || addDiff[1]) {
|
||||||
|
var match = /\S+/.exec(diffText);
|
||||||
|
if(match) {
|
||||||
|
var prefixOffset = match.index + match[0].length;
|
||||||
|
var prefix = diffText.substring(0, prefixOffset);
|
||||||
|
diffText = diffText.substring(prefixOffset);
|
||||||
|
removeDiff[1] += prefix;
|
||||||
|
addDiff[1] += prefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(diffText) {
|
||||||
|
var suffixOffset = diffText.length;
|
||||||
|
while(suffixOffset && /\S/.test(diffText[suffixOffset - 1])) {
|
||||||
|
suffixOffset--;
|
||||||
|
}
|
||||||
|
var suffix = diffText.substring(suffixOffset);
|
||||||
|
diffText = diffText.substring(0, suffixOffset);
|
||||||
|
if(diffText.length > distance) {
|
||||||
|
removeDiff[1] && result.push(removeDiff);
|
||||||
|
removeDiff = [-1, ''];
|
||||||
|
addDiff[1] && result.push(addDiff);
|
||||||
|
addDiff = [1, ''];
|
||||||
|
result.push([0, diffText]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
removeDiff[1] += diffText;
|
||||||
|
addDiff[1] += diffText;
|
||||||
|
}
|
||||||
|
removeDiff[1] += suffix;
|
||||||
|
addDiff[1] += suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
removeDiff[1] += diffText;
|
||||||
|
addDiff[1] += diffText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(diffType === -1) {
|
||||||
|
removeDiff[1] += diffText;
|
||||||
|
}
|
||||||
|
else if(diffType === 1) {
|
||||||
|
addDiff[1] += diffText;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return chars;
|
if(removeDiff[1] == addDiff[1]) {
|
||||||
|
result.push([0, addDiff[1]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
removeDiff[1] && result.push(removeDiff);
|
||||||
|
addDiff[1] && result.push(addDiff);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveComments(oldTextContent, newTextContent, discussionList) {
|
function moveComments(oldTextContent, newTextContent, discussionList) {
|
||||||
|
if(!discussionList.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var changes = diffMatchPatch.diff_main(oldTextContent, newTextContent);
|
var changes = diffMatchPatch.diff_main(oldTextContent, newTextContent);
|
||||||
var changed = false;
|
var changed = false;
|
||||||
var startOffset = 0;
|
var startOffset = 0;
|
||||||
@ -121,20 +168,20 @@ define([
|
|||||||
// selectionEnd
|
// selectionEnd
|
||||||
if(discussion.selectionEnd >= endOffset) {
|
if(discussion.selectionEnd >= endOffset) {
|
||||||
discussion.selectionEnd += diffOffset;
|
discussion.selectionEnd += diffOffset;
|
||||||
changed = true;
|
discussion.discussionIndex && (changed = true);
|
||||||
}
|
}
|
||||||
else if(discussion.selectionEnd > startOffset) {
|
else if(discussion.selectionEnd > startOffset) {
|
||||||
discussion.selectionEnd = startOffset;
|
discussion.selectionEnd = startOffset;
|
||||||
changed = true;
|
discussion.discussionIndex && (changed = true);
|
||||||
}
|
}
|
||||||
// selectionStart
|
// selectionStart
|
||||||
if(discussion.selectionStart >= endOffset) {
|
if(discussion.selectionStart >= endOffset) {
|
||||||
discussion.selectionStart += diffOffset;
|
discussion.selectionStart += diffOffset;
|
||||||
changed = true;
|
discussion.discussionIndex && (changed = true);
|
||||||
}
|
}
|
||||||
else if(discussion.selectionStart > startOffset) {
|
else if(discussion.selectionStart > startOffset) {
|
||||||
discussion.selectionStart = startOffset;
|
discussion.selectionStart = startOffset;
|
||||||
changed = true;
|
discussion.discussionIndex && (changed = true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
startOffset = endOffset;
|
startOffset = endOffset;
|
||||||
@ -157,28 +204,32 @@ define([
|
|||||||
var remoteDiscussionListCRC = utils.crc32(remoteDiscussionListJSON);
|
var remoteDiscussionListCRC = utils.crc32(remoteDiscussionListJSON);
|
||||||
|
|
||||||
// Check content
|
// Check content
|
||||||
var contentChanged = localContent != remoteContent;
|
|
||||||
var localContentChanged = syncAttributes.contentCRC != localContentCRC;
|
var localContentChanged = syncAttributes.contentCRC != localContentCRC;
|
||||||
var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC;
|
var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC;
|
||||||
var contentConflict = contentChanged && localContentChanged && remoteContentChanged;
|
var contentChanged = localContent != remoteContent && remoteContentChanged;
|
||||||
contentChanged = contentChanged && remoteContentChanged;
|
var contentConflict = contentChanged && localContentChanged;
|
||||||
|
|
||||||
// Check title
|
// Check title
|
||||||
syncAttributes.titleCRC = syncAttributes.titleCRC || localTitleCRC; // Not synchronized with Dropbox
|
syncAttributes.titleCRC = syncAttributes.titleCRC || localTitleCRC; // Not synchronized with Dropbox
|
||||||
var titleChanged = localTitle != remoteTitle;
|
|
||||||
var localTitleChanged = syncAttributes.titleCRC != localTitleCRC;
|
var localTitleChanged = syncAttributes.titleCRC != localTitleCRC;
|
||||||
var remoteTitleChanged = syncAttributes.titleCRC != remoteTitleCRC;
|
var remoteTitleChanged = syncAttributes.titleCRC != remoteTitleCRC;
|
||||||
var titleConflict = titleChanged && localTitleChanged && remoteTitleChanged;
|
var titleChanged = localTitle != remoteTitle && remoteTitleChanged;
|
||||||
titleChanged = titleChanged && remoteTitleChanged;
|
var titleConflict = titleChanged && localTitleChanged;
|
||||||
|
|
||||||
// Check discussionList
|
// Check discussionList
|
||||||
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 discussionListChanged = localDiscussionListJSON != remoteDiscussionListJSON && remoteDiscussionListChanged;
|
||||||
discussionListChanged = discussionListChanged && remoteDiscussionListChanged;
|
var discussionListConflict = discussionListChanged && localDiscussionListChanged;
|
||||||
|
|
||||||
var conflictList = [];
|
var conflictList = [];
|
||||||
|
var newContent = remoteContent;
|
||||||
|
var newTitle = remoteTitle;
|
||||||
|
var newDiscussionList = remoteDiscussionList;
|
||||||
|
var adjustLocalDiscussionList = false;
|
||||||
|
var adjustRemoteDiscussionList = false;
|
||||||
|
var mergeDiscussionList = false;
|
||||||
|
var diffs, patch;
|
||||||
if(
|
if(
|
||||||
(!merge && (contentConflict || titleConflict || discussionListConflict)) ||
|
(!merge && (contentConflict || titleConflict || discussionListConflict)) ||
|
||||||
(contentConflict && syncAttributes.content === undefined) ||
|
(contentConflict && syncAttributes.content === undefined) ||
|
||||||
@ -189,25 +240,21 @@ define([
|
|||||||
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 {
|
||||||
var oldDiscussionList;
|
|
||||||
var patch, delta;
|
|
||||||
if(contentConflict) {
|
if(contentConflict) {
|
||||||
// Patch content (line mode)
|
// Patch content
|
||||||
var oldContent = syncAttributes.content;
|
var oldContent = syncAttributes.content;
|
||||||
/*
|
diffs = diffMatchPatch.diff_main(oldContent, localContent);
|
||||||
var oldContentLines = linesToChars(syncAttributes.content);
|
diffMatchPatch.diff_cleanupSemantic(diffs);
|
||||||
var localContentLines = linesToChars(localContent);
|
patch = diffMatchPatch.patch_make(oldContent, diffs);
|
||||||
var remoteContentLines = linesToChars(remoteContent);
|
|
||||||
*/
|
|
||||||
patch = diffMatchPatch.patch_make(oldContent, localContent);
|
|
||||||
var patchResult = diffMatchPatch.patch_apply(patch, remoteContent);
|
var patchResult = diffMatchPatch.patch_apply(patch, remoteContent);
|
||||||
var newContent = patchResult[0];
|
newContent = patchResult[0];
|
||||||
if(patchResult[1].some(function(patchSuccess) {
|
if(patchResult[1].some(function(patchSuccess) {
|
||||||
return !patchSuccess;
|
return !patchSuccess;
|
||||||
})) {
|
})) {
|
||||||
// Conflicts (some modifications have not been applied properly)
|
// Remaining conflicts
|
||||||
var diffs = diffMatchPatch.diff_main(localContent, newContent);
|
diffs = diffMatchPatch.diff_main(localContent, newContent);
|
||||||
diffMatchPatch.diff_cleanupSemantic(diffs);
|
diffs = cleanupDiffs(diffs);
|
||||||
|
|
||||||
newContent = '';
|
newContent = '';
|
||||||
var conflict;
|
var conflict;
|
||||||
diffs.forEach(function(diff) {
|
diffs.forEach(function(diff) {
|
||||||
@ -231,84 +278,105 @@ define([
|
|||||||
conflictList.push(conflict);
|
conflictList.push(conflict);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
}
|
||||||
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(contentChanged) {
|
||||||
if(localDiscussionListChanged || !remoteDiscussionListChanged) {
|
if(localDiscussionListChanged) {
|
||||||
// Move local discussion according to content patch
|
adjustLocalDiscussionList = true;
|
||||||
var localDiscussionArray = _.values(localDiscussionList);
|
|
||||||
fileDesc.newDiscussion && localDiscussionArray.push(fileDesc.newDiscussion);
|
|
||||||
discussionListChanged |= moveComments(localContent, newContent, localDiscussionArray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(remoteDiscussionListChanged) {
|
if(remoteDiscussionListChanged) {
|
||||||
// Move remote discussion according to content patch
|
adjustRemoteDiscussionList = true;
|
||||||
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 {
|
else {
|
||||||
remoteDiscussionList = localDiscussionList;
|
adjustLocalDiscussionList = true;
|
||||||
|
newDiscussionList = localDiscussionList;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(conflictList.length) {
|
|
||||||
discussionListChanged = true;
|
|
||||||
// Add conflicts to discussionList
|
|
||||||
conflictList.forEach(function(conflict) {
|
|
||||||
// Create discussion index
|
|
||||||
var discussionIndex;
|
|
||||||
do {
|
|
||||||
discussionIndex = utils.randomString() + utils.randomString(); // Increased size to prevent collision
|
|
||||||
} while(_.has(remoteDiscussionList, discussionIndex));
|
|
||||||
conflict.discussionIndex = discussionIndex;
|
|
||||||
remoteDiscussionList[discussionIndex] = conflict;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteContent = newContent;
|
|
||||||
}
|
}
|
||||||
else if(discussionListConflict) {
|
|
||||||
// Patch remote discussionList with local modifications
|
if(discussionListConflict) {
|
||||||
oldDiscussionList = JSON.parse(syncAttributes.discussionList);
|
mergeDiscussionList = true;
|
||||||
delta = jsonDiffPatch.diff(oldDiscussionList, localDiscussionList);
|
|
||||||
jsonDiffPatch.patch(remoteDiscussionList, delta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(titleConflict) {
|
if(titleConflict) {
|
||||||
// Patch title
|
// Patch title
|
||||||
patch = diffMatchPatch.patch_make(syncAttributes.title, localTitle);
|
patch = diffMatchPatch.patch_make(syncAttributes.title, localTitle);
|
||||||
remoteTitle = diffMatchPatch.patch_apply(patch, remoteTitle)[0];
|
newTitle = diffMatchPatch.patch_apply(patch, remoteTitle)[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust local discussions offset
|
||||||
|
var editorSelection;
|
||||||
|
if(contentChanged) {
|
||||||
|
var localDiscussionArray = [];
|
||||||
|
// Adjust editor's cursor position and local discussions at the same time
|
||||||
|
if(fileMgr.currentFile === fileDesc) {
|
||||||
|
editorSelection = {
|
||||||
|
selectionStart: editor.inputElt.selectionStart,
|
||||||
|
selectionEnd: editor.inputElt.selectionEnd
|
||||||
|
};
|
||||||
|
localDiscussionArray.push(editorSelection);
|
||||||
|
fileDesc.newDiscussion && localDiscussionArray.push(fileDesc.newDiscussion);
|
||||||
|
}
|
||||||
|
if(adjustLocalDiscussionList) {
|
||||||
|
localDiscussionArray = localDiscussionArray.concat(_.values(localDiscussionList));
|
||||||
|
}
|
||||||
|
discussionListChanged |= moveComments(localContent, newContent, localDiscussionArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust remote discussions offset
|
||||||
|
if(adjustRemoteDiscussionList) {
|
||||||
|
var remoteDiscussionArray = _.values(remoteDiscussionList);
|
||||||
|
moveComments(remoteContent, newContent, remoteDiscussionArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch remote discussionList with local modifications
|
||||||
|
if(mergeDiscussionList) {
|
||||||
|
var oldDiscussionList = JSON.parse(syncAttributes.discussionList);
|
||||||
|
diffs = jsonDiffPatch.diff(oldDiscussionList, localDiscussionList);
|
||||||
|
jsonDiffPatch.patch(remoteDiscussionList, diffs);
|
||||||
|
_.each(remoteDiscussionList, function(discussion, discussionIndex) {
|
||||||
|
if(!discussion) {
|
||||||
|
delete remoteDiscussionList[discussionIndex];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(conflictList.length) {
|
||||||
|
discussionListChanged = true;
|
||||||
|
// Add conflicts to discussionList
|
||||||
|
conflictList.forEach(function(conflict) {
|
||||||
|
// Create discussion index
|
||||||
|
var discussionIndex;
|
||||||
|
do {
|
||||||
|
discussionIndex = utils.randomString() + utils.randomString(); // Increased size to prevent collision
|
||||||
|
} while(_.has(newDiscussionList, discussionIndex));
|
||||||
|
conflict.discussionIndex = discussionIndex;
|
||||||
|
newDiscussionList[discussionIndex] = conflict;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if(titleChanged) {
|
if(titleChanged) {
|
||||||
fileDesc.title = remoteTitle;
|
fileDesc.title = newTitle;
|
||||||
eventMgr.onTitleChanged(fileDesc);
|
eventMgr.onTitleChanged(fileDesc);
|
||||||
eventMgr.onMessage('"' + localTitle + '" has been renamed to "' + remoteTitle + '" on ' + this.providerName + '.');
|
eventMgr.onMessage('"' + localTitle + '" has been renamed to "' + newTitle + '" on ' + this.providerName + '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(contentChanged || discussionListChanged) {
|
if(contentChanged || discussionListChanged) {
|
||||||
var self = this;
|
var self = this;
|
||||||
editor.watcher.noWatch(function() {
|
editor.watcher.noWatch(function() {
|
||||||
if(contentChanged) {
|
if(contentChanged) {
|
||||||
if(!/\n$/.test(remoteContent)) {
|
if(!/\n$/.test(newContent)) {
|
||||||
remoteContent += '\n';
|
newContent += '\n';
|
||||||
}
|
}
|
||||||
if(fileMgr.currentFile === fileDesc) {
|
if(fileMgr.currentFile === fileDesc) {
|
||||||
editor.setValueNoWatch(remoteContent);
|
editor.setValueNoWatch(newContent);
|
||||||
|
editorSelection && editor.inputElt.setSelectionStartEnd(
|
||||||
|
editorSelection.selectionStart,
|
||||||
|
editorSelection.selectionEnd
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fileDesc.content = remoteContent;
|
fileDesc.content = newContent;
|
||||||
eventMgr.onContentChanged(fileDesc, remoteContent);
|
eventMgr.onContentChanged(fileDesc, newContent);
|
||||||
}
|
}
|
||||||
if(discussionListChanged) {
|
if(discussionListChanged) {
|
||||||
fileDesc.discussionList = remoteDiscussionList;
|
fileDesc.discussionList = remoteDiscussionList;
|
||||||
@ -331,7 +399,7 @@ define([
|
|||||||
editor.undoManager.saveState();
|
editor.undoManager.saveState();
|
||||||
eventMgr.onMessage('"' + remoteTitle + '" has been updated from ' + self.providerName + '.');
|
eventMgr.onMessage('"' + remoteTitle + '" has been updated from ' + self.providerName + '.');
|
||||||
if(conflictList.length) {
|
if(conflictList.length) {
|
||||||
eventMgr.onMessage('"' + remoteTitle + '" contains conflicts you need to review.');
|
eventMgr.onMessage('"' + remoteTitle + '" has conflicts that you have to review.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,10 @@ define([
|
|||||||
var scrollTop = 0;
|
var scrollTop = 0;
|
||||||
var inputElt;
|
var inputElt;
|
||||||
var $inputElt;
|
var $inputElt;
|
||||||
|
var contentElt;
|
||||||
|
var $contentElt;
|
||||||
|
var marginElt;
|
||||||
|
var $marginElt;
|
||||||
var previewElt;
|
var previewElt;
|
||||||
var pagedownEditor;
|
var pagedownEditor;
|
||||||
var refreshPreviewLater = (function() {
|
var refreshPreviewLater = (function() {
|
||||||
@ -70,7 +74,7 @@ define([
|
|||||||
this.startWatching = function() {
|
this.startWatching = function() {
|
||||||
this.isWatching = true;
|
this.isWatching = true;
|
||||||
contentObserver = contentObserver || new MutationObserver(checkContentChange);
|
contentObserver = contentObserver || new MutationObserver(checkContentChange);
|
||||||
contentObserver.observe(editor.contentElt, {
|
contentObserver.observe(contentElt, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
characterData: true
|
characterData: true
|
||||||
@ -113,11 +117,14 @@ define([
|
|||||||
}
|
}
|
||||||
editor.setValueNoWatch = setValueNoWatch;
|
editor.setValueNoWatch = setValueNoWatch;
|
||||||
|
|
||||||
function setSelectionStartEnd(start, end) {
|
function setSelectionStartEnd(start, end, applySelection) {
|
||||||
selectionStart = start;
|
selectionStart = start;
|
||||||
selectionEnd = end;
|
selectionEnd = end;
|
||||||
fileDesc.editorStart = selectionStart;
|
fileDesc.editorStart = selectionStart;
|
||||||
fileDesc.editorEnd = selectionEnd;
|
fileDesc.editorEnd = selectionEnd;
|
||||||
|
if(applySelection === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var range = createRange(start, end);
|
var range = createRange(start, end);
|
||||||
var selection = window.getSelection();
|
var selection = window.getSelection();
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -270,7 +277,7 @@ define([
|
|||||||
};
|
};
|
||||||
this.currentMode = undefined;
|
this.currentMode = undefined;
|
||||||
lastMode = undefined;
|
lastMode = undefined;
|
||||||
editor.contentElt.textContent = content;
|
contentElt.textContent = content;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var undoManager = new UndoManager();
|
var undoManager = new UndoManager();
|
||||||
@ -393,7 +400,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findOffset(offset) {
|
function findOffset(offset) {
|
||||||
var walker = document.createTreeWalker(editor.contentElt, 4);
|
var walker = document.createTreeWalker(contentElt, 4);
|
||||||
while(walker.nextNode()) {
|
while(walker.nextNode()) {
|
||||||
var text = walker.currentNode.nodeValue || '';
|
var text = walker.currentNode.nodeValue || '';
|
||||||
if (text.length > offset) {
|
if (text.length > offset) {
|
||||||
@ -405,7 +412,7 @@ define([
|
|||||||
offset -= text.length;
|
offset -= text.length;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
element: editor.contentElt,
|
element: contentElt,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
error: true
|
error: true
|
||||||
};
|
};
|
||||||
@ -505,20 +512,26 @@ define([
|
|||||||
editor.init = function(elt1, elt2) {
|
editor.init = function(elt1, elt2) {
|
||||||
inputElt = elt1;
|
inputElt = elt1;
|
||||||
$inputElt = $(inputElt);
|
$inputElt = $(inputElt);
|
||||||
|
editor.inputElt = inputElt;
|
||||||
|
editor.$inputElt = $inputElt;
|
||||||
|
|
||||||
previewElt = elt2;
|
previewElt = elt2;
|
||||||
|
|
||||||
editor.contentElt = crel('div', {
|
contentElt = crel('div', {
|
||||||
class: 'editor-content',
|
class: 'editor-content',
|
||||||
contenteditable: true
|
contenteditable: true
|
||||||
});
|
});
|
||||||
inputElt.appendChild(editor.contentElt);
|
inputElt.appendChild(contentElt);
|
||||||
editor.$contentElt = $(editor.contentElt);
|
editor.contentElt = contentElt;
|
||||||
|
$contentElt = $(contentElt);
|
||||||
|
editor.$contentElt = $contentElt;
|
||||||
|
|
||||||
editor.marginElt = crel('div', {
|
marginElt = crel('div', {
|
||||||
class: 'editor-margin'
|
class: 'editor-margin'
|
||||||
});
|
});
|
||||||
inputElt.appendChild(editor.marginElt);
|
inputElt.appendChild(marginElt);
|
||||||
editor.$marginElt = $(editor.marginElt);
|
$marginElt = $(marginElt);
|
||||||
|
editor.$marginElt = $marginElt;
|
||||||
|
|
||||||
watcher.startWatching();
|
watcher.startWatching();
|
||||||
|
|
||||||
@ -535,14 +548,14 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
inputElt.focus = function() {
|
inputElt.focus = function() {
|
||||||
editor.$contentElt.focus();
|
$contentElt.focus();
|
||||||
setSelectionStartEnd(selectionStart, selectionEnd);
|
setSelectionStartEnd(selectionStart, selectionEnd);
|
||||||
inputElt.scrollTop = scrollTop;
|
inputElt.scrollTop = scrollTop;
|
||||||
};
|
};
|
||||||
editor.$contentElt.focus(function() {
|
$contentElt.focus(function() {
|
||||||
inputElt.focused = true;
|
inputElt.focused = true;
|
||||||
});
|
});
|
||||||
editor.$contentElt.blur(function() {
|
$contentElt.blur(function() {
|
||||||
inputElt.focused = false;
|
inputElt.focused = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -585,7 +598,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
var clearNewline = false;
|
var clearNewline = false;
|
||||||
editor.$contentElt.on('keydown', function (evt) {
|
$contentElt.on('keydown', function (evt) {
|
||||||
if(
|
if(
|
||||||
evt.which === 17 || // Ctrl
|
evt.which === 17 || // Ctrl
|
||||||
evt.which === 91 || // Cmd
|
evt.which === 91 || // Cmd
|
||||||
@ -742,7 +755,7 @@ define([
|
|||||||
// Check modified
|
// Check modified
|
||||||
section.textWithFrontMatter != newSection.textWithFrontMatter ||
|
section.textWithFrontMatter != newSection.textWithFrontMatter ||
|
||||||
// Check that section has not been detached or moved
|
// Check that section has not been detached or moved
|
||||||
section.elt.parentNode !== editor.contentElt ||
|
section.elt.parentNode !== contentElt ||
|
||||||
// Check also the content since nodes can be injected in sections via copy/paste
|
// Check also the content since nodes can be injected in sections via copy/paste
|
||||||
section.elt.textContent != newSection.textWithFrontMatter) {
|
section.elt.textContent != newSection.textWithFrontMatter) {
|
||||||
leftIndex = index;
|
leftIndex = index;
|
||||||
@ -758,7 +771,7 @@ define([
|
|||||||
// Check modified
|
// Check modified
|
||||||
section.textWithFrontMatter != newSection.textWithFrontMatter ||
|
section.textWithFrontMatter != newSection.textWithFrontMatter ||
|
||||||
// Check that section has not been detached or moved
|
// Check that section has not been detached or moved
|
||||||
section.elt.parentNode !== editor.contentElt ||
|
section.elt.parentNode !== contentElt ||
|
||||||
// Check also the content since nodes can be injected in sections via copy/paste
|
// Check also the content since nodes can be injected in sections via copy/paste
|
||||||
section.elt.textContent != newSection.textWithFrontMatter) {
|
section.elt.textContent != newSection.textWithFrontMatter) {
|
||||||
rightIndex = -index;
|
rightIndex = -index;
|
||||||
@ -789,30 +802,30 @@ define([
|
|||||||
});
|
});
|
||||||
watcher.noWatch(function() {
|
watcher.noWatch(function() {
|
||||||
if(fileChanged === true) {
|
if(fileChanged === true) {
|
||||||
editor.contentElt.innerHTML = '';
|
contentElt.innerHTML = '';
|
||||||
editor.contentElt.appendChild(newSectionEltList);
|
contentElt.appendChild(newSectionEltList);
|
||||||
setSelectionStartEnd(selectionStart, selectionEnd);
|
setSelectionStartEnd(selectionStart, selectionEnd);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Remove outdated sections
|
// Remove outdated sections
|
||||||
sectionsToRemove.forEach(function(section) {
|
sectionsToRemove.forEach(function(section) {
|
||||||
// section can be already removed
|
// section can be already removed
|
||||||
section.elt.parentNode === editor.contentElt && editor.contentElt.removeChild(section.elt);
|
section.elt.parentNode === contentElt && contentElt.removeChild(section.elt);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(insertBeforeSection !== undefined) {
|
if(insertBeforeSection !== undefined) {
|
||||||
editor.contentElt.insertBefore(newSectionEltList, insertBeforeSection.elt);
|
contentElt.insertBefore(newSectionEltList, insertBeforeSection.elt);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
editor.contentElt.appendChild(newSectionEltList);
|
contentElt.appendChild(newSectionEltList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove unauthorized nodes (text nodes outside of sections or duplicated sections via copy/paste)
|
// Remove unauthorized nodes (text nodes outside of sections or duplicated sections via copy/paste)
|
||||||
var childNode = editor.contentElt.firstChild;
|
var childNode = contentElt.firstChild;
|
||||||
while(childNode) {
|
while(childNode) {
|
||||||
var nextNode = childNode.nextSibling;
|
var nextNode = childNode.nextSibling;
|
||||||
if(!childNode.generated) {
|
if(!childNode.generated) {
|
||||||
editor.contentElt.removeChild(childNode);
|
contentElt.removeChild(childNode);
|
||||||
}
|
}
|
||||||
childNode = nextNode;
|
childNode = nextNode;
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@ define([
|
|||||||
var comments = new Extension("comments", 'Comments');
|
var comments = new Extension("comments", 'Comments');
|
||||||
|
|
||||||
var commentTmpl = [
|
var commentTmpl = [
|
||||||
'<div class="comment-block">',
|
'<div class="comment-block<%= reply ? \' reply\' : \'\' %>">',
|
||||||
' <div class="comment-author"><%= author %></div>',
|
' <div class="comment-author"><i class="icon-comment"></i> <%= author %></div>',
|
||||||
' <div class="comment-content"><%= content %></div>',
|
' <div class="comment-content"><%= content %></div>',
|
||||||
'</div>',
|
'</div>',
|
||||||
].join('');
|
].join('');
|
||||||
@ -23,7 +23,7 @@ define([
|
|||||||
' <a href="#" class="action-remove-discussion pull-right<%= !type ? \'\': \' hide\' %>">',
|
' <a href="#" class="action-remove-discussion pull-right<%= !type ? \'\': \' hide\' %>">',
|
||||||
' <i class="icon-trash"></i>',
|
' <i class="icon-trash"></i>',
|
||||||
' </a>',
|
' </a>',
|
||||||
' <%- title %>',
|
' “<%- title %>”',
|
||||||
'</span>',
|
'</span>',
|
||||||
].join('');
|
].join('');
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ define([
|
|||||||
var offsetMap = {};
|
var offsetMap = {};
|
||||||
function setCommentEltCoordinates(commentElt, y) {
|
function setCommentEltCoordinates(commentElt, y) {
|
||||||
var lineIndex = Math.round(y / 10);
|
var lineIndex = Math.round(y / 10);
|
||||||
var yOffset = -8;
|
var yOffset = -10;
|
||||||
if(commentElt.className.indexOf(' icon-fork') !== -1) {
|
if(commentElt.className.indexOf(' icon-fork') !== -1) {
|
||||||
yOffset = -12;
|
yOffset = -12;
|
||||||
}
|
}
|
||||||
@ -48,9 +48,8 @@ define([
|
|||||||
|
|
||||||
var inputElt;
|
var inputElt;
|
||||||
var marginElt;
|
var marginElt;
|
||||||
var commentEltList = [];
|
|
||||||
var newCommentElt = crel('a', {
|
var newCommentElt = crel('a', {
|
||||||
class: 'discussion icon-comment new'
|
class: 'discussion icon-quote-left new'
|
||||||
});
|
});
|
||||||
var cursorY;
|
var cursorY;
|
||||||
comments.onCursorCoordinates = function(x, y) {
|
comments.onCursorCoordinates = function(x, y) {
|
||||||
@ -58,39 +57,56 @@ define([
|
|||||||
setCommentEltCoordinates(newCommentElt, cursorY);
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function Context(commentElt, fileDesc) {
|
||||||
|
this.commentElt = commentElt;
|
||||||
|
this.$commentElt = $(commentElt).addClass('active');
|
||||||
|
this.fileDesc = fileDesc;
|
||||||
|
this.discussionIndex = commentElt.discussionIndex;
|
||||||
|
}
|
||||||
|
Context.prototype.getDiscussion = function() {
|
||||||
|
if(!this.discussionIndex) {
|
||||||
|
return this.fileDesc.newDiscussion;
|
||||||
|
}
|
||||||
|
return this.fileDesc.discussionList[this.discussionIndex];
|
||||||
|
};
|
||||||
|
Context.prototype.getPopoverElt = function() {
|
||||||
|
return document.querySelector('.comments-popover .popover:last-child');
|
||||||
|
};
|
||||||
var currentContext;
|
var currentContext;
|
||||||
function movePopover(commentElt) {
|
function movePopover(commentElt) {
|
||||||
// Move popover in the margin
|
// Move popover in the margin
|
||||||
var context = currentContext;
|
var popoverElt = currentContext.getPopoverElt();
|
||||||
context.popoverElt = document.querySelector('.comments-popover .popover:last-child');
|
|
||||||
var left = 0;
|
var left = 0;
|
||||||
if(context.popoverElt.offsetWidth < marginElt.offsetWidth - 10) {
|
if(popoverElt.offsetWidth < marginElt.offsetWidth - 10) {
|
||||||
left = marginElt.offsetWidth - 10 - context.popoverElt.offsetWidth;
|
left = marginElt.offsetWidth - 10 - popoverElt.offsetWidth;
|
||||||
}
|
}
|
||||||
context.popoverElt.style.left = left + 'px';
|
popoverElt.style.left = left + 'px';
|
||||||
context.popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(commentElt.style.right) - commentElt.offsetWidth / 2 - left) + 'px';
|
popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(commentElt.style.right) - commentElt.offsetWidth / 2 - left) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
var cssApplier;
|
var cssApplier;
|
||||||
var currentFileDesc;
|
var currentFileDesc;
|
||||||
var refreshTimeoutId;
|
var refreshTimeoutId;
|
||||||
|
var commentEltMap = {};
|
||||||
var refreshDiscussions = _.debounce(function() {
|
var refreshDiscussions = _.debounce(function() {
|
||||||
if(currentFileDesc === undefined) {
|
if(currentFileDesc === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var author = storage['author.name'];
|
var author = storage['author.name'];
|
||||||
commentEltList.forEach(function(commentElt) {
|
|
||||||
marginElt.removeChild(commentElt);
|
|
||||||
});
|
|
||||||
commentEltList = [];
|
|
||||||
offsetMap = {};
|
offsetMap = {};
|
||||||
var discussionList = _.values(currentFileDesc.discussionList);
|
var discussionList = _.values(currentFileDesc.discussionList);
|
||||||
function refreshOne() {
|
function refreshOne() {
|
||||||
if(discussionList.length === 0) {
|
if(discussionList.length === 0) {
|
||||||
|
// Remove outdated commentElt
|
||||||
|
_.filter(commentEltMap, function(commentElt, discussionIndex) {
|
||||||
|
return !_.has(currentFileDesc.discussionList, discussionIndex);
|
||||||
|
}).forEach(function(commentElt) {
|
||||||
|
marginElt.removeChild(commentElt);
|
||||||
|
});
|
||||||
// Move newCommentElt
|
// Move newCommentElt
|
||||||
setCommentEltCoordinates(newCommentElt, cursorY);
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
||||||
if(currentContext && !currentContext.discussion.discussionIndex) {
|
if(currentContext && !currentContext.discussionIndex) {
|
||||||
inputElt.scrollTop += parseInt(newCommentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
inputElt.scrollTop += parseInt(newCommentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
||||||
movePopover(newCommentElt);
|
movePopover(newCommentElt);
|
||||||
}
|
}
|
||||||
@ -105,16 +121,19 @@ define([
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var isReplied = _.last(discussion.commentList).author != author;
|
var isReplied = _.last(discussion.commentList).author != author;
|
||||||
commentElt.className += ' icon-comment' + (isReplied ? ' replied' : ' added');
|
commentElt.className += ' icon-quote-left' + (isReplied ? ' replied' : ' added');
|
||||||
}
|
}
|
||||||
commentElt.discussionIndex = discussion.discussionIndex;
|
commentElt.discussionIndex = discussion.discussionIndex;
|
||||||
var coordinates = inputElt.getOffsetCoordinates(discussion.selectionEnd);
|
var coordinates = inputElt.getOffsetCoordinates(discussion.selectionEnd);
|
||||||
var lineIndex = setCommentEltCoordinates(commentElt, coordinates.y);
|
var lineIndex = setCommentEltCoordinates(commentElt, coordinates.y);
|
||||||
offsetMap[lineIndex] = (offsetMap[lineIndex] || 0) + 1;
|
offsetMap[lineIndex] = (offsetMap[lineIndex] || 0) + 1;
|
||||||
marginElt.appendChild(commentElt);
|
|
||||||
commentEltList.push(commentElt);
|
|
||||||
|
|
||||||
if(currentContext && currentContext.discussion == discussion) {
|
var oldCommentElt = commentEltMap[discussion.discussionIndex];
|
||||||
|
oldCommentElt && marginElt.removeChild(oldCommentElt);
|
||||||
|
marginElt.appendChild(commentElt);
|
||||||
|
commentEltMap[discussion.discussionIndex] = commentElt;
|
||||||
|
|
||||||
|
if(currentContext && currentContext.getDiscussion() == discussion) {
|
||||||
inputElt.scrollTop += parseInt(commentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
inputElt.scrollTop += parseInt(commentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
||||||
movePopover(commentElt);
|
movePopover(commentElt);
|
||||||
}
|
}
|
||||||
@ -140,15 +159,15 @@ define([
|
|||||||
if(currentContext !== undefined) {
|
if(currentContext !== undefined) {
|
||||||
// Refresh conversation if popover is open
|
// Refresh conversation if popover is open
|
||||||
var context = currentContext;
|
var context = currentContext;
|
||||||
if(context.discussion.discussionIndex) {
|
if(context.discussionIndex) {
|
||||||
context.discussion = currentFileDesc.discussionList[context.discussion.discussionIndex];
|
|
||||||
context.popoverElt.querySelector('.discussion-comment-list').innerHTML = getDiscussionComments();
|
context.popoverElt.querySelector('.discussion-comment-list').innerHTML = getDiscussionComments();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
cssApplier.undoToRange(context.rangyRange);
|
cssApplier.undoToRange(context.rangyRange);
|
||||||
}
|
}
|
||||||
catch(e) {}
|
catch(e) {}
|
||||||
context.selectionRange = inputElt.createRange(context.discussion.selectionStart, context.discussion.selectionEnd);
|
var discussion = context.getDiscussion();
|
||||||
|
context.selectionRange = inputElt.createRange(discussion.selectionStart, discussion.selectionEnd);
|
||||||
|
|
||||||
// Highlight selected text
|
// Highlight selected text
|
||||||
context.rangyRange = rangy.createRange();
|
context.rangyRange = rangy.createRange();
|
||||||
@ -174,7 +193,7 @@ define([
|
|||||||
comments.onDiscussionRemoved = function(fileDesc, discussion) {
|
comments.onDiscussionRemoved = function(fileDesc, discussion) {
|
||||||
if(currentFileDesc === fileDesc) {
|
if(currentFileDesc === fileDesc) {
|
||||||
// Close popover if the discussion has been removed
|
// Close popover if the discussion has been removed
|
||||||
if(currentContext !== undefined && currentContext.discussion.discussionIndex == discussion.discussionIndex) {
|
if(currentContext !== undefined && currentContext.discussionIndex == discussion.discussionIndex) {
|
||||||
closeCurrentPopover();
|
closeCurrentPopover();
|
||||||
}
|
}
|
||||||
refreshDiscussions();
|
refreshDiscussions();
|
||||||
@ -186,13 +205,17 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getDiscussionComments() {
|
function getDiscussionComments() {
|
||||||
if(currentContext.discussion.type == 'conflict') {
|
var discussion = currentContext.getDiscussion();
|
||||||
|
var author = storage['author.name'];
|
||||||
|
if(discussion.type == 'conflict') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return currentContext.discussion.commentList.map(function(comment) {
|
return discussion.commentList.map(function(comment) {
|
||||||
|
var commentAuthor = comment.author || 'Anonymous';
|
||||||
return _.template(commentTmpl, {
|
return _.template(commentTmpl, {
|
||||||
author: comment.author || 'Anonymous',
|
author: commentAuthor,
|
||||||
content: comment.content
|
content: comment.content,
|
||||||
|
reply: comment.author != author
|
||||||
});
|
});
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
@ -221,37 +244,36 @@ define([
|
|||||||
if(!currentContext) {
|
if(!currentContext) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var titleLength = currentContext.discussion.selectionEnd - currentContext.discussion.selectionStart;
|
var discussion = currentContext.getDiscussion();
|
||||||
var title = inputElt.textContent.substr(currentContext.discussion.selectionStart, titleLength > 20 ? 20 : titleLength);
|
var titleLength = discussion.selectionEnd - discussion.selectionStart;
|
||||||
|
var title = inputElt.textContent.substr(discussion.selectionStart, titleLength > 20 ? 20 : titleLength);
|
||||||
if(titleLength > 20) {
|
if(titleLength > 20) {
|
||||||
title += '...';
|
title += '...';
|
||||||
}
|
}
|
||||||
return _.template(popoverTitleTmpl, {
|
return _.template(popoverTitleTmpl, {
|
||||||
title: title,
|
title: title,
|
||||||
type: currentContext.discussion.type
|
type: discussion.type
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
content: function() {
|
content: function() {
|
||||||
var content = _.template(commentsPopoverContentHTML, {
|
var content = _.template(commentsPopoverContentHTML, {
|
||||||
commentList: getDiscussionComments(),
|
commentList: getDiscussionComments(),
|
||||||
type: currentContext.discussion.type
|
type: currentContext.getDiscussion().type
|
||||||
});
|
});
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
selector: '#wmd-input > .editor-margin > .discussion'
|
selector: '#wmd-input > .editor-margin > .discussion'
|
||||||
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
closeCurrentPopover();
|
closeCurrentPopover();
|
||||||
var context = {
|
var context = new Context(evt.target, currentFileDesc);
|
||||||
$commentElt: $(evt.target).addClass('active'),
|
|
||||||
fileDesc: currentFileDesc
|
|
||||||
};
|
|
||||||
currentContext = context;
|
currentContext = context;
|
||||||
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
||||||
|
|
||||||
// If it's an existing discussion
|
// If it's an existing discussion
|
||||||
if(evt.target.discussionIndex) {
|
var discussion = context.getDiscussion();
|
||||||
context.discussion = currentFileDesc.discussionList[evt.target.discussionIndex];
|
if(discussion) {
|
||||||
context.selectionRange = inputElt.createRange(context.discussion.selectionStart, context.discussion.selectionEnd);
|
context.selectionRange = inputElt.createRange(discussion.selectionStart, discussion.selectionEnd);
|
||||||
|
inputElt.setSelectionStartEnd(discussion.selectionStart, discussion.selectionEnd, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,24 +294,23 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.selectionRange = inputElt.createRange(selectionStart, selectionEnd);
|
context.selectionRange = inputElt.createRange(selectionStart, selectionEnd);
|
||||||
context.discussion = {
|
currentFileDesc.newDiscussion = {
|
||||||
selectionStart: selectionStart,
|
selectionStart: selectionStart,
|
||||||
selectionEnd: selectionEnd,
|
selectionEnd: selectionEnd,
|
||||||
commentList: []
|
commentList: []
|
||||||
};
|
};
|
||||||
currentFileDesc.newDiscussion = context.discussion;
|
|
||||||
}).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
var context = currentContext;
|
var context = currentContext;
|
||||||
context.popoverElt = document.querySelector('.comments-popover .popover:last-child');
|
movePopover(context.commentElt);
|
||||||
movePopover(evt.target);
|
var popoverElt = context.getPopoverElt();
|
||||||
|
|
||||||
// Scroll to the bottom of the discussion
|
// Scroll to the bottom of the discussion
|
||||||
context.popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
||||||
|
|
||||||
context.$authorInputElt = $(context.popoverElt.querySelector('.input-comment-author')).val(storage['author.name']);
|
context.$authorInputElt = $(popoverElt.querySelector('.input-comment-author')).val(storage['author.name']);
|
||||||
context.$contentInputElt = $(context.popoverElt.querySelector('.input-comment-content'));
|
context.$contentInputElt = $(popoverElt.querySelector('.input-comment-content'));
|
||||||
var $addButton = $(context.popoverElt.querySelector('.action-add-comment'));
|
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
||||||
context.$contentInputElt.keydown(function(evt) {
|
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
|
||||||
// Enter key
|
// Enter key
|
||||||
switch(evt.which) {
|
switch(evt.which) {
|
||||||
case 13:
|
case 13:
|
||||||
@ -310,24 +331,25 @@ define([
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var discussion = context.getDiscussion();
|
||||||
context.$contentInputElt.val('');
|
context.$contentInputElt.val('');
|
||||||
closeCurrentPopover();
|
closeCurrentPopover();
|
||||||
|
|
||||||
context.discussion.commentList.push({
|
discussion.commentList.push({
|
||||||
author: author,
|
author: author,
|
||||||
content: content
|
content: content
|
||||||
});
|
});
|
||||||
var discussionList = context.fileDesc.discussionList || {};
|
var discussionList = context.fileDesc.discussionList || {};
|
||||||
if(!context.discussion.discussionIndex) {
|
if(!discussion.discussionIndex) {
|
||||||
// Create discussion index
|
// Create discussion index
|
||||||
var discussionIndex;
|
var discussionIndex;
|
||||||
do {
|
do {
|
||||||
discussionIndex = utils.randomString() + utils.randomString(); // Increased size to prevent collision
|
discussionIndex = utils.randomString() + utils.randomString(); // Increased size to prevent collision
|
||||||
} while(_.has(discussionList, discussionIndex));
|
} while(_.has(discussionList, discussionIndex));
|
||||||
context.discussion.discussionIndex = discussionIndex;
|
discussion.discussionIndex = discussionIndex;
|
||||||
discussionList[discussionIndex] = context.discussion;
|
discussionList[discussionIndex] = discussion;
|
||||||
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage
|
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage
|
||||||
eventMgr.onDiscussionCreated(context.fileDesc, context.discussion);
|
eventMgr.onDiscussionCreated(context.fileDesc, discussion);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage
|
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage
|
||||||
@ -336,33 +358,28 @@ define([
|
|||||||
inputElt.focus();
|
inputElt.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
var $removeButton = $(context.popoverElt.querySelector('.action-remove-discussion'));
|
var $removeButton = $(popoverElt.querySelector('.action-remove-discussion'));
|
||||||
if(evt.target.discussionIndex) {
|
if(evt.target.discussionIndex) {
|
||||||
// If it's an existing discussion
|
// If it's an existing discussion
|
||||||
/*
|
var $removeCancelButton = $(popoverElt.querySelectorAll('.action-remove-discussion-cancel'));
|
||||||
if(context.discussion.type == 'conflict') {
|
var $removeConfirmButton = $(popoverElt.querySelectorAll('.action-remove-discussion-confirm'));
|
||||||
$(context.popoverElt.querySelector('.conflict-review')).removeClass('hide');
|
|
||||||
$(context.popoverElt.querySelector('.new-comment-block')).addClass('hide');
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
var $removeCancelButton = $(context.popoverElt.querySelectorAll('.action-remove-discussion-cancel'));
|
|
||||||
var $removeConfirmButton = $(context.popoverElt.querySelectorAll('.action-remove-discussion-confirm'));
|
|
||||||
$removeButton.click(function() {
|
$removeButton.click(function() {
|
||||||
$(context.popoverElt.querySelector('.new-comment-block')).addClass('hide');
|
$(popoverElt.querySelector('.new-comment-block')).addClass('hide');
|
||||||
$(context.popoverElt.querySelector('.remove-discussion-confirm')).removeClass('hide');
|
$(popoverElt.querySelector('.remove-discussion-confirm')).removeClass('hide');
|
||||||
context.popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
||||||
});
|
});
|
||||||
$removeCancelButton.click(function() {
|
$removeCancelButton.click(function() {
|
||||||
$(context.popoverElt.querySelector('.new-comment-block')).removeClass('hide');
|
$(popoverElt.querySelector('.new-comment-block')).removeClass('hide');
|
||||||
$(context.popoverElt.querySelector('.remove-discussion-confirm')).addClass('hide');
|
$(popoverElt.querySelector('.remove-discussion-confirm')).addClass('hide');
|
||||||
context.popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
||||||
context.$contentInputElt.focus();
|
context.$contentInputElt.focus();
|
||||||
});
|
});
|
||||||
$removeConfirmButton.click(function() {
|
$removeConfirmButton.click(function() {
|
||||||
closeCurrentPopover();
|
closeCurrentPopover();
|
||||||
delete context.fileDesc.discussionList[context.discussion.discussionIndex];
|
var discussion = context.getDiscussion();
|
||||||
|
delete context.fileDesc.discussionList[discussion.discussionIndex];
|
||||||
context.fileDesc.discussionList = context.fileDesc.discussionList; // Write discussionList in localStorage
|
context.fileDesc.discussionList = context.fileDesc.discussionList; // Write discussionList in localStorage
|
||||||
eventMgr.onCommentsChanged(context.fileDesc);
|
eventMgr.onDiscussionRemoved(context.fileDesc, discussion);
|
||||||
inputElt.focus();
|
inputElt.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -372,7 +389,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent from closing on click inside the popover
|
// Prevent from closing on click inside the popover
|
||||||
$(context.popoverElt).on('click', function(evt) {
|
$(popoverElt).on('click', function(evt) {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div class="discussion-comment-list"><%= commentList %></div>
|
<div class="discussion-comment-list"><%= commentList %></div>
|
||||||
<div class="new-comment-block<%= !type ? '': ' hide' %>">
|
<div class="new-comment-block<%= !type ? '': ' hide' %>">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input class="form-control input-comment-author" placeholder="Your name"></input>
|
<i class="icon-comment"></i> <input class="form-control input-comment-author" placeholder="Your name"></input>
|
||||||
<textarea class="form-control input-comment-content"></textarea>
|
<textarea class="form-control input-comment-content"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group text-right">
|
<div class="form-group text-right">
|
||||||
|
@ -129,6 +129,7 @@ define([
|
|||||||
syncAttributes.discussionList = discussionList;
|
syncAttributes.discussionList = discussionList;
|
||||||
}
|
}
|
||||||
syncAttributes.contentCRC = contentCRC;
|
syncAttributes.contentCRC = contentCRC;
|
||||||
|
syncAttributes.titleCRC = titleCRC; // Not synchronized but has to be there for syncMerge
|
||||||
syncAttributes.discussionListCRC = discussionListCRC;
|
syncAttributes.discussionListCRC = discussionListCRC;
|
||||||
|
|
||||||
callback(undefined, true);
|
callback(undefined, true);
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
@list-group-active-border: fade(@secondary, 5%);
|
@list-group-active-border: fade(@secondary, 5%);
|
||||||
@list-group-hover-bg: @btn-default-hover-bg;
|
@list-group-hover-bg: @btn-default-hover-bg;
|
||||||
@list-group-hover-border-color: fade(@secondary, 10%);
|
@list-group-hover-border-color: fade(@secondary, 10%);
|
||||||
@input-color: @secondary-color-dark;
|
@input-color: @secondary-color-darkest;
|
||||||
@input-color-placeholder: @disabled-color;
|
@input-color-placeholder: @disabled-color;
|
||||||
@btn-default-color: @secondary-color-darker;
|
@btn-default-color: @secondary-color-darker;
|
||||||
@btn-default-bg: @transparent;
|
@btn-default-bg: @transparent;
|
||||||
@ -1059,6 +1059,9 @@ a {
|
|||||||
top: 0;
|
top: 0;
|
||||||
.discussion {
|
.discussion {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
&:before {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
&.new {
|
&.new {
|
||||||
color: fade(@tertiary-color, 10%);
|
color: fade(@tertiary-color, 10%);
|
||||||
&:hover, &.active, &.active:hover {
|
&:hover, &.active, &.active:hover {
|
||||||
@ -1072,7 +1075,7 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.replied {
|
&.replied {
|
||||||
color: fade(@label-danger-bg, 45%);
|
color: fade(@label-danger-bg, 55%);
|
||||||
&:hover, &.active, &.active:hover {
|
&:hover, &.active, &.active:hover {
|
||||||
color: fade(@label-danger-bg, 80%) !important;
|
color: fade(@label-danger-bg, 80%) !important;
|
||||||
}
|
}
|
||||||
@ -1083,9 +1086,6 @@ a {
|
|||||||
&:hover, &.active, &.active:hover {
|
&:hover, &.active, &.active:hover {
|
||||||
color: @label-danger-bg !important;
|
color: @label-danger-bg !important;
|
||||||
}
|
}
|
||||||
&:before {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -1094,7 +1094,7 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.has-selection > .editor-margin .icon-comment.new {
|
&.has-selection > .editor-margin .discussion.new {
|
||||||
color: fade(@tertiary-color, 25%);
|
color: fade(@tertiary-color, 25%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1423,6 +1423,7 @@ input[type="file"] {
|
|||||||
padding: 5px 0 15px;
|
padding: 5px 0 15px;
|
||||||
border-bottom: 1px solid @hr-border;
|
border-bottom: 1px solid @hr-border;
|
||||||
line-height: @headings-line-height;
|
line-height: @headings-line-height;
|
||||||
|
overflow: hidden;
|
||||||
.action-remove-discussion {
|
.action-remove-discussion {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
@ -1447,15 +1448,25 @@ input[type="file"] {
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
.comment-author {
|
.comment-author {
|
||||||
padding-left: 12px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: @input-color;
|
||||||
|
}
|
||||||
|
.icon-comment {
|
||||||
|
font-size: 14px;
|
||||||
|
color: fade(@label-warning-bg, 60%);
|
||||||
|
}
|
||||||
|
.reply .icon-comment {
|
||||||
|
color: fade(@label-danger-bg, 70%);
|
||||||
}
|
}
|
||||||
.input-comment-author {
|
.input-comment-author {
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
.box-shadow(none);
|
.box-shadow(none);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
height: 32px;
|
height: 28px;
|
||||||
|
padding: 0 0 5px;
|
||||||
|
width: 150px;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user