Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/elements/issue-detail/mr-comment-list/mr-comment-list.js b/static_src/elements/issue-detail/mr-comment-list/mr-comment-list.js
new file mode 100644
index 0000000..aad9a8a
--- /dev/null
+++ b/static_src/elements/issue-detail/mr-comment-list/mr-comment-list.js
@@ -0,0 +1,163 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {cache} from 'lit-html/directives/cache.js';
+import {LitElement, html, css} from 'lit-element';
+
+import '../../chops/chops-button/chops-button.js';
+import './mr-comment.js';
+import {connectStore} from 'reducers/base.js';
+import * as userV0 from 'reducers/userV0.js';
+import * as ui from 'reducers/ui.js';
+import {userIsMember} from 'shared/helpers.js';
+import {SHARED_STYLES} from 'shared/shared-styles.js';
+
+/**
+ * `<mr-comment-list>`
+ *
+ * Display a list of Monorail comments.
+ *
+ */
+export class MrCommentList extends connectStore(LitElement) {
+  /** @override */
+  constructor() {
+    super();
+
+    this.commentsShownCount = 2;
+    this.comments = [];
+    this.headingLevel = 4;
+
+    this.focusId = null;
+
+    this.usersProjects = new Map();
+
+    this._hideComments = true;
+  }
+
+  /** @override */
+  static get properties() {
+    return {
+      commentsShownCount: {type: Number},
+      comments: {type: Array},
+      headingLevel: {type: Number},
+
+      focusId: {type: String},
+
+      usersProjects: {type: Object},
+
+      _hideComments: {type: Boolean},
+    };
+  }
+
+  /** @override */
+  stateChanged(state) {
+    this.focusId = ui.focusId(state);
+    this.usersProjects = userV0.projectsPerUser(state);
+  }
+
+  /** @override */
+  updated(changedProperties) {
+    super.updated(changedProperties);
+
+    if (!this._hideComments) return;
+
+    // If any hidden comment is focused, show all hidden comments.
+    const hiddenCount =
+      _hiddenCount(this.comments.length, this.commentsShownCount);
+    const hiddenComments = this.comments.slice(0, hiddenCount);
+    for (const comment of hiddenComments) {
+      if ('c' + comment.sequenceNum === this.focusId) {
+        this._hideComments = false;
+        break;
+      }
+    };
+  }
+
+  /** @override */
+  static get styles() {
+    return [SHARED_STYLES, css`
+      button.toggle {
+        background: none;
+        color: var(--chops-link-color);
+        border: 0;
+        border-bottom: var(--chops-normal-border);
+        border-top: var(--chops-normal-border);
+        width: 100%;
+        padding: 0.5em 8px;
+        text-align: left;
+        font-size: var(--chops-main-font-size);
+      }
+      button.toggle:hover {
+        cursor: pointer;
+        text-decoration: underline;
+      }
+      button.toggle[hidden] {
+        display: none;
+      }
+      .edit-slot {
+        margin-top: 3em;
+      }
+    `];
+  }
+
+  /** @override */
+  render() {
+    const hiddenCount =
+      _hiddenCount(this.comments.length, this.commentsShownCount);
+    return html`
+      <button @click=${this._toggleHide}
+          class="toggle"
+          ?hidden=${hiddenCount <= 0}>
+        ${this._hideComments ? 'Show' : 'Hide'}
+        ${hiddenCount}
+        older
+        ${hiddenCount == 1 ? 'comment' : 'comments'}
+      </button>
+      ${cache(this._hideComments ? '' :
+    html`${this.comments.slice(0, hiddenCount).map(
+        this.renderComment.bind(this))}`)}
+      ${this.comments.slice(hiddenCount).map(this.renderComment.bind(this))}
+    `;
+  }
+
+  /**
+   * Helper to render a single comment.
+   * @param {Comment} comment
+   * @return {TemplateResult}
+   */
+  renderComment(comment) {
+    const commenterIsMember = userIsMember(
+        comment.commenter, comment.projectName, this.usersProjects);
+    return html`
+      <mr-comment
+          .comment=${comment}
+          headingLevel=${this.headingLevel}
+          ?highlighted=${'c' + comment.sequenceNum === this.focusId}
+          ?commenterIsMember=${commenterIsMember}
+      ></mr-comment>`;
+  }
+
+  /**
+   * Hides or unhides comments that are hidden by default. For example,
+   * if an issue has 200 comments, the first 100 comments are shown initially,
+   * then the last 100 can be toggled to be shown.
+   * @private
+   */
+  _toggleHide() {
+    this._hideComments = !this._hideComments;
+  }
+}
+
+/**
+ * Computes how many comments the user is able to expand.
+ * @param {number} commentCount Total comments.
+ * @param {number} commentsShownCount The number of comments shown.
+ * @return {number} The number of hidden comments.
+ * @private
+ */
+function _hiddenCount(commentCount, commentsShownCount) {
+  return Math.max(commentCount - commentsShownCount, 0);
+}
+
+customElements.define('mr-comment-list', MrCommentList);