aboutsummaryrefslogtreecommitdiff
path: root/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java
blob: b8212fcefae29c359316b5afdd2bb2b179cf9703 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package org.wordpress.android.editor;

import android.webkit.JavascriptInterface;

import org.json.JSONException;
import org.json.JSONObject;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.JSONUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.wordpress.android.editor.EditorFragmentAbstract.MediaType;

public class JsCallbackReceiver {
    private static final String JS_CALLBACK_DELIMITER = "~";

    private static final String CALLBACK_DOM_LOADED = "callback-dom-loaded";
    private static final String CALLBACK_NEW_FIELD = "callback-new-field";

    private static final String CALLBACK_INPUT = "callback-input";
    private static final String CALLBACK_SELECTION_CHANGED = "callback-selection-changed";
    private static final String CALLBACK_SELECTION_STYLE = "callback-selection-style";

    private static final String CALLBACK_FOCUS_IN = "callback-focus-in";
    private static final String CALLBACK_FOCUS_OUT = "callback-focus-out";

    private static final String CALLBACK_IMAGE_REPLACED = "callback-image-replaced";
    private static final String CALLBACK_VIDEO_REPLACED = "callback-video-replaced";
    private static final String CALLBACK_IMAGE_TAP = "callback-image-tap";
    private static final String CALLBACK_LINK_TAP = "callback-link-tap";
    private static final String CALLBACK_MEDIA_REMOVED = "callback-media-removed";

    private static final String CALLBACK_VIDEOPRESS_INFO_REQUEST = "callback-videopress-info-request";

    private static final String CALLBACK_LOG = "callback-log";

    private static final String CALLBACK_RESPONSE_STRING = "callback-response-string";

    private static final String CALLBACK_ACTION_FINISHED = "callback-action-finished";

    private final OnJsEditorStateChangedListener mListener;

    private Set<String> mPreviousStyleSet = new HashSet<>();

    public JsCallbackReceiver(EditorFragmentAbstract editorFragmentAbstract) {
        mListener = (OnJsEditorStateChangedListener) editorFragmentAbstract;
    }

