Allow for comments to be collapsible

Previously in Polygerrit, comments were always expanded. You could
always see the full comment (if multiline) and any applicable actions.

This change creates a collapsed comment view.  It adds a preview of the
text to the header row when collapsed, and can be toggled open when any
part of the header is clicked.

Bug: Issue 4698
Change-Id: Idca5caf92eb32518b6737dbb5a3380d227513996
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
index c3b6233..864712d 100644
--- 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
@@ -44,15 +44,20 @@
         padding: .5em .7em;
       }
       .header {
+        cursor: pointer;
         display: flex;
-        padding-bottom: 0;
         font-family: 'Open Sans', sans-serif;
+        padding-bottom: 0;
       }
-      .headerLeft {
+      .headerMiddle {
+        color: #666;
         flex: 1;
+        overflow: hidden;
       }
       .authorName,
       .draftLabel {
+        display: block;
+        float: left;
         font-weight: bold;
       }
       .draftLabel {
@@ -62,6 +67,7 @@
       .date {
         justify-content: flex-end;
         margin-left: 5px;
+        white-space: nowrap;
       }
       a.date:link,
       a.date:visited {
@@ -113,19 +119,62 @@
         background-color: #fff;
         display: block;
       }
+      .show-hide {
+        margin-left: .4em;
+      }
+      input.show-hide {
+        display: none;
+      }
+      label.show-hide {
+        color: #000;
+        cursor: pointer;
+        display: block;
+        font-size: .8em;
+        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-linked-text,
+      #container.collapsed iron-autogrow-textarea {
+        display: none;
+      }
     </style>
     <div id="container"
         class="container"
         on-mouseenter="_handleMouseEnter"
         on-mouseleave="_handleMouseLeave">
-      <div class="header" id="header">
+      <div class="header" id="header" on-click="_handleToggleCollapsed">
         <div class="headerLeft">
           <span class="authorName">[[comment.author.name]]</span>
           <span class="draftLabel">DRAFT</span>
         </div>
+        <div class="headerMiddle">
+          <span class="collapsedContent">[[comment.message]]</span>
+        </div>
         <a class="date" href$="[[_computeLinkToComment(comment)]]" on-tap="_handleLinkTap">
           <gr-date-formatter date-str="[[comment.updated]]"></gr-date-formatter>
         </a>
+        <div class="show-hide">
+          <label class="show-hide">
+            <input type="checkbox" class="show-hide"
+               checked$="[[_commentCollapsed]]"
+               on-change="_handleToggleCollapsed">
+            [[_computeShowHideText(_commentCollapsed)]]
+          </label>
+        </div>
       </div>
       <iron-autogrow-textarea
           id="editTextarea"
@@ -137,6 +186,7 @@
       <gr-linked-text class="message"
           pre
           content="[[comment.message]]"
+          collapsed="[[_commentCollapsed]]"
           config="[[projectConfig.commentlinks]]"></gr-linked-text>
       <div class="actions" hidden$="[[!showActions]]">
         <gr-button class="action reply" on-tap="_handleReply">Reply</gr-button>
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
index 07badbf..791f949 100644
--- 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
@@ -81,6 +81,11 @@
       },
       patchNum: String,
       showActions: Boolean,
+      _commentCollapsed: {
+        type: Boolean,
+        value: true,
+        observer: '_toggleCollapseClass',
+      },
       projectConfig: Object,
 
       _xhrPromise: Object,  // Used for testing.
@@ -96,10 +101,20 @@
       '_loadLocalDraft(changeNum, patchNum, comment)',
     ],
 
+    attached: function() {
+      if (this.editing) {
+        this._commentCollapsed = false;
+      }
+    },
+
     detached: function() {
       this.cancelDebouncer('fire-update');
     },
 
+    _computeShowHideText: function(collapsed) {
+      return collapsed ? '◀' : '▼';
+    },
+
     save: function() {
       this.comment.message = this._messageText;
       this.disabled = true;
@@ -210,6 +225,18 @@
       }
     },
 
