user mentions in comments implemented and ready for testing, preserve new lines in comments implemented

git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/user/news-feed@94731 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Massimiliano Assante 2014-04-10 15:45:50 +00:00
parent 1728e6dfc3
commit 1e6587f314
14 changed files with 353 additions and 152 deletions

View File

@ -4,7 +4,6 @@ import java.util.ArrayList;
import org.gcube.portal.databook.shared.Comment; import org.gcube.portal.databook.shared.Comment;
import org.gcube.portal.databook.shared.Like; import org.gcube.portal.databook.shared.Like;
import org.gcube.portal.databook.shared.UserInfo;
import org.gcube.portlets.user.newsfeed.shared.EnhancedFeed; import org.gcube.portlets.user.newsfeed.shared.EnhancedFeed;
import org.gcube.portlets.user.newsfeed.shared.MoreFeedsBean; import org.gcube.portlets.user.newsfeed.shared.MoreFeedsBean;
import org.gcube.portlets.user.newsfeed.shared.UserSettings; import org.gcube.portlets.user.newsfeed.shared.UserSettings;
@ -36,7 +35,7 @@ public interface NewsService extends RemoteService {
boolean deleteFeed(String feedid); boolean deleteFeed(String feedid);
Comment comment(String feedid, String text, String feedOwnerId, boolean isAppFeed); Comment comment(String feedid, String text, ArrayList<String> mentionedUsers, String feedOwnerId, boolean isAppFeed);
Comment editComment(Comment toEdit); Comment editComment(Comment toEdit);

View File

@ -32,8 +32,9 @@ public interface NewsServiceAsync {
void getUserSettings(AsyncCallback<UserSettings> callback); void getUserSettings(AsyncCallback<UserSettings> callback);
void comment(String feedid, String text, String feedOwnerId, void comment(String feedid, String text, ArrayList<String> mentionedUsers,
boolean isAppFeed, AsyncCallback<Comment> callback); String feedOwnerId, boolean isAppFeed,
AsyncCallback<Comment> callback);
void getAllCommentsByFeed(String feedid, void getAllCommentsByFeed(String feedid,
AsyncCallback<ArrayList<Comment>> callback); AsyncCallback<ArrayList<Comment>> callback);

View File

@ -1,5 +1,7 @@
package org.gcube.portlets.user.newsfeed.client.event; package org.gcube.portlets.user.newsfeed.client.event;
import java.util.ArrayList;
import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate;
import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.GwtEvent;
@ -11,16 +13,24 @@ public class AddCommentEvent extends GwtEvent<AddCommentEventHandler> {
private TweetTemplate owner; private TweetTemplate owner;
private String text; private String text;
private ArrayList<String> mentionedUsers;
public AddCommentEvent(TweetTemplate owner, String text, ArrayList<String> mentionedUsers) {
this.owner = owner;
this.text = text;
this.mentionedUsers = mentionedUsers;
}
public TweetTemplate getOwner() { public TweetTemplate getOwner() {
return owner; return owner;
} }
public String getText() { public String getText() {
return text; return text;
} }
public AddCommentEvent(TweetTemplate owner, String text) {
this.owner = owner; public ArrayList<String> getMentionedUsers() {
this.text = text; return mentionedUsers;
} }
@Override @Override

View File

@ -169,7 +169,7 @@ public class NewsFeedPanel extends Composite {
eventBus.addHandler(AddCommentEvent.TYPE, new AddCommentEventHandler() { eventBus.addHandler(AddCommentEvent.TYPE, new AddCommentEventHandler() {
@Override @Override
public void onAddComment(AddCommentEvent event) { public void onAddComment(AddCommentEvent event) {
doAddComment(event.getOwner(), event.getText()); doAddComment(event.getOwner(), event.getText(), event.getMentionedUsers());
} }
}); });
@ -822,8 +822,8 @@ public class NewsFeedPanel extends Composite {
} }
private void doAddComment(final TweetTemplate owner, String text) { private void doAddComment(final TweetTemplate owner, String text, ArrayList<String> mentionedUsers) {
newsService.comment(owner.getFeedKey(), text, owner.getMyFeedUserId(), owner.isAppFeed(), new AsyncCallback<Comment>() { newsService.comment(owner.getFeedKey(), text, mentionedUsers, owner.getMyFeedUserId(), owner.isAppFeed(), new AsyncCallback<Comment>() {
@Override @Override
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
Window.alert("Could not deliver this comment: " + caught.getMessage()); Window.alert("Could not deliver this comment: " + caught.getMessage());

View File

@ -1,35 +1,25 @@
package org.gcube.portlets.user.newsfeed.client.templates; package org.gcube.portlets.user.newsfeed.client.templates;
import java.util.ArrayList;
import org.gcube.portal.databook.shared.Comment; import org.gcube.portal.databook.shared.Comment;
import org.gcube.portal.databook.shared.UserInfo; import org.gcube.portal.databook.shared.UserInfo;
import org.gcube.portlets.user.newsfeed.client.NewsService; import org.gcube.portlets.user.gcubewidgets.client.elements.Div;
import org.gcube.portlets.user.newsfeed.client.NewsServiceAsync;
import org.gcube.portlets.user.newsfeed.client.event.AddCommentEvent; import org.gcube.portlets.user.newsfeed.client.event.AddCommentEvent;
import org.gcube.portlets.user.newsfeed.client.event.EditCommentEvent; import org.gcube.portlets.user.newsfeed.client.event.EditCommentEvent;
import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel;
import org.gcube.portlets.widgets.pickuser.client.dialog.PickUsersDialog;
import org.gcube.portlets.widgets.pickuser.shared.PickingUser;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
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.Event;
import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Button;
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.HTML;
import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
public class AddCommentTemplate extends Composite { public class AddCommentTemplate extends Composite {
@ -40,13 +30,10 @@ public class AddCommentTemplate extends Composite {
private static CommentTemplateUiBinder uiBinder = GWT.create(CommentTemplateUiBinder.class); private static CommentTemplateUiBinder uiBinder = GWT.create(CommentTemplateUiBinder.class);
private final static String COMMENT_TEXT = "Add a comment ... use @ to mention someone"; public final static String COMMENT_TEXT = "Add a comment ... use @ to mention someone";
private final static String ERROR_UPDATE_TEXT = "Looks like empty to me!"; public final static String ERROR_UPDATE_TEXT = "Looks like empty to me!";
public static final String avatar_default = GWT.getModuleBaseURL() + "../images/Avatar_default.png"; public static final String avatar_default = GWT.getModuleBaseURL() + "../images/Avatar_default.png";
private final static int TEXT_AREA_WIDTH = 450;
private final static int TEXT_AREA_HEIGHT = 40;
private TweetTemplate owner; private TweetTemplate owner;
private HandlerManager eventBus; private HandlerManager eventBus;
private boolean isEditing = false; private boolean isEditing = false;
@ -56,6 +43,7 @@ public class AddCommentTemplate extends Composite {
@UiField HTMLPanel mainPanel; @UiField HTMLPanel mainPanel;
@UiField Image avatarImage; @UiField Image avatarImage;
@UiField SuperPosedTextArea commentTextArea; @UiField SuperPosedTextArea commentTextArea;
@UiField Div highlighterDIV;
@UiField Button submitButton; @UiField Button submitButton;
@UiField Button cancelButton; @UiField Button cancelButton;
@ -72,8 +60,6 @@ public class AddCommentTemplate extends Composite {
owner = caller; owner = caller;
avatarImage.setPixelSize(30, 30); avatarImage.setPixelSize(30, 30);
avatarImage.setUrl(myUserInfo.getAvatarId()); avatarImage.setUrl(myUserInfo.getAvatarId());
//commentTextArea.setPixelSize(TEXT_AREA_WIDTH, TEXT_AREA_HEIGHT);
commentTextArea.setText(COMMENT_TEXT);
} }
/** /**
* called on edit comment * called on edit comment
@ -89,13 +75,16 @@ public class AddCommentTemplate extends Composite {
owner = caller; owner = caller;
avatarImage.setPixelSize(30, 30); avatarImage.setPixelSize(30, 30);
avatarImage.setUrl(caller.getMyUserInfo().getAvatarId()); avatarImage.setUrl(caller.getMyUserInfo().getAvatarId());
//commentTextArea.setPixelSize(TEXT_AREA_WIDTH, TEXT_AREA_HEIGHT);
commentTextArea.setText(new HTML(toEdit.getText()).getText()); commentTextArea.setText(new HTML(toEdit.getText()).getText());
mainPanel.removeStyleName("comment-hidden"); mainPanel.removeStyleName("comment-hidden");
mainPanel.setStyleName("single-comment"); mainPanel.setStyleName("single-comment");
commentTextArea.addStyleName("comment-dark-color"); commentTextArea.addStyleName("comment-dark-color");
} }
/** Used by AddCommentTemplate to instantiate SuperPosedTextArea */
@UiFactory SuperPosedTextArea build() {
return new SuperPosedTextArea(highlighterDIV);
}
public void setFocus() { public void setFocus() {
commentTextArea.setFocus(true); commentTextArea.setFocus(true);
@ -117,8 +106,9 @@ public class AddCommentTemplate extends Composite {
toEdit.setText(escapeHtml(commentTextArea.getText())); toEdit.setText(escapeHtml(commentTextArea.getText()));
eventBus.fireEvent(new EditCommentEvent(owner, toEdit)); eventBus.fireEvent(new EditCommentEvent(owner, toEdit));
} }
else else { //it is ok to add this comment
eventBus.fireEvent(new AddCommentEvent(owner, escapeHtml(commentTextArea.getText()))); eventBus.fireEvent(new AddCommentEvent(owner, escapeHtml(commentTextArea.getText()), commentTextArea.getMentionedUsers()));
}
this.getWidget().setVisible(false); this.getWidget().setVisible(false);
owner.setCommentingDisabled(false); owner.setCommentingDisabled(false);
} }

View File

@ -1,6 +1,7 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:w="urn:import:org.gcube.portlets.user.gcubewidgets.client.elements"
xmlns:m="urn:import:org.gcube.portlets.user.newsfeed.client.templates"> xmlns:m="urn:import:org.gcube.portlets.user.newsfeed.client.templates">
<ui:style> <ui:style>
.important { .important {
@ -19,16 +20,14 @@
<td> <td>
<div id="comment-supercontainer"> <div id="comment-supercontainer">
<div id="comment-highlighterContainer"> <div id="comment-highlighterContainer">
<div id="comment-highlighter"> <!-- <div id="comment-highlighter"> -->
</div> <!-- </div> -->
<w:Div styleName="comment-highlighter" ui:field="highlighterDIV"></w:Div>
</div> </div>
<div id="comment-inputContainer"> <div id="comment-inputContainer">
<m:SuperPosedTextArea styleName="post-comment" ui:field="commentTextArea" /> <m:SuperPosedTextArea styleName="post-comment" ui:field="commentTextArea" />
</div> </div>
</div> </div>
<!-- <div class="commentContainer"> -->
<!-- <g:TextArea styleName="post-comment" ui:field="commentTextArea" /> -->
<!-- </div> -->
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -54,6 +54,11 @@ public class SingleComment extends Composite {
avatarImage.setUrl(toShow.getThumbnailURL()); avatarImage.setUrl(toShow.getThumbnailURL());
String commentToShow = toShow.getText(); String commentToShow = toShow.getText();
//replace the < & and >
commentToShow = commentToShow.replaceAll("&lt;","<").replaceAll("&gt;",">");
commentToShow = commentToShow.replaceAll("&amp;","&");
if (commentToShow.length() > MAX_SHOWTEXT_LENGTH) { if (commentToShow.length() > MAX_SHOWTEXT_LENGTH) {
commentToShow = commentToShow.substring(0, MAX_SHOWTEXT_LENGTH) + "..."; commentToShow = commentToShow.substring(0, MAX_SHOWTEXT_LENGTH) + "...";
seeMore.setHTML("<a class=\"seemore\"> See More </a>"); seeMore.setHTML("<a class=\"seemore\"> See More </a>");
@ -114,4 +119,6 @@ public class SingleComment extends Composite {
editImage.removeStyleName("uiEditButton"); editImage.removeStyleName("uiEditButton");
} }
} }
} }

View File

@ -5,6 +5,7 @@ package org.gcube.portlets.user.newsfeed.client.templates;
import java.util.ArrayList; import java.util.ArrayList;
import org.gcube.portlets.user.gcubewidgets.client.elements.Div;
import org.gcube.portlets.user.newsfeed.client.NewsService; import org.gcube.portlets.user.newsfeed.client.NewsService;
import org.gcube.portlets.user.newsfeed.client.NewsServiceAsync; import org.gcube.portlets.user.newsfeed.client.NewsServiceAsync;
import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel; import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel;
@ -19,6 +20,7 @@ import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.TextArea;
@ -32,24 +34,29 @@ public class SuperPosedTextArea extends TextArea {
private final NewsServiceAsync newsService = GWT.create(NewsService.class); private final NewsServiceAsync newsService = GWT.create(NewsService.class);
private final HandlerManager eventBus = new HandlerManager(null); private final HandlerManager eventBus = new HandlerManager(null);
private final static String COMMENT_TEXT = "Add a comment ... use @ to mention someone";
private final static String ERROR_UPDATE_TEXT = "Looks like empty to me!"; private PickUsersDialog pickUserDlg;
private Div highlighterDIV;
PickUsersDialog pickUserDlg;
public final static int ARROW_UP = 38; public final static int ARROW_UP = 38;
public final static int ARROW_DOWN = 40; public final static int ARROW_DOWN = 40;
private ArrayList<String> mentionedUsers = new ArrayList<String>(); private ArrayList<String> mentionedUsers = new ArrayList<String>();
private String areaId;
/** /**
* *
*/ */
public SuperPosedTextArea() { public SuperPosedTextArea(Div highlighterDIV) {
sinkEvents(Event.ONPASTE); sinkEvents(Event.ONPASTE);
sinkEvents(Event.ONKEYUP); sinkEvents(Event.ONKEYUP);
sinkEvents(Event.ONCONTEXTMENU); sinkEvents(Event.ONCONTEXTMENU);
sinkEvents(Event.ONKEYDOWN); sinkEvents(Event.ONKEYDOWN);
setText(AddCommentTemplate.COMMENT_TEXT);
this.highlighterDIV = highlighterDIV;
//needed to give unique identifiers to the Area (for the jQuery plugin)
areaId = "postTextArea"+Random.nextInt();
newsService.getOrganizationUsers(NewsFeedPanel.getCurrentScope(), new AsyncCallback<ArrayList<PickingUser>>() { newsService.getOrganizationUsers(NewsFeedPanel.getCurrentScope(), new AsyncCallback<ArrayList<PickingUser>>() {
@Override @Override
@ -61,19 +68,23 @@ public class SuperPosedTextArea extends TextArea {
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
} }
}); });
DOM.setElementAttribute(getElement(), "id", "postTextArea"); DOM.setElementAttribute(getElement(), "id", areaId);
bind(); bind();
Timer t = new Timer() { Timer t = new Timer() {
@Override @Override
public void run() { public void run() {
myAutoSize(); myAutoSize(areaId);
setCaretPositionToBegin(areaId);
} }
}; };
t.schedule(1000); t.schedule(200);
} }
public static native void myAutoSize() /*-{ /**
function autoSizeArea() { * This is the way to wrap jQuery plugins into GWT, wrap it in a function and call it.
$wnd.jQuery('#postTextArea').autosize(); */
public static native void myAutoSize(String myAreaId) /*-{
function autoSizeArea() {
$wnd.jQuery('#'+myAreaId).autosize();
} }
autoSizeArea(); autoSizeArea();
}-*/; }-*/;
@ -91,7 +102,7 @@ public class SuperPosedTextArea extends TextArea {
super.onBrowserEvent(event); super.onBrowserEvent(event);
switch (event.getTypeInt()) { switch (event.getTypeInt()) {
case Event.ONPASTE: { case Event.ONPASTE: {
if (getText().equals(COMMENT_TEXT) || getText().equals(ERROR_UPDATE_TEXT) ) { if (getText().equals(AddCommentTemplate.COMMENT_TEXT) || getText().equals(AddCommentTemplate.ERROR_UPDATE_TEXT) ) {
setText(""); setText("");
addStyleName("dark-color"); addStyleName("dark-color");
removeStyleName("error"); removeStyleName("error");
@ -100,7 +111,7 @@ public class SuperPosedTextArea extends TextArea {
} }
case Event.ONKEYUP: { case Event.ONKEYUP: {
injectInDiv(getText()); injectInDiv(getText());
pickUserDlg.onKeyUp(event.getKeyCode(), this.getAbsoluteLeft(), this.getAbsoluteTop()+45, getText()); pickUserDlg.onKeyUp(event.getKeyCode(), this.getAbsoluteLeft(), this.getAbsoluteTop()+this.getOffsetHeight(), getText());
break; break;
} }
case Event.ONCONTEXTMENU: { case Event.ONCONTEXTMENU: {
@ -121,15 +132,20 @@ public class SuperPosedTextArea extends TextArea {
} }
} }
protected void removeSampleText() { protected void removeSampleText() {
if (getText().equals(COMMENT_TEXT) || getText().equals(ERROR_UPDATE_TEXT) ) { if (getText().equals(AddCommentTemplate.COMMENT_TEXT) || getText().equals(AddCommentTemplate.ERROR_UPDATE_TEXT) ) {
setText(""); setText("");
addStyleName("darker-color"); addStyleName("darker-color");
removeStyleName("error"); removeStyleName("error");
} }
} }
protected void cleanHighlighterDiv() { protected void cleanHighlighterDiv() {
DOM.getElementById("comment-highlighter").setInnerHTML(""); //DOM.getElementById("comment-highlighter").setInnerHTML("");
highlighterDIV.getElement().setInnerHTML("");
} }
/**
* copy what is being written in the text area in the underneath DIV
* @param textAreaText
*/
private void injectInDiv(String textAreaText) { private void injectInDiv(String textAreaText) {
String text; String text;
// parse the text: // parse the text:
@ -141,7 +157,8 @@ public class SuperPosedTextArea extends TextArea {
text = text.replaceAll(mentionedUser,"<span class=\"highlightedUser\">"+mentionedUser+"</span>"); text = text.replaceAll(mentionedUser,"<span class=\"highlightedUser\">"+mentionedUser+"</span>");
} }
// re-inject the processed text into the div // re-inject the processed text into the div
DOM.getElementById("comment-highlighter").setInnerHTML(text); //DOM.getElementById("comment-highlighter").setInnerHTML(text);
highlighterDIV.getElement().setInnerHTML(text);
} }
/** /**
@ -158,7 +175,8 @@ public class SuperPosedTextArea extends TextArea {
String[] toSplit = getText().split("@"); //get the preceeding part String[] toSplit = getText().split("@"); //get the preceeding part
setText(toSplit[0]+toAdd); setText(toSplit[0]+toAdd);
Element highDiv = DOM.getElementById("comment-highlighter"); //Element highDiv = DOM.getElementById("comment-highlighter");
Element highDiv = highlighterDIV.getElement();
String[] htmlToSplit = highDiv.getInnerHTML().split("@"); //get the preceeding part String[] htmlToSplit = highDiv.getInnerHTML().split("@"); //get the preceeding part
String highLightedUser = "<span class=\"highlightedUser\">"+toAdd+"</span>"; String highLightedUser = "<span class=\"highlightedUser\">"+toAdd+"</span>";
@ -177,20 +195,26 @@ public class SuperPosedTextArea extends TextArea {
return mentionedUsers; return mentionedUsers;
} }
/**
* this position the caret at the begin
/*
*
* <script>
$(document).ready(function(){
setTimeout(function(){
$('.postTextArea').autosize();
}, 3000);
});
</script>
*
*
*/ */
public static native void setCaretPositionToBegin(String myAreaId) /*-{
var elem = $doc.getElementById(myAreaId);
if(elem != null) {
if(elem.createTextRange) {
var range = elem.createTextRange();
range.move('character', 0);
range.select();
}
else {
if(elem.selectionStart) {
elem.focus();
elem.setSelectionRange(0, 0);
}
else
elem.focus();
}
}
}-*/;
} }

View File

@ -149,7 +149,11 @@ public class TweetTemplate extends Composite {
feedText = feedText.substring(0, MAX_SHOWTEXT_LENGTH) + "..."; feedText = feedText.substring(0, MAX_SHOWTEXT_LENGTH) + "...";
seeMore.setHTML("<a class=\"seemore\"> See More </a>"); seeMore.setHTML("<a class=\"seemore\"> See More </a>");
} }
//replace the < & and >
feedText = feedText.replaceAll("&lt;","<").replaceAll("&gt;",">");
feedText = feedText.replaceAll("&amp;","&");
if (! isAppFeed) { if (! isAppFeed) {
messageArea.setHTML("<a>" + NewsFeedPanel.MESSAGE_LABEL + "</a>"); messageArea.setHTML("<a>" + NewsFeedPanel.MESSAGE_LABEL + "</a>");
contentArea.setHTML("<a class=\"link\" href=\""+GCubeSocialNetworking.USER_PROFILE_LINK contentArea.setHTML("<a class=\"link\" href=\""+GCubeSocialNetworking.USER_PROFILE_LINK

View File

@ -0,0 +1,39 @@
package org.gcube.portlets.user.newsfeed.server;
import java.util.ArrayList;
import org.gcube.applicationsupportlayer.social.NotificationsManager;
import org.gcube.portlets.widgets.pickuser.shared.PickingUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Massimiliano Assante ISTI-CNR
*
*/
public class MentionNotificationsThread implements Runnable {
private static Logger _log = LoggerFactory.getLogger(MentionNotificationsThread.class);
private String postText;
private String postId;
private NotificationsManager nm;
private ArrayList<PickingUser> users;
public MentionNotificationsThread(String postId, String postText, NotificationsManager nm, ArrayList<PickingUser> users) {
super();
this.postId = postId;
this.postText = postText;
this.nm = nm;
this.users = users;
}
@Override
public void run() {
for (PickingUser userToNotify : users) {
boolean result = nm.notifyUserTag(userToNotify.getUsername(), postId, postText);
_log.trace("Sending Notification for post mention to: " + userToNotify.getUsername() + " result?"+ result);
}
}
}

View File

@ -491,12 +491,22 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService
* @param feedOwnerId the username of the user who created the post that was commented * @param feedOwnerId the username of the user who created the post that was commented
*/ */
@Override @Override
public Comment comment(String feedid, String commentText, String feedOwnerId, boolean isAppFeed) { public Comment comment(String feedid, String commentText, ArrayList<String> mentionedUserFullNames, String feedOwnerId, boolean isAppFeed) {
boolean commentCommitResult = false; boolean commentCommitResult = false;
_log.trace("Trying to add this comment " + commentText); _log.trace("Trying to add this comment " + commentText);
UserInfo user = getUserSettings().getUserInfo(); UserInfo user = getUserSettings().getUserInfo();
String escapedCommentText = Utils.escapeHtmlAndTransformUrl(commentText);
ArrayList<PickingUser> mentionedUsers = null;
if (mentionedUserFullNames != null && ! mentionedUserFullNames.isEmpty()) {
mentionedUsers = getSelectedUserIds(mentionedUserFullNames);
escapedCommentText = Utils.convertMentionPeopleAnchorHTML(escapedCommentText, mentionedUsers);
}
Comment comment = new Comment(UUID.randomUUID().toString(), user.getUsername(), Comment comment = new Comment(UUID.randomUUID().toString(), user.getUsername(),
new Date(), feedid, transformUrls(escapeHtml(commentText)), user.getFullName(), user.getAvatarId()); new Date(), feedid, escapedCommentText, user.getFullName(), user.getAvatarId());
try { try {
if (store.addComment(comment)) if (store.addComment(comment))
commentCommitResult = true; commentCommitResult = true;
@ -521,7 +531,13 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService
//notify the other users who commented this post (excluding the ones above) //notify the other users who commented this post (excluding the ones above)
Thread commentsNotificationthread = new Thread(new CommentNotificationsThread(store, user.getUsername(), comment.getFeedid(), commentText, nm, feedOwnerId, favorites)); Thread commentsNotificationthread = new Thread(new CommentNotificationsThread(store, user.getUsername(), comment.getFeedid(), commentText, nm, feedOwnerId, favorites));
commentsNotificationthread.start(); commentsNotificationthread.start();
//send the notification to the mentioned users, if any
if (mentionedUsers != null && mentionedUsers.size() > 0) {
Thread thread = new Thread(new MentionNotificationsThread(comment.getFeedid(), commentText, nm, mentionedUsers));
thread.start();
}
} }
return comment; return comment;
} }
@ -683,7 +699,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService
} }
@Override @Override
public ArrayList<PickingUser> getOrganizationUsers(String currentScope) { public ArrayList<PickingUser> getOrganizationUsers(String currentScope) {
ArrayList<PickingUser> toReturn = UsersUtil.getOrganizationUsers(currentScope, getASLSession().getUsername(), withinPortal); ArrayList<PickingUser> toReturn = Utils.getOrganizationUsers(currentScope, getASLSession().getUsername(), withinPortal);
_log.trace("Returning " + toReturn.size() + " users for scope " + currentScope); _log.trace("Returning " + toReturn.size() + " users for scope " + currentScope);
return toReturn; return toReturn;
} }
@ -740,6 +756,26 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService
} }
return (Boolean) getASLSession().getAttribute(SESSION_ADMIN_ATTR); return (Boolean) getASLSession().getAttribute(SESSION_ADMIN_ATTR);
} }
/**
*
* @return the screennames of the addressee (user logins e.g. pino.pini)
*/
public ArrayList<PickingUser> getSelectedUserIds(ArrayList<String> fullNames) {
if (fullNames == null)
return new ArrayList<PickingUser>();
else {
ArrayList<PickingUser> allUsers = Utils.getOrganizationUsers("/"+OrganizationsUtil.getRootOrganizationName(), getASLSession().getUsername(), withinPortal);
ArrayList<PickingUser> toReturn = new ArrayList<PickingUser>();
for (String fullName : fullNames)
for (PickingUser puser : allUsers) {
if (puser.getFullName().compareTo(fullName) == 0) {
toReturn.add(puser);
break;
}
}
return toReturn;
}
}
/** /**
* tell if the user is a portal administrator or not * tell if the user is a portal administrator or not
* @param username * @param username

View File

@ -1,75 +0,0 @@
package org.gcube.portlets.user.newsfeed.server;
import java.util.ArrayList;
import java.util.List;
import org.gcube.common.scope.impl.ScopeBean;
import org.gcube.common.scope.impl.ScopeBean.Type;
import org.gcube.portal.custom.communitymanager.OrganizationsUtil;
import org.gcube.portlets.widgets.pickuser.shared.PickingUser;
import org.gcube.vomanagement.usermanagement.GroupManager;
import org.gcube.vomanagement.usermanagement.UserManager;
import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayGroupManager;
import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.UserModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.liferay.portal.service.UserLocalServiceUtil;
public class UsersUtil {
private static final Logger _log = LoggerFactory.getLogger(UsersUtil.class);
/**
*
* @param session the Asl Session
* @param withinPortal true when is on Liferay portal
* @return the users belonging to the current organization (scope)
*/
public static ArrayList<PickingUser> getOrganizationUsers(String scope, String currUser, boolean withinPortal) {
ArrayList<PickingUser> portalUsers = new ArrayList<PickingUser>();
try {
if (withinPortal) {
UserManager um = new LiferayUserManager();
GroupManager gm = new LiferayGroupManager();
ScopeBean sb = new ScopeBean(scope);
List<UserModel> users = null;
if (sb.is(Type.INFRASTRUCTURE))
users = um.listUsersByGroup(gm.getRootVO().getGroupId());
else if (sb.is(Type.VRE)) { //must be in VRE
//get the name from the scope
String orgName = scope.substring(scope.lastIndexOf("/")+1, scope.length());
//ask the users
users = um.listUsersByGroup(gm.getGroupId(orgName));
}
else {
_log.error("Error, you must be in SCOPE VRE OR INFRASTURCTURE, you are in VO SCOPE returning no users");
return portalUsers;
}
for (UserModel user : users) {
if (user.getScreenName().compareTo("test.user") != 0 && user.getScreenName().compareTo(currUser) != 0) { //skip test.user & current user
String thumbnailURL = "";
com.liferay.portal.model.UserModel lifeUser = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), user.getScreenName());
thumbnailURL = "/image/user_male_portrait?img_id="+lifeUser.getPortraitId();
portalUsers.add(new PickingUser(user.getUserId(), user.getScreenName(), user.getFullname(), thumbnailURL));
}
}
}
else { //test users
portalUsers.add(new PickingUser("12111", "massimiliano.assante", "Test User #1", ""));
portalUsers.add(new PickingUser("14111", "massimiliano.assante", "Test Second User #2", ""));
portalUsers.add(new PickingUser("11511", "massimiliano.assante", "Test Third User", ""));
portalUsers.add(new PickingUser("11611", "massimiliano.assante", "Test Fourth User", ""));
portalUsers.add(new PickingUser("11711", "massimiliano.assante", "Test Fifth User", ""));
portalUsers.add(new PickingUser("11811", "massimiliano.assante", "Test Sixth User", ""));
portalUsers.add(new PickingUser("15811", "massimiliano.assante", "Ninth Testing User", ""));
portalUsers.add(new PickingUser("15811", "massimiliano.assante", "Eighth Testing User", ""));
portalUsers.add(new PickingUser("11211", "giogio.giorgi", "Seventh Test User", ""));
portalUsers.add(new PickingUser("2222", "pino.pinetti", "Tenth Testing User", ""));
}
} catch (Exception e) {
_log.error("Error in server get all contacts ", e);
}
return portalUsers;
}
}

View File

@ -0,0 +1,167 @@
package org.gcube.portlets.user.newsfeed.server;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.gcube.common.scope.impl.ScopeBean;
import org.gcube.common.scope.impl.ScopeBean.Type;
import org.gcube.portal.custom.communitymanager.OrganizationsUtil;
import org.gcube.portal.databook.client.GCubeSocialNetworking;
import org.gcube.portlets.widgets.pickuser.shared.PickingUser;
import org.gcube.vomanagement.usermanagement.GroupManager;
import org.gcube.vomanagement.usermanagement.UserManager;
import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayGroupManager;
import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.UserModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.liferay.portal.service.UserLocalServiceUtil;
public class Utils {
private static final Logger _log = LoggerFactory.getLogger(Utils.class);
/**
*
* @param session the Asl Session
* @param withinPortal true when is on Liferay portal
* @return the users belonging to the current organization (scope)
*/
public static ArrayList<PickingUser> getOrganizationUsers(String scope, String currUser, boolean withinPortal) {
ArrayList<PickingUser> portalUsers = new ArrayList<PickingUser>();
try {
if (withinPortal) {
UserManager um = new LiferayUserManager();
GroupManager gm = new LiferayGroupManager();
ScopeBean sb = new ScopeBean(scope);
List<UserModel> users = null;
if (sb.is(Type.INFRASTRUCTURE))
users = um.listUsersByGroup(gm.getRootVO().getGroupId());
else if (sb.is(Type.VRE)) { //must be in VRE
//get the name from the scope
String orgName = scope.substring(scope.lastIndexOf("/")+1, scope.length());
//ask the users
users = um.listUsersByGroup(gm.getGroupId(orgName));
}
else {
_log.error("Error, you must be in SCOPE VRE OR INFRASTURCTURE, you are in VO SCOPE returning no users");
return portalUsers;
}
for (UserModel user : users) {
if (user.getScreenName().compareTo("test.user") != 0 && user.getScreenName().compareTo(currUser) != 0) { //skip test.user & current user
String thumbnailURL = "";
com.liferay.portal.model.UserModel lifeUser = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), user.getScreenName());
thumbnailURL = "/image/user_male_portrait?img_id="+lifeUser.getPortraitId();
portalUsers.add(new PickingUser(user.getUserId(), user.getScreenName(), user.getFullname(), thumbnailURL));
}
}
}
else { //test users
portalUsers.add(new PickingUser("12111", "massimiliano.assante", "Test User #1", ""));
portalUsers.add(new PickingUser("14111", "massimiliano.assante", "Test Second User #2", ""));
portalUsers.add(new PickingUser("11511", "massimiliano.assante", "Test Third User", ""));
portalUsers.add(new PickingUser("11611", "massimiliano.assante", "Test Fourth User", ""));
portalUsers.add(new PickingUser("11711", "massimiliano.assante", "Test Fifth User", ""));
portalUsers.add(new PickingUser("11811", "massimiliano.assante", "Test Sixth User", ""));
portalUsers.add(new PickingUser("15811", "massimiliano.assante", "Ninth Testing User", ""));
portalUsers.add(new PickingUser("15811", "massimiliano.assante", "Eighth Testing User", ""));
portalUsers.add(new PickingUser("11211", "giogio.giorgi", "Seventh Test User", ""));
portalUsers.add(new PickingUser("2222", "pino.pinetti", "Tenth Testing User", ""));
}
} catch (Exception e) {
_log.error("Error in server get all contacts ", e);
}
return portalUsers;
}
/**
* Escape an html string. Escaping data received from the client helps to
* prevent cross-site script vulnerabilities.
*
* @param html the html string to escape
* @return the escaped string
*/
protected static String escapeHtmlAndTransformUrl(String html) {
if (html == null) {
return null;
}
String toReturn = html.replaceAll("&", "&amp;").replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
// replace all the line breaks by <br/>
toReturn = toReturn.replaceAll("(\r\n|\n)"," <br/> ");
//transfrom the URL in a clickable URL
toReturn = transformUrls(toReturn);
// then replace all the double spaces by the html version &nbsp;
toReturn = toReturn.replaceAll("\\s\\s","&nbsp;&nbsp;");
return toReturn;
}
/**
* utility method that convert a url ina text in a clickable url by the browser
* and if the user has just pasted a link, converts the link in: shared a link
* @param feedText
* @return the text with the clickable url in it
*/
protected static String transformUrls(String feedText) {
StringBuilder sb = new StringBuilder();
// separate input by spaces ( URLs have no spaces )
String [] parts = feedText.split("\\s");
// Attempt to convert each item into an URL.
for (int i = 0; i < parts.length; i++) {
String toCheck = getHttpToken(parts[i]);
if (toCheck != null) {
try {
URL url = new URL(toCheck);
if (i == 0 && parts.length == 1) //then he shared just a link
return sb.append("<span style=\"color:gray; font-size:12px;\">shared </span><a class=\"link\" href=\"").append(url).append("\" target=\"_blank\">").append("a link.").append("</a> ").toString();
// If possible then replace with anchor...
sb.append("<a class=\"link\" href=\"").append(url).append("\" target=\"_blank\">").append(url).append("</a> ");
} catch (MalformedURLException e) {
// If there was an URL then it's not valid
_log.error("MalformedURLException returning... ");
return feedText;
}
} else {
sb.append(parts[i]);
sb.append(" ");
}
}
return sb.toString();
}
/**
* convert the mentioned people in HTML anchor and also Encode the params Base64
* @param escapedFeedText
* @param taggedPeople
* @return
*/
protected static String convertMentionPeopleAnchorHTML(String escapedFeedText, ArrayList<PickingUser> taggedPeople) {
for (PickingUser tagged : taggedPeople) {
String taggedHTML = "<a class=\"link\" href=\""+GCubeSocialNetworking.USER_PROFILE_LINK
+"?"+
new String(Base64.encodeBase64(GCubeSocialNetworking.USER_PROFILE_OID.getBytes()))+"="+
new String(Base64.encodeBase64(tagged.getUsername().getBytes()))+"\">"+tagged.getFullName()+"</a> ";
escapedFeedText = escapedFeedText.replace(tagged.getFullName(), taggedHTML);
}
return escapedFeedText;
}
/**
* check the tokens of a pasted text and see if there's any http link in it
* @param item a text token
* @return the actual http link
*/
private static String getHttpToken(String item) {
if (item.startsWith("http") || item.startsWith("www") || item.startsWith("(www") || item.startsWith("(http")) {
if (item.startsWith("("))
item = item.substring(1, item.length());
if (item.endsWith(".") || item.endsWith(")")) { //sometimes people write the url and close the phrase with a .
item = item.substring(0, item.length()-1);
}
item = item.startsWith("www") ? "http://"+item : item;
//System.out.println("getHttpToken returns -> " + item);
return item;
}
return null;
}
}

View File

@ -25,7 +25,7 @@ table {
position: relative; position: relative;
} }
#comment-highlighter { .comment-highlighter {
padding: 4px 2px; padding: 4px 2px;
color: #ffffff; color: #ffffff;
background-color: #FFF; background-color: #FFF;
@ -56,7 +56,7 @@ table {
letter-spacing: normal; letter-spacing: normal;
line-height: normal; line-height: normal;
border: 1px solid #999; border: 1px solid #C3CDE7;
width: 450px; width: 450px;
min-height: 40px; min-height: 40px;