// Copyright (C) 2016 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. (function() { 'use strict'; var NEWLINE_PATTERN = /\n/g; Polymer({ is: 'gr-diff-comment-thread', /** * Fired when the thread should be discarded. * * @event thread-discard */ properties: { changeNum: String, comments: { type: Array, value: function() { return []; }, }, keyEventTarget: { type: Object, value: function() { return document.body; }, }, patchNum: String, path: String, projectConfig: Object, side: { type: String, value: 'REVISION', }, _showActions: Boolean, _orderedComments: Array, }, behaviors: [ Gerrit.KeyboardShortcutBehavior, ], listeners: { 'comment-update': '_handleCommentUpdate', }, observers: [ '_commentsChanged(comments.splices)', ], keyBindings: { 'e shift+e': '_handleEKey', }, attached: function() { this._getLoggedIn().then(function(loggedIn) { this._showActions = loggedIn; }.bind(this)); }, addOrEditDraft: function(opt_lineNum) { var lastComment = this.comments[this.comments.length - 1]; if (lastComment && lastComment.__draft) { var commentEl = this._commentElWithDraftID( lastComment.id || lastComment.__draftID); commentEl.editing = true; } else { this.addDraft(opt_lineNum); } }, addDraft: function(opt_lineNum, opt_range) { var draft = this._newDraft(opt_lineNum, opt_range); draft.__editing = true; this.push('comments', draft); }, _getLoggedIn: function() { return this.$.restAPI.getLoggedIn(); }, _commentsChanged: function(changeRecord) { this._orderedComments = this._sortedComments(this.comments); }, _handleEKey: function(e) { if (this.shouldSuppressKeyboardShortcut(e)) { return; } // Don’t preventDefault in this case because it will render the event // useless for other handlers (other gr-diff-comment-thread elements). if (e.detail.keyboardEvent.shiftKey) { this._expandCollapseComments(true); } else { if (this.modifierPressed(e)) { return; } this._expandCollapseComments(false); } }, _expandCollapseComments: function(actionIsCollapse) { var comments = Polymer.dom(this.root).querySelectorAll('gr-diff-comment'); comments.forEach(function(comment) { comment.collapsed = actionIsCollapse; }); }, _sortedComments: function(comments) { return comments.slice().sort(function(c1, c2) { var c1Date = c1.__date || util.parseDate(c1.updated); var c2Date = c2.__date || util.parseDate(c2.updated); var dateCompare = c1Date - c2Date; // If same date, fall back to sorting by id. return dateCompare ? dateCompare : c1.id.localeCompare(c2.id); }); }, _createReplyComment: function(parent, content, opt_isEditing) { var reply = this._newReply( this._orderedComments[this._orderedComments.length - 1].id, parent.line, content); // If there is currently a comment in an editing state, add an attribute // so that the gr-diff-comment knows not to populate the draft text. for (var i = 0; i < this.comments.length; i++) { if (this.comments[i].__editing) { reply.__otherEditing = true; break; } } if (opt_isEditing) { reply.__editing = true; } this.push('comments', reply); if (!opt_isEditing) { // Allow the reply to render in the dom-repeat. this.async(function() { var commentEl = this._commentElWithDraftID(reply.__draftID); commentEl.save(); }, 1); } }, _handleCommentReply: function(e) { var comment = e.detail.comment; var quoteStr; if (e.detail.quote) { var msg = comment.message; quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n'; } this._createReplyComment(comment, quoteStr, true); }, _handleCommentAck: function(e) { var comment = e.detail.comment; this._createReplyComment(comment, 'Ack'); }, _handleCommentDone: function(e) { var comment = e.detail.comment; this._createReplyComment(comment, 'Done'); }, _handleCommentFix: function(e) { var comment = e.detail.comment; var msg = comment.message; var quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n'; var response = quoteStr + 'Please Fix'; this._createReplyComment(comment, response); }, _commentElWithDraftID: function(id) { var els = Polymer.dom(this.root).querySelectorAll('gr-diff-comment'); for (var i = 0; i < els.length; i++) { if (els[i].comment.id === id || els[i].comment.__draftID === id) { return els[i]; } } return null; }, _newReply: function(inReplyTo, opt_lineNum, opt_message) { var d = this._newDraft(opt_lineNum); d.in_reply_to = inReplyTo; if (opt_message != null) { d.message = opt_message; } return d; }, _newDraft: function(opt_lineNum, opt_range) { var d = { __draft: true, __draftID: Math.random().toString(36), __date: new Date(), path: this.path, side: this.side, }; if (opt_lineNum) { d.line = opt_lineNum; } if (opt_range) { d.range = { start_line: opt_range.startLine, start_character: opt_range.startChar, end_line: opt_range.endLine, end_character: opt_range.endChar, }; } return d; }, _handleCommentDiscard: function(e) { var diffCommentEl = Polymer.dom(e).rootTarget; var comment = diffCommentEl.comment; var idx = this._indexOf(comment, this.comments); if (idx == -1) { throw Error('Cannot find comment ' + JSON.stringify(diffCommentEl.comment)); } this.splice('comments', idx, 1); if (this.comments.length == 0) { this.fire('thread-discard', {lastComment: comment}); } // Check to see if there are any other open comments getting edited and // set the local storage value to its message value. for (var i = 0; i < this.comments.length; i++) { if (this.comments[i].__editing) { var commentLocation = { changeNum: this.changeNum, patchNum: this.patchNum, path: this.comments[i].path, line: this.comments[i].line, }; return this.$.storage.setDraftComment(commentLocation, this.comments[i].message); } } }, _handleCommentUpdate: function(e) { var comment = e.detail.comment; var index = this._indexOf(comment, this.comments); if (index === -1) { // This should never happen: comment belongs to another thread. console.error('Comment update for another comment thread.'); return; } this.comments[index] = comment; }, _indexOf: function(comment, arr) { for (var i = 0; i < arr.length; i++) { var c = arr[i]; if ((c.__draftID != null && c.__draftID == comment.__draftID) || (c.id != null && c.id == comment.id)) { return i; } } return -1; }, }); })();