2016-03-04 17:48:22 -05:00
|
|
|
|
// 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';
|
|
|
|
|
|
2016-05-18 12:37:53 -07:00
|
|
|
|
var STORAGE_DEBOUNCE_INTERVAL = 400;
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
Polymer({
|
|
|
|
|
is: 'gr-diff-comment',
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fired when the Reply action is triggered.
|
|
|
|
|
*
|
|
|
|
|
* @event reply
|
|
|
|
|
*/
|
|
|
|
|
|
2016-11-29 16:46:12 -08:00
|
|
|
|
/**
|
|
|
|
|
* Fired when the Ack action is triggered.
|
|
|
|
|
*
|
|
|
|
|
* @event ack
|
|
|
|
|
*/
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
/**
|
|
|
|
|
* Fired when the Done action is triggered.
|
|
|
|
|
*
|
|
|
|
|
* @event done
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fired when this comment is discarded.
|
|
|
|
|
*
|
2016-03-25 11:29:36 -04:00
|
|
|
|
* @event comment-discard
|
2016-03-04 17:48:22 -05:00
|
|
|
|
*/
|
|
|
|
|
|
2016-05-19 17:24:45 -07:00
|
|
|
|
/**
|
|
|
|
|
* Fired when this comment is saved.
|
|
|
|
|
*
|
|
|
|
|
* @event comment-save
|
|
|
|
|
*/
|
|
|
|
|
|
2016-05-23 15:24:05 -07:00
|
|
|
|
/**
|
|
|
|
|
* Fired when this comment is updated.
|
|
|
|
|
*
|
|
|
|
|
* @event comment-update
|
|
|
|
|
*/
|
|
|
|
|
|
2016-06-09 16:08:04 -07:00
|
|
|
|
/**
|
|
|
|
|
* @event comment-mouse-over
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @event comment-mouse-out
|
|
|
|
|
*/
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
properties: {
|
|
|
|
|
changeNum: String,
|
|
|
|
|
comment: {
|
|
|
|
|
type: Object,
|
|
|
|
|
notify: true,
|
2016-05-23 15:24:05 -07:00
|
|
|
|
observer: '_commentChanged',
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
disabled: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
value: false,
|
|
|
|
|
reflectToAttribute: true,
|
|
|
|
|
},
|
|
|
|
|
draft: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
value: false,
|
|
|
|
|
observer: '_draftChanged',
|
|
|
|
|
},
|
|
|
|
|
editing: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
value: false,
|
|
|
|
|
observer: '_editingChanged',
|
|
|
|
|
},
|
|
|
|
|
patchNum: String,
|
|
|
|
|
showActions: Boolean,
|
2016-10-10 17:31:56 -07:00
|
|
|
|
collapsed: {
|
2016-10-06 10:18:32 -07:00
|
|
|
|
type: Boolean,
|
|
|
|
|
value: true,
|
|
|
|
|
observer: '_toggleCollapseClass',
|
|
|
|
|
},
|
2016-03-04 17:48:22 -05:00
|
|
|
|
projectConfig: Object,
|
|
|
|
|
|
|
|
|
|
_xhrPromise: Object, // Used for testing.
|
2016-05-23 13:53:10 -07:00
|
|
|
|
_messageText: {
|
2016-05-18 12:37:53 -07:00
|
|
|
|
type: String,
|
2016-05-23 19:03:11 -04:00
|
|
|
|
value: '',
|
2016-05-23 13:53:10 -07:00
|
|
|
|
observer: '_messageTextChanged',
|
2016-05-18 12:37:53 -07:00
|
|
|
|
},
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
|
2016-05-23 19:03:11 -04:00
|
|
|
|
observers: [
|
|
|
|
|
'_commentMessageChanged(comment.message)',
|
|
|
|
|
'_loadLocalDraft(changeNum, patchNum, comment)',
|
|
|
|
|
],
|
2016-03-04 17:48:22 -05:00
|
|
|
|
|
2016-10-06 10:18:32 -07:00
|
|
|
|
attached: function() {
|
|
|
|
|
if (this.editing) {
|
2016-10-10 17:31:56 -07:00
|
|
|
|
this.collapsed = false;
|
2016-10-06 10:18:32 -07:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2016-05-23 15:24:05 -07:00
|
|
|
|
detached: function() {
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this.cancelDebouncer('fire-update');
|
2016-05-23 15:24:05 -07:00
|
|
|
|
},
|
|
|
|
|
|
2016-10-06 10:18:32 -07:00
|
|
|
|
_computeShowHideText: function(collapsed) {
|
|
|
|
|
return collapsed ? '◀' : '▼';
|
|
|
|
|
},
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
save: function() {
|
2016-05-23 13:53:10 -07:00
|
|
|
|
this.comment.message = this._messageText;
|
2016-03-04 17:48:22 -05:00
|
|
|
|
this.disabled = true;
|
2016-05-18 12:37:53 -07:00
|
|
|
|
|
2016-05-23 13:53:10 -07:00
|
|
|
|
this.$.storage.eraseDraftComment({
|
|
|
|
|
changeNum: this.changeNum,
|
|
|
|
|
patchNum: this.patchNum,
|
|
|
|
|
path: this.comment.path,
|
|
|
|
|
line: this.comment.line,
|
|
|
|
|
});
|
2016-05-18 12:37:53 -07:00
|
|
|
|
|
2016-05-03 15:14:57 -04:00
|
|
|
|
this._xhrPromise = this._saveDraft(this.comment).then(function(response) {
|
2016-03-04 17:48:22 -05:00
|
|
|
|
this.disabled = false;
|
2016-05-02 15:59:59 -04:00
|
|
|
|
if (!response.ok) { return response; }
|
|
|
|
|
|
2016-05-03 15:14:57 -04:00
|
|
|
|
return this.$.restAPI.getResponseObject(response).then(function(obj) {
|
|
|
|
|
var comment = obj;
|
|
|
|
|
comment.__draft = true;
|
|
|
|
|
// Maintain the ephemeral draft ID for identification by other
|
|
|
|
|
// elements.
|
|
|
|
|
if (this.comment.__draftID) {
|
|
|
|
|
comment.__draftID = this.comment.__draftID;
|
|
|
|
|
}
|
|
|
|
|
this.comment = comment;
|
|
|
|
|
this.editing = false;
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this._fireSave();
|
2016-05-03 15:14:57 -04:00
|
|
|
|
return obj;
|
|
|
|
|
}.bind(this));
|
2016-03-04 17:48:22 -05:00
|
|
|
|
}.bind(this)).catch(function(err) {
|
|
|
|
|
this.disabled = false;
|
2016-05-02 15:59:59 -04:00
|
|
|
|
throw err;
|
2016-03-04 17:48:22 -05:00
|
|
|
|
}.bind(this));
|
|
|
|
|
},
|
|
|
|
|
|
2016-05-23 15:24:05 -07:00
|
|
|
|
_commentChanged: function(comment) {
|
|
|
|
|
this.editing = !!comment.__editing;
|
2016-06-09 16:08:04 -07:00
|
|
|
|
if (this.editing) { // It's a new draft/reply, notify.
|
|
|
|
|
this._fireUpdate();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_getEventPayload: function(opt_mixin) {
|
|
|
|
|
var payload = {
|
|
|
|
|
comment: this.comment,
|
|
|
|
|
patchNum: this.patchNum,
|
|
|
|
|
};
|
|
|
|
|
for (var k in opt_mixin) {
|
|
|
|
|
payload[k] = opt_mixin[k];
|
|
|
|
|
}
|
|
|
|
|
return payload;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_fireSave: function() {
|
|
|
|
|
this.fire('comment-save', this._getEventPayload());
|
2016-05-23 15:24:05 -07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_fireUpdate: function() {
|
|
|
|
|
this.debounce('fire-update', function() {
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this.fire('comment-update', this._getEventPayload());
|
2016-07-14 12:31:09 -07:00
|
|
|
|
});
|
2016-05-23 15:24:05 -07:00
|
|
|
|
},
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
_draftChanged: function(draft) {
|
|
|
|
|
this.$.container.classList.toggle('draft', draft);
|
|
|
|
|
},
|
|
|
|
|
|
2016-06-09 16:08:04 -07:00
|
|
|
|
_editingChanged: function(editing, previousValue) {
|
2016-03-04 17:48:22 -05:00
|
|
|
|
this.$.container.classList.toggle('editing', editing);
|
|
|
|
|
if (editing) {
|
|
|
|
|
var textarea = this.$.editTextarea.textarea;
|
|
|
|
|
// Put the cursor at the end always.
|
|
|
|
|
textarea.selectionStart = textarea.value.length;
|
|
|
|
|
textarea.selectionEnd = textarea.selectionStart;
|
|
|
|
|
this.async(function() {
|
|
|
|
|
textarea.focus();
|
|
|
|
|
}.bind(this));
|
|
|
|
|
}
|
|
|
|
|
if (this.comment && this.comment.id) {
|
|
|
|
|
this.$$('.cancel').hidden = !editing;
|
|
|
|
|
}
|
2016-05-23 15:24:05 -07:00
|
|
|
|
if (this.comment) {
|
|
|
|
|
this.comment.__editing = this.editing;
|
|
|
|
|
}
|
2016-06-09 16:08:04 -07:00
|
|
|
|
if (editing != !!previousValue) {
|
|
|
|
|
// To prevent event firing on comment creation.
|
|
|
|
|
this._fireUpdate();
|
|
|
|
|
}
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_computeLinkToComment: function(comment) {
|
|
|
|
|
return '#' + comment.line;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_computeSaveDisabled: function(draft) {
|
|
|
|
|
return draft == null || draft.trim() == '';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_handleTextareaKeydown: function(e) {
|
2016-08-23 12:07:04 -07:00
|
|
|
|
switch (e.keyCode) {
|
|
|
|
|
case 27: // 'esc'
|
2016-11-02 14:18:48 -07:00
|
|
|
|
if (this._messageText.length === 0) {
|
|
|
|
|
this._handleCancel(e);
|
|
|
|
|
}
|
2016-08-23 12:07:04 -07:00
|
|
|
|
break;
|
|
|
|
|
case 83: // 's'
|
|
|
|
|
if (e.ctrlKey) {
|
|
|
|
|
this._handleSave(e);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2016-03-04 17:48:22 -05:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2016-10-06 10:18:32 -07:00
|
|
|
|
_handleToggleCollapsed: function() {
|
2016-10-10 17:31:56 -07:00
|
|
|
|
this.collapsed = !this.collapsed;
|
2016-10-06 10:18:32 -07:00
|
|
|
|
},
|
|
|
|
|
|
2016-10-10 17:31:56 -07:00
|
|
|
|
_toggleCollapseClass: function(collapsed) {
|
|
|
|
|
if (collapsed) {
|
2016-10-06 10:18:32 -07:00
|
|
|
|
this.$.container.classList.add('collapsed');
|
|
|
|
|
} else {
|
|
|
|
|
this.$.container.classList.remove('collapsed');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2016-05-23 19:03:11 -04:00
|
|
|
|
_commentMessageChanged: function(message) {
|
|
|
|
|
this._messageText = message || '';
|
|
|
|
|
},
|
|
|
|
|
|
2016-05-23 13:53:10 -07:00
|
|
|
|
_messageTextChanged: function(newValue, oldValue) {
|
2016-05-23 19:03:11 -04:00
|
|
|
|
if (!this.comment || (this.comment && this.comment.id)) { return; }
|
2016-05-18 12:37:53 -07:00
|
|
|
|
|
|
|
|
|
this.debounce('store', function() {
|
2016-05-23 13:53:10 -07:00
|
|
|
|
var message = this._messageText;
|
|
|
|
|
|
|
|
|
|
var commentLocation = {
|
|
|
|
|
changeNum: this.changeNum,
|
|
|
|
|
patchNum: this.patchNum,
|
|
|
|
|
path: this.comment.path,
|
|
|
|
|
line: this.comment.line,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if ((!this._messageText || !this._messageText.length) && oldValue) {
|
|
|
|
|
// If the draft has been modified to be empty, then erase the storage
|
|
|
|
|
// entry.
|
|
|
|
|
this.$.storage.eraseDraftComment(commentLocation);
|
|
|
|
|
} else {
|
|
|
|
|
this.$.storage.setDraftComment(commentLocation, message);
|
2016-05-18 12:37:53 -07:00
|
|
|
|
}
|
2016-05-23 15:24:05 -07:00
|
|
|
|
this._fireUpdate();
|
2016-05-23 13:53:10 -07:00
|
|
|
|
}, STORAGE_DEBOUNCE_INTERVAL);
|
2016-05-18 12:37:53 -07:00
|
|
|
|
},
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
_handleLinkTap: function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var hash = this._computeLinkToComment(this.comment);
|
|
|
|
|
// Don't add the hash to the window history if it's already there.
|
|
|
|
|
// Otherwise you mess up expected back button behavior.
|
|
|
|
|
if (window.location.hash == hash) { return; }
|
|
|
|
|
// Change the URL but don’t trigger a nav event. Otherwise it will
|
|
|
|
|
// reload the page.
|
|
|
|
|
page.show(window.location.pathname + hash, null, false);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_handleReply: function(e) {
|
2016-11-15 17:01:15 -08:00
|
|
|
|
e.preventDefault();
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this.fire('reply', this._getEventPayload(), {bubbles: false});
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_handleQuote: function(e) {
|
2016-11-15 17:01:15 -08:00
|
|
|
|
e.preventDefault();
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this.fire(
|
|
|
|
|
'reply', this._getEventPayload({quote: true}), {bubbles: false});
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
|
2016-11-29 16:46:12 -08:00
|
|
|
|
_handleAck: function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
this.fire('ack', this._getEventPayload(), {bubbles: false});
|
|
|
|
|
},
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
_handleDone: function(e) {
|
2016-11-15 17:01:15 -08:00
|
|
|
|
e.preventDefault();
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this.fire('done', this._getEventPayload(), {bubbles: false});
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_handleEdit: function(e) {
|
2016-11-15 17:01:15 -08:00
|
|
|
|
e.preventDefault();
|
2016-05-23 13:53:10 -07:00
|
|
|
|
this._messageText = this.comment.message;
|
2016-03-04 17:48:22 -05:00
|
|
|
|
this.editing = true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_handleSave: function(e) {
|
2016-11-15 17:01:15 -08:00
|
|
|
|
e.preventDefault();
|
2016-03-04 17:48:22 -05:00
|
|
|
|
this.save();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_handleCancel: function(e) {
|
2016-11-15 17:01:15 -08:00
|
|
|
|
e.preventDefault();
|
2016-03-04 17:48:22 -05:00
|
|
|
|
if (this.comment.message == null || this.comment.message.length == 0) {
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this._fireDiscard();
|
2016-03-04 17:48:22 -05:00
|
|
|
|
return;
|
|
|
|
|
}
|
2016-05-23 13:53:10 -07:00
|
|
|
|
this._messageText = this.comment.message;
|
2016-03-04 17:48:22 -05:00
|
|
|
|
this.editing = false;
|
|
|
|
|
},
|
|
|
|
|
|
2016-06-09 16:08:04 -07:00
|
|
|
|
_fireDiscard: function() {
|
2016-08-05 15:05:50 -07:00
|
|
|
|
this.cancelDebouncer('fire-update');
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this.fire('comment-discard', this._getEventPayload());
|
|
|
|
|
},
|
|
|
|
|
|
2016-03-04 17:48:22 -05:00
|
|
|
|
_handleDiscard: function(e) {
|
2016-11-15 17:01:15 -08:00
|
|
|
|
e.preventDefault();
|
2016-03-04 17:48:22 -05:00
|
|
|
|
if (!this.comment.__draft) {
|
|
|
|
|
throw Error('Cannot discard a non-draft comment.');
|
|
|
|
|
}
|
2016-05-23 19:03:11 -04:00
|
|
|
|
this.editing = false;
|
2016-03-04 17:48:22 -05:00
|
|
|
|
this.disabled = true;
|
2016-05-03 15:14:57 -04:00
|
|
|
|
if (!this.comment.id) {
|
2016-05-23 19:03:11 -04:00
|
|
|
|
this.disabled = false;
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this._fireDiscard();
|
2016-03-04 17:48:22 -05:00
|
|
|
|
return;
|
|
|
|
|
}
|
2016-05-03 15:14:57 -04:00
|
|
|
|
|
2016-05-23 15:24:05 -07:00
|
|
|
|
this._xhrPromise = this._deleteDraft(this.comment).then(
|
|
|
|
|
function(response) {
|
|
|
|
|
this.disabled = false;
|
|
|
|
|
if (!response.ok) { return response; }
|
2016-05-02 15:59:59 -04:00
|
|
|
|
|
2016-06-09 16:08:04 -07:00
|
|
|
|
this._fireDiscard();
|
2016-05-23 15:24:05 -07:00
|
|
|
|
}.bind(this)).catch(function(err) {
|
|
|
|
|
this.disabled = false;
|
|
|
|
|
throw err;
|
|
|
|
|
}.bind(this));
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
|
2016-05-03 15:14:57 -04:00
|
|
|
|
_saveDraft: function(draft) {
|
|
|
|
|
return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft);
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
|
|
|
|
|
2016-05-03 15:14:57 -04:00
|
|
|
|
_deleteDraft: function(draft) {
|
|
|
|
|
return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
|
|
|
|
|
draft);
|
2016-03-04 17:48:22 -05:00
|
|
|
|
},
|
2016-05-18 12:37:53 -07:00
|
|
|
|
|
2016-05-23 19:03:11 -04:00
|
|
|
|
_loadLocalDraft: function(changeNum, patchNum, comment) {
|
|
|
|
|
// Only apply local drafts to comments that haven't been saved
|
|
|
|
|
// remotely, and haven't been given a default message already.
|
|
|
|
|
if (!comment || comment.id || comment.message) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-05-18 12:37:53 -07:00
|
|
|
|
|
2016-05-23 19:03:11 -04:00
|
|
|
|
var draft = this.$.storage.getDraftComment({
|
|
|
|
|
changeNum: changeNum,
|
|
|
|
|
patchNum: patchNum,
|
|
|
|
|
path: comment.path,
|
|
|
|
|
line: comment.line,
|
|
|
|
|
});
|
2016-05-18 12:37:53 -07:00
|
|
|
|
|
2016-05-23 19:03:11 -04:00
|
|
|
|
if (draft) {
|
|
|
|
|
this.set('comment.message', draft.message);
|
|
|
|
|
}
|
2016-05-18 12:37:53 -07:00
|
|
|
|
},
|
2016-06-09 16:08:04 -07:00
|
|
|
|
|
|
|
|
|
_handleMouseEnter: function(e) {
|
|
|
|
|
this.fire('comment-mouse-over', this._getEventPayload());
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_handleMouseLeave: function(e) {
|
|
|
|
|
this.fire('comment-mouse-out', this._getEventPayload());
|
|
|
|
|
},
|
2016-03-04 17:48:22 -05:00
|
|
|
|
});
|
|
|
|
|
})();
|