user mentions via content editable div was flawed, this fix superposes a textarea and a div to make users look highlighted

git-svn-id: https://svn.research-infrastructures.eu/d4science/gcube/trunk/portlets/user/share-updates@76648 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Massimiliano Assante 2013-06-02 14:26:58 +00:00
parent 493934bcfd
commit e7b89c36bd
17 changed files with 426 additions and 361 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/share-updates-0.3.0-SNAPSHOT/WEB-INF/classes" path="src/main/java">
<classpathentry kind="src" output="target/share-updates-0.4.0-SNAPSHOT/WEB-INF/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
@ -31,5 +31,5 @@
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/share-updates-0.3.0-SNAPSHOT/WEB-INF/classes"/>
<classpathentry kind="output" path="target/share-updates-0.4.0-SNAPSHOT/WEB-INF/classes"/>
</classpath>

View File

@ -1,6 +1,6 @@
#Tue Apr 16 18:09:11 CEST 2013
#Sun Jun 02 00:32:02 CEST 2013
eclipse.preferences.version=1
jarsExcludedFromWebInfLib=
lastWarOutDir=/Users/massi/Documents/workspace/share-updates/target/share-updates-0.3.0-SNAPSHOT
lastWarOutDir=/Users/massi/Documents/workspace/share-updates/target/share-updates-0.4.0-SNAPSHOT
warSrcDir=src/main/webapp
warSrcDirIsOutput=false

View File

@ -5,9 +5,6 @@
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-sources/gwt"/>
<dependent-module archiveName="pickuser-widget-0.1.0-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/pickuser-widget/pickuser-widget">
<dependency-type>uses</dependency-type>
</dependent-module>
<property name="java-output-path" value="/${module}/target/www/WEB-INF/classes"/>
<property name="context-root" value="share-updates"/>
</wb-module>

View File

@ -13,7 +13,7 @@
<groupId>org.gcube.portlets.user</groupId>
<artifactId>share-updates</artifactId>
<packaging>war</packaging>
<version>0.3.0-SNAPSHOT</version>
<version>0.4.0-SNAPSHOT</version>
<name>gCube Share Updates Portlet</name>
<description>
@ -137,11 +137,6 @@
<artifactId>jtidy</artifactId>
<version>r938</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>net.eliasbalasis</groupId>
<artifactId>tibcopagebus4gwt</artifactId>

View File

