Convert gr-comment to typescript
The change converts the following files to typescript:
* elements/shared/gr-comment/gr-comment.ts
Change-Id: I34975acf43537d9317462eb721d64a82fd21b441
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
index 179caaa..0fa8e31 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
@@ -789,6 +789,9 @@
suite('_recomputeComments', () => {
setup(() => {
+ element._changeNum = '1';
+ element._change = {_number: '1'};
+ flushAsynchronousOperations();
// Fake computeDraftCount as its required for ChangeComments,
// see gr-comment-api#reloadDrafts.
sinon.stub(element.$.commentAPI, 'reloadDrafts')
@@ -927,7 +930,9 @@
rev4: {_number: 4, commit: {parents: []}},
},
current_revision: 'rev4',
+ _number: '1',
};
+ element._changeNum = '1';
element._commentThreads = THREADS;
const paperTabs = element.shadowRoot.querySelector('#primaryTabs');
MockInteractions.tap(paperTabs.querySelectorAll('paper-tab')[3]);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 2828cf1..d240f2c 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -14,30 +14,45 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
-import '../../../styles/shared-styles.js';
-import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
-import '../../plugins/gr-endpoint-param/gr-endpoint-param.js';
-import '../gr-button/gr-button.js';
-import '../gr-dialog/gr-dialog.js';
-import '../gr-date-formatter/gr-date-formatter.js';
-import '../gr-formatted-text/gr-formatted-text.js';
-import '../gr-icons/gr-icons.js';
-import '../gr-overlay/gr-overlay.js';
-import '../gr-rest-api-interface/gr-rest-api-interface.js';
-import '../gr-storage/gr-storage.js';
-import '../gr-textarea/gr-textarea.js';
-import '../gr-tooltip-content/gr-tooltip-content.js';
-import '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js';
-import '../gr-account-label/gr-account-label.js';
-import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {htmlTemplate} from './gr-comment_html.js';
-import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
-import {getRootElement} from '../../../scripts/rootElement.js';
-import {appContext} from '../../../services/app-context.js';
+import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../../styles/shared-styles';
+import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../../plugins/gr-endpoint-param/gr-endpoint-param';
+import '../gr-button/gr-button';
+import '../gr-dialog/gr-dialog';
+import '../gr-date-formatter/gr-date-formatter';
+import '../gr-formatted-text/gr-formatted-text';
+import '../gr-icons/gr-icons';
+import '../gr-overlay/gr-overlay';
+import '../gr-rest-api-interface/gr-rest-api-interface';
+import '../gr-storage/gr-storage';
+import '../gr-textarea/gr-textarea';
+import '../gr-tooltip-content/gr-tooltip-content';
+import '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog';
+import '../gr-account-label/gr-account-label';
+import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {htmlTemplate} from './gr-comment_html';
+import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import {getRootElement} from '../../../scripts/rootElement';
+import {appContext} from '../../../services/app-context';
+import {customElement, property, observe} from '@polymer/decorators';
+import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {GrTextarea} from '../gr-textarea/gr-textarea';
+import {GrStorage, StorageLocation} from '../gr-storage/gr-storage';
+import {GrOverlay} from '../gr-overlay/gr-overlay';
+import {
+ RobotCommentInfo,
+ PatchSetNum,
+ CommentInfo,
+ ConfigInfo,
+ AccountDetailInfo,
+} from '../../../types/common';
+import {GrButton} from '../gr-button/gr-button';
+import {GrConfirmDeleteCommentDialog} from '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog';
+import {GrDialog} from '../gr-dialog/gr-dialog';
const STORAGE_DEBOUNCE_INTERVAL = 400;
const TOAST_DEBOUNCE_INTERVAL = 200;
@@ -59,7 +74,7 @@
/**
* All candidates tips to show, will pick randomly.
*/
-const RESPECTFUL_REVIEW_TIPS= [
+const RESPECTFUL_REVIEW_TIPS = [
'Assume competence.',
'Provide rationale or context.',
'Consider how comments may be interpreted.',
@@ -68,14 +83,39 @@
'When disagreeing, explain the advantage of your approach.',
];
-/**
- * @extends PolymerElement
- */
-class GrComment extends KeyboardShortcutMixin(GestureEventListeners(
- LegacyElementMixin(PolymerElement))) {
- static get template() { return htmlTemplate; }
+interface Draft {
+ collapsed?: boolean;
+ __editing?: boolean;
+ __otherEditing?: boolean;
+ __draft?: boolean;
+ __draftID?: number;
+ __commentSide?: string;
+}
- static get is() { return 'gr-comment'; }
+export type Comment = Draft & CommentInfo;
+export type RobotComment = Draft & RobotCommentInfo;
+
+interface CommentOverlays {
+ confirmDelete?: GrOverlay | null;
+ confirmDiscard?: GrOverlay | null;
+}
+
+export interface GrComment {
+ $: {
+ restAPI: RestApiService & Element;
+ storage: GrStorage;
+ container: HTMLDivElement;
+ resolvedCheckbox: HTMLInputElement;
+ };
+}
+@customElement('gr-comment')
+export class GrComment extends KeyboardShortcutMixin(
+ GestureEventListeners(LegacyElementMixin(PolymerElement))
+) {
+ static get template() {
+ return htmlTemplate;
+ }
+
/**
* Fired when the create fix comment action is triggered.
*
@@ -118,138 +158,132 @@
* @event comment-anchor-tap
*/
- static get properties() {
- return {
- changeNum: String,
- /** @type {!Gerrit.Comment} */
- comment: {
- type: Object,
- notify: true,
- observer: '_commentChanged',
- },
- comments: {
- type: Array,
- },
- isRobotComment: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
- disabled: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
- draft: {
- type: Boolean,
- value: false,
- observer: '_draftChanged',
- },
- editing: {
- type: Boolean,
- value: false,
- observer: '_editingChanged',
- },
- discarding: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
- hasChildren: Boolean,
- patchNum: String,
- showActions: Boolean,
- _showHumanActions: Boolean,
- _showRobotActions: Boolean,
- collapsed: {
- type: Boolean,
- value: true,
- reflectToAttribute: true,
- observer: '_toggleCollapseClass',
- },
- /** @type {?} */
- projectConfig: Object,
- robotButtonDisabled: Boolean,
- _hasHumanReply: Boolean,
- _isAdmin: {
- type: Boolean,
- value: false,
- },
+ @property({type: Number})
+ changeNum!: number;
- _xhrPromise: Object, // Used for testing.
- _messageText: {
- type: String,
- value: '',
- observer: '_messageTextChanged',
- },
- commentSide: String,
- side: String,
+ @property({type: Object, notify: true, observer: '_commentChanged'})
+ comment!: Comment | RobotComment;
- resolved: Boolean,
+ @property({type: Array})
+ comments?: (Comment | RobotComment)[];
- _numPendingDraftRequests: {
- type: Object,
- value:
- {number: 0}, // Intentional to share the object across instances.
- },
+ @property({type: Boolean, reflectToAttribute: true})
+ isRobotComment = false;
- _enableOverlay: {
- type: Boolean,
- value: false,
- },
+ @property({type: Boolean, reflectToAttribute: true})
+ disabled = false;
- /**
- * Property for storing references to overlay elements. When the overlays
- * are moved to getRootElement() to be shown they are no-longer
- * children, so they can't be queried along the tree, so they are stored
- * here.
- */
- _overlays: {
- type: Object,
- value: () => { return {}; },
- },
+ @property({type: Boolean, observer: '_draftChanged'})
+ draft = false;
- _showRespectfulTip: {
- type: Boolean,
- value: false,
- },
- showPatchset: {
- type: Boolean,
- value: true,
- },
- _respectfulReviewTip: String,
- _respectfulTipDismissed: {
- type: Boolean,
- value: false,
- },
- _unableToSave: {
- type: Boolean,
- value: false,
- },
- _selfAccount: Object,
- };
- }
+ @property({type: Boolean, observer: '_editingChanged'})
+ editing = false;
- static get observers() {
- return [
- '_commentMessageChanged(comment.message)',
- '_loadLocalDraft(changeNum, patchNum, comment)',
- '_isRobotComment(comment)',
- '_calculateActionstoShow(showActions, isRobotComment)',
- '_computeHasHumanReply(comment, comments.*)',
- '_onEditingChange(editing)',
- ];
- }
+ @property({type: Boolean, reflectToAttribute: true})
+ discarding = false;
+
+ @property({type: Boolean})
+ hasChildren?: boolean;
+
+ @property({type: String})
+ patchNum!: PatchSetNum;
+
+ @property({type: Boolean})
+ showActions?: boolean;
+
+ @property({type: Boolean})
+ _showHumanActions?: boolean;
+
+ @property({type: Boolean})
+ _showRobotActions?: boolean;
+
+ @property({
+ type: Boolean,
+ reflectToAttribute: true,
+ observer: '_toggleCollapseClass',
+ })
+ collapsed = true;
+
+ @property({type: Object})
+ projectConfig?: ConfigInfo;
+
+ @property({type: Boolean})
+ robotButtonDisabled?: boolean;
+
+ @property({type: Boolean})
+ _hasHumanReply?: boolean;
+
+ @property({type: Boolean})
+ _isAdmin = false;
+
+ @property({type: Object})
+ _xhrPromise?: Promise<any>; // Used for testing.
+
+ @property({type: String, observer: '_messageTextChanged'})
+ _messageText = '';
+
+ @property({type: String})
+ commentSide?: string;
+
+ @property({type: String})
+ side?: string;
+
+ @property({type: Boolean})
+ resolved?: boolean;
+
+ // Intentional to share the object across instances.
+ @property({type: Object})
+ _numPendingDraftRequests: {number: number} = {number: 0};
+
+ @property({type: Boolean})
+ _enableOverlay = false;
+
+ /**
+ * Property for storing references to overlay elements. When the overlays
+ * are moved to getRootElement() to be shown they are no-longer
+ * children, so they can't be queried along the tree, so they are stored
+ * here.
+ */
+ @property({type: Object})
+ _overlays: CommentOverlays = {};
+
+ @property({type: Boolean})
+ _showRespectfulTip = false;
+
+ @property({type: Boolean})
+ showPatchset = true;
+
+ @property({type: String})
+ _respectfulReviewTip?: string;
+
+ @property({type: Boolean})
+ _respectfulTipDismissed = false;
+
+ @property({type: Boolean})
+ _unableToSave = false;
+
+ @property({type: Object})
+ _selfAccount?: AccountDetailInfo;
get keyBindings() {
return {
'ctrl+enter meta+enter ctrl+s meta+s': '_handleSaveKey',
- 'esc': '_handleEsc',
+ esc: '_handleEsc',
};
}
- constructor() {
- super();
- this.reporting = appContext.reportingService;
+ reporting = appContext.reportingService;
+
+ /** @override */
+ ready() {
+ super.ready();
+ if (
+ this.changeNum === undefined ||
+ this.patchNum === undefined ||
+ this.comment === undefined
+ ) {
+ throw new Error('Not all required properties are defined.');
+ }
}
/** @override */
@@ -261,10 +295,10 @@
if (this.editing) {
this.collapsed = false;
} else if (this.comment) {
- this.collapsed = this.comment.collapsed;
+ this.collapsed = !!this.comment.collapsed;
}
this._getIsAdmin().then(isAdmin => {
- this._isAdmin = isAdmin;
+ this._isAdmin = !!isAdmin;
});
}
@@ -277,51 +311,51 @@
}
}
- _getAuthor(comment) {
+ _getAuthor(comment: Comment) {
return comment.author || this._selfAccount;
}
- _onEditingChange(editing) {
- this.dispatchEvent(new CustomEvent('comment-editing-changed', {
- detail: !!editing,
- bubbles: true,
- composed: true,
- }));
+ @observe('editing')
+ _onEditingChange(editing?: boolean) {
+ this.dispatchEvent(
+ new CustomEvent('comment-editing-changed', {
+ detail: !!editing,
+ bubbles: true,
+ composed: true,
+ })
+ );
if (!editing) return;
// visibility based on cache this will make sure we only and always show
// a tip once every Math.max(a day, period between creating comments)
- const cachedVisibilityOfRespectfulTip =
- this.$.storage.getRespectfulTipVisibility();
+ const cachedVisibilityOfRespectfulTip = this.$.storage.getRespectfulTipVisibility();
if (!cachedVisibilityOfRespectfulTip) {
// we still want to show the tip with a probability of 30%
if (this.getRandomNum(0, 3) >= 1) return;
this._showRespectfulTip = true;
const randomIdx = this.getRandomNum(0, RESPECTFUL_REVIEW_TIPS.length);
this._respectfulReviewTip = RESPECTFUL_REVIEW_TIPS[randomIdx];
- this.reporting.reportInteraction(
- 'respectful-tip-appeared',
- {tip: this._respectfulReviewTip}
- );
+ this.reporting.reportInteraction('respectful-tip-appeared', {
+ tip: this._respectfulReviewTip,
+ });
// update cache
this.$.storage.setRespectfulTipVisibility();
}
}
/** Set as a separate method so easy to stub. */
- getRandomNum(min, max) {
+ getRandomNum(min: number, max: number) {
return Math.floor(Math.random() * (max - min) + min);
}
- _computeVisibilityOfTip(showTip, tipDismissed) {
+ _computeVisibilityOfTip(showTip: boolean, tipDismissed: boolean) {
return showTip && !tipDismissed;
}
_dismissRespectfulTip() {
this._respectfulTipDismissed = true;
- this.reporting.reportInteraction(
- 'respectful-tip-dismissed',
- {tip: this._respectfulReviewTip}
- );
+ this.reporting.reportInteraction('respectful-tip-dismissed', {
+ tip: this._respectfulReviewTip,
+ });
// add a 14-day delay to the tip cache
this.$.storage.setRespectfulTipVisibility(/* delayDays= */ 14);
}
@@ -330,16 +364,17 @@
this.reporting.reportInteraction('respectful-read-more-clicked');
}
- get textarea() {
- return this.shadowRoot.querySelector('#editTextarea');
+ get textarea(): GrTextarea | null {
+ return this.shadowRoot?.querySelector('#editTextarea') as GrTextarea | null;
}
get confirmDeleteOverlay() {
if (!this._overlays.confirmDelete) {
this._enableOverlay = true;
flush();
- this._overlays.confirmDelete = this.shadowRoot
- .querySelector('#confirmDeleteOverlay');
+ this._overlays.confirmDelete = this.shadowRoot?.querySelector(
+ '#confirmDeleteOverlay'
+ ) as GrOverlay | null;
}
return this._overlays.confirmDelete;
}
@@ -348,21 +383,23 @@
if (!this._overlays.confirmDiscard) {
this._enableOverlay = true;
flush();
- this._overlays.confirmDiscard = this.shadowRoot
- .querySelector('#confirmDiscardOverlay');
+ this._overlays.confirmDiscard = this.shadowRoot?.querySelector(
+ '#confirmDiscardOverlay'
+ ) as GrOverlay | null;
}
return this._overlays.confirmDiscard;
}
- _computeShowHideIcon(collapsed) {
+ _computeShowHideIcon(collapsed: boolean) {
return collapsed ? 'gr-icons:expand-more' : 'gr-icons:expand-less';
}
- _computeShowHideAriaLabel(collapsed) {
+ _computeShowHideAriaLabel(collapsed: boolean) {
return collapsed ? 'Expand' : 'Collapse';
}
- _calculateActionstoShow(showActions, isRobotComment) {
+ @observe('showActions', 'isRobotComment')
+ _calculateActionstoShow(showActions?: boolean, isRobotComment?: boolean) {
// Polymer 2: check for undefined
if ([showActions, isRobotComment].includes(undefined)) {
return;
@@ -372,7 +409,8 @@
this._showRobotActions = showActions && isRobotComment;
}
- _isRobotComment(comment) {
+ @observe('comment')
+ _isRobotComment(comment: RobotComment) {
this.isRobotComment = !!comment.robot_id;
}
@@ -384,20 +422,18 @@
return this.$.restAPI.getIsAdmin();
}
- _computeDraftTooltip(unableToSave) {
- return unableToSave ? `Unable to save draft. Please try to save again.` :
- `This draft is only visible to you. To publish drafts, click the 'Reply'`
- + `or 'Start review' button at the top of the change or press the 'A' key.`;
+ _computeDraftTooltip(unableToSave: boolean) {
+ return unableToSave
+ ? 'Unable to save draft. Please try to save again.'
+ : "This draft is only visible to you. To publish drafts, click the 'Reply'" +
+ "or 'Start review' button at the top of the change or press the 'A' key.";
}
- _computeDraftText(unableToSave) {
+ _computeDraftText(unableToSave: boolean) {
return 'DRAFT' + (unableToSave ? '(Failed to save)' : '');
}
- /**
- * @param {*=} opt_comment
- */
- save(opt_comment) {
+ save(opt_comment?: Comment) {
let comment = opt_comment;
if (!comment) {
comment = this.comment;
@@ -411,29 +447,32 @@
return this._discardDraft();
}
- this._xhrPromise = this._saveDraft(comment).then(response => {
- this.disabled = false;
- if (!response.ok) { return response; }
-
- this._eraseDraftComment();
- return this.$.restAPI.getResponseObject(response).then(obj => {
- const resComment = obj;
- resComment.__draft = true;
- // Maintain the ephemeral draft ID for identification by other
- // elements.
- if (this.comment.__draftID) {
- resComment.__draftID = this.comment.__draftID;
+ this._xhrPromise = this._saveDraft(comment)
+ .then(response => {
+ this.disabled = false;
+ if (!response.ok) {
+ return;
}
- resComment.__commentSide = this.commentSide;
- this.comment = resComment;
- this._fireSave();
- return obj;
- });
- })
- .catch(err => {
- this.disabled = false;
- throw err;
+
+ this._eraseDraftComment();
+ return this.$.restAPI.getResponseObject(response).then(obj => {
+ const resComment = (obj as unknown) as Comment;
+ resComment.__draft = true;
+ // Maintain the ephemeral draft ID for identification by other
+ // elements.
+ if (this.comment.__draftID) {
+ resComment.__draftID = this.comment.__draftID;
+ }
+ resComment.__commentSide = this.commentSide;
+ this.comment = resComment;
+ this._fireSave();
+ return obj;
});
+ })
+ .catch(err => {
+ this.disabled = false;
+ throw err;
+ });
return this._xhrPromise;
}
@@ -443,6 +482,8 @@
// prior to it being saved.
this.cancelDebouncer('store');
+ if (!this.comment.path || this.comment.line === undefined)
+ throw new Error('Cannot erase Draft Comment');
this.$.storage.eraseDraftComment({
changeNum: this.changeNum,
patchNum: this._getPatchNum(),
@@ -452,57 +493,62 @@
});
}
- _commentChanged(comment) {
+ _commentChanged(comment: Comment) {
this.editing = !!comment.__editing;
this.resolved = !comment.unresolved;
- if (this.editing) { // It's a new draft/reply, notify.
+ if (this.editing) {
+ // It's a new draft/reply, notify.
this._fireUpdate();
}
}
+ @observe('comment', 'comments.*')
_computeHasHumanReply() {
if (!this.comment || !this.comments) return;
// hide please fix button for robot comment that has human reply
- this._hasHumanReply = this.comments
- .some(c => c.in_reply_to && c.in_reply_to === this.comment.id &&
- !c.robot_id);
+ this._hasHumanReply = this.comments.some(
+ c =>
+ c.in_reply_to &&
+ c.in_reply_to === this.comment.id &&
+ !(c as RobotComment).robot_id
+ );
}
- /**
- * @param {!Object=} opt_mixin
- *
- * @return {!Object}
- */
- _getEventPayload(opt_mixin) {
- return {...opt_mixin, comment: this.comment,
- patchNum: this.patchNum};
+ _getEventPayload(opt_mixin?: Record<string, any>) {
+ return {...opt_mixin, comment: this.comment, patchNum: this.patchNum};
}
_fireSave() {
- this.dispatchEvent(new CustomEvent('comment-save', {
- detail: this._getEventPayload(),
- composed: true, bubbles: true,
- }));
+ this.dispatchEvent(
+ new CustomEvent('comment-save', {
+ detail: this._getEventPayload(),
+ composed: true,
+ bubbles: true,
+ })
+ );
}
_fireUpdate() {
this.debounce('fire-update', () => {
- this.dispatchEvent(new CustomEvent('comment-update', {
- detail: this._getEventPayload(),
- composed: true, bubbles: true,
- }));
+ this.dispatchEvent(
+ new CustomEvent('comment-update', {
+ detail: this._getEventPayload(),
+ composed: true,
+ bubbles: true,
+ })
+ );
});
}
- _computeAccountLabelClass(draft) {
+ _computeAccountLabelClass(draft: boolean) {
return draft ? 'draft' : '';
}
- _draftChanged(draft) {
+ _draftChanged(draft: boolean) {
this.$.container.classList.toggle('draft', draft);
}
- _editingChanged(editing, previousValue) {
+ _editingChanged(editing?: boolean, previousValue?: boolean) {
// Polymer 2: observer fires when at least one property is defined.
// Do nothing to prevent comment.__editing being overwritten
// if previousValue is undefined
@@ -510,7 +556,9 @@
this.$.container.classList.toggle('editing', editing);
if (this.comment && this.comment.id) {
- const cancelButton = this.shadowRoot.querySelector('.cancel');
+ const cancelButton = this.shadowRoot?.querySelector(
+ '.cancel'
+ ) as GrButton | null;
if (cancelButton) {
cancelButton.hidden = !editing;
}
@@ -518,7 +566,7 @@
if (this.comment) {
this.comment.__editing = this.editing;
}
- if (editing != !!previousValue) {
+ if (!!editing !== !!previousValue) {
// To prevent event firing on comment creation.
this._fireUpdate();
}
@@ -530,27 +578,28 @@
}
}
- _computeDeleteButtonClass(isAdmin, draft) {
+ _computeDeleteButtonClass(isAdmin: boolean, draft: boolean) {
return isAdmin && !draft ? 'showDeleteButtons' : '';
}
- _computeSaveDisabled(draft, comment, resolved) {
+ _computeSaveDisabled(draft: string, comment: Comment, resolved?: boolean) {
// If resolved state has changed and a msg exists, save should be enabled.
- if (!comment || comment.unresolved === resolved && draft) {
+ if (!comment || (comment.unresolved === resolved && draft)) {
return false;
}
return !draft || draft.trim() === '';
}
- _handleSaveKey(e) {
- if (!this._computeSaveDisabled(this._messageText, this.comment,
- this.resolved)) {
+ _handleSaveKey(e: Event) {
+ if (
+ !this._computeSaveDisabled(this._messageText, this.comment, this.resolved)
+ ) {
e.preventDefault();
this._handleSave(e);
}
}
- _handleEsc(e) {
+ _handleEsc(e: Event) {
if (!this._messageText.length) {
e.preventDefault();
this._handleCancel(e);
@@ -561,7 +610,7 @@
this.collapsed = !this.collapsed;
}
- _toggleCollapseClass(collapsed) {
+ _toggleCollapseClass(collapsed: boolean) {
if (collapsed) {
this.$.container.classList.add('collapsed');
} else {
@@ -569,77 +618,92 @@
}
}
- _commentMessageChanged(message) {
+ @observe('comment.message')
+ _commentMessageChanged(message: string) {
this._messageText = message || '';
}
- _messageTextChanged(newValue, oldValue) {
+ _messageTextChanged(_: string, oldValue: string) {
if (!this.comment || (this.comment && this.comment.id)) {
return;
}
- this.debounce('store', () => {
- const message = this._messageText;
- const commentLocation = {
- changeNum: this.changeNum,
- patchNum: this._getPatchNum(),
- path: this.comment.path,
- line: this.comment.line,
- range: this.comment.range,
- };
+ this.debounce(
+ 'store',
+ () => {
+ const message = this._messageText;
+ if (!this.comment.path || this.comment.line === undefined)
+ throw new Error('missing path or line in comment');
+ const commentLocation: StorageLocation = {
+ changeNum: this.changeNum,
+ patchNum: this._getPatchNum(),
+ path: this.comment.path,
+ line: this.comment.line,
+ range: this.comment.range,
+ };
- 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);
- }
- }, STORAGE_DEBOUNCE_INTERVAL);
+ 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);
+ }
+ },
+ STORAGE_DEBOUNCE_INTERVAL
+ );
}
- _handleAnchorClick(e) {
+ _handleAnchorClick(e: Event) {
e.preventDefault();
if (!this.comment.line) {
return;
}
- this.dispatchEvent(new CustomEvent('comment-anchor-tap', {
- bubbles: true,
- composed: true,
- detail: {
- number: this.comment.line || FILE,
- side: this.side,
- },
- }));
+ this.dispatchEvent(
+ new CustomEvent('comment-anchor-tap', {
+ bubbles: true,
+ composed: true,
+ detail: {
+ number: this.comment.line || FILE,
+ side: this.side,
+ },
+ })
+ );
}
- _handleEdit(e) {
+ _handleEdit(e: Event) {
e.preventDefault();
+ if (!this.comment.message) throw new Error('message undefined');
this._messageText = this.comment.message;
this.editing = true;
this.reporting.recordDraftInteraction();
}
- _handleSave(e) {
+ _handleSave(e: Event) {
e.preventDefault();
// Ignore saves started while already saving.
if (this.disabled) {
return;
}
- const timingLabel = this.comment.id ?
- REPORT_UPDATE_DRAFT : REPORT_CREATE_DRAFT;
+ const timingLabel = this.comment.id
+ ? REPORT_UPDATE_DRAFT
+ : REPORT_CREATE_DRAFT;
const timer = this.reporting.getTimer(timingLabel);
this.set('comment.__editing', false);
- return this.save().then(() => { timer.end(); });
+ return this.save().then(() => {
+ timer.end();
+ });
}
- _handleCancel(e) {
+ _handleCancel(e: Event) {
e.preventDefault();
- if (!this.comment.message ||
- this.comment.message.trim().length === 0 ||
- !this.comment.id) {
+ if (
+ !this.comment.message ||
+ this.comment.message.trim().length === 0 ||
+ !this.comment.id
+ ) {
this._fireDiscard();
return;
}
@@ -649,33 +713,40 @@
_fireDiscard() {
this.cancelDebouncer('fire-update');
- this.dispatchEvent(new CustomEvent('comment-discard', {
- detail: this._getEventPayload(),
- composed: true, bubbles: true,
- }));
+ this.dispatchEvent(
+ new CustomEvent('comment-discard', {
+ detail: this._getEventPayload(),
+ composed: true,
+ bubbles: true,
+ })
+ );
}
_handleFix() {
- this.dispatchEvent(new CustomEvent('create-fix-comment', {
- bubbles: true,
- composed: true,
- detail: this._getEventPayload(),
- }));
+ this.dispatchEvent(
+ new CustomEvent('create-fix-comment', {
+ bubbles: true,
+ composed: true,
+ detail: this._getEventPayload(),
+ })
+ );
}
_handleShowFix() {
- this.dispatchEvent(new CustomEvent('open-fix-preview', {
- bubbles: true,
- composed: true,
- detail: this._getEventPayload(),
- }));
+ this.dispatchEvent(
+ new CustomEvent('open-fix-preview', {
+ bubbles: true,
+ composed: true,
+ detail: this._getEventPayload(),
+ })
+ );
}
- _hasNoFix(comment) {
- return !comment || !comment.fix_suggestions;
+ _hasNoFix(comment: Comment) {
+ return !comment || !(comment as RobotComment).fix_suggestions;
}
- _handleDiscard(e) {
+ _handleDiscard(e: Event) {
e.preventDefault();
this.reporting.recordDraftInteraction();
@@ -685,21 +756,25 @@
}
this._openOverlay(this.confirmDiscardOverlay).then(() => {
- this.confirmDiscardOverlay.querySelector('#confirmDiscardDialog')
- .resetFocus();
+ const dialog = this.confirmDiscardOverlay?.querySelector(
+ '#confirmDiscardDialog'
+ ) as GrDialog | null;
+ if (dialog) dialog.resetFocus();
});
}
- _handleConfirmDiscard(e) {
+ _handleConfirmDiscard(e: Event) {
e.preventDefault();
const timer = this.reporting.getTimer(REPORT_DISCARD_DRAFT);
this._closeConfirmDiscardOverlay();
- return this._discardDraft().then(() => { timer.end(); });
+ return this._discardDraft().then(() => {
+ timer.end();
+ });
}
_discardDraft() {
if (!this.comment.__draft) {
- throw Error('Cannot discard a non-draft comment.');
+ return Promise.reject(new Error('Cannot discard a non-draft comment.'));
}
this.discarding = true;
this.editing = false;
@@ -709,22 +784,23 @@
if (!this.comment.id) {
this.disabled = false;
this._fireDiscard();
- return;
+ return Promise.resolve();
}
- this._xhrPromise = this._deleteDraft(this.comment).then(response => {
- this.disabled = false;
- if (!response.ok) {
- this.discarding = false;
- return response;
- }
+ this._xhrPromise = this._deleteDraft(this.comment)
+ .then(response => {
+ this.disabled = false;
+ if (!response.ok) {
+ this.discarding = false;
+ }
- this._fireDiscard();
- })
- .catch(err => {
- this.disabled = false;
- throw err;
- });
+ this._fireDiscard();
+ return response;
+ })
+ .catch(err => {
+ this.disabled = false;
+ throw err;
+ });
return this._xhrPromise;
}
@@ -733,7 +809,7 @@
this._closeOverlay(this.confirmDiscardOverlay);
}
- _getSavingMessage(numPending, requestFailed) {
+ _getSavingMessage(numPending: number, requestFailed?: boolean) {
if (requestFailed) {
return UNSAVED_MESSAGE;
}
@@ -763,19 +839,30 @@
// Cancel the debouncer so that error toasts from the error-manager will
// not be overridden.
this.cancelDebouncer('draft-toast');
- this._updateRequestToast(this._numPendingDraftRequests.number,
- /* requestFailed=*/true);
+ this._updateRequestToast(
+ this._numPendingDraftRequests.number,
+ /* requestFailed=*/ true
+ );
}
- _updateRequestToast(numPending, requestFailed) {
+ _updateRequestToast(numPending: number, requestFailed?: boolean) {
const message = this._getSavingMessage(numPending, requestFailed);
- this.debounce('draft-toast', () => {
- // Note: the event is fired on the body rather than this element because
- // this element may not be attached by the time this executes, in which
- // case the event would not bubble.
- document.body.dispatchEvent(new CustomEvent(
- 'show-alert', {detail: {message}, bubbles: true, composed: true}));
- }, TOAST_DEBOUNCE_INTERVAL);
+ this.debounce(
+ 'draft-toast',
+ () => {
+ // Note: the event is fired on the body rather than this element because
+ // this element may not be attached by the time this executes, in which
+ // case the event would not bubble.
+ document.body.dispatchEvent(
+ new CustomEvent('show-alert', {
+ detail: {message},
+ bubbles: true,
+ composed: true,
+ })
+ );
+ },
+ TOAST_DEBOUNCE_INTERVAL
+ );
}
_handleDraftFailure() {
@@ -784,43 +871,51 @@
this._handleFailedDraftRequest();
}
- _saveDraft(draft) {
+ _saveDraft(draft: Comment) {
this._showStartRequest();
- return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft)
- .then(result => {
- if (result.ok) { // remove
- this._unableToSave = false;
- this.$.container.classList.remove('unableToSave');
- this._showEndRequest();
- } else {
- this._handleDraftFailure();
- }
- return result;
- })
- .catch(err => {
+ return this.$.restAPI
+ .saveDiffDraft(this.changeNum, this.patchNum, draft)
+ .then(result => {
+ if (result.ok) {
+ // remove
+ this._unableToSave = false;
+ this.$.container.classList.remove('unableToSave');
+ this._showEndRequest();
+ } else {
this._handleDraftFailure();
- throw (err);
- });
+ }
+ return result;
+ })
+ .catch(err => {
+ this._handleDraftFailure();
+ throw err;
+ });
}
- _deleteDraft(draft) {
+ _deleteDraft(draft: Comment) {
this._showStartRequest();
- return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
- draft).then(result => {
- if (result.ok) {
- this._showEndRequest();
- } else {
- this._handleFailedDraftRequest();
- }
- return result;
- });
+ return this.$.restAPI
+ .deleteDiffDraft(this.changeNum, this.patchNum, draft)
+ .then(result => {
+ if (result.ok) {
+ this._showEndRequest();
+ } else {
+ this._handleFailedDraftRequest();
+ }
+ return result;
+ });
}
- _getPatchNum() {
- return this.isOnParent() ? 'PARENT' : this.patchNum;
+ _getPatchNum(): PatchSetNum {
+ return this.isOnParent() ? ('PARENT' as PatchSetNum) : this.patchNum;
}
- _loadLocalDraft(changeNum, patchNum, comment) {
+ @observe('changeNum', 'patchNum', 'comment')
+ _loadLocalDraft(
+ changeNum: number,
+ patchNum?: PatchSetNum,
+ comment?: Comment
+ ) {
// Polymer 2: check for undefined
if ([changeNum, patchNum, comment].includes(undefined)) {
return;
@@ -831,8 +926,16 @@
//
// Don't get local draft if there is another comment that is currently
// in an editing state.
- if (!comment || comment.id || comment.message || comment.__otherEditing) {
- delete comment.__otherEditing;
+ if (
+ !comment ||
+ comment.id ||
+ comment.message ||
+ comment.__otherEditing ||
+ !comment.path ||
+ !comment.line ||
+ !comment.range
+ ) {
+ if (comment) delete comment.__otherEditing;
return;
}
@@ -856,10 +959,13 @@
// the parent by ref.
const payload = this._getEventPayload();
payload.comment.unresolved = !this.$.resolvedCheckbox.checked;
- this.dispatchEvent(new CustomEvent('comment-update', {
- detail: payload,
- composed: true, bubbles: true,
- }));
+ this.dispatchEvent(
+ new CustomEvent('comment-update', {
+ detail: payload,
+ composed: true,
+ bubbles: true,
+ })
+ );
if (!this.editing) {
// Save the resolved state immediately.
this.save(payload.comment);
@@ -874,31 +980,49 @@
this._closeOverlay(this.confirmDeleteOverlay);
}
- _openOverlay(overlay) {
+ _openOverlay(overlay?: GrOverlay | null) {
+ if (!overlay) {
+ return Promise.reject(new Error('undefined overlay'));
+ }
getRootElement().appendChild(overlay);
return overlay.open();
}
- _computeHideRunDetails(comment, collapsed) {
+ _computeHideRunDetails(comment: RobotComment, collapsed: boolean) {
if (!comment) return true;
return !(comment.robot_id && comment.url && !collapsed);
}
- _closeOverlay(overlay) {
- getRootElement().removeChild(overlay);
- overlay.close();
+ _closeOverlay(overlay?: GrOverlay | null) {
+ if (overlay) {
+ getRootElement().removeChild(overlay);
+ overlay.close();
+ }
}
_handleConfirmDeleteComment() {
- const dialog =
- this.confirmDeleteOverlay.querySelector('#confirmDeleteComment');
- this.$.restAPI.deleteComment(
- this.changeNum, this.patchNum, this.comment.id, dialog.message)
- .then(newComment => {
- this._handleCancelDeleteComment();
- this.comment = newComment;
- });
+ const dialog = this.confirmDeleteOverlay?.querySelector(
+ '#confirmDeleteComment'
+ ) as GrConfirmDeleteCommentDialog | null;
+ if (!dialog || !dialog.message) {
+ throw new Error('missing confirm delete dialog');
+ }
+ this.$.restAPI
+ .deleteComment(
+ this.changeNum,
+ this.patchNum,
+ this.comment.id,
+ dialog.message
+ )
+ .then(newComment => {
+ this._handleCancelDeleteComment();
+ this.comment = newComment;
+ });
}
}
-customElements.define(GrComment.is, GrComment);
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-comment': GrComment;
+ }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
index f4111e7..3dfe105 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
@@ -3226,7 +3226,7 @@
commentID: UrlEncodedCommentId,
reason: string
) {
- return this._getChangeURLAndSend({
+ return (this._getChangeURLAndSend({
changeNum,
method: HttpMethod.POST,
patchNum,
@@ -3234,7 +3234,7 @@
body: {reason},
parseResponse: true,
anonymizedEndpoint: '/comments/*/delete',
- });
+ }) as unknown) as Promise<CommentInfo>;
}
/**
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.ts b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.ts
index 176f6c9..15914c5 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.ts
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.ts
@@ -25,7 +25,7 @@
patchNum: PatchSetNum;
path: string;
line: number;
- range: CommentRange;
+ range?: CommentRange;
}
export interface StorageObject {
diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
index e8fc3f9..05500ec 100644
--- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
@@ -78,6 +78,7 @@
GroupAuditEventInfo,
EncodedGroupId,
Base64FileContent,
+ UrlEncodedCommentId,
} from '../../../types/common';
import {ParsedChangeInfo} from '../../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
import {HttpMethod} from '../../../constants/constants';
@@ -604,6 +605,18 @@
label: string
): Promise<Response>;
+ deleteComment(
+ changeNum: ChangeNum,
+ patchNum: PatchSetNum,
+ commentID: UrlEncodedCommentId,
+ reason: string
+ ): Promise<CommentInfo>;
+ deleteDiffDraft(
+ changeNum: ChangeNum,
+ patchNum: PatchSetNum,
+ draft: {id: UrlEncodedCommentId}
+ ): Promise<Response>;
+
deleteChangeCommitMessage(
changeNum: ChangeNum,
messageId: ChangeMessageId
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index cc74ef1..eec0fc3 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -1039,7 +1039,7 @@
* The CommentInfo entity contains information about an inline comment.
* https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#comment-info
*/
-export interface CommentInfo {
+export interface CommentInfo extends CommentInput {
patch_set?: PatchSetNum;
id: UrlEncodedCommentId;
path?: string;
@@ -1047,9 +1047,9 @@
parent?: number;
line?: number;
range?: CommentRange;
- in_reply_to?: string;
+ in_reply_to?: UrlEncodedCommentId;
message?: string;
- updated: string;
+ updated: Timestamp;
author?: AccountInfo;
tag?: string;
unresolved?: boolean;