/*
* Copyright (c) 2009-2012 Lockheed Martin Corporation
*
* 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 org.eurekastreams.web.client.ui.common.stream.comments;
import java.util.Date;
import org.eurekastreams.commons.formatting.DateFormatter;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.stream.StreamEntityDTO;
import org.eurekastreams.server.search.modelview.CommentDTO;
import org.eurekastreams.web.client.events.CommentDeletedEvent;
import org.eurekastreams.web.client.jsni.EffectsFacade;
import org.eurekastreams.web.client.jsni.WidgetJSNIFacadeImpl;
import org.eurekastreams.web.client.ui.Session;
import org.eurekastreams.web.client.ui.common.avatar.AvatarLinkPanel;
import org.eurekastreams.web.client.ui.common.avatar.AvatarWidget.Size;
import org.eurekastreams.web.client.ui.common.stream.renderers.RenderUtilities;
import org.eurekastreams.web.client.ui.common.stream.renderers.content.ContentParser;
import org.eurekastreams.web.client.ui.common.stream.renderers.content.ContentSegment;
import org.eurekastreams.web.client.ui.common.stream.renderers.content.ParsedContentRenderer;
import org.eurekastreams.web.client.ui.common.stream.transformers.StreamSearchLinkBuilder;
import org.eurekastreams.web.client.ui.pages.master.StaticResourceBundle;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
/**
* Comment panel.
*/
public class CommentPanel extends Composite
{
/** Length above which comments qualify to be truncated. */
private static final int COMMENT_LIMIT = 300;
/** Length at which long comments are truncated. */
private static final int TRUNCATE_LENGTH = 250;
/** JSNI Facade. */
private final WidgetJSNIFacadeImpl jsniFacade = new WidgetJSNIFacadeImpl();
/** Effects facade. */
private final EffectsFacade effects = new EffectsFacade();
/** For converting to HTML. */
private final ParsedContentRenderer contentRenderer = new ParsedContentRenderer();
/** For determining link URLs. */
private final StreamSearchLinkBuilder searchLinkBuilder = new StreamSearchLinkBuilder();
/**
* Default constructor.
*
* @param comment
* the comment.
* @param truncate
* If long comments should be truncated.
*/
public CommentPanel(final CommentDTO comment, final boolean truncate)
{
final FlowPanel commentContainer = new FlowPanel();
commentContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageComment());
StreamEntityDTO author = new StreamEntityDTO(EntityType.PERSON, comment.getAuthorAccountId());
author.setAvatarId(comment.getAuthorAvatarId());
author.setActive(comment.isAuthorActive());
author.setDisplayName(comment.getAuthorDisplayName());
commentContainer.add(AvatarLinkPanel.create(author, Size.VerySmall));
FlowPanel body = new FlowPanel();
body.addStyleName(StaticResourceBundle.INSTANCE.coreCss().body());
commentContainer.add(body);
Widget authorName = RenderUtilities.renderEntityName(author, "");
authorName.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageCommentAuthor());
body.add(authorName);
// parse the comment content
ContentSegment segments = new ContentParser().split(comment.getBody().trim());
boolean oversize = truncate && isTooLong(segments);
// build/display the comment content, truncating if too long
final ComplexPanel textContainer = new FlowPanel();
textContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageCommentText());
body.add(textContainer);
// render only up to the limit
Widget shortTextWidget = null;
int size = 0;
ContentSegment segment = segments;
for (; segment != null; segment = segment.getNext())
{
// check for when to stop
if (oversize && !segment.isTag())
{
int spaceLeft = TRUNCATE_LENGTH - size;
int contentLength = segment.getContent().length();
if (contentLength > spaceLeft)
{
if (segment.isText())
{
String shortText = getTruncatedText(segment.getContent(), spaceLeft);
if (shortText != null)
{
shortTextWidget = new InlineLabel(shortText);
textContainer.add(shortTextWidget);
}
}
break;
}
size += contentLength;
}
contentRenderer.renderSegment(segment, textContainer, searchLinkBuilder);
}
final ContentSegment remainingSegments = segment;
final Widget shortTextToReplace = shortTextWidget;
// if too long, add a ... and "show more" link
if (oversize)
{
final InlineLabel ellipsis = new InlineLabel("...");
body.add(ellipsis);
final InlineLabel more = new InlineLabel("show more");
more.addStyleName(StaticResourceBundle.INSTANCE.coreCss().showMoreCommentLink());
more.addStyleName(StaticResourceBundle.INSTANCE.coreCss().linkedLabel());
more.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent inEvent)
{
// remove the truncated segment, ... and "show more"
if (shortTextToReplace != null)
{
shortTextToReplace.removeFromParent();
}
more.removeFromParent();
ellipsis.removeFromParent();
// render the rest of the content
contentRenderer.renderList(remainingSegments, textContainer, searchLinkBuilder);
}
});
body.add(more);
}
// timestamp and actions
Panel timestampActions = new FlowPanel();
timestampActions.addStyleName(StaticResourceBundle.INSTANCE.coreCss().commentTimestamp());
body.add(timestampActions);
DateFormatter dateFormatter = new DateFormatter(new Date());
Label dateLink = new InlineLabel(dateFormatter.timeAgo(comment.getTimeSent()));
dateLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().commentTimestamp());
timestampActions.add(dateLink);
Panel actionsPanel = new FlowPanel();
timestampActions.add(actionsPanel);
if (comment.isDeletable())
{
Label sep = new InlineLabel("\u2219");
sep.addStyleName(StaticResourceBundle.INSTANCE.coreCss().actionLinkSeparator());
actionsPanel.add(sep);
Label deleteLink = new InlineLabel("Delete");
deleteLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().delete());
deleteLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().linkedLabel());
actionsPanel.add(deleteLink);
deleteLink.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
if (jsniFacade.confirm("Are you sure you want to delete this comment?"))
{
Session.getInstance().getActionProcessor()
.makeRequest("deleteComment", comment.getId(), new AsyncCallback<Boolean>()
{
/*
* implement the async call back methods
*/
public void onFailure(final Throwable caught)
{
// No failure state.
}
public void onSuccess(final Boolean result)
{
effects.fadeOut(commentContainer.getElement(), true);
Session.getInstance().getEventBus()
.notifyObservers(new CommentDeletedEvent(comment.getActivityId()));
}
});
}
}
});
}
initWidget(commentContainer);
}
/**
* Determines if the content needs to be truncated.
*
* @param segments
* List of segments.
* @return True if too long.
*/
private boolean isTooLong(final ContentSegment segments)
{
int size = 0;
for (ContentSegment segment = segments; segment != null; segment = segment.getNext())
{
if (!segment.isTag())
{
size += segment.getContent().length();
if (size > COMMENT_LIMIT)
{
return true;
}
}
}
return false;
}
/**
* Gets a truncated piece of the given text, trimmed at a word break.
*
* @param fullSegment
* Original full text.
* @param maxLength
* Maximum length.
* @return Truncated text, or null
*/
private String getTruncatedText(final String fullSegment, final int maxLength)
{
// start looking at the character after the desired break, looking for the end of a word
int i = maxLength;
while (i >= 0 && " \t".indexOf(fullSegment.charAt(i)) < 0)
{
i--;
}
return i <= 0 ? null : fullSegment.substring(0, i);
}
}