ChangeScreen2: Show inline comments in history
When a history comment is expanded include the inline comments written on that revision by the author at the same timestamp. This makes it easier to follow a change's evolution over time, especially if the reader is new to the change and does not have the entire email thread in their inbox. Bug: issue 93 Change-Id: I57fe29fea2b3651306ff0a58746cefcfe1669267
This commit is contained in:
		| @@ -71,7 +71,6 @@ import com.google.gwt.user.client.EventListener; | ||||
| import com.google.gwt.user.client.rpc.AsyncCallback; | ||||
| import com.google.gwt.user.client.ui.Anchor; | ||||
| import com.google.gwt.user.client.ui.Button; | ||||
| import com.google.gwt.user.client.ui.FlowPanel; | ||||
| import com.google.gwt.user.client.ui.HTMLPanel; | ||||
| import com.google.gwt.user.client.ui.Image; | ||||
| import com.google.gwt.user.client.ui.ToggleButton; | ||||
| @@ -150,7 +149,7 @@ public class ChangeScreen2 extends Screen { | ||||
|   @UiField CommitBox commit; | ||||
|   @UiField RelatedChanges related; | ||||
|   @UiField FileTable files; | ||||
|   @UiField FlowPanel history; | ||||
|   @UiField History history; | ||||
|  | ||||
|   @UiField Button includedIn; | ||||
|   @UiField Button revisions; | ||||
| @@ -520,6 +519,7 @@ public class ChangeScreen2 extends Screen { | ||||
|  | ||||
|   private List<NativeMap<JsArray<CommentInfo>>> loadComments( | ||||
|       RevisionInfo rev, CallbackGroup group) { | ||||
|     final int id = rev._number(); | ||||
|     final List<NativeMap<JsArray<CommentInfo>>> r = | ||||
|         new ArrayList<NativeMap<JsArray<CommentInfo>>>(1); | ||||
|     ChangeApi.revision(changeId.get(), rev.name()) | ||||
| @@ -528,6 +528,7 @@ public class ChangeScreen2 extends Screen { | ||||
|         @Override | ||||
|         public void onSuccess(NativeMap<JsArray<CommentInfo>> result) { | ||||
|           r.add(result); | ||||
|           history.addComments(id, result); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
| @@ -657,7 +658,6 @@ public class ChangeScreen2 extends Screen { | ||||
|     renderCommitSubject(info); | ||||
|     renderOwner(info); | ||||
|     renderActionTextDate(info); | ||||
|     renderHistory(info); | ||||
|     initIncludedInAction(info); | ||||
|     initRevisionsAction(info, revision); | ||||
|     initDownloadAction(info, revision); | ||||
| @@ -673,6 +673,7 @@ public class ChangeScreen2 extends Screen { | ||||
|     related.set(info, revision); | ||||
|     reviewers.set(info); | ||||
|     quickApprove.set(info, revision); | ||||
|     history.set(commentLinkProcessor, changeId, info); | ||||
|  | ||||
|     if (Gerrit.isSignedIn()) { | ||||
|       initEditMessageAction(info, revision); | ||||
| @@ -736,15 +737,6 @@ public class ChangeScreen2 extends Screen { | ||||
|     actionDate.setInnerText(FormatUtil.relativeFormat(info.updated())); | ||||
|   } | ||||
|  | ||||
|   private void renderHistory(ChangeInfo info) { | ||||
|     JsArray<MessageInfo> messages = info.messages(); | ||||
|     if (messages != null) { | ||||
|       for (int i = 0; i < messages.length(); i++) { | ||||
|         history.add(new Message(commentLinkProcessor, messages.get(i))); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void showUpdates(ChangeInfo newInfo) { | ||||
|     if (!isAttached() || newInfo.updated().equals(lastDisplayedUpdate)) { | ||||
|       return; | ||||
|   | ||||
| @@ -432,6 +432,6 @@ limitations under the License. | ||||
|         </g:Button> | ||||
|       </div> | ||||
|     </div> | ||||
|     <g:FlowPanel ui:field='history'/> | ||||
|     <c:History ui:field='history'/> | ||||
|   </g:HTMLPanel> | ||||
| </ui:UiBinder> | ||||
|   | ||||
| @@ -0,0 +1,83 @@ | ||||
| // Copyright (C) 2013 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. | ||||
|  | ||||
| package com.google.gerrit.client.change; | ||||
|  | ||||
| import com.google.gerrit.client.Gerrit; | ||||
| import com.google.gerrit.client.changes.CommentInfo; | ||||
| import com.google.gerrit.client.ui.CommentLinkProcessor; | ||||
| import com.google.gerrit.reviewdb.client.Change; | ||||
| import com.google.gerrit.reviewdb.client.PatchSet; | ||||
| import com.google.gwt.core.client.GWT; | ||||
| import com.google.gwt.event.dom.client.ClickEvent; | ||||
| import com.google.gwt.uibinder.client.UiBinder; | ||||
| import com.google.gwt.uibinder.client.UiField; | ||||
| import com.google.gwt.uibinder.client.UiHandler; | ||||
| import com.google.gwt.user.client.ui.Anchor; | ||||
| import com.google.gwt.user.client.ui.Composite; | ||||
| import com.google.gwt.user.client.ui.FlowPanel; | ||||
| import com.google.gwt.user.client.ui.HTMLPanel; | ||||
| import com.google.gwtorm.client.KeyUtil; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
|  | ||||
| class FileComments extends Composite { | ||||
|   interface Binder extends UiBinder<HTMLPanel, FileComments> {} | ||||
|   private static final Binder uiBinder = GWT.create(Binder.class); | ||||
|  | ||||
|   private final String url; | ||||
|   @UiField Anchor path; | ||||
|   @UiField FlowPanel comments; | ||||
|  | ||||
|   FileComments(CommentLinkProcessor clp, | ||||
|       PatchSet.Id ps, | ||||
|       String title, | ||||
|       List<CommentInfo> list) { | ||||
|     initWidget(uiBinder.createAndBindUi(this)); | ||||
|  | ||||
|     url = url(ps, list.get(0)); | ||||
|     path.setHref("#" + url); | ||||
|     path.setText(title); | ||||
|  | ||||
|     Collections.sort(list, new Comparator<CommentInfo>() { | ||||
|       @Override | ||||
|       public int compare(CommentInfo a, CommentInfo b) { | ||||
|         return a.line() - b.line(); | ||||
|       } | ||||
|     }); | ||||
|     for (CommentInfo c : list) { | ||||
|       comments.add(new LineComment(clp, c)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @UiHandler("path") | ||||
|   void onClick(ClickEvent e) { | ||||
|     e.preventDefault(); | ||||
|     e.stopPropagation(); | ||||
|     Gerrit.display(url); | ||||
|   } | ||||
|  | ||||
|   private static String url(PatchSet.Id ps, CommentInfo info) { | ||||
|     // TODO(sop): Switch to Dispatcher.toPatchSideBySide. | ||||
|     Change.Id c = ps.getParentKey(); | ||||
|     return new StringBuilder() | ||||
|       .append("/c/").append(c.get()).append('/') | ||||
|       .append(ps.get()).append('/') | ||||
|       .append(KeyUtil.encode(info.path())) | ||||
|       .append(",cm") | ||||
|       .toString(); | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- | ||||
| Copyright (C) 2013 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. | ||||
| --> | ||||
| <ui:UiBinder | ||||
|     xmlns:ui='urn:ui:com.google.gwt.uibinder' | ||||
|     xmlns:c='urn:import:com.google.gerrit.client' | ||||
|     xmlns:g='urn:import:com.google.gwt.user.client.ui'> | ||||
|   <ui:style> | ||||
|     .box { | ||||
|     } | ||||
|     .path { | ||||
|       display: block; | ||||
|       white-space: nowrap; | ||||
|     } | ||||
|     .comments { | ||||
|       margin-left: 1em; | ||||
|     } | ||||
|   </ui:style> | ||||
|  | ||||
|   <g:HTMLPanel styleName='{style.box}'> | ||||
|     <g:Anchor styleName='{style.path}' ui:field='path'/> | ||||
|     <g:FlowPanel styleName='{style.comments}' ui:field='comments'/> | ||||
|   </g:HTMLPanel> | ||||
| </ui:UiBinder> | ||||
| @@ -0,0 +1,172 @@ | ||||
| // Copyright (C) 2013 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. | ||||
|  | ||||
| package com.google.gerrit.client.change; | ||||
|  | ||||
| import com.google.gerrit.client.account.AccountInfo; | ||||
| import com.google.gerrit.client.changes.ChangeApi; | ||||
| import com.google.gerrit.client.changes.ChangeInfo; | ||||
| import com.google.gerrit.client.changes.ChangeInfo.MessageInfo; | ||||
| import com.google.gerrit.client.changes.CommentInfo; | ||||
| import com.google.gerrit.client.rpc.NativeMap; | ||||
| import com.google.gerrit.client.rpc.Natives; | ||||
| import com.google.gerrit.client.ui.CommentLinkProcessor; | ||||
| import com.google.gerrit.reviewdb.client.Change; | ||||
| import com.google.gerrit.reviewdb.client.PatchSet; | ||||
| import com.google.gwt.core.client.JsArray; | ||||
| import com.google.gwt.user.client.rpc.AsyncCallback; | ||||
| import com.google.gwt.user.client.ui.FlowPanel; | ||||
| import com.google.gwt.user.client.ui.Widget; | ||||
|  | ||||
| import java.sql.Timestamp; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
|  | ||||
| class History extends FlowPanel { | ||||
|   private CommentLinkProcessor clp; | ||||
|   private Change.Id changeId; | ||||
|  | ||||
|   private final Set<Integer> loaded = new HashSet<Integer>(); | ||||
|   private final Map<AuthorRevision, List<CommentInfo>> byAuthor = | ||||
|       new HashMap<AuthorRevision, List<CommentInfo>>(); | ||||
|  | ||||
|   void set(CommentLinkProcessor clp, Change.Id id, ChangeInfo info) { | ||||
|     this.clp = clp; | ||||
|     this.changeId = id; | ||||
|  | ||||
|     JsArray<MessageInfo> messages = info.messages(); | ||||
|     if (messages != null) { | ||||
|       for (MessageInfo msg : Natives.asList(messages)) { | ||||
|         Message ui = new Message(this, msg); | ||||
|         if (loaded.contains(msg._revisionNumber())) { | ||||
|           ui.addComments(comments(msg)); | ||||
|         } | ||||
|         add(ui); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   CommentLinkProcessor getCommentLinkProcessor() { | ||||
|     return clp; | ||||
|   } | ||||
|  | ||||
|   Change.Id getChangeId() { | ||||
|     return changeId; | ||||
|   } | ||||
|  | ||||
|   void addComments(int id, NativeMap<JsArray<CommentInfo>> map) { | ||||
|     loaded.add(id); | ||||
|  | ||||
|     for (String path : map.keySet()) { | ||||
|       for (CommentInfo c : Natives.asList(map.get(path))) { | ||||
|         c.setPath(path); | ||||
|         if (c.author() != null) { | ||||
|           AuthorRevision k = new AuthorRevision(c.author(), id); | ||||
|           List<CommentInfo> l = byAuthor.get(k); | ||||
|           if (l == null) { | ||||
|             l = new ArrayList<CommentInfo>(); | ||||
|             byAuthor.put(k, l); | ||||
|           } | ||||
|           l.add(c); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void load(final int revisionNumber) { | ||||
|     if (revisionNumber > 0 && loaded.add(revisionNumber)) { | ||||
|       ChangeApi.revision(new PatchSet.Id(changeId, revisionNumber)) | ||||
|         .view("comments") | ||||
|         .get(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() { | ||||
|           @Override | ||||
|           public void onSuccess(NativeMap<JsArray<CommentInfo>> result) { | ||||
|             addComments(revisionNumber, result); | ||||
|             update(revisionNumber); | ||||
|           } | ||||
|  | ||||
|           @Override | ||||
|           public void onFailure(Throwable caught) { | ||||
|           } | ||||
|         }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void update(int revisionNumber) { | ||||
|     for (Widget child : getChildren()) { | ||||
|       Message ui = (Message) child; | ||||
|       MessageInfo info = ui.getMessageInfo(); | ||||
|       if (info._revisionNumber() == revisionNumber) { | ||||
|         ui.addComments(comments(info)); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private List<CommentInfo> comments(MessageInfo msg) { | ||||
|     if (msg.author() == null) { | ||||
|       return Collections.emptyList(); | ||||
|     } | ||||
|  | ||||
|     AuthorRevision k = new AuthorRevision(msg.author(), msg._revisionNumber()); | ||||
|     List<CommentInfo> list = byAuthor.get(k); | ||||
|     if (list == null) { | ||||
|       return Collections.emptyList(); | ||||
|     } | ||||
|  | ||||
|     Timestamp when = msg.date(); | ||||
|     List<CommentInfo> match = new ArrayList<CommentInfo>(); | ||||
|     List<CommentInfo> other = new ArrayList<CommentInfo>(); | ||||
|     for (int i = 0; i < list.size(); i++) { | ||||
|       CommentInfo c = list.get(i); | ||||
|       if (c.updated().compareTo(when) <= 0) { | ||||
|         match.add(c); | ||||
|       } else { | ||||
|         other.add(c); | ||||
|       } | ||||
|     } | ||||
|     if (match.isEmpty()) { | ||||
|       return Collections.emptyList(); | ||||
|     } else if (other.isEmpty()) { | ||||
|       byAuthor.remove(k); | ||||
|     } else { | ||||
|       byAuthor.put(k, other); | ||||
|     } | ||||
|     return match; | ||||
|   } | ||||
|  | ||||
|   private static final class AuthorRevision { | ||||
|     final int author; | ||||
|     final int revision; | ||||
|  | ||||
|     AuthorRevision(AccountInfo author, int revision) { | ||||
|       this.author = author._account_id(); | ||||
|       this.revision = revision; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|       return author * 31 + revision; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean equals(Object o) { | ||||
|       AuthorRevision b = (AuthorRevision) o; | ||||
|       return author == b.author && revision == b.revision; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,46 @@ | ||||
| // Copyright (C) 2013 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. | ||||
|  | ||||
| package com.google.gerrit.client.change; | ||||
|  | ||||
| import com.google.gerrit.client.changes.CommentInfo; | ||||
| import com.google.gerrit.client.changes.Util; | ||||
| import com.google.gerrit.client.ui.CommentLinkProcessor; | ||||
| import com.google.gwt.core.client.GWT; | ||||
| import com.google.gwt.dom.client.Element; | ||||
| import com.google.gwt.uibinder.client.UiBinder; | ||||
| import com.google.gwt.uibinder.client.UiField; | ||||
| import com.google.gwt.user.client.ui.Composite; | ||||
| import com.google.gwt.user.client.ui.HTMLPanel; | ||||
| import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; | ||||
|  | ||||
| class LineComment extends Composite { | ||||
|   interface Binder extends UiBinder<HTMLPanel, LineComment> {} | ||||
|   private static final Binder uiBinder = GWT.create(Binder.class); | ||||
|  | ||||
|   @UiField Element location; | ||||
|   @UiField Element message; | ||||
|  | ||||
|   LineComment(CommentLinkProcessor clp, CommentInfo info) { | ||||
|     initWidget(uiBinder.createAndBindUi(this)); | ||||
|  | ||||
|     location.setInnerText(info.has_line() | ||||
|         ? Util.M.lineHeader(info.line()) | ||||
|         : Util.C.fileCommentHeader()); | ||||
|     if (info.message() != null) { | ||||
|       message.setInnerSafeHtml(clp.apply(new SafeHtmlBuilder() | ||||
|           .append(info.message().trim()).wikify())); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- | ||||
| Copyright (C) 2013 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. | ||||
| --> | ||||
| <ui:UiBinder | ||||
|     xmlns:ui='urn:ui:com.google.gwt.uibinder' | ||||
|     xmlns:c='urn:import:com.google.gerrit.client' | ||||
|     xmlns:g='urn:import:com.google.gwt.user.client.ui'> | ||||
|   <ui:style> | ||||
|     .box { | ||||
|       position: relative; | ||||
|     } | ||||
|     .location { | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       left: 0; | ||||
|       font-weight: bold; | ||||
|     } | ||||
|     .message { | ||||
|       margin-left: 100px; | ||||
|     } | ||||
|   </ui:style> | ||||
|  | ||||
|   <g:HTMLPanel styleName='{style.box}'> | ||||
|     <div class='{style.location}' ui:field='location'/> | ||||
|     <div class='{style.message}' ui:field='message'/> | ||||
|   </g:HTMLPanel> | ||||
| </ui:UiBinder> | ||||
| @@ -18,8 +18,11 @@ import com.google.gerrit.client.AvatarImage; | ||||
| import com.google.gerrit.client.FormatUtil; | ||||
| import com.google.gerrit.client.Gerrit; | ||||
| import com.google.gerrit.client.changes.ChangeInfo.MessageInfo; | ||||
| import com.google.gerrit.client.changes.CommentInfo; | ||||
| import com.google.gerrit.client.changes.Util; | ||||
| import com.google.gerrit.client.ui.CommentLinkProcessor; | ||||
| import com.google.gerrit.reviewdb.client.Patch; | ||||
| import com.google.gerrit.reviewdb.client.PatchSet; | ||||
| import com.google.gwt.core.client.GWT; | ||||
| import com.google.gwt.dom.client.Element; | ||||
| import com.google.gwt.event.dom.client.ClickEvent; | ||||
| @@ -28,10 +31,17 @@ import com.google.gwt.resources.client.CssResource; | ||||
| import com.google.gwt.uibinder.client.UiBinder; | ||||
| import com.google.gwt.uibinder.client.UiField; | ||||
| import com.google.gwt.user.client.ui.Composite; | ||||
| import com.google.gwt.user.client.ui.FlowPanel; | ||||
| import com.google.gwt.user.client.ui.HTMLPanel; | ||||
| import com.google.gwt.user.client.ui.UIObject; | ||||
| import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.TreeMap; | ||||
|  | ||||
| class Message extends Composite { | ||||
|   interface Binder extends UiBinder<HTMLPanel, Message> {} | ||||
|   private static final Binder uiBinder = GWT.create(Binder.class); | ||||
| @@ -46,11 +56,16 @@ class Message extends Composite { | ||||
|   @UiField Element summary; | ||||
|   @UiField Element date; | ||||
|   @UiField Element message; | ||||
|   @UiField FlowPanel comments; | ||||
|  | ||||
|   private final History history; | ||||
|   private final MessageInfo info; | ||||
|   private List<CommentInfo> commentList; | ||||
|  | ||||
|   @UiField(provided = true) | ||||
|   AvatarImage avatar; | ||||
|  | ||||
|   Message(CommentLinkProcessor clp, MessageInfo info) { | ||||
|   Message(History parent, MessageInfo info) { | ||||
|     if (info.author() != null) { | ||||
|       avatar = new AvatarImage(info.author()); | ||||
|       avatar.setSize("", ""); | ||||
| @@ -66,23 +81,40 @@ class Message extends Composite { | ||||
|       } | ||||
|     }, ClickEvent.getType()); | ||||
|  | ||||
|     this.history = parent; | ||||
|     this.info = info; | ||||
|  | ||||
|     name.setInnerText(authorName(info)); | ||||
|     date.setInnerText(FormatUtil.shortFormatDayTime(info.date())); | ||||
|     if (info.message() != null) { | ||||
|       String msg = info.message().trim(); | ||||
|       summary.setInnerText(msg); | ||||
|       message.setInnerSafeHtml(clp.apply( | ||||
|           new SafeHtmlBuilder().append(msg).wikify())); | ||||
|       message.setInnerSafeHtml(history.getCommentLinkProcessor() | ||||
|         .apply(new SafeHtmlBuilder().append(msg).wikify())); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   MessageInfo getMessageInfo() { | ||||
|     return info; | ||||
|   } | ||||
|  | ||||
|   private boolean isOpen() { | ||||
|     return UIObject.isVisible(message); | ||||
|   } | ||||
|  | ||||
|   void setOpen(boolean open) { | ||||
|     if (open && info._revisionNumber() > 0) { | ||||
|       if (commentList == null) { | ||||
|         history.load(info._revisionNumber()); | ||||
|       } else if (!commentList.isEmpty()) { | ||||
|         renderComments(commentList); | ||||
|         commentList = Collections.emptyList(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     UIObject.setVisible(summary, !open); | ||||
|     UIObject.setVisible(message, open); | ||||
|     comments.setVisible(open && comments.getWidgetCount() > 0); | ||||
|     if (open) { | ||||
|       removeStyleName(style.closed()); | ||||
|     } else { | ||||
| @@ -90,6 +122,46 @@ class Message extends Composite { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void addComments(List<CommentInfo> list) { | ||||
|     if (isOpen()) { | ||||
|       renderComments(list); | ||||
|       comments.setVisible(comments.getWidgetCount() > 0); | ||||
|       commentList = Collections.emptyList(); | ||||
|     } else { | ||||
|       commentList = list; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void renderComments(List<CommentInfo> list) { | ||||
|     CommentLinkProcessor clp = history.getCommentLinkProcessor(); | ||||
|     PatchSet.Id ps = new PatchSet.Id( | ||||
|         history.getChangeId(), | ||||
|         info._revisionNumber()); | ||||
|     TreeMap<String, List<CommentInfo>> m = byPath(list); | ||||
|     List<CommentInfo> l = m.remove(Patch.COMMIT_MSG); | ||||
|     if (l != null) { | ||||
|       comments.add(new FileComments(clp, ps, Util.C.commitMessage(), l)); | ||||
|     } | ||||
|     for (Map.Entry<String, List<CommentInfo>> e : m.entrySet()) { | ||||
|       comments.add(new FileComments(clp, ps, e.getKey(), e.getValue())); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private static TreeMap<String, List<CommentInfo>> | ||||
|   byPath(List<CommentInfo> list) { | ||||
|     TreeMap<String, List<CommentInfo>> m = | ||||
|         new TreeMap<String, List<CommentInfo>>(); | ||||
|     for (CommentInfo c : list) { | ||||
|       List<CommentInfo> l = m.get(c.path()); | ||||
|       if (l == null) { | ||||
|         l = new ArrayList<CommentInfo>(); | ||||
|         m.put(c.path(), l); | ||||
|       } | ||||
|       l.add(c); | ||||
|     } | ||||
|     return m; | ||||
|   } | ||||
|  | ||||
|   static String authorName(MessageInfo info) { | ||||
|     if (info.author() != null) { | ||||
|       if (info.author().name() != null) { | ||||
|   | ||||
| @@ -93,6 +93,7 @@ limitations under the License. | ||||
|       <div ui:field='message' | ||||
|            aria-hidden='true' | ||||
|            style='display: NONE'/> | ||||
|       <g:FlowPanel ui:field='comments' visible='false'/> | ||||
|     </div> | ||||
|   </g:HTMLPanel> | ||||
| </ui:UiBinder> | ||||
|   | ||||
| @@ -267,6 +267,7 @@ public class ChangeInfo extends JavaScriptObject { | ||||
|   public static class MessageInfo extends JavaScriptObject { | ||||
|     public final native AccountInfo author() /*-{ return this.author; }-*/; | ||||
|     public final native String message() /*-{ return this.message; }-*/; | ||||
|     public final native int _revisionNumber() /*-{ return this._revision_number || 0; }-*/; | ||||
|     private final native String dateRaw() /*-{ return this.date; }-*/; | ||||
|  | ||||
|     public final Timestamp date() { | ||||
|   | ||||
| @@ -42,7 +42,7 @@ public class CommentInfo extends JavaScriptObject { | ||||
|   } | ||||
|  | ||||
|   private final native void setId(String id) /*-{ this.id = id; }-*/; | ||||
|   private final native void setPath(String path) /*-{ this.path = path; }-*/; | ||||
|   public final native void setPath(String path) /*-{ this.path = path; }-*/; | ||||
|  | ||||
|   private final void setSide(Side side) { | ||||
|     setSideRaw(side.toString()); | ||||
| @@ -68,7 +68,7 @@ public class CommentInfo extends JavaScriptObject { | ||||
|   } | ||||
|   private final native String sideRaw() /*-{ return this.side }-*/; | ||||
|  | ||||
|   public final native int line() /*-{ return this.line; }-*/; | ||||
|   public final native int line() /*-{ return this.line || 0; }-*/; | ||||
|   public final native String in_reply_to() /*-{ return this.in_reply_to; }-*/; | ||||
|   public final native String message() /*-{ return this.message; }-*/; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Shawn Pearce
					Shawn Pearce