Fixed className leak in undo/redo buttons

This commit is contained in:
benweet 2014-05-23 23:58:18 +01:00
parent 349d43bd0d
commit 492f5ffbc0
3 changed files with 461 additions and 447 deletions

View File

@ -27,6 +27,7 @@ define([
var pagedownEditor; var pagedownEditor;
var refreshPreviewLater = (function() { var refreshPreviewLater = (function() {
var elapsedTime = 0; var elapsedTime = 0;
var timeoutId;
var refreshPreview = function() { var refreshPreview = function() {
var startTime = Date.now(); var startTime = Date.now();
pagedownEditor.refreshPreview(); pagedownEditor.refreshPreview();
@ -36,7 +37,8 @@ define([
return _.debounce(refreshPreview, 500); return _.debounce(refreshPreview, 500);
} }
return function() { return function() {
setTimeout(refreshPreview, elapsedTime < 2000 ? elapsedTime : 2000); clearTimeout(timeoutId);
timeoutId = setTimeout(refreshPreview, elapsedTime < 2000 ? elapsedTime : 2000);
}; };
})(); })();
eventMgr.addListener('onPagedownConfigure', function(pagedownEditorParam) { eventMgr.addListener('onPagedownConfigure', function(pagedownEditorParam) {
@ -174,9 +176,10 @@ define([
var min = Math.min(this.selectionStart, this.selectionEnd); var min = Math.min(this.selectionStart, this.selectionEnd);
var max = Math.max(this.selectionStart, this.selectionEnd); var max = Math.max(this.selectionStart, this.selectionEnd);
var range = this.createRange(min, max); var range = this.createRange(min, max);
var selection = document.getSelection(); var selection = rangy.getSelection();
selection.removeAllRanges(); selection.removeAllRanges();
selection.addRange(range); selection.addRange(range, this.selectionStart > this.selectionEnd);
selection.detach();
range.detach(); range.detach();
}; };
this.setSelectionStartEnd = function(start, end) { this.setSelectionStartEnd = function(start, end) {
@ -203,7 +206,7 @@ define([
var selectionStart = self.selectionStart; var selectionStart = self.selectionStart;
var selectionEnd = self.selectionEnd; var selectionEnd = self.selectionEnd;
var range; var range;
var selection = document.getSelection(); var selection = rangy.getSelection();
if(selection.rangeCount > 0) { if(selection.rangeCount > 0) {
var selectionRange = selection.getRangeAt(0); var selectionRange = selection.getRangeAt(0);
var element = selectionRange.startContainer; var element = selectionRange.startContainer;
@ -220,7 +223,7 @@ define([
element = container = container.parentNode; element = container = container.parentNode;
} while(element && element != inputElt); } while(element && element != inputElt);
if(false) { if(selection.isBackwards()) {
selectionStart = offset + (range + '').length; selectionStart = offset + (range + '').length;
selectionEnd = offset; selectionEnd = offset;
} }
@ -231,6 +234,7 @@ define([
} }
selectionRange.detach(); selectionRange.detach();
} }
selection.detach();
self.setSelectionStartEnd(selectionStart, selectionEnd); self.setSelectionStartEnd(selectionStart, selectionEnd);
} }
undoMgr.saveSelectionState(); undoMgr.saveSelectionState();

View File

@ -1,479 +1,489 @@
define([ define([
"jquery", "jquery",
"underscore", "underscore",
"utils", "utils",
"storage", "storage",
"crel", "crel",
"rangy", "rangy",
"classes/Extension", "classes/Extension",
"text!html/commentsPopoverContent.html", "text!html/commentsPopoverContent.html",
"bootstrap" "bootstrap"
], function($, _, utils, storage, crel, rangy, Extension, commentsPopoverContentHTML) { ], function($, _, utils, storage, crel, rangy, Extension, commentsPopoverContentHTML) {
var comments = new Extension("comments", 'Comments', false, true); var comments = new Extension("comments", 'Comments', false, true);
var commentTmpl = [ var commentTmpl = [
'<div class="comment-block<%= reply ? \' reply\' : \'\' %>">', '<div class="comment-block<%= reply ? \' reply\' : \'\' %>">',
' <div class="comment-author"><i class="icon-comment"></i> <%= 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('');
var popoverTitleTmpl = [ var popoverTitleTmpl = [
'<span class="clearfix">', '<span class="clearfix">',
' <a href="#" class="action-remove-discussion pull-right">', ' <a href="#" class="action-remove-discussion pull-right">',
' <i class="icon-trash"></i>', ' <i class="icon-trash"></i>',
' </a>', ' </a>',
' “<%- title %>”', ' “<%- title %>”',
'</span>' '</span>'
].join(''); ].join('');
var eventMgr; var eventMgr;
comments.onEventMgrCreated = function(eventMgrParam) { comments.onEventMgrCreated = function(eventMgrParam) {
eventMgr = eventMgrParam; eventMgr = eventMgrParam;
}; };
var editor; var editor;
var selectionMgr; var selectionMgr;
comments.onEditorCreated = function(editorParam) { comments.onEditorCreated = function(editorParam) {
editor = editorParam; editor = editorParam;
selectionMgr = editor.selectionMgr; selectionMgr = editor.selectionMgr;
}; };
var yList = []; var yList = [];
function setCommentEltCoordinates(commentElt, y, isNew) {
y = Math.round(y);
var yListIndex = y - 21;
// Avoid overlap of comment icons
while(yListIndex < y + 22) {
if(yList[yListIndex]) {
y = yListIndex + 22;
}
yListIndex++;
}
!isNew && (yList[y] = 1);
var yOffset = -8;
if(commentElt.className.indexOf(' icon-split') !== -1) {
yOffset = -12;
}
var top = y + yOffset;
commentElt.style.top = top + 'px';
commentElt.style.right = '12px';
}
var inputElt; function setCommentEltCoordinates(commentElt, y, isNew) {
var marginElt; y = Math.round(y);
var newCommentElt = crel('a', { var yListIndex = y - 21;
class: 'discussion icon-comment new' // Avoid overlap of comment icons
}); while(yListIndex < y + 22) {
if(yList[yListIndex]) {
y = yListIndex + 22;
}
yListIndex++;
}
!isNew && (yList[y] = 1);
var yOffset = -8;
if(commentElt.className.indexOf(' icon-split') !== -1) {
yOffset = -12;
}
var top = y + yOffset;
commentElt.style.top = top + 'px';
commentElt.style.right = '12px';
}
function Context(commentElt, fileDesc) { var inputElt;
this.commentElt = commentElt; var marginElt;
this.$commentElt = $(commentElt).addClass('active'); var newCommentElt = crel('a', {
this.fileDesc = fileDesc; class: 'discussion icon-comment new'
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;
function movePopover(commentElt) {
// Move popover in the margin
var popoverElt = currentContext.getPopoverElt();
var left = 0;
if(popoverElt.offsetWidth < marginElt.offsetWidth - 10) {
left = marginElt.offsetWidth - 10 - popoverElt.offsetWidth;
}
popoverElt.style.left = left + 'px';
popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(commentElt.style.right) - commentElt.offsetWidth / 2 - left) + 'px';
var popoverTopOffset = window.innerHeight - currentContext.hr.getBoundingClientRect().top;
if(popoverTopOffset < 0) {
popoverElt.style.top = (parseInt(popoverElt.style.top) + popoverTopOffset) + 'px';
}
}
var cssApplier; function Context(commentElt, fileDesc) {
var currentFileDesc; this.commentElt = commentElt;
var refreshTimeoutId; this.$commentElt = $(commentElt).addClass('active');
var commentEltMap = {}; this.fileDesc = fileDesc;
var sortedCommentEltList = []; this.discussionIndex = commentElt.discussionIndex;
var someReplies = false; }
var $openDiscussionElt;
var $openDiscussionIconElt;
var refreshDiscussions = _.debounce(function() {
if(currentFileDesc === undefined) {
return;
}
someReplies = false;
sortedCommentEltList = [];
var author = storage['author.name'];
yList = [];
var discussionList = _.sortBy(currentFileDesc.discussionList, function(discussion) {
return discussion.selectionEnd;
});
function refreshOne() {
var coordinates;
if(discussionList.length === 0) {
// Remove outdated commentElt
_.filter(commentEltMap, function(commentElt, discussionIndex) {
return !_.has(currentFileDesc.discussionList, discussionIndex);
}).forEach(function(commentElt) {
marginElt.removeChild(commentElt);
delete commentEltMap[commentElt.discussionIndex];
});
// Move newCommentElt
if(currentContext && !currentContext.discussionIndex) {
coordinates = selectionMgr.getCoordinates(currentContext.getDiscussion().selectionEnd);
setCommentEltCoordinates(newCommentElt, coordinates.y, true);
inputElt.scrollTop += parseInt(newCommentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
movePopover(newCommentElt);
}
sortedCommentEltList = _.sortBy(commentEltMap, function(commentElt) {
return commentElt.selectionEnd;
});
$openDiscussionElt.toggleClass('some', sortedCommentEltList.length !== 0);
$openDiscussionElt.toggleClass('replied', someReplies);
$openDiscussionIconElt.toggleClass('icon-chat', sortedCommentEltList.length !== 0);
return;
}
var discussion = discussionList.shift();
var commentElt = commentEltMap[discussion.discussionIndex];
if(!commentElt) {
commentElt = crel('a');
}
var className = 'discussion';
var isReplied = !discussion.commentList || !author || _.last(discussion.commentList).author != author;
isReplied && (someReplies = true);
if(discussion.type == 'conflict') {
className += ' icon-split';
}
else {
className += ' icon-comment';
}
className += isReplied ? ' replied' : ' added';
commentElt.className = className;
commentElt.discussionIndex = discussion.discussionIndex;
commentElt.selectionEnd = discussion.selectionEnd;
coordinates = selectionMgr.getCoordinates(discussion.selectionEnd);
setCommentEltCoordinates(commentElt, coordinates.y);
marginElt.appendChild(commentElt); Context.prototype.getDiscussion = function() {
commentEltMap[discussion.discussionIndex] = commentElt; 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;
if(currentContext && currentContext.getDiscussion() === discussion) { function movePopover(commentElt) {
inputElt.scrollTop += parseInt(commentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4; // Move popover in the margin
movePopover(commentElt); var popoverElt = currentContext.getPopoverElt();
} var left = 0;
refreshTimeoutId = setTimeout(refreshOne, 5); if(popoverElt.offsetWidth < marginElt.offsetWidth - 10) {
} left = marginElt.offsetWidth - 10 - popoverElt.offsetWidth;
clearTimeout(refreshTimeoutId); }
refreshTimeoutId = setTimeout(refreshOne, 5); popoverElt.style.left = left + 'px';
}, 50); popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(commentElt.style.right) - commentElt.offsetWidth / 2 - left) + 'px';
comments.onLayoutResize = refreshDiscussions; var popoverTopOffset = window.innerHeight - currentContext.hr.getBoundingClientRect().top;
if(popoverTopOffset < 0) {
popoverElt.style.top = (parseInt(popoverElt.style.top) + popoverTopOffset) + 'px';
}
}
comments.onFileOpen = function(fileDesc) { var cssApplier;
currentFileDesc = fileDesc; var currentFileDesc;
refreshDiscussions(); var refreshTimeoutId;
}; var commentEltMap = {};
var sortedCommentEltList = [];
var someReplies = false;
var $openDiscussionElt;
var $openDiscussionIconElt;
var refreshDiscussions = _.debounce(function() {
if(currentFileDesc === undefined) {
return;
}
someReplies = false;
sortedCommentEltList = [];
var author = storage['author.name'];
yList = [];
var discussionList = _.sortBy(currentFileDesc.discussionList, function(discussion) {
return discussion.selectionEnd;
});
comments.onContentChanged = function(fileDesc) { function refreshOne() {
currentFileDesc === fileDesc && refreshDiscussions(); var coordinates;
}; if(discussionList.length === 0) {
// Remove outdated commentElt
_.filter(commentEltMap, function(commentElt, discussionIndex) {
return !_.has(currentFileDesc.discussionList, discussionIndex);
}).forEach(function(commentElt) {
marginElt.removeChild(commentElt);
delete commentEltMap[commentElt.discussionIndex];
});
// Move newCommentElt
if(currentContext && !currentContext.discussionIndex) {
coordinates = selectionMgr.getCoordinates(currentContext.getDiscussion().selectionEnd);
setCommentEltCoordinates(newCommentElt, coordinates.y, true);
inputElt.scrollTop += parseInt(newCommentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
movePopover(newCommentElt);
}
sortedCommentEltList = _.sortBy(commentEltMap, function(commentElt) {
return commentElt.selectionEnd;
});
$openDiscussionElt.toggleClass('some', sortedCommentEltList.length !== 0);
$openDiscussionElt.toggleClass('replied', someReplies);
$openDiscussionIconElt.toggleClass('icon-chat', sortedCommentEltList.length !== 0);
return;
}
var discussion = discussionList.shift();
var commentElt = commentEltMap[discussion.discussionIndex];
if(!commentElt) {
commentElt = crel('a');
}
var className = 'discussion';
var isReplied = !discussion.commentList || !author || _.last(discussion.commentList).author != author;
isReplied && (someReplies = true);
if(discussion.type == 'conflict') {
className += ' icon-split';
}
else {
className += ' icon-comment';
}
className += isReplied ? ' replied' : ' added';
commentElt.className = className;
commentElt.discussionIndex = discussion.discussionIndex;
commentElt.selectionEnd = discussion.selectionEnd;
coordinates = selectionMgr.getCoordinates(discussion.selectionEnd);
setCommentEltCoordinates(commentElt, coordinates.y);
comments.onCommentsChanged = function(fileDesc) { marginElt.appendChild(commentElt);
if(currentFileDesc !== fileDesc) { commentEltMap[discussion.discussionIndex] = commentElt;
return;
}
if(currentContext !== undefined) {
// Refresh conversation if popover is open
var context = currentContext;
if(context.discussionIndex) {
context.getPopoverElt().querySelector('.discussion-comment-list').innerHTML = getDiscussionComments();
}
try {
cssApplier.undoToRange(context.rangyRange);
context.rangyRange.detach();
}
catch(e) {}
var discussion = context.getDiscussion();
context.selectionRange = selectionMgr.createRange(discussion.selectionStart, discussion.selectionEnd);
// Highlight selected text if(currentContext && currentContext.getDiscussion() === discussion) {
context.rangyRange = rangy.createRange(); inputElt.scrollTop += parseInt(commentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset); movePopover(commentElt);
context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset); }
setTimeout(function() { // Need to delay this because it's not refreshed properly refreshTimeoutId = setTimeout(refreshOne, 5);
if(currentContext === context) { }
cssApplier.applyToRange(context.rangyRange);
}
}, 50);
}
refreshDiscussions();
};
function closeCurrentPopover() { clearTimeout(refreshTimeoutId);
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy'); refreshTimeoutId = setTimeout(refreshOne, 5);
} }, 50);
comments.onLayoutResize = refreshDiscussions;
comments.onDiscussionCreated = function(fileDesc) { comments.onFileOpen = function(fileDesc) {
currentFileDesc === fileDesc && refreshDiscussions(); currentFileDesc = fileDesc;
}; refreshDiscussions();
};
comments.onDiscussionRemoved = function(fileDesc, discussion) { comments.onContentChanged = function(fileDesc) {
if(currentFileDesc === fileDesc) { currentFileDesc === fileDesc && refreshDiscussions();
// Close popover if the discussion has been removed };
if(currentContext !== undefined && currentContext.discussionIndex == discussion.discussionIndex) {
closeCurrentPopover();
}
refreshDiscussions();
}
};
function getDiscussionComments() { comments.onCommentsChanged = function(fileDesc) {
var discussion = currentContext.getDiscussion(); if(currentFileDesc !== fileDesc) {
var author = storage['author.name']; return;
var result = []; }
if(discussion.commentList) { if(currentContext !== undefined) {
result = discussion.commentList.map(function(comment) { // Refresh conversation if popover is open
var commentAuthor = comment.author || 'Anonymous'; var context = currentContext;
return _.template(commentTmpl, { if(context.discussionIndex) {
author: commentAuthor, context.getPopoverElt().querySelector('.discussion-comment-list').innerHTML = getDiscussionComments();
content: comment.content, }
reply: comment.author != author try {
}); cssApplier.undoToRange(context.rangyRange);
}); context.rangyRange.detach();
} }
if(discussion.type == 'conflict') { catch(e) {
result.unshift(_.template(commentTmpl, { }
author: 'StackEdit', var discussion = context.getDiscussion();
content: 'Multiple users have made conflicting modifications.', context.selectionRange = selectionMgr.createRange(discussion.selectionStart, discussion.selectionEnd);
reply: true
}));
}
return result.join('');
}
comments.onReady = function() { // Highlight selected text
cssApplier = rangy.createCssClassApplier("comment-highlight", { context.rangyRange = rangy.createRange();
normalize: false context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
}); context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset);
var previousContent = ''; setTimeout(function() { // Need to delay this because it's not refreshed properly
if(currentContext === context) {
cssApplier.applyToRange(context.rangyRange);
}
}, 50);
}
refreshDiscussions();
};
inputElt = document.getElementById('wmd-input'); function closeCurrentPopover() {
marginElt = document.querySelector('#wmd-input > .editor-margin'); currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
marginElt.appendChild(newCommentElt); }
var $popoverContainer = $(crel('div', {
class: 'comments-popover'
}));
$(document.body).append($popoverContainer).on('click', function(evt) {
// Close on click outside the popover
if(currentContext && currentContext.$commentElt[0] !== evt.target) {
closeCurrentPopover();
}
}).popover({
placement: 'auto top',
container: '.comments-popover',
html: true,
title: function() {
if(!currentContext) {
return true;
}
var discussion = currentContext.getDiscussion();
var titleLength = discussion.selectionEnd - discussion.selectionStart;
var title = editor.getValue().substr(discussion.selectionStart, titleLength > 20 ? 20 : titleLength);
if(titleLength > 20) {
title += '...';
}
return _.template(popoverTitleTmpl, {
title: title
});
},
content: function() {
var content = _.template(commentsPopoverContentHTML, {
commentList: getDiscussionComments()
});
return content;
},
selector: '#wmd-input > .editor-margin > .discussion'
});
$(marginElt).on('show.bs.popover', function(evt) {
closeCurrentPopover();
var context = new Context(evt.target, currentFileDesc);
currentContext = context;
// If it's not an existing discussion comments.onDiscussionCreated = function(fileDesc) {
var discussion = context.getDiscussion(); currentFileDesc === fileDesc && refreshDiscussions();
if(!discussion) { };
// Get selected text
var selectionStart = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
var selectionEnd = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
if(selectionStart === selectionEnd) {
var offset = selectionMgr.getClosestWordOffset(selectionStart);
selectionStart = offset.start;
selectionEnd = offset.end;
}
discussion = {
selectionStart: selectionStart,
selectionEnd: selectionEnd,
commentList: []
};
currentFileDesc.newDiscussion = discussion;
var coordinates = selectionMgr.getCoordinates(selectionStart);
setCommentEltCoordinates(newCommentElt, coordinates.y, true);
}
context.selectionRange = selectionMgr.createRange(discussion.selectionStart, discussion.selectionEnd);
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
}).on('shown.bs.popover', function(evt) { comments.onDiscussionRemoved = function(fileDesc, discussion) {
var context = currentContext; if(currentFileDesc === fileDesc) {
var popoverElt = context.getPopoverElt(); // Close popover if the discussion has been removed
context.$authorInputElt = $(popoverElt.querySelector('.input-comment-author')).val(storage['author.name']); if(currentContext !== undefined && currentContext.discussionIndex == discussion.discussionIndex) {
context.$contentInputElt = $(popoverElt.querySelector('.input-comment-content')); closeCurrentPopover();
context.hr = popoverElt.querySelector('hr'); }
movePopover(context.commentElt); refreshDiscussions();
}
};
// Scroll to the bottom of the discussion function getDiscussionComments() {
popoverElt.querySelector('.scrollport').scrollTop = 9999999; var discussion = currentContext.getDiscussion();
var author = storage['author.name'];
var result = [];
if(discussion.commentList) {
result = discussion.commentList.map(function(comment) {
var commentAuthor = comment.author || 'Anonymous';
return _.template(commentTmpl, {
author: commentAuthor,
content: comment.content,
reply: comment.author != author
});
});
}
if(discussion.type == 'conflict') {
result.unshift(_.template(commentTmpl, {
author: 'StackEdit',
content: 'Multiple users have made conflicting modifications.',
reply: true
}));
}
return result.join('');
}
var $addButton = $(popoverElt.querySelector('.action-add-comment')); comments.onReady = function() {
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) { cssApplier = rangy.createCssClassApplier("comment-highlight", {
// Enter key normalize: false
switch(evt.which) { });
case 13: var previousContent = '';
evt.preventDefault();
$addButton.click();
return;
case 27:
evt.preventDefault();
closeCurrentPopover();
editor.focus();
editor.adjustCursorPosition();
return;
}
});
$addButton.click(function(evt) {
var author = utils.getInputTextValue(context.$authorInputElt);
var content = utils.getInputTextValue(context.$contentInputElt, evt);
if(evt.isPropagationStopped()) {
return;
}
var discussion = context.getDiscussion(); inputElt = document.getElementById('wmd-input');
context.$contentInputElt.val(''); marginElt = document.querySelector('#wmd-input > .editor-margin');
closeCurrentPopover(); marginElt.appendChild(newCommentElt);
var $popoverContainer = $(crel('div', {
class: 'comments-popover'
}));
$(document.body).append($popoverContainer).on('click', function(evt) {
// Close on click outside the popover
if(currentContext && currentContext.$commentElt[0] !== evt.target) {
closeCurrentPopover();
}
}).popover({
placement: 'auto top',
container: '.comments-popover',
html: true,
title: function() {
if(!currentContext) {
return true;
}
var discussion = currentContext.getDiscussion();
var titleLength = discussion.selectionEnd - discussion.selectionStart;
var title = editor.getValue().substr(discussion.selectionStart, titleLength > 20 ? 20 : titleLength);
if(titleLength > 20) {
title += '...';
}
return _.template(popoverTitleTmpl, {
title: title
});
},
content: function() {
var content = _.template(commentsPopoverContentHTML, {
commentList: getDiscussionComments()
});
return content;
},
selector: '#wmd-input > .editor-margin > .discussion'
});
$(marginElt).on('show.bs.popover', function(evt) {
closeCurrentPopover();
var context = new Context(evt.target, currentFileDesc);
currentContext = context;
discussion.commentList = discussion.commentList || []; // If it's not an existing discussion
discussion.commentList.push({ var discussion = context.getDiscussion();
author: author, if(!discussion) {
content: content // Get selected text
}); var selectionStart = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
var discussionList = context.fileDesc.discussionList || {}; var selectionEnd = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
if(!discussion.discussionIndex) { if(selectionStart === selectionEnd) {
// Create discussion index var offset = selectionMgr.getClosestWordOffset(selectionStart);
var discussionIndex; selectionStart = offset.start;
do { selectionEnd = offset.end;
discussionIndex = utils.randomString(); }
} while(_.has(discussionList, discussionIndex)); discussion = {
discussion.discussionIndex = discussionIndex; selectionStart: selectionStart,
discussionList[discussionIndex] = discussion; selectionEnd: selectionEnd,
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage commentList: []
eventMgr.onDiscussionCreated(context.fileDesc, discussion); };
} currentFileDesc.newDiscussion = discussion;
else { var coordinates = selectionMgr.getCoordinates(selectionStart);
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage setCommentEltCoordinates(newCommentElt, coordinates.y, true);
eventMgr.onCommentsChanged(context.fileDesc); }
} context.selectionRange = selectionMgr.createRange(discussion.selectionStart, discussion.selectionEnd);
inputElt.focus(); inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
});
var $removeButton = $(popoverElt.querySelector('.action-remove-discussion')); }).on('shown.bs.popover', function(evt) {
if(evt.target.discussionIndex) { var context = currentContext;
// If it's an existing discussion var popoverElt = context.getPopoverElt();
$removeButton.click(function() { context.$authorInputElt = $(popoverElt.querySelector('.input-comment-author')).val(storage['author.name']);
closeCurrentPopover(); context.$contentInputElt = $(popoverElt.querySelector('.input-comment-content'));
var discussion = context.getDiscussion(); context.hr = popoverElt.querySelector('hr');
delete context.fileDesc.discussionList[discussion.discussionIndex]; movePopover(context.commentElt);
context.fileDesc.discussionList = context.fileDesc.discussionList; // Write discussionList in localStorage
eventMgr.onDiscussionRemoved(context.fileDesc, discussion);
inputElt.focus();
});
}
else {
// Otherwise hide the remove button
$removeButton.hide();
}
// Highlight selected text // Scroll to the bottom of the discussion
context.rangyRange = rangy.createRange(); popoverElt.querySelector('.scrollport').scrollTop = 9999999;
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset);
setTimeout(function() { // Need to delay this because it's not refreshed properly
if(currentContext === context) {
cssApplier.applyToRange(context.rangyRange);
}
}, 50);
// Focus on textarea var $addButton = $(popoverElt.querySelector('.action-add-comment'));
context.$contentInputElt.focus().val(previousContent); $().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
}).on('hide.bs.popover', function() { // Enter key
if(!currentContext) { switch(evt.which) {
return; case 13:
} evt.preventDefault();
currentContext.$commentElt.removeClass('active'); $addButton.click();
return;
case 27:
evt.preventDefault();
closeCurrentPopover();
editor.focus();
editor.adjustCursorPosition();
return;
}
});
$addButton.click(function(evt) {
var author = utils.getInputTextValue(context.$authorInputElt);
var content = utils.getInputTextValue(context.$contentInputElt, evt);
if(evt.isPropagationStopped()) {
return;
}
// Save content and author for later var discussion = context.getDiscussion();
previousContent = currentContext.$contentInputElt.val(); context.$contentInputElt.val('');
storage['author.name'] = currentContext.$authorInputElt.val(); closeCurrentPopover();
// Remove highlight discussion.commentList = discussion.commentList || [];
cssApplier.undoToRange(currentContext.rangyRange); discussion.commentList.push({
currentContext.rangyRange.detach(); author: author,
currentContext = undefined; content: content
delete currentFileDesc.newDiscussion; });
}); var discussionList = context.fileDesc.discussionList || {};
if(!discussion.discussionIndex) {
// Create discussion index
var discussionIndex;
do {
discussionIndex = utils.randomString();
} while(_.has(discussionList, discussionIndex));
discussion.discussionIndex = discussionIndex;
discussionList[discussionIndex] = discussion;
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage
eventMgr.onDiscussionCreated(context.fileDesc, discussion);
}
else {
context.fileDesc.discussionList = discussionList; // Write discussionList in localStorage
eventMgr.onCommentsChanged(context.fileDesc);
}
inputElt.focus();
});
// Prevent from closing on click inside the popover var $removeButton = $(popoverElt.querySelector('.action-remove-discussion'));
$popoverContainer.on('click', '.popover', function(evt) { if(evt.target.discussionIndex) {
evt.stopPropagation(); // If it's an existing discussion
}); $removeButton.click(function() {
closeCurrentPopover();
var discussion = context.getDiscussion();
delete context.fileDesc.discussionList[discussion.discussionIndex];
context.fileDesc.discussionList = context.fileDesc.discussionList; // Write discussionList in localStorage
eventMgr.onDiscussionRemoved(context.fileDesc, discussion);
inputElt.focus();
});
}
else {
// Otherwise hide the remove button
$removeButton.hide();
}
// Highlight selected text
context.rangyRange = rangy.createRange();
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset);
setTimeout(function() { // Need to delay this because it's not refreshed properly
if(currentContext === context) {
cssApplier.applyToRange(context.rangyRange);
}
}, 50);
// Focus on textarea
context.$contentInputElt.focus().val(previousContent);
}).on('hide.bs.popover', function() {
if(!currentContext) {
return;
}
currentContext.$commentElt.removeClass('active');
// Save content and author for later
previousContent = currentContext.$contentInputElt.val();
storage['author.name'] = currentContext.$authorInputElt.val();
// Remove highlight
try {
cssApplier.undoToRange(currentContext.rangyRange);
currentContext.rangyRange.detach();
}
catch(e) {
}
currentContext = undefined;
delete currentFileDesc.newDiscussion;
});
// Prevent from closing on click inside the popover
$popoverContainer.on('click', '.popover', function(evt) {
evt.stopPropagation();
});
var $newCommentElt = $(newCommentElt); var $newCommentElt = $(newCommentElt);
$openDiscussionElt = $('.button-open-discussion').click(function(evt) { $openDiscussionElt = $('.button-open-discussion').click(function(evt) {
var $commentElt = $newCommentElt; var $commentElt = $newCommentElt;
if(currentContext) { if(currentContext) {
if(!currentContext.discussionIndex) { if(!currentContext.discussionIndex) {
$commentElt = $(_.first(sortedCommentEltList)); $commentElt = $(_.first(sortedCommentEltList));
} }
else { else {
var curentIndex = -1; var curentIndex = -1;
sortedCommentEltList.some(function(elt, index) { sortedCommentEltList.some(function(elt, index) {
if(elt === currentContext.commentElt) { if(elt === currentContext.commentElt) {
curentIndex = index; curentIndex = index;
return true; return true;
} }
}); });
$commentElt = $(sortedCommentEltList[(curentIndex + 1)]); $commentElt = $(sortedCommentEltList[(curentIndex + 1)]);
} }
} }
if($commentElt.length === 0) { if($commentElt.length === 0) {
// Close the popover properly // Close the popover properly
closeCurrentPopover(); closeCurrentPopover();
editor.focus(); editor.focus();
editor.adjustCursorPosition(); editor.adjustCursorPosition();
} }
else { else {
$commentElt.click(); $commentElt.click();
} }
evt.stopPropagation(); evt.stopPropagation();
}); });
$openDiscussionIconElt = $openDiscussionElt.find('i'); $openDiscussionIconElt = $openDiscussionElt.find('i');
}; };
return comments; return comments;
}); });

View File

@ -1425,6 +1425,7 @@
var disabledYShift = "-20px"; var disabledYShift = "-20px";
var highlightYShift = "-40px"; var highlightYShift = "-40px";
var image = button.getElementsByTagName("span")[0]; var image = button.getElementsByTagName("span")[0];
button.className = button.className.replace(/ disabled/g, "");
if (isEnabled) { if (isEnabled) {
image.style.backgroundPosition = button.XShift + " " + normalYShift; image.style.backgroundPosition = button.XShift + " " + normalYShift;
button.onmouseover = function () { button.onmouseover = function () {
@ -1454,7 +1455,6 @@
return false; return false;
} }
} }
button.className = button.className.replace(/ disabled/g, "");
} }
else { else {
image.style.backgroundPosition = button.XShift + " " + disabledYShift; image.style.backgroundPosition = button.XShift + " " + disabledYShift;