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:
parent
493934bcfd
commit
e7b89c36bd
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
7
pom.xml
7
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}-*/;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
text = textAreaText.replaceAll("(\r\n|\n)","<br />");
|
||||
text = text.replaceAll("\\s\\s"," ");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
@ -199,84 +195,6 @@ public class ShareUpdateServiceImpl extends RemoteServiceServlet implements Shar
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(" "," ");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private UserSettings getUserSettingsFromSession() {
|
||||
|
@ -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]);
|
||||
|
|
|
@ -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;
|
||||
|
||||
border: 1px solid #DBDBDB;
|
||||
}
|
||||
|
||||
|
@ -16,6 +90,7 @@
|
|||
text-align: right;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.member-photo {
|
||||
display: block;
|
||||
padding: 2px;
|
||||
|
@ -37,7 +112,7 @@
|
|||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.hide-description span label{
|
||||
.hide-description span label {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
|
@ -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,7 +235,6 @@ a.link:hover {
|
|||
}
|
||||
|
||||
/* smart textarea */
|
||||
|
||||
#mycontentEditableElement input[type="text"] {
|
||||
font-family: verdana, arial, sans-serif;
|
||||
font-size: 11px;
|
||||
|
|
|
@ -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>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="shareUpdateDiv"></div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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>
|
|
@ -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));
|
Loading…
Reference in New Issue