|  | /** | 
|  | * @license | 
|  | * 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'; | 
|  |  | 
|  | const PATCH_SET_PREFIX_PATTERN = /^Patch Set \d+: /; | 
|  | const LABEL_TITLE_SCORE_PATTERN = /^([A-Za-z0-9-]+)([+-]\d+)$/; | 
|  |  | 
|  | Polymer({ | 
|  | is: 'gr-message', | 
|  |  | 
|  | /** | 
|  | * Fired when this message's permalink is tapped. | 
|  | * | 
|  | * @event scroll-to | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Fired when this message's reply link is tapped. | 
|  | * | 
|  | * @event reply | 
|  | */ | 
|  |  | 
|  | listeners: { | 
|  | tap: '_handleTap', | 
|  | }, | 
|  |  | 
|  | properties: { | 
|  | changeNum: Number, | 
|  | /** @type {?} */ | 
|  | message: Object, | 
|  | author: { | 
|  | type: Object, | 
|  | computed: '_computeAuthor(message)', | 
|  | }, | 
|  | comments: { | 
|  | type: Object, | 
|  | observer: '_commentsChanged', | 
|  | }, | 
|  | config: Object, | 
|  | hideAutomated: { | 
|  | type: Boolean, | 
|  | value: false, | 
|  | }, | 
|  | hidden: { | 
|  | type: Boolean, | 
|  | computed: '_computeIsHidden(hideAutomated, isAutomated)', | 
|  | reflectToAttribute: true, | 
|  | }, | 
|  | isAutomated: { | 
|  | type: Boolean, | 
|  | computed: '_computeIsAutomated(message)', | 
|  | }, | 
|  | showAvatar: { | 
|  | type: Boolean, | 
|  | computed: '_computeShowAvatar(author, config)', | 
|  | }, | 
|  | showOnBehalfOf: { | 
|  | type: Boolean, | 
|  | computed: '_computeShowOnBehalfOf(message)', | 
|  | }, | 
|  | showReplyButton: { | 
|  | type: Boolean, | 
|  | computed: '_computeShowReplyButton(message, _loggedIn)', | 
|  | }, | 
|  | projectName: { | 
|  | type: String, | 
|  | observer: '_projectNameChanged', | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * A mapping from label names to objects representing the minimum and | 
|  | * maximum possible values for that label. | 
|  | */ | 
|  | labelExtremes: Object, | 
|  |  | 
|  | /** | 
|  | * @type {{ commentlinks: Array }} | 
|  | */ | 
|  | _projectConfig: Object, | 
|  | // Computed property needed to trigger Polymer value observing. | 
|  | _expanded: { | 
|  | type: Object, | 
|  | computed: '_computeExpanded(message.expanded)', | 
|  | }, | 
|  | _loggedIn: { | 
|  | type: Boolean, | 
|  | value: false, | 
|  | }, | 
|  | }, | 
|  |  | 
|  | observers: [ | 
|  | '_updateExpandedClass(message.expanded)', | 
|  | ], | 
|  |  | 
|  | ready() { | 
|  | this.$.restAPI.getConfig().then(config => { | 
|  | this.config = config; | 
|  | }); | 
|  | this.$.restAPI.getLoggedIn().then(loggedIn => { | 
|  | this._loggedIn = loggedIn; | 
|  | }); | 
|  | }, | 
|  |  | 
|  | _updateExpandedClass(expanded) { | 
|  | if (expanded) { | 
|  | this.classList.add('expanded'); | 
|  | } else { | 
|  | this.classList.remove('expanded'); | 
|  | } | 
|  | }, | 
|  |  | 
|  | _computeAuthor(message) { | 
|  | return message.author || message.updated_by; | 
|  | }, | 
|  |  | 
|  | _computeShowAvatar(author, config) { | 
|  | return !!(author && config && config.plugin && config.plugin.has_avatars); | 
|  | }, | 
|  |  | 
|  | _computeShowOnBehalfOf(message) { | 
|  | const author = message.author || message.updated_by; | 
|  | return !!(author && message.real_author && | 
|  | author._account_id != message.real_author._account_id); | 
|  | }, | 
|  |  | 
|  | _computeShowReplyButton(message, loggedIn) { | 
|  | return !!message.message && loggedIn && | 
|  | !this._computeIsAutomated(message); | 
|  | }, | 
|  |  | 
|  | _computeExpanded(expanded) { | 
|  | return expanded; | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * If there is no value set on the message object as to whether _expanded | 
|  | * should be true or not, then _expanded is set to true if there are | 
|  | * inline comments (otherwise false). | 
|  | */ | 
|  | _commentsChanged(value) { | 
|  | if (this.message && this.message.expanded === undefined) { | 
|  | this.set('message.expanded', Object.keys(value || {}).length > 0); | 
|  | } | 
|  | }, | 
|  |  | 
|  | _handleTap(e) { | 
|  | if (this.message.expanded) { return; } | 
|  | e.stopPropagation(); | 
|  | this.set('message.expanded', true); | 
|  | }, | 
|  |  | 
|  | _handleAuthorTap(e) { | 
|  | if (!this.message.expanded) { return; } | 
|  | e.stopPropagation(); | 
|  | this.set('message.expanded', false); | 
|  | }, | 
|  |  | 
|  | _computeIsAutomated(message) { | 
|  | return !!(message.reviewer || | 
|  | this._computeIsReviewerUpdate(message) || | 
|  | (message.tag && message.tag.startsWith('autogenerated'))); | 
|  | }, | 
|  |  | 
|  | _computeIsHidden(hideAutomated, isAutomated) { | 
|  | return hideAutomated && isAutomated; | 
|  | }, | 
|  |  | 
|  | _computeIsReviewerUpdate(event) { | 
|  | return event.type === 'REVIEWER_UPDATE'; | 
|  | }, | 
|  |  | 
|  | _getScores(message) { | 
|  | if (!message.message) { return []; } | 
|  | const line = message.message.split('\n', 1)[0]; | 
|  | const patchSetPrefix = PATCH_SET_PREFIX_PATTERN; | 
|  | if (!line.match(patchSetPrefix)) { return []; } | 
|  | const scoresRaw = line.split(patchSetPrefix)[1]; | 
|  | if (!scoresRaw) { return []; } | 
|  | return scoresRaw.split(' ') | 
|  | .map(s => s.match(LABEL_TITLE_SCORE_PATTERN)) | 
|  | .filter(ms => ms && ms.length === 3) | 
|  | .map(ms => ({label: ms[1], value: ms[2]})); | 
|  | }, | 
|  |  | 
|  | _computeScoreClass(score, labelExtremes) { | 
|  | const classes = []; | 
|  | if (score.value > 0) { | 
|  | classes.push('positive'); | 
|  | } else if (score.value < 0) { | 
|  | classes.push('negative'); | 
|  | } | 
|  | const extremes = labelExtremes[score.label]; | 
|  | if (extremes) { | 
|  | const intScore = parseInt(score.value, 10); | 
|  | if (intScore === extremes.max) { | 
|  | classes.push('max'); | 
|  | } else if (intScore === extremes.min) { | 
|  | classes.push('min'); | 
|  | } | 
|  | } | 
|  | return classes.join(' '); | 
|  | }, | 
|  |  | 
|  | _computeClass(expanded, showAvatar, message) { | 
|  | const classes = []; | 
|  | classes.push(expanded ? 'expanded' : 'collapsed'); | 
|  | classes.push(showAvatar ? 'showAvatar' : 'hideAvatar'); | 
|  | return classes.join(' '); | 
|  | }, | 
|  |  | 
|  | _computeMessageHash(message) { | 
|  | return '#message-' + message.id; | 
|  | }, | 
|  |  | 
|  | _handleLinkTap(e) { | 
|  | e.preventDefault(); | 
|  |  | 
|  | this.fire('scroll-to', {message: this.message}, {bubbles: false}); | 
|  |  | 
|  | const hash = this._computeMessageHash(this.message); | 
|  | // 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); | 
|  | }, | 
|  |  | 
|  | _handleReplyTap(e) { | 
|  | e.preventDefault(); | 
|  | this.fire('reply', {message: this.message}); | 
|  | }, | 
|  |  | 
|  | _projectNameChanged(name) { | 
|  | this.$.restAPI.getProjectConfig(name).then(config => { | 
|  | this._projectConfig = config; | 
|  | }); | 
|  | }, | 
|  |  | 
|  | _computeExpandToggleIcon(expanded) { | 
|  | return expanded ? 'gr-icons:expand-less' : 'gr-icons:expand-more'; | 
|  | }, | 
|  |  | 
|  | _toggleExpanded(e) { | 
|  | e.stopPropagation(); | 
|  | this.set('message.expanded', !this.message.expanded); | 
|  | }, | 
|  | }); | 
|  | })(); |