Stackedit/public/res/extensions/comments.js

234 lines
9.0 KiB
JavaScript
Raw Normal View History

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, '&amp;').replace(/</g, '&lt;').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;
});