Add reply button to each cover message comment

Reply button in cover message comment would allow to reply onto a
specific comment. When pressed a new cover message comment would
be created with the master cover message copied and '>' prefixes
inserted.

Feature: issue 592
Change-Id: I7ae221d8de09f2b643e3564ffddb03a9a2ceadf6
This commit is contained in:
Mani Chandel
2013-11-21 18:56:54 +05:30
committed by Shawn Pearce
parent 08c3cd446d
commit 57d0f41ef9
6 changed files with 100 additions and 6 deletions

View File

@@ -417,7 +417,7 @@ public class ChangeScreen2 extends Screen {
private void onReply() {
if (Gerrit.isSignedIn()) {
replyAction.onReply();
replyAction.onReply(null);
} else {
Gerrit.doSignIn(getToken());
}
@@ -673,7 +673,6 @@ 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);
@@ -687,6 +686,8 @@ public class ChangeScreen2 extends Screen {
});
}
}
history.set(commentLinkProcessor, replyAction, changeId, info);
if (current) {
loadMergeable(info.status(), canSubmit);
}

View File

@@ -40,14 +40,17 @@ import java.util.Set;
class History extends FlowPanel {
private CommentLinkProcessor clp;
private ReplyAction replyAction;
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) {
void set(CommentLinkProcessor clp, ReplyAction ra,
Change.Id id, ChangeInfo info) {
this.clp = clp;
this.replyAction = ra;
this.changeId = id;
JsArray<MessageInfo> messages = info.messages();
@@ -70,6 +73,10 @@ class History extends FlowPanel {
return changeId;
}
void replyTo(MessageInfo info) {
replyAction.onReply(info);
}
void addComments(int id, NativeMap<JsArray<CommentInfo>> map) {
loaded.add(id);

View File

@@ -25,11 +25,14 @@ 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.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
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.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
@@ -55,6 +58,7 @@ class Message extends Composite {
@UiField Element name;
@UiField Element summary;
@UiField Element date;
@UiField Button reply;
@UiField Element message;
@UiField FlowPanel comments;
@@ -91,6 +95,19 @@ class Message extends Composite {
summary.setInnerText(msg);
message.setInnerSafeHtml(history.getCommentLinkProcessor()
.apply(new SafeHtmlBuilder().append(msg).wikify()));
} else {
reply.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
}
@UiHandler("reply")
void onReply(ClickEvent e) {
e.stopPropagation();
if (Gerrit.isSignedIn()) {
history.replyTo(info);
} else {
Gerrit.doSignIn(com.google.gwt.user.client.History.getToken());
}
}

View File

@@ -66,7 +66,7 @@ limitations under the License.
position: absolute;
top: 0;
left: 120px;
width: 915px;
width: 880px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -76,7 +76,25 @@ limitations under the License.
white-space: nowrap;
position: absolute;
top: 0;
right: 5px;
right: 18px;
}
.reply {
position: absolute;
top: 0;
right: 1px;
cursor: pointer;
outline: none;
border: none;
background: transparent;
margin: 0;
padding: 0;
line-height: 15px;
font-family: Arial Unicode MS, sans-serif;
font-size: 18px;
}
.closed .reply {
visibility: HIDDEN;
}
</ui:style>
@@ -89,6 +107,12 @@ limitations under the License.
<div class='{style.name}' ui:field='name'/>
<div ui:field='summary' class='{style.summary}'/>
<div class='{style.date}' ui:field='date'/>
<g:Button styleName='{style.reply}'
ui:field='reply'
title='Reply to this message'>
<ui:attribute name='title'/>
<div>&#x21a9;</div>
</g:Button>
</g:HTMLPanel>
<div ui:field='message'
aria-hidden='true'

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.client.change;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JsArrayString;
@@ -57,7 +58,7 @@ class ReplyAction {
: NativeMap.<JsArrayString> create();
}
void onReply() {
void onReply(MessageInfo msg) {
if (popup != null) {
popup.hide();
return;
@@ -72,6 +73,9 @@ class ReplyAction {
allLabels = null;
permittedLabels = null;
}
if (msg != null) {
replyBox.replyTo(msg);
}
final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
p.setStyleName(style.replyBox());

View File

@@ -18,6 +18,7 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
import com.google.gerrit.client.changes.ReviewInput;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap;
@@ -27,6 +28,7 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -38,6 +40,7 @@ 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.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
@@ -102,8 +105,18 @@ class ReplyBox extends Composite {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
Window.scrollTo(0, 0);
message.setFocus(true);
}});
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
@Override
public boolean execute() {
String t = message.getText();
if (t != null) {
message.setCursorPos(t.length());
}
return false;
}}, 0);
}
@UiHandler("message")
@@ -155,6 +168,34 @@ class ReplyBox extends Composite {
hide();
}
void replyTo(MessageInfo msg) {
if (msg.message() != null) {
String t = message.getText();
String m = quote(msg);
if (t == null || t.isEmpty()) {
t = m;
} else if (t.endsWith("\n\n")) {
t += m;
} else if (t.endsWith("\n")) {
t += "\n" + m;
} else {
t += "\n\n" + m;
}
message.setText(t + "\n\n");
}
}
private static String quote(MessageInfo msg) {
String m = msg.message().trim();
if (m.startsWith("Patch Set ")) {
int i = m.indexOf('\n');
if (i > 0) {
m = m.substring(i + 1).trim();
}
}
return "> " + m.replaceAll("\\n", "\\\n> ");
}
private void hide() {
for (Widget w = getParent(); w != null; w = w.getParent()) {
if (w instanceof PopupPanel) {