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,18 +2,31 @@ 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); | ||||||
|         var top = (y - 8) + 'px'; |         var top = (y - 8) + 'px'; | ||||||
|         var right = ((offsetMap[lineIndex] || 0) * 25 + 10) + 'px'; |         var right = ((offsetMap[lineIndex] || 0) * 25 + 10) + 'px'; | ||||||
|         commentElt.style.top = top; |         commentElt.style.top = top; | ||||||
| @ -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
	 benweet
						benweet