Editor and base API changes Project: http://git-wip-us.apache.org/repos/asf/incubator-wave/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-wave/commit/a768997c Tree: http://git-wip-us.apache.org/repos/asf/incubator-wave/tree/a768997c Diff: http://git-wip-us.apache.org/repos/asf/incubator-wave/diff/a768997c
Branch: refs/heads/swellrt Commit: a768997c3943f25ed4c1b761ca6c5508d0eb1d2b Parents: 8ebaa26 Author: Pablo Ojanguren <pablo...@gmail.com> Authored: Wed Aug 31 00:27:34 2016 +0200 Committer: Pablo Ojanguren <pablo...@gmail.com> Committed: Wed Aug 31 00:27:34 2016 +0200 ---------------------------------------------------------------------- .../java/org/swellrt/api/BrowserSession.java | 10 + .../java/org/swellrt/api/ServiceCallback.java | 5 + wave/src/main/java/org/swellrt/api/SwellRT.java | 271 +++++++- .../main/java/org/swellrt/api/SwellRTUtils.java | 15 + .../main/java/org/swellrt/api/WaveClient.java | 39 +- .../java/org/swellrt/api/js/WaveClientJS.java | 25 + .../org/swellrt/api/js/editor/TextEditorJS.java | 73 +- .../api/js/editor/TextEditorJSListener.java | 4 +- .../org/swellrt/client/editor/TextEditor.java | 664 +++++++++++-------- .../client/editor/TextEditorListener.java | 4 +- .../doodad/annotation/AnnotationHandler.java | 39 +- .../annotation/jso/JsoAnnotationController.java | 24 +- .../client/doodad/annotation/jso/JsoRange.java | 88 ++- .../wave/client/doodad/widget/WidgetDoodad.java | 9 +- .../doodad/widget/jso/JsoWidgetController.java | 23 +- .../editor/content/misc/AnnotationPaint.java | 2 + .../content/misc/AnnotationSpreadRenderer.java | 28 +- .../editor/content/paragraph/Paragraph.java | 2 + .../content/paragraph/ParagraphRenderer.java | 23 +- .../editor/harness/DefaultTestHarness.java | 11 +- 20 files changed, 947 insertions(+), 412 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/BrowserSession.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/BrowserSession.java b/wave/src/main/java/org/swellrt/api/BrowserSession.java index 1b492b9..9d215db 100644 --- a/wave/src/main/java/org/swellrt/api/BrowserSession.java +++ b/wave/src/main/java/org/swellrt/api/BrowserSession.java @@ -81,6 +81,10 @@ public class BrowserSession { return token; }-*/; + + public static String getWindowSessionId() { + return getToken(); + } public static native String getSessionId() /*-{ @@ -100,6 +104,12 @@ public class BrowserSession { return null; }-*/; + public static native String getUserAddress() /*-{ + if ($wnd.__session && $wnd.__session['address'] != null) + return $wnd.__session['address']; + + return null; + }-*/; /** * Define session data in a window object. http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/ServiceCallback.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/ServiceCallback.java b/wave/src/main/java/org/swellrt/api/ServiceCallback.java index 5e08297..0bceb27 100644 --- a/wave/src/main/java/org/swellrt/api/ServiceCallback.java +++ b/wave/src/main/java/org/swellrt/api/ServiceCallback.java @@ -1,5 +1,7 @@ package org.swellrt.api; +import org.swellrt.api.js.generic.ModelJS; + import com.google.gwt.core.client.JavaScriptObject; public class ServiceCallback extends JavaScriptObject { @@ -23,6 +25,9 @@ public class ServiceCallback extends JavaScriptObject { return r; }-*/; + public static native JavaScriptResponse success(ModelJS cObject) /*-{ + return cObject; + }-*/; public static native JavaScriptResponse error(String json) /*-{ var r; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/SwellRT.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/SwellRT.java b/wave/src/main/java/org/swellrt/api/SwellRT.java index f7e9263..7d3a83e 100644 --- a/wave/src/main/java/org/swellrt/api/SwellRT.java +++ b/wave/src/main/java/org/swellrt/api/SwellRT.java @@ -7,6 +7,7 @@ import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.JsonUtils; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; @@ -17,7 +18,9 @@ import com.google.gwt.user.client.Command; import com.google.gwt.user.client.ui.RootPanel; import org.swellrt.api.ServiceCallback.JavaScriptResponse; +import org.swellrt.api.js.generic.ModelJS; import org.swellrt.client.WaveLoader; +import org.swellrt.client.editor.TextEditor; import org.swellrt.model.generic.Model; import org.swellrt.model.generic.TypeIdGenerator; import org.waveprotocol.box.stat.Timing; @@ -25,6 +28,9 @@ import org.waveprotocol.box.webclient.client.ClientIdGenerator; import org.waveprotocol.box.webclient.client.RemoteViewServiceMultiplexer; import org.waveprotocol.box.webclient.client.WaveSocket.WaveSocketStartCallback; import org.waveprotocol.box.webclient.client.WaveWebSocketClient; +import org.waveprotocol.wave.client.common.util.JsoView; +import org.waveprotocol.wave.client.doodad.annotation.jso.JsoAnnotationController; +import org.waveprotocol.wave.client.doodad.widget.jso.JsoWidgetController; import org.waveprotocol.wave.client.events.ClientEvents; import org.waveprotocol.wave.client.events.NetworkStatusEvent; import org.waveprotocol.wave.client.events.NetworkStatusEventHandler; @@ -37,12 +43,14 @@ import org.waveprotocol.wave.model.schema.SchemaProvider; import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.Preconditions; +import org.waveprotocol.wave.model.util.StringMap; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.model.waveref.WaveRef; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -133,12 +141,20 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { /** List of living waves for the active session. */ - private Map<WaveId, WaveLoader> waveWrappers = CollectionUtils.newHashMap();; + private Map<WaveId, WaveLoader> waveRegistry = CollectionUtils.newHashMap(); + + /** List of living collab objects with waves as substrate */ + private Map<WaveId, ModelJS> objectRegistry = CollectionUtils.newHashMap(); + + /** List of editors created in the app */ + private Map<Element, TextEditor> editorRegistry = CollectionUtils.newHashMap(); /** A listener to global data/network/runtime events */ private SwellRT.Listener listener = null; private boolean useWebSocket = true; + + private boolean shouldOpenWebsocket = true; /** @@ -166,10 +182,10 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { protected void cleanChannelData() { // Destroy all waves - for (Entry<WaveId, WaveLoader> entry : waveWrappers.entrySet()) + for (Entry<WaveId, WaveLoader> entry : waveRegistry.entrySet()) entry.getValue().destroy(); - waveWrappers.clear(); + waveRegistry.clear(); websocket.disconnect(true); channel = null; } @@ -192,8 +208,10 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { else { // Clean everything before setting new session - cleanChannelData(); cleanSessionData(); + shouldOpenWebsocket = true; + if (websocket != null) + websocket.disconnect(true); JavaScriptResponse responseData = ServiceCallback.JavaScriptResponse.success(response.getText()); @@ -241,8 +259,9 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { else { // Clean everything before setting new session - cleanChannelData(); - cleanSessionData(); + shouldOpenWebsocket = true; + if (websocket != null) + websocket.disconnect(true); JavaScriptResponse responseData = ServiceCallback.JavaScriptResponse.success(response.getText()); @@ -276,10 +295,34 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { public void logout(final ServiceCallback callback) throws RequestException { - // Clean up session data - cleanChannelData(); + // + // Clean session, websocket ,objects and registries + // cleanSessionData(); - + + shouldOpenWebsocket = true; + + for (ModelJS co: objectRegistry.values()) + SwellRTUtils.deleteJsObject(co); + + objectRegistry.clear(); + + for (WaveLoader wave: waveRegistry.values()) + wave.destroy(); + + waveRegistry.clear(); + websocket.disconnect(false); + + for (TextEditor editor: editorRegistry.values()) + editor.cleanUp(); + + editorRegistry.clear(); + + + // + // Call server to close remote session + // + String url = baseServerUrl + "/swell/auth"; url = BrowserSession.addSessionToUrl(url); @@ -306,6 +349,183 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { }); } + + protected void openWebsocket(final Callback<Void, Void> callback) { + Preconditions.checkArgument(loggedInUser != null, "User not logged in. Can't open websocket."); + + // this is needed to atmosphere to work + setWebsocketAddress(baseServerUrl); + + // Use Model.MODEL_VERSION to get the client version + websocket = new WaveWebSocketClient(SwellRTUtils.toWebsocketAddress(baseServerUrl), "1.0"); + websocket.connect(new WaveSocketStartCallback() { + + @Override + public void onSuccess() { + channel = new RemoteViewServiceMultiplexer(websocket, loggedInUser.getAddress()); + shouldOpenWebsocket = false; + callback.onSuccess((Void) null); + } + + @Override + public void onFailure() { + callback.onFailure((Void) null); + + } + }); + + } + + private void openProc(WaveId waveId, final Callback<WaveLoader, String> callback) { + + final WaveLoader wave = + new WaveLoader(WaveRef.of(waveId), channel, TypeIdGenerator.get() + .getUnderlyingGenerator(), waveDomain, Collections.<ParticipantId> emptySet(), + loggedInUser, this); + + if (wave.isLoaded()) { + callback.onSuccess(wave); + } else { + + try { + + wave.load(new Command() { + @Override + public void execute() { + callback.onSuccess(wave); + } + }); + + } catch(RuntimeException e) { + callback.onFailure(e.getMessage()); + } + } + + + } + + /** + * Open or create a collaborative object. + * The underlying websocket will be openend if it is necessary. + * + * @param parameters field "id" for collab object id or void to create a new one + * @param callback + * @throws RequestException + */ + public void open(JavaScriptObject parameters, final ServiceCallback callback) throws RequestException { + + Preconditions.checkArgument(loggedInUser != null, "Login is not present"); + + JsoView p = JsoView.as(parameters); + + WaveId id = null; + if (p.getString("id") != null) { + id = WaveId.deserialise(p.getString("id")); + } else { + id = TypeIdGenerator.get().newWaveId(); + } + + final WaveId waveId = id; + + final Callback<WaveLoader, String> openProcCallback = new Callback<WaveLoader, String>() { + + @Override + public void onFailure(String reason) { + callback.onComplete(ServiceCallback.JavaScriptResponse.error("SERVICE_EXCEPTION", reason)); + } + + @Override + public void onSuccess(WaveLoader wave) { + + waveRegistry.put(waveId, wave); + + ModelJS cobJsFacade = null; + + Model cob = + Model.create(wave.getWave().getWave(), wave.getLocalDomain(), + wave.getLoggedInUser(), + wave.isNewWave(), wave.getIdGenerator()); + + cobJsFacade = ModelJS.create(cob); + cob.addListener(cobJsFacade); + + objectRegistry.put(waveId, cobJsFacade); + + callback.onComplete(ServiceCallback.JavaScriptResponse.success(cobJsFacade)); + } + + }; + + + if (shouldOpenWebsocket) { + openWebsocket(new Callback<Void, Void>() { + + + @Override + public void onFailure(Void reason) { + callback.onComplete(ServiceCallback.JavaScriptResponse.error("WEBSOCKET_ERROR", "Websocket can't be open")); + } + + @Override + public void onSuccess(Void result) { + openProc(waveId, openProcCallback); + } + }); + } else { + openProc(waveId, openProcCallback); + } + + } + + private native String extractWaveIdParameter(JavaScriptObject parameters) /*-{ + + if (parameters == null || parameters === undefined) + return null; + + if (typeof parameters == "string") + return parameters; + + if (parameters.id && typeof parameters.id == "function") + return parameters.id(); + + if (parameters.id && typeof parameters.id == "string") + return parameters.id; + + return null; + }-*/; + + + public void close(JavaScriptObject parameters, final ServiceCallback callback) throws RequestException { + String id = extractWaveIdParameter(parameters); + Preconditions.checkArgument(id != null, "Missing object or id"); + WaveId waveId = WaveId.deserialise(id); + Preconditions.checkArgument(waveRegistry.containsKey(waveId), "Object is not opened"); + + for (TextEditor e: editorRegistry.values()) + if (e.getWaveId().equals(waveId)) + e.cleanUp(); + + waveRegistry.remove(waveId).destroy(); + ModelJS co = objectRegistry.remove(waveId); + SwellRTUtils.deleteJsObject(co); + + } + + + public TextEditor createTextEditor(Element parent, StringMap<JsoWidgetController> widgetControllers, StringMap<JsoAnnotationController> annotationControllers) { + + TextEditor textEditor = TextEditor.create(parent, + widgetControllers, + annotationControllers); + + editorRegistry.put(parent, textEditor); + + return textEditor; + } + + // + // ******************************************************************************* + // /** * Performs a login against Wave's /auth servlet. This method doesn't start a @@ -424,7 +644,7 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { } - public void stopComms() { + private void stopComms() { websocket.disconnect(true); channel = null; } @@ -630,10 +850,10 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { if (loggedInUser == null) throw new SessionNotStartedException(); // Destroy all waves - for (Entry<WaveId, WaveLoader> entry : waveWrappers.entrySet()) + for (Entry<WaveId, WaveLoader> entry : waveRegistry.entrySet()) entry.getValue().destroy(); - waveWrappers.clear(); + waveRegistry.clear(); // Disconnect from Wave's websocket stopComms(); @@ -646,15 +866,15 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { protected WaveLoader getWaveWrapper(WaveId waveId, boolean isNew) { - if (!waveWrappers.containsKey(waveId) || waveWrappers.get(waveId).isClosed()) { + if (!waveRegistry.containsKey(waveId) || waveRegistry.get(waveId).isClosed()) { WaveLoader ww = new WaveLoader(WaveRef.of(waveId), channel, TypeIdGenerator.get() .getUnderlyingGenerator(), waveDomain, Collections.<ParticipantId> emptySet(), loggedInUser, this); - waveWrappers.put(waveId, ww); + waveRegistry.put(waveId, ww); } - return waveWrappers.get(waveId); + return waveRegistry.get(waveId); } @@ -748,21 +968,23 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { throw new InvalidIdException(); } - WaveLoader waveWrapper = waveWrappers.get(waveId); + WaveLoader waveWrapper = waveRegistry.get(waveId); if (waveWrapper == null) throw new InvalidIdException(); waveWrapper.destroy(); - waveWrappers.remove(waveId); + waveRegistry.remove(waveId); } - protected WaveDocuments<? extends InteractiveDocument> getDocumentRegistry(Model model) { - Preconditions.checkArgument(model != null, - "Unable to get document registry from a null data model"); - Preconditions.checkArgument(waveWrappers.containsKey(model.getWaveId()), - "Wave wrapper is not avaiable for the model"); - return waveWrappers.get(model.getWaveId()).getDocumentRegistry(); + + protected WaveDocuments<? extends InteractiveDocument> getDocumentRegistry(WaveId waveId) { + Preconditions.checkArgument(waveId != null, + "Can't get document registry from null object id"); + Preconditions.checkArgument(waveRegistry.containsKey(waveId), + "No object registered for the id. Can't get document registry."); + + return waveRegistry.get(waveId).getDocumentRegistry(); } @@ -808,7 +1030,8 @@ public class SwellRT implements EntryPoint, UnsavedDataListener { @Override public void onNetworkStatus(NetworkStatusEvent event) { - if (listener == null) return; + // Don't fire events if user is not logged in + if (listener == null || loggedInUser == null) return; switch (event.getStatus()) { case CONNECTED: http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/SwellRTUtils.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/SwellRTUtils.java b/wave/src/main/java/org/swellrt/api/SwellRTUtils.java index 11916f3..79d7654 100644 --- a/wave/src/main/java/org/swellrt/api/SwellRTUtils.java +++ b/wave/src/main/java/org/swellrt/api/SwellRTUtils.java @@ -224,4 +224,19 @@ public class SwellRTUtils { return encodeWaveRefUri(model.getWaveRef()); } + public static String toWebsocketAddress(String httpAddress) { + + String websocketAddress = httpAddress + "/"; + + if (websocketAddress.startsWith("http://")) + websocketAddress = websocketAddress.replace("http://", "ws://"); + else if (websocketAddress.startsWith("https://")) + websocketAddress = websocketAddress.replace("https://", "wss://"); + + return websocketAddress; + } + + public static native void deleteJsObject(JavaScriptObject o) /*-{ + delete o; + }-*/; } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/WaveClient.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/WaveClient.java b/wave/src/main/java/org/swellrt/api/WaveClient.java index 504a708..eaeaf4e 100644 --- a/wave/src/main/java/org/swellrt/api/WaveClient.java +++ b/wave/src/main/java/org/swellrt/api/WaveClient.java @@ -8,6 +8,7 @@ import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.JsonUtils; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.shared.UmbrellaException; import com.google.gwt.http.client.RequestException; import com.google.gwt.logging.impl.StackTracePrintStream; @@ -26,6 +27,7 @@ import org.waveprotocol.wave.client.doodad.widget.jso.JsoWidgetController; import org.waveprotocol.wave.client.wave.InteractiveDocument; import org.waveprotocol.wave.client.wave.WaveDocuments; import org.waveprotocol.wave.concurrencycontrol.common.UnsavedDataListener.UnsavedDataInfo; +import org.waveprotocol.wave.model.id.WaveId; /** * SwellRT client API entrypoint @@ -138,12 +140,12 @@ public class WaveClient implements SwellRT.Listener { coreClient.logout(onComplete); } - public void openChannel() { - + public void open(JavaScriptObject parameters, ServiceCallback onComplete) throws RequestException { + coreClient.open(parameters, onComplete); } - public void closeChannel() { - + public void close(JavaScriptObject parameters, ServiceCallback onComplete) throws RequestException { + coreClient.close(parameters, onComplete); } @@ -386,26 +388,23 @@ public class WaveClient implements SwellRT.Listener { public TextEditorJS getTextEditor(String elementId, JavaScriptObject widgets, JavaScriptObject annotations) { - Preconditions.checkArgument(Document.get().getElementById(elementId) != null, - "Element id is not provided"); + Element parent = Document.get().getElementById(elementId); + Preconditions.checkArgument(parent != null, "Can't hook editor in a null element"); + + TextEditor textEditor = coreClient.createTextEditor(parent, JsoWidgetController.fromJso(widgets), + JsoAnnotationController.fromJso(annotations)); - TextEditor textEditor = TextEditor.create(elementId, - JsoWidgetController.fromJso(widgets), - JsoAnnotationController.fromJso(annotations)); - return TextEditorJS.create(textEditor, this); + TextEditorJS textEditorJS = TextEditorJS.create(textEditor, this); + textEditor.initialize(textEditorJS); + + return textEditorJS; } /** - * Set TextEditor dependencies from current wave/model. In particular, set the - * document registry associated with TextType's Model before editing. - * - * @param text + * Get the document registry of a object's sustrate wave */ - public void configureTextEditor(TextEditor editor, TextType text) { - WaveDocuments<? extends InteractiveDocument> documentRegistry = - coreClient.getDocumentRegistry(text.getModel()); - - editor.setDocumentRegistry(documentRegistry); + public WaveDocuments<? extends InteractiveDocument> getDocumentRegistry(WaveId waveId) { + return coreClient.getDocumentRegistry(waveId); } /** @@ -622,7 +621,7 @@ public class WaveClient implements SwellRT.Listener { GWT.log("Exception: " + ((Throwable) e).getMessage()); } - String exceptionCode = "UNWRAPPED_EXCEPTION"; + String exceptionCode = "EXCEPTION"; if (e instanceof InvalidIdException) exceptionCode = "INVALID_ID_EXCEPTION"; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/js/WaveClientJS.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/js/WaveClientJS.java b/wave/src/main/java/org/swellrt/api/js/WaveClientJS.java index 6f5de51..c338901 100644 --- a/wave/src/main/java/org/swellrt/api/js/WaveClientJS.java +++ b/wave/src/main/java/org/swellrt/api/js/WaveClientJS.java @@ -238,6 +238,31 @@ public class WaveClientJS extends JavaScriptObject { } }, + + // + // Manage Collaborative Objects + // + + open: function(parameters, onComplete) { + + try { + return delega...@org.swellrt.api.WaveClient::open(Lcom/google/gwt/core/client/JavaScriptObject;Lorg/swellrt/api/ServiceCallback;)(parameters, onComplete) + } catch (e) { + throw @org.swellrt.api.WaveClient::wrapJavaException(Ljava/lang/Object;)(e); + } + + }, + + close: function(parameters, onComplete) { + + try { + return delega...@org.swellrt.api.WaveClient::close(Lcom/google/gwt/core/client/JavaScriptObject;Lorg/swellrt/api/ServiceCallback;)(parameters, onComplete) + } catch (e) { + throw @org.swellrt.api.WaveClient::wrapJavaException(Ljava/lang/Object;)(e); + } + + }, + startSession: function(url, user, password, onSuccess, onFailure) { http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJS.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJS.java b/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJS.java index 318b5de..b4bafe9 100644 --- a/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJS.java +++ b/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJS.java @@ -4,6 +4,11 @@ import com.google.gwt.core.client.JavaScriptObject; import org.swellrt.api.WaveClient; import org.swellrt.client.editor.TextEditor; +import org.swellrt.client.editor.TextEditor.Configurator; +import org.swellrt.model.generic.TextType; +import org.waveprotocol.wave.client.wave.InteractiveDocument; +import org.waveprotocol.wave.client.wave.WaveDocuments; +import org.waveprotocol.wave.model.id.WaveId; public class TextEditorJS extends JavaScriptObject { @@ -18,12 +23,9 @@ public class TextEditorJS extends JavaScriptObject { }, edit: function(text) { - // TODO check for cleanUp(); - var _text = text.getDelegate(); - clie...@org.swellrt.api.WaveClient::configureTextEditor(Lorg/swellrt/client/editor/TextEditor;Lorg/swellrt/model/generic/TextType;)(delegate, _text); - delegate.@org.swellrt.client.editor.TextEditor::edit(Lorg/swellrt/model/generic/TextType;)(_text); + @org.swellrt.api.js.editor.TextEditorJS::edit(Lorg/swellrt/client/editor/TextEditor;Lorg/swellrt/model/generic/TextType;Lorg/swellrt/api/WaveClient;)(delegate, _text, client); }, cleanUp: function() { @@ -48,42 +50,45 @@ public class TextEditorJS extends JavaScriptObject { return delegate.@org.swellrt.client.editor.TextEditor::getWidget(Lcom/google/gwt/dom/client/Element;)(element); }, - setAnnotation: function(name, value) { - delegate.@org.swellrt.client.editor.TextEditor::setAnnotation(Ljava/lang/String;Ljava/lang/String;)(name, value); + setAnnotation: function(key, value) { + return delegate.@org.swellrt.client.editor.TextEditor::setAnnotation(Ljava/lang/String;Ljava/lang/String;)(key, value); + }, + + setAnnotationInRange: function(range, key, value) { + return delegate.@org.swellrt.client.editor.TextEditor::setAnnotationInRange(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoRange;Ljava/lang/String;Ljava/lang/String;)(range, key, value); }, getSelection: function() { return delegate.@org.swellrt.client.editor.TextEditor::getSelection()(); }, - getAnnotationSet: function(name) { - return delegate.@org.swellrt.client.editor.TextEditor::getAnnotationSet(Ljava/lang/String;)(name); + getAnnotationSet: function(key) { + return delegate.@org.swellrt.client.editor.TextEditor::getAnnotationSet(Ljava/lang/String;)(key); }, - getAnnotation: function(range, key) { - return delegate.@org.swellrt.client.editor.TextEditor::getAnnotation(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoEditorRange;Ljava/lang/String;)(range, key); + getAnnotationInRange: function(range, key) { + return delegate.@org.swellrt.client.editor.TextEditor::getAnnotationInRange(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoRange;Ljava/lang/String;)(range, key); }, - - clearAnnotation: function(a, b) { - if (a && b && typeof a == 'string' && typeof b == 'object') { - - // a -> annotation key - // b -> editor range - delegate.@org.swellrt.client.editor.TextEditor::clearAnnotation(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoEditorRange;Ljava/lang/String;)(b, a); - - } else if (a && typeof a == 'string') { - - // a -> annotation key - delegate.@org.swellrt.client.editor.TextEditor::clearAnnotation(Ljava/lang/String;)(a); - - } else if (a && typeof a == 'object') { - - // a -> editor range - delegate.@org.swellrt.client.editor.TextEditor::clearAnnotation(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoEditorRange;)(a); - } + clearAnnotation: function(keyPrefix) { + delegate.@org.swellrt.client.editor.TextEditor::clearAnnotation(Ljava/lang/String;)(keyPrefix); + }, + clearAnnotationInRange: function(range, keyPrefix) { + delegate.@org.swellrt.client.editor.TextEditor::clearAnnotationInRange(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoRange;Ljava/lang/String;)(range, keyPrefix); }, + + setText: function(range, text) { + return delegate.@org.swellrt.client.editor.TextEditor::setText(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoRange;Ljava/lang/String;)(range, text); + }, + + getText: function(range) { + return delegate.@org.swellrt.client.editor.TextEditor::getText(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoRange;)(range); + }, + + deleteText: function(range) { + delegate.@org.swellrt.client.editor.TextEditor::deleteText(Lorg/waveprotocol/wave/client/doodad/annotation/jso/JsoRange;)(range); + } }; @@ -95,5 +100,17 @@ public class TextEditorJS extends JavaScriptObject { protected TextEditorJS() { } + + private static void edit(TextEditor editor, TextType text, WaveClient client) { + final WaveId waveId = text.getModel().getWaveId(); + editor.edit(text, new Configurator() { + + @Override + public WaveDocuments<? extends InteractiveDocument> getDocumentRegistry() { + return client.getDocumentRegistry(waveId); + } + + }); + } } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJSListener.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJSListener.java b/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJSListener.java index abd05b1..45f8d15 100644 --- a/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJSListener.java +++ b/wave/src/main/java/org/swellrt/api/js/editor/TextEditorJSListener.java @@ -3,7 +3,7 @@ package org.swellrt.api.js.editor; import com.google.gwt.core.client.JavaScriptObject; import org.swellrt.client.editor.TextEditorListener; -import org.waveprotocol.wave.client.doodad.annotation.jso.JsoEditorRange; +import org.waveprotocol.wave.client.doodad.annotation.jso.JsoRange; import org.waveprotocol.wave.client.gadget.StateMap; public class TextEditorJSListener extends JavaScriptObject implements TextEditorListener { @@ -17,7 +17,7 @@ public class TextEditorJSListener extends JavaScriptObject implements TextEditor }-*/; @Override - public final native void onSelectionChange(JsoEditorRange editorRange) /*-{ + public final native void onSelectionChange(JsoRange editorRange) /*-{ if (this.executor !== undefined) this.executor(editorRange); }-*/; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/client/editor/TextEditor.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/client/editor/TextEditor.java b/wave/src/main/java/org/swellrt/client/editor/TextEditor.java index 017318f..1943eeb 100644 --- a/wave/src/main/java/org/swellrt/client/editor/TextEditor.java +++ b/wave/src/main/java/org/swellrt/client/editor/TextEditor.java @@ -6,22 +6,25 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.swellrt.api.BrowserSession; import org.swellrt.client.editor.TextEditorDefinitions.ParagraphAnnotation; import org.swellrt.model.generic.TextType; import org.swellrt.model.shared.ModelUtils; +import org.waveprotocol.wave.client.account.impl.ProfileManagerImpl; import org.waveprotocol.wave.client.common.util.JsoStringMap; import org.waveprotocol.wave.client.common.util.JsoStringSet; -import org.waveprotocol.wave.client.common.util.JsoView; import org.waveprotocol.wave.client.common.util.LogicalPanel; import org.waveprotocol.wave.client.doodad.annotation.AnnotationHandler; import org.waveprotocol.wave.client.doodad.annotation.jso.JsoAnnotation; import org.waveprotocol.wave.client.doodad.annotation.jso.JsoAnnotationController; -import org.waveprotocol.wave.client.doodad.annotation.jso.JsoEditorRange; +import org.waveprotocol.wave.client.doodad.annotation.jso.JsoRange; import org.waveprotocol.wave.client.doodad.annotation.jso.JsoParagraphAnnotation; import org.waveprotocol.wave.client.doodad.diff.DiffAnnotationHandler; import org.waveprotocol.wave.client.doodad.diff.DiffDeleteRenderer; import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler; import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler.LinkAttributeAugmenter; +import org.waveprotocol.wave.client.doodad.selection.SelectionAnnotationHandler; +import org.waveprotocol.wave.client.doodad.selection.SelectionExtractor; import org.waveprotocol.wave.client.doodad.widget.WidgetDoodad; import org.waveprotocol.wave.client.doodad.widget.jso.JsoWidget; import org.waveprotocol.wave.client.doodad.widget.jso.JsoWidgetController; @@ -40,13 +43,14 @@ import org.waveprotocol.wave.client.editor.content.misc.AnnotationPaint; import org.waveprotocol.wave.client.editor.content.misc.AnnotationPaint.EventHandler; import org.waveprotocol.wave.client.editor.content.misc.AnnotationPaint.MutationHandler; import org.waveprotocol.wave.client.editor.content.misc.StyleAnnotationHandler; -import org.waveprotocol.wave.client.editor.content.paragraph.Line; import org.waveprotocol.wave.client.editor.content.paragraph.LineRendering; import org.waveprotocol.wave.client.editor.content.paragraph.Paragraph; import org.waveprotocol.wave.client.editor.content.paragraph.Paragraph.LineStyle; import org.waveprotocol.wave.client.editor.content.paragraph.ParagraphBehaviour; import org.waveprotocol.wave.client.editor.keys.KeyBindingRegistry; import org.waveprotocol.wave.client.editor.util.EditorAnnotationUtil; +import org.waveprotocol.wave.client.scheduler.SchedulerInstance; +import org.waveprotocol.wave.client.scheduler.TimerService; import org.waveprotocol.wave.client.wave.InteractiveDocument; import org.waveprotocol.wave.client.wave.RegistriesHolder; import org.waveprotocol.wave.client.wave.WaveDocuments; @@ -62,15 +66,17 @@ import org.waveprotocol.wave.model.document.util.DocHelper; import org.waveprotocol.wave.model.document.util.LineContainers; import org.waveprotocol.wave.model.document.util.Point; import org.waveprotocol.wave.model.document.util.Range; +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.Preconditions; import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV; import org.waveprotocol.wave.model.util.ReadableStringSet.Proc; import org.waveprotocol.wave.model.util.StringMap; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; import com.google.gwt.user.client.Event; /** @@ -82,6 +88,15 @@ import com.google.gwt.user.client.Event; */ public class TextEditor implements EditorUpdateListener { + public interface Configurator { + + /** + * The gateway to get UI-versions of Blips. Registry is GWT related, so must + * be injected in the Editor by the JS API. Model's classes have to ignore it. + */ + public WaveDocuments<? extends InteractiveDocument> getDocumentRegistry(); + + } public static class JSLogSink extends LogSink { @@ -104,7 +119,7 @@ public class TextEditor implements EditorUpdateListener { } - + public static class CustomLogger extends AbstractLogger { public CustomLogger(LogSink sink) { @@ -151,17 +166,18 @@ public class TextEditor implements EditorUpdateListener { private LogicalPanel.Impl docPanel; private ContentDocument doc; - - /** - * The gateway to get UI-versions of Blips. Registry is GWT related, so must - * be injected in the Editor by the JS API. Model's classes have to ignore it. - */ - private WaveDocuments<? extends InteractiveDocument> documentRegistry; - + private SelectionExtractor selectionExtractor; + + /** The actual implementation of the editor */ private Editor editor; + /** Listener for editor events */ private TextEditorListener listener; + + private WaveId containerWaveId; + private boolean shouldFireEvents = false; + /** * Registry of JavaScript controllers for widgets */ @@ -183,14 +199,12 @@ public class TextEditor implements EditorUpdateListener { } - public static TextEditor create(String containerElementId, StringMap<JsoWidgetController> widgetControllers, StringMap<JsoAnnotationController> annotationControllers) { - Element e = Document.get().getElementById(containerElementId); - Preconditions.checkNotNull(e, "Editor's parent element doesn't exist"); - - TextEditor editor = new TextEditor(e, widgetControllers, annotationControllers); - editor.registerDoodads(); + public static TextEditor create(Element parent, StringMap<JsoWidgetController> widgetControllers, StringMap<JsoAnnotationController> annotationControllers) { + Preconditions.checkNotNull(parent, "Element to hook editor canvas doesn't exist"); + TextEditor editor = new TextEditor(parent, widgetControllers, annotationControllers); return editor; } + protected TextEditor(final Element containerElement, StringMap<JsoWidgetController> widgetControllers, StringMap<JsoAnnotationController> annotationControllers) { this.editorPanel = new LogicalPanel.Impl() { @@ -202,26 +216,223 @@ public class TextEditor implements EditorUpdateListener { this.annotationRegistry = annotationControllers; } - - /** - * Inject document registry which manages UI versions of blips. Registry must - * be only injected by the JS API. - * - * @param documentRegistry + * This is a nasty method to pass a reference of editor's js facade + * to widget and annotation controllers. + * + * So there is a circular reference between TextEditor and TextEditorJS. + * + * @param editorJsFacade editor's pure JavaScript facade */ - public void setDocumentRegistry(WaveDocuments<? extends InteractiveDocument> documentRegistry) { - this.documentRegistry = documentRegistry; + public void initialize(JavaScriptObject editorJsFacade) { + registerDoodads(editorJsFacade); + registerAnnotations(editorJsFacade); + } + + + protected void registerDoodads(final JavaScriptObject editorJsFacade) { + + + // TOPLEVEL_CONTAINER_TAGNAME + LineRendering.registerContainer(TOPLEVEL_CONTAINER_TAGNAME, + registries.getElementHandlerRegistry()); + + StyleAnnotationHandler.register(registries); + + // Listen for Diff annotations to paint new content or to insert a + // delete-content tag + // to be rendered by the DiffDeleteRendere + DiffAnnotationHandler.register(registries.getAnnotationHandlerRegistry(), + registries.getPaintRegistry()); + DiffDeleteRenderer.register(registries.getElementHandlerRegistry()); + + // + // Reuse existing link annotation handler, but also support external + // controller to get notified on mutation or input events + // + LinkAnnotationHandler.register(registries, new LinkAttributeAugmenter() { + @Override + public Map<String, String> augment(Map<String, Object> annotations, boolean isEditing, + Map<String, String> current) { + return current; + } + }); + + // Set reference to js editor's facade to controllers. + widgetRegistry.each(new ProcV<JsoWidgetController>() { + + @Override + public void apply(String key, JsoWidgetController value) { + value.setEditorJsFacade(editorJsFacade); + } + + }); + + WidgetDoodad.register(registries.getElementHandlerRegistry(), widgetRegistry); + + } + + protected void registerAnnotations(final JavaScriptObject editorJsFacade) { + + // Configure annotations. + // For shake of encapsulation, we don't expose native JS type + // JsoAnntationController to inner editor classes. + + annotationRegistry.each(new ProcV<JsoAnnotationController>() { + + @Override + public void apply(String key, JsoAnnotationController controller) { + + // sets the circular reference to the editor's js facade + controller.setEditorJsFacade(editorJsFacade); + + if (key.contains(AnnotationConstants.LINK_PREFIX)) { + + // Link annotations are actually registered in registerDoodads() method + // here only handlers are registered + + AnnotationPaint.registerEventHandler(AnnotationConstants.LINK_PREFIX, new EventHandler() { + + @Override + public void onEvent(ContentElement node, Event event) { + if (controller != null && shouldFireEvents) + controller.onEvent(JsoRange.Builder.create(node.getMutableDoc()).range(node) + .annotation(AnnotationConstants.LINK_PREFIX, null).build(), event); + } + }); + + AnnotationPaint.setMutationHandler(AnnotationConstants.LINK_PREFIX, + new MutationHandler() { + + @Override + public void onMutation(ContentElement node) { + if (controller != null && shouldFireEvents) + controller.onChange(JsoRange.Builder.create(node.getMutableDoc()) + .range(node).annotation(AnnotationConstants.LINK_PREFIX, null).build()); + } + + @Override + public void onAdded(ContentElement node) { + if (controller != null && shouldFireEvents) + controller.onAdd(JsoRange.Builder.create(node.getMutableDoc()) + .range(node).annotation(AnnotationConstants.LINK_PREFIX, null).build()); + } + + @Override + public void onRemoved(ContentElement node) { + if (controller != null && shouldFireEvents) + controller.onRemove(JsoRange.Builder.create(node.getMutableDoc()) + .range(node).annotation(AnnotationConstants.LINK_PREFIX, null).build()); + } + + }); + + } else if (TextEditorDefinitions.isParagraphAnnotation(key)) { + + if (key.equals(ParagraphAnnotation.HEADER.toString())) { + + Paragraph.registerEventHandler(ParagraphBehaviour.HEADING, + new Paragraph.EventHandler() { + + @Override + public void onEvent(ContentElement node, Event event) { + if (controller != null && shouldFireEvents) + controller.onEvent( + JsoRange.Builder.create(node.getMutableDoc()).range(node) + .annotation(key, node.getAttribute(Paragraph.SUBTYPE_ATTR)).build(), + event); + } + }); + + Paragraph.registerMutationHandler(ParagraphBehaviour.HEADING, + new Paragraph.MutationHandler() { + + @Override + public void onMutation(ContentElement node) { + if (controller != null && shouldFireEvents) + controller + .onChange(JsoRange.Builder.create(node.getMutableDoc()).range(node) + .annotation(key, node.getAttribute(Paragraph.SUBTYPE_ATTR)).build()); + } + + @Override + public void onAdded(ContentElement node) { + if (controller != null && shouldFireEvents) + controller + .onAdd(JsoRange.Builder.create(node.getMutableDoc()).range(node) + .annotation(key, node.getAttribute(Paragraph.SUBTYPE_ATTR)).build()); + } + + @Override + public void onRemoved(ContentElement node) { + if (controller != null && shouldFireEvents) + controller + .onRemove(JsoRange.Builder.create(node.getMutableDoc()).range(node) + .annotation(key, node.getAttribute(Paragraph.SUBTYPE_ATTR)).build()); + + } + + + + + }); + + } + + } else if (TextEditorDefinitions.isStyleAnnotation(key)) { - public void edit(TextType text) { - Preconditions.checkNotNull(text, "Text object is null"); - Preconditions.checkNotNull(documentRegistry, "Document registry hasn't been initialized"); + // Nothing to do with style annotations + + } else { + + // Register custom annotations + AnnotationHandler.register(registries, key, controller, new AnnotationHandler.Activator() { + @Override + public boolean shouldFireEvent() { + return shouldFireEvents; + } + }); + + } + + } + + }); + + } + + + public void setListener(TextEditorListener listener) { + this.listener = listener; + } + + public WaveId getWaveId() { + return containerWaveId; + } + + + /** + * Start an editing session. + * + * @param text the TextType instance to edit + * @param configurator editor dependencies not related with text + */ + public void edit(TextType text, Configurator configurator) { + Preconditions.checkNotNull(text, "Text object can't be null"); + Preconditions.checkNotNull(configurator, "Editor configurator can't be null"); + + shouldFireEvents = false; if (!isClean()) cleanUp(); - doc = getContentDocument(text); + // Place here selection extractor to ensure session id and user id are refreshed + SelectionAnnotationHandler.register(registries, BrowserSession.getWindowSessionId(), new ProfileManagerImpl()); + TimerService clock = SchedulerInstance.getLowPriorityTimer(); + selectionExtractor = new SelectionExtractor(clock, BrowserSession.getUserAddress(), BrowserSession.getWindowSessionId()); + + doc = getContentDocument(text, configurator.getDocumentRegistry()); Preconditions.checkArgument(doc != null, "Can't edit an unattached TextType"); doc.setRegistries(registries); @@ -257,22 +468,22 @@ public class TextEditor implements EditorUpdateListener { editor.setEditing(true); editor.focus(true); - + + selectionExtractor.start(editor); + + containerWaveId = text.getModel().getWaveId(); + + shouldFireEvents = true; } - /* ---------------------------------------------------------- */ - - - - private ContentDocument getContentDocument(TextType text) { + private ContentDocument getContentDocument(TextType text, WaveDocuments<? extends InteractiveDocument> documentRegistry) { Preconditions.checkArgument(text != null, "Unable to get ContentDocument from null TextType"); Preconditions.checkArgument(documentRegistry != null, "Unable to get ContentDocument from null DocumentRegistry"); - return documentRegistry.getBlipDocument(ModelUtils.serialize(text.getModel().getWaveletId()), text.getDocumentId()).getDocument(); } @@ -286,10 +497,13 @@ public class TextEditor implements EditorUpdateListener { public void cleanUp() { if (editor != null) { + if (selectionExtractor != null) + selectionExtractor.start(editor); editor.removeUpdateListener(this); editor.removeContentAndUnrender(); editor.reset(); doc = null; + containerWaveId = null; } } @@ -319,125 +533,16 @@ public class TextEditor implements EditorUpdateListener { return JsoWidget.create(w.getImplNodelet(), w); } - - /** * Get the widget associated with the DOM element * * @param domElement the widget element or a descendant */ public JsoWidget getWidget(Element domElement) { - return WidgetDoodad.getWidget(editor.getDocument(), domElement); - } - - protected void registerDoodads() { - - - // TOPLEVEL_CONTAINER_TAGNAME - LineRendering.registerContainer(TOPLEVEL_CONTAINER_TAGNAME, - registries.getElementHandlerRegistry()); - - StyleAnnotationHandler.register(registries); - - // Listen for Diff annotations to paint new content or to insert a - // delete-content tag - // to be rendered by the DiffDeleteRendere - DiffAnnotationHandler.register(registries.getAnnotationHandlerRegistry(), - registries.getPaintRegistry()); - DiffDeleteRenderer.register(registries.getElementHandlerRegistry()); - - // - // Reuse existing link annotation handler, but also support external - // controller to - // get notified on mutation or input events - // - LinkAnnotationHandler.register(registries, new LinkAttributeAugmenter() { - @Override - public Map<String, String> augment(Map<String, Object> annotations, boolean isEditing, - Map<String, String> current) { - return current; - } - }); - - WidgetDoodad.register(registries.getElementHandlerRegistry(), widgetRegistry); - - - // Configure annotations. - // For shake of encapsulation, we don't expose native JS type JsoAnntationController to inner editor classes. - - annotationRegistry.each(new ProcV<JsoAnnotationController>() { - - @Override - public void apply(String key, JsoAnnotationController controller) { - - - if (key.contains(AnnotationConstants.LINK_PREFIX)) { - - AnnotationPaint.registerEventHandler(AnnotationConstants.LINK_PREFIX, new EventHandler() { - - @Override - public void onEvent(ContentElement node, Event event) { - if (controller != null) - controller.onEvent(JsoEditorRange.Builder.create(node.getMutableDoc()).range(node).annotation(AnnotationConstants.LINK_PREFIX, null).build(), event); - } - }); - - AnnotationPaint.setMutationHandler(AnnotationConstants.LINK_PREFIX, new MutationHandler() { - - @Override - public void onMutation(ContentElement node) { - if (controller != null) - controller.onChange(JsoEditorRange.Builder.create(node.getMutableDoc()).range(node).annotation(AnnotationConstants.LINK_PREFIX, null).build()); - } - }); - - } else if (TextEditorDefinitions.isParagraphAnnotation(key)) { - - if (key.equals(ParagraphAnnotation.HEADER.toString())) { - - Paragraph.registerEventHandler(ParagraphBehaviour.HEADING, new Paragraph.EventHandler() { - - @Override - public void onEvent(ContentElement node, Event event) { - if (controller != null) - controller.onEvent(JsoEditorRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(Paragraph.SUBTYPE_ATTR)).build(), event); - } - }); - - Paragraph.registerMutationHandler(ParagraphBehaviour.HEADING, new Paragraph.MutationHandler() { - - @Override - public void onMutation(ContentElement node) { - if (controller != null) - controller.onChange(JsoEditorRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(Paragraph.SUBTYPE_ATTR)).build()); - } - }); - - } - - } else if (TextEditorDefinitions.isStyleAnnotation(key)) { - - // Nothing to do with style annotations - - } else { - - // Register custom annotations - AnnotationHandler.register(registries, key, controller); - - } - - } - - }); - - + return WidgetDoodad.getWidget(editor.getDocument(), domElement); } - - - public void setListener(TextEditorListener listener) { - this.listener = listener; - } + protected boolean isValidAnnotationKey(String key) { return (TextEditorDefinitions.isParagraphAnnotation(key) || @@ -454,7 +559,7 @@ public class TextEditor implements EditorUpdateListener { * * @return a native JS object having a property for each annotation. */ - protected JsoStringMap<String> getAnnotationsOverRange(final Range range) { + protected JsoStringMap<String> getAllAnnotationsInRange(final Range range) { // Map to contain the current state of each annotation @@ -611,50 +716,37 @@ public class TextEditor implements EditorUpdateListener { return ranges; } - - @Override - public void onUpdate(final EditorUpdateEvent event) { - if (event.selectionLocationChanged()) { - Range range = editor.getSelectionHelper().getOrderedSelectionRange(); - JsoEditorRange.Builder editorRangeBuilder = JsoEditorRange.Builder.create(editor.getDocument()); - if (range != null) { - editorRangeBuilder.range(range) - .annotations(getAnnotationsOverRange(range)).build(); - } else { - editorRangeBuilder.annotations(getAnnotationsByDefault()); + + public JsoAnnotation getAnnotationInRange(JsoRange editorRange, String key) { + Preconditions.checkNotNull(editorRange, "Range can't be null"); + Preconditions.checkArgument(isValidAnnotationKey(key), "Invalid annotation key"); + + if (TextEditorDefinitions.isParagraphAnnotation(key)) { + + for(Entry<String, LineStyle> ls: ParagraphAnnotation.fromString(key).getLineStyles().entrySet()) { + + if (Paragraph.appliesEntirely(editor.getDocument(), editorRange.start(), editorRange.end(), ls.getValue())) { + Point<ContentNode> point = editor.getDocument().locate(editorRange.start()); + ContentNode lineNode = LineContainers.getRelatedLineElement(editor.getDocument(), point); + return JsoParagraphAnnotation.create(editor.getDocument(), editorRange.start(), editorRange.end(), key, ls.getKey(), lineNode.asElement().getImplNodelet()); } - - if (listener != null) - listener.onSelectionChange(editorRangeBuilder.build()); - + } + + } else { + + Range range = EditorAnnotationUtil.getEncompassingAnnotationRange(editor.getDocument(), key, editorRange.start()); + if (range == null) + return null; + + return JsoAnnotation.create(editor, range, key); } - } + + return null; + + + } - protected void getParagraphAnnotations(int start, int end, JsoStringMap<String> annotations) { - - TextEditorDefinitions.PARAGRAPH_ANNOTATIONS.each(new Proc() { - - @Override - public void apply(String annotationName) { - - Collection<Entry<String, LineStyle>> styles = - TextEditorDefinitions.ParagraphAnnotation.fromString(annotationName).values - .entrySet(); - - String annotationValue = null; - for (Entry<String, LineStyle> s : styles) { - if (Paragraph.appliesEntirely(editor.getDocument(), start, end, - s.getValue())) { - annotationValue = s.getKey(); - break; - } - } - annotations.put(annotationName, annotationValue); - } - }); - - } /** @@ -689,19 +781,20 @@ public class TextEditor implements EditorUpdateListener { * @param key * @param value */ - public void setAnnotation(String key, String value) { + public JsoAnnotation setAnnotation(String key, String value) { Preconditions.checkArgument(isValidAnnotationKey(key), "Unknown annotation key"); - + final Range range = editor.getSelectionHelper().getOrderedSelectionRange(); + if (range == null) return null; + if (TextEditorDefinitions.isParagraphAnnotation(key)) { - final Range range = editor.getSelectionHelper().getOrderedSelectionRange(); - if (range != null) { - setParagraphAnnotation(key, value, range.getStart(), range.getEnd()); - } + setParagraphAnnotation(key, value, range.getStart(), range.getEnd()); } else { EditorAnnotationUtil.setAnnotationOverSelection(editor, key, value); } - + + + return JsoAnnotation.create(editor, range, key); } /** @@ -711,7 +804,7 @@ public class TextEditor implements EditorUpdateListener { * @param key * @param value */ - public void setAnnotationOverRange(JsoEditorRange range, String key, String value) { + public JsoAnnotation setAnnotationInRange(JsoRange range, String key, String value) { Preconditions.checkArgument(isValidAnnotationKey(key), "Unknown annotation key"); Preconditions.checkArgument(range != null && range.start() <= range.end(), "Invalid range object"); @@ -721,110 +814,141 @@ public class TextEditor implements EditorUpdateListener { EditorAnnotationUtil.setAnnotationOverRange(editor.getDocument(), editor.getCaretAnnotations(), key, value, range.start(), range.end()); } + return JsoAnnotation.create(editor, new Range(range.start(), range.end()), key); } /** * Clear the annotation in the current selection or caret position. * - * @param annotationName + * @param keyPrefix */ - public void clearAnnotation(String annotationName) { - Preconditions.checkNotNull(annotationName, "Annotation key can't be null"); - - if (TextEditorDefinitions.isParagraphAnnotation(annotationName)) { - final Range range = editor.getSelectionHelper().getOrderedSelectionRange(); - setParagraphAnnotation(annotationName, null, range.getStart(), range.getEnd()); - } else { - EditorAnnotationUtil.clearAnnotationsOverSelection(editor, annotationName); - } + public void clearAnnotation(String keyPrefix) { + Range r = editor.getSelectionHelper().getOrderedSelectionRange(); + if (r == null) { + return; + } + clearAnnotationInRange(JsoRange.Builder.create(editor.getDocument()).range(r).build(), keyPrefix); } + /** - * Clear an annotation in the caret or document range. + * Clear all annotations in the range starting with the prefix * - * @param editorRange the range in the doc - * @param key annotation key + * @param range the range in the doc */ - public void clearAnnotation(JsoEditorRange editorRange, String key) { - Preconditions.checkNotNull(editorRange, "Range can't be null"); - Preconditions.checkArgument(isValidAnnotationKey(key), "Invalid annotation key"); + public void clearAnnotationInRange(JsoRange range, String keyPrefix) { + Preconditions.checkNotNull(keyPrefix, "Annotation key or prefix can't be null"); + Preconditions.checkNotNull(range, "Range can't be null"); + + StringMap<String> annotations = range.getAnnotations(); + if (annotations.isEmpty()) { + annotations = getAllAnnotationsInRange(new Range(range.start(), range.end())); + } + + List<String> textAnnotations = new ArrayList<String>(); - if (TextEditorDefinitions.isParagraphAnnotation(key)) { - setParagraphAnnotation(key, null, editorRange.start(), editorRange.end()); - } else { - EditorAnnotationUtil.clearAnnotationsOverRange(editor.getDocument(), editor.getCaretAnnotations(), new String[] { key }, editorRange.start(), editorRange.end()); + annotations.each(new ProcV<String>() { + + @Override + public void apply(String key, String value) { + if (key.startsWith(keyPrefix)) { + if (TextEditorDefinitions.isParagraphAnnotation(key)) + setParagraphAnnotation(key, null, range.start(), range.end()); + else + textAnnotations.add(key); + } } - + + }); + + + if (!textAnnotations.isEmpty()) { + String[] anotArray = (String[]) textAnnotations.toArray(new String[]{}); + EditorAnnotationUtil.clearAnnotationsOverRange(editor.getDocument(), editor.getCaretAnnotations(), anotArray, range.start(), range.end()); + } + } + + /** + * Insert o replace text over range in the document + * + * @param range + * @param text + */ + public JsoRange setText(JsoRange range, String text) { + Preconditions.checkNotNull(range, "Range can't be null"); + CMutableDocument doc = editor.getDocument(); + if (range.start() == range.end()) { + doc.insertText(range.start(), text); + } else if (range.start() > 0 && range.start() < range.end() && range.end() < editor.getDocument().size()) { + doc.beginMutationGroup(); + doc.deleteRange(range.start(), range.end()); + doc.insertText(range.start(), text); + doc.endMutationGroup(); + } else { + Preconditions.checkArgument(false, "Range is not correct"); + } + + + return JsoRange.Builder.create(doc).range(range.start(), range.start()+text.length(), text.length()).build(); + } /** - * Clear all annotations in the range + * Delete text in a range * - * @param editorRange the range in the doc + * @param range + * @return */ - public void clearAnnotation(JsoEditorRange editorRange) { - Preconditions.checkNotNull(editorRange, "Range can't be null"); - - // Separate paragraph annotations - List<String> textAnnotations = new ArrayList<String>(); - for (String s: editorRange.getAnnotationKeys()) { - if (TextEditorDefinitions.isParagraphAnnotation(s)) - setParagraphAnnotation(s, null, editorRange.start(), editorRange.end()); - else - textAnnotations.add(s); - } - - if (!textAnnotations.isEmpty()) - EditorAnnotationUtil.clearAnnotationsOverRange(editor.getDocument(), editor.getCaretAnnotations(), (String[]) textAnnotations.toArray(), editorRange.start(), editorRange.end()); + public void deleteText(JsoRange range) { + Preconditions.checkNotNull(range, "Range can't be null"); + CMutableDocument doc = editor.getDocument(); + doc.deleteRange(range.start(), range.end()); } - - public JsoAnnotation getAnnotation(JsoEditorRange editorRange, String key) { - Preconditions.checkNotNull(editorRange, "Range can't be null"); - Preconditions.checkArgument(isValidAnnotationKey(key), "Invalid annotation key"); - - if (TextEditorDefinitions.isParagraphAnnotation(key)) { - - for(Entry<String, LineStyle> ls: ParagraphAnnotation.fromString(key).getLineStyles().entrySet()) { - - if (Paragraph.appliesEntirely(editor.getDocument(), editorRange.start(), editorRange.end(), ls.getValue())) { - Point<ContentNode> point = editor.getDocument().locate(editorRange.start()); - ContentNode lineNode = LineContainers.getRelatedLineElement(editor.getDocument(), point); - return JsoParagraphAnnotation.create(editor.getDocument(), editorRange.start(), editorRange.end(), key, ls.getKey(), lineNode.asElement().getImplNodelet()); - } - - } - - } else { - - Range range = EditorAnnotationUtil.getEncompassingAnnotationRange(editor.getDocument(), key, editorRange.start()); - if (range == null) - return null; - - return JsoAnnotation.create(editor, range, key); - } - - return null; - - - } + public String getText(JsoRange range) { + Preconditions.checkNotNull(range, "Range can't be null"); + CMutableDocument doc = editor.getDocument(); + Preconditions.checkArgument(range.start() >= 0 && range.start() <= range.end() && range.end() < doc.size(), "Range is not correct"); + return DocHelper.getText(doc, range.start(), range.end()); + } + /** - * Gets the current selection. See {@link JsoEditorRange} for methods to + * Gets the current selection. See {@link JsoRange} for methods to * update the document's selection. * * Includes annotations. * * @return */ - public JsoEditorRange getSelection() { + public JsoRange getSelection() { Range r = editor.getSelectionHelper().getOrderedSelectionRange(); if (r == null) { return null; - } - return JsoEditorRange.Builder.create(editor.getDocument()).range(r) - .annotations(getAnnotationsOverRange(r)).build(); - + } + return JsoRange.Builder.create(editor.getDocument()).range(r) + .annotations(getAllAnnotationsInRange(r)).build(); } + + + @Override + public void onUpdate(final EditorUpdateEvent event) { + if (event.selectionLocationChanged()) { + Range range = editor.getSelectionHelper().getOrderedSelectionRange(); + JsoRange.Builder editorRangeBuilder = JsoRange.Builder.create(editor.getDocument()); + if (range != null) { + editorRangeBuilder.range(range) + .annotations(getAllAnnotationsInRange(range)).build(); + } else { + editorRangeBuilder.annotations(getAnnotationsByDefault()); + } + + + if (listener != null) + listener.onSelectionChange(editorRangeBuilder.build()); + + } + } + } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/swellrt/client/editor/TextEditorListener.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/swellrt/client/editor/TextEditorListener.java b/wave/src/main/java/org/swellrt/client/editor/TextEditorListener.java index 7835434..a3e7b9d 100644 --- a/wave/src/main/java/org/swellrt/client/editor/TextEditorListener.java +++ b/wave/src/main/java/org/swellrt/client/editor/TextEditorListener.java @@ -1,11 +1,11 @@ package org.swellrt.client.editor; -import org.waveprotocol.wave.client.doodad.annotation.jso.JsoEditorRange; +import org.waveprotocol.wave.client.doodad.annotation.jso.JsoRange; public interface TextEditorListener { - public void onSelectionChange(JsoEditorRange annotationsInRage); + public void onSelectionChange(JsoRange annotationsInRage); } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/AnnotationHandler.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/AnnotationHandler.java b/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/AnnotationHandler.java index fa13de4..f5c3bef 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/AnnotationHandler.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/AnnotationHandler.java @@ -3,10 +3,9 @@ package org.waveprotocol.wave.client.doodad.annotation; import java.util.HashMap; import java.util.Map; -import org.waveprotocol.wave.client.doodad.annotation.jso.JsoEditorRange; -import org.waveprotocol.wave.client.common.util.JsoStringSet; import org.waveprotocol.wave.client.common.util.JsoView; import org.waveprotocol.wave.client.doodad.annotation.jso.JsoAnnotationController; +import org.waveprotocol.wave.client.doodad.annotation.jso.JsoRange; import org.waveprotocol.wave.client.editor.content.AnnotationPainter; import org.waveprotocol.wave.client.editor.content.AnnotationPainter.PaintFunction; import org.waveprotocol.wave.client.editor.content.ContentElement; @@ -22,11 +21,7 @@ import org.waveprotocol.wave.model.document.AnnotationMutationHandler; import org.waveprotocol.wave.model.document.util.AnnotationRegistry; import org.waveprotocol.wave.model.document.util.DocumentContext; import org.waveprotocol.wave.model.util.CollectionUtils; -import org.waveprotocol.wave.model.util.Preconditions; import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV; -import org.waveprotocol.wave.model.util.ReadableStringSet.Proc; -import org.waveprotocol.wave.model.util.StringMap; -import org.waveprotocol.wave.model.util.StringSet; import com.google.gwt.user.client.Event; @@ -39,6 +34,10 @@ import com.google.gwt.user.client.Event; */ public class AnnotationHandler implements AnnotationMutationHandler { + public interface Activator { + public boolean shouldFireEvent(); + } + private static AnnotationHandler handlerInstance = null; private final AnnotationPainter painter; @@ -128,9 +127,7 @@ public class AnnotationHandler implements AnnotationMutationHandler { annotationRegistry.registerBehaviour(key, new DefaultAnnotationBehaviour(AnnotationFamily.CONTENT)); // Register painter to update attributes of the local view - JsoStringSet keySet = JsoStringSet.create(); - keySet.add(key); - painterRegistry.registerPaintFunction(keySet, new RenderFunc(key, styleClass, stylesInline)); + painterRegistry.registerPaintFunction(CollectionUtils.newStringSet(key), new RenderFunc(key, styleClass, stylesInline)); if (eventHandler != null) AnnotationPaint.registerEventHandler(key, eventHandler); @@ -144,7 +141,7 @@ public class AnnotationHandler implements AnnotationMutationHandler { } - public static void register(Registries registries, String key, JsoAnnotationController controller) { + public static void register(Registries registries, String key, JsoAnnotationController controller, Activator activator) { AnnotationHandler.register(registries, key, new AnnotationPaint.MutationHandler() { @@ -153,8 +150,22 @@ public class AnnotationHandler implements AnnotationMutationHandler { @Override public void onMutation(ContentElement node) { String valueAttr = AnnotationPaint.VALUE_ATTR_PREFIX + AnnotationHandler.getSafeKey(key); - if (controller != null) - controller.onChange(JsoEditorRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(valueAttr)).build()); + if (controller != null && activator.shouldFireEvent()) + controller.onChange(JsoRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(valueAttr)).build()); + } + + @Override + public void onAdded(ContentElement node) { + String valueAttr = AnnotationPaint.VALUE_ATTR_PREFIX + AnnotationHandler.getSafeKey(key); + if (controller != null && activator.shouldFireEvent()) + controller.onAdd(JsoRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(valueAttr)).build()); + } + + @Override + public void onRemoved(ContentElement node) { + String valueAttr = AnnotationPaint.VALUE_ATTR_PREFIX + AnnotationHandler.getSafeKey(key); + if (controller != null && activator.shouldFireEvent()) + controller.onRemove(JsoRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(valueAttr)).build()); } }, @@ -163,8 +174,8 @@ public class AnnotationHandler implements AnnotationMutationHandler { @Override public void onEvent(ContentElement node, Event event) { String valueAttr = AnnotationPaint.VALUE_ATTR_PREFIX + AnnotationHandler.getSafeKey(key); - if (controller != null) - controller.onEvent(JsoEditorRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(valueAttr)).build(), event); + if (controller != null && activator.shouldFireEvent()) + controller.onEvent(JsoRange.Builder.create(node.getMutableDoc()).range(node).annotation(key, node.getAttribute(valueAttr)).build(), event); } }, controller != null ? controller.getStyleClass() : null, http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoAnnotationController.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoAnnotationController.java b/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoAnnotationController.java index 288a601..59be2ac 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoAnnotationController.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoAnnotationController.java @@ -97,15 +97,35 @@ public class JsoAnnotationController extends JavaScriptObject { /** * Handle events raised in the rendered annotation element. */ - public native final void onEvent(JsoEditorRange annotationContent, Event event) /*-{ + public native final void onEvent(JsoRange annotationContent, Event event) /*-{ if (this.onEvent) this.onEvent(annotationContent, event); }-*/; - public native final void onChange(JsoEditorRange annotationContent) /*-{ + public native final void onChange(JsoRange annotationContent) /*-{ if (this.onChange) this.onChange(annotationContent); }-*/; + + public native final void onAdd(JsoRange annotationContent) /*-{ + if (this.onAdd) + this.onAdd(annotationContent); + }-*/; + + public native final void onRemove(JsoRange annotationContent) /*-{ + if (this.onRemove) + this.onRemove(annotationContent); + }-*/; + + /** + * Sets the reference to the editor's pure javascript facade. + * Allows handlers to interact with the editor. + * + * @param jsEditorFacade + */ + public final native void setEditorJsFacade(JavaScriptObject jsEditorFacade) /*-{ + this.editor = jsEditorFacade; + }-*/; } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoRange.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoRange.java b/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoRange.java index 889cb09..0d4e4ee 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoRange.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/doodad/annotation/jso/JsoRange.java @@ -4,12 +4,17 @@ import org.waveprotocol.wave.client.common.util.JsoStringMap; import org.waveprotocol.wave.client.common.util.JsoView; import org.waveprotocol.wave.client.editor.content.CMutableDocument; import org.waveprotocol.wave.client.editor.content.ContentElement; +import org.waveprotocol.wave.client.editor.content.ContentNode; import org.waveprotocol.wave.model.document.AnnotationInterval; import org.waveprotocol.wave.model.document.util.DocHelper; +import org.waveprotocol.wave.model.document.util.Point; import org.waveprotocol.wave.model.document.util.Range; +import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV; +import org.waveprotocol.wave.model.util.StringMap; import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Node; /** * A single native Js class to handle different document and editor ranged stuff. @@ -17,13 +22,20 @@ import com.google.gwt.core.client.JavaScriptObject; * @author pablo...@gmail.com (Pablo Ojanguren) * */ -public class JsoEditorRange extends JavaScriptObject { +public class JsoRange extends JavaScriptObject { public static class Builder { JsoView jso; - CMutableDocument doc; + JsoView annotations; + + + CMutableDocument doc; + int start; + int end; + int length; + public static Builder create(CMutableDocument doc) { return new Builder(doc); @@ -35,9 +47,9 @@ public class JsoEditorRange extends JavaScriptObject { } public Builder range(int start, int end, int length) { - jso.setNumber("start", start); - jso.setNumber("end", end); - jso.setNumber("lenght", length); + this.start = start; + this.end = end; + this.length = length; return this; } @@ -46,28 +58,26 @@ public class JsoEditorRange extends JavaScriptObject { } public Builder range(ContentElement contentElement) { - int start = contentElement.getContext().locationMapper().getLocation(contentElement); - int end = contentElement.getContext().locationMapper().getLocation(contentElement.getNextSibling()); - int lenght = DocHelper.getItemSize(doc, contentElement); - return range(start, end, lenght); + start = contentElement.getContext().locationMapper().getLocation(contentElement); + end = contentElement.getContext().locationMapper().getLocation(contentElement.getNextSibling()); + length = DocHelper.getItemSize(doc, contentElement); + return this; } public Builder annotation(String key, String value) { - JsoView annotations; - - if (jso.getJso("annotations") == null) { - jso.setJso("annotations", JsoView.create()); + if (annotations == null) { + annotations = JsoView.create(); } - annotations = jso.getJsoView("annotations"); + annotations.setString(key, value); return this; } public Builder annotations(JsoStringMap<String> map) { - jso.setJso("annotations", map.backend); + annotations = map.backend; return this; } @@ -86,14 +96,30 @@ public class JsoEditorRange extends JavaScriptObject { } - public JsoEditorRange build() { + public JsoRange build() { + + jso.setNumber("start", start); + jso.setNumber("end", end); + jso.setNumber("lenght", length); + + if (annotations != null) + jso.setJso("annotations", annotations); + + Node node = null; + Point<ContentNode> point = doc.locate(start); + if (point != null) + node = point.getCanonicalNode().getImplNodeletRightwards(); + + jso.setJso("node", node); + + functionize(jso, doc); return jso.cast(); } private native void functionize(JavaScriptObject jso, CMutableDocument doc) /*-{ if (jso != null && typeof jso.start == "number" && typeof jso.end == "number") - jso.text = @org.waveprotocol.wave.client.doodad.annotation.jso.JsoEditorRange::getRangeText(IILorg/waveprotocol/wave/client/editor/content/CMutableDocument;)(jso.start, jso.end, doc); + jso.text = @org.waveprotocol.wave.client.doodad.annotation.jso.JsoRange::getRangeText(IILorg/waveprotocol/wave/client/editor/content/CMutableDocument;)(jso.start, jso.end, doc); else jso = ""; }-*/; @@ -112,18 +138,20 @@ public class JsoEditorRange extends JavaScriptObject { return this.annotations; }-*/; - public final String[] getAnnotationKeys() { - JsoView jsoKeys = getAnnotationsJsoView(); - String[] keys = new String[jsoKeys.countEntries()]; - jsoKeys.each(new ProcV<Object>() { - int c = 0; - @Override - public void apply(String key, Object value) { - keys[c++] = key; - } - - }); - return keys; + public final StringMap<String> getAnnotations() { + StringMap<String> map = CollectionUtils.<String>createStringMap(); + JsoView annotations = getAnnotationsJsoView(); + + annotations.each(new ProcV<String>(){ + + @Override + public void apply(String key, String value) { + map.put(key, value); + } + + }); + + return map; } private static String getRangeText(int start, int end, CMutableDocument doc) { @@ -134,7 +162,7 @@ public class JsoEditorRange extends JavaScriptObject { doc.deleteRange(start, end); } - protected JsoEditorRange() { + protected JsoRange() { } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/WidgetDoodad.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/WidgetDoodad.java b/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/WidgetDoodad.java index 9a1a680..75eb377 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/WidgetDoodad.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/WidgetDoodad.java @@ -93,7 +93,14 @@ public class WidgetDoodad { } } } + + + @Override + public void onRemovedFromParent(ContentElement element, ContentElement newParent) { + } + } + static class WidgetEventHandler extends NodeEventHandlerImpl { @@ -188,7 +195,7 @@ public class WidgetDoodad { public static void register(ElementHandlerRegistry registry, StringMap<JsoWidgetController> controllers) { - widgetControllers = controllers; + widgetControllers = controllers; WidgetRendererHandler renderer = new WidgetRendererHandler(controllers); WidgetEventHandler eventHandler = new WidgetEventHandler(controllers); http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/jso/JsoWidgetController.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/jso/JsoWidgetController.java b/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/jso/JsoWidgetController.java index eae09ed..f08a577 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/jso/JsoWidgetController.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/doodad/widget/jso/JsoWidgetController.java @@ -73,13 +73,13 @@ public class JsoWidgetController extends JavaScriptObject { element.innerHTML="<span style='background: #FFD677;'>"+after+"</span>"; }, - onActivated: function(element) { - // attach event handlers - }, - - onDeactivated: function(element) { - // deattach event handlers - } + onActivated: function(element) { + // attach event handlers + }, + + onDeactivated: function(element) { + // deattach event handlers + } }; @@ -113,5 +113,14 @@ public class JsoWidgetController extends JavaScriptObject { this.onDeactivated(parent); }-*/; + /** + * Sets the reference to the editor's pure javascript facade. + * Allows handlers to interact with the editor. + * + * @param jsEditorFacade + */ + public final native void setEditorJsFacade(JavaScriptObject jsEditorFacade) /*-{ + this.editor = jsEditorFacade; + }-*/; } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationPaint.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationPaint.java b/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationPaint.java index 6fda1f9..c4df1d5 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationPaint.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationPaint.java @@ -91,7 +91,9 @@ public class AnnotationPaint { * Handlers may register callback for mutation events over painted regions. */ public interface MutationHandler { + void onAdded(ContentElement node); void onMutation(ContentElement node); + void onRemoved(ContentElement node); } static final Map<String, MutationHandler> mutationHandlerRegistry = http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationSpreadRenderer.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationSpreadRenderer.java b/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationSpreadRenderer.java index daaa71c..8d6d8c8 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationSpreadRenderer.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/editor/content/misc/AnnotationSpreadRenderer.java @@ -78,17 +78,17 @@ class AnnotationSpreadRenderer extends RenderingMutationHandler { private static Set<MutationHandler> getMutationHandlers(ContentElement element) { Set<MutationHandler> handlers = new HashSet<MutationHandler>(); - + + element.getAttributes().each(new ProcV<String>() { @Override public void apply(String key, String value) { - if (key.startsWith(AnnotationPaint.MUTATION_LISTENER_ATTR)) { - MutationHandler h = AnnotationPaint.mutationHandlerRegistry.get(key); - if (h != null) - handlers.add(h); - } - + if (key.startsWith(AnnotationPaint.MUTATION_LISTENER_ATTR_PREFIX)) { + MutationHandler h = AnnotationPaint.mutationHandlerRegistry.get(value); + if (h != null) + handlers.add(h); + } } }); @@ -223,12 +223,26 @@ class AnnotationSpreadRenderer extends RenderingMutationHandler { element.setBothNodelets(newNodelet); } } + + @Override + public void onAddedToParent(ContentElement element, ContentElement oldParent) { + Set<MutationHandler> handlers = getMutationHandlers(element); + for (MutationHandler h : handlers) { + h.onAdded(element); + } + } @Override public void onRemovedFromParent(ContentElement element, ContentElement newParent) { if (newParent != null) { return; } + + Set<MutationHandler> handlers = getMutationHandlers(element); + for (MutationHandler h: handlers) { + h.onRemoved(element); + } + removeListener(DomHelper.castToOld(element.getImplNodelet())); super.onRemovedFromParent(element, newParent); } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/a768997c/wave/src/main/java/org/waveprotocol/wave/client/editor/content/paragraph/Paragraph.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/editor/content/paragraph/Paragraph.java b/wave/src/main/java/org/waveprotocol/wave/client/editor/content/paragraph/Paragraph.java index cbc7210..94b0849 100644 --- a/wave/src/main/java/org/waveprotocol/wave/client/editor/content/paragraph/Paragraph.java +++ b/wave/src/main/java/org/waveprotocol/wave/client/editor/content/paragraph/Paragraph.java @@ -54,7 +54,9 @@ public class Paragraph { * Callback for mutation events within a paragraph. */ public interface MutationHandler { + void onAdded(ContentElement node); void onMutation(ContentElement node); + void onRemoved(ContentElement node); } /** A registry of event handlers by paragraph type */