SideBySide2: Rewrite comment box UI
Use absolute positioned divs similar to the way the message history on ChangeScreen2 is organized. This is slightly smaller and faster DOM for the browser to handle, and works reasonably well for the comments. The new UI is fewer lines of code, uses less vertical screen space, and fixes a number of layout glitches and interaction issues. Change-Id: If6b382ef7ae57ebc811074a37fa17928020403be
This commit is contained in:
@@ -15,148 +15,43 @@
|
|||||||
package com.google.gerrit.client.diff;
|
package com.google.gerrit.client.diff;
|
||||||
|
|
||||||
import com.google.gerrit.client.changes.CommentInfo;
|
import com.google.gerrit.client.changes.CommentInfo;
|
||||||
import com.google.gerrit.client.ui.CommentLinkProcessor;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
|
||||||
import com.google.gwt.core.client.Scheduler;
|
import com.google.gwt.core.client.Scheduler;
|
||||||
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
||||||
import com.google.gwt.event.dom.client.ClickEvent;
|
|
||||||
import com.google.gwt.event.dom.client.ClickHandler;
|
|
||||||
import com.google.gwt.event.shared.HandlerRegistration;
|
|
||||||
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.Composite;
|
import com.google.gwt.user.client.ui.Composite;
|
||||||
import com.google.gwt.user.client.ui.HTML;
|
|
||||||
import com.google.gwt.user.client.ui.Widget;
|
|
||||||
import com.google.gwtexpui.safehtml.client.SafeHtml;
|
|
||||||
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
|
|
||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
|
||||||
import net.codemirror.lib.LineWidget;
|
import net.codemirror.lib.LineWidget;
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
|
|
||||||
/** An HtmlPanel for displaying a comment */
|
/** An HtmlPanel for displaying a comment */
|
||||||
abstract class CommentBox extends Composite {
|
abstract class CommentBox extends Composite {
|
||||||
interface CommentBoxStyle extends CssResource {
|
static {
|
||||||
String open();
|
Resources.I.style().ensureInjected();
|
||||||
String close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommentLinkProcessor commentLinkProcessor;
|
|
||||||
private CommentInfo original;
|
|
||||||
private PatchSet.Id patchSetId;
|
|
||||||
private PaddingManager widgetManager;
|
private PaddingManager widgetManager;
|
||||||
private SideBySide2 diffView;
|
|
||||||
private boolean draft;
|
|
||||||
private LineWidget selfWidget;
|
private LineWidget selfWidget;
|
||||||
private CodeMirror cm;
|
|
||||||
private HandlerRegistration regClick;
|
|
||||||
private ClickHandler clickFocusHandler;
|
|
||||||
|
|
||||||
@UiField(provided=true)
|
|
||||||
CommentBoxHeader header;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
HTML contentPanelMessage;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
CommentBoxResources res;
|
|
||||||
|
|
||||||
CommentBox(
|
|
||||||
SideBySide2 host,
|
|
||||||
CodeMirror cmInstance,
|
|
||||||
UiBinder<? extends Widget, CommentBox> binder,
|
|
||||||
PatchSet.Id id, CommentInfo info, CommentLinkProcessor linkProcessor,
|
|
||||||
boolean isDraft) {
|
|
||||||
diffView = host;
|
|
||||||
cm = cmInstance;
|
|
||||||
commentLinkProcessor = linkProcessor;
|
|
||||||
original = info;
|
|
||||||
patchSetId = id;
|
|
||||||
draft = isDraft;
|
|
||||||
header = new CommentBoxHeader(info.author(), info.updated(), isDraft);
|
|
||||||
initWidget(binder.createAndBindUi(this));
|
|
||||||
clickFocusHandler = new ClickHandler() {
|
|
||||||
@Override
|
|
||||||
public void onClick(ClickEvent event) {
|
|
||||||
cm.focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
enableClickFocusHandler();
|
|
||||||
res.style().ensureInjected();
|
|
||||||
setMessageText(info.message());
|
|
||||||
}
|
|
||||||
|
|
||||||
void resizePaddingWidget() {
|
void resizePaddingWidget() {
|
||||||
Scheduler.get().scheduleDeferred(new ScheduledCommand(){
|
Scheduler.get().scheduleDeferred(new ScheduledCommand(){
|
||||||
public void execute() {
|
public void execute() {
|
||||||
if (selfWidget == null || widgetManager == null) {
|
assert selfWidget != null;
|
||||||
throw new IllegalStateException(
|
assert widgetManager != null;
|
||||||
"resizePaddingWidget() called before setting up widgets");
|
|
||||||
}
|
|
||||||
selfWidget.changed();
|
selfWidget.changed();
|
||||||
widgetManager.resizePaddingWidget();
|
widgetManager.resizePaddingWidget();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMessageText(String message) {
|
abstract CommentInfo getCommentInfo();
|
||||||
if (message == null) {
|
abstract boolean isOpen();
|
||||||
message = "";
|
|
||||||
} else {
|
|
||||||
message = message.trim();
|
|
||||||
}
|
|
||||||
header.setSummaryText(message);
|
|
||||||
SafeHtml buf = new SafeHtmlBuilder().append(message).wikify();
|
|
||||||
buf = commentLinkProcessor.apply(buf);
|
|
||||||
SafeHtml.set(contentPanelMessage, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDate(Timestamp when) {
|
|
||||||
header.setDate(when);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOpen(boolean open) {
|
void setOpen(boolean open) {
|
||||||
if (open) {
|
|
||||||
removeStyleName(res.style().close());
|
|
||||||
addStyleName(res.style().open());
|
|
||||||
} else {
|
|
||||||
removeStyleName(res.style().open());
|
|
||||||
addStyleName(res.style().close());
|
|
||||||
}
|
|
||||||
resizePaddingWidget();
|
resizePaddingWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isOpen() {
|
|
||||||
return getStyleName().contains(res.style().open());
|
|
||||||
}
|
|
||||||
|
|
||||||
SideBySide2 getDiffView() {
|
|
||||||
return diffView;
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchSet.Id getPatchSetId() {
|
|
||||||
return patchSetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
CommentInfo getOriginal() {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateOriginal(CommentInfo newInfo) {
|
|
||||||
original = newInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
PaddingManager getPaddingManager() {
|
PaddingManager getPaddingManager() {
|
||||||
return widgetManager;
|
return widgetManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isDraft() {
|
|
||||||
return draft;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPaddingManager(PaddingManager manager) {
|
void setPaddingManager(PaddingManager manager) {
|
||||||
widgetManager = manager;
|
widgetManager = manager;
|
||||||
}
|
}
|
||||||
@@ -168,27 +63,4 @@ abstract class CommentBox extends Composite {
|
|||||||
LineWidget getSelfWidget() {
|
LineWidget getSelfWidget() {
|
||||||
return selfWidget;
|
return selfWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror getCm() {
|
|
||||||
return cm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiHandler("header")
|
|
||||||
void onHeaderClick(ClickEvent e) {
|
|
||||||
setOpen(!isOpen());
|
|
||||||
cm.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void enableClickFocusHandler() {
|
|
||||||
if (regClick == null) {
|
|
||||||
regClick = addDomHandler(clickFocusHandler, ClickEvent.getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void disableClickFocusHandler() {
|
|
||||||
if (regClick != null) {
|
|
||||||
regClick.removeHandler();
|
|
||||||
regClick = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
//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.diff;
|
|
||||||
|
|
||||||
import com.google.gerrit.client.AvatarImage;
|
|
||||||
import com.google.gerrit.client.FormatUtil;
|
|
||||||
import com.google.gerrit.client.account.AccountInfo;
|
|
||||||
import com.google.gerrit.client.patches.PatchUtil;
|
|
||||||
import com.google.gwt.core.client.GWT;
|
|
||||||
import com.google.gwt.dom.client.Element;
|
|
||||||
import com.google.gwt.event.dom.client.ClickEvent;
|
|
||||||
import com.google.gwt.event.dom.client.ClickHandler;
|
|
||||||
import com.google.gwt.event.dom.client.HasClickHandlers;
|
|
||||||
import com.google.gwt.event.dom.client.LoadEvent;
|
|
||||||
import com.google.gwt.event.dom.client.LoadHandler;
|
|
||||||
import com.google.gwt.event.shared.HandlerRegistration;
|
|
||||||
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.HTMLPanel;
|
|
||||||
import com.google.gwt.user.client.ui.UIObject;
|
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An HtmlPanel representing the header of a CommentBox, displaying
|
|
||||||
* the author's avatar (if applicable), the author's name, the summary,
|
|
||||||
* and the date.
|
|
||||||
*/
|
|
||||||
class CommentBoxHeader extends Composite implements HasClickHandlers {
|
|
||||||
interface Binder extends UiBinder<HTMLPanel, CommentBoxHeader> {}
|
|
||||||
private static Binder uiBinder = GWT.create(Binder.class);
|
|
||||||
|
|
||||||
interface CommentBoxHeaderStyle extends CssResource {
|
|
||||||
String name();
|
|
||||||
String summary();
|
|
||||||
String date();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean draft;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Element avatarCell;
|
|
||||||
|
|
||||||
@UiField(provided=true)
|
|
||||||
AvatarImage avatar;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Element name;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Element summary;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Element date;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
CommentBoxHeaderStyle headerStyle;
|
|
||||||
|
|
||||||
CommentBoxHeader(AccountInfo author, Timestamp when, boolean isDraft) {
|
|
||||||
if (author != null) {
|
|
||||||
avatar = new AvatarImage(author, 26);
|
|
||||||
avatar.setSize("", "");
|
|
||||||
} else {
|
|
||||||
avatar = new AvatarImage();
|
|
||||||
}
|
|
||||||
initWidget(uiBinder.createAndBindUi(this));
|
|
||||||
UIObject.setVisible(avatarCell, false);
|
|
||||||
avatar.addLoadHandler(new LoadHandler() {
|
|
||||||
@Override
|
|
||||||
public void onLoad(LoadEvent event) {
|
|
||||||
UIObject.setVisible(avatarCell, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
draft = isDraft;
|
|
||||||
if (when != null) {
|
|
||||||
setDate(when);
|
|
||||||
}
|
|
||||||
if (isDraft) {
|
|
||||||
name.setInnerText(PatchUtil.C.draft());
|
|
||||||
} else {
|
|
||||||
name.setInnerText(FormatUtil.name(author));
|
|
||||||
name.setTitle(FormatUtil.nameEmail(author));
|
|
||||||
date.setTitle(FormatUtil.mediumFormat(when));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDate(Timestamp when) {
|
|
||||||
if (draft) {
|
|
||||||
date.setInnerText(PatchUtil.M.draftSaved(when));
|
|
||||||
} else {
|
|
||||||
date.setInnerText(FormatUtil.shortFormatDayTime(when));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSummaryText(String message) {
|
|
||||||
summary.setInnerText(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HandlerRegistration addClickHandler(ClickHandler handler) {
|
|
||||||
return addDomHandler(handler, ClickEvent.getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?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:g='urn:import:com.google.gwt.user.client.ui'
|
|
||||||
xmlns:c='urn:import:com.google.gerrit.client'>
|
|
||||||
<ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' />
|
|
||||||
<ui:style field='headerStyle'
|
|
||||||
type='com.google.gerrit.client.diff.CommentBoxHeader.CommentBoxHeaderStyle'>
|
|
||||||
.avatarCell {
|
|
||||||
width: 26px;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
.name {
|
|
||||||
width: 15%;
|
|
||||||
font-weight: bold;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.summary {
|
|
||||||
width: 60%;
|
|
||||||
}
|
|
||||||
.date {
|
|
||||||
width: 15%;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</ui:style>
|
|
||||||
<g:HTMLPanel>
|
|
||||||
<table class='{res.style.table}'>
|
|
||||||
<tr>
|
|
||||||
<td ui:field='avatarCell' class='{headerStyle.avatarCell}'>
|
|
||||||
<c:AvatarImage ui:field='avatar' />
|
|
||||||
</td>
|
|
||||||
<td ui:field='name' class='{headerStyle.name}'></td>
|
|
||||||
<td class='{headerStyle.summary}'>
|
|
||||||
<div ui:field='summary' class='{res.style.summaryText}'></div>
|
|
||||||
</td>
|
|
||||||
<td ui:field='date' class='{headerStyle.date}'></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</g:HTMLPanel>
|
|
||||||
</ui:UiBinder>
|
|
||||||
@@ -1,45 +1,78 @@
|
|||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
.commentBox {
|
.commentBox {
|
||||||
background-color: #e5ecf9;
|
position: relative;
|
||||||
|
width: 679px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #fcfa96;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
-webkit-box-shadow: 3px 3px 3px #888888;
|
-webkit-box-shadow: 3px 3px 3px #888888;
|
||||||
-moz-box-shadow: 3px 3px 3px #888888;
|
-moz-box-shadow: 3px 3px 3px #888888;
|
||||||
box-shadow: 3px 3px 3px #888888;
|
box-shadow: 3px 3px 3px #888888;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
overflow: visible;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.header { cursor: pointer; }
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summaryText {
|
.summary {
|
||||||
color: #777;
|
color: #777;
|
||||||
height: 1em;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 120px;
|
||||||
|
width: 408px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: 2px;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.open .summaryText {
|
.date {
|
||||||
display: none;
|
white-space: nowrap;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close .contentPanel {
|
.contents {
|
||||||
display: none;
|
margin-left: 28px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.contents p,
|
||||||
|
.contents ul {
|
||||||
|
-webkit-margin-before: 0;
|
||||||
|
-webkit-margin-after: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.commentBox button {
|
||||||
margin-left: 5px;
|
margin-right: 3px;
|
||||||
margin-right: 5px;
|
margin-bottom: 1px;
|
||||||
|
padding: 1px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid black;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #4d90fe;
|
||||||
|
background-image: -webkit-linear-gradient(top, #4d90fe, #4d90fe);
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
-webkit-box-sizing: content-box;
|
||||||
|
}
|
||||||
|
.commentBox button div {
|
||||||
|
width: 25px;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.diff;
|
package com.google.gerrit.client.diff;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.FormatUtil;
|
||||||
import com.google.gerrit.client.changes.CommentApi;
|
import com.google.gerrit.client.changes.CommentApi;
|
||||||
import com.google.gerrit.client.changes.CommentInfo;
|
import com.google.gerrit.client.changes.CommentInfo;
|
||||||
import com.google.gerrit.client.changes.CommentInput;
|
import com.google.gerrit.client.changes.CommentInput;
|
||||||
@@ -22,88 +23,99 @@ import com.google.gerrit.client.ui.CommentLinkProcessor;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gwt.core.client.GWT;
|
import com.google.gwt.core.client.GWT;
|
||||||
import com.google.gwt.core.client.JavaScriptObject;
|
import com.google.gwt.core.client.JavaScriptObject;
|
||||||
|
import com.google.gwt.core.client.Scheduler;
|
||||||
|
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
|
||||||
|
import com.google.gwt.dom.client.Element;
|
||||||
import com.google.gwt.event.dom.client.ClickEvent;
|
import com.google.gwt.event.dom.client.ClickEvent;
|
||||||
|
import com.google.gwt.event.dom.client.ClickHandler;
|
||||||
import com.google.gwt.event.dom.client.DoubleClickEvent;
|
import com.google.gwt.event.dom.client.DoubleClickEvent;
|
||||||
|
import com.google.gwt.event.dom.client.DoubleClickHandler;
|
||||||
import com.google.gwt.event.dom.client.KeyCodes;
|
import com.google.gwt.event.dom.client.KeyCodes;
|
||||||
import com.google.gwt.event.dom.client.KeyDownEvent;
|
import com.google.gwt.event.dom.client.KeyDownEvent;
|
||||||
import com.google.gwt.event.dom.client.MouseMoveEvent;
|
import com.google.gwt.event.dom.client.MouseMoveEvent;
|
||||||
import com.google.gwt.event.dom.client.MouseMoveHandler;
|
import com.google.gwt.event.dom.client.MouseMoveHandler;
|
||||||
import com.google.gwt.resources.client.CssResource;
|
|
||||||
import com.google.gwt.uibinder.client.UiBinder;
|
import com.google.gwt.uibinder.client.UiBinder;
|
||||||
import com.google.gwt.uibinder.client.UiField;
|
import com.google.gwt.uibinder.client.UiField;
|
||||||
import com.google.gwt.uibinder.client.UiHandler;
|
import com.google.gwt.uibinder.client.UiHandler;
|
||||||
import com.google.gwt.user.client.Timer;
|
import com.google.gwt.user.client.Timer;
|
||||||
import com.google.gwt.user.client.ui.Button;
|
import com.google.gwt.user.client.ui.Button;
|
||||||
|
import com.google.gwt.user.client.ui.HTML;
|
||||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||||
|
import com.google.gwt.user.client.ui.UIObject;
|
||||||
import com.google.gwtexpui.globalkey.client.NpTextArea;
|
import com.google.gwtexpui.globalkey.client.NpTextArea;
|
||||||
|
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
|
||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
import net.codemirror.lib.CodeMirror;
|
||||||
|
|
||||||
/** An HtmlPanel for displaying and editing a draft */
|
/** An HtmlPanel for displaying and editing a draft */
|
||||||
class DraftBox extends CommentBox {
|
class DraftBox extends CommentBox {
|
||||||
interface Binder extends UiBinder<HTMLPanel, DraftBox> {}
|
interface Binder extends UiBinder<HTMLPanel, DraftBox> {}
|
||||||
private static UiBinder<HTMLPanel, CommentBox> uiBinder =
|
private static Binder uiBinder = GWT.create(Binder.class);
|
||||||
GWT.create(Binder.class);
|
|
||||||
|
|
||||||
interface DraftBoxStyle extends CssResource {
|
|
||||||
String edit();
|
|
||||||
String view();
|
|
||||||
String newDraft();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
NpTextArea editArea;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Button edit;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Button save;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Button cancel;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
Button discard;
|
|
||||||
|
|
||||||
@UiField
|
|
||||||
DraftBoxStyle draftStyle;
|
|
||||||
|
|
||||||
private static final int INITIAL_COLS = 60;
|
|
||||||
private static final int INITIAL_LINES = 5;
|
private static final int INITIAL_LINES = 5;
|
||||||
private static final int MAX_LINES = 30;
|
private static final int MAX_LINES = 30;
|
||||||
|
|
||||||
private boolean isNew;
|
private final SideBySide2 parent;
|
||||||
|
private final CodeMirror cm;
|
||||||
|
private final CommentLinkProcessor linkProcessor;
|
||||||
|
private final PatchSet.Id psId;
|
||||||
|
private CommentInfo comment;
|
||||||
private PublishedBox replyToBox;
|
private PublishedBox replyToBox;
|
||||||
private Timer expandTimer;
|
private Timer expandTimer;
|
||||||
|
|
||||||
DraftBox(
|
@UiField Element summary;
|
||||||
SideBySide2 host,
|
@UiField Element date;
|
||||||
CodeMirror cm,
|
|
||||||
PatchSet.Id id,
|
@UiField Element p_view;
|
||||||
CommentInfo info,
|
@UiField HTML message;
|
||||||
CommentLinkProcessor linkProcessor,
|
@UiField Button edit;
|
||||||
boolean isNewDraft,
|
@UiField Button discard1;
|
||||||
boolean saveOnInit) {
|
|
||||||
super(host, cm, uiBinder, id, info, linkProcessor, true);
|
@UiField Element p_edit;
|
||||||
|
@UiField NpTextArea editArea;
|
||||||
|
@UiField Button save;
|
||||||
|
@UiField Button cancel;
|
||||||
|
@UiField Button discard2;
|
||||||
|
|
||||||
|
DraftBox(
|
||||||
|
SideBySide2 parent,
|
||||||
|
CodeMirror cm,
|
||||||
|
CommentLinkProcessor clp,
|
||||||
|
PatchSet.Id id,
|
||||||
|
CommentInfo info) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.cm = cm;
|
||||||
|
this.linkProcessor = clp;
|
||||||
|
this.psId = id;
|
||||||
|
initWidget(uiBinder.createAndBindUi(this));
|
||||||
|
|
||||||
isNew = isNewDraft;
|
|
||||||
editArea.setText(info.message());
|
|
||||||
editArea.setCharacterWidth(INITIAL_COLS);
|
|
||||||
editArea.setVisibleLines(INITIAL_LINES);
|
|
||||||
editArea.setSpellCheck(true);
|
|
||||||
expandTimer = new Timer() {
|
expandTimer = new Timer() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
expandText();
|
expandText();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (saveOnInit) {
|
set(info);
|
||||||
onSave(null);
|
|
||||||
}
|
addDomHandler(new ClickHandler() {
|
||||||
if (isNew) {
|
@Override
|
||||||
addStyleName(draftStyle.newDraft());
|
public void onClick(ClickEvent event) {
|
||||||
}
|
if (!isEdit()) {
|
||||||
|
setOpen(!isOpen());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, ClickEvent.getType());
|
||||||
|
addDomHandler(new DoubleClickHandler() {
|
||||||
|
@Override
|
||||||
|
public void onDoubleClick(DoubleClickEvent event) {
|
||||||
|
if (isEdit()) {
|
||||||
|
editArea.setFocus(true);
|
||||||
|
} else {
|
||||||
|
setOpen(true);
|
||||||
|
setEdit(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, DoubleClickEvent.getType());
|
||||||
addDomHandler(new MouseMoveHandler() {
|
addDomHandler(new MouseMoveHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void onMouseMove(MouseMoveEvent event) {
|
public void onMouseMove(MouseMoveEvent event) {
|
||||||
@@ -112,10 +124,38 @@ class DraftBox extends CommentBox {
|
|||||||
}, MouseMoveEvent.getType());
|
}, MouseMoveEvent.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void set(CommentInfo info) {
|
||||||
|
date.setInnerText(FormatUtil.shortFormatDayTime(info.updated()));
|
||||||
|
if (info.message() != null) {
|
||||||
|
String msg = info.message().trim();
|
||||||
|
summary.setInnerText(msg);
|
||||||
|
message.setHTML(linkProcessor.apply(
|
||||||
|
new SafeHtmlBuilder().append(msg).wikify()));
|
||||||
|
}
|
||||||
|
this.comment = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CommentInfo getCommentInfo() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isOpen() {
|
||||||
|
return UIObject.isVisible(p_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setOpen(boolean open) {
|
||||||
|
UIObject.setVisible(summary, !open);
|
||||||
|
UIObject.setVisible(p_view, open);
|
||||||
|
super.setOpen(open);
|
||||||
|
}
|
||||||
|
|
||||||
private void expandText() {
|
private void expandText() {
|
||||||
double cols = editArea.getCharacterWidth();
|
double cols = editArea.getCharacterWidth();
|
||||||
int rows = 2;
|
int rows = 2;
|
||||||
for (String line : editArea.getText().split("\n")) {
|
for (String line : editArea.getValue().split("\n")) {
|
||||||
rows += Math.ceil((1.0 + line.length()) / cols);
|
rows += Math.ceil((1.0 + line.length()) / cols);
|
||||||
}
|
}
|
||||||
rows = Math.max(INITIAL_LINES, Math.min(rows, MAX_LINES));
|
rows = Math.max(INITIAL_LINES, Math.min(rows, MAX_LINES));
|
||||||
@@ -125,22 +165,33 @@ class DraftBox extends CommentBox {
|
|||||||
resizePaddingWidget();
|
resizePaddingWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isEdit() {
|
||||||
|
return UIObject.isVisible(p_edit);
|
||||||
|
}
|
||||||
|
|
||||||
void setEdit(boolean edit) {
|
void setEdit(boolean edit) {
|
||||||
|
UIObject.setVisible(summary, false);
|
||||||
|
UIObject.setVisible(p_view, !edit);
|
||||||
|
UIObject.setVisible(p_edit, edit);
|
||||||
|
|
||||||
if (edit) {
|
if (edit) {
|
||||||
setOpen(true);
|
final String msg = comment.message() != null
|
||||||
removeStyleName(draftStyle.view());
|
? comment.message().trim()
|
||||||
addStyleName(draftStyle.edit());
|
: "";
|
||||||
editArea.setText(getOriginal().message());
|
editArea.setValue(msg);
|
||||||
expandText();
|
|
||||||
editArea.setReadOnly(false);
|
|
||||||
editArea.setFocus(true);
|
editArea.setFocus(true);
|
||||||
disableClickFocusHandler();
|
expandText();
|
||||||
|
if (msg.length() > 0) {
|
||||||
|
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
|
||||||
|
@Override
|
||||||
|
public boolean execute() {
|
||||||
|
editArea.setCursorPos(msg.length());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
expandTimer.cancel();
|
expandTimer.cancel();
|
||||||
editArea.setReadOnly(true);
|
|
||||||
removeStyleName(draftStyle.edit());
|
|
||||||
addStyleName(draftStyle.view());
|
|
||||||
enableClickFocusHandler();
|
|
||||||
}
|
}
|
||||||
resizePaddingWidget();
|
resizePaddingWidget();
|
||||||
}
|
}
|
||||||
@@ -149,75 +200,113 @@ class DraftBox extends CommentBox {
|
|||||||
replyToBox = box;
|
replyToBox = box;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeUI() {
|
@Override
|
||||||
setEdit(false);
|
protected void onUnload() {
|
||||||
expandTimer.cancel();
|
expandTimer.cancel();
|
||||||
|
super.onUnload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeUI() {
|
||||||
if (replyToBox != null) {
|
if (replyToBox != null) {
|
||||||
replyToBox.unregisterReplyBox();
|
replyToBox.unregisterReplyBox();
|
||||||
}
|
}
|
||||||
CommentInfo info = getOriginal();
|
parent.removeDraft(this, comment.side(), comment.line() - 1);
|
||||||
getDiffView().removeDraft(this, info.side(), info.line() - 1);
|
|
||||||
removeFromParent();
|
removeFromParent();
|
||||||
getSelfWidget().clear();
|
getSelfWidget().clear();
|
||||||
|
|
||||||
PaddingManager manager = getPaddingManager();
|
PaddingManager manager = getPaddingManager();
|
||||||
manager.remove(this);
|
manager.remove(this);
|
||||||
manager.resizePaddingWidget();
|
manager.resizePaddingWidget();
|
||||||
getCm().focus();
|
cm.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiHandler("contentPanelMessage")
|
@UiHandler("message")
|
||||||
void onDoubleClick(DoubleClickEvent e) {
|
void onMessageClick(ClickEvent e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiHandler("message")
|
||||||
|
void onMessageDoubleClick(DoubleClickEvent e) {
|
||||||
setEdit(true);
|
setEdit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiHandler("edit")
|
@UiHandler("edit")
|
||||||
void onEdit(ClickEvent e) {
|
void onEdit(ClickEvent e) {
|
||||||
|
e.stopPropagation();
|
||||||
setEdit(true);
|
setEdit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiHandler("save")
|
@UiHandler("save")
|
||||||
void onSave(ClickEvent e) {
|
void onSave(ClickEvent e) {
|
||||||
final String message = editArea.getText();
|
e.stopPropagation();
|
||||||
if (message.equals("")) {
|
onSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSave() {
|
||||||
|
String message = editArea.getValue().trim();
|
||||||
|
if (message.length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CommentInfo original = getOriginal();
|
|
||||||
|
CommentInfo original = comment;
|
||||||
CommentInput input = CommentInput.create(original);
|
CommentInput input = CommentInput.create(original);
|
||||||
input.setMessage(message);
|
input.setMessage(message);
|
||||||
setEdit(false);
|
enableEdit(false);
|
||||||
|
|
||||||
GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() {
|
GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(CommentInfo result) {
|
public void onSuccess(CommentInfo result) {
|
||||||
updateOriginal(result);
|
enableEdit(true);
|
||||||
setMessageText(message);
|
set(result);
|
||||||
setDate(result.updated());
|
if (result.message().length() < 70) {
|
||||||
if (isNew) {
|
UIObject.setVisible(p_edit, false);
|
||||||
removeStyleName(draftStyle.newDraft());
|
setOpen(false);
|
||||||
isNew = false;
|
} else {
|
||||||
|
setEdit(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
enableEdit(true);
|
||||||
|
super.onFailure(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (isNew) {
|
if (original.id() == null) {
|
||||||
CommentApi.createDraft(getPatchSetId(), input, cb);
|
CommentApi.createDraft(psId, input, cb);
|
||||||
} else {
|
} else {
|
||||||
CommentApi.updateDraft(getPatchSetId(), original.id(), input, cb);
|
CommentApi.updateDraft(psId, original.id(), input, cb);
|
||||||
}
|
}
|
||||||
getCm().focus();
|
cm.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableEdit(boolean on) {
|
||||||
|
editArea.setEnabled(on);
|
||||||
|
save.setEnabled(on);
|
||||||
|
cancel.setEnabled(on);
|
||||||
|
discard2.setEnabled(on);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiHandler("cancel")
|
@UiHandler("cancel")
|
||||||
void onCancel(ClickEvent e) {
|
void onCancel(ClickEvent e) {
|
||||||
setEdit(false);
|
e.stopPropagation();
|
||||||
getCm().focus();
|
if (comment.id() == null && editArea.getValue().length() == 0) {
|
||||||
}
|
|
||||||
|
|
||||||
@UiHandler("discard")
|
|
||||||
void onDiscard(ClickEvent e) {
|
|
||||||
if (isNew) {
|
|
||||||
removeUI();
|
removeUI();
|
||||||
} else {
|
} else {
|
||||||
setEdit(false);
|
setEdit(false);
|
||||||
CommentApi.deleteDraft(getPatchSetId(), getOriginal().id(),
|
cm.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiHandler({"discard1", "discard2"})
|
||||||
|
void onDiscard(ClickEvent e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (comment.id() == null) {
|
||||||
|
removeUI();
|
||||||
|
} else {
|
||||||
|
setEdit(false);
|
||||||
|
CommentApi.deleteDraft(psId, comment.id(),
|
||||||
new GerritCallback<JavaScriptObject>() {
|
new GerritCallback<JavaScriptObject>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(JavaScriptObject result) {
|
public void onSuccess(JavaScriptObject result) {
|
||||||
@@ -228,29 +317,21 @@ class DraftBox extends CommentBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiHandler("editArea")
|
@UiHandler("editArea")
|
||||||
void onCtrlS(KeyDownEvent e) {
|
void onKeyDown(KeyDownEvent e) {
|
||||||
if ((e.isControlKeyDown() || e.isMetaKeyDown())
|
if ((e.isControlKeyDown() || e.isMetaKeyDown())
|
||||||
&& !e.isAltKeyDown() && !e.isShiftKeyDown()) {
|
&& !e.isAltKeyDown() && !e.isShiftKeyDown()) {
|
||||||
switch (e.getNativeKeyCode()) {
|
switch (e.getNativeKeyCode()) {
|
||||||
case 's':
|
case 's':
|
||||||
case 'S':
|
case 'S':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSave(null);
|
onSave();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE
|
||||||
|
&& comment.id() == null && editArea.getValue().length() == 0) {
|
||||||
|
removeUI();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
expandTimer.schedule(250);
|
expandTimer.schedule(250);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** TODO: Unused now. Re-enable this after implementing auto-save */
|
|
||||||
void onEsc(KeyDownEvent e) {
|
|
||||||
if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
|
|
||||||
if (isNew) {
|
|
||||||
removeUI();
|
|
||||||
} else {
|
|
||||||
onCancel(null);
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,49 +14,82 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
<ui:UiBinder
|
||||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||||
xmlns:d='urn:import:com.google.gerrit.client.diff'
|
xmlns:c='urn:import:com.google.gerrit.client'
|
||||||
xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'>
|
xmlns:e='urn:import:com.google.gwtexpui.globalkey.client'
|
||||||
<ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' />
|
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
|
||||||
<ui:style field='draftStyle' type='com.google.gerrit.client.diff.DraftBox.DraftBoxStyle'>
|
<ui:with field='res' type='com.google.gerrit.client.diff.Resources'/>
|
||||||
.edit .messagePanel {
|
<ui:style>
|
||||||
display: none;
|
.draft {
|
||||||
|
width: 45px;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #aaa;
|
||||||
}
|
}
|
||||||
textarea.editArea {
|
.editArea { max-width: 637px; }
|
||||||
margin-left: 5px;
|
button.button div {
|
||||||
margin-bottom: 2px;
|
width: 35px;
|
||||||
}
|
}
|
||||||
.view .editArea {
|
button.discard {
|
||||||
display: none;
|
color: #d14836;
|
||||||
}
|
background-color: #d14836;
|
||||||
.newDraft .cancel {
|
background-image: -webkit-linear-gradient(top, #d14836, #d14836);
|
||||||
display: none;
|
position: absolute;
|
||||||
|
left: 150px;
|
||||||
}
|
}
|
||||||
</ui:style>
|
</ui:style>
|
||||||
<g:HTMLPanel addStyleNames='{res.style.commentBox}'>
|
|
||||||
<d:CommentBoxHeader ui:field='header' />
|
<g:HTMLPanel styleName='{res.style.commentBox}'>
|
||||||
<div class='{res.style.contentPanel}'>
|
<div class='{res.style.contents}'>
|
||||||
<c:NpTextArea ui:field='editArea' addStyleNames='{draftStyle.editArea}'/>
|
<div class='{res.style.header}'>
|
||||||
<div class='{res.style.button}'>
|
<div class='{style.draft}'>Draft</div>
|
||||||
<g:Button ui:field='save' addStyleNames='{draftStyle.editArea}'>
|
<div ui:field='summary' class='{res.style.summary}'/>
|
||||||
<ui:msg>Save</ui:msg>
|
<div ui:field='date' class='{res.style.date}'/>
|
||||||
</g:Button>
|
</div>
|
||||||
<g:Button ui:field='cancel'
|
<div ui:field='p_view' aria-hidden='true' style='display: NONE'>
|
||||||
addStyleNames='{draftStyle.editArea} {draftStyle.cancel}'>
|
<g:HTML ui:field='message' styleName=''/>
|
||||||
<ui:msg>Cancel</ui:msg>
|
<div style='position: relative'>
|
||||||
</g:Button>
|
<g:Button ui:field='edit'
|
||||||
<g:Button ui:field='discard' addStyleNames='{draftStyle.editArea}'>
|
title='Edit this draft comment'
|
||||||
<ui:msg>Discard</ui:msg>
|
styleName='{style.button}'>
|
||||||
</g:Button>
|
<ui:attribute name='title'/>
|
||||||
|
<div><ui:msg>Edit</ui:msg></div>
|
||||||
|
</g:Button>
|
||||||
|
<g:Button ui:field='discard1'
|
||||||
|
title='Discard this draft comment'
|
||||||
|
styleName='{style.button}'
|
||||||
|
addStyleNames='{style.discard}'>
|
||||||
|
<ui:attribute name='title'/>
|
||||||
|
<div><ui:msg>Discard</ui:msg></div>
|
||||||
|
</g:Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ui:field='p_edit' aria-hidden='true' style='display: NONE'>
|
||||||
|
<e:NpTextArea ui:field='editArea'
|
||||||
|
characterWidth='60'
|
||||||
|
visibleLines='5'
|
||||||
|
spellCheck='true'
|
||||||
|
styleName='{style.editArea}'/>
|
||||||
|
<div style='position: relative'>
|
||||||
|
<g:Button ui:field='save'
|
||||||
|
title='Save this draft comment'
|
||||||
|
styleName='{style.button}'>
|
||||||
|
<ui:attribute name='title'/>
|
||||||
|
<div><ui:msg>Save</ui:msg></div>
|
||||||
|
</g:Button>
|
||||||
|
<g:Button ui:field='cancel' styleName='{style.button}'>
|
||||||
|
<div><ui:msg>Cancel</ui:msg></div>
|
||||||
|
</g:Button>
|
||||||
|
<g:Button ui:field='discard2'
|
||||||
|
title='Discard this draft comment'
|
||||||
|
styleName='{style.button}'
|
||||||
|
addStyleNames='{style.discard}'>
|
||||||
|
<ui:attribute name='title'/>
|
||||||
|
<div><ui:msg>Discard</ui:msg></div>
|
||||||
|
</g:Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='{res.style.contentPanel}'>
|
|
||||||
<g:HTML ui:field='contentPanelMessage'
|
|
||||||
addStyleNames='{res.style.message} {draftStyle.messagePanel}'></g:HTML>
|
|
||||||
<g:Button ui:field='edit' addStyleNames='{draftStyle.messagePanel} {res.style.button}'>
|
|
||||||
<ui:msg>Edit</ui:msg>
|
|
||||||
</g:Button>
|
|
||||||
</div>
|
|
||||||
</g:HTMLPanel>
|
</g:HTMLPanel>
|
||||||
</ui:UiBinder>
|
</ui:UiBinder>
|
||||||
|
|||||||
@@ -14,33 +14,109 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.diff;
|
package com.google.gerrit.client.diff;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.AvatarImage;
|
||||||
|
import com.google.gerrit.client.FormatUtil;
|
||||||
import com.google.gerrit.client.Gerrit;
|
import com.google.gerrit.client.Gerrit;
|
||||||
|
import com.google.gerrit.client.changes.CommentApi;
|
||||||
import com.google.gerrit.client.changes.CommentInfo;
|
import com.google.gerrit.client.changes.CommentInfo;
|
||||||
|
import com.google.gerrit.client.changes.CommentInput;
|
||||||
|
import com.google.gerrit.client.changes.Util;
|
||||||
|
import com.google.gerrit.client.rpc.GerritCallback;
|
||||||
import com.google.gerrit.client.ui.CommentLinkProcessor;
|
import com.google.gerrit.client.ui.CommentLinkProcessor;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gwt.core.client.GWT;
|
import com.google.gwt.core.client.GWT;
|
||||||
|
import com.google.gwt.dom.client.Element;
|
||||||
import com.google.gwt.event.dom.client.ClickEvent;
|
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.UiBinder;
|
||||||
|
import com.google.gwt.uibinder.client.UiField;
|
||||||
import com.google.gwt.uibinder.client.UiHandler;
|
import com.google.gwt.uibinder.client.UiHandler;
|
||||||
|
import com.google.gwt.user.client.ui.Button;
|
||||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||||
|
import com.google.gwt.user.client.ui.UIObject;
|
||||||
import net.codemirror.lib.CodeMirror;
|
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
|
||||||
|
|
||||||
/** An HtmlPanel for displaying a published comment */
|
/** An HtmlPanel for displaying a published comment */
|
||||||
class PublishedBox extends CommentBox {
|
class PublishedBox extends CommentBox {
|
||||||
interface Binder extends UiBinder<HTMLPanel, PublishedBox> {}
|
interface Binder extends UiBinder<HTMLPanel, PublishedBox> {}
|
||||||
private static UiBinder<HTMLPanel, CommentBox> uiBinder =
|
private static Binder uiBinder = GWT.create(Binder.class);
|
||||||
GWT.create(Binder.class);
|
|
||||||
|
|
||||||
|
static interface Style extends CssResource {
|
||||||
|
String closed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SideBySide2 parent;
|
||||||
|
private final PatchSet.Id psId;
|
||||||
|
private final CommentInfo comment;
|
||||||
private DraftBox replyBox;
|
private DraftBox replyBox;
|
||||||
|
|
||||||
|
@UiField Style style;
|
||||||
|
@UiField Element name;
|
||||||
|
@UiField Element summary;
|
||||||
|
@UiField Element date;
|
||||||
|
@UiField Element message;
|
||||||
|
@UiField Element buttons;
|
||||||
|
@UiField Button reply;
|
||||||
|
@UiField Button done;
|
||||||
|
|
||||||
|
@UiField(provided = true)
|
||||||
|
AvatarImage avatar;
|
||||||
|
|
||||||
PublishedBox(
|
PublishedBox(
|
||||||
SideBySide2 host,
|
SideBySide2 parent,
|
||||||
CodeMirror cm,
|
CommentLinkProcessor clp,
|
||||||
PatchSet.Id id,
|
PatchSet.Id psId,
|
||||||
CommentInfo info,
|
CommentInfo info) {
|
||||||
CommentLinkProcessor linkProcessor) {
|
this.parent = parent;
|
||||||
super(host, cm, uiBinder, id, info, linkProcessor, false);
|
this.psId = psId;
|
||||||
|
this.comment = info;
|
||||||
|
|
||||||
|
if (info.author() != null) {
|
||||||
|
avatar = new AvatarImage(info.author(), 26);
|
||||||
|
avatar.setSize("", "");
|
||||||
|
} else {
|
||||||
|
avatar = new AvatarImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
initWidget(uiBinder.createAndBindUi(this));
|
||||||
|
addDomHandler(new ClickHandler() {
|
||||||
|
@Override
|
||||||
|
public void onClick(ClickEvent event) {
|
||||||
|
setOpen(!isOpen());
|
||||||
|
}
|
||||||
|
}, ClickEvent.getType());
|
||||||
|
|
||||||
|
name.setInnerText(authorName(info));
|
||||||
|
date.setInnerText(FormatUtil.shortFormatDayTime(info.updated()));
|
||||||
|
if (info.message() != null) {
|
||||||
|
String msg = info.message().trim();
|
||||||
|
summary.setInnerText(msg);
|
||||||
|
message.setInnerSafeHtml(clp.apply(
|
||||||
|
new SafeHtmlBuilder().append(msg).wikify()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CommentInfo getCommentInfo() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isOpen() {
|
||||||
|
return UIObject.isVisible(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOpen(boolean open) {
|
||||||
|
UIObject.setVisible(summary, !open);
|
||||||
|
UIObject.setVisible(message, open);
|
||||||
|
UIObject.setVisible(buttons, open);
|
||||||
|
if (open) {
|
||||||
|
removeStyleName(style.closed());
|
||||||
|
} else {
|
||||||
|
addStyleName(style.closed());
|
||||||
|
}
|
||||||
|
super.setOpen(open);
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerReplyBox(DraftBox box) {
|
void registerReplyBox(DraftBox box) {
|
||||||
@@ -59,25 +135,47 @@ class PublishedBox extends CommentBox {
|
|||||||
|
|
||||||
@UiHandler("reply")
|
@UiHandler("reply")
|
||||||
void onReply(ClickEvent e) {
|
void onReply(ClickEvent e) {
|
||||||
|
e.stopPropagation();
|
||||||
if (!Gerrit.isSignedIn()) {
|
if (!Gerrit.isSignedIn()) {
|
||||||
Gerrit.doSignIn(getDiffView().getToken());
|
Gerrit.doSignIn(parent.getToken());
|
||||||
} else if (replyBox == null) {
|
} else if (replyBox == null) {
|
||||||
DraftBox box = getDiffView().addReply(getOriginal(), "", false);
|
registerReplyBox(parent.addDraftBox(parent.createReply(comment)));
|
||||||
registerReplyBox(box);
|
|
||||||
} else {
|
} else {
|
||||||
openReplyBox();
|
openReplyBox();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiHandler("replyDone")
|
@UiHandler("done")
|
||||||
void onReplyDone(ClickEvent e) {
|
void onReplyDone(ClickEvent e) {
|
||||||
|
e.stopPropagation();
|
||||||
if (!Gerrit.isSignedIn()) {
|
if (!Gerrit.isSignedIn()) {
|
||||||
Gerrit.doSignIn(getDiffView().getToken());
|
Gerrit.doSignIn(parent.getToken());
|
||||||
} else if (replyBox == null) {
|
} else if (replyBox == null) {
|
||||||
DraftBox box = getDiffView().addReply(getOriginal(), "Done", true);
|
done.setEnabled(false);
|
||||||
registerReplyBox(box);
|
CommentInput input = CommentInput.create(parent.createReply(comment));
|
||||||
|
input.setMessage("Done");
|
||||||
|
CommentApi.createDraft(psId, input,
|
||||||
|
new GerritCallback<CommentInfo>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(CommentInfo result) {
|
||||||
|
done.setEnabled(true);
|
||||||
|
setOpen(false);
|
||||||
|
registerReplyBox(parent.addDraftBox(result));
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
openReplyBox();
|
openReplyBox();
|
||||||
|
setOpen(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String authorName(CommentInfo info) {
|
||||||
|
if (info.author() != null) {
|
||||||
|
if (info.author().name() != null) {
|
||||||
|
return info.author().name();
|
||||||
|
}
|
||||||
|
return Gerrit.getConfig().getAnonymousCowardName();
|
||||||
|
}
|
||||||
|
return Util.C.messageNoAuthor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,18 +14,58 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
<ui:UiBinder
|
||||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||||
xmlns:d='urn:import:com.google.gerrit.client.diff'>
|
xmlns:c='urn:import:com.google.gerrit.client'
|
||||||
<ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' />
|
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
|
||||||
<g:HTMLPanel addStyleNames='{res.style.commentBox}'>
|
<ui:with field='res' type='com.google.gerrit.client.diff.Resources'/>
|
||||||
<d:CommentBoxHeader ui:field='header' />
|
<ui:style type='com.google.gerrit.client.diff.PublishedBox.Style'>
|
||||||
<g:HTMLPanel addStyleNames='{res.style.contentPanel}'>
|
.avatar {
|
||||||
<g:HTML ui:field='contentPanelMessage' addStyleNames='{res.style.message}'></g:HTML>
|
position: absolute;
|
||||||
<div class='{res.style.button}'>
|
width: 26px;
|
||||||
<g:Button ui:field='reply'><ui:msg>Reply ...</ui:msg></g:Button>
|
height: 26px;
|
||||||
<g:Button ui:field='replyDone'><ui:msg>Reply 'Done'</ui:msg></g:Button>
|
}
|
||||||
|
.closed .avatar {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.closed .name {
|
||||||
|
width: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
</ui:style>
|
||||||
|
|
||||||
|
<g:HTMLPanel
|
||||||
|
styleName='{res.style.commentBox}'
|
||||||
|
addStyleNames='{style.closed}'>
|
||||||
|
<c:AvatarImage ui:field='avatar' styleName='{style.avatar}'/>
|
||||||
|
<div class='{res.style.contents}'>
|
||||||
|
<div class='{res.style.header}'>
|
||||||
|
<div ui:field='name' class='{style.name}'/>
|
||||||
|
<div ui:field='summary' class='{res.style.summary}'/>
|
||||||
|
<div ui:field='date' class='{res.style.date}'/>
|
||||||
</div>
|
</div>
|
||||||
</g:HTMLPanel>
|
<div ui:field='message' aria-hidden='true' style='display: NONE'/>
|
||||||
|
<div ui:field='buttons' aria-hidden='true' style='display: NONE'>
|
||||||
|
<g:Button ui:field='reply' styleName=''
|
||||||
|
title='Reply to this comment'>
|
||||||
|
<ui:attribute name='title'/>
|
||||||
|
<div><ui:msg>Reply</ui:msg></div>
|
||||||
|
</g:Button>
|
||||||
|
<g:Button ui:field='done' styleName=''
|
||||||
|
title='Reply "Done" to this comment'>
|
||||||
|
<ui:attribute name='title'/>
|
||||||
|
<div><ui:msg>Done</ui:msg></div>
|
||||||
|
</g:Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</g:HTMLPanel>
|
</g:HTMLPanel>
|
||||||
</ui:UiBinder>
|
</ui:UiBinder>
|
||||||
|
|||||||
@@ -14,24 +14,21 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.diff;
|
package com.google.gerrit.client.diff;
|
||||||
|
|
||||||
|
import com.google.gwt.core.client.GWT;
|
||||||
import com.google.gwt.resources.client.ClientBundle;
|
import com.google.gwt.resources.client.ClientBundle;
|
||||||
import com.google.gwt.resources.client.CssResource;
|
import com.google.gwt.resources.client.CssResource;
|
||||||
|
|
||||||
/**
|
/** Resources used by diff. */
|
||||||
* Resources used by diff.
|
interface Resources extends ClientBundle {
|
||||||
*/
|
static final Resources I = GWT.create(Resources.class);
|
||||||
interface CommentBoxResources extends ClientBundle {
|
|
||||||
@Source("CommentBoxUi.css")
|
@Source("CommentBoxUi.css") Style style();
|
||||||
Style style();
|
|
||||||
|
|
||||||
interface Style extends CssResource {
|
interface Style extends CssResource {
|
||||||
String open();
|
|
||||||
String close();
|
|
||||||
String commentBox();
|
String commentBox();
|
||||||
String table();
|
String contents();
|
||||||
String summaryText();
|
String header();
|
||||||
String contentPanel();
|
String summary();
|
||||||
String message();
|
String date();
|
||||||
String button();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,34 +460,29 @@ public class SideBySide2 extends Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DraftBox addNewDraft(CodeMirror cm, int line) {
|
private DraftBox addNewDraft(CodeMirror cm, int line) {
|
||||||
Side side = getSideFromCm(cm);
|
return addDraftBox(CommentInfo.create(
|
||||||
CommentInfo info = CommentInfo.create(
|
|
||||||
path,
|
path,
|
||||||
side,
|
getSideFromCm(cm),
|
||||||
line + 1,
|
line + 1,
|
||||||
null,
|
null,
|
||||||
null);
|
null));
|
||||||
return addDraftBox(info, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DraftBox addReply(CommentInfo replyTo, String initMessage, boolean doSave) {
|
CommentInfo createReply(CommentInfo replyTo) {
|
||||||
Side side = replyTo.side();
|
return CommentInfo.create(
|
||||||
int line = replyTo.line();
|
|
||||||
CommentInfo info = CommentInfo.create(
|
|
||||||
path,
|
path,
|
||||||
side,
|
replyTo.side(),
|
||||||
line,
|
replyTo.line(),
|
||||||
replyTo.id(),
|
replyTo.id(),
|
||||||
initMessage);
|
null);
|
||||||
return addDraftBox(info, doSave);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DraftBox addDraftBox(CommentInfo info, boolean doSave) {
|
DraftBox addDraftBox(CommentInfo info) {
|
||||||
CodeMirror cm = getCmFromSide(info.side());
|
CodeMirror cm = getCmFromSide(info.side());
|
||||||
DraftBox box = new DraftBox(this, cm, revision, info, commentLinkProcessor,
|
DraftBox box = new DraftBox(this, cm, commentLinkProcessor, revision, info);
|
||||||
true, doSave);
|
|
||||||
addCommentBox(info, box);
|
addCommentBox(info, box);
|
||||||
if (!doSave) {
|
if (info.id() == null) {
|
||||||
|
box.setOpen(true);
|
||||||
box.setEdit(true);
|
box.setEdit(true);
|
||||||
}
|
}
|
||||||
LineHandle handle = cm.getLineHandle(info.line() - 1);
|
LineHandle handle = cm.getLineHandle(info.line() - 1);
|
||||||
@@ -560,9 +555,8 @@ public class SideBySide2 extends Screen {
|
|||||||
List<CommentInfo> sorted = sortComment(published);
|
List<CommentInfo> sorted = sortComment(published);
|
||||||
for (CommentInfo info : sorted) {
|
for (CommentInfo info : sorted) {
|
||||||
CodeMirror cm = getCmFromSide(info.side());
|
CodeMirror cm = getCmFromSide(info.side());
|
||||||
PublishedBox box =
|
PublishedBox box = new PublishedBox(this, commentLinkProcessor,
|
||||||
new PublishedBox(this, cm, revision, info, commentLinkProcessor);
|
revision, info);
|
||||||
box.setOpen(false);
|
|
||||||
allBoxes.add(box);
|
allBoxes.add(box);
|
||||||
publishedMap.put(info.id(), box);
|
publishedMap.put(info.id(), box);
|
||||||
int line = info.line() - 1;
|
int line = info.line() - 1;
|
||||||
@@ -576,11 +570,9 @@ public class SideBySide2 extends Screen {
|
|||||||
private void renderDrafts() {
|
private void renderDrafts() {
|
||||||
List<CommentInfo> sorted = sortComment(drafts);
|
List<CommentInfo> sorted = sortComment(drafts);
|
||||||
for (CommentInfo info : sorted) {
|
for (CommentInfo info : sorted) {
|
||||||
DraftBox box =
|
DraftBox box = new DraftBox(
|
||||||
new DraftBox(this, getCmFromSide(info.side()), revision, info,
|
this, getCmFromSide(info.side()), commentLinkProcessor,
|
||||||
commentLinkProcessor, false, false);
|
revision, info);
|
||||||
box.setOpen(false);
|
|
||||||
box.setEdit(false);
|
|
||||||
allBoxes.add(box);
|
allBoxes.add(box);
|
||||||
if (published != null) {
|
if (published != null) {
|
||||||
PublishedBox replyToBox = publishedMap.get(info.in_reply_to());
|
PublishedBox replyToBox = publishedMap.get(info.in_reply_to());
|
||||||
@@ -606,7 +598,7 @@ public class SideBySide2 extends Screen {
|
|||||||
for (CommentBox box : lineActiveBoxMap.values()) {
|
for (CommentBox box : lineActiveBoxMap.values()) {
|
||||||
List<SkippedLine> temp = new ArrayList<SkippedLine>();
|
List<SkippedLine> temp = new ArrayList<SkippedLine>();
|
||||||
for (SkippedLine skip : skips) {
|
for (SkippedLine skip : skips) {
|
||||||
CommentInfo info = box.getOriginal();
|
CommentInfo info = box.getCommentInfo();
|
||||||
int startLine = info.side() == Side.PARENT
|
int startLine = info.side() == Side.PARENT
|
||||||
? skip.getStartA()
|
? skip.getStartA()
|
||||||
: skip.getStartB();
|
: skip.getStartB();
|
||||||
@@ -846,7 +838,7 @@ public class SideBySide2 extends Screen {
|
|||||||
CommentBox box = lineActiveBoxMap.get(handle);
|
CommentBox box = lineActiveBoxMap.get(handle);
|
||||||
if (box == null) {
|
if (box == null) {
|
||||||
lineActiveBoxMap.put(handle, addNewDraft(cm, line));
|
lineActiveBoxMap.put(handle, addNewDraft(cm, line));
|
||||||
} else if (box.isDraft()) {
|
} else if (box instanceof DraftBox) {
|
||||||
((DraftBox) lineActiveBoxMap.get(handle)).setEdit(true);
|
((DraftBox) lineActiveBoxMap.get(handle)).setEdit(true);
|
||||||
} else {
|
} else {
|
||||||
((PublishedBox) box).onReply(null);
|
((PublishedBox) box).onReply(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user