2014-03-22 01:57:31 +00:00
|
|
|
define([
|
|
|
|
"jquery",
|
|
|
|
"underscore",
|
|
|
|
"utils",
|
|
|
|
"crel",
|
|
|
|
"rangy",
|
|
|
|
"classes/Extension",
|
|
|
|
"text!html/commentsPopoverContent.html",
|
|
|
|
"bootstrap"
|
|
|
|
], function($, _, utils, crel, rangy, Extension, commentsPopoverContentHTML) {
|
|
|
|
|
|
|
|
var comments = new Extension("comments", 'Comments');
|
|
|
|
|
|
|
|
var offsetMap = {};
|
2014-03-23 02:33:41 +00:00
|
|
|
function setCommentEltCoordinates(commentElt, y) {
|
|
|
|
var lineIndex = Math.round(y/10);
|
|
|
|
var top = (y - 8) + 'px';
|
|
|
|
var right = ((offsetMap[lineIndex] || 0) * 25 + 10) + 'px';
|
|
|
|
commentElt.style.top = top;
|
|
|
|
commentElt.style.right = right;
|
|
|
|
return lineIndex;
|
|
|
|
}
|
2014-03-22 01:57:31 +00:00
|
|
|
|
|
|
|
var inputElt;
|
|
|
|
var marginElt;
|
2014-03-23 02:33:41 +00:00
|
|
|
var commentEltList = [];
|
2014-03-22 01:57:31 +00:00
|
|
|
var newCommentElt = crel('a', {
|
|
|
|
class: 'icon-comment new'
|
|
|
|
});
|
2014-03-23 02:33:41 +00:00
|
|
|
var cursorY;
|
|
|
|
comments.onCursorCoordinates = function(x, y) {
|
|
|
|
cursorY = y;
|
|
|
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
2014-03-22 01:57:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
var fileDesc;
|
|
|
|
comments.onFileSelected = function(selectedFileDesc) {
|
|
|
|
fileDesc = selectedFileDesc;
|
2014-03-23 02:33:41 +00:00
|
|
|
refreshDiscussions();
|
2014-03-22 01:57:31 +00:00
|
|
|
};
|
|
|
|
|
2014-03-23 02:33:41 +00:00
|
|
|
var openedPopover;
|
|
|
|
comments.onLayoutResize = function() {
|
|
|
|
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
|
|
|
refreshDiscussions();
|
|
|
|
};
|
|
|
|
|
|
|
|
var refreshId;
|
|
|
|
function refreshDiscussions() {
|
|
|
|
clearTimeout(refreshId);
|
|
|
|
commentEltList.forEach(function(commentElt) {
|
|
|
|
marginElt.removeChild(commentElt);
|
|
|
|
});
|
|
|
|
commentEltList = [];
|
|
|
|
offsetMap = {};
|
|
|
|
var discussionList = _.map(fileDesc.discussionList, _.identity);
|
|
|
|
function refreshOne() {
|
|
|
|
if(discussionList.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var discussion = discussionList.pop();
|
|
|
|
var commentElt = crel('a', {
|
|
|
|
class: 'icon-comment'
|
|
|
|
});
|
|
|
|
commentElt.discussion = discussion;
|
|
|
|
var coordinates = inputElt.getOffsetCoordinates(discussion.selectionEnd);
|
|
|
|
var lineIndex = setCommentEltCoordinates(commentElt, coordinates.y);
|
|
|
|
offsetMap[lineIndex] = (offsetMap[lineIndex] || 0) + 1;
|
|
|
|
marginElt.appendChild(commentElt);
|
|
|
|
commentEltList.push(commentElt);
|
|
|
|
|
|
|
|
// Replace newCommentElt
|
|
|
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
|
|
|
|
|
|
|
refreshId = setTimeout(refreshOne, 0);
|
|
|
|
}
|
|
|
|
refreshId = setTimeout(refreshOne, 50);
|
|
|
|
}
|
|
|
|
|
2014-03-22 01:57:31 +00:00
|
|
|
comments.onReady = function() {
|
|
|
|
var cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
|
|
|
normalize: false
|
|
|
|
});
|
|
|
|
var selectionRange;
|
|
|
|
var rangyRange;
|
|
|
|
var currentDiscussion;
|
|
|
|
|
|
|
|
inputElt = document.getElementById('wmd-input');
|
|
|
|
marginElt = document.querySelector('#wmd-input > .editor-margin');
|
|
|
|
marginElt.appendChild(newCommentElt);
|
2014-03-23 02:33:41 +00:00
|
|
|
$(document.body).append(crel('div', {
|
|
|
|
class: 'comments-popover'
|
|
|
|
})).popover({
|
2014-03-22 01:57:31 +00:00
|
|
|
placement: 'auto top',
|
|
|
|
container: '.comments-popover',
|
|
|
|
html: true,
|
|
|
|
title: function() {
|
|
|
|
if(!currentDiscussion) {
|
|
|
|
return '...';
|
|
|
|
}
|
|
|
|
var titleLength = currentDiscussion.selectionEnd - currentDiscussion.selectionStart;
|
2014-03-23 02:33:41 +00:00
|
|
|
var title = inputElt.textContent.substr(currentDiscussion.selectionStart, titleLength > 20 ? 20 : titleLength);
|
|
|
|
if(titleLength > 20) {
|
2014-03-22 01:57:31 +00:00
|
|
|
title += '...';
|
|
|
|
}
|
|
|
|
return title.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ');
|
|
|
|
},
|
|
|
|
content: function() {
|
|
|
|
var content = _.template(commentsPopoverContentHTML, {
|
|
|
|
});
|
|
|
|
return content;
|
|
|
|
},
|
|
|
|
selector: '#wmd-input > .editor-margin > .icon-comment'
|
|
|
|
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
|
|
|
$(evt.target).addClass('active');
|
2014-03-23 02:33:41 +00:00
|
|
|
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
|
|
|
|
|
|
|
// If it's an existing discussion
|
|
|
|
if(evt.target.discussion) {
|
|
|
|
currentDiscussion = evt.target.discussion;
|
|
|
|
selectionRange = inputElt.createRange(currentDiscussion.selectionStart, currentDiscussion.selectionEnd);
|
|
|
|
return;
|
|
|
|
}
|
2014-03-22 01:57:31 +00:00
|
|
|
|
|
|
|
// Get selected text
|
|
|
|
var inputSelection = inputElt.getSelectionStartEnd();
|
|
|
|
var selectionStart = inputSelection.selectionStart;
|
|
|
|
var selectionEnd = inputSelection.selectionEnd;
|
|
|
|
if(selectionStart === selectionEnd) {
|
|
|
|
var after = inputElt.textContent.substring(selectionStart);
|
|
|
|
var match = /\S+/.exec(after);
|
|
|
|
if(match) {
|
|
|
|
selectionStart += match.index;
|
|
|
|
if(match.index === 0) {
|
2014-03-23 02:33:41 +00:00
|
|
|
while(selectionStart && /\S/.test(inputElt.textContent[selectionStart - 1])) {
|
2014-03-22 01:57:31 +00:00
|
|
|
selectionStart--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
selectionEnd += match.index + match[0].length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
selectionRange = inputElt.createRange(selectionStart, selectionEnd);
|
|
|
|
currentDiscussion = {
|
|
|
|
selectionStart: selectionStart,
|
|
|
|
selectionEnd: selectionEnd,
|
2014-03-23 02:33:41 +00:00
|
|
|
commentList: []
|
2014-03-22 01:57:31 +00:00
|
|
|
};
|
|
|
|
}).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
|
|
|
|
|
|
|
// Move the popover in the margin
|
2014-03-23 02:33:41 +00:00
|
|
|
var popoverElt = document.querySelector('.comments-popover .popover:last-child');
|
2014-03-22 01:57:31 +00:00
|
|
|
var left = -10;
|
|
|
|
if(popoverElt.offsetWidth < marginElt.offsetWidth) {
|
|
|
|
left = marginElt.offsetWidth - popoverElt.offsetWidth - 10;
|
|
|
|
}
|
|
|
|
popoverElt.style.left = left + 'px';
|
2014-03-23 02:33:41 +00:00
|
|
|
popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(evt.target.style.right) - evt.target.offsetWidth / 2 - left) + 'px';
|
2014-03-22 01:57:31 +00:00
|
|
|
|
2014-03-23 02:33:41 +00:00
|
|
|
var $textarea = $(popoverElt.querySelector('.input-comment-content'));
|
|
|
|
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
|
|
|
$textarea.keydown(function(evt) {
|
|
|
|
// Enter key
|
|
|
|
switch(evt.which) {
|
|
|
|
case 13:
|
|
|
|
evt.preventDefault();
|
|
|
|
$addButton.click();
|
|
|
|
return;
|
|
|
|
case 27:
|
|
|
|
evt.preventDefault();
|
|
|
|
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
|
|
|
inputElt.focus();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$addButton.click(function(evt) {
|
2014-03-22 01:57:31 +00:00
|
|
|
var author = utils.getInputTextValue(popoverElt.querySelector('.input-comment-author'));
|
2014-03-23 02:33:41 +00:00
|
|
|
var content = utils.getInputTextValue($textarea, evt);
|
2014-03-22 01:57:31 +00:00
|
|
|
if(evt.isPropagationStopped()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-23 02:33:41 +00:00
|
|
|
var discussionList = fileDesc.discussionList || {};
|
2014-03-22 01:57:31 +00:00
|
|
|
if(!currentDiscussion.discussionIndex) {
|
2014-03-23 02:33:41 +00:00
|
|
|
// Create discussion index
|
|
|
|
var discussionIndex;
|
2014-03-22 01:57:31 +00:00
|
|
|
do {
|
2014-03-23 02:33:41 +00:00
|
|
|
discussionIndex = utils.randomString();
|
|
|
|
} while(_.has(discussionList, discussionIndex));
|
|
|
|
currentDiscussion.discussionIndex = discussionIndex;
|
|
|
|
discussionList[discussionIndex] = currentDiscussion;
|
2014-03-22 01:57:31 +00:00
|
|
|
}
|
2014-03-23 02:33:41 +00:00
|
|
|
currentDiscussion.commentList.push({
|
2014-03-22 01:57:31 +00:00
|
|
|
author: author,
|
|
|
|
content: content
|
|
|
|
});
|
2014-03-23 02:33:41 +00:00
|
|
|
fileDesc.discussionList = discussionList;
|
2014-03-22 01:57:31 +00:00
|
|
|
openedPopover.popover('toggle').popover('destroy');
|
2014-03-23 02:33:41 +00:00
|
|
|
refreshDiscussions();
|
|
|
|
inputElt.focus();
|
2014-03-22 01:57:31 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Prevent from closing on click inside the popover
|
|
|
|
$(popoverElt).on('click', function(evt) {
|
|
|
|
evt.stopPropagation();
|
|
|
|
});
|
|
|
|
setTimeout(function() {
|
|
|
|
openedPopover = $(evt.target);
|
|
|
|
|
|
|
|
// Highlight selected text
|
|
|
|
rangyRange = rangy.createRange();
|
|
|
|
rangyRange.setStart(selectionRange.startContainer, selectionRange.startOffset);
|
|
|
|
rangyRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);
|
|
|
|
cssApplier.applyToRange(rangyRange);
|
|
|
|
|
|
|
|
// Focus on textarea
|
2014-03-23 02:33:41 +00:00
|
|
|
$textarea.focus();
|
2014-03-22 01:57:31 +00:00
|
|
|
}, 10);
|
|
|
|
}).on('hide.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
|
|
|
$(evt.target).removeClass('active');
|
|
|
|
|
|
|
|
// Remove highlight
|
|
|
|
rangyRange && cssApplier.undoToRange(rangyRange);
|
|
|
|
openedPopover = undefined;
|
|
|
|
rangyRange = undefined;
|
|
|
|
currentDiscussion = undefined;
|
|
|
|
}).on('click', function() {
|
|
|
|
// Close on click outside the popover
|
|
|
|
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return comments;
|
|
|
|
});
|