Fixed className leak in undo/redo buttons
This commit is contained in:
parent
349d43bd0d
commit
492f5ffbc0
@ -27,6 +27,7 @@ define([
|
||||
var pagedownEditor;
|
||||
var refreshPreviewLater = (function() {
|
||||
var elapsedTime = 0;
|
||||
var timeoutId;
|
||||
var refreshPreview = function() {
|
||||
var startTime = Date.now();
|
||||
pagedownEditor.refreshPreview();
|
||||
@ -36,7 +37,8 @@ define([
|
||||
return _.debounce(refreshPreview, 500);
|
||||
}
|
||||
return function() {
|
||||
setTimeout(refreshPreview, elapsedTime < 2000 ? elapsedTime : 2000);
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(refreshPreview, elapsedTime < 2000 ? elapsedTime : 2000);
|
||||
};
|
||||
})();
|
||||
eventMgr.addListener('onPagedownConfigure', function(pagedownEditorParam) {
|
||||
@ -174,9 +176,10 @@ define([
|
||||
var min = Math.min(this.selectionStart, this.selectionEnd);
|
||||
var max = Math.max(this.selectionStart, this.selectionEnd);
|
||||
var range = this.createRange(min, max);
|
||||
var selection = document.getSelection();
|
||||
var selection = rangy.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
selection.addRange(range, this.selectionStart > this.selectionEnd);
|
||||
selection.detach();
|
||||
range.detach();
|
||||
};
|
||||
this.setSelectionStartEnd = function(start, end) {
|
||||
@ -203,7 +206,7 @@ define([
|
||||
var selectionStart = self.selectionStart;
|
||||
var selectionEnd = self.selectionEnd;
|
||||
var range;
|
||||
var selection = document.getSelection();
|
||||
var selection = rangy.getSelection();
|
||||
if(selection.rangeCount > 0) {
|
||||
var selectionRange = selection.getRangeAt(0);
|
||||
var element = selectionRange.startContainer;
|
||||
@ -220,7 +223,7 @@ define([
|
||||
element = container = container.parentNode;
|
||||
} while(element && element != inputElt);
|
||||
|
||||
if(false) {
|
||||
if(selection.isBackwards()) {
|
||||
selectionStart = offset + (range + '').length;
|
||||
selectionEnd = offset;
|
||||
}
|
||||
@ -231,6 +234,7 @@ define([
|
||||
}
|
||||
selectionRange.detach();
|
||||
}
|
||||
selection.detach();
|
||||
self.setSelectionStartEnd(selectionStart, selectionEnd);
|
||||
}
|
||||
undoMgr.saveSelectionState();
|
||||
|
@ -1,479 +1,489 @@
|
||||
define([
|
||||
"jquery",
|
||||
"underscore",
|
||||
"utils",
|
||||
"storage",
|
||||
"crel",
|
||||
"rangy",
|
||||
"classes/Extension",
|
||||
"text!html/commentsPopoverContent.html",
|
||||
"bootstrap"
|
||||
"jquery",
|
||||
"underscore",
|
||||
"utils",
|
||||
"storage",
|
||||
"crel",
|
||||
"rangy",
|
||||
"classes/Extension",
|
||||
"text!html/commentsPopoverContent.html",
|
||||
"bootstrap"
|
||||
], 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 = [
|
||||
'<div class="comment-block<%= reply ? \' reply\' : \'\' %>">',
|
||||
' <div class="comment-author"><i class="icon-comment"></i> <%= author %></div>',
|
||||
' <div class="comment-content"><%= content %></div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
var popoverTitleTmpl = [
|
||||
'<span class="clearfix">',
|
||||
' <a href="#" class="action-remove-discussion pull-right">',
|
||||
' <i class="icon-trash"></i>',
|
||||
' </a>',
|
||||
' “<%- title %>”',
|
||||
'</span>'
|
||||
].join('');
|
||||
var commentTmpl = [
|
||||
'<div class="comment-block<%= reply ? \' reply\' : \'\' %>">',
|
||||
' <div class="comment-author"><i class="icon-comment"></i> <%= author %></div>',
|
||||
' <div class="comment-content"><%= content %></div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
var popoverTitleTmpl = [
|
||||
'<span class="clearfix">',
|
||||
' <a href="#" class="action-remove-discussion pull-right">',
|
||||
' <i class="icon-trash"></i>',
|
||||
' </a>',
|
||||
' “<%- title %>”',
|
||||
'</span>'
|
||||
].join('');
|
||||
|
||||
var eventMgr;
|
||||
comments.onEventMgrCreated = function(eventMgrParam) {
|
||||
eventMgr = eventMgrParam;
|
||||
};
|
||||
var eventMgr;
|
||||
comments.onEventMgrCreated = function(eventMgrParam) {
|
||||
eventMgr = eventMgrParam;
|
||||
};
|
||||
|
||||
var editor;
|
||||
var selectionMgr;
|
||||
comments.onEditorCreated = function(editorParam) {
|
||||
editor = editorParam;
|
||||
selectionMgr = editor.selectionMgr;
|
||||
};
|
||||
var editor;
|
||||
var selectionMgr;
|
||||
comments.onEditorCreated = function(editorParam) {
|
||||
editor = editorParam;
|
||||
selectionMgr = editor.selectionMgr;
|
||||
};
|
||||
|
||||
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 yList = [];
|
||||
|
||||
var inputElt;
|
||||
var marginElt;
|
||||
var newCommentElt = crel('a', {
|
||||
class: 'discussion icon-comment new'
|
||||
});
|
||||
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';
|
||||
}
|
||||
|
||||
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;
|
||||
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 inputElt;
|
||||
var marginElt;
|
||||
var newCommentElt = crel('a', {
|
||||
class: 'discussion icon-comment new'
|
||||
});
|
||||
|
||||
var cssApplier;
|
||||
var currentFileDesc;
|
||||
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;
|
||||
});
|
||||
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);
|
||||
function Context(commentElt, fileDesc) {
|
||||
this.commentElt = commentElt;
|
||||
this.$commentElt = $(commentElt).addClass('active');
|
||||
this.fileDesc = fileDesc;
|
||||
this.discussionIndex = commentElt.discussionIndex;
|
||||
}
|
||||
|
||||
marginElt.appendChild(commentElt);
|
||||
commentEltMap[discussion.discussionIndex] = commentElt;
|
||||
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;
|
||||
|
||||
if(currentContext && currentContext.getDiscussion() === discussion) {
|
||||
inputElt.scrollTop += parseInt(commentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
||||
movePopover(commentElt);
|
||||
}
|
||||
refreshTimeoutId = setTimeout(refreshOne, 5);
|
||||
}
|
||||
clearTimeout(refreshTimeoutId);
|
||||
refreshTimeoutId = setTimeout(refreshOne, 5);
|
||||
}, 50);
|
||||
comments.onLayoutResize = refreshDiscussions;
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
comments.onFileOpen = function(fileDesc) {
|
||||
currentFileDesc = fileDesc;
|
||||
refreshDiscussions();
|
||||
};
|
||||
var cssApplier;
|
||||
var currentFileDesc;
|
||||
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) {
|
||||
currentFileDesc === fileDesc && refreshDiscussions();
|
||||
};
|
||||
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);
|
||||
|
||||
comments.onCommentsChanged = function(fileDesc) {
|
||||
if(currentFileDesc !== fileDesc) {
|
||||
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);
|
||||
marginElt.appendChild(commentElt);
|
||||
commentEltMap[discussion.discussionIndex] = commentElt;
|
||||
|
||||
// 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);
|
||||
}
|
||||
refreshDiscussions();
|
||||
};
|
||||
if(currentContext && currentContext.getDiscussion() === discussion) {
|
||||
inputElt.scrollTop += parseInt(commentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
||||
movePopover(commentElt);
|
||||
}
|
||||
refreshTimeoutId = setTimeout(refreshOne, 5);
|
||||
}
|
||||
|
||||
function closeCurrentPopover() {
|
||||
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
|
||||
}
|
||||
clearTimeout(refreshTimeoutId);
|
||||
refreshTimeoutId = setTimeout(refreshOne, 5);
|
||||
}, 50);
|
||||
comments.onLayoutResize = refreshDiscussions;
|
||||
|
||||
comments.onDiscussionCreated = function(fileDesc) {
|
||||
currentFileDesc === fileDesc && refreshDiscussions();
|
||||
};
|
||||
comments.onFileOpen = function(fileDesc) {
|
||||
currentFileDesc = fileDesc;
|
||||
refreshDiscussions();
|
||||
};
|
||||
|
||||
comments.onDiscussionRemoved = function(fileDesc, discussion) {
|
||||
if(currentFileDesc === fileDesc) {
|
||||
// Close popover if the discussion has been removed
|
||||
if(currentContext !== undefined && currentContext.discussionIndex == discussion.discussionIndex) {
|
||||
closeCurrentPopover();
|
||||
}
|
||||
refreshDiscussions();
|
||||
}
|
||||
};
|
||||
comments.onContentChanged = function(fileDesc) {
|
||||
currentFileDesc === fileDesc && refreshDiscussions();
|
||||
};
|
||||
|
||||
function getDiscussionComments() {
|
||||
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('');
|
||||
}
|
||||
comments.onCommentsChanged = function(fileDesc) {
|
||||
if(currentFileDesc !== fileDesc) {
|
||||
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);
|
||||
|
||||
comments.onReady = function() {
|
||||
cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
||||
normalize: false
|
||||
});
|
||||
var previousContent = '';
|
||||
// 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);
|
||||
}
|
||||
refreshDiscussions();
|
||||
};
|
||||
|
||||
inputElt = document.getElementById('wmd-input');
|
||||
marginElt = document.querySelector('#wmd-input > .editor-margin');
|
||||
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;
|
||||
function closeCurrentPopover() {
|
||||
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
|
||||
}
|
||||
|
||||
// If it's not an existing discussion
|
||||
var discussion = context.getDiscussion();
|
||||
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;
|
||||
comments.onDiscussionCreated = function(fileDesc) {
|
||||
currentFileDesc === fileDesc && refreshDiscussions();
|
||||
};
|
||||
|
||||
}).on('shown.bs.popover', function(evt) {
|
||||
var context = currentContext;
|
||||
var popoverElt = context.getPopoverElt();
|
||||
context.$authorInputElt = $(popoverElt.querySelector('.input-comment-author')).val(storage['author.name']);
|
||||
context.$contentInputElt = $(popoverElt.querySelector('.input-comment-content'));
|
||||
context.hr = popoverElt.querySelector('hr');
|
||||
movePopover(context.commentElt);
|
||||
comments.onDiscussionRemoved = function(fileDesc, discussion) {
|
||||
if(currentFileDesc === fileDesc) {
|
||||
// Close popover if the discussion has been removed
|
||||
if(currentContext !== undefined && currentContext.discussionIndex == discussion.discussionIndex) {
|
||||
closeCurrentPopover();
|
||||
}
|
||||
refreshDiscussions();
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll to the bottom of the discussion
|
||||
popoverElt.querySelector('.scrollport').scrollTop = 9999999;
|
||||
function getDiscussionComments() {
|
||||
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'));
|
||||
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
|
||||
// Enter key
|
||||
switch(evt.which) {
|
||||
case 13:
|
||||
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;
|
||||
}
|
||||
comments.onReady = function() {
|
||||
cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
||||
normalize: false
|
||||
});
|
||||
var previousContent = '';
|
||||
|
||||
var discussion = context.getDiscussion();
|
||||
context.$contentInputElt.val('');
|
||||
closeCurrentPopover();
|
||||
inputElt = document.getElementById('wmd-input');
|
||||
marginElt = document.querySelector('#wmd-input > .editor-margin');
|
||||
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 || [];
|
||||
discussion.commentList.push({
|
||||
author: author,
|
||||
content: content
|
||||
});
|
||||
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();
|
||||
});
|
||||
// If it's not an existing discussion
|
||||
var discussion = context.getDiscussion();
|
||||
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;
|
||||
|
||||
var $removeButton = $(popoverElt.querySelector('.action-remove-discussion'));
|
||||
if(evt.target.discussionIndex) {
|
||||
// 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();
|
||||
}
|
||||
}).on('shown.bs.popover', function(evt) {
|
||||
var context = currentContext;
|
||||
var popoverElt = context.getPopoverElt();
|
||||
context.$authorInputElt = $(popoverElt.querySelector('.input-comment-author')).val(storage['author.name']);
|
||||
context.$contentInputElt = $(popoverElt.querySelector('.input-comment-content'));
|
||||
context.hr = popoverElt.querySelector('hr');
|
||||
movePopover(context.commentElt);
|
||||
|
||||
// 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);
|
||||
// Scroll to the bottom of the discussion
|
||||
popoverElt.querySelector('.scrollport').scrollTop = 9999999;
|
||||
|
||||
// Focus on textarea
|
||||
context.$contentInputElt.focus().val(previousContent);
|
||||
}).on('hide.bs.popover', function() {
|
||||
if(!currentContext) {
|
||||
return;
|
||||
}
|
||||
currentContext.$commentElt.removeClass('active');
|
||||
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
||||
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
|
||||
// Enter key
|
||||
switch(evt.which) {
|
||||
case 13:
|
||||
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;
|
||||
}
|
||||
|
||||
// Save content and author for later
|
||||
previousContent = currentContext.$contentInputElt.val();
|
||||
storage['author.name'] = currentContext.$authorInputElt.val();
|
||||
var discussion = context.getDiscussion();
|
||||
context.$contentInputElt.val('');
|
||||
closeCurrentPopover();
|
||||
|
||||
// Remove highlight
|
||||
cssApplier.undoToRange(currentContext.rangyRange);
|
||||
currentContext.rangyRange.detach();
|
||||
currentContext = undefined;
|
||||
delete currentFileDesc.newDiscussion;
|
||||
});
|
||||
discussion.commentList = discussion.commentList || [];
|
||||
discussion.commentList.push({
|
||||
author: author,
|
||||
content: content
|
||||
});
|
||||
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
|
||||
$popoverContainer.on('click', '.popover', function(evt) {
|
||||
evt.stopPropagation();
|
||||
});
|
||||
var $removeButton = $(popoverElt.querySelector('.action-remove-discussion'));
|
||||
if(evt.target.discussionIndex) {
|
||||
// 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);
|
||||
$openDiscussionElt = $('.button-open-discussion').click(function(evt) {
|
||||
var $commentElt = $newCommentElt;
|
||||
if(currentContext) {
|
||||
if(!currentContext.discussionIndex) {
|
||||
$commentElt = $(_.first(sortedCommentEltList));
|
||||
}
|
||||
else {
|
||||
var curentIndex = -1;
|
||||
sortedCommentEltList.some(function(elt, index) {
|
||||
if(elt === currentContext.commentElt) {
|
||||
curentIndex = index;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
$commentElt = $(sortedCommentEltList[(curentIndex + 1)]);
|
||||
}
|
||||
}
|
||||
if($commentElt.length === 0) {
|
||||
// Close the popover properly
|
||||
closeCurrentPopover();
|
||||
editor.focus();
|
||||
editor.adjustCursorPosition();
|
||||
}
|
||||
else {
|
||||
$commentElt.click();
|
||||
}
|
||||
evt.stopPropagation();
|
||||
});
|
||||
$openDiscussionIconElt = $openDiscussionElt.find('i');
|
||||
};
|
||||
var $newCommentElt = $(newCommentElt);
|
||||
$openDiscussionElt = $('.button-open-discussion').click(function(evt) {
|
||||
var $commentElt = $newCommentElt;
|
||||
if(currentContext) {
|
||||
if(!currentContext.discussionIndex) {
|
||||
$commentElt = $(_.first(sortedCommentEltList));
|
||||
}
|
||||
else {
|
||||
var curentIndex = -1;
|
||||
sortedCommentEltList.some(function(elt, index) {
|
||||
if(elt === currentContext.commentElt) {
|
||||
curentIndex = index;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
$commentElt = $(sortedCommentEltList[(curentIndex + 1)]);
|
||||
}
|
||||
}
|
||||
if($commentElt.length === 0) {
|
||||
// Close the popover properly
|
||||
closeCurrentPopover();
|
||||
editor.focus();
|
||||
editor.adjustCursorPosition();
|
||||
}
|
||||
else {
|
||||
$commentElt.click();
|
||||
}
|
||||
evt.stopPropagation();
|
||||
});
|
||||
$openDiscussionIconElt = $openDiscussionElt.find('i');
|
||||
};
|
||||
|
||||
return comments;
|
||||
return comments;
|
||||
});
|
||||
|
@ -1425,6 +1425,7 @@
|
||||
var disabledYShift = "-20px";
|
||||
var highlightYShift = "-40px";
|
||||
var image = button.getElementsByTagName("span")[0];
|
||||
button.className = button.className.replace(/ disabled/g, "");
|
||||
if (isEnabled) {
|
||||
image.style.backgroundPosition = button.XShift + " " + normalYShift;
|
||||
button.onmouseover = function () {
|
||||
@ -1454,7 +1455,6 @@
|
||||
return false;
|
||||
}
|
||||
}
|
||||
button.className = button.className.replace(/ disabled/g, "");
|
||||
}
|
||||
else {
|
||||
image.style.backgroundPosition = button.XShift + " " + disabledYShift;
|
||||
|
Loading…
Reference in New Issue
Block a user