+    _handleToggleCollapsed: function() {
+      this._commentCollapsed = !this._commentCollapsed;
+    },
+
+    _toggleCollapseClass: function(_commentCollapsed) {
+      if (_commentCollapsed) {
+        this.$.container.classList.add('collapsed');
+      } else {
+        this.$.container.classList.remove('collapsed');
+      }
+    },
+
     _commentMessageChanged: function(message) {
       this._messageText = message || '';
     },
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
index bddc3ab..b05746e 100644
--- 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
@@ -39,6 +39,12 @@
 </test-fixture>
 
 <script>
+
+  function isVisible(el) {
+    assert.ok(el);
+    return getComputedStyle(el).getPropertyValue('display') !== 'none';
+  }
+
   suite('gr-diff-comment tests', function() {
     var element;
     setup(function() {
@@ -58,6 +64,32 @@
       };
     });
 
+    test('collapsible comments', function() {
+      // When a comment (not draft) is loaded, it should be collapsed
+      assert.isFalse(isVisible(element.$$('gr-linked-text')),
+          'gr-linked-text is not visible');
+      assert.isFalse(isVisible(element.$$('.actions')),
+          'actions are not visible');
+      assert.isFalse(isVisible(element.$$('iron-autogrow-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.isTrue(isVisible(element.$$('gr-linked-text')),
+          'gr-linked-text is visible');
+      assert.isTrue(isVisible(element.$$('.actions')),
+          'actions are visible');
+      assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+          'textarea is not visible');
+      assert.isFalse(isVisible(element.$$('.collapsedContent')),
+          'header middle content is not visible');
+    });
+
     test('proper event fires on reply', function(done) {
       element.addEventListener('reply', function(e) {
         assert.ok(e.detail.comment);
@@ -135,11 +167,6 @@
       };
     });
 
-    function isVisible(el) {
-      assert.ok(el);
-      return getComputedStyle(el).getPropertyValue('display') != 'none';
-    }
-
     test('button visibility states', function() {
       element.showActions = false;
       assert.isTrue(element.$$('.actions').hasAttribute('hidden'));
@@ -181,6 +208,67 @@
       assert.isTrue(isVisible(element.$$('.cancel')), 'cancel is visible');
     });
 
+    test('collapsible drafts', function() {
+      element.addEventListener('reply', function(e) {
+        assert.ok(e.detail.comment);
+        done();
+      });
+      assert.isFalse(isVisible(element.$$('gr-linked-text')),
+          'gr-linked-text is not visible');
+      assert.isFalse(isVisible(element.$$('.actions')),
+          'actions are not visible');
+      assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+          'textarea is not visible');
+      assert.isTrue(isVisible(element.$$('.collapsedContent')),
+          'header middle content is visible');
+
+      MockInteractions.tap(element.$.header);
+      assert.isTrue(isVisible(element.$$('gr-linked-text')),
+          'gr-linked-text is visible');
+      assert.isTrue(isVisible(element.$$('.actions')),
+          'actions are visible');
+      assert.isFalse(isVisible(element.$$('iron-autogrow-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'));
+      assert.isFalse(isVisible(element.$$('gr-linked-text')),
+          'gr-linked-text is not visible');
+      assert.isTrue(isVisible(element.$$('.actions')),
+          'actions are visible');
+      assert.isTrue(isVisible(element.$$('iron-autogrow-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.isFalse(isVisible(element.$$('gr-linked-text')),
+          'gr-linked-text is not visible');
+      assert.isFalse(isVisible(element.$$('.actions')),
+          'actions are not visible');
+      assert.isFalse(isVisible(element.$$('iron-autogrow-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-linked-text')),
+          'gr-linked-text is not visible');
+      assert.isTrue(isVisible(element.$$('.actions')),
+          'actions are visible');
+      assert.isTrue(isVisible(element.$$('iron-autogrow-textarea')),
+          'textarea is visible');
+      assert.isFalse(isVisible(element.$$('.collapsedContent')),
+          'header middle content is not visible');
+    });
+
     test('draft creation/cancelation', function(done) {
       assert.isFalse(element.editing);
       MockInteractions.tap(element.$$('.edit'));