Comments support
This commit is contained in:
parent
e310d234cf
commit
8b565e32ce
@ -14,7 +14,7 @@ define([], function() {
|
|||||||
constants.DEFAULT_FILE_TITLE = "Title";
|
constants.DEFAULT_FILE_TITLE = "Title";
|
||||||
constants.DEFAULT_FOLDER_NAME = "New folder";
|
constants.DEFAULT_FOLDER_NAME = "New folder";
|
||||||
constants.GDRIVE_DEFAULT_FILE_TITLE = "New Markdown document";
|
constants.GDRIVE_DEFAULT_FILE_TITLE = "New Markdown document";
|
||||||
constants.EDITOR_DEFAULT_PADDING = 30;
|
constants.EDITOR_DEFAULT_PADDING = 35;
|
||||||
constants.CHECK_ONLINE_PERIOD = 120000;
|
constants.CHECK_ONLINE_PERIOD = 120000;
|
||||||
constants.AJAX_TIMEOUT = 30000;
|
constants.AJAX_TIMEOUT = 30000;
|
||||||
constants.ASYNC_TASK_DEFAULT_TIMEOUT = 60000;
|
constants.ASYNC_TASK_DEFAULT_TIMEOUT = 60000;
|
||||||
|
@ -397,7 +397,7 @@ define([
|
|||||||
|
|
||||||
if(pagedownEditor !== undefined) {
|
if(pagedownEditor !== undefined) {
|
||||||
// If the editor is already created
|
// If the editor is already created
|
||||||
$editorElt.val(initDocumentContent);
|
editor.contentElt.textContent = initDocumentContent;
|
||||||
pagedownEditor.undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
pagedownEditor.undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
||||||
$editorElt.focus();
|
$editorElt.focus();
|
||||||
return;
|
return;
|
||||||
@ -455,7 +455,7 @@ define([
|
|||||||
eventMgr.onPagedownConfigure(pagedownEditor);
|
eventMgr.onPagedownConfigure(pagedownEditor);
|
||||||
pagedownEditor.hooks.chain("onPreviewRefresh", eventMgr.onAsyncPreview);
|
pagedownEditor.hooks.chain("onPreviewRefresh", eventMgr.onAsyncPreview);
|
||||||
pagedownEditor.run();
|
pagedownEditor.run();
|
||||||
$editorElt.val(initDocumentContent);
|
editor.contentElt.textContent = initDocumentContent;
|
||||||
pagedownEditor.undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
pagedownEditor.undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
||||||
$editorElt.focus();
|
$editorElt.focus();
|
||||||
|
|
||||||
@ -799,7 +799,7 @@ define([
|
|||||||
});
|
});
|
||||||
$(".action-import-docs-settings-confirm").click(function() {
|
$(".action-import-docs-settings-confirm").click(function() {
|
||||||
storage.clear();
|
storage.clear();
|
||||||
var allowedKeys = /^file\.|^folder\.|^publish\.|^settings$|^sync\.|^google\.|^themeV3$|^version$/;
|
var allowedKeys = /^file\.|^folder\.|^publish\.|^settings$|^sync\.|^google\.|^author\.|^themeV3$|^version$/;
|
||||||
_.each(newstorage, function(value, key) {
|
_.each(newstorage, function(value, key) {
|
||||||
if(allowedKeys.test(key)) {
|
if(allowedKeys.test(key)) {
|
||||||
storage[key] = value;
|
storage[key] = value;
|
||||||
|
@ -61,23 +61,66 @@ define([
|
|||||||
fileDesc = selectedFileDesc;
|
fileDesc = selectedFileDesc;
|
||||||
});
|
});
|
||||||
|
|
||||||
function saveEditorState() {
|
function saveSelectionState() {
|
||||||
if(!inputElt.focused) {
|
var selection = window.getSelection();
|
||||||
return;
|
if (selection.rangeCount > 0) {
|
||||||
|
var range = selection.getRangeAt(0);
|
||||||
|
var element = range.startContainer;
|
||||||
|
|
||||||
|
if ((inputElt.compareDocumentPosition(element) & 0x10)) {
|
||||||
|
var container = element;
|
||||||
|
var offset = range.startOffset;
|
||||||
|
do {
|
||||||
|
while (element = element.previousSibling) {
|
||||||
|
if (element.textContent) {
|
||||||
|
offset += element.textContent.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element = container = container.parentNode;
|
||||||
|
} while (element && element != inputElt);
|
||||||
|
selectionStart = offset;
|
||||||
|
selectionEnd = offset + (range + '').length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
selectionStart = inputElt.selectionStart;
|
|
||||||
selectionEnd = inputElt.selectionEnd;
|
|
||||||
scrollTop = inputElt.scrollTop;
|
|
||||||
if(fileChanged === false) {
|
if(fileChanged === false) {
|
||||||
fileDesc.editorStart = selectionStart;
|
fileDesc.editorStart = selectionStart;
|
||||||
fileDesc.editorEnd = selectionEnd;
|
fileDesc.editorEnd = selectionEnd;
|
||||||
fileDesc.editorScrollTop = scrollTop;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var previousTextContent;
|
var previousTextContent;
|
||||||
|
function getContentChange(textContent) {
|
||||||
|
// Find the first modified char
|
||||||
|
var startIndex = 0;
|
||||||
|
var startIndexMax = Math.min(previousTextContent.length, textContent.length);
|
||||||
|
while (startIndex < startIndexMax) {
|
||||||
|
if (previousTextContent.charCodeAt(startIndex) !== textContent.charCodeAt(startIndex)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startIndex++;
|
||||||
|
}
|
||||||
|
// Find the last modified char
|
||||||
|
var endIndex = 1;
|
||||||
|
var endIndexMax = Math.min(previousTextContent.length - startIndex, textContent.length - startIndex);
|
||||||
|
while (endIndex <= endIndexMax) {
|
||||||
|
if (previousTextContent.charCodeAt(previousTextContent.length - endIndex) !== textContent.charCodeAt(textContent.length - endIndex)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
endIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var replacement = textContent.substring(startIndex, textContent.length - endIndex + 1);
|
||||||
|
endIndex = previousTextContent.length - endIndex + 1;
|
||||||
|
return {
|
||||||
|
startIndex: startIndex,
|
||||||
|
endIndex: endIndex,
|
||||||
|
replacement: replacement
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function checkContentChange() {
|
function checkContentChange() {
|
||||||
saveEditorState();
|
saveSelectionState();
|
||||||
var currentTextContent = inputElt.textContent;
|
var currentTextContent = inputElt.textContent;
|
||||||
if(fileChanged === false) {
|
if(fileChanged === false) {
|
||||||
if(currentTextContent == previousTextContent) {
|
if(currentTextContent == previousTextContent) {
|
||||||
@ -86,6 +129,37 @@ define([
|
|||||||
if(!/\n$/.test(currentTextContent)) {
|
if(!/\n$/.test(currentTextContent)) {
|
||||||
currentTextContent += '\n';
|
currentTextContent += '\n';
|
||||||
}
|
}
|
||||||
|
var change = getContentChange(currentTextContent);
|
||||||
|
var endOffset = change.startIndex + change.replacement.length - change.endIndex;
|
||||||
|
|
||||||
|
// Move comments according to change
|
||||||
|
var updateDiscussionList = false;
|
||||||
|
_.each(fileDesc.discussionList, function(discussion) {
|
||||||
|
if(discussion.isRemoved === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// selectionEnd
|
||||||
|
if(discussion.selectionEnd >= change.endIndex) {
|
||||||
|
discussion.selectionEnd += endOffset;
|
||||||
|
updateDiscussionList = true;
|
||||||
|
}
|
||||||
|
else if(discussion.selectionEnd > change.startIndex) {
|
||||||
|
discussion.selectionEnd = change.startIndex;
|
||||||
|
updateDiscussionList = true;
|
||||||
|
}
|
||||||
|
// selectionStart
|
||||||
|
if(discussion.selectionStart >= change.endIndex) {
|
||||||
|
discussion.selectionStart += endOffset;
|
||||||
|
updateDiscussionList = true;
|
||||||
|
}
|
||||||
|
else if(discussion.selectionStart > change.startIndex) {
|
||||||
|
discussion.selectionStart = change.startIndex;
|
||||||
|
updateDiscussionList = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(updateDiscussionList === true) {
|
||||||
|
fileDesc.discussionList = fileDesc.discussionList; // Write discussionList in localStorage
|
||||||
|
}
|
||||||
fileDesc.content = currentTextContent;
|
fileDesc.content = currentTextContent;
|
||||||
eventMgr.onContentChanged(fileDesc, currentTextContent);
|
eventMgr.onContentChanged(fileDesc, currentTextContent);
|
||||||
}
|
}
|
||||||
@ -199,7 +273,7 @@ define([
|
|||||||
var cursorY = 0;
|
var cursorY = 0;
|
||||||
var isBackwardSelection = false;
|
var isBackwardSelection = false;
|
||||||
function updateCursorCoordinates() {
|
function updateCursorCoordinates() {
|
||||||
saveEditorState();
|
saveSelectionState();
|
||||||
$inputElt.toggleClass('has-selection', selectionStart !== selectionEnd);
|
$inputElt.toggleClass('has-selection', selectionStart !== selectionEnd);
|
||||||
|
|
||||||
var element;
|
var element;
|
||||||
@ -234,8 +308,10 @@ define([
|
|||||||
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
|
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustCursorPosition() {
|
var adjustCursorPosition = _.debounce(function() {
|
||||||
inputElt && setTimeout(function() {
|
if(inputElt === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
updateCursorCoordinates();
|
updateCursorCoordinates();
|
||||||
|
|
||||||
var adjust = inputElt.offsetHeight / 2;
|
var adjust = inputElt.offsetHeight / 2;
|
||||||
@ -251,7 +327,6 @@ define([
|
|||||||
inputElt.scrollTop += cursorY - cursorMaxY;
|
inputElt.scrollTop += cursorY - cursorMaxY;
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
|
||||||
eventMgr.addListener('onLayoutResize', adjustCursorPosition);
|
eventMgr.addListener('onLayoutResize', adjustCursorPosition);
|
||||||
|
|
||||||
editor.init = function(elt1, elt2) {
|
editor.init = function(elt1, elt2) {
|
||||||
@ -279,7 +354,12 @@ define([
|
|||||||
characterData: true
|
characterData: true
|
||||||
});
|
});
|
||||||
|
|
||||||
$(inputElt).scroll(saveEditorState);
|
$(inputElt).scroll(function() {
|
||||||
|
scrollTop = inputElt.scrollTop;
|
||||||
|
if(fileChanged === false) {
|
||||||
|
fileDesc.editorScrollTop = scrollTop;
|
||||||
|
}
|
||||||
|
});
|
||||||
$(previewElt).scroll(function() {
|
$(previewElt).scroll(function() {
|
||||||
if(fileChanged === false) {
|
if(fileChanged === false) {
|
||||||
fileDesc.previewScrollTop = previewElt.scrollTop;
|
fileDesc.previewScrollTop = previewElt.scrollTop;
|
||||||
@ -303,64 +383,16 @@ define([
|
|||||||
return this.textContent;
|
return this.textContent;
|
||||||
},
|
},
|
||||||
set: function (value) {
|
set: function (value) {
|
||||||
var currentValue = this.textContent;
|
var contentChange = getContentChange(value);
|
||||||
|
var range = inputElt.createRange(contentChange.startIndex, contentChange.endIndex);
|
||||||
// Find the first modified char
|
|
||||||
var startIndex = 0;
|
|
||||||
var startIndexMax = Math.min(currentValue.length, value.length);
|
|
||||||
while (startIndex < startIndexMax) {
|
|
||||||
if (currentValue.charCodeAt(startIndex) !== value.charCodeAt(startIndex)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
startIndex++;
|
|
||||||
}
|
|
||||||
// Find the last modified char
|
|
||||||
var endIndex = 1;
|
|
||||||
var endIndexMax = Math.min(currentValue.length - startIndex, value.length - startIndex);
|
|
||||||
while (endIndex <= endIndexMax) {
|
|
||||||
if (currentValue.charCodeAt(currentValue.length - endIndex) !== value.charCodeAt(value.length - endIndex)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
endIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var replacementText = value.substring(startIndex, value.length - endIndex + 1);
|
|
||||||
endIndex = currentValue.length - endIndex + 1;
|
|
||||||
|
|
||||||
var range = inputElt.createRange(startIndex, endIndex);
|
|
||||||
range.deleteContents();
|
range.deleteContents();
|
||||||
range.insertNode(document.createTextNode(replacementText));
|
range.insertNode(document.createTextNode(contentChange.replacement));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(inputElt, 'selectionStart', {
|
Object.defineProperty(inputElt, 'selectionStart', {
|
||||||
get: function () {
|
get: function () {
|
||||||
var selection = window.getSelection();
|
|
||||||
|
|
||||||
if (selection.rangeCount) {
|
|
||||||
var range = selection.getRangeAt(0),
|
|
||||||
element = range.startContainer,
|
|
||||||
container = element,
|
|
||||||
offset = range.startOffset;
|
|
||||||
|
|
||||||
if (!(this.compareDocumentPosition(element) & 0x10)) {
|
|
||||||
return selectionStart;
|
return selectionStart;
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
while (element = element.previousSibling) {
|
|
||||||
if (element.textContent) {
|
|
||||||
offset += element.textContent.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
element = container = container.parentNode;
|
|
||||||
} while (element && element != this);
|
|
||||||
|
|
||||||
return offset;
|
|
||||||
} else {
|
|
||||||
return selectionStart;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
set: function (value) {
|
set: function (value) {
|
||||||
inputElt.setSelectionStartEnd(value, selectionEnd);
|
inputElt.setSelectionStartEnd(value, selectionEnd);
|
||||||
@ -372,13 +404,7 @@ define([
|
|||||||
|
|
||||||
Object.defineProperty(inputElt, 'selectionEnd', {
|
Object.defineProperty(inputElt, 'selectionEnd', {
|
||||||
get: function () {
|
get: function () {
|
||||||
var selection = window.getSelection();
|
|
||||||
|
|
||||||
if (selection.rangeCount) {
|
|
||||||
return this.selectionStart + (selection.getRangeAt(0) + '').length;
|
|
||||||
} else {
|
|
||||||
return selectionEnd;
|
return selectionEnd;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
set: function (value) {
|
set: function (value) {
|
||||||
inputElt.setSelectionStartEnd(selectionStart, value);
|
inputElt.setSelectionStartEnd(selectionStart, value);
|
||||||
@ -388,34 +414,24 @@ define([
|
|||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
inputElt.setSelectionStartEnd = function (ss, se) {
|
inputElt.setSelectionStartEnd = function (start, end) {
|
||||||
selectionStart = ss;
|
selectionStart = start;
|
||||||
selectionEnd = se;
|
selectionEnd = end;
|
||||||
var range = inputElt.createRange(ss, se);
|
var range = inputElt.createRange(start, end);
|
||||||
|
|
||||||
var selection = window.getSelection();
|
var selection = window.getSelection();
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
};
|
};
|
||||||
|
|
||||||
inputElt.getSelectionStartEnd = function () {
|
inputElt.createRange = function(start, end) {
|
||||||
return {
|
|
||||||
selectionStart: selectionStart,
|
|
||||||
selectionEnd: selectionEnd
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
inputElt.createRange = function(ss, se) {
|
|
||||||
|
|
||||||
var range = document.createRange(),
|
|
||||||
offset = _.isObject(ss) ? ss : findOffset(ss);
|
|
||||||
|
|
||||||
|
var range = document.createRange();
|
||||||
|
var offset = _.isObject(start) ? start : findOffset(start);
|
||||||
range.setStart(offset.element, offset.offset);
|
range.setStart(offset.element, offset.offset);
|
||||||
|
|
||||||
if (se && se != ss) {
|
if (end && end != start) {
|
||||||
offset = _.isObject(se) ? se : findOffset(se);
|
offset = _.isObject(end) ? end : findOffset(end);
|
||||||
}
|
}
|
||||||
|
|
||||||
range.setEnd(offset.element, offset.offset);
|
range.setEnd(offset.element, offset.offset);
|
||||||
return range;
|
return range;
|
||||||
};
|
};
|
||||||
@ -435,7 +451,7 @@ define([
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
saveEditorState();
|
saveSelectionState();
|
||||||
adjustCursorPosition();
|
adjustCursorPosition();
|
||||||
|
|
||||||
var cmdOrCtrl = evt.metaKey || evt.ctrlKey;
|
var cmdOrCtrl = evt.metaKey || evt.ctrlKey;
|
||||||
|
@ -210,6 +210,11 @@ define([
|
|||||||
addEventHook("onSectionsCreated");
|
addEventHook("onSectionsCreated");
|
||||||
addEventHook("onCursorCoordinates");
|
addEventHook("onCursorCoordinates");
|
||||||
|
|
||||||
|
// Operations on comments
|
||||||
|
addEventHook("onDiscussionCreated");
|
||||||
|
addEventHook("onDiscussionRemoved");
|
||||||
|
addEventHook("onCommentAdded");
|
||||||
|
|
||||||
// Refresh twitter buttons
|
// Refresh twitter buttons
|
||||||
addEventHook("onTweet");
|
addEventHook("onTweet");
|
||||||
|
|
||||||
|
@ -2,15 +2,28 @@ define([
|
|||||||
"jquery",
|
"jquery",
|
||||||
"underscore",
|
"underscore",
|
||||||
"utils",
|
"utils",
|
||||||
|
"storage",
|
||||||
"crel",
|
"crel",
|
||||||
"rangy",
|
"rangy",
|
||||||
"classes/Extension",
|
"classes/Extension",
|
||||||
"text!html/commentsPopoverContent.html",
|
"text!html/commentsPopoverContent.html",
|
||||||
"bootstrap"
|
"bootstrap"
|
||||||
], function($, _, utils, crel, rangy, Extension, commentsPopoverContentHTML) {
|
], function($, _, utils, storage, crel, rangy, Extension, commentsPopoverContentHTML) {
|
||||||
|
|
||||||
var comments = new Extension("comments", 'Comments');
|
var comments = new Extension("comments", 'Comments');
|
||||||
|
|
||||||
|
var commentTmpl = [
|
||||||
|
'<div class="comment-block">',
|
||||||
|
' <div class="comment-author"><%= author %></div>',
|
||||||
|
' <div class="comment-content"><%= content %></div>',
|
||||||
|
'</div>',
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
var eventMgr;
|
||||||
|
comments.onEventMgrCreated = function(eventMgrParam) {
|
||||||
|
eventMgr = eventMgrParam;
|
||||||
|
};
|
||||||
|
|
||||||
var offsetMap = {};
|
var offsetMap = {};
|
||||||
function setCommentEltCoordinates(commentElt, y) {
|
function setCommentEltCoordinates(commentElt, y) {
|
||||||
var lineIndex = Math.round(y / 10);
|
var lineIndex = Math.round(y / 10);
|
||||||
@ -33,32 +46,28 @@ define([
|
|||||||
setCommentEltCoordinates(newCommentElt, cursorY);
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
||||||
};
|
};
|
||||||
|
|
||||||
var fileDesc;
|
|
||||||
comments.onFileSelected = function(selectedFileDesc) {
|
|
||||||
fileDesc = selectedFileDesc;
|
|
||||||
refreshDiscussions();
|
|
||||||
};
|
|
||||||
|
|
||||||
var openedPopover;
|
|
||||||
comments.onLayoutResize = function() {
|
|
||||||
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
|
||||||
refreshDiscussions();
|
|
||||||
};
|
|
||||||
|
|
||||||
var refreshId;
|
var refreshId;
|
||||||
|
var currentFileDesc;
|
||||||
function refreshDiscussions() {
|
function refreshDiscussions() {
|
||||||
|
if(currentFileDesc === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var author = storage['author.name'];
|
||||||
clearTimeout(refreshId);
|
clearTimeout(refreshId);
|
||||||
commentEltList.forEach(function(commentElt) {
|
commentEltList.forEach(function(commentElt) {
|
||||||
marginElt.removeChild(commentElt);
|
marginElt.removeChild(commentElt);
|
||||||
});
|
});
|
||||||
commentEltList = [];
|
commentEltList = [];
|
||||||
offsetMap = {};
|
offsetMap = {};
|
||||||
var discussionList = _.map(fileDesc.discussionList, _.identity);
|
var discussionList = _.map(currentFileDesc.discussionList, _.identity);
|
||||||
function refreshOne() {
|
function refreshOne() {
|
||||||
|
var discussion;
|
||||||
|
do {
|
||||||
if(discussionList.length === 0) {
|
if(discussionList.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var discussion = discussionList.pop();
|
discussion = discussionList.pop();
|
||||||
|
} while(discussion.isRemoved);
|
||||||
var commentElt = crel('a', {
|
var commentElt = crel('a', {
|
||||||
class: 'icon-comment'
|
class: 'icon-comment'
|
||||||
});
|
});
|
||||||
@ -69,63 +78,115 @@ define([
|
|||||||
marginElt.appendChild(commentElt);
|
marginElt.appendChild(commentElt);
|
||||||
commentEltList.push(commentElt);
|
commentEltList.push(commentElt);
|
||||||
|
|
||||||
// Replace newCommentElt
|
// Move newCommentElt
|
||||||
setCommentEltCoordinates(newCommentElt, cursorY);
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
||||||
|
|
||||||
refreshId = setTimeout(refreshOne, 0);
|
// Apply class later for fade effect
|
||||||
|
commentElt.offsetWidth; // Refresh
|
||||||
|
var isReplied = _.last(discussion.commentList).author != author;
|
||||||
|
commentElt.className += isReplied ? ' replied' : ' added';
|
||||||
|
refreshId = setTimeout(refreshOne, 50);
|
||||||
}
|
}
|
||||||
refreshId = setTimeout(refreshOne, 50);
|
refreshId = setTimeout(refreshOne, 50);
|
||||||
}
|
}
|
||||||
|
var debouncedRefreshDiscussions = _.debounce(refreshDiscussions, 2000);
|
||||||
|
|
||||||
|
comments.onFileOpen = function(fileDesc) {
|
||||||
|
currentFileDesc = fileDesc;
|
||||||
|
refreshDiscussions();
|
||||||
|
};
|
||||||
|
|
||||||
|
comments.onContentChanged = function(fileDesc, content) {
|
||||||
|
currentFileDesc === fileDesc && debouncedRefreshDiscussions();
|
||||||
|
};
|
||||||
|
|
||||||
|
var currentContext;
|
||||||
|
function closeCurrentPopover() {
|
||||||
|
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
|
||||||
|
}
|
||||||
|
comments.onLayoutResize = function() {
|
||||||
|
closeCurrentPopover();
|
||||||
|
refreshDiscussions();
|
||||||
|
};
|
||||||
|
|
||||||
|
comments.onDiscussionCreated = function() {
|
||||||
|
refreshDiscussions();
|
||||||
|
};
|
||||||
|
|
||||||
|
comments.onDiscussionRemoved = function() {
|
||||||
|
refreshDiscussions();
|
||||||
|
};
|
||||||
|
|
||||||
|
comments.onCommentAdded = function() {
|
||||||
|
refreshDiscussions();
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDiscussionComments() {
|
||||||
|
return currentContext.discussion.commentList.map(function(comment) {
|
||||||
|
return _.template(commentTmpl, {
|
||||||
|
author: comment.author || 'Anonymous',
|
||||||
|
content: comment.content
|
||||||
|
});
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
comments.onReady = function() {
|
comments.onReady = function() {
|
||||||
var cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
var cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
||||||
normalize: false
|
normalize: false
|
||||||
});
|
});
|
||||||
var selectionRange;
|
var previousContent = '';
|
||||||
var rangyRange;
|
|
||||||
var currentDiscussion;
|
|
||||||
|
|
||||||
inputElt = document.getElementById('wmd-input');
|
inputElt = document.getElementById('wmd-input');
|
||||||
marginElt = document.querySelector('#wmd-input > .editor-margin');
|
marginElt = document.querySelector('#wmd-input > .editor-margin');
|
||||||
marginElt.appendChild(newCommentElt);
|
marginElt.appendChild(newCommentElt);
|
||||||
$(document.body).append(crel('div', {
|
$(document.body).append(crel('div', {
|
||||||
class: 'comments-popover'
|
class: 'comments-popover'
|
||||||
})).popover({
|
})).on('click', function(evt) {
|
||||||
|
// Close on click outside the popover
|
||||||
|
if(currentContext && currentContext.$commentElt[0] !== evt.target) {
|
||||||
|
closeCurrentPopover();
|
||||||
|
}
|
||||||
|
}).popover({
|
||||||
placement: 'auto top',
|
placement: 'auto top',
|
||||||
container: '.comments-popover',
|
container: '.comments-popover',
|
||||||
html: true,
|
html: true,
|
||||||
title: function() {
|
title: function() {
|
||||||
if(!currentDiscussion) {
|
if(!currentContext) {
|
||||||
return '...';
|
return true;
|
||||||
}
|
}
|
||||||
var titleLength = currentDiscussion.selectionEnd - currentDiscussion.selectionStart;
|
var titleLength = currentContext.discussion.selectionEnd - currentContext.discussion.selectionStart;
|
||||||
var title = inputElt.textContent.substr(currentDiscussion.selectionStart, titleLength > 20 ? 20 : titleLength);
|
var title = inputElt.textContent.substr(currentContext.discussion.selectionStart, titleLength > 20 ? 20 : titleLength);
|
||||||
if(titleLength > 20) {
|
if(titleLength > 20) {
|
||||||
title += '...';
|
title += '...';
|
||||||
}
|
}
|
||||||
return title.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ');
|
title = title.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ');
|
||||||
|
return '<a href="#" class="action-remove-discussion pull-right"><i class="icon-trash"></i></a>' + title;
|
||||||
},
|
},
|
||||||
content: function() {
|
content: function() {
|
||||||
var content = _.template(commentsPopoverContentHTML, {
|
var content = _.template(commentsPopoverContentHTML, {
|
||||||
|
commentList: getDiscussionComments()
|
||||||
});
|
});
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
selector: '#wmd-input > .editor-margin > .icon-comment'
|
selector: '#wmd-input > .editor-margin > .icon-comment'
|
||||||
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
$(evt.target).addClass('active');
|
closeCurrentPopover();
|
||||||
|
var context = {
|
||||||
|
$commentElt: $(evt.target).addClass('active')
|
||||||
|
};
|
||||||
|
currentContext = context;
|
||||||
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
||||||
|
|
||||||
// If it's an existing discussion
|
// If it's an existing discussion
|
||||||
if(evt.target.discussion) {
|
if(evt.target.discussion) {
|
||||||
currentDiscussion = evt.target.discussion;
|
context.discussion = evt.target.discussion;
|
||||||
selectionRange = inputElt.createRange(currentDiscussion.selectionStart, currentDiscussion.selectionEnd);
|
context.selectionRange = inputElt.createRange(context.discussion.selectionStart, context.discussion.selectionEnd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get selected text
|
// Get selected text
|
||||||
var inputSelection = inputElt.getSelectionStartEnd();
|
var selectionStart = inputElt.selectionStart;
|
||||||
var selectionStart = inputSelection.selectionStart;
|
var selectionEnd = inputElt.selectionEnd;
|
||||||
var selectionEnd = inputSelection.selectionEnd;
|
|
||||||
if(selectionStart === selectionEnd) {
|
if(selectionStart === selectionEnd) {
|
||||||
var after = inputElt.textContent.substring(selectionStart);
|
var after = inputElt.textContent.substring(selectionStart);
|
||||||
var match = /\S+/.exec(after);
|
var match = /\S+/.exec(after);
|
||||||
@ -139,26 +200,30 @@ define([
|
|||||||
selectionEnd += match.index + match[0].length;
|
selectionEnd += match.index + match[0].length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectionRange = inputElt.createRange(selectionStart, selectionEnd);
|
context.selectionRange = inputElt.createRange(selectionStart, selectionEnd);
|
||||||
currentDiscussion = {
|
context.discussion = {
|
||||||
selectionStart: selectionStart,
|
selectionStart: selectionStart,
|
||||||
selectionEnd: selectionEnd,
|
selectionEnd: selectionEnd,
|
||||||
commentList: []
|
commentList: []
|
||||||
};
|
};
|
||||||
}).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
|
|
||||||
// Move the popover in the margin
|
// Move the popover in the margin
|
||||||
var popoverElt = document.querySelector('.comments-popover .popover:last-child');
|
var context = currentContext;
|
||||||
var left = -10;
|
context.popoverElt = document.querySelector('.comments-popover .popover:last-child');
|
||||||
if(popoverElt.offsetWidth < marginElt.offsetWidth) {
|
var left = -5;
|
||||||
left = marginElt.offsetWidth - popoverElt.offsetWidth - 10;
|
if(context.popoverElt.offsetWidth < marginElt.offsetWidth - 5) {
|
||||||
|
left = marginElt.offsetWidth - 10 - context.popoverElt.offsetWidth;
|
||||||
}
|
}
|
||||||
popoverElt.style.left = left + 'px';
|
context.popoverElt.style.left = left + 'px';
|
||||||
popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(evt.target.style.right) - evt.target.offsetWidth / 2 - left) + 'px';
|
context.popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(evt.target.style.right) - evt.target.offsetWidth / 2 - left) + 'px';
|
||||||
|
|
||||||
var $textarea = $(popoverElt.querySelector('.input-comment-content'));
|
// Scroll to the bottom of the discussion
|
||||||
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
context.popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
||||||
$textarea.keydown(function(evt) {
|
|
||||||
|
context.$authorInputElt = $(context.popoverElt.querySelector('.input-comment-author')).val(storage['author.name']);
|
||||||
|
context.$contentInputElt = $(context.popoverElt.querySelector('.input-comment-content'));
|
||||||
|
var $addButton = $(context.popoverElt.querySelector('.action-add-comment'));
|
||||||
|
context.$contentInputElt.keydown(function(evt) {
|
||||||
// Enter key
|
// Enter key
|
||||||
switch(evt.which) {
|
switch(evt.which) {
|
||||||
case 13:
|
case 13:
|
||||||
@ -167,65 +232,106 @@ define([
|
|||||||
return;
|
return;
|
||||||
case 27:
|
case 27:
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
closeCurrentPopover();
|
||||||
inputElt.focus();
|
inputElt.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$addButton.click(function(evt) {
|
$addButton.click(function(evt) {
|
||||||
var author = utils.getInputTextValue(popoverElt.querySelector('.input-comment-author'));
|
var author = utils.getInputTextValue(context.$authorInputElt);
|
||||||
var content = utils.getInputTextValue($textarea, evt);
|
var content = utils.getInputTextValue(context.$contentInputElt, evt);
|
||||||
if(evt.isPropagationStopped()) {
|
if(evt.isPropagationStopped()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var discussionList = fileDesc.discussionList || {};
|
context.$contentInputElt.val('');
|
||||||
if(!currentDiscussion.discussionIndex) {
|
closeCurrentPopover();
|
||||||
|
|
||||||
|
var discussionList = currentFileDesc.discussionList || {};
|
||||||
|
var isNew = false;
|
||||||
|
if(!context.discussion.discussionIndex) {
|
||||||
|
isNew = true;
|
||||||
// Create discussion index
|
// Create discussion index
|
||||||
var discussionIndex;
|
var discussionIndex;
|
||||||
do {
|
do {
|
||||||
discussionIndex = utils.randomString();
|
discussionIndex = utils.randomString();
|
||||||
} while(_.has(discussionList, discussionIndex));
|
} while(_.has(discussionList, discussionIndex));
|
||||||
currentDiscussion.discussionIndex = discussionIndex;
|
context.discussion.discussionIndex = discussionIndex;
|
||||||
discussionList[discussionIndex] = currentDiscussion;
|
discussionList[discussionIndex] = context.discussion;
|
||||||
}
|
}
|
||||||
currentDiscussion.commentList.push({
|
context.discussion.commentList.push({
|
||||||
author: author,
|
author: author,
|
||||||
content: content
|
content: content
|
||||||
});
|
});
|
||||||
fileDesc.discussionList = discussionList;
|
currentFileDesc.discussionList = discussionList; // Write discussionList in localStorage
|
||||||
openedPopover.popover('toggle').popover('destroy');
|
isNew ?
|
||||||
refreshDiscussions();
|
eventMgr.onDiscussionCreated(currentFileDesc, context.discussion) :
|
||||||
|
eventMgr.onCommentAdded(currentFileDesc, context.discussion);
|
||||||
inputElt.focus();
|
inputElt.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var $removeButton = $(context.popoverElt.querySelector('.action-remove-discussion'));
|
||||||
|
if(evt.target.discussion) {
|
||||||
|
// If it's an existing discussion
|
||||||
|
var $removeCancelButton = $(context.popoverElt.querySelector('.action-remove-discussion-cancel'));
|
||||||
|
var $removeConfirmButton = $(context.popoverElt.querySelector('.action-remove-discussion-confirm'));
|
||||||
|
$removeButton.click(function() {
|
||||||
|
$(context.popoverElt.querySelector('.new-comment-block')).addClass('hide');
|
||||||
|
$(context.popoverElt.querySelector('.remove-discussion-confirm')).removeClass('hide');
|
||||||
|
context.popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
||||||
|
});
|
||||||
|
$removeCancelButton.click(function() {
|
||||||
|
$(context.popoverElt.querySelector('.new-comment-block')).removeClass('hide');
|
||||||
|
$(context.popoverElt.querySelector('.remove-discussion-confirm')).addClass('hide');
|
||||||
|
context.popoverElt.querySelector('.popover-content').scrollTop = 9999999;
|
||||||
|
context.$contentInputElt.focus();
|
||||||
|
});
|
||||||
|
$removeConfirmButton.click(function() {
|
||||||
|
closeCurrentPopover();
|
||||||
|
context.discussion.isRemoved = true;
|
||||||
|
delete context.discussion.selectionStart;
|
||||||
|
delete context.discussion.selectionEnd;
|
||||||
|
delete context.discussion.commentList;
|
||||||
|
currentFileDesc.discussionList = currentFileDesc.discussionList; // Write discussionList in localStorage
|
||||||
|
eventMgr.onDiscussionRemoved(currentFileDesc, context.discussion);
|
||||||
|
inputElt.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise hide the remove button
|
||||||
|
$removeButton.hide();
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent from closing on click inside the popover
|
// Prevent from closing on click inside the popover
|
||||||
$(popoverElt).on('click', function(evt) {
|
$(context.popoverElt).on('click', function(evt) {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
});
|
});
|
||||||
setTimeout(function() {
|
|
||||||
openedPopover = $(evt.target);
|
|
||||||
|
|
||||||
// Highlight selected text
|
// Highlight selected text
|
||||||
rangyRange = rangy.createRange();
|
context.rangyRange = rangy.createRange();
|
||||||
rangyRange.setStart(selectionRange.startContainer, selectionRange.startOffset);
|
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
|
||||||
rangyRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);
|
context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset);
|
||||||
cssApplier.applyToRange(rangyRange);
|
setTimeout(function() { // Need to delay this because it's not refreshed properly
|
||||||
|
if(currentContext === context) {
|
||||||
|
cssApplier.applyToRange(context.rangyRange);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
// Focus on textarea
|
// Focus on textarea
|
||||||
$textarea.focus();
|
context.$contentInputElt.focus().val(previousContent);
|
||||||
}, 10);
|
|
||||||
}).on('hide.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('hide.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
$(evt.target).removeClass('active');
|
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
|
// Remove highlight
|
||||||
rangyRange && cssApplier.undoToRange(rangyRange);
|
cssApplier.undoToRange(currentContext.rangyRange);
|
||||||
openedPopover = undefined;
|
currentContext = undefined;
|
||||||
rangyRange = undefined;
|
|
||||||
currentDiscussion = undefined;
|
|
||||||
}).on('click', function() {
|
|
||||||
// Close on click outside the popover
|
|
||||||
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="form-horizontal">
|
<div class="discussion-comment-list"><%= commentList %></div>
|
||||||
<div class="discussion-history"></div>
|
<div class="new-comment-block">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input class="form-control input-comment-author" placeholder="Your name"></input>
|
<input class="form-control input-comment-author" placeholder="Your name"></input>
|
||||||
<textarea class="form-control input-comment-content"></textarea>
|
<textarea class="form-control input-comment-content"></textarea>
|
||||||
@ -8,3 +8,11 @@
|
|||||||
<button class="btn btn-primary action-add-comment">Add</button>
|
<button class="btn btn-primary action-add-comment">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="remove-discussion-confirm hide">
|
||||||
|
<br/>
|
||||||
|
<blockquote>Remove this discussion, really?</blockquote>
|
||||||
|
<div class="form-group text-right">
|
||||||
|
<button class="btn btn-default action-remove-discussion-cancel">No</button>
|
||||||
|
<button class="btn btn-primary action-remove-discussion-confirm">Yes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -451,6 +451,18 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
function mergeDiscussion(localDiscussion, realtimeDiscussion, takeServer) {
|
function mergeDiscussion(localDiscussion, realtimeDiscussion, takeServer) {
|
||||||
|
if(localDiscussion.isRemoved === true) {
|
||||||
|
realtimeDiscussion.set('isRemoved');
|
||||||
|
realtimeDiscussion.delete('selectionStart');
|
||||||
|
realtimeDiscussion.delete('selectionEnd');
|
||||||
|
return realtimeDiscussion.delete('commentList');
|
||||||
|
}
|
||||||
|
if(realtimeDiscussion.get('isRemoved') === true) {
|
||||||
|
localDiscussion.isRemoved = true;
|
||||||
|
delete localDiscussion.selectionStart;
|
||||||
|
delete localDiscussion.selectionEnd;
|
||||||
|
return delete localDiscussion.commentList;
|
||||||
|
}
|
||||||
if(takeServer) {
|
if(takeServer) {
|
||||||
localDiscussion.selectionStart = realtimeDiscussion.get('selectionStart');
|
localDiscussion.selectionStart = realtimeDiscussion.get('selectionStart');
|
||||||
localDiscussion.selectionEnd = realtimeDiscussion.get('selectionEnd');
|
localDiscussion.selectionEnd = realtimeDiscussion.get('selectionEnd');
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
@popover-arrow-outer-color: @secondary-border-color;
|
@popover-arrow-outer-color: @secondary-border-color;
|
||||||
@popover-title-bg: @transparent;
|
@popover-title-bg: @transparent;
|
||||||
@alert-border-radius: 0;
|
@alert-border-radius: 0;
|
||||||
@label-warning-bg: #d90;
|
@label-warning-bg: #da0;
|
||||||
@label-danger-bg: #d00;
|
@label-danger-bg: #d00;
|
||||||
|
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#preview-contents {
|
#preview-contents {
|
||||||
padding: 30px;
|
padding: 35px;
|
||||||
margin: 0 auto 200px;
|
margin: 0 auto 200px;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
@ -362,7 +362,7 @@ a {
|
|||||||
|
|
||||||
.form-control.error {
|
.form-control.error {
|
||||||
border-color: @error-border;
|
border-color: @error-border;
|
||||||
.box-shadow(~"@{form-control-inset-shadow}, 0 0 8px rgba(255, 134, 97, 0.6)");
|
.box-shadow(~"@{form-control-inset-shadow}, 0 0 8px rgba(255, 0, 0, 0.6)");
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-block {
|
.help-block {
|
||||||
@ -773,7 +773,7 @@ a {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
right: 30px;
|
right: 35px;
|
||||||
.ui-layout-resizer-south-closed & {
|
.ui-layout-resizer-south-closed & {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@ -1060,22 +1060,28 @@ a {
|
|||||||
top: 0;
|
top: 0;
|
||||||
.icon-comment {
|
.icon-comment {
|
||||||
&.new {
|
&.new {
|
||||||
color: fade(@tertiary-color, 12%);
|
color: fade(@tertiary-color, 10%);
|
||||||
&:hover {
|
&:hover, &.active, &.active:hover {
|
||||||
color: fade(@tertiary-color, 35%) !important;
|
color: fade(@tertiary-color, 35%) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.replied {
|
&.replied {
|
||||||
color: fade(@label-danger-bg, 30%);
|
color: fade(@label-danger-bg, 35%);
|
||||||
&:hover, &.active, &.active:hover {
|
&:hover, &.active, &.active:hover {
|
||||||
color: fade(@label-danger-bg, 45%) !important;
|
color: fade(@label-danger-bg, 45%) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
position: absolute;
|
&.added {
|
||||||
color: fade(@label-warning-bg, 40%);
|
color: fade(@label-warning-bg, 40%);
|
||||||
cursor: pointer;
|
|
||||||
&:hover, &.active, &.active:hover {
|
&:hover, &.active, &.active:hover {
|
||||||
color: fade(@label-warning-bg, 60%) !important;
|
color: fade(@label-warning-bg, 60%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transition(~"color ease-in-out .25s");
|
||||||
|
position: absolute;
|
||||||
|
color: fade(#fff, 0%);
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover, &.active {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1399,7 +1405,7 @@ input[type="file"] {
|
|||||||
*********************/
|
*********************/
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
max-width: 240px;
|
max-width: 230px;
|
||||||
padding: 10px 20px 0;
|
padding: 10px 20px 0;
|
||||||
//.box-shadow(0 5px 30px rgba(0,0,0,.175));
|
//.box-shadow(0 5px 30px rgba(0,0,0,.175));
|
||||||
.popover-title {
|
.popover-title {
|
||||||
@ -1408,6 +1414,10 @@ input[type="file"] {
|
|||||||
padding: 5px 0 15px;
|
padding: 5px 0 15px;
|
||||||
border-bottom: 1px solid @hr-border;
|
border-bottom: 1px solid @hr-border;
|
||||||
line-height: @headings-line-height;
|
line-height: @headings-line-height;
|
||||||
|
.action-remove-discussion {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.icon-lock {
|
.icon-lock {
|
||||||
font-size: 38px;
|
font-size: 38px;
|
||||||
@ -1417,10 +1427,20 @@ input[type="file"] {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.popover-content {
|
.popover-content {
|
||||||
padding-bottom: 0;
|
padding: 10px 20px 0;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 230px;
|
||||||
|
margin: 0 -20px;
|
||||||
.btn {
|
.btn {
|
||||||
padding: 6px 11px;
|
padding: 6px 11px;
|
||||||
}
|
}
|
||||||
|
.comment-block {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.comment-author {
|
||||||
|
padding-left: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
.input-comment-author {
|
.input-comment-author {
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
Loading…
Reference in New Issue
Block a user