diff --git a/services/web/app/views/project/editor/review-panel.jade b/services/web/app/views/project/editor/review-panel.jade index aade8bde51..3e4962f676 100644 --- a/services/web/app/views/project/editor/review-panel.jade +++ b/services/web/app/views/project/editor/review-panel.jade @@ -1,5 +1,10 @@ #review-panel .review-panel-toolbar + resolved-comments-dropdown( + entries="reviewPanel.entries" + threads="reviewPanel.commentThreads" + docs="docs" + ) span.review-panel-toolbar-label(ng-click="editor.wantTrackChanges = true;", ng-if="editor.wantTrackChanges === false") Track Changes is strong off span.review-panel-toolbar-label(ng-click="editor.wantTrackChanges = false;", ng-if="editor.wantTrackChanges === true") Track Changes is @@ -30,10 +35,6 @@ entry="entry" threads="reviewPanel.commentThreads" on-resolve="resolveComment(entry, entry_id)" - on-unresolve="unresolveComment(entry, entry_id)" - on-show-thread="showThread(entry)" - on-hide-thread="hideThread(entry)" - on-delete="deleteComment(entry_id)" on-reply="submitReply(entry, entry_id);" on-indicator-click="toggleReviewPanel();" ) @@ -66,8 +67,6 @@ change-entry( entry="entry" user="users[entry.metadata.user_id]" - on-reject="rejectChange(entry.id);" - on-accept="acceptChange(entry.id);" on-indicator-click="toggleReviewPanel();" ng-click="gotoEntry(doc_id, entry)" ) @@ -76,9 +75,6 @@ comment-entry( entry="entry" users="users" - on-resolve="resolveComment(entry, entry.id)" - on-unresolve="unresolveComment(entry, entry.id)" - on-delete="deleteComment(entry.id)" on-reply="submitReply(entry, entry_id);" on-indicator-click="toggleReviewPanel();" ng-click="gotoEntry(doc_id, entry)" @@ -127,7 +123,7 @@ script(type='text/ng-template', id='changeEntryTemplate') span(ng-switch-when="delete") Deleted  del.rp-content-highlight {{ entry.content }} .rp-entry-metadata - span {{ entry.metadata.ts | date : 'MMM d, y h:mm a' }} •  + | {{ entry.metadata.ts | date : 'MMM d, y h:mm a' }} •  span.rp-entry-user(style="color: hsl({{ user.hue }}, 70%, 40%);") {{ user.name }} .rp-entry-actions a.rp-entry-button(href, ng-click="onReject();") @@ -139,55 +135,66 @@ script(type='text/ng-template', id='changeEntryTemplate') script(type='text/ng-template', id='commentEntryTemplate') div - .rp-entry-callout.rp-entry-callout-comment(ng-if="!threads[entry.thread_id].resolved") + .rp-entry-callout.rp-entry-callout-comment .rp-entry-indicator( ng-class="{ 'rp-entry-indicator-focused': entry.focused }" ng-click="onIndicatorClick();" ) i.fa.fa-comment .rp-entry.rp-entry-comment( - ng-class="{ 'rp-entry-focused': entry.focused, 'rp-entry-comment-resolved': threads[entry.thread_id].resolved}" + ng-class="{ 'rp-entry-focused': entry.focused }" ) - .rp-comment( - ng-if="!threads[entry.thread_id].resolved || entry.showWhenResolved" - ng-repeat="comment in threads[entry.thread_id].messages" - ng-class="comment.user.isSelf ? 'rp-comment-self' : '';" - ) - .rp-avatar( - ng-if="!comment.user.isSelf;" - style="background-color: hsl({{ comment.user.hue }}, 70%, 50%);" - ) {{ comment.user.avatar_text | limitTo : 1 }} - .rp-comment-body(style="color: hsl({{ comment.user.hue }}, 70%, 90%);") - p.rp-comment-content {{ comment.content }} - p.rp-comment-metadata + div + .rp-comment( + ng-repeat="comment in threads[entry.thread_id].messages track by comment.id" + ) + p.rp-comment-content + span.rp-entry-user( + style="color: hsl({{ comment.user.hue }}, 70%, 40%);" + ng-if="$first || comment.user.id !== threads[entry.thread_id].messages[$index - 1].user.id" + ) {{ comment.user.name }}:  + | {{ comment.content }} + .rp-entry-metadata | {{ comment.timestamp | date : 'MMM d, y h:mm a' }} - |  •  - span(style="color: hsl({{ comment.user.hue }}, 70%, 40%);") {{ comment.user.name }} - .rp-comment-reply(ng-if="!threads[entry.thread_id].resolved || entry.showWhenResolved") + .rp-comment-reply textarea.rp-comment-input( ng-model="entry.replyContent" ng-keypress="handleCommentReplyKeyPress($event);" stop-propagation="click" placeholder="{{ 'Hit \"Enter\" to reply' + (entry.resolved ? ' and re-open' : '') }}" ) - .rp-comment-resolved-description(ng-if="threads[entry.thread_id].resolved && !entry.showWhenResolved") - div - | Comment resolved by - span(style="color: hsl({{ threads[entry.thread_id].resolved_by_user.hue }}, 70%, 40%);") {{ threads[entry.thread_id].resolved_by_user.name }} - div {{ threads[entry.thread_id].resolved_at | date : 'MMM d, y h:mm a' }} .rp-entry-actions - a.rp-entry-button(href, ng-click="onResolve();", ng-if="!threads[entry.thread_id].resolved") - i.fa.fa-check - |  Mark as resolved - a.rp-entry-button(href, ng-click="onShowThread();", ng-if="threads[entry.thread_id].resolved && !entry.showWhenResolved") - |  Show - a.rp-entry-button(href, ng-click="onHideThread();", ng-if="threads[entry.thread_id].resolved && entry.showWhenResolved") - |  Hide - a.rp-entry-button(href, ng-click="onUnresolve();", ng-if="threads[entry.thread_id].resolved") - |  Re-open - a.rp-entry-button(href, ng-click="onDelete();", ng-if="threads[entry.thread_id].resolved") - |  Delete - + a.rp-entry-button(href, ng-click="onResolve();") + i.fa.fa-inbox + |  Resolve + a.rp-entry-button(href, ng-click="onReply();") + i.fa.fa-reply + |  Reply + +script(type='text/ng-template', id='resolvedCommentEntryTemplate') + .rp-entry.rp-entry-comment + div + .rp-comment-context + | Quoted text on  + .rp-comment-context-file {{ doc.doc.name }} + .rp-comment-context-quote {{ thread.content }} + .rp-comment( + ng-repeat="comment in thread.messages track by comment.id" + ) + p.rp-comment-content + span.rp-entry-user( + style="color: hsl({{ comment.user.hue }}, 70%, 40%);" + ng-if="$first || comment.user.id !== thread.messages[$index - 1].user.id" + ) {{ comment.user.name }}:  + | {{ comment.content }} + .rp-entry-metadata + | {{ comment.timestamp | date : 'MMM d, y h:mm a' }} + .rp-entry-actions + a.rp-entry-button(href, ng-click="onUnresolve();") + |  Re-open + a.rp-entry-button(href, ng-click="onDelete();") + |  Delete + script(type='text/ng-template', id='addCommentEntryTemplate') div @@ -223,4 +230,28 @@ script(type='text/ng-template', id='addCommentEntryTemplate') |  Cancel a.rp-entry-button(href, ng-click="submitNewComment()") i.fa.fa-comment - |  Comment \ No newline at end of file + |  Comment + +script(type='text/ng-template', id='resolvedCommentsDropdownTemplate') + .resolved-comments + .resolved-comments-backdrop( + ng-class="{ 'resolved-comments-backdrop-visible' : state.isOpen }" + ng-click="state.isOpen = !state.isOpen" + ) + a.resolved-comments-toggle( + href + ng-click="state.isOpen = !state.isOpen" + ) + i.fa.fa-inbox + .resolved-comments-dropdown( + ng-class="{ 'resolved-comments-dropdown-open' : state.isOpen }" + ) + div( + ng-repeat="doc in docs" + ) + resolved-comment-entry( + ng-repeat="thread in resolvedCommentsPerFile[doc.doc.id]" + thread="thread" + doc="doc" + ) + diff --git a/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee b/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee index cd1231c798..2ad425b737 100644 --- a/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee +++ b/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee @@ -5,6 +5,8 @@ define [ "ide/review-panel/directives/changeEntry" "ide/review-panel/directives/commentEntry" "ide/review-panel/directives/addCommentEntry" + "ide/review-panel/directives/resolvedCommentEntry" + "ide/review-panel/directives/resolvedCommentsDropdown" "ide/review-panel/filters/notEmpty" "ide/review-panel/filters/orderOverviewEntries" ], () -> \ No newline at end of file diff --git a/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee b/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee index 76798b1935..2ee7862379 100644 --- a/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee +++ b/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee @@ -10,10 +10,6 @@ define [ onResolve: "&" onReply: "&" onIndicatorClick: "&" - onDelete: "&" - onUnresolve: "&" - onShowThread: "&" - onHideThread: "&" link: (scope, element, attrs) -> scope.handleCommentReplyKeyPress = (ev) -> if ev.keyCode == 13 and !ev.shiftKey and !ev.ctrlKey and !ev.metaKey diff --git a/services/web/public/coffee/ide/review-panel/directives/resolvedCommentEntry.coffee b/services/web/public/coffee/ide/review-panel/directives/resolvedCommentEntry.coffee new file mode 100644 index 0000000000..e3691c1962 --- /dev/null +++ b/services/web/public/coffee/ide/review-panel/directives/resolvedCommentEntry.coffee @@ -0,0 +1,11 @@ +define [ + "base" +], (App) -> + App.directive "resolvedCommentEntry", () -> + restrict: "E" + templateUrl: "resolvedCommentEntryTemplate" + scope: + thread: "=" + doc: "=" + onReopen: "&" + onDelete: "&" \ No newline at end of file diff --git a/services/web/public/coffee/ide/review-panel/directives/resolvedCommentsDropdown.coffee b/services/web/public/coffee/ide/review-panel/directives/resolvedCommentsDropdown.coffee new file mode 100644 index 0000000000..5bdd85e71e --- /dev/null +++ b/services/web/public/coffee/ide/review-panel/directives/resolvedCommentsDropdown.coffee @@ -0,0 +1,33 @@ +define [ + "base" +], (App) -> + App.directive "resolvedCommentsDropdown", () -> + restrict: "E" + templateUrl: "resolvedCommentsDropdownTemplate" + scope: + entries : "=" + threads : "=" + docs : "=" + link: (scope, element, attrs) -> + scope.state = + isOpen: false + + scope.resolvedCommentsPerFile = {} + + filterResolvedComments = () -> + scope.resolvedCommentsPerFile = {} + + for fileId, fileEntries of scope.entries + scope.resolvedCommentsPerFile[fileId] = {} + for entryId, entry of fileEntries + if entry.type == "comment" and scope.threads[entry.thread_id].resolved? + scope.resolvedCommentsPerFile[fileId][entryId] = angular.copy scope.threads[entry.thread_id] + scope.resolvedCommentsPerFile[fileId][entryId].content = entry.content + + console.log scope.resolvedCommentsPerFile + + + scope.$watchCollection "entries", filterResolvedComments + scope.$watchCollection "threads", filterResolvedComments + + diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less index c2dd9f314c..48c37e0159 100644 --- a/services/web/public/stylesheets/app/editor/review-panel.less +++ b/services/web/public/stylesheets/app/editor/review-panel.less @@ -100,7 +100,6 @@ .rp-size-expanded & { display: flex; align-items: center; - justify-content: space-between; padding: 0 5px; } .rp-state-current-file & { @@ -120,6 +119,8 @@ .review-panel-toolbar-label { cursor: pointer; margin-right: 5px; + text-align: right; + flex-grow: 1; } .rp-entry-list { @@ -229,6 +230,10 @@ border-bottom: solid 1px @rp-border-grey; cursor: pointer; } + .resolved-comments-dropdown & { + position: static; + margin-bottom: 5px; + } border-left: solid @rp-entry-ribbon-width transparent; border-radius: 3px; @@ -291,8 +296,6 @@ .rp-entry-metadata { font-size: @rp-small-font-size; - .rp-state-overview & { - } } .rp-entry-user { font-weight: @rp-semibold-weight; @@ -328,69 +331,39 @@ } .rp-comment { - display: flex; - align-items: flex-start; - padding: 5px; + margin: 2px 5px; + padding-bottom: 3px; + line-height: 1.4; + border-bottom: solid 1px @rp-border-grey; + + &:last-child { + margin-bottom: 2px; + border-bottom-width: 0; + } .rp-state-overview & { - padding: 3px 0; - line-height: 1.2; + padding: 0; } } - .rp-comment-body { - position: relative; - background-color: currentColor; - flex-grow: 1; - padding: 2px 5px; - margin-left: @rp-entry-arrow-width; - border-radius: 3px; - - .rp-comment-self & { - margin-left: 0; - margin-right: @rp-entry-arrow-width; - } - - &::after { - .triangle(left, @rp-entry-arrow-width, @rp-entry-arrow-width * 1.5, inherit); - top: (@review-off-width / 2) - @rp-entry-arrow-width; - left: -@rp-entry-arrow-width; - content: ''; - - .rp-comment-self & { - .triangle(right, @rp-entry-arrow-width, @rp-entry-arrow-width * 1.5, inherit); - right: -@rp-entry-arrow-width; - left: auto; - } - - } - } - .rp-comment-content { - margin: 0; - color: @rp-type-darkgrey; - } - - .rp-comment-metadata { - color: @rp-type-blue; - font-size: @rp-small-font-size; - margin: 0; - } - - .rp-comment-reply { - padding: 0 5px; - - .rp-state-overview & { - padding: 3px 0 0; - } + .rp-comment-content { + margin: 0; + color: @rp-type-darkgrey; } - .rp-comment-resolved-description { - padding: 5px; - - .rp-state-overview & { - padding: 0px; - } + .rp-comment-metadata { + color: @rp-type-blue; + font-size: @rp-small-font-size; + margin: 0; } + .rp-comment-reply { + padding: 0 5px; + + .rp-state-overview & { + padding: 3px 0 0; + } + } + .rp-add-comment-btn { .rp-button(); display: block; @@ -665,3 +638,45 @@ } } + +.resolved-comments-toggle { + font-size: @rp-icon-large-size; + color: lighten(@rp-type-blue, 25%); + border: solid 1px @rp-border-grey; + border-radius: 3px; + padding: 0 4px; + + &:hover, + &:focus { + text-decoration: none; + color: @rp-type-blue; + } +} + +.resolved-comments-backdrop { + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0, 0, 0, .5); + + &-visible { + display: block; + } +} + +.resolved-comments-dropdown { + display: none; + position: absolute; + width: 300px; + background-color: @rp-bg-blue; + text-align: left; + padding: 5px; + border-radius: 3px; + + &-open { + display: block; + } +}