Stability fixes

This commit is contained in:
benweet 2014-04-06 01:59:32 +01:00
parent da2351d837
commit f0e60a2637
7 changed files with 342 additions and 299 deletions

View File

@ -27,12 +27,9 @@ define([
) { ) {
throw 'invalid'; throw 'invalid';
} }
if(discussion.type == 'conflict') { discussion.commentList && discussion.commentList.forEach(function(comment) {
return;
}
discussion.commentList.forEach(function(comment) {
if( if(
(!_.isString(comment.author)) || (!(!comment.author || _.isString(comment.author))) ||
(!_.isString(comment.content)) (!_.isString(comment.content))
) { ) {
throw 'invalid'; throw 'invalid';
@ -46,7 +43,7 @@ define([
}; };
Provider.prototype.serializeContent = function(content, discussionList) { Provider.prototype.serializeContent = function(content, discussionList) {
if(discussionList.length > 2) { // It's a serialized JSON if(discussionList.length > 2) { // Serialized JSON
return content + '<!--se_discussion_list:' + discussionList + '-->'; return content + '<!--se_discussion_list:' + discussionList + '-->';
} }
return content; return content;
@ -86,24 +83,29 @@ define([
var addDiff = [1, '']; var addDiff = [1, ''];
var distance = 20; var distance = 20;
function pushDiff() { function pushDiff() {
var separator = '///'; if(!removeDiff[1] && !addDiff[1]) {
if(removeDiff[1]) { return;
removeDiff[1] = '///' + removeDiff[1] + separator;
separator = '';
result.push(removeDiff);
} }
if(addDiff[1]) { if(!removeDiff[1] || !addDiff[1]) {
addDiff[1] = separator + addDiff[1] + '///'; result.push([0, removeDiff[1] + addDiff[1]]);
}
else {
removeDiff[1] = '///' + removeDiff[1] + '///';
addDiff[1] += '///';
result.push(removeDiff);
result.push(addDiff); result.push(addDiff);
} }
removeDiff = [-1, '']; removeDiff = [-1, ''];
addDiff = [1, '']; addDiff = [1, ''];
} }
diffs.forEach(function(diff) { diffs.forEach(function(diff, index) {
function firstOrLast() {
return index === 0 || index === diffs.length - 1;
}
var diffType = diff[0]; var diffType = diff[0];
var diffText = diff[1]; var diffText = diff[1];
if(diffType === 0) { if(diffType === 0) {
if(diffText.length > distance) { if(firstOrLast() || diffText.length > distance) {
if(removeDiff[1] || addDiff[1]) { if(removeDiff[1] || addDiff[1]) {
var match = /\s/.exec(diffText); var match = /\s/.exec(diffText);
if(match) { if(match) {
@ -121,7 +123,7 @@ define([
} }
var suffix = diffText.substring(suffixOffset); var suffix = diffText.substring(suffixOffset);
diffText = diffText.substring(0, suffixOffset); diffText = diffText.substring(0, suffixOffset);
if(diffText.length > distance) { if(firstOrLast() || diffText.length > distance) {
pushDiff(); pushDiff();
result.push([0, diffText]); result.push([0, diffText]);
} }
@ -154,51 +156,6 @@ define([
return result; return result;
} }
function moveComments(oldTextContent, newTextContent, discussionList) {
if(!discussionList.length) {
return;
}
var changes = diffMatchPatch.diff_main(oldTextContent, newTextContent);
var changed = 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;
discussion.discussionIndex && (changed = true);
}
else if(discussion.selectionEnd > startOffset) {
discussion.selectionEnd = startOffset;
discussion.discussionIndex && (changed = true);
}
// selectionStart
if(discussion.selectionStart >= endOffset) {
discussion.selectionStart += diffOffset;
discussion.discussionIndex && (changed = true);
}
else if(discussion.selectionStart > startOffset) {
discussion.selectionStart = startOffset;
discussion.discussionIndex && (changed = true);
}
});
startOffset = endOffset;
});
return changed;
}
var localContent = fileDesc.content; var localContent = fileDesc.content;
var localTitle = fileDesc.title; var localTitle = fileDesc.title;
var localDiscussionListJSON = fileDesc.discussionListJSON; var localDiscussionListJSON = fileDesc.discussionListJSON;
@ -314,7 +271,7 @@ define([
} }
} }
// Adjust local discussions offset // Adjust local discussions offsets
var editorSelection; var editorSelection;
if(contentChanged) { if(contentChanged) {
var localDiscussionArray = []; var localDiscussionArray = [];
@ -330,13 +287,13 @@ define([
if(adjustLocalDiscussionList) { if(adjustLocalDiscussionList) {
localDiscussionArray = localDiscussionArray.concat(_.values(localDiscussionList)); localDiscussionArray = localDiscussionArray.concat(_.values(localDiscussionList));
} }
discussionListChanged |= moveComments(localContent, newContent, localDiscussionArray); discussionListChanged |= editor.adjustCommentOffsets(localContent, newContent, localDiscussionArray);
} }
// Adjust remote discussions offset // Adjust remote discussions offsets
if(adjustRemoteDiscussionList) { if(adjustRemoteDiscussionList) {
var remoteDiscussionArray = _.values(remoteDiscussionList); var remoteDiscussionArray = _.values(remoteDiscussionList);
moveComments(remoteContent, newContent, remoteDiscussionArray); editor.adjustCommentOffsets(remoteContent, newContent, remoteDiscussionArray);
} }
// Patch remote discussionList with local modifications // Patch remote discussionList with local modifications

View File

@ -359,7 +359,7 @@ define([
var $titleContainer; var $titleContainer;
var marginWidth = 18 * 2 + 25 + 25; var marginWidth = 18 * 2 + 25 + 25;
var titleWidth = 18 + 348; var titleWidth = 18 + 348;
var leftButtonsWidth = 18 * 4 + 80 + 160 + 160 + 80; var leftButtonsWidth = 18 * 4 + 80 + 160 + 200 + 80;
var rightButtonsWidth = 18 + 80; var rightButtonsWidth = 18 + 80;
var buttonsDropdownWidth = 40; var buttonsDropdownWidth = 40;
function adjustWindow() { function adjustWindow() {
@ -394,7 +394,8 @@ define([
if(pagedownEditor !== undefined) { if(pagedownEditor !== undefined) {
// If the editor is already created // If the editor is already created
return editor.undoMgr.init(); editor.undoMgr.init();
return pagedownEditor.uiManager.setUndoRedoButtonStates();
} }
// Create the converter and the editor // Create the converter and the editor
@ -450,10 +451,12 @@ define([
$("#wmd-code-button").append($('<i class="icon-code">')).appendTo($btnGroupElt); $("#wmd-code-button").append($('<i class="icon-code">')).appendTo($btnGroupElt);
$("#wmd-image-button").append($('<i class="icon-picture">')).appendTo($btnGroupElt); $("#wmd-image-button").append($('<i class="icon-picture">')).appendTo($btnGroupElt);
$btnGroupElt = $('.wmd-button-group3'); $btnGroupElt = $('.wmd-button-group3');
var $openDiscussionElt = $btnGroupElt.find('.button-open-discussion');
$("#wmd-olist-button").append($('<i class="icon-list-numbered">')).appendTo($btnGroupElt); $("#wmd-olist-button").append($('<i class="icon-list-numbered">')).appendTo($btnGroupElt);
$("#wmd-ulist-button").append($('<i class="icon-list-bullet">')).appendTo($btnGroupElt); $("#wmd-ulist-button").append($('<i class="icon-list-bullet">')).appendTo($btnGroupElt);
$("#wmd-heading-button").append($('<i class="icon-text-height">')).appendTo($btnGroupElt); $("#wmd-heading-button").append($('<i class="icon-text-height">')).appendTo($btnGroupElt);
$("#wmd-hr-button").append($('<i class="icon-ellipsis">')).appendTo($btnGroupElt); $("#wmd-hr-button").append($('<i class="icon-ellipsis">')).appendTo($btnGroupElt);
$openDiscussionElt.appendTo($btnGroupElt);
$btnGroupElt = $('.wmd-button-group4'); $btnGroupElt = $('.wmd-button-group4');
$("#wmd-undo-button").append($('<i class="icon-reply">')).appendTo($btnGroupElt); $("#wmd-undo-button").append($('<i class="icon-reply">')).appendTo($btnGroupElt);
$("#wmd-redo-button").append($('<i class="icon-forward">')).appendTo($btnGroupElt); $("#wmd-redo-button").append($('<i class="icon-forward">')).appendTo($btnGroupElt);

View File

@ -1,4 +1,5 @@
/* jshint -W084, -W099 */ /* jshint -W084, -W099 */
// Credit to http://dabblet.com/
define([ define([
'jquery', 'jquery',
'underscore', 'underscore',
@ -8,15 +9,10 @@ define([
'diff_match_patch_uncompressed', 'diff_match_patch_uncompressed',
'jsondiffpatch', 'jsondiffpatch',
'crel', 'crel',
'rangy',
'MutationObservers', 'MutationObservers',
'libs/prism-markdown' 'libs/prism-markdown'
], function ($, _, settings, eventMgr, Prism, diff_match_patch, jsondiffpatch, crel) { ], function ($, _, settings, eventMgr, Prism, diff_match_patch, jsondiffpatch, crel, rangy) {
function strSplice(str, i, remove, add) {
remove = +remove || 0;
add = add || '';
return str.slice(0, i) + add + str.slice(i + remove);
}
var editor = {}; var editor = {};
var scrollTop = 0; var scrollTop = 0;
@ -110,6 +106,7 @@ define([
}); });
function SelectionMgr() { function SelectionMgr() {
var self = this;
this.selectionStart = 0; this.selectionStart = 0;
this.selectionEnd = 0; this.selectionEnd = 0;
this.cursorY = 0; this.cursorY = 0;
@ -147,21 +144,26 @@ define([
if(end === undefined) { if(end === undefined) {
end = this.selectionEnd; end = this.selectionEnd;
} }
var maxOffset = inputElt.textContent.length - 1;
if(start > maxOffset) {
start = maxOffset;
range = undefined;
skipSelectionUpdate = false;
}
if(end > maxOffset) {
end = maxOffset;
range = undefined;
skipSelectionUpdate = false;
}
this.selectionStart = start;
this.selectionEnd = end;
var min = Math.min(start, end); var min = Math.min(start, end);
var max = Math.max(start, end); var max = Math.max(start, end);
range = range || this.createRange(min, max); range = range || this.createRange(min, max);
if(start < end || !skipSelectionUpdate) {
this.selectionStart = min;
this.selectionEnd = max;
}
else {
this.selectionStart = max;
this.selectionEnd = min;
}
if(!skipSelectionUpdate) { if(!skipSelectionUpdate) {
var selection = window.getSelection(); var selection = rangy.getSelection();
selection.removeAllRanges(); selection.removeAllRanges();
selection.addRange(range); selection.addRange(range, start > end);
} }
fileDesc.editorStart = this.selectionStart; fileDesc.editorStart = this.selectionStart;
fileDesc.editorEnd = this.selectionEnd; fileDesc.editorEnd = this.selectionEnd;
@ -174,47 +176,42 @@ define([
} }
return range; return range;
}; };
this.saveSelectionState = function(skipSelectionUpdate) { this.saveSelectionState = (function() {
if(fileChanged === false) { function save() {
var selection = window.getSelection(); if(fileChanged === false) {
if(!skipSelectionUpdate && selection.rangeCount > 0) { var selection = rangy.getSelection();
var range = selection.getRangeAt(0); if(selection.rangeCount > 0) {
var element = range.startContainer; var range = selection.getRangeAt(0);
var element = range.startContainer;
if ((inputElt.compareDocumentPosition(element) & 0x10)) { if ((contentElt.compareDocumentPosition(element) & 0x10)) {
var container = element; var container = element;
var offset = range.startOffset; var offset = range.startOffset;
do { do {
while (element = element.previousSibling) { while (element = element.previousSibling) {
if (element.textContent) { if (element.textContent) {
offset += element.textContent.length; offset += element.textContent.length;
}
} }
element = container = container.parentNode;
} while (element && element != inputElt);
if(selection.isBackwards()) {
self.setSelectionStartEnd(offset + (range + '').length, offset, range, true);
}
else {
self.setSelectionStartEnd(offset, offset + (range + '').length, range, true);
} }
element = container = container.parentNode;
} while (element && element != inputElt);
// Determine if it's a backward selection
var isBackwardSelection = false;
if (!selection.isCollapsed) {
var tmpRange = document.createRange();
tmpRange.setStart(selection.anchorNode, selection.anchorOffset);
tmpRange.setEnd(selection.focusNode, selection.focusOffset);
isBackwardSelection = tmpRange.collapsed;
tmpRange.detach();
}
if(isBackwardSelection) {
this.setSelectionStartEnd(offset + (range + '').length, offset, range, true);
}
else {
this.setSelectionStartEnd(offset, offset + (range + '').length, range, true);
} }
} }
} }
undoMgr.saveSelectionState();
} }
undoMgr.saveSelectionState(); var debouncedSave = _.debounce(save, 0);
}; return function(debounced) {
debounced ? debouncedSave() : save();
};
})();
this.getCoordinates = function(inputOffset, container, offset) { this.getCoordinates = function(inputOffset, container, offset) {
if(!container) { if(!container) {
offset = this.findOffset(inputOffset); offset = this.findOffset(inputOffset);
@ -236,7 +233,7 @@ define([
container: container, container: container,
offset: offset offset: offset
}; };
if(selectedChar === undefined || selectedChar == '\n') { if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) {
if(startOffset.offset === 0) { if(startOffset.offset === 0) {
startOffset = inputOffset - 1; startOffset = inputOffset - 1;
} }
@ -265,31 +262,41 @@ define([
} }
var selectionMgr = new SelectionMgr(); var selectionMgr = new SelectionMgr();
editor.selectionMgr = selectionMgr; editor.selectionMgr = selectionMgr;
$(document).on('selectionchange', function() {
selectionMgr.saveSelectionState(true);
});
var adjustCursorPosition = _.debounce(function() { var adjustCursorPosition = (function() {
if(inputElt === undefined) { var adjust = _.debounce(function() {
return; var adjust = inputElt.offsetHeight / 2;
} if(adjust > 130) {
selectionMgr.saveSelectionState(); adjust = 130;
}
var adjust = inputElt.offsetHeight / 2; var cursorMinY = inputElt.scrollTop + adjust;
if(adjust > 130) { var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjust;
adjust = 130; if(selectionMgr.cursorY < cursorMinY) {
} inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
var cursorMinY = inputElt.scrollTop + adjust; }
var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjust; else if(selectionMgr.cursorY > cursorMaxY) {
if(selectionMgr.cursorY < cursorMinY) { inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY;
inputElt.scrollTop += selectionMgr.cursorY - cursorMinY; }
} }, 0);
else if(selectionMgr.cursorY > cursorMaxY) { return function() {
inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY; if(inputElt === undefined) {
} return;
}, 0); }
selectionMgr.saveSelectionState(true);
adjust();
};
})();
eventMgr.addListener('onLayoutResize', adjustCursorPosition); eventMgr.addListener('onLayoutResize', adjustCursorPosition);
var textContent; var textContent;
function setValue(value) { function setValue(value) {
var startOffset = diffMatchPatch.diff_commonPrefix(textContent, value); var startOffset = diffMatchPatch.diff_commonPrefix(textContent, value);
if(startOffset === textContent.length) {
startOffset--;
}
var endOffset = Math.min( var endOffset = Math.min(
diffMatchPatch.diff_commonSuffix(textContent, value), diffMatchPatch.diff_commonSuffix(textContent, value),
textContent.length - startOffset, textContent.length - startOffset,
@ -310,7 +317,7 @@ define([
function getValue() { function getValue() {
return textContent; return textContent;
} }
editor.setValueNoWatch = getValue; editor.getValue = getValue;
function UndoMgr() { function UndoMgr() {
var undoStack = []; var undoStack = [];
@ -459,48 +466,10 @@ define([
currentTextContent += '\n'; currentTextContent += '\n';
} }
undoMgr.currentMode = undoMgr.currentMode || 'typing'; undoMgr.currentMode = undoMgr.currentMode || 'typing';
var changes = diffMatchPatch.diff_main(textContent, currentTextContent);
textContent = currentTextContent;
// Move comments according to changes
var updateDiscussionList = false;
var startOffset = 0;
var discussionList = _.values(fileDesc.discussionList); var discussionList = _.values(fileDesc.discussionList);
fileDesc.newDiscussion && discussionList.push(fileDesc.newDiscussion); fileDesc.newDiscussion && discussionList.push(fileDesc.newDiscussion);
changes.forEach(function(change) { var updateDiscussionList = adjustCommentOffsets(textContent, currentTextContent, discussionList);
var changeType = change[0]; textContent = currentTextContent;
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;
});
if(updateDiscussionList === true) { if(updateDiscussionList === true) {
fileDesc.discussionList = fileDesc.discussionList; // Write discussionList in localStorage fileDesc.discussionList = fileDesc.discussionList; // Write discussionList in localStorage
} }
@ -525,12 +494,57 @@ define([
} }
} }
function adjustCommentOffsets(oldTextContent, newTextContent, discussionList) {
if(!discussionList.length) {
return;
}
var changes = diffMatchPatch.diff_main(oldTextContent, newTextContent);
var changed = 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;
discussion.discussionIndex && (changed = true);
}
else if(discussion.selectionEnd > startOffset) {
discussion.selectionEnd = startOffset;
discussion.discussionIndex && (changed = true);
}
// selectionStart
if(discussion.selectionStart >= endOffset) {
discussion.selectionStart += diffOffset;
discussion.discussionIndex && (changed = true);
}
else if(discussion.selectionStart > startOffset) {
discussion.selectionStart = startOffset;
discussion.discussionIndex && (changed = true);
}
});
if(changeType === 1) {
startOffset += changeText.length;
}
});
return changed;
}
editor.adjustCommentOffsets = adjustCommentOffsets;
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;
contentElt = crel('div', { contentElt = crel('div', {
@ -538,10 +552,8 @@ define([
contenteditable: true contenteditable: true
}); });
inputElt.appendChild(contentElt); inputElt.appendChild(contentElt);
editor.contentElt = contentElt;
$contentElt = $(contentElt); $contentElt = $(contentElt);
editor.$contentElt = $contentElt; editor.$contentElt = $contentElt;
marginElt = crel('div', { marginElt = crel('div', {
class: 'editor-margin' class: 'editor-margin'
}); });
@ -568,12 +580,6 @@ define([
selectionMgr.setSelectionStartEnd(); selectionMgr.setSelectionStartEnd();
inputElt.scrollTop = scrollTop; inputElt.scrollTop = scrollTop;
}; };
$contentElt.focus(function() {
inputElt.focused = true;
});
$contentElt.blur(function() {
inputElt.focused = false;
});
Object.defineProperty(inputElt, 'value', { Object.defineProperty(inputElt, 'value', {
get: function () { get: function () {
@ -584,7 +590,7 @@ define([
Object.defineProperty(inputElt, 'selectionStart', { Object.defineProperty(inputElt, 'selectionStart', {
get: function () { get: function () {
return selectionMgr.selectionStart; return Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
}, },
set: function (value) { set: function (value) {
selectionMgr.setSelectionStartEnd(value); selectionMgr.setSelectionStartEnd(value);
@ -596,7 +602,7 @@ define([
Object.defineProperty(inputElt, 'selectionEnd', { Object.defineProperty(inputElt, 'selectionEnd', {
get: function () { get: function () {
return selectionMgr.selectionEnd; return Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
}, },
set: function (value) { set: function (value) {
selectionMgr.setSelectionStartEnd(undefined, value); selectionMgr.setSelectionStartEnd(undefined, value);
@ -642,9 +648,7 @@ define([
} }
}) })
.on('mouseup', function() { .on('mouseup', function() {
setTimeout(function() { selectionMgr.saveSelectionState(true);
selectionMgr.saveSelectionState();
}, 0);
}) })
.on('paste', function () { .on('paste', function () {
undoMgr.currentMode = 'paste'; undoMgr.currentMode = 'paste';
@ -658,52 +662,52 @@ define([
var action = function (action, options) { var action = function (action, options) {
options = options || {}; options = options || {};
var text = inputElt.value, var textContent = getValue();
ss = options.start || selectionMgr.selectionStart, var selectionStart = options.start || selectionMgr.selectionStart;
se = options.end || selectionMgr.selectionEnd, var selectionEnd = options.end || selectionMgr.selectionEnd;
state = { var state = {
ss: ss, selectionStart: selectionStart,
se: se, selectionEnd: selectionEnd,
before: text.slice(0, ss), before: textContent.slice(0, selectionStart),
after: text.slice(se), after: textContent.slice(selectionEnd),
selection: text.slice(ss, se) selection: textContent.slice(selectionStart, selectionEnd)
}; };
actions[action](state, options); actions[action](state, options);
inputElt.value = state.before + state.selection + state.after; setValue(state.before + state.selection + state.after);
selectionMgr.setSelectionStartEnd(state.ss, state.se); selectionMgr.setSelectionStartEnd(state.selectionStart, state.selectionEnd);
$inputElt.trigger('input'); $inputElt.trigger('input');
}; };
var actions = { var actions = {
indent: function (state, options) { indent: function (state, options) {
function strSplice(str, i, remove, add) {
remove = +remove || 0;
add = add || '';
return str.slice(0, i) + add + str.slice(i + remove);
}
var lf = state.before.lastIndexOf('\n') + 1; var lf = state.before.lastIndexOf('\n') + 1;
if (options.inverse) { if (options.inverse) {
if (/\s/.test(state.before.charAt(lf))) { if (/\s/.test(state.before.charAt(lf))) {
state.before = strSplice(state.before, lf, 1); state.before = strSplice(state.before, lf, 1);
state.ss--; state.selectionStart--;
state.se--; state.selectionEnd--;
} }
state.selection = state.selection.replace(/^[ \t]/gm, ''); state.selection = state.selection.replace(/^[ \t]/gm, '');
} else if (state.selection) { } else if (state.selection) {
state.before = strSplice(state.before, lf, 0, '\t'); state.before = strSplice(state.before, lf, 0, '\t');
state.selection = state.selection.replace(/\r?\n(?=[\s\S])/g, '\n\t'); state.selection = state.selection.replace(/\r?\n(?=[\s\S])/g, '\n\t');
state.selectionStart++;
state.ss++; state.selectionEnd++;
state.se++;
} else { } else {
state.before += '\t'; state.before += '\t';
state.selectionStart++;
state.ss++; state.selectionEnd++;
state.se++;
return; return;
} }
state.se = state.ss + state.selection.length; state.selectionEnd = state.selectionStart + state.selection.length;
}, },
newline: function (state) { newline: function (state) {
@ -711,8 +715,8 @@ define([
if(clearNewline) { if(clearNewline) {
state.before = state.before.substring(0, lf); state.before = state.before.substring(0, lf);
state.selection = ''; state.selection = '';
state.ss = lf; state.selectionStart = lf;
state.se = lf; state.selectionEnd = lf;
clearNewline = false; clearNewline = false;
return; return;
} }
@ -732,8 +736,8 @@ define([
state.before += '\n' + indent; state.before += '\n' + indent;
state.selection = ''; state.selection = '';
state.ss += indent.length + 1; state.selectionStart += indent.length + 1;
state.se = state.ss; state.selectionEnd = state.selectionStart;
}, },
}; };
}; };
@ -843,17 +847,18 @@ define([
}); });
} }
var entityMap = { var escape = (function() {
"&": "&amp;", var entityMap = {
"<": "&lt;", "&": "&amp;",
"\u00a0": ' ', "<": "&lt;",
}; "\u00a0": ' ',
};
function escape(str) { return function(str) {
return str.replace(/[&<\u00a0]/g, function(s) { return str.replace(/[&<\u00a0]/g, function(s) {
return entityMap[s]; return entityMap[s];
}); });
} };
})();
function highlight(section) { function highlight(section) {
var text = escape(section.text); var text = escape(section.text);

View File

@ -10,7 +10,7 @@ define([
"bootstrap" "bootstrap"
], function($, _, utils, storage, crel, rangy, Extension, commentsPopoverContentHTML) { ], function($, _, utils, storage, crel, rangy, Extension, commentsPopoverContentHTML) {
var comments = new Extension("comments", 'Comments'); var comments = new Extension("comments", 'Comments', false, true);
var commentTmpl = [ var commentTmpl = [
'<div class="comment-block<%= reply ? \' reply\' : \'\' %>">', '<div class="comment-block<%= reply ? \' reply\' : \'\' %>">',
@ -40,14 +40,15 @@ 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 = -10; var yOffset = -9;
if(commentElt.className.indexOf(' icon-split') !== -1) { if(commentElt.className.indexOf(' icon-split') !== -1) {
yOffset = -12; yOffset = -12;
} }
var top = (y + yOffset) + 'px'; var top = y + yOffset;
var right = ((offsetMap[lineIndex] || 0) * 25 + 10) + 'px'; var right = (offsetMap[lineIndex] || 0) * 25 + 10;
commentElt.style.top = top; commentElt.orderedIndex = lineIndex * 10000 + right;
commentElt.style.right = right; commentElt.style.top = top + 'px';
commentElt.style.right = right + 'px';
return lineIndex; return lineIndex;
} }
@ -87,17 +88,27 @@ define([
} }
popoverElt.style.left = left + 'px'; popoverElt.style.left = left + 'px';
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 $contentInputElt = currentContext.$contentInputElt;
var popoverTopOffset = document.body.offsetHeight - $contentInputElt.offset().top - $contentInputElt.outerHeight();
if(popoverTopOffset < 0) {
popoverElt.style.top = (parseInt(popoverElt.style.top) + popoverTopOffset) + 'px';
}
} }
var cssApplier; var cssApplier;
var currentFileDesc; var currentFileDesc;
var refreshTimeoutId; var refreshTimeoutId;
var commentEltMap = {}; var commentEltMap = {};
var sortedCommentEltList = [];
var someReplies = false;
var $openDiscussionElt;
var $openDiscussionIconElt;
var refreshDiscussions = _.debounce(function() { var refreshDiscussions = _.debounce(function() {
if(currentFileDesc === undefined) { if(currentFileDesc === undefined) {
return; return;
} }
someReplies = false;
sortedCommentEltList = [];
var author = storage['author.name']; var author = storage['author.name'];
offsetMap = {}; offsetMap = {};
var discussionList = _.values(currentFileDesc.discussionList); var discussionList = _.values(currentFileDesc.discussionList);
@ -116,24 +127,32 @@ define([
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);
} }
sortedCommentEltList = _.sortBy(commentEltMap, function(commentElt) {
return commentElt.orderedIndex;
});
$openDiscussionElt.toggleClass('some', sortedCommentEltList.length !== 0);
$openDiscussionElt.toggleClass('replied', someReplies);
$openDiscussionIconElt.toggleClass('icon-chat', sortedCommentEltList.length !== 0);
return; return;
} }
var discussion = discussionList.pop(); var discussion = discussionList.pop();
var commentElt = crel('a', { var commentElt = commentEltMap[discussion.discussionIndex];
class: 'discussion' if(!commentElt) {
}); commentElt = crel('a');
var isReplied = !discussion.commentList || _.last(discussion.commentList).author != author;
commentElt.className += ' icon-quote-left' + (isReplied ? ' replied' : ' added');
if(discussion.type == 'conflict') {
commentElt.className += ' icon-split';
} }
var className = 'discussion';
var isReplied = !discussion.commentList || _.last(discussion.commentList).author != author;
isReplied && (someReplies = true);
className += ' icon-quote-left' + (isReplied ? ' replied' : ' added');
if(discussion.type == 'conflict') {
className += ' icon-split';
}
commentElt.className = className;
commentElt.discussionIndex = discussion.discussionIndex; commentElt.discussionIndex = discussion.discussionIndex;
var coordinates = editor.selectionMgr.getCoordinates(discussion.selectionEnd); var coordinates = editor.selectionMgr.getCoordinates(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;
var oldCommentElt = commentEltMap[discussion.discussionIndex];
oldCommentElt && marginElt.removeChild(oldCommentElt);
marginElt.appendChild(commentElt); marginElt.appendChild(commentElt);
commentEltMap[discussion.discussionIndex] = commentElt; commentEltMap[discussion.discussionIndex] = commentElt;
@ -225,7 +244,7 @@ define([
if(discussion.type == 'conflict') { if(discussion.type == 'conflict') {
result.unshift(_.template(commentTmpl, { result.unshift(_.template(commentTmpl, {
author: 'StackEdit', author: 'StackEdit',
content: 'Multiple users have made conflicting modifications that you have to review.', content: 'Multiple users have made conflicting modifications.',
reply: true reply: true
})); }));
} }
@ -276,6 +295,9 @@ define([
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) { }).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
closeCurrentPopover(); closeCurrentPopover();
var context = new Context(evt.target, currentFileDesc); var context = new Context(evt.target, currentFileDesc);
if(evt.target !== newCommentElt) {
$newCommentElt.addClass('hide');
}
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;
@ -295,15 +317,15 @@ define([
var match = /\S+/.exec(after); var match = /\S+/.exec(after);
if(match) { if(match) {
selectionStart += match.index; selectionStart += match.index;
if(match.index === 0) { selectionEnd = selectionStart + match[0].length;
while(selectionStart && /\S/.test(textContent[selectionStart - 1])) { }
selectionStart--; if(!match || match.index === 0) {
} while(selectionStart && /\S/.test(textContent[selectionStart - 1])) {
selectionStart--;
} }
selectionEnd += match.index + match[0].length;
} }
} }
context.selectionRange = editor.selectionMgr.createRange(selectionStart, selectionEnd); context.selectionRange = editor.selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd, undefined, true);
currentFileDesc.newDiscussion = { currentFileDesc.newDiscussion = {
selectionStart: selectionStart, selectionStart: selectionStart,
selectionEnd: selectionEnd, selectionEnd: selectionEnd,
@ -311,14 +333,14 @@ define([
}; };
}).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;
movePopover(context.commentElt);
var popoverElt = context.getPopoverElt(); var popoverElt = context.getPopoverElt();
// Scroll to the bottom of the discussion
popoverElt.querySelector('.popover-content').scrollTop = 9999999;
context.$authorInputElt = $(popoverElt.querySelector('.input-comment-author')).val(storage['author.name']); context.$authorInputElt = $(popoverElt.querySelector('.input-comment-author')).val(storage['author.name']);
context.$contentInputElt = $(popoverElt.querySelector('.input-comment-content')); context.$contentInputElt = $(popoverElt.querySelector('.input-comment-content'));
movePopover(context.commentElt);
// Scroll to the bottom of the discussion
popoverElt.querySelector('.scrollport').scrollTop = 9999999;
var $addButton = $(popoverElt.querySelector('.action-add-comment')); var $addButton = $(popoverElt.querySelector('.action-add-comment'));
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) { $().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
// Enter key // Enter key
@ -377,12 +399,12 @@ define([
$removeButton.click(function() { $removeButton.click(function() {
$(popoverElt.querySelector('.new-comment-block')).addClass('hide'); $(popoverElt.querySelector('.new-comment-block')).addClass('hide');
$(popoverElt.querySelector('.remove-discussion-confirm')).removeClass('hide'); $(popoverElt.querySelector('.remove-discussion-confirm')).removeClass('hide');
popoverElt.querySelector('.popover-content').scrollTop = 9999999; popoverElt.querySelector('.scrollport').scrollTop = 9999999;
}); });
$removeCancelButton.click(function() { $removeCancelButton.click(function() {
$(popoverElt.querySelector('.new-comment-block')).removeClass('hide'); $(popoverElt.querySelector('.new-comment-block')).removeClass('hide');
$(popoverElt.querySelector('.remove-discussion-confirm')).addClass('hide'); $(popoverElt.querySelector('.remove-discussion-confirm')).addClass('hide');
popoverElt.querySelector('.popover-content').scrollTop = 9999999; popoverElt.querySelector('.scrollport').scrollTop = 9999999;
context.$contentInputElt.focus(); context.$contentInputElt.focus();
}); });
$removeConfirmButton.click(function() { $removeConfirmButton.click(function() {
@ -421,6 +443,7 @@ define([
return; return;
} }
currentContext.$commentElt.removeClass('active'); currentContext.$commentElt.removeClass('active');
$newCommentElt.removeClass('hide');
// Save content and author for later // Save content and author for later
previousContent = currentContext.$contentInputElt.val(); previousContent = currentContext.$contentInputElt.val();
@ -431,6 +454,29 @@ define([
currentContext = undefined; currentContext = undefined;
delete currentFileDesc.newDiscussion; delete currentFileDesc.newDiscussion;
}); });
var $newCommentElt = $(newCommentElt);
$openDiscussionElt = $('.button-open-discussion').click(function(evt) {
var $commentElt = $newCommentElt;
if(sortedCommentEltList.length) {
if(!currentContext) {
$commentElt = $(_.first(sortedCommentEltList));
}
else {
var curentIndex = -1;
sortedCommentEltList.some(function(elt, index) {
if(elt === currentContext.commentElt) {
curentIndex = index;
return true;
}
});
$commentElt = $(sortedCommentEltList[(curentIndex + 1) % sortedCommentEltList.length]);
}
}
$commentElt.click();
evt.stopPropagation();
});
$openDiscussionIconElt = $openDiscussionElt.find('i');
}; };
return comments; return comments;

View File

@ -19,7 +19,9 @@
<li class="wmd-button-group2 btn-group"></li> <li class="wmd-button-group2 btn-group"></li>
</ul> </ul>
<ul class="nav left-buttons"> <ul class="nav left-buttons">
<li class="wmd-button-group3 btn-group"></li> <li class="wmd-button-group3 btn-group">
<a class="btn btn-success button-open-discussion"><i class="icon-comment-alt"></i></a>
</li>
</ul> </ul>
<ul class="nav left-buttons"> <ul class="nav left-buttons">
<li class="wmd-button-group4 btn-group"></li> <li class="wmd-button-group4 btn-group"></li>

View File

@ -1,18 +1,20 @@
<div class="discussion-comment-list"><%= commentList %></div> <div class="scrollport">
<div class="new-comment-block"> <div class="discussion-comment-list"><%= commentList %></div>
<div class="form-group"> <div class="new-comment-block">
<i class="icon-comment"></i> <input class="form-control input-comment-author" placeholder="Your name"></input> <div class="form-group">
<textarea class="form-control input-comment-content"></textarea> <i class="icon-comment"></i> <input class="form-control input-comment-author" placeholder="Your name"></input>
<textarea class="form-control input-comment-content"></textarea>
</div>
<div class="form-group text-right">
<button class="btn btn-primary action-add-comment">Add</button>
</div>
</div> </div>
<div class="form-group text-right"> <div class="remove-discussion-confirm hide">
<button class="btn btn-primary action-add-comment">Add</button> <blockquote>Remove this discussion, really?</blockquote>
</div> <div class="form-group text-right">
</div> <button class="btn btn-default action-remove-discussion-cancel">No</button>
<div class="remove-discussion-confirm hide"> <button class="btn btn-primary action-remove-discussion-confirm">Yes</button>
<br/> </div>
<blockquote>Remove this discussion, really?</blockquote>
<div class="form-group text-right">
<button class="btn btn-default action-remove-discussion-cancel">No</button>
<button class="btn btn-primary action-remove-discussion-confirm">Yes</button>
</div> </div>
</div> </div>
<hr/>

View File

@ -19,7 +19,7 @@
@primary-border-color: fade(@primary, 10%); @primary-border-color: fade(@primary, 10%);
// Preview and menus // Preview and menus
@secondary-bg: lighten(@secondary-desaturated, 45%); @secondary-bg: lighten(@secondary-desaturated, 48%);
@secondary-bg-light: lighten(@secondary-desaturated, 47%); @secondary-bg-light: lighten(@secondary-desaturated, 47%);
@secondary-bg-lighter: #fff; @secondary-bg-lighter: #fff;
@secondary-color: lighten(@primary-desaturated, 10%); @secondary-color: lighten(@primary-desaturated, 10%);
@ -111,7 +111,7 @@
@btn-info-color: fade(@secondary-desaturated, 35%); @btn-info-color: fade(@secondary-desaturated, 35%);
@btn-info-bg: @transparent; @btn-info-bg: @transparent;
@btn-info-border: @transparent; @btn-info-border: @transparent;
@btn-info-hover-bg: @secondary-bg; @btn-info-hover-bg: lighten(@secondary-desaturated, 45%);
@gray-lighter: @body-bg; @gray-lighter: @body-bg;
@modal-header-border-color: @secondary-border-color-light; @modal-header-border-color: @secondary-border-color-light;
@modal-content-bg: @secondary-bg-lighter; @modal-content-bg: @secondary-bg-lighter;
@ -127,8 +127,14 @@
@popover-arrow-outer-color: @secondary-border-color; @popover-arrow-outer-color: @secondary-border-color;
@popover-title-bg: @transparent; @popover-title-bg: @transparent;
@alert-border-radius: 0; @alert-border-radius: 0;
@label-warning-bg: spin(darken(@logo-yellow, 4%), -4); @label-warning-bg: spin(darken(@logo-yellow, 4%), -6);
@label-danger-bg: spin(darken(@logo-orange, 4%), -4); @label-danger-bg: spin(darken(@logo-orange, 4%), -4);
@state-warning-text: spin(darken(@logo-yellow, 18%), -6);
@state-warning-bg: fade(spin(@logo-yellow, -6), 12%);
@state-warning-border: fade(spin(@logo-yellow, -6), 24%);
@state-danger-text: spin(darken(@logo-orange, 20%), -4);
@state-danger-bg: fade(spin(@logo-orange, -4), 10%);
@state-danger-border: fade(spin(@logo-orange, -4), 20%);
body { body {
@ -443,6 +449,25 @@ a {
} }
} }
.button-open-discussion {
&.some {
color: lighten(@alert-warning-text, 10%);
&:hover,
&:focus {
.alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);
background-color: @alert-warning-bg !important;
}
}
&.replied {
color: lighten(@alert-danger-text, 10%);
&:hover,
&:focus {
.alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);
background-color: @alert-danger-bg !important;
}
}
}
.file-title-navbar { .file-title-navbar {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
@ -1058,12 +1083,13 @@ a {
position: absolute; position: absolute;
top: 0; top: 0;
.discussion { .discussion {
font-size: 18px; font-size: 17px;
&:before { &:before {
margin-right: 0; margin-right: 0;
} }
&.new { &.new {
color: fade(@tertiary-color, 10%); display: none;
color: fade(@tertiary-color, 20%);
&:hover, &.active, &.active:hover { &:hover, &.active, &.active:hover {
color: fade(@tertiary-color, 35%) !important; color: fade(@tertiary-color, 35%) !important;
} }
@ -1091,12 +1117,11 @@ a {
} }
} }
&.has-selection > .editor-margin .discussion.new { &.has-selection > .editor-margin .discussion.new {
color: fade(@tertiary-color, 25%); display: inline-block;
} }
.comment-highlight { .comment-highlight {
background-color: fade(@label-warning-bg, 30%); background-color: fade(@label-warning-bg, 30%);
//border-radius: @border-radius-base;
} }
.conflict { .conflict {
@ -1150,13 +1175,6 @@ a {
padding: 0.2em; padding: 0.2em;
} }
/*
.pre {
line-height: 1.8;
margin-left: 1.8em
}
*/
.link, .linkref { .link, .linkref {
.md-underlined-text { .md-underlined-text {
color: inherit; color: inherit;
@ -1438,16 +1456,23 @@ input[type="file"] {
display: none; display: none;
} }
.popover-content { .popover-content {
padding: 10px 20px 0; margin: 0;
overflow: auto; padding: 0;
max-height: 230px; .scrollport {
margin: 0 -20px; overflow: auto;
max-height: 230px;
padding: 10px 20px 0;
margin: 0 -20px;
}
.btn { .btn {
padding: 6px 11px; padding: 6px 11px;
} }
.comment-block { .comment-block {
margin-bottom: 5px; margin-bottom: 5px;
} }
.form-group, blockquote {
margin-bottom: 10px;
}
.comment-author { .comment-author {
font-weight: bold; font-weight: bold;
color: @input-color; color: @input-color;
@ -1466,9 +1491,12 @@ input[type="file"] {
font-weight: bold; font-weight: bold;
height: 28px; height: 28px;
padding: 0 0 5px; padding: 0 0 5px;
width: 150px; width: 140px;
display: inline-block; display: inline-block;
} }
hr {
margin: 0 0 10px;
}
} }
.comments-popover & { .comments-popover & {