diff --git a/public/res/editor.js b/public/res/editor.js
index 273bff3b..335196ef 100644
--- a/public/res/editor.js
+++ b/public/res/editor.js
@@ -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();
diff --git a/public/res/extensions/comments.js b/public/res/extensions/comments.js
index 4c5c14c4..0057c504 100644
--- a/public/res/extensions/comments.js
+++ b/public/res/extensions/comments.js
@@ -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 = [
- '
'
- ].join('');
- var popoverTitleTmpl = [
- '',
- ' ',
- ' ',
- ' ',
- ' “<%- title %>”',
- ''
- ].join('');
+ var commentTmpl = [
+ ''
+ ].join('');
+ var popoverTitleTmpl = [
+ '',
+ ' ',
+ ' ',
+ ' ',
+ ' “<%- title %>”',
+ ''
+ ].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;
});
diff --git a/public/res/libs/Markdown.Editor.js b/public/res/libs/Markdown.Editor.js
index 742d94e7..d95b0fcb 100644
--- a/public/res/libs/Markdown.Editor.js
+++ b/public/res/libs/Markdown.Editor.js
@@ -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;