Move comment and thread elements to shared

Even before the refactorings, the comment and thread elements were also used
from views outside of diff/. Now they are now completely independent from
gr-diff and descendants, and can move into their own folder and drop the -diff
from their name.

The dependency left from diff/gr-diff-host onto comment[-thread], makes sense
since gr-diff-host is the Gerrit wrapper for gr-diff with gr-comment[-thread].

Change-Id: I5076428da980198989edc605e5edc0e4d66529dd
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
deleted file mode 100644
index c3a1de4..0000000
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
+++ /dev/null
@@ -1,126 +0,0 @@
-<!--
-@license
-Copyright (C) 2015 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.
--->
-
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
-<link rel="import" href="../../../styles/shared-styles.html">
-<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
-<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
-<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-<link rel="import" href="../../shared/gr-storage/gr-storage.html">
-<link rel="import" href="../gr-diff-comment/gr-diff-comment.html">
-
-<dom-module id="gr-diff-comment-thread">
-  <template>
-    <style include="shared-styles">
-      gr-button {
-        margin-left: .5em;
-      }
-      #actions {
-        margin-left: auto;
-        padding: .5em .7em;
-      }
-      #container {
-        background-color: var(--comment-background-color);
-        border: 1px solid var(--border-color);
-        color: var(--comment-text-color);
-        display: block;
-        margin-bottom: 1px;
-        white-space: normal;
-      }
-      #container.unresolved {
-        background-color: var(--unresolved-comment-background-color);
-      }
-      #commentInfoContainer {
-        border-top: 1px dotted var(--border-color);
-        display: flex;
-      }
-      #unresolvedLabel {
-        font-family: var(--font-family);
-        margin: auto 0;
-        padding: .5em .7em;
-      }
-      .pathInfo {
-        display: flex;
-        align-items: baseline;
-      }
-      .descriptionText {
-        margin-left: .5rem;
-        font-style: italic;
-      }
-    </style>
-    <template is="dom-if" if="[[showFilePath]]">
-      <div class="pathInfo">
-        <a href$="[[_getDiffUrlForComment(projectName, changeNum, path, patchNum)]]">[[_computeDisplayPath(path)]]</a>
-        <span class="descriptionText">Patchset [[patchNum]]</span>
-      </div>
-    </template>
-    <div id="container" class$="[[_computeHostClass(unresolved)]]">
-      <template id="commentList" is="dom-repeat" items="[[_orderedComments]]"
-          as="comment">
-        <gr-diff-comment
-            comment="{{comment}}"
-            robot-button-disabled="[[_hideActions(_showActions, _lastComment)]]"
-            change-num="[[changeNum]]"
-            patch-num="[[patchNum]]"
-            draft="[[_isDraft(comment)]]"
-            show-actions="[[_showActions]]"
-            comment-side="[[comment.__commentSide]]"
-            side="[[comment.side]]"
-            root-id="[[rootId]]"
-            project-config="[[_projectConfig]]"
-            on-create-fix-comment="_handleCommentFix"
-            on-comment-discard="_handleCommentDiscard"
-            on-comment-save="_handleCommentSavedOrDiscarded"></gr-diff-comment>
-      </template>
-      <div id="commentInfoContainer"
-          hidden$="[[_hideActions(_showActions, _lastComment)]]">
-        <span id="unresolvedLabel" hidden$="[[!unresolved]]">Unresolved</span>
-        <div id="actions">
-          <gr-button
-              id="replyBtn"
-              link
-              secondary
-              class="action reply"
-              on-tap="_handleCommentReply">Reply</gr-button>
-          <gr-button
-              id="quoteBtn"
-              link
-              secondary
-              class="action quote"
-              on-tap="_handleCommentQuote">Quote</gr-button>
-          <gr-button
-              id="ackBtn"
-              link
-              secondary
-              class="action ack"
-              on-tap="_handleCommentAck">Ack</gr-button>
-          <gr-button
-              id="doneBtn"
-              link
-              secondary
-              class="action done"
-              on-tap="_handleCommentDone">Done</gr-button>
-        </div>
-      </div>
-    </div>
-    <gr-reporting id="reporting"></gr-reporting>
-    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
-    <gr-storage id="storage"></gr-storage>
-  </template>
-  <script src="gr-diff-comment-thread.js"></script>
-</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
deleted file mode 100644
index a2439d7..0000000
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ /dev/null
@@ -1,493 +0,0 @@
-/**
- * @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 UNRESOLVED_EXPAND_COUNT = 5;
-  const NEWLINE_PATTERN = /\n/g;
-
-  Polymer({
-    is: 'gr-diff-comment-thread',
-
-    /**
-     * Fired when the thread should be discarded.
-     *
-     * @event thread-discard
-     */
-
-    /**
-     * Fired when a comment in the thread is permanently modified.
-     *
-     * @event thread-changed
-     */
-
-     /**
-      * gr-diff-comment-thread exposes the following attributes that allow a
-      * diff widget like gr-diff to show the thread in the right location:
-      *
-      * line-num:
-      *     1-based line number or undefined if it refers to the entire file.
-      *
-      * comment-side:
-      *     "left" or "right". These indicate which of the two diffed versions
-      *     the comment relates to. In the case of unified diff, the left
-      *     version is the one whose line number column is further to the left.
-      *
-      * range:
-      *     The range of text that the comment refers to (start_line,
-      *     start_character, end_line, end_character), serialized as JSON. If
-      *     set, range's end_line will have the same value as line-num. Line
-      *     numbers are 1-based, char numbers are 0-based. The start position
-      *     (start_line, start_character) is inclusive, and the end position
-      *     (end_line, end_character) is exclusive.
-      */
-    properties: {
-      changeNum: String,
-      comments: {
-        type: Array,
-        value() { return []; },
-      },
-      /**
-       * @type {?{start_line: number, start_character: number, end_line: number,
-       *          end_character: number}}
-       */
-      range: {
-        type: Object,
-        reflectToAttribute: true,
-      },
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      commentSide: {
-        type: String,
-        reflectToAttribute: true,
-      },
-      patchNum: String,
-      path: String,
-      projectName: {
-        type: String,
-        observer: '_projectNameChanged',
-      },
-      hasDraft: {
-        type: Boolean,
-        notify: true,
-        reflectToAttribute: true,
-      },
-      isOnParent: {
-        type: Boolean,
-        value: false,
-      },
-      parentIndex: {
-        type: Number,
-        value: null,
-      },
-      rootId: {
-        type: String,
-        notify: true,
-        computed: '_computeRootId(comments.*)',
-      },
-      /**
-       * If this is true, the comment thread also needs to have the change and
-       * line properties property set
-       */
-      showFilePath: {
-        type: Boolean,
-        value: false,
-      },
-      /** Necessary only if showFilePath is true or when used with gr-diff */
-      lineNum: {
-        type: Number,
-        reflectToAttribute: true,
-      },
-      unresolved: {
-        type: Boolean,
-        notify: true,
-        reflectToAttribute: true,
-      },
-      _showActions: Boolean,
-      _lastComment: Object,
-      _orderedComments: Array,
-      _projectConfig: Object,
-    },
-
-    behaviors: [
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.PathListBehavior,
-    ],
-
-    listeners: {
-      'comment-update': '_handleCommentUpdate',
-    },
-
-    observers: [
-      '_commentsChanged(comments.*)',
-    ],
-
-    keyBindings: {
-      'e shift+e': '_handleEKey',
-    },
-
-    attached() {
-      this._getLoggedIn().then(loggedIn => {
-        this._showActions = loggedIn;
-      });
-      this._setInitialExpandedState();
-    },
-
-    addOrEditDraft(opt_lineNum, opt_range) {
-      const lastComment = this.comments[this.comments.length - 1] || {};
-      if (lastComment.__draft) {
-        const commentEl = this._commentElWithDraftID(
-            lastComment.id || lastComment.__draftID);
-        commentEl.editing = true;
-
-        // If the comment was collapsed, re-open it to make it clear which
-        // actions are available.
-        commentEl.collapsed = false;
-      } else {
-        const range = opt_range ? opt_range :
-            lastComment ? lastComment.range : undefined;
-        const unresolved = lastComment ? lastComment.unresolved : undefined;
-        this.addDraft(opt_lineNum, range, unresolved);
-      }
-    },
-
-    addDraft(opt_lineNum, opt_range, opt_unresolved) {
-      const draft = this._newDraft(opt_lineNum, opt_range);
-      draft.__editing = true;
-      draft.unresolved = opt_unresolved === false ? opt_unresolved : true;
-      this.push('comments', draft);
-    },
-
-    fireRemoveSelf() {
-      this.dispatchEvent(new CustomEvent('thread-discard',
-          {detail: {rootId: this.rootId}, bubbles: false}));
-    },
-
-    _getDiffUrlForComment(projectName, changeNum, path, patchNum) {
-      return Gerrit.Nav.getUrlForDiffById(changeNum,
-          projectName, path, patchNum,
-          null, this.lineNum);
-    },
-
-    _computeDisplayPath(path) {
-      const lineString = this.lineNum ? `#${this.lineNum}` : '';
-      return this.computeDisplayPath(path) + lineString;
-    },
-
-    _getLoggedIn() {
-      return this.$.restAPI.getLoggedIn();
-    },
-
-    _commentsChanged() {
-      this._orderedComments = this._sortedComments(this.comments);
-      this.updateThreadProperties();
-    },
-
-    updateThreadProperties() {
-      if (this._orderedComments.length) {
-        this._lastComment = this._getLastComment();
-        this.unresolved = this._lastComment.unresolved;
-        this.hasDraft = this._lastComment.__draft;
-      }
-    },
-
-    _hideActions(_showActions, _lastComment) {
-      return !_showActions || !_lastComment || !!_lastComment.__draft;
-    },
-
-    _getLastComment() {
-      return this._orderedComments[this._orderedComments.length - 1] || {};
-    },
-
-    _handleEKey(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(actionIsCollapse) {
-      const comments =
-          Polymer.dom(this.root).querySelectorAll('gr-diff-comment');
-      for (const comment of comments) {
-        comment.collapsed = actionIsCollapse;
-      }
-    },
-
-    /**
-     * Sets the initial state of the comment thread.
-     * Expands the thread if one of the following is true:
-     * - last {UNRESOLVED_EXPAND_COUNT} comments expanded by default if the
-     * thread is unresolved,
-     * - it's a robot comment.
-     */
-    _setInitialExpandedState() {
-      if (this._orderedComments) {
-        for (let i = 0; i < this._orderedComments.length; i++) {
-          const comment = this._orderedComments[i];
-          const isRobotComment = !!comment.robot_id;
-          // False if it's an unresolved comment under UNRESOLVED_EXPAND_COUNT.
-          const resolvedThread = !this.unresolved ||
-                this._orderedComments.length - i - 1 >= UNRESOLVED_EXPAND_COUNT;
-          comment.collapsed = !isRobotComment && resolvedThread;
-        }
-      }
-    },
-
-    _sortedComments(comments) {
-      return comments.slice().sort((c1, c2) => {
-        const c1Date = c1.__date || util.parseDate(c1.updated);
-        const c2Date = c2.__date || util.parseDate(c2.updated);
-        const dateCompare = c1Date - c2Date;
-        // Ensure drafts are at the end. There should only be one but in edge
-        // cases could be more. In the unlikely event two drafts are being
-        // compared, use the typical date compare.
-        if (c2.__draft && !c1.__draft ) { return -1; }
-        if (c1.__draft && !c2.__draft ) { return 1; }
-        if (dateCompare === 0 && (!c1.id || !c1.id.localeCompare)) { return 0; }
-        // If same date, fall back to sorting by id.
-        return dateCompare ? dateCompare : c1.id.localeCompare(c2.id);
-      });
-    },
-
-    _createReplyComment(parent, content, opt_isEditing,
-        opt_unresolved) {
-      this.$.reporting.recordDraftInteraction();
-      const reply = this._newReply(
-          this._orderedComments[this._orderedComments.length - 1].id,
-          parent.line,
-          content,
-          opt_unresolved,
-          parent.range);
-
-      // 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 (let 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(() => {
-          const commentEl = this._commentElWithDraftID(reply.__draftID);
-          commentEl.save();
-        }, 1);
-      }
-    },
-
-    _isDraft(comment) {
-      return !!comment.__draft;
-    },
-
-    /**
-     * @param {boolean=} opt_quote
-     */
-    _processCommentReply(opt_quote) {
-      const comment = this._lastComment;
-      let quoteStr;
-      if (opt_quote) {
-        const msg = comment.message;
-        quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n';
-      }
-      this._createReplyComment(comment, quoteStr, true, comment.unresolved);
-    },
-
-    _handleCommentReply(e) {
-      this._processCommentReply();
-    },
-
-    _handleCommentQuote(e) {
-      this._processCommentReply(true);
-    },
-
-    _handleCommentAck(e) {
-      const comment = this._lastComment;
-      this._createReplyComment(comment, 'Ack', false, false);
-    },
-
-    _handleCommentDone(e) {
-      const comment = this._lastComment;
-      this._createReplyComment(comment, 'Done', false, false);
-    },
-
-    _handleCommentFix(e) {
-      const comment = e.detail.comment;
-      const msg = comment.message;
-      const quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n';
-      const response = quoteStr + 'Please Fix';
-      this._createReplyComment(comment, response, false, true);
-    },
-
-    _commentElWithDraftID(id) {
-      const els = Polymer.dom(this.root).querySelectorAll('gr-diff-comment');
-      for (const el of els) {
-        if (el.comment.id === id || el.comment.__draftID === id) {
-          return el;
-        }
-      }
-      return null;
-    },
-
-    _newReply(inReplyTo, opt_lineNum, opt_message, opt_unresolved,
-        opt_range) {
-      const d = this._newDraft(opt_lineNum);
-      d.in_reply_to = inReplyTo;
-      d.range = opt_range;
-      if (opt_message != null) {
-        d.message = opt_message;
-      }
-      if (opt_unresolved !== undefined) {
-        d.unresolved = opt_unresolved;
-      }
-      return d;
-    },
-
-    /**
-     * @param {number=} opt_lineNum
-     * @param {!Object=} opt_range
-     */
-    _newDraft(opt_lineNum, opt_range) {
-      const d = {
-        __draft: true,
-        __draftID: Math.random().toString(36),
-        __date: new Date(),
-        path: this.path,
-        patchNum: this.patchNum,
-        side: this._getSide(this.isOnParent),
-        __commentSide: this.commentSide,
-      };
-      if (opt_lineNum) {
-        d.line = opt_lineNum;
-      }
-      if (opt_range) {
-        d.range = opt_range;
-      }
-      if (this.parentIndex) {
-        d.parent = this.parentIndex;
-      }
-      return d;
-    },
-
-    _getSide(isOnParent) {
-      if (isOnParent) { return 'PARENT'; }
-      return 'REVISION';
-    },
-
-    _computeRootId(comments) {
-      // Keep the root ID even if the comment was removed, so that notification
-      // to sync will know which thread to remove.
-      if (!comments.base.length) { return this.rootId; }
-      const rootComment = comments.base[0];
-      return rootComment.id || rootComment.__draftID;
-    },
-
-    _handleCommentDiscard(e) {
-      const diffCommentEl = Polymer.dom(e).rootTarget;
-      const comment = diffCommentEl.comment;
-      const 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.fireRemoveSelf();
-      }
-      this._handleCommentSavedOrDiscarded(e);
-
-      // Check to see if there are any other open comments getting edited and
-      // set the local storage value to its message value.
-      for (const changeComment of this.comments) {
-        if (changeComment.__editing) {
-          const commentLocation = {
-            changeNum: this.changeNum,
-            patchNum: this.patchNum,
-            path: changeComment.path,
-            line: changeComment.line,
-          };
-          return this.$.storage.setDraftComment(commentLocation,
-              changeComment.message);
-        }
-      }
-    },
-
-    _handleCommentSavedOrDiscarded(e) {
-      this.dispatchEvent(new CustomEvent('thread-changed',
-          {detail: {rootId: this.rootId, path: this.path},
-            bubbles: false}));
-    },
-
-    _handleCommentUpdate(e) {
-      const comment = e.detail.comment;
-      const index = this._indexOf(comment, this.comments);
-      if (index === -1) {
-        // This should never happen: comment belongs to another thread.
-        console.warn('Comment update for another comment thread.');
-        return;
-      }
-      this.set(['comments', index], comment);
-      // Because of the way we pass these comment objects around by-ref, in
-      // combination with the fact that Polymer does dirty checking in
-      // observers, the this.set() call above will not cause a thread update in
-      // some situations.
-      this.updateThreadProperties();
-    },
-
-    _indexOf(comment, arr) {
-      for (let i = 0; i < arr.length; i++) {
-        const c = arr[i];
-        if ((c.__draftID != null && c.__draftID == comment.__draftID) ||
-            (c.id != null && c.id == comment.id)) {
-          return i;
-        }
-      }
-      return -1;
-    },
-
-    _computeHostClass(unresolved) {
-      return unresolved ? 'unresolved' : '';
-    },
-
-    /**
-     * Load the project config when a project name has been provided.
-     * @param {string} name The project name.
-     */
-    _projectNameChanged(name) {
-      if (!name) { return; }
-      this.$.restAPI.getProjectConfig(name).then(config => {
-        this._projectConfig = config;
-      });
-    },
-  });
-})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
deleted file mode 100644
index 1881497..0000000
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ /dev/null
@@ -1,751 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2015 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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-diff-comment-thread</title>
-
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../scripts/util.js"></script>
-
-<link rel="import" href="gr-diff-comment-thread.html">
-
-<script>void(0);</script>
-
-<test-fixture id="basic">
-  <template>
-    <gr-diff-comment-thread></gr-diff-comment-thread>
-  </template>
-</test-fixture>
-
-<test-fixture id="withComment">
-  <template>
-    <gr-diff-comment-thread></gr-diff-comment-thread>
-  </template>
-</test-fixture>
-
-<script>
-  suite('gr-diff-comment-thread tests', () => {
-    let element;
-    let sandbox;
-
-    setup(() => {
-      sandbox = sinon.sandbox.create();
-      stub('gr-rest-api-interface', {
-        getLoggedIn() { return Promise.resolve(false); },
-      });
-      sandbox = sinon.sandbox.create();
-      element = fixture('basic');
-    });
-
-    teardown(() => {
-      sandbox.restore();
-    });
-
-    test('comments are sorted correctly', () => {
-      const comments = [
-        {
-          message: 'i like you, too',
-          in_reply_to: 'sallys_confession',
-          __date: new Date('2015-12-25'),
-        }, {
-          id: 'sallys_confession',
-          message: 'i like you, jack',
-          updated: '2015-12-24 15:00:20.396000000',
-        }, {
-          id: 'sally_to_dr_finklestein',
-          message: 'i’m running away',
-          updated: '2015-10-31 09:00:20.396000000',
-        }, {
-          id: 'sallys_defiance',
-          in_reply_to: 'sally_to_dr_finklestein',
-          message: 'i will poison you so i can get away',
-          updated: '2015-10-31 15:00:20.396000000',
-        }, {
-          id: 'dr_finklesteins_response',
-          in_reply_to: 'sally_to_dr_finklestein',
-          message: 'no i will pull a thread and your arm will fall off',
-          updated: '2015-10-31 11:00:20.396000000',
-        }, {
-          id: 'sallys_mission',
-          message: 'i have to find santa',
-          updated: '2015-12-24 15:00:20.396000000',
-        },
-      ];
-      const results = element._sortedComments(comments);
-      assert.deepEqual(results, [
-        {
-          id: 'sally_to_dr_finklestein',
-          message: 'i’m running away',
-          updated: '2015-10-31 09:00:20.396000000',
-        }, {
-          id: 'dr_finklesteins_response',
-          in_reply_to: 'sally_to_dr_finklestein',
-          message: 'no i will pull a thread and your arm will fall off',
-          updated: '2015-10-31 11:00:20.396000000',
-        }, {
-          id: 'sallys_defiance',
-          in_reply_to: 'sally_to_dr_finklestein',
-          message: 'i will poison you so i can get away',
-          updated: '2015-10-31 15:00:20.396000000',
-        }, {
-          id: 'sallys_confession',
-          message: 'i like you, jack',
-          updated: '2015-12-24 15:00:20.396000000',
-        }, {
-          id: 'sallys_mission',
-          message: 'i have to find santa',
-          updated: '2015-12-24 15:00:20.396000000',
-        }, {
-          message: 'i like you, too',
-          in_reply_to: 'sallys_confession',
-          __date: new Date('2015-12-25'),
-        },
-      ]);
-    });
-
-    test('addOrEditDraft w/ edit draft', () => {
-      element.comments = [{
-        id: 'jacks_reply',
-        message: 'i like you, too',
-        in_reply_to: 'sallys_confession',
-        updated: '2015-12-25 15:00:20.396000000',
-        __draft: true,
-      }];
-      const commentElStub = sandbox.stub(element, '_commentElWithDraftID',
-          () => { return {}; });
-      const addDraftStub = sandbox.stub(element, 'addDraft');
-
-      element.addOrEditDraft(123);
-
-      assert.isTrue(commentElStub.called);
-      assert.isFalse(addDraftStub.called);
-    });
-
-    test('addOrEditDraft w/o edit draft', () => {
-      element.comments = [];
-      const commentElStub = sandbox.stub(element, '_commentElWithDraftID',
-          () => { return {}; });
-      const addDraftStub = sandbox.stub(element, 'addDraft');
-
-      element.addOrEditDraft(123);
-
-      assert.isFalse(commentElStub.called);
-      assert.isTrue(addDraftStub.called);
-    });
-
-    test('_hideActions', () => {
-      let showActions = true;
-      const lastComment = {};
-      assert.equal(element._hideActions(showActions, lastComment), false);
-      showActions = false;
-      assert.equal(element._hideActions(showActions, lastComment), true);
-      showActions = true;
-      lastComment.__draft = true;
-      assert.equal(element._hideActions(showActions, lastComment), true);
-    });
-
-    test('setting project name loads the project config', done => {
-      const projectName = 'foo/bar/baz';
-      const getProjectStub = sandbox.stub(element.$.restAPI, 'getProjectConfig')
-          .returns(Promise.resolve({}));
-      element.projectName = projectName;
-      flush(() => {
-        assert.isTrue(getProjectStub.calledWithExactly(projectName));
-        done();
-      });
-    });
-
-    test('optionally show file path', () => {
-      // Path info doesn't exist when showFilePath is false. Because it's in a
-      // dom-if it is not yet in the dom.
-      assert.isNotOk(element.$$('.pathInfo'));
-
-      sandbox.stub(Gerrit.Nav, 'getUrlForDiffById');
-      element.changeNum = 123;
-      element.projectName = 'test project';
-      element.path = 'path/to/file';
-      element.patchNum = 3;
-      element.lineNum = 5;
-      element.showFilePath = true;
-      flushAsynchronousOperations();
-      assert.isOk(element.$$('.pathInfo'));
-      assert.notEqual(getComputedStyle(element.$$('.pathInfo')).display,
-          'none');
-      assert.isTrue(Gerrit.Nav.getUrlForDiffById.lastCall.calledWithExactly(
-          element.changeNum, element.projectName, element.path,
-          element.patchNum, null, element.lineNum));
-    });
-
-    test('_computeDisplayPath', () => {
-      const path = 'path/to/file';
-      assert.equal(element._computeDisplayPath(path), 'path/to/file');
-
-      element.lineNum = 5;
-      assert.equal(element._computeDisplayPath(path), 'path/to/file#5');
-    });
-  });
-
-  suite('comment action tests', () => {
-    let element;
-    let sandbox;
-
-    setup(() => {
-      sandbox = sinon.sandbox.create();
-      stub('gr-rest-api-interface', {
-        getLoggedIn() { return Promise.resolve(false); },
-        saveDiffDraft() {
-          return Promise.resolve({
-            ok: true,
-            text() {
-              return Promise.resolve(')]}\'\n' +
-                  JSON.stringify({
-                    id: '7afa4931_de3d65bd',
-                    path: '/path/to/file.txt',
-                    line: 5,
-                    in_reply_to: 'baf0414d_60047215',
-                    updated: '2015-12-21 02:01:10.850000000',
-                    message: 'Done',
-                  }));
-            },
-          });
-        },
-        deleteDiffDraft() { return Promise.resolve({ok: true}); },
-      });
-      element = fixture('withComment');
-      element.comments = [{
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 'baf0414d_60047215',
-        line: 5,
-        message: 'is this a crossover episode!?',
-        updated: '2015-12-08 19:48:33.843000000',
-        path: '/path/to/file.txt',
-      }];
-      flushAsynchronousOperations();
-    });
-
-    teardown(() => {
-      sandbox.restore();
-    });
-
-    test('reply', () => {
-      const commentEl = element.$$('gr-diff-comment');
-      const reportStub = sandbox.stub(element.$.reporting,
-          'recordDraftInteraction');
-      assert.ok(commentEl);
-
-      const replyBtn = element.$.replyBtn;
-      MockInteractions.tap(replyBtn);
-      flushAsynchronousOperations();
-
-      const drafts = element._orderedComments.filter(c => {
-        return c.__draft == true;
-      });
-      assert.equal(drafts.length, 1);
-      assert.notOk(drafts[0].message, 'message should be empty');
-      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-      assert.isTrue(reportStub.calledOnce);
-    });
-
-    test('quote reply', () => {
-      const commentEl = element.$$('gr-diff-comment');
-      const reportStub = sandbox.stub(element.$.reporting,
-          'recordDraftInteraction');
-      assert.ok(commentEl);
-
-      const quoteBtn = element.$.quoteBtn;
-      MockInteractions.tap(quoteBtn);
-      flushAsynchronousOperations();
-
-      const drafts = element._orderedComments.filter(c => {
-        return c.__draft == true;
-      });
-      assert.equal(drafts.length, 1);
-      assert.equal(drafts[0].message, '> is this a crossover episode!?\n\n');
-      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-      assert.isTrue(reportStub.calledOnce);
-    });
-
-    test('quote reply multiline', () => {
-      const reportStub = sandbox.stub(element.$.reporting,
-          'recordDraftInteraction');
-      element.comments = [{
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 'baf0414d_60047215',
-        line: 5,
-        message: 'is this a crossover episode!?\nIt might be!',
-        updated: '2015-12-08 19:48:33.843000000',
-      }];
-      flushAsynchronousOperations();
-
-      const commentEl = element.$$('gr-diff-comment');
-      assert.ok(commentEl);
-
-      const quoteBtn = element.$.quoteBtn;
-      MockInteractions.tap(quoteBtn);
-      flushAsynchronousOperations();
-
-      const drafts = element._orderedComments.filter(c => {
-        return c.__draft == true;
-      });
-      assert.equal(drafts.length, 1);
-      assert.equal(drafts[0].message,
-          '> is this a crossover episode!?\n> It might be!\n\n');
-      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-      assert.isTrue(reportStub.calledOnce);
-    });
-
-    test('ack', done => {
-      const reportStub = sandbox.stub(element.$.reporting,
-          'recordDraftInteraction');
-      element.changeNum = '42';
-      element.patchNum = '1';
-
-      const commentEl = element.$$('gr-diff-comment');
-      assert.ok(commentEl);
-
-      const ackBtn = element.$.ackBtn;
-      MockInteractions.tap(ackBtn);
-      flush(() => {
-        const drafts = element.comments.filter(c => {
-          return c.__draft == true;
-        });
-        assert.equal(drafts.length, 1);
-        assert.equal(drafts[0].message, 'Ack');
-        assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-        assert.equal(drafts[0].unresolved, false);
-        assert.isTrue(reportStub.calledOnce);
-        done();
-      });
-    });
-
-    test('done', done => {
-      const reportStub = sandbox.stub(element.$.reporting,
-          'recordDraftInteraction');
-      element.changeNum = '42';
-      element.patchNum = '1';
-      const commentEl = element.$$('gr-diff-comment');
-      assert.ok(commentEl);
-
-      const doneBtn = element.$.doneBtn;
-      MockInteractions.tap(doneBtn);
-      flush(() => {
-        const drafts = element.comments.filter(c => {
-          return c.__draft == true;
-        });
-        assert.equal(drafts.length, 1);
-        assert.equal(drafts[0].message, 'Done');
-        assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-        assert.isFalse(drafts[0].unresolved);
-        assert.isTrue(reportStub.calledOnce);
-        done();
-      });
-    });
-
-    test('save', done => {
-      element.changeNum = '42';
-      element.patchNum = '1';
-      element.path = '/path/to/file.txt';
-      const commentEl = element.$$('gr-diff-comment');
-      assert.ok(commentEl);
-
-      const saveOrDiscardStub = sandbox.stub();
-      element.addEventListener('thread-changed', saveOrDiscardStub);
-      element.$$('gr-diff-comment')._fireSave();
-
-      flush(() => {
-        assert.isTrue(saveOrDiscardStub.called);
-        assert.equal(saveOrDiscardStub.lastCall.args[0].detail.rootId,
-            'baf0414d_60047215');
-        assert.equal(element.rootId, 'baf0414d_60047215');
-        assert.equal(saveOrDiscardStub.lastCall.args[0].detail.path,
-            '/path/to/file.txt');
-        done();
-      });
-    });
-
-    test('please fix', done => {
-      element.changeNum = '42';
-      element.patchNum = '1';
-      const commentEl = element.$$('gr-diff-comment');
-      assert.ok(commentEl);
-      commentEl.addEventListener('create-fix-comment', () => {
-        const drafts = element._orderedComments.filter(c => {
-          return c.__draft == true;
-        });
-        assert.equal(drafts.length, 1);
-        assert.equal(
-            drafts[0].message, '> is this a crossover episode!?\n\nPlease Fix');
-        assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-        assert.isTrue(drafts[0].unresolved);
-        done();
-      });
-      commentEl.fire('create-fix-comment', {comment: commentEl.comment},
-          {bubbles: false});
-    });
-
-    test('discard', done => {
-      element.changeNum = '42';
-      element.patchNum = '1';
-      element.path = '/path/to/file.txt';
-      element.push('comments', element._newReply(
-          element.comments[0].id,
-          element.comments[0].line,
-          element.comments[0].path,
-          'it’s pronouced jiff, not giff'));
-      flushAsynchronousOperations();
-
-      const saveOrDiscardStub = sandbox.stub();
-      element.addEventListener('thread-changed', saveOrDiscardStub);
-      const draftEl =
-          Polymer.dom(element.root).querySelectorAll('gr-diff-comment')[1];
-      assert.ok(draftEl);
-      draftEl.addEventListener('comment-discard', () => {
-        const drafts = element.comments.filter(c => {
-          return c.__draft == true;
-        });
-        assert.equal(drafts.length, 0);
-        assert.isTrue(saveOrDiscardStub.called);
-        assert.equal(saveOrDiscardStub.lastCall.args[0].detail.rootId,
-            element.rootId);
-        assert.equal(saveOrDiscardStub.lastCall.args[0].detail.path,
-            element.path);
-        done();
-      });
-      draftEl.fire('comment-discard', {comment: draftEl.comment},
-          {bubbles: false});
-    });
-
-    test('discard with a single comment still fires event with previous rootId',
-        done => {
-          element.changeNum = '42';
-          element.patchNum = '1';
-          element.path = '/path/to/file.txt';
-          element.comments = [];
-          element.addOrEditDraft('1');
-          flushAsynchronousOperations();
-          const rootId = element.rootId;
-          assert.isOk(rootId);
-
-          const saveOrDiscardStub = sandbox.stub();
-          element.addEventListener('thread-changed', saveOrDiscardStub);
-          const draftEl =
-          Polymer.dom(element.root).querySelectorAll('gr-diff-comment')[0];
-          assert.ok(draftEl);
-          draftEl.addEventListener('comment-discard', () => {
-            assert.equal(element.comments.length, 0);
-            assert.isTrue(saveOrDiscardStub.called);
-            assert.equal(saveOrDiscardStub.lastCall.args[0].detail.rootId,
-                rootId);
-            assert.equal(saveOrDiscardStub.lastCall.args[0].detail.path,
-                element.path);
-            done();
-          });
-          draftEl.fire('comment-discard', {comment: draftEl.comment},
-          {bubbles: false});
-        });
-
-    test('first editing comment does not add __otherEditing attribute', () => {
-      element.comments = [{
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 'baf0414d_60047215',
-        line: 5,
-        message: 'is this a crossover episode!?',
-        updated: '2015-12-08 19:48:33.843000000',
-        __draft: true,
-      }];
-
-      const replyBtn = element.$.replyBtn;
-      MockInteractions.tap(replyBtn);
-      flushAsynchronousOperations();
-
-      const editing = element._orderedComments.filter(c => {
-        return c.__editing == true;
-      });
-      assert.equal(editing.length, 1);
-      assert.equal(!!editing[0].__otherEditing, false);
-    });
-
-    test('When not editing other comments, local storage not set' +
-        ' after discard', done => {
-      element.changeNum = '42';
-      element.patchNum = '1';
-      element.comments = [{
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 'baf0414d_60047215',
-        line: 5,
-        message: 'is this a crossover episode!?',
-        updated: '2015-12-08 19:48:31.843000000',
-      },
-      {
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        __draftID: '1',
-        in_reply_to: 'baf0414d_60047215',
-        line: 5,
-        message: 'yes',
-        updated: '2015-12-08 19:48:32.843000000',
-        __draft: true,
-        __editing: true,
-      },
-      {
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        __draftID: '2',
-        in_reply_to: 'baf0414d_60047215',
-        line: 5,
-        message: 'no',
-        updated: '2015-12-08 19:48:33.843000000',
-        __draft: true,
-      }];
-      const storageStub = sinon.stub(element.$.storage, 'setDraftComment');
-      flushAsynchronousOperations();
-
-      const draftEl =
-      Polymer.dom(element.root).querySelectorAll('gr-diff-comment')[1];
-      assert.ok(draftEl);
-      draftEl.addEventListener('comment-discard', () => {
-        assert.isFalse(storageStub.called);
-        storageStub.restore();
-        done();
-      });
-      draftEl.fire('comment-discard', {comment: draftEl.comment},
-          {bubbles: false});
-    });
-
-    test('comment-update', () => {
-      const commentEl = element.$$('gr-diff-comment');
-      const updatedComment = {
-        id: element.comments[0].id,
-        foo: 'bar',
-      };
-      commentEl.fire('comment-update', {comment: updatedComment});
-      assert.strictEqual(element.comments[0], updatedComment);
-    });
-
-    suite('jack and sally comment data test consolidation', () => {
-      setup(() => {
-        element.comments = [
-          {
-            id: 'jacks_reply',
-            message: 'i like you, too',
-            in_reply_to: 'sallys_confession',
-            updated: '2015-12-25 15:00:20.396000000',
-            unresolved: false,
-          }, {
-            id: 'sallys_confession',
-            in_reply_to: 'nonexistent_comment',
-            message: 'i like you, jack',
-            updated: '2015-12-24 15:00:20.396000000',
-          }, {
-            id: 'sally_to_dr_finklestein',
-            in_reply_to: 'nonexistent_comment',
-            message: 'i’m running away',
-            updated: '2015-10-31 09:00:20.396000000',
-          }, {
-            id: 'sallys_defiance',
-            message: 'i will poison you so i can get away',
-            updated: '2015-10-31 15:00:20.396000000',
-          }];
-      });
-
-      test('orphan replies', () => {
-        assert.equal(4, element._orderedComments.length);
-      });
-
-      test('keyboard shortcuts', () => {
-        const expandCollapseStub =
-            sinon.stub(element, '_expandCollapseComments');
-        MockInteractions.pressAndReleaseKeyOn(element, 69, null, 'e');
-        assert.isTrue(expandCollapseStub.lastCall.calledWith(false));
-
-        MockInteractions.pressAndReleaseKeyOn(element, 69, 'shift', 'e');
-        assert.isTrue(expandCollapseStub.lastCall.calledWith(true));
-      });
-
-      test('comment in_reply_to is either null or most recent comment', () => {
-        element._createReplyComment(element.comments[3], 'dummy', true);
-        flushAsynchronousOperations();
-        assert.equal(element._orderedComments.length, 5);
-        assert.equal(element._orderedComments[4].in_reply_to, 'jacks_reply');
-      });
-
-      test('resolvable comments', () => {
-        assert.isFalse(element.unresolved);
-        element._createReplyComment(element.comments[3], 'dummy', true, true);
-        flushAsynchronousOperations();
-        assert.isTrue(element.unresolved);
-      });
-
-      test('_setInitialExpandedState', () => {
-        element.unresolved = true;
-        element._setInitialExpandedState();
-        for (let i = 0; i < element.comments.length; i++) {
-          assert.isFalse(element.comments[i].collapsed);
-        }
-        element.unresolved = false;
-        element._setInitialExpandedState();
-        for (let i = 0; i < element.comments.length; i++) {
-          assert.isTrue(element.comments[i].collapsed);
-        }
-        for (let i = 0; i < element.comments.length; i++) {
-          element.comments[i].robot_id = 123;
-        }
-        element._setInitialExpandedState();
-        for (let i = 0; i < element.comments.length; i++) {
-          assert.isFalse(element.comments[i].collapsed);
-        }
-      });
-    });
-
-    test('_computeHostClass', () => {
-      assert.equal(element._computeHostClass(true), 'unresolved');
-      assert.equal(element._computeHostClass(false), '');
-    });
-
-    test('addDraft sets unresolved state correctly', () => {
-      let unresolved = true;
-      element.comments = [];
-      element.addDraft(null, null, unresolved);
-      assert.equal(element.comments[0].unresolved, true);
-
-      unresolved = false; // comment should get added as actually resolved.
-      element.comments = [];
-      element.addDraft(null, null, unresolved);
-      assert.equal(element.comments[0].unresolved, false);
-
-      element.comments = [];
-      element.addDraft();
-      assert.equal(element.comments[0].unresolved, true);
-    });
-
-    test('_newDraft', () => {
-      element.commentSide = 'left';
-      element.patchNum = 3;
-      const draft = element._newDraft();
-      assert.equal(draft.__commentSide, 'left');
-      assert.equal(draft.patchNum, 3);
-    });
-
-    test('new comment gets created', () => {
-      element.comments = [];
-      element.addOrEditDraft(1);
-      assert.equal(element.comments.length, 1);
-      // Mock a submitted comment.
-      element.comments[0].id = element.comments[0].__draftID;
-      element.comments[0].__draft = false;
-      element.addOrEditDraft(1);
-      assert.equal(element.comments.length, 2);
-    });
-
-    test('unresolved label', () => {
-      element.unresolved = false;
-      assert.isTrue(element.$.unresolvedLabel.hasAttribute('hidden'));
-      element.unresolved = true;
-      assert.isFalse(element.$.unresolvedLabel.hasAttribute('hidden'));
-    });
-
-    test('draft comments are at the end of orderedComments', () => {
-      element.comments = [{
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 2,
-        line: 5,
-        message: 'Earlier draft',
-        updated: '2015-12-08 19:48:33.843000000',
-        __draft: true,
-      },
-      {
-        author: {
-          name: 'Mr. Peanutbutter2',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 1,
-        line: 5,
-        message: 'This comment was left last but is not a draft',
-        updated: '2015-12-10 19:48:33.843000000',
-      },
-      {
-        author: {
-          name: 'Mr. Peanutbutter2',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 3,
-        line: 5,
-        message: 'Later draft',
-        updated: '2015-12-09 19:48:33.843000000',
-        __draft: true,
-      }];
-      assert.equal(element._orderedComments[0].id, '1');
-      assert.equal(element._orderedComments[1].id, '2');
-      assert.equal(element._orderedComments[2].id, '3');
-    });
-
-    test('reflects lineNum and commentSide to attributes', () => {
-      element.lineNum = 7;
-      element.commentSide = 'left';
-
-      assert.equal(element.getAttribute('line-num'), '7');
-      assert.equal(element.getAttribute('comment-side'), 'left');
-    });
-
-    test('reflects range to JSON serialized attribute if set', () => {
-      element.range = {
-        start_line: 4,
-        end_line: 5,
-        start_character: 6,
-        end_character: 7,
-      };
-
-      assert.deepEqual(
-          JSON.parse(element.getAttribute('range')),
-          {start_line: 4, end_line: 5, start_character: 6, end_character: 7});
-    });
-
-    test('removes range attribute if range is unset', () => {
-      element.range = {
-        start_line: 4,
-        end_line: 5,
-        start_character: 6,
-        end_character: 7,
-      };
-      element.range = undefined;
-
-      assert.notOk(element.hasAttribute('range'));
-    });
-  });
-</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
deleted file mode 100644
index 8633b0e..0000000
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ /dev/null
@@ -1,388 +0,0 @@
-<!--
-@license
-Copyright (C) 2015 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.
--->
-
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../styles/shared-styles.html">
-<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
-<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
-<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
-<link rel="import" href="../../shared/gr-button/gr-button.html">
-<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
-<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
-<link rel="import" href="../../shared/gr-formatted-text/gr-formatted-text.html">
-<link rel="import" href="../../shared/gr-icons/gr-icons.html">
-<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
-<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-<link rel="import" href="../../shared/gr-storage/gr-storage.html">
-<link rel="import" href="../../shared/gr-textarea/gr-textarea.html">
-<link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
-<link rel="import" href="../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html">
-<script src="../../../scripts/rootElement.js"></script>
-
-<dom-module id="gr-diff-comment">
-  <template>
-    <style include="shared-styles">
-      :host {
-        display: block;
-        font-family: var(--font-family);
-        padding: .7em .7em;
-        --iron-autogrow-textarea: {
-          box-sizing: border-box;
-          padding: 2px;
-        };
-      }
-      :host([disabled]) {
-        pointer-events: none;
-      }
-      :host([disabled]) .actions,
-      :host([disabled]) .robotActions,
-      :host([disabled]) .date {
-        opacity: .5;
-      }
-      :host([discarding]) {
-        display: none;
-      }
-      .header {
-        align-items: baseline;
-        cursor: pointer;
-        display: flex;
-        font-family: 'Open Sans', sans-serif;
-        margin: -.7em -.7em 0 -.7em;
-        padding: .7em;
-      }
-      .container.collapsed .header {
-        margin-bottom: -.7em;
-      }
-      .headerMiddle {
-        color: var(--deemphasized-text-color);
-        flex: 1;
-        overflow: hidden;
-      }
-      .authorName,
-      .draftLabel,
-      .draftTooltip {
-        font-weight: var(--font-weight-bold);
-      }
-      .draftLabel,
-      .draftTooltip {
-        color: var(--deemphasized-text-color);
-        display: none;
-      }
-      .date {
-        justify-content: flex-end;
-        margin-left: 5px;
-        min-width: 4.5em;
-        text-align: right;
-        white-space: nowrap;
-      }
-      span.date {
-        color: var(--deemphasized-text-color);
-      }
-      span.date:hover {
-        text-decoration: underline;
-      }
-      .actions {
-        display: flex;
-        justify-content: flex-end;
-        padding-top: 0;
-      }
-      .action {
-        margin-left: 1em;
-      }
-      .robotActions {
-        display: flex;
-        justify-content: flex-start;
-        padding-top: 0;
-      }
-      .robotActions .action {
-        /* Keep button text lined up with output text */
-        margin-left: -.3rem;
-        margin-right: 1em;
-      }
-      .rightActions {
-        display: flex;
-        justify-content: flex-end;
-      }
-      .editMessage {
-        display: none;
-        margin: .5em 0;
-        width: 100%;
-      }
-      .container:not(.draft) .actions .hideOnPublished {
-        display: none;
-      }
-      .draft .reply,
-      .draft .quote,
-      .draft .ack,
-      .draft .done {
-        display: none;
-      }
-      .draft .draftLabel,
-      .draft .draftTooltip {
-        display: inline;
-      }
-      .draft:not(.editing) .save,
-      .draft:not(.editing) .cancel {
-        display: none;
-      }
-      .editing .message,
-      .editing .reply,
-      .editing .quote,
-      .editing .ack,
-      .editing .done,
-      .editing .edit,
-      .editing .discard,
-      .editing .unresolved {
-        display: none;
-      }
-      .editing .editMessage {
-        display: block;
-      }
-      .show-hide {
-        margin-left: .4em;
-      }
-      .robotId {
-        color: var(--deemphasized-text-color);
-        margin-bottom: .8em;
-        margin-top: -.4em;
-      }
-      .robotIcon {
-        margin-right: .2em;
-        /* because of the antenna of the robot, it looks off center even when it
-         is centered. artificially adjust margin to account for this. */
-        margin-top: -.3em;
-      }
-      .runIdInformation {
-        margin: .7em 0;
-      }
-      .robotRun {
-        margin-left: .5em;
-      }
-      .robotRunLink {
-        margin-left: .5em;
-      }
-      input.show-hide {
-        display: none;
-      }
-      label.show-hide {
-        color: var(--comment-text-color);
-        cursor: pointer;
-        display: block;
-        font-size: .8rem;
-        height: 1.1em;
-        margin-top: .1em;
-      }
-      #container .collapsedContent {
-        display: none;
-      }
-      #container.collapsed {
-        padding-bottom: 3px;
-      }
-      #container.collapsed .collapsedContent {
-        display: block;
-        overflow: hidden;
-        padding-left: 5px;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-      }
-      #container.collapsed .actions,
-      #container.collapsed gr-formatted-text,
-      #container.collapsed gr-textarea {
-        display: none;
-      }
-      .resolve,
-      .unresolved {
-        align-items: center;
-        display: flex;
-        flex: 1;
-        margin: 0;
-      }
-      .resolve label {
-        color: var(--comment-text-color);
-      }
-      gr-dialog .main {
-        display: flex;
-        flex-direction: column;
-        width: 100%;
-      }
-      #deleteBtn {
-        display: none;
-        --gr-button: {
-          color: var(--deemphasized-text-color);
-          padding: 0;
-        }
-      }
-      #deleteBtn.showDeleteButtons {
-        display: block;
-      }
-    </style>
-    <div id="container" class="container">
-      <div class="header" id="header" on-tap="_handleToggleCollapsed">
-        <div class="headerLeft">
-          <span class="authorName">[[comment.author.name]]</span>
-          <span class="draftLabel">DRAFT</span>
-          <gr-tooltip-content class="draftTooltip"
-              has-tooltip
-              title="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."
-              max-width="20em"
-              show-icon></gr-tooltip-content>
-        </div>
-        <div class="headerMiddle">
-          <span class="collapsedContent">[[comment.message]]</span>
-        </div>
-        <gr-button
-            id="deleteBtn"
-            link
-            secondary
-            class$="action delete [[_computeDeleteButtonClass(_isAdmin, draft)]]"
-            on-tap="_handleCommentDelete">
-          (Delete)
-        </gr-button>
-        <span class="date" on-tap="_handleAnchorTap">
-          <gr-date-formatter
-              has-tooltip
-              date-str="[[comment.updated]]"></gr-date-formatter>
-        </span>
-        <div class="show-hide">
-          <label class="show-hide">
-            <input type="checkbox" class="show-hide"
-               checked$="[[collapsed]]"
-               on-change="_handleToggleCollapsed">
-            [[_computeShowHideText(collapsed)]]
-          </label>
-        </div>
-      </div>
-      <div class="body">
-        <template is="dom-if" if="[[comment.robot_id]]">
-          <div class="robotId" hidden$="[[collapsed]]">
-            <iron-icon class="robotIcon" icon="gr-icons:robot"></iron-icon>
-            [[comment.robot_id]]
-          </div>
-        </template>
-        <template is="dom-if" if="[[editing]]">
-          <gr-textarea
-              id="editTextarea"
-              class="editMessage"
-              autocomplete="on"
-              monospace
-              disabled="{{disabled}}"
-              rows="4"
-              text="{{_messageText}}"></gr-textarea>
-        </template>
-        <!--The message class is needed to ensure selectability from
-        gr-diff-selection.-->
-        <gr-formatted-text class="message"
-            content="[[comment.message]]"
-            no-trailing-margin="[[!comment.__draft]]"
-            collapsed="[[collapsed]]"
-            config="[[projectConfig.commentlinks]]"></gr-formatted-text>
-        <div hidden$="[[!comment.robot_run_id]]" class="message">
-          <div class="runIdInformation" hidden$="[[collapsed]]">
-            Run ID:
-            <template is="dom-if" if="[[comment.url]]">
-              <a class="robotRunLink" href$="[[comment.url]]">
-                <span class="robotRun link">[[comment.robot_run_id]]</span>
-              </a>
-            </template>
-            <template is="dom-if" if="[[!comment.url]]">
-              <span class="robotRun text">[[comment.robot_run_id]]</span>
-            </template>
-          </div>
-        </div>
-        <div class="actions humanActions" hidden$="[[!_showHumanActions]]">
-          <div class="action resolve hideOnPublished">
-            <label>
-              <input type="checkbox"
-                  id="resolvedCheckbox"
-                  checked="[[resolved]]"
-                  on-change="_handleToggleResolved">
-              Resolved
-            </label>
-          </div>
-          <div class="rightActions">
-            <gr-button
-                link
-                secondary
-                class="action cancel hideOnPublished"
-                on-tap="_handleCancel">Cancel</gr-button>
-            <gr-button
-                link
-                secondary
-                class="action discard hideOnPublished"
-                on-tap="_handleDiscard">Discard</gr-button>
-            <gr-button
-                link
-                secondary
-                class="action edit hideOnPublished"
-                on-tap="_handleEdit">Edit</gr-button>
-            <gr-button
-                link
-                secondary
-                disabled$="[[_computeSaveDisabled(_messageText, comment, resolved)]]"
-                class="action save hideOnPublished"
-                on-tap="_handleSave">Save</gr-button>
-          </div>
-        </div>
-        <div class="robotActions" hidden$="[[!_showRobotActions]]">
-          <template is="dom-if" if="[[isRobotComment]]">
-            <gr-button
-                link
-                secondary
-                class="action fix"
-                on-tap="_handleFix"
-                disabled="[[robotButtonDisabled]]">
-              Please Fix
-            </gr-button>
-            <gr-endpoint-decorator name="robot-comment-controls">
-              <gr-endpoint-param name="comment" value="[[comment]]">
-              </gr-endpoint-param>
-            </gr-endpoint-decorator>
-          </template>
-        </div>
-      </div>
-    </div>
-    <template is="dom-if" if="[[_enableOverlay]]">
-      <gr-overlay id="confirmDeleteOverlay" with-backdrop>
-        <gr-confirm-delete-comment-dialog id="confirmDeleteComment"
-            on-confirm="_handleConfirmDeleteComment"
-            on-cancel="_handleCancelDeleteComment">
-        </gr-confirm-delete-comment-dialog>
-      </gr-overlay>
-      <gr-overlay id="confirmDiscardOverlay" with-backdrop>
-        <gr-dialog
-            id="confirmDiscardDialog"
-            confirm-label="Discard"
-            confirm-on-enter
-            on-confirm="_handleConfirmDiscard"
-            on-cancel="_closeConfirmDiscardOverlay">
-          <div class="header" slot="header">
-            Discard comment
-          </div>
-          <div class="main" slot="main">
-            Are you sure you want to discard this draft comment?
-          </div>
-        </gr-dialog>
-      </gr-overlay>
-    </template>
-    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
-    <gr-storage id="storage"></gr-storage>
-    <gr-reporting id="reporting"></gr-reporting>
-  </template>
-  <script src="gr-diff-comment.js"></script>
-</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
deleted file mode 100644
index a46a0b2..0000000
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ /dev/null
@@ -1,648 +0,0 @@
-/**
- * @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 STORAGE_DEBOUNCE_INTERVAL = 400;
-  const TOAST_DEBOUNCE_INTERVAL = 200;
-
-  const SAVING_MESSAGE = 'Saving';
-  const DRAFT_SINGULAR = 'draft...';
-  const DRAFT_PLURAL = 'drafts...';
-  const SAVED_MESSAGE = 'All changes saved';
-
-  const REPORT_CREATE_DRAFT = 'CreateDraftComment';
-  const REPORT_UPDATE_DRAFT = 'UpdateDraftComment';
-  const REPORT_DISCARD_DRAFT = 'DiscardDraftComment';
-
-  const FILE = 'FILE';
-
-  Polymer({
-    is: 'gr-diff-comment',
-
-    /**
-     * Fired when the create fix comment action is triggered.
-     *
-     * @event create-fix-comment
-     */
-
-    /**
-     * Fired when this comment is discarded.
-     *
-     * @event comment-discard
-     */
-
-    /**
-     * Fired when this comment is saved.
-     *
-     * @event comment-save
-     */
-
-    /**
-     * Fired when this comment is updated.
-     *
-     * @event comment-update
-     */
-
-    /**
-     * Fired when the comment's timestamp is tapped.
-     *
-     * @event comment-anchor-tap
-     */
-
-    properties: {
-      changeNum: String,
-      /** @type {?} */
-      comment: {
-        type: Object,
-        notify: true,
-        observer: '_commentChanged',
-      },
-      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,
-        observer: '_toggleCollapseClass',
-      },
-      /** @type {?} */
-      projectConfig: Object,
-      robotButtonDisabled: Boolean,
-      _isAdmin: {
-        type: Boolean,
-        value: false,
-      },
-
-      _xhrPromise: Object, // Used for testing.
-      _messageText: {
-        type: String,
-        value: '',
-        observer: '_messageTextChanged',
-      },
-      commentSide: String,
-
-      resolved: Boolean,
-
-      _numPendingDraftRequests: {
-        type: Object,
-        value: {number: 0}, // Intentional to share the object across instances.
-      },
-
-      _enableOverlay: {
-        type: Boolean,
-        value: false,
-      },
-
-      /**
-       * Property for storing references to overlay elements. When the overlays
-       * are moved to Gerrit.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: () => ({}),
-      },
-    },
-
-    observers: [
-      '_commentMessageChanged(comment.message)',
-      '_loadLocalDraft(changeNum, patchNum, comment)',
-      '_isRobotComment(comment)',
-      '_calculateActionstoShow(showActions, isRobotComment)',
-    ],
-
-    behaviors: [
-      Gerrit.KeyboardShortcutBehavior,
-    ],
-
-    keyBindings: {
-      'ctrl+enter meta+enter ctrl+s meta+s': '_handleSaveKey',
-      'esc': '_handleEsc',
-    },
-
-    attached() {
-      if (this.editing) {
-        this.collapsed = false;
-      } else if (this.comment) {
-        this.collapsed = this.comment.collapsed;
-      }
-      this._getIsAdmin().then(isAdmin => {
-        this._isAdmin = isAdmin;
-      });
-    },
-
-    detached() {
-      this.cancelDebouncer('fire-update');
-      if (this.textarea) {
-        this.textarea.closeDropdown();
-      }
-    },
-
-    get textarea() {
-      return this.$$('#editTextarea');
-    },
-
-    get confirmDeleteOverlay() {
-      if (!this._overlays.confirmDelete) {
-        this._enableOverlay = true;
-        Polymer.dom.flush();
-        this._overlays.confirmDelete = this.$$('#confirmDeleteOverlay');
-      }
-      return this._overlays.confirmDelete;
-    },
-
-    get confirmDiscardOverlay() {
-      if (!this._overlays.confirmDiscard) {
-        this._enableOverlay = true;
-        Polymer.dom.flush();
-        this._overlays.confirmDiscard = this.$$('#confirmDiscardOverlay');
-      }
-      return this._overlays.confirmDiscard;
-    },
-
-    _computeShowHideText(collapsed) {
-      return collapsed ? 'â—€' : 'â–¼';
-    },
-
-    _calculateActionstoShow(showActions, isRobotComment) {
-      this._showHumanActions = showActions && !isRobotComment;
-      this._showRobotActions = showActions && isRobotComment;
-    },
-
-    _isRobotComment(comment) {
-      this.isRobotComment = !!comment.robot_id;
-    },
-
-    isOnParent() {
-      return this.side === 'PARENT';
-    },
-
-    _getIsAdmin() {
-      return this.$.restAPI.getIsAdmin();
-    },
-
-    /**
-     * @param {*=} opt_comment
-     */
-    save(opt_comment) {
-      let comment = opt_comment;
-      if (!comment) { comment = this.comment; }
-
-      this.set('comment.message', this._messageText);
-      this.editing = false;
-      this.disabled = true;
-
-      if (!this._messageText) {
-        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;
-          }
-          resComment.__commentSide = this.commentSide;
-          this.comment = resComment;
-          this._fireSave();
-          return obj;
-        });
-      }).catch(err => {
-        this.disabled = false;
-        throw err;
-      });
-
-      return this._xhrPromise;
-    },
-
-    _eraseDraftComment() {
-      // Prevents a race condition in which removing the draft comment occurs
-      // prior to it being saved.
-      this.cancelDebouncer('store');
-
-      this.$.storage.eraseDraftComment({
-        changeNum: this.changeNum,
-        patchNum: this._getPatchNum(),
-        path: this.comment.path,
-        line: this.comment.line,
-        range: this.comment.range,
-      });
-    },
-
-    _commentChanged(comment) {
-      this.editing = !!comment.__editing;
-      this.resolved = !comment.unresolved;
-      if (this.editing) { // It's a new draft/reply, notify.
-        this._fireUpdate();
-      }
-    },
-
-    /**
-     * @param {!Object=} opt_mixin
-     *
-     * @return {!Object}
-     */
-    _getEventPayload(opt_mixin) {
-      return Object.assign({}, opt_mixin, {
-        comment: this.comment,
-        patchNum: this.patchNum,
-      });
-    },
-
-    _fireSave() {
-      this.fire('comment-save', this._getEventPayload());
-    },
-
-    _fireUpdate() {
-      this.debounce('fire-update', () => {
-        this.fire('comment-update', this._getEventPayload());
-      });
-    },
-
-    _draftChanged(draft) {
-      this.$.container.classList.toggle('draft', draft);
-    },
-
-    _editingChanged(editing, previousValue) {
-      this.$.container.classList.toggle('editing', editing);
-      if (this.comment && this.comment.id) {
-        this.$$('.cancel').hidden = !editing;
-      }
-      if (this.comment) {
-        this.comment.__editing = this.editing;
-      }
-      if (editing != !!previousValue) {
-        // To prevent event firing on comment creation.
-        this._fireUpdate();
-      }
-      if (editing) {
-        this.async(() => {
-          Polymer.dom.flush();
-          this.textarea.putCursorAtEnd();
-        }, 1);
-      }
-    },
-
-    _computeDeleteButtonClass(isAdmin, draft) {
-      return isAdmin && !draft ? 'showDeleteButtons' : '';
-    },
-
-    _computeSaveDisabled(draft, comment, resolved) {
-      // If resolved state has changed and a msg exists, save should be enabled.
-      if (comment.unresolved === resolved && draft) { return false; }
-      return !draft || draft.trim() === '';
-    },
-
-    _handleSaveKey(e) {
-      if (!this._computeSaveDisabled(this._messageText, this.comment,
-          this.resolved)) {
-        e.preventDefault();
-        this._handleSave(e);
-      }
-    },
-
-    _handleEsc(e) {
-      if (!this._messageText.length) {
-        e.preventDefault();
-        this._handleCancel(e);
-      }
-    },
-
-    _handleToggleCollapsed() {
-      this.collapsed = !this.collapsed;
-    },
-
-    _toggleCollapseClass(collapsed) {
-      if (collapsed) {
-        this.$.container.classList.add('collapsed');
-      } else {
-        this.$.container.classList.remove('collapsed');
-      }
-    },
-
-    _commentMessageChanged(message) {
-      this._messageText = message || '';
-    },
-
-    _messageTextChanged(newValue, oldValue) {
-      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,
-        };
-
-        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);
-    },
-
-    _handleAnchorTap(e) {
-      e.preventDefault();
-      if (!this.comment.line) { return; }
-      this.dispatchEvent(new CustomEvent('comment-anchor-tap', {
-        bubbles: true,
-        detail: {
-          number: this.comment.line || FILE,
-          side: this.side,
-        },
-      }));
-    },
-
-    _handleEdit(e) {
-      e.preventDefault();
-      this._messageText = this.comment.message;
-      this.editing = true;
-      this.$.reporting.recordDraftInteraction();
-    },
-
-    _handleSave(e) {
-      e.preventDefault();
-
-      // Ignore saves started while already saving.
-      if (this.disabled) { return; }
-      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(); });
-    },
-
-    _handleCancel(e) {
-      e.preventDefault();
-
-      if (!this.comment.message ||
-          this.comment.message.trim().length === 0 ||
-          !this.comment.id) {
-        this._fireDiscard();
-        return;
-      }
-      this._messageText = this.comment.message;
-      this.editing = false;
-    },
-
-    _fireDiscard() {
-      this.cancelDebouncer('fire-update');
-      this.fire('comment-discard', this._getEventPayload());
-    },
-
-    _handleFix() {
-      this.dispatchEvent(new CustomEvent('create-fix-comment', {
-        bubbles: true,
-        detail: this._getEventPayload(),
-      }));
-    },
-
-    _handleDiscard(e) {
-      e.preventDefault();
-      this.$.reporting.recordDraftInteraction();
-
-      if (!this._messageText) {
-        this._discardDraft();
-        return;
-      }
-
-      this._openOverlay(this.confirmDiscardOverlay).then(() => {
-        this.confirmDiscardOverlay.querySelector('#confirmDiscardDialog')
-            .resetFocus();
-      });
-    },
-
-    _handleConfirmDiscard(e) {
-      e.preventDefault();
-      const timer = this.$.reporting.getTimer(REPORT_DISCARD_DRAFT);
-      this._closeConfirmDiscardOverlay();
-      return this._discardDraft().then(() => { timer.end(); });
-    },
-
-    _discardDraft() {
-      if (!this.comment.__draft) {
-        throw Error('Cannot discard a non-draft comment.');
-      }
-      this.discarding = true;
-      this.editing = false;
-      this.disabled = true;
-      this._eraseDraftComment();
-
-      if (!this.comment.id) {
-        this.disabled = false;
-        this._fireDiscard();
-        return;
-      }
-
-      this._xhrPromise = this._deleteDraft(this.comment).then(response => {
-        this.disabled = false;
-        if (!response.ok) {
-          this.discarding = false;
-          return response;
-        }
-
-        this._fireDiscard();
-      }).catch(err => {
-        this.disabled = false;
-        throw err;
-      });
-
-      return this._xhrPromise;
-    },
-
-    _closeConfirmDiscardOverlay() {
-      this._closeOverlay(this.confirmDiscardOverlay);
-    },
-
-    _getSavingMessage(numPending) {
-      if (numPending === 0) { return SAVED_MESSAGE; }
-      return [
-        SAVING_MESSAGE,
-        numPending,
-        numPending === 1 ? DRAFT_SINGULAR : DRAFT_PLURAL,
-      ].join(' ');
-    },
-
-    _showStartRequest() {
-      const numPending = ++this._numPendingDraftRequests.number;
-      this._updateRequestToast(numPending);
-    },
-
-    _showEndRequest() {
-      const numPending = --this._numPendingDraftRequests.number;
-      this._updateRequestToast(numPending);
-    },
-
-    _handleFailedDraftRequest() {
-      this._numPendingDraftRequests.number--;
-
-      // Cancel the debouncer so that error toasts from the error-manager will
-      // not be overridden.
-      this.cancelDebouncer('draft-toast');
-    },
-
-    _updateRequestToast(numPending) {
-      const message = this._getSavingMessage(numPending);
-      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}));
-      }, TOAST_DEBOUNCE_INTERVAL);
-    },
-
-    _saveDraft(draft) {
-      this._showStartRequest();
-      return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft)
-          .then(result => {
-            if (result.ok) {
-              this._showEndRequest();
-            } else {
-              this._handleFailedDraftRequest();
-            }
-            return result;
-          });
-    },
-
-    _deleteDraft(draft) {
-      this._showStartRequest();
-      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;
-    },
-
-    _loadLocalDraft(changeNum, patchNum, comment) {
-      // Only apply local drafts to comments that haven't been saved
-      // remotely, and haven't been given a default message already.
-      //
-      // 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;
-        return;
-      }
-
-      const draft = this.$.storage.getDraftComment({
-        changeNum,
-        patchNum: this._getPatchNum(),
-        path: comment.path,
-        line: comment.line,
-        range: comment.range,
-      });
-
-      if (draft) {
-        this.set('comment.message', draft.message);
-      }
-    },
-
-    _handleToggleResolved() {
-      this.$.reporting.recordDraftInteraction();
-      this.resolved = !this.resolved;
-      // Modify payload instead of this.comment, as this.comment is passed from
-      // the parent by ref.
-      const payload = this._getEventPayload();
-      payload.comment.unresolved = !this.$.resolvedCheckbox.checked;
-      this.fire('comment-update', payload);
-      if (!this.editing) {
-        // Save the resolved state immediately.
-        this.save(payload.comment);
-      }
-    },
-
-    _handleCommentDelete() {
-      this._openOverlay(this.confirmDeleteOverlay);
-    },
-
-    _handleCancelDeleteComment() {
-      this._closeOverlay(this.confirmDeleteOverlay);
-    },
-
-    _openOverlay(overlay) {
-      Polymer.dom(Gerrit.getRootElement()).appendChild(overlay);
-      return overlay.open();
-    },
-
-    _closeOverlay(overlay) {
-      Polymer.dom(Gerrit.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;
-          });
-    },
-  });
-})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
deleted file mode 100644
index 912e615..0000000
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ /dev/null
@@ -1,847 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2015 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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-diff-comment</title>
-
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../scripts/util.js"></script>
-
-<link rel="import" href="gr-diff-comment.html">
-
-<script>void(0);</script>
-
-<test-fixture id="basic">
-  <template>
-    <gr-diff-comment></gr-diff-comment>
-  </template>
-</test-fixture>
-
-<test-fixture id="draft">
-  <template>
-    <gr-diff-comment draft="true"></gr-diff-comment>
-  </template>
-</test-fixture>
-
-<script>
-
-  function isVisible(el) {
-    assert.ok(el);
-    return getComputedStyle(el).getPropertyValue('display') !== 'none';
-  }
-
-  suite('gr-diff-comment tests', () => {
-    let element;
-    let sandbox;
-    setup(() => {
-      stub('gr-rest-api-interface', {
-        getAccount() { return Promise.resolve(null); },
-      });
-      element = fixture('basic');
-      element.comment = {
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 'baf0414d_60047215',
-        line: 5,
-        message: 'is this a crossover episode!?',
-        updated: '2015-12-08 19:48:33.843000000',
-      };
-      sandbox = sinon.sandbox.create();
-    });
-
-    teardown(() => {
-      sandbox.restore();
-    });
-
-    test('collapsible comments', () => {
-      // When a comment (not draft) is loaded, it should be collapsed
-      assert.isTrue(element.collapsed);
-      assert.isFalse(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is not visible');
-      assert.isFalse(isVisible(element.$$('.actions')),
-          'actions are not visible');
-      assert.isNotOk(element.textarea, 'textarea is not visible');
-
-      // The header middle content is only visible when comments are collapsed.
-      // It shows the message in a condensed way, and limits to a single line.
-      assert.isTrue(isVisible(element.$$('.collapsedContent')),
-          'header middle content is visible');
-
-      // When the header row is clicked, the comment should expand
-      MockInteractions.tap(element.$.header);
-      assert.isFalse(element.collapsed);
-      assert.isTrue(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is visible');
-      assert.isTrue(isVisible(element.$$('.actions')),
-          'actions are visible');
-      assert.isNotOk(element.textarea, 'textarea is not visible');
-      assert.isFalse(isVisible(element.$$('.collapsedContent')),
-          'header middle content is not visible');
-    });
-
-    test('clicking on date link fires event', () => {
-      element.side = 'PARENT';
-      const stub = sinon.stub();
-      element.addEventListener('comment-anchor-tap', stub);
-      const dateEl = element.$$('.date');
-      assert.ok(dateEl);
-      MockInteractions.tap(dateEl);
-
-      assert.isTrue(stub.called);
-      assert.deepEqual(stub.lastCall.args[0].detail,
-          {side: element.side, number: element.comment.line});
-    });
-
-    test('message is not retrieved from storage when other edits', done => {
-      const storageStub = sandbox.stub(element.$.storage, 'getDraftComment');
-      const loadSpy = sandbox.spy(element, '_loadLocalDraft');
-
-      element.changeNum = 1;
-      element.patchNum = 1;
-      element.comment = {
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        line: 5,
-        __otherEditing: true,
-      };
-      flush(() => {
-        assert.isTrue(loadSpy.called);
-        assert.isFalse(storageStub.called);
-        done();
-      });
-    });
-
-    test('message is retrieved from storage when no other edits', done => {
-      const storageStub = sandbox.stub(element.$.storage, 'getDraftComment');
-      const loadSpy = sandbox.spy(element, '_loadLocalDraft');
-
-      element.changeNum = 1;
-      element.patchNum = 1;
-      element.comment = {
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        line: 5,
-      };
-      flush(() => {
-        assert.isTrue(loadSpy.called);
-        assert.isTrue(storageStub.called);
-        done();
-      });
-    });
-
-    test('_getPatchNum', () => {
-      element.side = 'PARENT';
-      element.patchNum = 1;
-      assert.equal(element._getPatchNum(), 'PARENT');
-      element.side = 'REVISION';
-      assert.equal(element._getPatchNum(), 1);
-    });
-
-    test('comment expand and collapse', () => {
-      element.collapsed = true;
-      assert.isFalse(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is not visible');
-      assert.isFalse(isVisible(element.$$('.actions')),
-          'actions are not visible');
-      assert.isNotOk(element.textarea, 'textarea is not visible');
-      assert.isTrue(isVisible(element.$$('.collapsedContent')),
-          'header middle content is visible');
-
-      element.collapsed = false;
-      assert.isFalse(element.collapsed);
-      assert.isTrue(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is visible');
-      assert.isTrue(isVisible(element.$$('.actions')),
-          'actions are visible');
-      assert.isNotOk(element.textarea, 'textarea is not visible');
-      assert.isFalse(isVisible(element.$$('.collapsedContent')),
-          'header middle content is is not visible');
-    });
-
-    suite('while editing', () => {
-      setup(() => {
-        element.editing = true;
-        element._messageText = 'test';
-        sandbox.stub(element, '_handleCancel');
-        sandbox.stub(element, '_handleSave');
-        flushAsynchronousOperations();
-      });
-
-      suite('when text is empty', () => {
-        setup(() => {
-          element._messageText = '';
-          element.comment = {};
-        });
-
-        test('esc closes comment when text is empty', () => {
-          MockInteractions.pressAndReleaseKeyOn(
-              element.textarea, 27); // esc
-          assert.isTrue(element._handleCancel.called);
-        });
-
-        test('ctrl+enter does not save', () => {
-          MockInteractions.pressAndReleaseKeyOn(
-              element.textarea, 13, 'ctrl'); // ctrl + enter
-          assert.isFalse(element._handleSave.called);
-        });
-
-        test('meta+enter does not save', () => {
-          MockInteractions.pressAndReleaseKeyOn(
-              element.textarea, 13, 'meta'); // meta + enter
-          assert.isFalse(element._handleSave.called);
-        });
-
-        test('ctrl+s does not save', () => {
-          MockInteractions.pressAndReleaseKeyOn(
-              element.textarea, 83, 'ctrl'); // ctrl + s
-          assert.isFalse(element._handleSave.called);
-        });
-      });
-
-      test('esc does not close comment that has content', () => {
-        MockInteractions.pressAndReleaseKeyOn(
-            element.textarea, 27); // esc
-        assert.isFalse(element._handleCancel.called);
-      });
-
-      test('ctrl+enter saves', () => {
-        MockInteractions.pressAndReleaseKeyOn(
-            element.textarea, 13, 'ctrl'); // ctrl + enter
-        assert.isTrue(element._handleSave.called);
-      });
-
-      test('meta+enter saves', () => {
-        MockInteractions.pressAndReleaseKeyOn(
-            element.textarea, 13, 'meta'); // meta + enter
-        assert.isTrue(element._handleSave.called);
-      });
-
-      test('ctrl+s saves', () => {
-        MockInteractions.pressAndReleaseKeyOn(
-            element.textarea, 83, 'ctrl'); // ctrl + s
-        assert.isTrue(element._handleSave.called);
-      });
-    });
-    test('delete comment button for non-admins is hidden', () => {
-      element._isAdmin = false;
-      assert.isFalse(element.$$('.action.delete')
-          .classList.contains('showDeleteButtons'));
-    });
-
-    test('delete comment button for admins with draft is hidden', () => {
-      element._isAdmin = false;
-      element.draft = true;
-      assert.isFalse(element.$$('.action.delete')
-          .classList.contains('showDeleteButtons'));
-    });
-
-    test('delete comment', done => {
-      sandbox.stub(
-          element.$.restAPI, 'deleteComment').returns(Promise.resolve({}));
-      sandbox.spy(element.confirmDeleteOverlay, 'open');
-      element.changeNum = 42;
-      element.patchNum = 0xDEADBEEF;
-      element._isAdmin = true;
-      assert.isTrue(element.$$('.action.delete')
-          .classList.contains('showDeleteButtons'));
-      MockInteractions.tap(element.$$('.action.delete'));
-      flush(() => {
-        element.confirmDeleteOverlay.open.lastCall.returnValue.then(() => {
-          const dialog =
-              this.confirmDeleteOverlay.querySelector('#confirmDeleteComment');
-          dialog.message = 'removal reason';
-          element._handleConfirmDeleteComment();
-          assert.isTrue(element.$.restAPI.deleteComment.calledWith(
-              42, 0xDEADBEEF, 'baf0414d_60047215', 'removal reason'));
-          done();
-        });
-      });
-    });
-
-    suite('draft update reporting', () => {
-      let endStub;
-      let getTimerStub;
-      let mockEvent;
-
-      setup(() => {
-        mockEvent = {preventDefault() {}};
-        sandbox.stub(element, 'save')
-            .returns(Promise.resolve({}));
-        sandbox.stub(element, '_discardDraft')
-            .returns(Promise.resolve({}));
-        endStub = sinon.stub();
-        getTimerStub = sandbox.stub(element.$.reporting, 'getTimer')
-            .returns({end: endStub});
-      });
-
-      test('create', () => {
-        element.comment = {};
-        return element._handleSave(mockEvent).then(() => {
-          assert.isTrue(endStub.calledOnce);
-          assert.isTrue(getTimerStub.calledOnce);
-          assert.equal(getTimerStub.lastCall.args[0], 'CreateDraftComment');
-        });
-      });
-
-      test('update', () => {
-        element.comment = {id: 'abc_123'};
-        return element._handleSave(mockEvent).then(() => {
-          assert.isTrue(endStub.calledOnce);
-          assert.isTrue(getTimerStub.calledOnce);
-          assert.equal(getTimerStub.lastCall.args[0], 'UpdateDraftComment');
-        });
-      });
-
-      test('discard', () => {
-        element.comment = {id: 'abc_123'};
-        sandbox.stub(element, '_closeConfirmDiscardOverlay');
-        return element._handleConfirmDiscard(mockEvent).then(() => {
-          assert.isTrue(endStub.calledOnce);
-          assert.isTrue(getTimerStub.calledOnce);
-          assert.equal(getTimerStub.lastCall.args[0], 'DiscardDraftComment');
-        });
-      });
-    });
-
-    test('edit reports interaction', () => {
-      const reportStub = sandbox.stub(element.$.reporting,
-          'recordDraftInteraction');
-      MockInteractions.tap(element.$$('.edit'));
-      assert.isTrue(reportStub.calledOnce);
-    });
-
-    test('discard reports interaction', () => {
-      const reportStub = sandbox.stub(element.$.reporting,
-          'recordDraftInteraction');
-      element.draft = true;
-      MockInteractions.tap(element.$$('.discard'));
-      assert.isTrue(reportStub.calledOnce);
-    });
-  });
-
-  suite('gr-diff-comment draft tests', () => {
-    let element;
-    let sandbox;
-
-    setup(() => {
-      stub('gr-rest-api-interface', {
-        getAccount() { return Promise.resolve(null); },
-        saveDiffDraft() {
-          return Promise.resolve({
-            ok: true,
-            text() {
-              return Promise.resolve(
-                  ')]}\'\n{' +
-                  '"id": "baf0414d_40572e03",' +
-                  '"path": "/path/to/file",' +
-                  '"line": 5,' +
-                  '"updated": "2015-12-08 21:52:36.177000000",' +
-                  '"message": "saved!"' +
-                '}'
-              );
-            },
-          });
-        },
-        removeChangeReviewer() {
-          return Promise.resolve({ok: true});
-        },
-      });
-      stub('gr-storage', {
-        getDraftComment() { return null; },
-      });
-      element = fixture('draft');
-      element.changeNum = 42;
-      element.patchNum = 1;
-      element.editing = false;
-      element.comment = {
-        __commentSide: 'right',
-        __draft: true,
-        __draftID: 'temp_draft_id',
-        path: '/path/to/file',
-        line: 5,
-      };
-      element.commentSide = 'right';
-      sandbox = sinon.sandbox.create();
-    });
-
-    teardown(() => {
-      sandbox.restore();
-    });
-
-    test('button visibility states', () => {
-      element.showActions = false;
-      assert.isTrue(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
-
-      element.showActions = true;
-      assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
-
-      element.draft = true;
-      assert.isTrue(isVisible(element.$$('.edit')), 'edit is visible');
-      assert.isTrue(isVisible(element.$$('.discard')), 'discard is visible');
-      assert.isFalse(isVisible(element.$$('.save')), 'save is not visible');
-      assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible');
-      assert.isTrue(isVisible(element.$$('.resolve')), 'resolve is visible');
-      assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
-
-      element.editing = true;
-      flushAsynchronousOperations();
-      assert.isFalse(isVisible(element.$$('.edit')), 'edit is not visible');
-      assert.isFalse(isVisible(element.$$('.discard')), 'discard not visible');
-      assert.isTrue(isVisible(element.$$('.save')), 'save is visible');
-      assert.isTrue(isVisible(element.$$('.cancel')), 'cancel is visible');
-      assert.isTrue(isVisible(element.$$('.resolve')), 'resolve is visible');
-      assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
-
-      element.draft = false;
-      element.editing = false;
-      flushAsynchronousOperations();
-      assert.isFalse(isVisible(element.$$('.edit')), 'edit is not visible');
-      assert.isFalse(isVisible(element.$$('.discard')),
-          'discard is not visible');
-      assert.isFalse(isVisible(element.$$('.save')), 'save is not visible');
-      assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible');
-      assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
-
-      element.comment.id = 'foo';
-      element.draft = true;
-      element.editing = true;
-      flushAsynchronousOperations();
-      assert.isTrue(isVisible(element.$$('.cancel')), 'cancel is visible');
-      assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
-
-      element.isRobotComment = true;
-      element.draft = true;
-      assert.isTrue(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isFalse(element.$$('.robotActions').hasAttribute('hidden'));
-
-      // It is not expected to see Robot comment drafts, but if they appear,
-      // they will behave the same as non-drafts.
-      element.draft = false;
-      assert.isTrue(element.$$('.humanActions').hasAttribute('hidden'));
-      assert.isFalse(element.$$('.robotActions').hasAttribute('hidden'));
-
-      // A robot comment with run ID should display plain text.
-      element.set(['comment', 'robot_run_id'], 'text');
-      element.editing = false;
-      element.collapsed = false;
-      flushAsynchronousOperations();
-      assert.isNotOk(element.$$('.robotRun.link'));
-      assert.notEqual(getComputedStyle(element.$$('.robotRun.text')).display,
-          'none');
-
-      // A robot comment with run ID and url should display a link.
-      element.set(['comment', 'url'], '/path/to/run');
-      flushAsynchronousOperations();
-      assert.notEqual(getComputedStyle(element.$$('.robotRun.link')).display,
-          'none');
-      assert.equal(getComputedStyle(element.$$('.robotRun.text')).display,
-          'none');
-    });
-
-    test('collapsible drafts', () => {
-      assert.isTrue(element.collapsed);
-      assert.isFalse(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is not visible');
-      assert.isFalse(isVisible(element.$$('.actions')),
-          'actions are not visible');
-      assert.isNotOk(element.textarea, 'textarea is not visible');
-      assert.isTrue(isVisible(element.$$('.collapsedContent')),
-          'header middle content is visible');
-
-      MockInteractions.tap(element.$.header);
-      assert.isFalse(element.collapsed);
-      assert.isTrue(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is visible');
-      assert.isTrue(isVisible(element.$$('.actions')),
-          'actions are visible');
-      assert.isNotOk(element.textarea, 'textarea is not visible');
-      assert.isFalse(isVisible(element.$$('.collapsedContent')),
-          'header middle content is is not visible');
-
-      // When the edit button is pressed, should still see the actions
-      // and also textarea
-      MockInteractions.tap(element.$$('.edit'));
-      flushAsynchronousOperations();
-      assert.isFalse(element.collapsed);
-      assert.isFalse(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is not visible');
-      assert.isTrue(isVisible(element.$$('.actions')),
-          'actions are visible');
-      assert.isTrue(isVisible(element.textarea), 'textarea is visible');
-      assert.isFalse(isVisible(element.$$('.collapsedContent')),
-          'header middle content is not visible');
-
-      // When toggle again, everything should be hidden except for textarea
-      // and header middle content should be visible
-      MockInteractions.tap(element.$.header);
-      assert.isTrue(element.collapsed);
-      assert.isFalse(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is not visible');
-      assert.isFalse(isVisible(element.$$('.actions')),
-          'actions are not visible');
-      assert.isFalse(isVisible(element.$$('gr-textarea')),
-          'textarea is not visible');
-      assert.isTrue(isVisible(element.$$('.collapsedContent')),
-          'header middle content is visible');
-
-      // When toggle again, textarea should remain open in the state it was
-      // before
-      MockInteractions.tap(element.$.header);
-      assert.isFalse(isVisible(element.$$('gr-formatted-text')),
-          'gr-formatted-text is not visible');
-      assert.isTrue(isVisible(element.$$('.actions')),
-          'actions are visible');
-      assert.isTrue(isVisible(element.textarea), 'textarea is visible');
-      assert.isFalse(isVisible(element.$$('.collapsedContent')),
-          'header middle content is not visible');
-    });
-
-    test('draft creation/cancellation', done => {
-      assert.isFalse(element.editing);
-      MockInteractions.tap(element.$$('.edit'));
-      assert.isTrue(element.editing);
-
-      element._messageText = '';
-      const eraseMessageDraftSpy = sandbox.spy(element, '_eraseDraftComment');
-
-      // Save should be disabled on an empty message.
-      let disabled = element.$$('.save').hasAttribute('disabled');
-      assert.isTrue(disabled, 'save button should be disabled.');
-      element._messageText = '     ';
-      disabled = element.$$('.save').hasAttribute('disabled');
-      assert.isTrue(disabled, 'save button should be disabled.');
-
-      const updateStub = sinon.stub();
-      element.addEventListener('comment-update', updateStub);
-
-      let numDiscardEvents = 0;
-      element.addEventListener('comment-discard', e => {
-        numDiscardEvents++;
-        assert.isFalse(eraseMessageDraftSpy.called);
-        if (numDiscardEvents === 2) {
-          assert.isFalse(updateStub.called);
-          done();
-        }
-      });
-      MockInteractions.tap(element.$$('.cancel'));
-      element.flushDebouncer('fire-update');
-      element._messageText = '';
-      flushAsynchronousOperations();
-      MockInteractions.pressAndReleaseKeyOn(element.textarea, 27); // esc
-    });
-
-    test('draft discard removes message from storage', done => {
-      element._messageText = '';
-      const eraseMessageDraftSpy = sandbox.spy(element, '_eraseDraftComment');
-      sandbox.stub(element, '_closeConfirmDiscardOverlay');
-
-      element.addEventListener('comment-discard', e => {
-        assert.isTrue(eraseMessageDraftSpy.called);
-        done();
-      });
-      element._handleConfirmDiscard({preventDefault: sinon.stub()});
-    });
-
-    test('storage is cleared only after save success', () => {
-      element._messageText = 'test';
-      const eraseStub = sandbox.stub(element, '_eraseDraftComment');
-      sandbox.stub(element.$.restAPI, 'getResponseObject')
-          .returns(Promise.resolve({}));
-
-      sandbox.stub(element, '_saveDraft').returns(Promise.resolve({ok: false}));
-
-      const savePromise = element.save();
-      assert.isFalse(eraseStub.called);
-      return savePromise.then(() => {
-        assert.isFalse(eraseStub.called);
-
-        element._saveDraft.restore();
-        sandbox.stub(element, '_saveDraft')
-            .returns(Promise.resolve({ok: true}));
-        return element.save().then(() => {
-          assert.isTrue(eraseStub.called);
-        });
-      });
-    });
-
-    test('_computeSaveDisabled', () => {
-      const comment = {unresolved: true};
-      const msgComment = {message: 'test', unresolved: true};
-      assert.equal(element._computeSaveDisabled('', comment, false), true);
-      assert.equal(element._computeSaveDisabled('test', comment, false), false);
-      assert.equal(element._computeSaveDisabled('', msgComment, false), true);
-      assert.equal(
-          element._computeSaveDisabled('test', msgComment, false), false);
-      assert.equal(
-          element._computeSaveDisabled('test2', msgComment, false), false);
-      assert.equal(element._computeSaveDisabled('test', comment, true), false);
-      assert.equal(element._computeSaveDisabled('', comment, true), true);
-      assert.equal(element._computeSaveDisabled('', comment, false), true);
-    });
-
-    suite('confirm discard', () => {
-      let discardStub;
-      let overlayStub;
-      let mockEvent;
-
-      setup(() => {
-        discardStub = sandbox.stub(element, '_discardDraft');
-        overlayStub = sandbox.stub(element, '_openOverlay')
-            .returns(Promise.resolve());
-        mockEvent = {preventDefault: sinon.stub()};
-      });
-
-      test('confirms discard of comments with message text', () => {
-        element._messageText = 'test';
-        element._handleDiscard(mockEvent);
-        assert.isTrue(overlayStub.calledWith(element.confirmDiscardOverlay));
-        assert.isFalse(discardStub.called);
-      });
-
-      test('no confirmation for comments without message text', () => {
-        element._messageText = '';
-        element._handleDiscard(mockEvent);
-        assert.isFalse(overlayStub.called);
-        assert.isTrue(discardStub.calledOnce);
-      });
-    });
-
-    test('ctrl+s saves comment', done => {
-      const stub = sinon.stub(element, 'save', () => {
-        assert.isTrue(stub.called);
-        stub.restore();
-        done();
-        return Promise.resolve();
-      });
-      element._messageText = 'is that the horse from horsing around??';
-      element.editing = true;
-      flushAsynchronousOperations();
-      MockInteractions.pressAndReleaseKeyOn(
-          element.textarea.$.textarea.textarea,
-          83, 'ctrl'); // 'ctrl + s'
-    });
-
-    test('draft saving/editing', done => {
-      const fireStub = sinon.stub(element, 'fire');
-      const cancelDebounce = sandbox.stub(element, 'cancelDebouncer');
-
-      element.draft = true;
-      MockInteractions.tap(element.$$('.edit'));
-      element._messageText = 'good news, everyone!';
-      element.flushDebouncer('fire-update');
-      element.flushDebouncer('store');
-      assert(fireStub.calledWith('comment-update'),
-          'comment-update should be sent');
-      assert.isTrue(fireStub.calledOnce);
-
-      element._messageText = 'good news, everyone!';
-      element.flushDebouncer('fire-update');
-      element.flushDebouncer('store');
-      assert.isTrue(fireStub.calledOnce,
-          'No events should fire for text editing');
-
-      MockInteractions.tap(element.$$('.save'));
-
-      assert.isTrue(element.disabled,
-          'Element should be disabled when creating draft.');
-
-      element._xhrPromise.then(draft => {
-        assert(fireStub.calledWith('comment-save'),
-            'comment-save should be sent');
-        assert(cancelDebounce.calledWith('store'));
-
-        assert.deepEqual(fireStub.lastCall.args[1], {
-          comment: {
-            __commentSide: 'right',
-            __draft: true,
-            __draftID: 'temp_draft_id',
-            id: 'baf0414d_40572e03',
-            line: 5,
-            message: 'saved!',
-            path: '/path/to/file',
-            updated: '2015-12-08 21:52:36.177000000',
-          },
-          patchNum: 1,
-        });
-        assert.isFalse(element.disabled,
-            'Element should be enabled when done creating draft.');
-        assert.equal(draft.message, 'saved!');
-        assert.isFalse(element.editing);
-      }).then(() => {
-        MockInteractions.tap(element.$$('.edit'));
-        element._messageText = 'You’ll be delivering a package to Chapek 9, ' +
-            'a world where humans are killed on sight.';
-        MockInteractions.tap(element.$$('.save'));
-        assert.isTrue(element.disabled,
-            'Element should be disabled when updating draft.');
-
-        element._xhrPromise.then(draft => {
-          assert.isFalse(element.disabled,
-              'Element should be enabled when done updating draft.');
-          assert.equal(draft.message, 'saved!');
-          assert.isFalse(element.editing);
-          fireStub.restore();
-          done();
-        });
-      });
-    });
-
-    test('draft prevent save when disabled', () => {
-      const saveStub = sandbox.stub(element, 'save').returns(Promise.resolve());
-      element.showActions = true;
-      element.draft = true;
-      MockInteractions.tap(element.$.header);
-      MockInteractions.tap(element.$$('.edit'));
-      element._messageText = 'good news, everyone!';
-      element.flushDebouncer('fire-update');
-      element.flushDebouncer('store');
-
-      element.disabled = true;
-      MockInteractions.tap(element.$$('.save'));
-      assert.isFalse(saveStub.called);
-
-      element.disabled = false;
-      MockInteractions.tap(element.$$('.save'));
-      assert.isTrue(saveStub.calledOnce);
-    });
-
-    test('proper event fires on resolve, comment is not saved', done => {
-      const save = sandbox.stub(element, 'save');
-      element.addEventListener('comment-update', e => {
-        assert.isTrue(e.detail.comment.unresolved);
-        assert.isFalse(save.called);
-        done();
-      });
-      MockInteractions.tap(element.$$('.resolve input'));
-    });
-
-    test('resolved comment state indicated by checkbox', () => {
-      sandbox.stub(element, 'save');
-      element.comment = {unresolved: false};
-      assert.isTrue(element.$$('.resolve input').checked);
-      element.comment = {unresolved: true};
-      assert.isFalse(element.$$('.resolve input').checked);
-    });
-
-    test('resolved checkbox saves with tap when !editing', () => {
-      element.editing = false;
-      const save = sandbox.stub(element, 'save');
-
-      element.comment = {unresolved: false};
-      assert.isTrue(element.$$('.resolve input').checked);
-      element.comment = {unresolved: true};
-      assert.isFalse(element.$$('.resolve input').checked);
-      assert.isFalse(save.called);
-      MockInteractions.tap(element.$.resolvedCheckbox);
-      assert.isTrue(element.$$('.resolve input').checked);
-      assert.isTrue(save.called);
-    });
-
-    suite('draft saving messages', () => {
-      test('_getSavingMessage', () => {
-        assert.equal(element._getSavingMessage(0), 'All changes saved');
-        assert.equal(element._getSavingMessage(1), 'Saving 1 draft...');
-        assert.equal(element._getSavingMessage(2), 'Saving 2 drafts...');
-        assert.equal(element._getSavingMessage(3), 'Saving 3 drafts...');
-      });
-
-      test('_show{Start,End}Request', () => {
-        const updateStub = sandbox.stub(element, '_updateRequestToast');
-        element._numPendingDraftRequests.number = 1;
-
-        element._showStartRequest();
-        assert.isTrue(updateStub.calledOnce);
-        assert.equal(updateStub.lastCall.args[0], 2);
-        assert.equal(element._numPendingDraftRequests.number, 2);
-
-        element._showEndRequest();
-        assert.isTrue(updateStub.calledTwice);
-        assert.equal(updateStub.lastCall.args[0], 1);
-        assert.equal(element._numPendingDraftRequests.number, 1);
-
-        element._showEndRequest();
-        assert.isTrue(updateStub.calledThrice);
-        assert.equal(updateStub.lastCall.args[0], 0);
-        assert.equal(element._numPendingDraftRequests.number, 0);
-      });
-    });
-
-    test('cancelling an unsaved draft discards, persists in storage', () => {
-      const discardSpy = sandbox.spy(element, '_fireDiscard');
-      const storeStub = sandbox.stub(element.$.storage, 'setDraftComment');
-      const eraseStub = sandbox.stub(element.$.storage, 'eraseDraftComment');
-      element._messageText = 'test text';
-      flushAsynchronousOperations();
-      element.flushDebouncer('store');
-
-      assert.isTrue(storeStub.called);
-      assert.equal(storeStub.lastCall.args[1], 'test text');
-      element._handleCancel({preventDefault: () => {}});
-      assert.isTrue(discardSpy.called);
-      assert.isFalse(eraseStub.called);
-    });
-
-    test('cancelling edit on a saved draft does not store', () => {
-      element.comment.id = 'foo';
-      const discardSpy = sandbox.spy(element, '_fireDiscard');
-      const storeStub = sandbox.stub(element.$.storage, 'setDraftComment');
-      element._messageText = 'test text';
-      flushAsynchronousOperations();
-      element.flushDebouncer('store');
-
-      assert.isFalse(storeStub.called);
-      element._handleCancel({preventDefault: () => {}});
-      assert.isTrue(discardSpy.called);
-    });
-
-    test('deleting text from saved draft and saving deletes the draft', () => {
-      element.comment = {id: 'foo', message: 'test'};
-      element._messageText = '';
-      const discardStub = sandbox.stub(element, '_discardDraft');
-
-      element.save();
-      assert.isTrue(discardStub.called);
-    });
-
-    test('_handleFix fires create-fix event', done => {
-      element.addEventListener('create-fix-comment', e => {
-        assert.deepEqual(e.detail, element._getEventPayload());
-        done();
-      });
-      element.isRobotComment = true;
-      flushAsynchronousOperations();
-
-      MockInteractions.tap(element.$$('.fix'));
-    });
-  });
-</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
index 3eb7b37..4c310b9 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
@@ -19,7 +19,7 @@
 <link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
 <link rel="import" href="../../core/gr-reporting/gr-reporting.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
+<link rel="import" href="../../shared/gr-comment-thread/gr-comment-thread.html">
 <link rel="import" href="../gr-diff/gr-diff.html">
 
 <dom-module id="gr-diff-host">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index 0611a12..8b9d8066 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -601,7 +601,7 @@
     },
 
     _createThreadElement(thread) {
-      const threadEl = document.createElement('gr-diff-comment-thread');
+      const threadEl = document.createElement('gr-comment-thread');
       threadEl.className = 'comment-thread';
       threadEl.slot = `${thread.commentSide}-${thread.lineNum}`;
       threadEl.comments = thread.comments;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index 53a60d7..ab9daec 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -256,7 +256,7 @@
       threadEls[0].dispatchEvent(
           new CustomEvent('thread-discard', {detail: {rootId: 4711}}));
       const attachedThreads = element.queryAllEffectiveChildren(
-          'gr-diff-comment-thread');
+          'gr-comment-thread');
       assert.equal(attachedThreads.length, 1);
       assert.equal(attachedThreads[0].rootId, 42);
     });
@@ -1130,7 +1130,7 @@
           commentSide, undefined, false));
 
       let threads = Polymer.dom(element.$.diff)
-          .queryDistributedElements('gr-diff-comment-thread');
+          .queryDistributedElements('gr-comment-thread');
 
       assert.equal(threads.length, 1);
       assert.equal(threads[0].commentSide, commentSide);
@@ -1151,7 +1151,7 @@
           '3', 1, commentSide, range, true));
 
       threads = Polymer.dom(element.$.diff)
-          .queryDistributedElements('gr-diff-comment-thread');
+          .queryDistributedElements('gr-comment-thread');
 
       assert.equal(threads.length, 2);
       assert.equal(threads[1].commentSide, commentSide);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index 27e467d..6a9d88f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -83,7 +83,7 @@
         targetClasses.push(SelectionClass.BLAME);
       } else {
         const commentSelected =
-            this._elementDescendedFromClass(e.target, 'gr-diff-comment');
+            this._elementDescendedFromClass(e.target, 'gr-comment');
         const side = this.diffBuilder.getSideByLineEl(lineEl);
 
         targetClasses.push(side === 'left' ?
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 5a56069..dadf8a7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -221,7 +221,7 @@
         [this.Shortcut.EXPAND_ALL_DIFF_CONTEXT]: '_handleExpandAllDiffContext',
         [this.Shortcut.NEXT_UNREVIEWED_FILE]: '_handleNextUnreviewedFile',
 
-        // Final two are actually handled by gr-diff-comment-thread.
+        // Final two are actually handled by gr-comment-thread.
         [this.Shortcut.EXPAND_ALL_COMMENT_THREADS]: null,
         [this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS]: null,
       };