    @JavascriptInterface
    public void executeCallback(String callbackId, String params) {
        switch (callbackId) {
            case CALLBACK_DOM_LOADED:
                mListener.onDomLoaded();
                break;
            case CALLBACK_SELECTION_STYLE:
                // Compare the new styles to the previous ones, and notify the JsCallbackListener of the changeset
                Set<String> rawStyleSet = Utils.splitDelimitedString(params, JS_CALLBACK_DELIMITER);

                // Strip link details from active style set
                Set<String> newStyleSet = new HashSet<>();
                for (String element : rawStyleSet) {
                    if (element.matches("link:(.*)")) {
                        newStyleSet.add("link");
                    } else if (!element.matches("link-title:(.*)")) {
                        newStyleSet.add(element);
                    }
                }

                mListener.onSelectionStyleChanged(Utils.getChangeMapFromSets(mPreviousStyleSet, newStyleSet));
                mPreviousStyleSet = newStyleSet;
                break;
            case CALLBACK_SELECTION_CHANGED:
                // Called for changes to the field in current focus and for changes made to selection
                // (includes moving the caret without selecting text)
                // TODO: Possibly needed for handling WebView scrolling when caret moves (from iOS)
                Set<String> selectionKeyValueSet = Utils.splitDelimitedString(params, JS_CALLBACK_DELIMITER);
                mListener.onSelectionChanged(Utils.buildMapFromKeyValuePairs(selectionKeyValueSet));
                break;
            case CALLBACK_INPUT:
                // Called on key press
                // TODO: Possibly needed for handling WebView scrolling when caret moves (from iOS)
                break;
            case CALLBACK_FOCUS_IN:
                // TODO: Needed to handle displaying/graying the format bar when focus changes between the title and content
                AppLog.d(AppLog.T.EDITOR, "Focus in callback received");
                break;
            case CALLBACK_FOCUS_OUT:
                // TODO: Needed to handle displaying/graying the format bar when focus changes between the title and content
                AppLog.d(AppLog.T.EDITOR, "Focus out callback received");
                break;
            case CALLBACK_NEW_FIELD:
                // TODO: Used for logging/testing purposes on iOS
                AppLog.d(AppLog.T.EDITOR, "New field created, " + params);
                break;
            case CALLBACK_IMAGE_REPLACED:
                AppLog.d(AppLog.T.EDITOR, "Image replaced, " + params);

                // Extract the local media id from the callback string (stripping the 'id=' part)
                if (params.length() > 3) {
                    mListener.onMediaReplaced(params.substring(3));
                }
                break;
            case CALLBACK_VIDEO_REPLACED:
                AppLog.d(AppLog.T.EDITOR, "Video replaced, " + params);

                // Extract the local media id from the callback string (stripping the 'id=' part)
                if (params.length() > 3) {
                    mListener.onMediaReplaced(params.substring(3));
                }
                break;
            case CALLBACK_IMAGE_TAP:
                AppLog.d(AppLog.T.EDITOR, "Image tapped, " + params);

                String uploadStatus = "";

                List<String> mediaIds = new ArrayList<>();
                mediaIds.add("id");
                mediaIds.add("url");
                mediaIds.add("meta");
                mediaIds.add("type");

                Set<String> mediaDataSet = Utils.splitValuePairDelimitedString(params, JS_CALLBACK_DELIMITER, mediaIds);
                Map<String, String> mediaDataMap = Utils.buildMapFromKeyValuePairs(mediaDataSet);

                String mediaId = mediaDataMap.get("id");

                String mediaUrl = mediaDataMap.get("url");
                if (mediaUrl != null) {
                    mediaUrl = Utils.decodeHtml(mediaUrl);
                }

                MediaType mediaType = MediaType.fromString(mediaDataMap.get("type"));

                String mediaMeta = mediaDataMap.get("meta");
                JSONObject mediaMetaJson = new JSONObject();

                if (mediaMeta != null) {
                    mediaMeta = Utils.decodeHtml(mediaMeta);

                    try {
                        mediaMetaJson = new JSONObject(mediaMeta);
                        String classes = JSONUtils.getString(mediaMetaJson, "classes");
                        Set<String> classesSet = Utils.splitDelimitedString(classes, ", ");

                        if (classesSet.contains("uploading")) {
                            uploadStatus = "uploading";
                        } else if (classesSet.contains("failed")) {
                            uploadStatus = "failed";
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                        AppLog.d(AppLog.T.EDITOR, "Media meta data from callback-image-tap was not JSON-formatted");
                    }
                }

                mListener.onMediaTapped(mediaId, mediaType, mediaMetaJson, uploadStatus);
                break;
            case CALLBACK_LINK_TAP:
                // Extract and HTML-decode the link data from the callback params
                AppLog.d(AppLog.T.EDITOR, "Link tapped, " + params);

                List<String> linkIds = new ArrayList<>();
                linkIds.add("url");
                linkIds.add("title");

                Set<String> linkDataSet = Utils.splitValuePairDelimitedString(params, JS_CALLBACK_DELIMITER, linkIds);
                Map<String, String> linkDataMap = Utils.buildMapFromKeyValuePairs(linkDataSet);

                String url = linkDataMap.get("url");
                if (url != null) {
                    url = Utils.decodeHtml(url);
                }

                String title = linkDataMap.get("title");
                if (title != null) {
                    title = Utils.decodeHtml(title);
                }

                mListener.onLinkTapped(url, title);
                break;
            case CALLBACK_MEDIA_REMOVED:
                AppLog.d(AppLog.T.EDITOR, "Media removed, " + params);
                // Extract the media id from the callback string (stripping the 'id=' part of the callback string)
                if (params.length() > 3) {
                    mListener.onMediaRemoved(params.substring(3));
                }
                break;
            case CALLBACK_VIDEOPRESS_INFO_REQUEST:
                // Extract the VideoPress id from the callback string (stripping the 'id=' part of the callback string)
                if (params.length() > 3) {
                    mListener.onVideoPressInfoRequested(params.substring(3));
                }
                break;
            case CALLBACK_LOG:
                // Strip 'msg=' from beginning of string
                if (params.length() > 4) {
                    AppLog.d(AppLog.T.EDITOR, callbackId + ": " + params.substring(4));
                }
                break;
            case CALLBACK_RESPONSE_STRING:
                AppLog.d(AppLog.T.EDITOR, callbackId + ": " + params);
                Set<String> responseDataSet;
                if (params.startsWith("function=") && params.contains(JS_CALLBACK_DELIMITER)) {
                    String functionName = params.substring("function=".length(), params.indexOf(JS_CALLBACK_DELIMITER));

                    List<String> responseIds = new ArrayList<>();
                    switch (functionName) {
                        case "getHTMLForCallback":
                            responseIds.add("id");
                            responseIds.add("contents");
                            break;
                        case "getSelectedTextToLinkify":
                            responseIds.add("result");
                            break;
                        case "getFailedMedia":
                            responseIds.add("ids");
                    }

                    responseDataSet = Utils.splitValuePairDelimitedString(params, JS_CALLBACK_DELIMITER, responseIds);
                } else {
                    responseDataSet = Utils.splitDelimitedString(params, JS_CALLBACK_DELIMITER);
                }
                mListener.onGetHtmlResponse(Utils.buildMapFromKeyValuePairs(responseDataSet));
                break;
            case CALLBACK_ACTION_FINISHED:
                mListener.onActionFinished();
                break;
            default:
                AppLog.d(AppLog.T.EDITOR, "Unhandled callback: " + callbackId + ":" + params);
        }
    }
}