@ -17,7 +17,7 @@ import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
*/
@RemoteServiceRelativePath("shareupdateServlet")
public interface ShareUpdateService extends RemoteService {
ClientFeed share(String feedText, FeedType type, PrivacyLevel pLevel, String vreName, String linkTitle, String linkDesc, String url, String urlThumbnail, String host);
ClientFeed share(String feedText, FeedType type, PrivacyLevel pLevel, String vreName, String linkTitle, String linkDesc, String url, String urlThumbnail, String host, ArrayList<String> mentionedUsers);
UserSettings getUserSettings();

View File

@ -17,7 +17,8 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
public interface ShareUpdateServiceAsync {
void share(String feedText, FeedType type, PrivacyLevel pLevel,
String vreName, String linkTitle, String linkDesc, String url,
String urlThumbnail, String host, AsyncCallback<ClientFeed> callback);
String urlThumbnail, String host, ArrayList<String> mentionedUsers,
AsyncCallback<ClientFeed> callback);
void checkLink(String linkToCheck, AsyncCallback<LinkPreview> callback);

View File

@ -1,51 +0,0 @@
package org.gcube.portlets.user.shareupdates.client.elements;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasText;
/**
*
* @author Massimiliano Assante, ISTI-CNR
*
*/
public class ContentEditDiv extends HTML implements HasText {
public ContentEditDiv() {
super(DOM.createElement("div"));
//important to make it act like a textarea
DOM.setElementAttribute(getElement(), "contentEditable", "true");
DOM.setElementAttribute(getElement(), "id", "mycontentEditableElement");
}
public ContentEditDiv(String text) {
this();
setText(text);
}
public void setEnabled(boolean enabled) {
DOM.setElementPropertyBoolean(getElement(), "disabled", !enabled);
}
public static native void setEndOfContenteditable() /*-{
var contentEditableElement = $doc.getElementById("mycontentEditableElement");
var range,selection;
if($doc.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = $doc.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = $wnd.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
}
else if($doc.selection)//IE 8 and lower
{
range = $doc.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
range.select();//Select the range (make it the visible selection
}
}-*/;
}

View File

@ -1,26 +0,0 @@
package org.gcube.portlets.user.shareupdates.client.elements;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasText;
/**
*
* @author Massimiliano Assante, ISTI-CNR
*
*/
public class Span extends HTML implements HasText {
public Span() {
super(DOM.createElement("span"));
}
public Span(String text) {
this();
setText(text);
}
public void setCSSClassName(String className) {
getElement().addClassName(className);
}
}

View File

@ -1,24 +0,0 @@
package org.gcube.portlets.user.shareupdates.client.elements;
import com.google.gwt.user.client.ui.TextBox;
/**
*
* @author Massimiliano Assante, ISTI-CNR
*
*/
public class TagBox extends TextBox {
public TagBox() {
}
public TagBox(String text) {
super();
this.setStylePrimaryName("user-token");
getElement().setAttribute("value", text);
//random heuristic, the "i" and "l" char occupies very few px :)
int iCounter = 14 - 2*(text.split("i").length + (text.split("l").length));
setWidth((text.length()*6+iCounter)+"px");
setReadOnly(true);
}
}

View File

@ -1,5 +1,7 @@
package org.gcube.portlets.user.shareupdates.client.form;
import java.util.ArrayList;
import net.eliasbalasis.tibcopagebus4gwt.client.PageBusAdapter;
import net.eliasbalasis.tibcopagebus4gwt.client.PageBusAdapterException;
@ -43,7 +45,7 @@ public class ShareUpdateForm extends Composite {
.create(ShareUpdateService.class);
final PageBusAdapter pageBusAdapter = new PageBusAdapter();
protected final static String SHARE_UPDATE_TEXT = "Share an update or paste a link, use “@” and then start typing to tag people";
protected final static String SHARE_UPDATE_TEXT = "Share an update or paste a link, use “@” to mention someone";
protected final static String ERROR_UPDATE_TEXT = "Looks like empty to me!";
private final static String LISTBOX_LEVEL = " - ";
@ -65,6 +67,18 @@ public class ShareUpdateForm extends Composite {
}
@UiField
HTMLPanel mainPanel;
@UiField
LinkPlaceholder preview;
@UiField
Button submitButton;
@UiField
Image avatarImage;
@UiField SuperPosedTextArea shareTextArea;
@UiField ListBox privacyLevel = new ListBox(false);
UserInfo myUserInfo;
@ -110,18 +124,6 @@ public class ShareUpdateForm extends Composite {
}
});
}
@UiField
LinkPlaceholder preview;
@UiField
Button submitButton;
@UiField
Image avatarImage;
@UiField SmartTextArea shareTextArea;
@UiField ListBox privacyLevel = new ListBox(false);
@UiHandler("shareTextArea")
void onShareUpdateClick(ClickEvent e) {
@ -147,8 +149,8 @@ public class ShareUpdateForm extends Composite {
return;
}
//then you can post, but pass html
String toPost = shareTextArea.getHTML().trim();
postTweet(toPost);
String toPost = shareTextArea.getText();
postTweet(toPost, shareTextArea.getMentionedUsers());
}
});
}
@ -156,15 +158,13 @@ public class ShareUpdateForm extends Composite {
*
* @param textToPost
*/
private void postTweet(String textToPost) {
//String toShare = escapeHtml(textToPost);
String toShare = textToPost;
if (! checkTextLength(new HTML(toShare).getText())) { //need to convert it to text
private void postTweet(String textToPost, ArrayList<String> mentionedUsers) {
String toShare = escapeHtml(textToPost);
if (! checkTextLength(toShare)) {
Window.alert("We found a single word containing more than 50 chars and it's not a link, is it meaningful?");
return;
}
submitButton.setEnabled(false);
shareTextArea.setEnabled(false);
@ -186,11 +186,12 @@ public class ShareUpdateForm extends Composite {
linkUrlThumbnail = myLinkPreviewer.getUrlThumbnail();
linkHost = myLinkPreviewer.getHost();
}
shareupdateService.share(toShare, FeedType.TWEET, getPrivacyLevel(), vreId, linkTitle, linkDescription, linkUrl, linkUrlThumbnail, linkHost, new AsyncCallback<ClientFeed>() {
shareupdateService.share(toShare, FeedType.TWEET, getPrivacyLevel(), vreId, linkTitle, linkDescription, linkUrl, linkUrlThumbnail, linkHost, mentionedUsers, new AsyncCallback<ClientFeed>() {
public void onFailure(Throwable caught) {
submitButton.setEnabled(true);
shareTextArea.setEnabled(true);
shareTextArea.setText(SHARE_UPDATE_TEXT);
shareTextArea.cleanHighlihterDiv();
preview.clear();
myLinkPreviewer = null;
}
@ -199,6 +200,7 @@ public class ShareUpdateForm extends Composite {
submitButton.setEnabled(true);
shareTextArea.setEnabled(true);
shareTextArea.setText(SHARE_UPDATE_TEXT);
shareTextArea.cleanHighlihterDiv();
preview.clear();
myLinkPreviewer = null;
if (feed == null)
@ -232,6 +234,7 @@ public class ShareUpdateForm extends Composite {
/**
* Escape an html string. Escaping data received from the client helps to
* prevent cross-site script vulnerabilities.

View File

@ -4,16 +4,22 @@
<g:HTMLPanel ui:field="mainPanel">
<table class="shareContainer">
<tr>
<td width="60px;" align="middle">
<td width="60px;" align="middle" valign="top">
<a href="">
<g:Image title="Edit Profile Picture" styleName="member-photo"
url="" ui:field="avatarImage" width="60" height="60" />
</a>
</td>
<td>
<div class="shareContainer-in">
<m:SmartTextArea styleName="post-message" ui:field="shareTextArea" />
<td valign="top">
<div id="supercontainer">
<div id="highlighterContainer">
<div id="highlighter">
</div>
</div>
<div id="inputContainer">
<m:SuperPosedTextArea styleName="postTextArea" ui:field="shareTextArea" />
</div>
</div>
</td>
</tr>
@ -25,7 +31,8 @@
<td valign="middle" width="50%">
<div style="text-align: left; padding-top: 2px; color: #777;">
privacy level:
<g:ListBox styleName="wizardListbox" ui:field="privacyLevel" visible="false"/>
<g:ListBox styleName="wizardListbox" ui:field="privacyLevel"
visible="false" />
<span id="staticPrivacyLevel"></span>
</div>
</td>

View File

@ -11,47 +11,39 @@ import org.gcube.portlets.user.pickuser.client.events.PickedUserEventHandler;
import org.gcube.portlets.user.pickuser.shared.PickingUser;
import org.gcube.portlets.user.shareupdates.client.ShareUpdateService;
import org.gcube.portlets.user.shareupdates.client.ShareUpdateServiceAsync;
import org.gcube.portlets.user.shareupdates.client.elements.ContentEditDiv;
import org.gcube.portlets.user.shareupdates.client.elements.Span;
import org.gcube.portlets.user.shareupdates.client.elements.TagBox;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.TextArea;
/**
*
* @author Massimiliano Assante, ISTI-CNR
*
* SmartTextArea allows pasting of links with preview generation and tagging of people
* @author massi
*
*/
public class SmartTextArea extends ContentEditDiv {
/**
* Create a remote service proxy to talk to the server-side Greeting service.
*/
public class SuperPosedTextArea extends TextArea {
private final ShareUpdateServiceAsync shareupdateService = GWT
.create(ShareUpdateService.class);
private final HandlerManager eventBus = new HandlerManager(null);
PickUsersDialog pickUserDlg;
public final static int ARROW_UP = 38;
public final static int ARROW_DOWN = 40;
PickUsersDialog pickUserDlg;
private ArrayList<String> mentionedUsers = new ArrayList<String>();
/**
*
*/
public SmartTextArea() {
bind();
public SuperPosedTextArea() {
sinkEvents(Event.ONPASTE);
sinkEvents(Event.ONCONTEXTMENU);
sinkEvents(Event.ONKEYUP);
sinkEvents(Event.ONCONTEXTMENU);
sinkEvents(Event.ONKEYDOWN);
shareupdateService.getPortalUsers(new AsyncCallback<ArrayList<PickingUser>>() {
@ -65,56 +57,43 @@ public class SmartTextArea extends ContentEditDiv {
public void onFailure(Throwable caught) {
}
});
DOM.setElementAttribute(getElement(), "id", "postTextArea");
bind();
}
/**
* events binder
* @param element
*/
private void bind() {
eventBus.addHandler(PickedUserEvent.TYPE, new PickedUserEventHandler() {
@Override
public void onSelectedUser(PickedUserEvent event) {
String[] toSplit = getHTML().split("@"); //get the interesting part
TagBox span = new TagBox(event.getSelectedUser().getFullName());
setHTML(toSplit[0]);
getElement().appendChild(span.getElement());
getElement().appendChild(new Span(" ").getElement());
setEndOfContenteditable();
}
});
public SuperPosedTextArea(Element element) {
super(element);
}
/**
* paste and other events overridden
* paste event overridden
*/
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
switch (event.getTypeInt()) {
case Event.ONPASTE: {
final String before = getHTML();
final String before = getText();
GWT.log("BEFORE:" + before);
Timer t = new Timer() {
@Override
public void run() {
String toCheck = extractLink(getHTML());
if (toCheck != null) {
GWT.log("toCheck1:" + toCheck);
ShareUpdateForm.get().checkLink(new HTML(toCheck).getText());
String sanitized = before + toCheck;
setHTML(sanitized);
setEndOfContenteditable();
}
String toCheck = getText().replaceAll(before, "");
ShareUpdateForm.get().checkLink(toCheck);
}
};
t.schedule(100);
break;
}
case Event.ONCONTEXTMENU: {
removeSampleText();
case Event.ONKEYUP: {
injectInDiv(getText());
pickUserDlg.onKeyUp(event.getKeyCode(), this.getAbsoluteLeft(), this.getAbsoluteTop()+65, getText());
break;
}
case Event.ONKEYUP: {
pickUserDlg.onKeyUp(event.getKeyCode(), this.getAbsoluteLeft(), this.getAbsoluteTop()+65, getText());
case Event.ONCONTEXTMENU: {
removeSampleText();
break;
}
case Event.ONKEYDOWN: {
@ -129,47 +108,61 @@ public class SmartTextArea extends ContentEditDiv {
break;
}
}
DomEvent.fireNativeEvent(event, this, this.getElement());
}
/**
* extract the pasted link
* @param textToCheck
* @return null if there's no link present
*/
private String extractLink(String textToCheck) {
GWT.log("textToCheck:" + textToCheck);
String [] parts = textToCheck.split("\\s");
// Attempt to convert each item into an URL.
for( String item : parts ) {
if (item.startsWith("http")) {
return item;
}
if (item.startsWith("href") || item.startsWith("HREF")) { //this is for (damned) safari because sometimes it paste <a href="http... nsteand of http
textToCheck = textToCheck.replaceAll("\"", " ");
textToCheck = textToCheck.replaceAll("href", " ");
textToCheck = textToCheck.replaceAll("HREF", " ");
return extractLink(textToCheck);
}
}
//else we try plain text here (chrome)
String plainText = new HTML(textToCheck).getText();
if (textToCheck.length() != plainText.length()) {
plainText = textToCheck.replaceAll("nbsp;", " ");
String [] pparts = plainText.split("\\s");
for( String item : pparts ) {
if (item.startsWith("http"))
return item;
}
}
return null;
}
protected void removeSampleText() {
if (getText().equals(ShareUpdateForm.SHARE_UPDATE_TEXT) || getText().equals(ShareUpdateForm.ERROR_UPDATE_TEXT) ) {
setText("");
addStyleName("dark-color");
addStyleName("darker-color");
removeStyleName("error");
}
}
protected void cleanHighlihterDiv() {
DOM.getElementById("highlighter").setInnerHTML("");
}
private void injectInDiv(String textAreaText) {
String text;
// parse the text:
// replace all the line braks by <br/>, and all the double spaces by the html version &nbsp;
text = textAreaText.replaceAll("(\r\n|\n)","<br />");
text = text.replaceAll("\\s\\s","&nbsp;&nbsp;");
for (String mentionedUser : mentionedUsers) {
text = text.replaceAll(mentionedUser,"<span class=\"highlightedUser\">"+mentionedUser+"</span>");
}
// re-inject the processed text into the div
DOM.getElementById("highlighter").setInnerHTML(text);
}
/**
* events binder
*/
private void bind() {
eventBus.addHandler(PickedUserEvent.TYPE, new PickedUserEventHandler() {
@Override
public void onSelectedUser(PickedUserEvent event) {
String toAdd = event.getSelectedUser().getFullName();
mentionedUsers.add(toAdd);
String[] toSplit = getText().split("@"); //get the preceeding part
setText(toSplit[0]+toAdd);
Element highDiv = DOM.getElementById("highlighter");
String[] htmlToSplit = highDiv.getInnerHTML().split("@"); //get the preceeding part
String highLightedUser = "<span class=\"highlightedUser\">"+toAdd+"</span>";
highDiv.setInnerHTML(htmlToSplit[0]+highLightedUser);
}
});
}
public ArrayList<String> getMentionedUsers() {
ArrayList<String> toReturn = new ArrayList<String>();
for (String mentionedUser : mentionedUsers) {
if (getText().contains(mentionedUser))
toReturn.add(mentionedUser);
}
GWT.log(toReturn.toString());
return mentionedUsers;
}
}

View File

@ -24,7 +24,6 @@ import org.gcube.applicationsupportlayer.social.NotificationsManager;
import org.gcube.common.core.utils.logging.GCUBEClientLog;
import org.gcube.portal.custom.communitymanager.OrganizationsUtil;
import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper;
import org.gcube.portal.databook.client.GCubeSocialNetworking;
import org.gcube.portal.databook.server.DBCassandraAstyanaxImpl;
import org.gcube.portal.databook.server.DatabookStore;
import org.gcube.portal.databook.shared.ClientFeed;
@ -52,10 +51,7 @@ import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayGroupManager;
import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.GroupModel;
import org.gcube.vomanagement.usermanagement.model.UserModel;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.htmlparser.beans.StringBean;
import org.jsoup.Jsoup;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;
@ -128,9 +124,9 @@ public class ShareUpdateServiceImpl extends RemoteServiceServlet implements Shar
/**
*
*/
public ClientFeed share(String postText, FeedType feedType, PrivacyLevel pLevel, String vreId, String linkTitle, String linkDesc, String url, String urlThumbnail, String host) {
public ClientFeed share(String postText, FeedType feedType, PrivacyLevel pLevel, String vreId, String linkTitle, String linkDesc, String url, String urlThumbnail, String host, ArrayList<String> mentionedUserFullNames) {
String escapedFeedText = transformPost(postText);
String escapedFeedText = escapeHtml(postText);
ASLSession session = getASLSession();
String username = session.getUsername();
String email = username+"@isti.cnr.it";
@ -188,7 +184,7 @@ public class ShareUpdateServiceImpl extends RemoteServiceServlet implements Shar
toShare.getUriThumbnail(), toShare.getLinkHost());
//send the notification to the mentioned users
ArrayList<String> mentionedUserIds = getSelectedUserIds(extractPeopleTags(postText));
ArrayList<String> mentionedUserIds = getSelectedUserIds(mentionedUserFullNames);
if (mentionedUserIds != null && mentionedUserIds.size() > 0) {
NotificationsManager nm = new ApplicationNotificationsManager(session);
Thread thread = new Thread(new MentionNotificationsThread(toShare.getKey(), escapedFeedText, nm, mentionedUserIds));
@ -198,84 +194,6 @@ public class ShareUpdateServiceImpl extends RemoteServiceServlet implements Shar
return cf;
}
/**
* this method is used when posting a feed
*
* It converts the tagged people <input with the actual person profile link, not trivial
*
* @param postText is the html you get from the contentDIV smartarea sth like:
* text text text <input .... value="Massimiliano Assante" ... type="text"><span> etc etc</span>
*
* @return a String ready to be posted
*/
private String transformPost(String postText) {
ArrayList<String> taggedPeople = extractPeopleTags(postText);
if (taggedPeople == null || taggedPeople.size() == 0) { //there are no tagged people, remove html and go
String escapedFeedText = escapeHtml(postText); //here escape html to avoid xss attacks
String html = "<html><head></head><body>" + escapedFeedText + "</body></html>";
return html2text(html);
} else {
_log.trace("postText curing: " + postText);
// this is needed to reconstruct the place of people tags, selfexplaining i think
int i = 0;
while (postText.contains("<input")) {
//the replacing does not affect html but affects the While guard
postText = postText.replaceFirst("<input", "_usr_place_holder_["+i+"]<br");
i++;
}
String html = "<html><head></head><body>" + postText + "</body></html>";
String postWithPlaceHolders = html2text(html);
_log.trace("before cure: " + postWithPlaceHolders);
/*
* at this point you have the html removed and somthing like "text text text _usr_place_holder_[0] text text text _usr_place_holder_[1]"
* you need to replace _usr_place_holder_[i](s) with the people with same index in taggedPeople ArrayList
*/
String escapedFeedText = escapeHtml(postWithPlaceHolders); //here escape html to avoid xss attacks
ArrayList<String> usernames = getSelectedUserIds(taggedPeople);
i = 0;
for (String tagged : taggedPeople) {
String username = (i < usernames.size()) ? usernames.get(i) : "";
String taggedHTML = "<a class=\"link\" style=\"font-size:14px;\" href=\""+GCubeSocialNetworking.USER_PROFILE_LINK+"?uid="+ username + "\">"+tagged+"</a> ";
escapedFeedText = escapedFeedText.replace("_usr_place_holder_["+i+"]", taggedHTML);
i++;
}
_log.trace("After cure: " + escapedFeedText);
return escapedFeedText;
}
}
/**
* this method extractPeopleTags from the user post
* @param postText text with tagged people: txt .. <input with the actual person profile
* @return
*/
private ArrayList<String> extractPeopleTags(String postText) {
ArrayList<String> toReturn = new ArrayList<String>();
String toParse = "<html><head></head><body>" + postText + "</body></html>";
HtmlCleaner cleaner = new HtmlCleaner();
// parse the string HTML
TagNode pageData = cleaner.clean(toParse);
TagNode[] inputElements = pageData.getElementsByName("input", true);
if (inputElements != null) {
for (int i = 0; i < inputElements.length; i++) {
System.out.println("Found input " + inputElements[i].getAttributes().get("value"));
toReturn.add(inputElements[i].getAttributes().get("value"));
}
} else {
_log.trace("No person tags in this post");
}
return toReturn;
}
/**
* remove all the html and leave the text
* @param html
* @return the text inside the html
*/
private static String html2text(String html) {
return Jsoup.parse(html).text().replace("&nbsp;"," ");
}
@ -297,13 +215,11 @@ public class ShareUpdateServiceImpl extends RemoteServiceServlet implements Shar
* @return the text with the clickable url in it
*/
public String transformUrls(String feedText) {
System.out.println("transformUrls" + 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++) {
System.out.println("part: " + parts[i]);
if (parts[i].startsWith("http")) {
try {
URL url = new URL(parts[i]);

View File

@ -1,14 +1,88 @@
/* Superpose TextArea and Highlight DIV trick starts here */
#supercontainer {
position: relative;
}
#highlighterContainer {
position: absolute;
left: 0;
top: 0;
cursor: text;
width: 525px;
height: 54px;
}
#inputContainer {
position: relative;
}
#highlighter {
padding: 4px 2px;
color: #ffffff;
background-color: #FFF;
margin: 0px;
font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial,
sans-serif;
font-size: 13px;
letter-spacing: normal;
line-height: normal;
border: 1px solid transparent;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
width: 525px;
height: 54px;
word-wrap: break-word; /* this is very important when usere paste long links*/
}
.postTextArea {
padding: 4px 2px;
color: #999;
background-color: transparent;
margin: 0px;
font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial,
sans-serif;
font-size: 13px;
letter-spacing: normal;
line-height: normal;
border: 1px solid #999;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
width: 525px;
height: 54px;
}
.highlightedUser {
background-color: #D8DFEA !important;
}
/* DIV trick ends here */
.darker-color {
color: #333;
background-color: transparent;
transition: background .25s ease-in-out;
-moz-transition: background .25s ease-in-out;
-webkit-transition: background .25s ease-in-out;
}
.framed {
margin: 0 0 10px;
padding: 10px;
margin: 0px 5px;
background: #FFF url(images/vre_bg_gray.png) repeat-x left bottom;
border-radius: 6px !important;
-moz-border-radius: 6px !important;
-webkit-border-radius: 6px !important;
-moz-border-radius: 6px !important;
-webkit-border-radius: 6px !important;
border: 1px solid #DBDBDB;
}
@ -16,6 +90,7 @@
text-align: right;
padding-top: 2px;
}
.member-photo {
display: block;
padding: 2px;
@ -34,11 +109,11 @@
}
.hide-description {
margin-top: 5px;
margin-top: 5px;
}
.hide-description span label{
padding-left: 5px;
.hide-description span label {
padding-left: 5px;
}
.linkpreview-bgcolor {
@ -125,35 +200,6 @@ a.link:hover {
padding-bottom: 3px;
}
.shareContainer-in {
padding-left: 5px;
}
.post-message {
height: 54px;
color: #999;
font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial,
sans-serif;
font-size: 13px;
padding: 4px 2px;
width: 525px;
border: 1px solid #999;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
letter-spacing: normal;
max-height: 54px;
overflow: auto;
}
.dark-color {
color: #333;
background-color: #FFF;
transition: background .25s ease-in-out;
-moz-transition: background .25s ease-in-out;
-webkit-transition: background .25s ease-in-out;
}
.toolsContainer {
padding-top: 3px;
width: 600px;
@ -189,13 +235,12 @@ a.link:hover {
}
/* smart textarea */
#mycontentEditableElement input[type="text"] {
#mycontentEditableElement input[type="text"] {
font-family: verdana, arial, sans-serif;
font-size: 11px;
padding: 1px 3px;
color: #1C2A47;
border: 1px solid #9DACCC;
border: 1px solid #9DACCC;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;

View File

@ -6,18 +6,30 @@
<!-- differences in layout. -->
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!-- -->
<!-- Consider inlining CSS to reduce the number of requested files -->
<!-- -->
<link type="text/css" rel="stylesheet" href="ShareUpdates.css">
<!-- -->
<!-- Consider inlining CSS to reduce the number of requested files -->
<!-- -->
<link type="text/css" rel="stylesheet" href="ShareUpdates.css">
<script type="text/javascript" language="javascript" src="shareupdates/shareupdates.nocache.js"></script>
</head>
<script type="text/javascript"
src="shareupdates/shareupdates.nocache.js"></script>
<script
src='http://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js'></script>
<script src='js/jquery.autosize.js'></script>
<script>
$(document).ready(function(){
setTimeout(function(){
$('.postTextArea').autosize();
}, 3000);
});
</script>
<body>
<div id="shareUpdateDiv"></div>
</body>
</head>
<body>
<div id="shareUpdateDiv"></div>
</body>
</html>

View File

@ -16,4 +16,14 @@
<script type="text/javascript" language="javascript"
src='<%=request.getContextPath()%>/shareupdates/shareupdates.nocache.js'></script>
<script
src='http://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js'></script>
<script src='<%=request.getContextPath()%>/js/jquery.autosize.js'></script>
<script>
$(document).ready(function(){
setTimeout(function(){
$('.postTextArea').autosize();
}, 3000);
});
</script>
<div id="shareUpdateDiv"></div>

View File

@ -0,0 +1,187 @@
/*!
jQuery Autosize v1.16.12
(c) 2013 Jack Moore - jacklmoore.com
updated: 2013-05-31
license: http://www.opensource.org/licenses/mit-license.php
*/
(function ($) {
var
defaults = {
className: 'autosizejs',
append: '',
callback: false
},
hidden = 'hidden',
borderBox = 'border-box',
lineHeight = 'lineHeight',
// border:0 is unnecessary, but avoids a bug in FireFox on OSX
copy = '<textarea tabindex="-1" style="position:absolute; top:-999px; left:0; right:auto; bottom:auto; border:0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden;"/>',
// line-height is conditionally included because IE7/IE8/old Opera do not return the correct value.
copyStyle = [
'fontFamily',
'fontSize',
'fontWeight',
'fontStyle',
'letterSpacing',
'textTransform',
'wordSpacing',
'textIndent'
],
oninput = 'oninput',
onpropertychange = 'onpropertychange',
// to keep track which textarea is being mirrored when adjust() is called.
mirrored,
// the mirror element, which is used to calculate what size the mirrored element should be.
mirror = $(copy).data('autosize', true)[0];
// test that line-height can be accurately copied.
mirror.style.lineHeight = '99px';
if ($(mirror).css(lineHeight) === '99px') {
copyStyle.push(lineHeight);
}
mirror.style.lineHeight = '';
$.fn.autosize = function (options) {
options = $.extend({}, defaults, options || {});
if (mirror.parentNode !== document.body) {
$(document.body).append(mirror);
}
return this.each(function () {
var
ta = this,
$ta = $(ta),
minHeight,
maxHeight,
resize,
boxOffset = 0,
callback = $.isFunction(options.callback);
if ($ta.data('autosize')) {
// exit if autosize has already been applied, or if the textarea is the mirror element.
return;
}
if ($ta.css('box-sizing') === borderBox || $ta.css('-moz-box-sizing') === borderBox || $ta.css('-webkit-box-sizing') === borderBox){
boxOffset = $ta.outerHeight() - $ta.height();
}
// IE8 and lower return 'auto', which parses to NaN, if no min-height is set.
minHeight = Math.max(parseInt($ta.css('minHeight'), 10) - boxOffset || 0, $ta.height());
resize = ($ta.css('resize') === 'none' || $ta.css('resize') === 'vertical') ? 'none' : 'horizontal';
$ta.css({
overflow: hidden,
overflowY: hidden,
wordWrap: 'break-word',
resize: resize
}).data('autosize', true);
function initMirror() {
mirrored = ta;
mirror.className = options.className;
maxHeight = parseInt($ta.css('maxHeight'), 10);
// mirror is a duplicate textarea located off-screen that
// is automatically updated to contain the same text as the
// original textarea. mirror always has a height of 0.
// This gives a cross-browser supported way getting the actual
// height of the text, through the scrollTop property.
$.each(copyStyle, function(i, val){
mirror.style[val] = $ta.css(val);
});
// The textarea overflow is probably now hidden, but Chrome doesn't reflow the text to account for the
// new space made available by removing the scrollbars. This workaround causes Chrome to reflow the text.
if (oninput in ta) {
var value = ta.value;
ta.value = '';
ta.value = value;
}
}
// Using mainly bare JS in this function because it is going
// to fire very often while typing, and needs to very efficient.
function adjust() {
var height, overflow, original;
if (mirrored !== ta) {
initMirror();
}
mirror.value = ta.value + options.append;
mirror.style.overflowY = ta.style.overflowY;
original = parseInt(ta.style.height,10);
// Update the width in case the original textarea width has changed
// A floor of 0 is needed because IE8 returns a negative value for hidden textareas, raising an error.
mirror.style.width = Math.max($ta.width(), 0) + 'px';
// Needed for IE8 and lower to reliably return the correct scrollTop
mirror.scrollTop = 0;
mirror.scrollTop = 9e4;
// Using scrollTop rather than scrollHeight because scrollHeight is non-standard and includes padding.
height = mirror.scrollTop;
if (maxHeight && height > maxHeight) {
height = maxHeight;
overflow = 'scroll';
} else if (height < minHeight) {
height = minHeight;
}
height += boxOffset;
ta.style.overflowY = overflow || hidden;
if (original !== height) {
ta.style.height = height + 'px';
if (callback) {
options.callback.call(ta,ta);
}
}
}
if (onpropertychange in ta) {
if (oninput in ta) {
// Detects IE9. IE9 does not fire onpropertychange or oninput for deletions,
// so binding to onkeyup to catch most of those occassions. There is no way that I
// know of to detect something like 'cut' in IE9.
ta[oninput] = ta.onkeyup = adjust;
} else {
// IE7 / IE8
ta[onpropertychange] = function(){
if(event.propertyName === 'value'){
adjust();
}
};
}
} else {
// Modern Browsers
ta[oninput] = adjust;
}
$(window).on('resize', function(){
active = false;
adjust();
});
// Allow for manual triggering if needed.
$ta.on('autosize', function(){
active = false;
adjust();
});
// Call adjust in case the textarea already contains text.
adjust();
});
};
}(window.jQuery || window.Zepto));