From c0c935fa4b3b6135be8099b637da57bede6b850f Mon Sep 17 00:00:00 2001
From: Alex Forcier
Date: Wed, 27 Jan 2016 09:46:20 -1000
Subject: [PATCH 1/6] Squashed 'libs/editor/' changes from 6647346..d99856b
d99856b Merge branch 'merge-wpandroid' into develop
aee093e Implemented missing onAuthHeaderRequested method in the example app
fdccfc1 Hide video fullscreen button
6fd8591 Pause video playback when the WebView is no longer visible to the user
84da174 Use a blank placeholder src instead of videopress.mp4 to trigger videopress video loading
435c012 Added some sanity checks for substring usage to JsCallbackReceiver
6c4d042 Fixed a bug where videos would sometimes get deleted if they were the only thing in the post
d1aeaca Added support for video shortcodes on non-VideoPress sites
9f1b954 Merge branch 'feature/visual-editor' into feature/editor-video-playback
a61fbf9 Added missing bracket in JS editor
040ceb8 Follow redirects when requesting thumbnails for the image settings dialog
4fbd821 Always defer to super.shouldInterceptRequest in editor WebView unless an auth header is present
c933ee8 Merge branch 'feature/visual-editor' into feature/editor-video-playback
10a08fa Merge pull request #273 from wordpress-mobile/issue/15-track-editor-evnts
bce6d92 Merge branch 'develop' into issue/15-track-editor-evnts
3e2c261 When looking up a VideoPress ID in the DB fails, refresh the blog media and check again
c803d21 Remove the onError attribute from VideoPress videos if an empty video url is returned
4ca4c4c Added the ability to tap a placeholder VideoPress video to attempt to load it
1c638bf Don't use the placeholder poster image for videopress videos
e9e8f7f Implemented missing method in example app
d8a8510 fix typo in event name
b13ac66 Track EDITOR_EDITED_IMAGE
da3cd0c Add space after videos when converting from VideoPress shortcode
ca6499f track most editor events
74b9932 use wp-analytics 1.1.0
1964844 Only apply custom headers for network URL resource requests
b3dd7e3 Implement new EditorFragmentListener method in mock activity for integration tests
fee3ffb Convert VideoPress shortcodes to video elements and back in the visual editor
9d56f49 Imported ZSSRichTextEditor video methods and callback methods from iOS
5b9a4c3 Imported video CSS from iOS
5bbc482 Added missing method implementation to test mock activity
77f7096 Retrieve an auth header if necessary when launching the image settings dialog
1214827 Use the safeToAddWordPressComAuthHeader utility method for resource requests in the new editor's WebView
b2e2b74 Updated the utils artifact version for the editor
12f748a Moved setupUrlConnection() to the utils library
108a24c Renamed EditorFragment's setWebViewHeader to setCustomHttpHeader
1ac139e Use custom headers for image settings dialog image requests
542db23 Extracted some duplicated HttpURLConnection building code into a utility method
96984f0 When an Authorization HTTP header is detected, force using the HTTPS protocol in the WebViewClient
3302ab9 Added support for custom headers to the editor's WebView, set through the EditorFragmentAbstract
02ce3e4 Editor 0.5 version bump
190a822 Added full support for gallery types
25b3632 Added a fix enabling galleries to be added to the editor before the DOM loads
acd7f9c Fixed creating a new post by making a gallery from the Media Library (the gallery wasn't being added to the new post)
1b6d47c Merge branch 'feature/visual-editor' into feature/editor-galleries
b1cb369 Added support for creating galleries from local images, using a placeholder shortcode while uploading
92a3359 Extracted duplicate paragraph-wrapping code into a separate method in the ZSS editor
26d93c0 Added support for inserting galleries using media library images
git-subtree-dir: libs/editor
git-subtree-split: d99856bfb3e35e5c86f74af3f517739749e923ea
---
WordPressEditor/build.gradle | 8 +-
.../MockEditorActivity.java | 10 +
.../android/editor/EditorFragment.java | 151 ++++-
.../editor/EditorFragmentAbstract.java | 16 +
.../editor/EditorMediaUploadListener.java | 1 +
.../android/editor/EditorWebViewAbstract.java | 117 ++++
.../editor/ImageSettingsDialogFragment.java | 8 +-
.../android/editor/JsCallbackReceiver.java | 14 +-
.../android/editor/LegacyEditorFragment.java | 5 +
.../OnJsEditorStateChangedListener.java | 1 +
.../org/wordpress/android/editor/Utils.java | 35 +-
.../editor/EditorFragmentAbstractTest.java | 5 +
.../example/EditorExampleActivity.java | 10 +
.../editor-common/assets/ZSSRichTextEditor.js | 546 +++++++++++++++++-
libs/editor-common/assets/editor-android.css | 4 +
libs/editor-common/assets/editor.css | 94 ++-
libs/editor-common/assets/wpposter.svg | 7 +
17 files changed, 988 insertions(+), 44 deletions(-)
create mode 100644 libs/editor-common/assets/wpposter.svg
diff --git a/WordPressEditor/build.gradle b/WordPressEditor/build.gradle
index 4dc82f05e1ee..df07667ac968 100644
--- a/WordPressEditor/build.gradle
+++ b/WordPressEditor/build.gradle
@@ -23,8 +23,8 @@ android {
buildToolsVersion "23.0.2"
defaultConfig {
- versionCode 4
- versionName "0.4"
+ versionCode 5
+ versionName "0.5"
minSdkVersion 14
targetSdkVersion 23
}
@@ -48,8 +48,8 @@ android {
dependencies {
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:support-v4:23.1.1'
- compile 'org.wordpress:analytics:1.0.0'
- compile 'org.wordpress:utils:1.6.0'
+ compile 'org.wordpress:analytics:1.2.0'
+ compile 'org.wordpress:utils:1.7.0'
// Test libraries
testCompile 'junit:junit:4.11'
diff --git a/WordPressEditor/src/androidTest/java/org.wordpress.android.editor/MockEditorActivity.java b/WordPressEditor/src/androidTest/java/org.wordpress.android.editor/MockEditorActivity.java
index 96e93382b662..fed7a083739d 100644
--- a/WordPressEditor/src/androidTest/java/org.wordpress.android.editor/MockEditorActivity.java
+++ b/WordPressEditor/src/androidTest/java/org.wordpress.android.editor/MockEditorActivity.java
@@ -61,6 +61,16 @@ public void onFeaturedImageChanged(int mediaId) {
}
+ @Override
+ public void onVideoPressInfoRequested(String videoId) {
+
+ }
+
+ @Override
+ public String onAuthHeaderRequested(String url) {
+ return "";
+ }
+
@Override
public void saveMediaFile(MediaFile mediaFile) {
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java
index 22ec71085260..9366b73def16 100755
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java
@@ -30,16 +30,21 @@
import com.android.volley.toolbox.ImageLoader;
+import org.json.JSONException;
import org.json.JSONObject;
+import org.wordpress.android.analytics.AnalyticsTracker;
+import org.wordpress.android.analytics.AnalyticsTracker.Stat;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.JSONUtils;
import org.wordpress.android.util.StringUtils;
import org.wordpress.android.util.ToastUtils;
+import org.wordpress.android.util.UrlUtils;
import org.wordpress.android.util.helpers.MediaFile;
import org.wordpress.android.util.helpers.MediaGallery;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
@@ -50,7 +55,8 @@
import java.util.concurrent.TimeUnit;
public class EditorFragment extends EditorFragmentAbstract implements View.OnClickListener, View.OnTouchListener,
- OnJsEditorStateChangedListener, OnImeBackListener, EditorMediaUploadListener {
+ OnJsEditorStateChangedListener, OnImeBackListener, EditorWebViewAbstract.AuthHeaderRequestListener,
+ EditorMediaUploadListener {
private static final String ARG_PARAM_TITLE = "param_title";
private static final String ARG_PARAM_CONTENT = "param_content";
@@ -87,8 +93,10 @@ public class EditorFragment extends EditorFragmentAbstract implements View.OnCli
private boolean mHideActionBarOnSoftKeyboardUp = false;
private ConcurrentHashMap mWaitingMediaFiles;
+ private Set mWaitingGalleries;
private Set mUploadingMediaIds;
private Set mFailedMediaIds;
+ private MediaGallery mUploadingMediaGallery;
private String mJavaScriptResult = "";
@@ -126,6 +134,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
}
mWaitingMediaFiles = new ConcurrentHashMap<>();
+ mWaitingGalleries = Collections.newSetFromMap(new ConcurrentHashMap());
mUploadingMediaIds = new HashSet<>();
mFailedMediaIds = new HashSet<>();
@@ -135,6 +144,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
mWebView.setOnTouchListener(this);
mWebView.setOnImeBackListener(this);
+ mWebView.setAuthHeaderRequestListener(this);
+
+ if (mCustomHttpHeaders != null && mCustomHttpHeaders.size() > 0) {
+ for (Map.Entry entry : mCustomHttpHeaders.entrySet()) {
+ mWebView.setCustomHeader(entry.getKey(), entry.getValue());
+ }
+ }
// Ensure that the content field is always filling the remaining screen space
mWebView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@@ -222,6 +238,14 @@ public void onDetach() {
super.onDetach();
}
+ @Override
+ public void setUserVisibleHint(boolean isVisibleToUser) {
+ if (mDomHasLoaded) {
+ mWebView.notifyVisibilityChanged(isVisibleToUser);
+ }
+ super.setUserVisibleHint(isVisibleToUser);
+ }
+
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putCharSequence(KEY_TITLE, getTitle());
@@ -365,6 +389,8 @@ protected void initJsEditor() {
public void onClick(View v) {
int id = v.getId();
if (id == R.id.format_bar_button_html) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_HTML);
+
// Don't switch to HTML mode if currently uploading media
if (!mUploadingMediaIds.isEmpty()) {
((ToggleButton) v).setChecked(false);
@@ -405,6 +431,7 @@ public void onClick(View v) {
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').focus();");
}
} else if (id == R.id.format_bar_button_media) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_IMAGE);
((ToggleButton) v).setChecked(false);
if (mSourceView.getVisibility() == View.VISIBLE) {
@@ -419,8 +446,10 @@ public void onClick(View v) {
if (!((ToggleButton) v).isChecked()) {
// The link button was checked when it was pressed; remove the current link
mWebView.execJavaScriptFromString("ZSSEditor.unlink();");
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_UNLINK);
return;
}
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_LINK);
((ToggleButton) v).setChecked(false);
@@ -479,6 +508,11 @@ public void onImeBack() {
showActionBarIfNeeded();
}
+ @Override
+ public String onAuthHeaderRequested(String url) {
+ return mEditorFragmentListener.onAuthHeaderRequested(url);
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
@@ -706,11 +740,13 @@ public void run() {
if (URLUtil.isNetworkUrl(mediaUrl)) {
String mediaId = mediaFile.getMediaId();
mWebView.execJavaScriptFromString("ZSSEditor.insertImage('" + mediaUrl + "', '" + mediaId + "');");
+ AnalyticsTracker.track(Stat.EDITOR_ADDED_PHOTO_VIA_WP_MEDIA_LIBRARY);
} else {
String id = mediaFile.getMediaId();
mWebView.execJavaScriptFromString("ZSSEditor.insertLocalImage(" + id + ", '" + mediaUrl + "');");
mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + id + ", " + 0 + ");");
mUploadingMediaIds.add(id);
+ AnalyticsTracker.track(Stat.EDITOR_ADDED_PHOTO_VIA_LOCAL_LIBRARY);
}
}
});
@@ -718,7 +754,35 @@ public void run() {
@Override
public void appendGallery(MediaGallery mediaGallery) {
- // TODO
+ if (!mDomHasLoaded) {
+ // If the DOM hasn't loaded yet, we won't be able to add a gallery to the ZSSEditor
+ // Place it in a queue to be handled when the DOM loaded callback is received
+ mWaitingGalleries.add(mediaGallery);
+ return;
+ }
+
+ if (mediaGallery.getIds().isEmpty()) {
+ mUploadingMediaGallery = mediaGallery;
+ mWebView.execJavaScriptFromString("ZSSEditor.insertLocalGallery('" + mediaGallery.getUniqueId() + "');");
+ } else {
+ // Ensure that the content field is in focus (it may not be if we're adding a gallery to a new post by a
+ // share action and not via the format bar button)
+ mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').focus();");
+
+ mWebView.execJavaScriptFromString("ZSSEditor.insertGallery('" + mediaGallery.getIdsStr() + "', '" +
+ mediaGallery.getType() + "', " + mediaGallery.getNumColumns() + ");");
+ }
+ }
+
+ @Override
+ public void setUrlForVideoPressId(final String videoId, final String videoUrl, final String posterUrl) {
+ mWebView.post(new Runnable() {
+ @Override
+ public void run() {
+ mWebView.execJavaScriptFromString("ZSSEditor.setVideoPressLinks('" + videoId + "', '" +
+ videoUrl + "', '" + posterUrl + "');");
+ }
+ });
}
@Override
@@ -770,6 +834,7 @@ public void onMediaUploadFailed(final String mediaId) {
mWebView.post(new Runnable() {
@Override
public void run() {
+ AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_FAILED);
mWebView.execJavaScriptFromString("ZSSEditor.markImageUploadFailed(" + mediaId + ");");
mFailedMediaIds.add(mediaId);
mUploadingMediaIds.remove(mediaId);
@@ -777,6 +842,27 @@ public void run() {
});
}
+ @Override
+ public void onGalleryMediaUploadSucceeded(final long galleryId, String remoteMediaId, int remaining) {
+ if (galleryId == mUploadingMediaGallery.getUniqueId()) {
+ ArrayList mediaIds = mUploadingMediaGallery.getIds();
+ mediaIds.add(remoteMediaId);
+ mUploadingMediaGallery.setIds(mediaIds);
+
+ if (remaining == 0) {
+ mWebView.post(new Runnable() {
+ @Override
+ public void run() {
+ mWebView.execJavaScriptFromString("ZSSEditor.replacePlaceholderGallery('" + galleryId + "', '" +
+ mUploadingMediaGallery.getIdsStr() + "', '" +
+ mUploadingMediaGallery.getType() + "', " +
+ mUploadingMediaGallery.getNumColumns() + ");");
+ }
+ });
+ }
+ }
+ }
+
public void onDomLoaded() {
mWebView.post(new Runnable() {
public void run() {
@@ -820,6 +906,19 @@ public void run() {
}
mWaitingMediaFiles.clear();
}
+
+ // Add any galleries that were placed in a queue due to the DOM not having loaded yet
+ if (mWaitingGalleries.size() > 0) {
+ // Gallery insertion will only work if the content field is in focus
+ // (for a new post, no field is in focus until user action)
+ mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').focus();");
+
+ for (MediaGallery mediaGallery : mWaitingGalleries) {
+ appendGallery(mediaGallery);
+ }
+
+ mWaitingGalleries.clear();
+ }
}
});
}
@@ -894,6 +993,7 @@ public void onClick(DialogInterface dialog, int id) {
mWebView.post(new Runnable() {
@Override
public void run() {
+ AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED);
mWebView.execJavaScriptFromString("ZSSEditor.unmarkImageUploadFailed(" + mediaId + ");");
mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + mediaId + ", " + 0 + ");");
mFailedMediaIds.remove(mediaId);
@@ -908,17 +1008,34 @@ public void run() {
if (fragmentManager.findFragmentByTag(ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_TAG) != null) {
return;
}
-
+ AnalyticsTracker.track(Stat.EDITOR_EDITED_IMAGE);
ImageSettingsDialogFragment imageSettingsDialogFragment = new ImageSettingsDialogFragment();
imageSettingsDialogFragment.setTargetFragment(this,
ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_REQUEST_CODE);
Bundle dialogBundle = new Bundle();
- dialogBundle.putString("imageMeta", meta.toString());
dialogBundle.putString("maxWidth", mBlogSettingMaxImageWidth);
dialogBundle.putBoolean("featuredImageSupported", mFeaturedImageSupported);
+ // Request and add an authorization header for HTTPS images
+ // Use https:// when requesting the auth header, in case the image is incorrectly using http://.
+ // If an auth header is returned, force https:// for the actual HTTP request.
+ HashMap headerMap = new HashMap<>(mCustomHttpHeaders);
+ try {
+ final String imageSrc = meta.getString("src");
+ String authHeader = mEditorFragmentListener.onAuthHeaderRequested(UrlUtils.makeHttps(imageSrc));
+ if (authHeader.length() > 0) {
+ meta.put("src", UrlUtils.makeHttps(imageSrc));
+ headerMap.put("Authorization", authHeader);
+ }
+ } catch (JSONException e) {
+ AppLog.e(T.EDITOR, "Could not retrieve image url from JSON metadata");
+ }
+ dialogBundle.putSerializable("headerMap", headerMap);
+
+ dialogBundle.putString("imageMeta", meta.toString());
+
String imageId = JSONUtils.getString(meta, "attachment_id");
if (!imageId.isEmpty()) {
dialogBundle.putBoolean("isFeatured", mFeaturedImageId == Integer.parseInt(imageId));
@@ -933,6 +1050,8 @@ public void run() {
ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_TAG)
.addToBackStack(null)
.commit();
+
+ mWebView.notifyVisibilityChanged(false);
break;
}
}
@@ -950,6 +1069,11 @@ public void onLinkTapped(String url, String title) {
linkDialogFragment.show(getFragmentManager(), "LinkDialogFragment");
}
+ @Override
+ public void onVideoPressInfoRequested(final String videoId) {
+ mEditorFragmentListener.onVideoPressInfoRequested(videoId);
+ }
+
public void onGetHtmlResponse(Map inputArgs) {
String functionId = inputArgs.get("function");
@@ -1051,7 +1175,7 @@ private void clearFormatBarButtons() {
private void onFormattingButtonClicked(ToggleButton toggleButton) {
String tag = toggleButton.getTag().toString();
-
+ trackFormattingButtonClicked(toggleButton);
if (mWebView.getVisibility() == View.VISIBLE) {
mWebView.execJavaScriptFromString("ZSSEditor.set" + StringUtils.capitalize(tag) + "();");
} else {
@@ -1059,6 +1183,23 @@ private void onFormattingButtonClicked(ToggleButton toggleButton) {
}
}
+ private void trackFormattingButtonClicked(ToggleButton toggleButton) {
+ int id = toggleButton.getId();
+ if (id == R.id.format_bar_button_bold) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_BOLD);
+ } else if (id == R.id.format_bar_button_italic) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_ITALIC);
+ } else if (id == R.id.format_bar_button_ol) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_ORDERED_LIST);
+ } else if (id == R.id.format_bar_button_ul) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_UNORDERED_LIST);
+ } else if (id == R.id.format_bar_button_quote) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_BLOCKQUOTE);
+ } else if (id == R.id.format_bar_button_strikethrough) {
+ AnalyticsTracker.track(Stat.EDITOR_TAPPED_STRIKETHROUGH);
+ }
+ }
+
/**
* In HTML mode, applies formatting to selected text, or inserts formatting tag at current cursor position
* @param toggleButton format bar button which was clicked
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java
index 2e8b032c8370..ce874bd49a91 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java
@@ -11,6 +11,9 @@
import org.wordpress.android.util.helpers.MediaFile;
import org.wordpress.android.util.helpers.MediaGallery;
+import java.util.HashMap;
+import java.util.Map;
+
public abstract class EditorFragmentAbstract extends Fragment {
public abstract void setTitle(CharSequence text);
public abstract void setContent(CharSequence text);
@@ -18,6 +21,7 @@ public abstract class EditorFragmentAbstract extends Fragment {
public abstract CharSequence getContent();
public abstract void appendMediaFile(MediaFile mediaFile, String imageUrl, ImageLoader imageLoader);
public abstract void appendGallery(MediaGallery mediaGallery);
+ public abstract void setUrlForVideoPressId(String videoPressId, String url, String posterUrl);
public abstract boolean hasFailedMediaUploads();
public abstract void setTitlePlaceholder(CharSequence text);
public abstract void setContentPlaceholder(CharSequence text);
@@ -35,6 +39,8 @@ public abstract class EditorFragmentAbstract extends Fragment {
protected ImageLoader mImageLoader;
protected boolean mDebugModeEnabled;
+ protected HashMap mCustomHttpHeaders;
+
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
@@ -83,6 +89,14 @@ public void setFeaturedImageId(int featuredImageId) {
mFeaturedImageId = featuredImageId;
}
+ public void setCustomHttpHeader(String name, String value) {
+ if (mCustomHttpHeaders == null) {
+ mCustomHttpHeaders = new HashMap<>();
+ }
+
+ mCustomHttpHeaders.put(name, value);
+ }
+
public void setDebugModeEnabled(boolean debugModeEnabled) {
mDebugModeEnabled = debugModeEnabled;
}
@@ -113,6 +127,8 @@ public interface EditorFragmentListener {
void onMediaRetryClicked(String mediaId);
void onMediaUploadCancelClicked(String mediaId, boolean delete);
void onFeaturedImageChanged(int mediaId);
+ void onVideoPressInfoRequested(String videoId);
+ String onAuthHeaderRequested(String url);
// TODO: remove saveMediaFile, it's currently needed for the legacy editor
void saveMediaFile(MediaFile mediaFile);
}
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java
index 6f052fac2b30..c8a25cd42783 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java
@@ -4,4 +4,5 @@ public interface EditorMediaUploadListener {
void onMediaUploadSucceeded(String localId, String remoteId, String remoteUrl);
void onMediaUploadProgress(String localId, float progress);
void onMediaUploadFailed(String localId);
+ void onGalleryMediaUploadSucceeded(long galleryId, String remoteId, int remaining);
}
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java
index 979e167fcf3e..5aec2abd4547 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java
@@ -1,18 +1,32 @@
package org.wordpress.android.editor;
import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
import android.content.Context;
+import android.os.Build;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.JsResult;
+import android.webkit.URLUtil;
import android.webkit.WebChromeClient;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.HTTPUtils;
+import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.util.UrlUtils;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.util.HashMap;
+import java.util.Map;
/**
* A text editor WebView with support for JavaScript execution.
@@ -21,6 +35,9 @@ public abstract class EditorWebViewAbstract extends WebView {
public abstract void execJavaScriptFromString(String javaScript);
private OnImeBackListener mOnImeBackListener;
+ private AuthHeaderRequestListener mAuthHeaderRequestListener;
+
+ private Map mHeaderMap = new HashMap<>();
public EditorWebViewAbstract(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -43,6 +60,73 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
AppLog.e(AppLog.T.EDITOR, description);
}
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
+ String url = request.getUrl().toString();
+
+ if (!URLUtil.isNetworkUrl(url)) {
+ return super.shouldInterceptRequest(view, request);
+ }
+
+ // Request and add an authorization header for HTTPS resource requests.
+ // Use https:// when requesting the auth header, in case the resource is incorrectly using http://.
+ // If an auth header is returned, force https:// for the actual HTTP request.
+ String authHeader = mAuthHeaderRequestListener.onAuthHeaderRequested(UrlUtils.makeHttps(url));
+ if (StringUtils.notNullStr(authHeader).length() > 0) {
+ try {
+ url = UrlUtils.makeHttps(url);
+
+ // Keep any existing request headers from the WebResourceRequest
+ Map headerMap = request.getRequestHeaders();
+ for (Map.Entry entry : mHeaderMap.entrySet()) {
+ headerMap.put(entry.getKey(), entry.getValue());
+ }
+ headerMap.put("Authorization", authHeader);
+
+ HttpURLConnection conn = HTTPUtils.setupUrlConnection(url, headerMap);
+ return new WebResourceResponse(conn.getContentType(), conn.getContentEncoding(),
+ conn.getInputStream());
+ } catch (IOException e) {
+ AppLog.e(AppLog.T.EDITOR, e);
+ }
+ }
+
+ return super.shouldInterceptRequest(view, request);
+ }
+
+ /**
+ * Compatibility method for API < 21
+ */
+ @SuppressWarnings("deprecation")
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+ if (!URLUtil.isNetworkUrl(url)) {
+ return super.shouldInterceptRequest(view, url);
+ }
+
+ // Request and add an authorization header for HTTPS resource requests.
+ // Use https:// when requesting the auth header, in case the resource is incorrectly using http://.
+ // If an auth header is returned, force https:// for the actual HTTP request.
+ String authHeader = mAuthHeaderRequestListener.onAuthHeaderRequested(UrlUtils.makeHttps(url));
+ if (StringUtils.notNullStr(authHeader).length() > 0) {
+ try {
+ url = UrlUtils.makeHttps(url);
+
+ Map headerMap = new HashMap<>(mHeaderMap);
+ headerMap.put("Authorization", authHeader);
+
+ HttpURLConnection conn = HTTPUtils.setupUrlConnection(url, headerMap);
+ return new WebResourceResponse(conn.getContentType(), conn.getContentEncoding(),
+ conn.getInputStream());
+ } catch (IOException e) {
+ AppLog.e(AppLog.T.EDITOR, e);
+ }
+ }
+
+ return super.shouldInterceptRequest(view, url);
+ }
});
this.setWebChromeClient(new WebChromeClient() {
@@ -65,10 +149,35 @@ public boolean onCheckIsTextEditor() {
return true;
}
+ @Override
+ public void setVisibility(int visibility) {
+ notifyVisibilityChanged(visibility == View.VISIBLE);
+ super.setVisibility(visibility);
+ }
+
+ /**
+ * Handles events that should be triggered when the WebView is hidden or is shown to the user
+ * @param visible the new visibility status of the WebView
+ */
+ public void notifyVisibilityChanged(boolean visible) {
+ if (!visible) {
+ this.post(new Runnable() {
+ @Override
+ public void run() {
+ execJavaScriptFromString("ZSSEditor.pauseAllVideos();");
+ }
+ });
+ }
+ }
+
public void setOnImeBackListener(OnImeBackListener listener) {
mOnImeBackListener = listener;
}
+ public void setAuthHeaderRequestListener(AuthHeaderRequestListener listener) {
+ mAuthHeaderRequestListener = listener;
+ }
+
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
@@ -78,4 +187,12 @@ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
}
return super.onKeyPreIme(keyCode, event);
}
+
+ public void setCustomHeader(String name, String value) {
+ mHeaderMap.put(name, value);
+ }
+
+ public interface AuthHeaderRequestListener {
+ String onAuthHeaderRequested(String url);
+ }
}
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/ImageSettingsDialogFragment.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/ImageSettingsDialogFragment.java
index ff6c0a99c84b..ab2932d662ab 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/ImageSettingsDialogFragment.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/ImageSettingsDialogFragment.java
@@ -32,6 +32,7 @@
import java.util.Arrays;
import java.util.Locale;
+import java.util.Map;
/**
* A full-screen DialogFragment with image settings.
@@ -59,6 +60,8 @@ public class ImageSettingsDialogFragment extends DialogFragment {
private boolean mIsFeatured;
+ private Map mHttpHeaders;
+
private CharSequence mPreviousActionBarTitle;
private boolean mPreviousHomeAsUpEnabled;
private View mPreviousCustomView;
@@ -145,6 +148,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
try {
mImageMeta = new JSONObject(bundle.getString("imageMeta"));
+ mHttpHeaders = (Map) bundle.getSerializable("headerMap");
+
final String imageSrc = mImageMeta.getString("src");
final String imageFilename = imageSrc.substring(imageSrc.lastIndexOf("/") + 1);
@@ -175,7 +180,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
mIsFeatured = bundle.getBoolean("isFeatured", false);
mFeaturedCheckBox.setChecked(mIsFeatured);
}
-
} catch (JSONException e1) {
AppLog.d(AppLog.T.EDITOR, "Missing JSON properties");
}
@@ -318,7 +322,7 @@ private void loadThumbnail(final String src, final ImageView thumbnailImage) {
@Override
public void run() {
if (isAdded()) {
- final Uri localUri = Utils.downloadExternalMedia(getActivity(), Uri.parse(src));
+ final Uri localUri = Utils.downloadExternalMedia(getActivity(), Uri.parse(src), mHttpHeaders);
if (getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java
index 5a56ff530e27..6e74071c66a4 100755
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java
@@ -30,6 +30,8 @@ public class JsCallbackReceiver {
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_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";
@@ -159,14 +161,22 @@ public void executeCallback(String callbackId, String params) {
mListener.onLinkTapped(url, title);
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
- AppLog.d(AppLog.T.EDITOR, callbackId + ": " + params.substring(4));
+ 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 responseDataSet;
- if (params.startsWith("function=")) {
+ if (params.startsWith("function=") && params.contains(JS_CALLBACK_DELIMITER)) {
String functionName = params.substring("function=".length(), params.indexOf(JS_CALLBACK_DELIMITER));
List responseIds = new ArrayList<>();
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java
index eb3747dfcf3e..3ca172e685a6 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java
@@ -1130,6 +1130,11 @@ public void appendGallery(MediaGallery mediaGallery) {
editableText.insert(selectionEnd + 1, "\n\n");
}
+ @Override
+ public void setUrlForVideoPressId(String videoPressId, String url, String posterUrl) {
+
+ }
+
@Override
public boolean hasFailedMediaUploads() {
return false;
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java
index 89941ba79ead..27d9b7fa5082 100755
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java
@@ -10,5 +10,6 @@ public interface OnJsEditorStateChangedListener {
void onSelectionStyleChanged(Map changeSet);
void onMediaTapped(String mediaId, String url, JSONObject meta, String uploadStatus);
void onLinkTapped(String url, String title);
+ void onVideoPressInfoRequested(String videoId);
void onGetHtmlResponse(Map responseArgs);
}
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java
index cf06eb3e1a71..c4c5665defef 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java
@@ -6,19 +6,19 @@
import android.net.Uri;
import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.HTTPUtils;
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.HttpURLConnection;
import java.net.URLDecoder;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -27,7 +27,6 @@
import java.util.StringTokenizer;
public class Utils {
-
public static String getHtmlFromFile(Activity activity, String filename) {
try {
AssetManager assetManager = activity.getAssets();
@@ -156,24 +155,40 @@ public static Map getChangeMapFromSets(Set oldSet, Set new
return changeMap;
}
- public static Uri downloadExternalMedia(Context context, Uri imageUri) {
+ public static Uri downloadExternalMedia(Context context, Uri imageUri, Map headers) {
if(context != null && imageUri != null) {
File cacheDir = null;
- if(context.getApplicationContext() != null) {
+ if (context.getApplicationContext() != null) {
cacheDir = context.getCacheDir();
}
try {
InputStream inputStream;
- if(imageUri.toString().startsWith("content://")) {
+ if (imageUri.toString().startsWith("content://")) {
inputStream = context.getContentResolver().openInputStream(imageUri);
- if(inputStream == null) {
+ if (inputStream == null) {
AppLog.e(AppLog.T.UTILS, "openInputStream returned null");
return null;
}
} else {
- inputStream = (new URL(imageUri.toString())).openStream();
+ if (headers == null) {
+ headers = Collections.emptyMap();
+ }
+
+ HttpURLConnection conn = HTTPUtils.setupUrlConnection(imageUri.toString(), headers);
+
+ // If the HTTP response is a redirect, follow it
+ int responseCode = conn.getResponseCode();
+ if (responseCode != HttpURLConnection.HTTP_OK) {
+ if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
+ || responseCode == HttpURLConnection.HTTP_MOVED_TEMP
+ || responseCode == HttpURLConnection.HTTP_SEE_OTHER) {
+ conn = HTTPUtils.setupUrlConnection(conn.getHeaderField("Location"), headers);
+ }
+ }
+
+ inputStream = conn.getInputStream();
}
String fileName = "thumb-" + System.currentTimeMillis();
@@ -183,7 +198,7 @@ public static Uri downloadExternalMedia(Context context, Uri imageUri) {
byte[] data = new byte[1024];
int count;
- while((count = inputStream.read(data)) != -1) {
+ while ((count = inputStream.read(data)) != -1) {
output.write(data, 0, count);
}
diff --git a/WordPressEditor/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java b/WordPressEditor/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java
index 0a0b7860bc6e..bf2427eaa21a 100644
--- a/WordPressEditor/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java
+++ b/WordPressEditor/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java
@@ -69,6 +69,11 @@ public void appendMediaFile(MediaFile mediaFile, String imageUrl, ImageLoader im
public void appendGallery(MediaGallery mediaGallery) {
}
+ @Override
+ public void setUrlForVideoPressId(String videoPressId, String url, String posterUrl) {
+
+ }
+
@Override
public boolean hasFailedMediaUploads() {
return false;
diff --git a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java
index eb8e04f0866b..0edcce21abae 100644
--- a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java
+++ b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java
@@ -162,6 +162,16 @@ public void onFeaturedImageChanged(int mediaId) {
}
+ @Override
+ public void onVideoPressInfoRequested(String videoId) {
+
+ }
+
+ @Override
+ public String onAuthHeaderRequested(String url) {
+ return "";
+ }
+
@Override
public void onEditorFragmentInitialized() {
// arbitrary setup
diff --git a/libs/editor-common/assets/ZSSRichTextEditor.js b/libs/editor-common/assets/ZSSRichTextEditor.js
index d016bcc1e080..511240a39aa2 100755
--- a/libs/editor-common/assets/ZSSRichTextEditor.js
+++ b/libs/editor-common/assets/ZSSRichTextEditor.js
@@ -47,6 +47,9 @@ ZSSEditor.currentSelection;
// The current editing image
ZSSEditor.currentEditingImage;
+// The current editing video
+ZSSEditor.currentEditingVideo;
+
// The current editing link
ZSSEditor.currentEditingLink;
@@ -62,6 +65,10 @@ ZSSEditor.lastTappedNode = null;
// The default paragraph separator
ZSSEditor.defaultParagraphSeparator = 'p';
+// Video format tags supported by the [video] shortcode: https://codex.wordpress.org/Video_Shortcode
+// mp4, m4v and webm prioritized since they're supported by the stock player as of Android API 23
+ZSSEditor.videoShortcodeFormats = ["mp4", "m4v", "webm", "ogv", "wmv", "flv"];
+
/**
* The initializer function that must be called onLoad
*/
@@ -628,6 +635,25 @@ ZSSEditor.setBackgroundColor = function(color) {
ZSSEditor.sendEnabledStyles();
};
+/**
+ * @brief Wraps given HTML in paragraph tags and appends a new line
+ * @details This method makes sure that passed HTML is wrapped in a separate paragraph.
+ * It also appends a new opening paragraph tag and a space. This step is necessary to keep any spans or
+ * divs in the HTML from being read by the WebView as a style and applied to all future paragraphs.
+ */
+ZSSEditor.wrapInParagraphTags = function(html) {
+ var space = ' ';
+ var paragraphOpenTag = '<' + this.defaultParagraphSeparator + '>';
+ var paragraphCloseTag = '' + this.defaultParagraphSeparator + '>';
+
+ if (this.getFocusedField().getHTML().length == 0) {
+ html = paragraphOpenTag + html;
+ }
+ html = html + paragraphCloseTag + paragraphOpenTag + space;
+
+ return html;
+};
+
// Needs addClass method
ZSSEditor.insertLink = function(url, title) {
@@ -802,18 +828,9 @@ ZSSEditor.updateImage = function(url, alt) {
};
ZSSEditor.insertImage = function(url, remoteId, alt) {
- var space = ' ';
- var paragraphOpenTag = '<' + this.defaultParagraphSeparator + '>';
- var paragraphCloseTag = '' + this.defaultParagraphSeparator + '>';
-
var html = '';
- if (this.getFocusedField().getHTML().length == 0) {
- html = paragraphOpenTag + html;
- }
- html = html + paragraphCloseTag + paragraphOpenTag + space;
-
- this.insertHTML(html);
+ this.insertHTML(this.wrapInParagraphTags(html));
this.sendEnabledStyles();
};
@@ -831,9 +848,6 @@ ZSSEditor.insertImage = function(url, remoteId, alt) {
* does not check for that. It would be a mistake.
*/
ZSSEditor.insertLocalImage = function(imageNodeIdentifier, localImageUrl) {
- var space = ' ';
- var paragraphOpenTag = '<' + this.defaultParagraphSeparator + '>';
- var paragraphCloseTag = '' + this.defaultParagraphSeparator + '>';
var progressIdentifier = this.getImageProgressIdentifier(imageNodeIdentifier);
var imageContainerIdentifier = this.getImageContainerIdentifier(imageNodeIdentifier);
var imgContainerStart = '';
@@ -842,12 +856,7 @@ ZSSEditor.insertLocalImage = function(imageNodeIdentifier, localImageUrl) {
var image = '';
var html = imgContainerStart + progress+image + imgContainerEnd;
- if (this.getFocusedField().getHTML().length == 0) {
- html = paragraphOpenTag + html;
- }
- html = html + paragraphCloseTag + paragraphOpenTag + space;
-
- this.insertHTML(html);
+ this.insertHTML(this.wrapInParagraphTags(html));
this.sendEnabledStyles();
};
@@ -1097,6 +1106,417 @@ ZSSEditor.removeImage = function(imageNodeIdentifier) {
}
};
+/**
+ * @brief Inserts a video tag using the videoURL as source and posterURL as the
+ * image to show while video is loading.
+ *
+ * @param videoURL the url of the video to present
+ * @param posterURL the url of an image to show while the video is loading
+ * @param alt the alt description when the video is not supported.
+ *
+ */
+ZSSEditor.insertVideo = function(videoURL, posterURL, alt) {
+ var html = '';
+
+ this.insertHTML(html);
+ this.sendEnabledStyles();
+};
+
+/**
+ * @brief Inserts a video tag marked with a identifier using only a poster image. Useful for videos that need to be uploaded.
+ * @details By inserting a video with only a porter URL, we can make sure the video element is shown to the user
+ * as soon as it's selected for uploading. Once the video is successfully uploaded
+ * the application should call replaceLocalVideoWithRemoteVideo().
+ *
+ * @param videoNodeIdentifier This is a unique ID provided by the caller. It exists as
+ * a mechanism to update the video node with the remote URL
+ * when replaceLocalVideoWithRemoteVideo() is called.
+ * @param posterURL The URL of a poster image to display while the video is being uploaded.
+ */
+ZSSEditor.insertInProgressVideoWithIDUsingPosterImage = function(videoNodeIdentifier, posterURL) {
+ var space = ' ';
+ var progressIdentifier = this.getVideoProgressIdentifier(videoNodeIdentifier);
+ var videoContainerIdentifier = this.getVideoContainerIdentifier(videoNodeIdentifier);
+ var videoContainerStart = '';
+ var videoContainerEnd = '';
+ var progress = '';
+ var video = '';
+ var html = space + videoContainerStart + progress + video + videoContainerEnd + space;
+ this.insertHTML(html);
+ this.sendEnabledStyles();
+};
+
+ZSSEditor.getVideoNodeWithIdentifier = function(videoNodeIdentifier) {
+ return $('video[data-wpid="' + videoNodeIdentifier+'"]');
+};
+
+ZSSEditor.getVideoProgressIdentifier = function(videoNodeIdentifier) {
+ return 'progress_' + videoNodeIdentifier;
+};
+
+ZSSEditor.getVideoProgressNodeWithIdentifier = function(videoNodeIdentifier) {
+ return $('#'+this.getVideoProgressIdentifier(videoNodeIdentifier));
+};
+
+ZSSEditor.getVideoContainerIdentifier = function(videoNodeIdentifier) {
+ return 'video_container_' + videoNodeIdentifier;
+};
+
+ZSSEditor.getVideoContainerNodeWithIdentifier = function(videoNodeIdentifier) {
+ return $('#'+this.getVideoContainerIdentifier(videoNodeIdentifier));
+};
+
+
+/**
+ * @brief Replaces a local Video URL with a remote Video URL. Useful for videos that have
+ * just finished uploading.
+ * @details The remote Video can be available after a while, when uploading Videos. This method
+ * allows for the remote URL to be loaded once the upload completes.
+ *
+ * @param videoNodeIdentifier This is a unique ID provided by the caller. It exists as
+ * a mechanism to update the Video node with the remote URL
+ * when replaceLocalVideoWithRemoteVideo() is called.
+ * @param remoteVideoUrl The URL of the remote Video to display.
+ * @param remotePosterUrl The URL of thre remote poster image to display
+ * @param videopressID VideoPress Guid of the video if any
+ */
+ZSSEditor.replaceLocalVideoWithRemoteVideo = function(videoNodeIdentifier, remoteVideoUrl, remotePosterUrl, videopressID) {
+ var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
+
+ if (videoNode.length == 0) {
+ // even if the Video is not present anymore we must do callback
+ this.markVideoUploadDone(videoNodeIdentifier);
+ return;
+ }
+ videoNode.attr('src', remoteVideoUrl);
+ videoNode.attr('controls', '');
+ videoNode.attr('preload', 'metadata');
+ if (videopressID != '') {
+ videoNode.attr('data-wpvideopress', videopressID);
+ }
+ videoNode.attr('poster', remotePosterUrl);
+ var thisObj = this;
+ videoNode.on('webkitbeginfullscreen', function (event){ thisObj.sendVideoFullScreenStarted(); } );
+ videoNode.on('webkitendfullscreen', function (event){ thisObj.sendVideoFullScreenEnded(); } );
+ videoNode.on('error', function(event) { videoNode.load()} );
+ this.markVideoUploadDone(videoNodeIdentifier);
+};
+
+/**
+ * @brief Update the progress indicator for the Video identified with the value in progress.
+ *
+ * @param VideoNodeIdentifier This is a unique ID provided by the caller.
+ * @param progress A value between 0 and 1 indicating the progress on the Video.
+ */
+ZSSEditor.setProgressOnVideo = function(videoNodeIdentifier, progress) {
+ var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
+ if (videoNode.length == 0){
+ return;
+ }
+ if (progress < 1){
+ videoNode.addClass("uploading");
+ }
+
+ var videoProgressNode = this.getVideoProgressNodeWithIdentifier(videoNodeIdentifier);
+ if (videoProgressNode.length == 0){
+ return;
+ }
+ videoProgressNode.attr("value",progress);
+};
+
+/**
+ * @brief Notifies that the Video upload as finished
+ *
+ * @param VideoNodeIdentifier The unique Video ID for the uploaded Video
+ */
+ZSSEditor.markVideoUploadDone = function(videoNodeIdentifier) {
+ var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
+ if (videoNode.length > 0) {
+
+ // remove identifier attributed from Video
+ videoNode.removeAttr('data-wpid');
+
+ // remove uploading style
+ videoNode.removeClass("uploading");
+ videoNode.removeAttr("class");
+
+ // Remove all extra formatting nodes for progress
+ if (videoNode.parent().attr("id") == this.getVideoContainerIdentifier(videoNodeIdentifier)) {
+ // remove id from container to avoid to report a user removal
+ videoNode.parent().attr("id", "");
+ videoNode.parent().replaceWith(videoNode);
+ }
+ }
+ var joinedArguments = ZSSEditor.getJoinedFocusedFieldIdAndCaretArguments();
+ ZSSEditor.callback("callback-input", joinedArguments);
+ // We invoke the sendVideoReplacedCallback with a delay to avoid for
+ // it to be ignored by the webview because of the previous callback being done.
+ var thisObj = this;
+ setTimeout(function() { thisObj.sendVideoReplacedCallback(videoNodeIdentifier);}, 500);
+};
+
+/**
+ * @brief Callbacks to native that the video upload as finished and the local url was replaced by the remote url
+ *
+ * @param videoNodeIdentifier the unique video ID for the uploaded Video
+ */
+ZSSEditor.sendVideoReplacedCallback = function( videoNodeIdentifier ) {
+ var arguments = ['id=' + encodeURIComponent( videoNodeIdentifier )];
+
+ var joinedArguments = arguments.join( defaultCallbackSeparator );
+
+ this.callback("callback-video-replaced", joinedArguments);
+};
+
+/**
+ * @brief Callbacks to native that the video entered full screen mode
+ *
+ */
+ZSSEditor.sendVideoFullScreenStarted = function() {
+ this.callback("callback-video-fullscreen-started", "empty");
+};
+
+/**
+ * @brief Callbacks to native that the video entered full screen mode
+ *
+ */
+ZSSEditor.sendVideoFullScreenEnded = function() {
+ this.callback("callback-video-fullscreen-ended", "empty");
+};
+
+/**
+ * @brief Callbacks to native that the video upload as finished and the local url was replaced by the remote url
+ *
+ * @param videoNodeIdentifier the unique video ID for the uploaded Video
+ */
+ZSSEditor.sendVideoPressInfoRequest = function( videoPressID ) {
+ var arguments = ['id=' + encodeURIComponent( videoPressID )];
+
+ var joinedArguments = arguments.join( defaultCallbackSeparator );
+
+ this.callback("callback-videopress-info-request", joinedArguments);
+};
+
+
+/**
+ * @brief Marks the Video as failed to upload
+ *
+ * @param VideoNodeIdentifier This is a unique ID provided by the caller.
+ * @param message A message to show to the user, overlayed on the Video
+ */
+ZSSEditor.markVideoUploadFailed = function(videoNodeIdentifier, message) {
+ var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
+ if (videoNode.length == 0){
+ return;
+ }
+
+ var sizeClass = '';
+ if ( videoNode[0].width > 480 && videoNode[0].height > 240 ) {
+ sizeClass = "largeFail";
+ } else if ( videoNode[0].width < 100 || videoNode[0].height < 100 ) {
+ sizeClass = "smallFail";
+ }
+
+ videoNode.addClass('failed');
+
+ var videoContainerNode = this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier);
+ if(videoContainerNode.length != 0){
+ videoContainerNode.attr("data-failed", message);
+ videoNode.removeClass("uploading");
+ videoContainerNode.addClass('failed');
+ videoContainerNode.addClass(sizeClass);
+ }
+
+ var videoProgressNode = this.getVideoProgressNodeWithIdentifier(videoNodeIdentifier);
+ if (videoProgressNode.length != 0){
+ videoProgressNode.addClass('failed');
+ }
+};
+
+/**
+ * @brief Unmarks the Video as failed to upload
+ *
+ * @param VideoNodeIdentifier This is a unique ID provided by the caller.
+ */
+ZSSEditor.unmarkVideoUploadFailed = function(videoNodeIdentifier, message) {
+ var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
+ if (videoNode.length != 0){
+ videoNode.removeClass('failed');
+ }
+
+ var videoContainerNode = this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier);
+ if(videoContainerNode.length != 0){
+ videoContainerNode.removeAttr("data-failed");
+ videoContainerNode.removeClass('failed');
+ }
+
+ var videoProgressNode = this.getVideoProgressNodeWithIdentifier(videoNodeIdentifier);
+ if (videoProgressNode.length != 0){
+ videoProgressNode.removeClass('failed');
+ }
+};
+
+/**
+ * @brief Remove the Video from the DOM.
+ *
+ * @param videoNodeIdentifier This is a unique ID provided by the caller.
+ */
+ZSSEditor.removeVideo = function(videoNodeIdentifier) {
+ var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
+ if (videoNode.length != 0){
+ videoNode.remove();
+ }
+
+ // if Video is inside options container we need to remove the container
+ var videoContainerNode = this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier);
+ if (videoContainerNode.length != 0){
+ //reset id before removal to avoid detection of user removal
+ videoContainerNode.attr("id","");
+ videoContainerNode.remove();
+ }
+};
+
+ZSSEditor.replaceVideoPressVideosForShortcode = function ( html) {
+ // call methods to restore any transformed content from its visual presentation to its source code.
+ var regex = /';
this.insertHTML(this.wrapInParagraphTags(container));
}
@@ -2909,7 +2909,6 @@ ZSSField.prototype.sendVideoFullScreenEnded = function() {
// MARK: - Callback Execution
ZSSField.prototype.callback = function(callbackScheme, callbackPath) {
-
var url = callbackScheme + ":";
url = url + "id=" + this.getNodeId();
@@ -2921,7 +2920,11 @@ ZSSField.prototype.callback = function(callbackScheme, callbackPath) {
if (isUsingiOS) {
ZSSEditor.callbackThroughIFrame(url);
} else if (isUsingAndroid) {
- nativeCallbackHandler.executeCallback(callbackScheme, callbackPath);
+ if (nativeState.androidApiLevel < 17) {
+ ZSSEditor.callbackThroughIFrame(url);
+ } else {
+ nativeCallbackHandler.executeCallback(callbackScheme, callbackPath);
+ }
} else {
console.log(url);
}
diff --git a/libs/editor-common/assets/android-editor.html b/libs/editor-common/assets/android-editor.html
index b0bb0f5e99e1..623908c9e91a 100755
--- a/libs/editor-common/assets/android-editor.html
+++ b/libs/editor-common/assets/android-editor.html
@@ -3,6 +3,11 @@
%%TITLE%%
+
From 108ed89ccdc90830463e57ce4ff5ba64ef0acc8f Mon Sep 17 00:00:00 2001
From: Alex Forcier
Date: Tue, 23 Feb 2016 15:28:15 -0800
Subject: [PATCH 6/6] Squashed 'libs/editor/' changes from 50ddd25..7f3b589
7f3b589 Updated example app to use the modified onMediaUploadSucceeded call
b203bf0 Fixed an outdated reference in the ZSSEditor
f9dbe59 Editor 0.6 version bump
76711d6 Updated localized string calls in ZSSEditor video methods
c0b804b Merge branch 'feature/visual-editor' into feature/visual-editor-insert-video
b87b917 Escape quotes for URLs being passed to the JS editor
d06f2e2 Use hasAttribute to null-check when parsing for failed media items
d3dff92 Wrapped called to getThumbnailURL in notNullStr() in the editor fragment
951bb3d Removed unnecessary console.logs
11feabd Drop data-failed attribute from ZSSEditor.insertLocalVideo
3c54e44 Merge branch 'feature/visual-editor' into feature/visual-editor-insert-video
7feb0e9 Use API<19 compatibility upload UI for video (imported from images)
d806115 Include videos in count of failed media uploads
2fc0151 Mark uploading videos as failed when opening a post containing them, so they can be retried
c6ac1e9 Send VideoPress shortcode and poster URL to visual editor when video upload completes
8f74166 Stop adding unnecessary 'data-wpid' attributed to completed video uploads
b16606a Merge branch 'feature/visual-editor' into feature/visual-editor-insert-video
916c025 Handle video upload failure and support retrying
fe3e855 Support cancelling in-progress video uploads and removing the video from the UI
2b14632 Differentiate media types in OnJsEditorStateChangedListener.onMediaTapped() native-side
26032af Added handling for completed video uploads, using the new remote URL and clearing placeholder and container
adebbff Add CSS for video_container in API<19 compatibility mode
282dbea Use appropriate JS methods for local video insertion and upload progress updates
d6ca1c7 Use placeholder image instead of video tag for uploading videos
10ae531 Track media type along with id in current uploads
2c9380b s/photo/image/ in example activity demo menu
7486336 Added video upload demo options to example activity
d923987 Merge branch 'feature/visual-editor' into feature/visual-editor-insert-video
7487a90 Add support for remote video insertion to visual editor
5497330 Merge branch 'feature/visual-editor' into feature/visual-editor-add-video
9594791 Append break tags when inserting video tags for non-VideoPress videos
a8a7858 Remove unused fullscreen video functionality from ZSSEditor
git-subtree-dir: libs/editor
git-subtree-split: 7f3b589d033fea945b226ccd85f31fe7dd7f0832
---
WordPressEditor/build.gradle | 4 +-
.../android/editor/EditorFragment.java | 156 ++++++++---
.../editor/EditorFragmentAbstract.java | 15 ++
.../editor/EditorMediaUploadListener.java | 4 +-
.../android/editor/JsCallbackReceiver.java | 9 +-
.../OnJsEditorStateChangedListener.java | 4 +-
.../org/wordpress/android/editor/Utils.java | 7 +
.../example/EditorExampleActivity.java | 42 ++-
example/src/main/res/values/strings.xml | 6 +-
.../editor-common/assets/ZSSRichTextEditor.js | 253 +++++++++---------
libs/editor-common/assets/editor-android.css | 18 +-
11 files changed, 324 insertions(+), 194 deletions(-)
diff --git a/WordPressEditor/build.gradle b/WordPressEditor/build.gradle
index 4898792eca6a..c63ac5abf601 100644
--- a/WordPressEditor/build.gradle
+++ b/WordPressEditor/build.gradle
@@ -23,8 +23,8 @@ android {
buildToolsVersion "23.0.2"
defaultConfig {
- versionCode 5
- versionName "0.5"
+ versionCode 6
+ versionName "0.6"
minSdkVersion 14
targetSdkVersion 23
}
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java
index 439cce75807d..e81895deba11 100755
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java
@@ -36,6 +36,7 @@
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.JSONUtils;
import org.wordpress.android.util.ProfilingUtils;
+import org.wordpress.android.util.ShortcodeUtils;
import org.wordpress.android.util.StringUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.util.UrlUtils;
@@ -93,7 +94,7 @@ public class EditorFragment extends EditorFragmentAbstract implements View.OnCli
private ConcurrentHashMap mWaitingMediaFiles;
private Set mWaitingGalleries;
- private Set mUploadingMediaIds;
+ private Map mUploadingMedia;
private Set mFailedMediaIds;
private MediaGallery mUploadingMediaGallery;
@@ -137,7 +138,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
mWaitingMediaFiles = new ConcurrentHashMap<>();
mWaitingGalleries = Collections.newSetFromMap(new ConcurrentHashMap());
- mUploadingMediaIds = new HashSet<>();
+ mUploadingMedia = new HashMap<>();
mFailedMediaIds = new HashSet<>();
// -- WebView configuration
@@ -234,7 +235,7 @@ public void onResume() {
@Override
public void onDetach() {
// Soft cancel (delete flag off) all media uploads currently in progress
- for (String mediaId : mUploadingMediaIds) {
+ for (String mediaId : mUploadingMedia.keySet()) {
mEditorFragmentListener.onMediaUploadCancelClicked(mediaId, false);
}
super.onDetach();
@@ -411,7 +412,7 @@ public void onClick(View v) {
mEditorFragmentListener.onTrackableEvent(TrackableEvent.HTML_BUTTON_TAPPED);
// Don't switch to HTML mode if currently uploading media
- if (!mUploadingMediaIds.isEmpty()) {
+ if (!mUploadingMedia.isEmpty()) {
((ToggleButton) v).setChecked(false);
if (isAdded()) {
@@ -753,17 +754,38 @@ public void appendMediaFile(final MediaFile mediaFile, final String mediaUrl, Im
return;
}
+ final String safeMediaUrl = Utils.escapeQuotes(mediaUrl);
+
mWebView.post(new Runnable() {
@Override
public void run() {
if (URLUtil.isNetworkUrl(mediaUrl)) {
String mediaId = mediaFile.getMediaId();
- mWebView.execJavaScriptFromString("ZSSEditor.insertImage('" + mediaUrl + "', '" + mediaId + "');");
+ if (mediaFile.isVideo()) {
+ String posterUrl = Utils.escapeQuotes(StringUtils.notNullStr(mediaFile.getThumbnailURL()));
+ String videoPressId = ShortcodeUtils.getVideoPressIdFromShortCode(
+ mediaFile.getVideoPressShortCode());
+
+ mWebView.execJavaScriptFromString("ZSSEditor.insertVideo('" + safeMediaUrl + "', '" +
+ posterUrl + "', '" + videoPressId + "');");
+ } else {
+ mWebView.execJavaScriptFromString("ZSSEditor.insertImage('" + safeMediaUrl + "', '" + mediaId +
+ "');");
+ }
} else {
String id = mediaFile.getMediaId();
- mWebView.execJavaScriptFromString("ZSSEditor.insertLocalImage(" + id + ", '" + mediaUrl + "');");
- mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + id + ", " + 0 + ");");
- mUploadingMediaIds.add(id);
+ if (mediaFile.isVideo()) {
+ String posterUrl = Utils.escapeQuotes(StringUtils.notNullStr(mediaFile.getThumbnailURL()));
+ mWebView.execJavaScriptFromString("ZSSEditor.insertLocalVideo(" + id + ", '" + posterUrl +
+ "');");
+ mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnVideo(" + id + ", " + 0 + ");");
+ mUploadingMedia.put(id, MediaType.VIDEO);
+ } else {
+ mWebView.execJavaScriptFromString("ZSSEditor.insertLocalImage(" + id + ", '" + safeMediaUrl +
+ "');");
+ mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + id + ", " + 0 + ");");
+ mUploadingMedia.put(id, MediaType.IMAGE);
+ }
}
}
});
@@ -796,8 +818,8 @@ public void setUrlForVideoPressId(final String videoId, final String videoUrl, f
mWebView.post(new Runnable() {
@Override
public void run() {
- mWebView.execJavaScriptFromString("ZSSEditor.setVideoPressLinks('" + videoId + "', '" +
- videoUrl + "', '" + posterUrl + "');");
+ mWebView.execJavaScriptFromString("ZSSEditor.setVideoPressLinks('" + videoId + "', '" +
+ Utils.escapeQuotes(videoUrl) + "', '" + Utils.escapeQuotes(posterUrl) + "');");
}
});
}
@@ -823,27 +845,48 @@ public void setContentPlaceholder(CharSequence placeholderText) {
}
@Override
- public void onMediaUploadSucceeded(final String mediaId, final String remoteId, final String remoteUrl) {
- mWebView.post(new Runnable() {
- @Override
- public void run() {
- mWebView.execJavaScriptFromString("ZSSEditor.replaceLocalImageWithRemoteImage(" + mediaId + ", '" +
- remoteId + "', '" + remoteUrl + "');");
- mUploadingMediaIds.remove(mediaId);
- }
- });
+ public void onMediaUploadSucceeded(final String localMediaId, final MediaFile mediaFile) {
+ final MediaType mediaType = mUploadingMedia.get(localMediaId);
+ if (mediaType != null) {
+ mWebView.post(new Runnable() {
+ @Override
+ public void run() {
+ String remoteUrl = Utils.escapeQuotes(mediaFile.getFileURL());
+ if (mediaType.equals(MediaType.IMAGE)) {
+ String remoteMediaId = mediaFile.getMediaId();
+ mWebView.execJavaScriptFromString("ZSSEditor.replaceLocalImageWithRemoteImage(" + localMediaId +
+ ", '" + remoteMediaId + "', '" + remoteUrl + "');");
+ } else if (mediaType.equals(MediaType.VIDEO)) {
+ String posterUrl = Utils.escapeQuotes(StringUtils.notNullStr(mediaFile.getThumbnailURL()));
+ String videoPressId = ShortcodeUtils.getVideoPressIdFromShortCode(
+ mediaFile.getVideoPressShortCode());
+ mWebView.execJavaScriptFromString("ZSSEditor.replaceLocalVideoWithRemoteVideo(" + localMediaId +
+ ", '" + remoteUrl + "', '" + posterUrl + "', '" + videoPressId + "');");
+ }
+ mUploadingMedia.remove(localMediaId);
+ }
+ });
+ }
}
@Override
public void onMediaUploadProgress(final String mediaId, final float progress) {
- mWebView.post(new Runnable() {
- @Override
- public void run() {
- String progressString = String.format(Locale.US, "%.1f", progress);
- mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + mediaId + ", " +
- progressString + ");");
- }
- });
+ final MediaType mediaType = mUploadingMedia.get(mediaId);
+ if (mediaType != null) {
+ mWebView.post(new Runnable() {
+ @Override
+ public void run() {
+ String progressString = String.format(Locale.US, "%.1f", progress);
+ if (mediaType.equals(MediaType.IMAGE)) {
+ mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + mediaId + ", " +
+ progressString + ");");
+ } else if (mediaType.equals(MediaType.VIDEO)) {
+ mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnVideo(" + mediaId + ", " +
+ progressString + ");");
+ }
+ }
+ });
+ }
}
@Override
@@ -851,10 +894,18 @@ public void onMediaUploadFailed(final String mediaId, final String errorMessage)
mWebView.post(new Runnable() {
@Override
public void run() {
- mWebView.execJavaScriptFromString("ZSSEditor.markImageUploadFailed(" + mediaId + ", '"
- + errorMessage.replace("'", "\\'").replace("\"", "\\\"") + "');");
+ MediaType mediaType = mUploadingMedia.get(mediaId);
+ switch (mediaType) {
+ case IMAGE:
+ mWebView.execJavaScriptFromString("ZSSEditor.markImageUploadFailed(" + mediaId + ", '"
+ + Utils.escapeQuotes(errorMessage) + "');");
+ break;
+ case VIDEO:
+ mWebView.execJavaScriptFromString("ZSSEditor.markVideoUploadFailed(" + mediaId + ", '"
+ + Utils.escapeQuotes(errorMessage) + "');");
+ }
mFailedMediaIds.add(mediaId);
- mUploadingMediaIds.remove(mediaId);
+ mUploadingMedia.remove(mediaId);
}
});
}
@@ -900,11 +951,11 @@ public void run() {
// If there are images that are still in progress (because the editor exited before they completed),
// set them to failed, so the user can restart them (otherwise they will stay stuck in 'uploading' mode)
- mWebView.execJavaScriptFromString("ZSSEditor.markAllUploadingImagesAsFailed('"
+ mWebView.execJavaScriptFromString("ZSSEditor.markAllUploadingMediaAsFailed('"
+ getString(R.string.tap_to_try_again) + "');");
// Update the list of failed media uploads
- mWebView.execJavaScriptFromString("ZSSEditor.getFailedImages();");
+ mWebView.execJavaScriptFromString("ZSSEditor.getFailedMedia();");
hideActionBarIfNeeded();
@@ -980,7 +1031,11 @@ public void run() {
});
}
- public void onMediaTapped(final String mediaId, String url, final JSONObject meta, String uploadStatus) {
+ public void onMediaTapped(final String mediaId, final MediaType mediaType, final JSONObject meta, String uploadStatus) {
+ if (mediaType == null) {
+ return;
+ }
+
switch (uploadStatus) {
case "uploading":
// Display 'cancel upload' dialog
@@ -993,8 +1048,14 @@ public void onClick(DialogInterface dialog, int id) {
mWebView.post(new Runnable() {
@Override
public void run() {
- mWebView.execJavaScriptFromString("ZSSEditor.removeImage(" + mediaId + ");");
- mUploadingMediaIds.remove(mediaId);
+ switch (mediaType) {
+ case IMAGE:
+ mWebView.execJavaScriptFromString("ZSSEditor.removeImage(" + mediaId + ");");
+ break;
+ case VIDEO:
+ mWebView.execJavaScriptFromString("ZSSEditor.removeVideo(" + mediaId + ");");
+ }
+ mUploadingMedia.remove(mediaId);
}
});
dialog.dismiss();
@@ -1017,15 +1078,30 @@ public void onClick(DialogInterface dialog, int id) {
mWebView.post(new Runnable() {
@Override
public void run() {
- mWebView.execJavaScriptFromString("ZSSEditor.unmarkImageUploadFailed(" + mediaId + ");");
- mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + mediaId + ", " + 0 + ");");
+ switch (mediaType) {
+ case IMAGE:
+ mWebView.execJavaScriptFromString("ZSSEditor.unmarkImageUploadFailed(" + mediaId
+ + ");");
+ mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + mediaId + ", "
+ + 0 + ");");
+ break;
+ case VIDEO:
+ mWebView.execJavaScriptFromString("ZSSEditor.unmarkVideoUploadFailed(" + mediaId
+ + ");");
+ mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnVideo(" + mediaId + ", "
+ + 0 + ");");
+ }
mFailedMediaIds.remove(mediaId);
- mUploadingMediaIds.add(mediaId);
+ mUploadingMedia.put(mediaId, mediaType);
}
});
break;
default:
- // Show media options fragment
+ if (!mediaType.equals(MediaType.IMAGE)) {
+ return;
+ }
+
+ // Only show image options fragment for image taps
FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager.findFragmentByTag(ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_TAG) != null) {
@@ -1125,7 +1201,7 @@ public void onGetHtmlResponse(Map inputArgs) {
mJavaScriptResult = inputArgs.get("result");
mGetSelectedTextCountDownLatch.countDown();
break;
- case "getFailedImages":
+ case "getFailedMedia":
String[] mediaIds = inputArgs.get("ids").split(",");
for (String mediaId : mediaIds) {
if (!mediaId.equals("")) {
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java
index c8101d88829a..d2eb0d011709 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java
@@ -27,6 +27,21 @@ public abstract class EditorFragmentAbstract extends Fragment {
// TODO: remove this as soon as we can (we'll need to drop the legacy editor or fix html2spanned translation)
public abstract Spanned getSpannedContent();
+ public enum MediaType {
+ IMAGE, VIDEO;
+
+ public static MediaType fromString(String value) {
+ if (value != null) {
+ for (MediaType mediaType : MediaType.values()) {
+ if (value.equalsIgnoreCase(mediaType.toString())) {
+ return mediaType;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
private static final String FEATURED_IMAGE_SUPPORT_KEY = "featured-image-supported";
private static final String FEATURED_IMAGE_WIDTH_KEY = "featured-image-width";
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java
index 5d52fd95943b..26ba44150379 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java
@@ -1,7 +1,9 @@
package org.wordpress.android.editor;
+import org.wordpress.android.util.helpers.MediaFile;
+
public interface EditorMediaUploadListener {
- void onMediaUploadSucceeded(String localId, String remoteId, String remoteUrl);
+ void onMediaUploadSucceeded(String localId, MediaFile mediaFile);
void onMediaUploadProgress(String localId, float progress);
void onMediaUploadFailed(String localId, String errorMessage);
void onGalleryMediaUploadSucceeded(long galleryId, String remoteId, int remaining);
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java
index 6e74071c66a4..c957e095d8e0 100755
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java
@@ -13,6 +13,8 @@
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 = "~";
@@ -103,6 +105,7 @@ public void executeCallback(String callbackId, String params) {
mediaIds.add("id");
mediaIds.add("url");
mediaIds.add("meta");
+ mediaIds.add("type");
Set mediaDataSet = Utils.splitValuePairDelimitedString(params, JS_CALLBACK_DELIMITER, mediaIds);
Map mediaDataMap = Utils.buildMapFromKeyValuePairs(mediaDataSet);
@@ -114,6 +117,8 @@ public void executeCallback(String callbackId, String params) {
mediaUrl = Utils.decodeHtml(mediaUrl);
}
+ MediaType mediaType = MediaType.fromString(mediaDataMap.get("type"));
+
String mediaMeta = mediaDataMap.get("meta");
JSONObject mediaMetaJson = new JSONObject();
@@ -136,7 +141,7 @@ public void executeCallback(String callbackId, String params) {
}
}
- mListener.onMediaTapped(mediaId, mediaUrl, mediaMetaJson, uploadStatus);
+ mListener.onMediaTapped(mediaId, mediaType, mediaMetaJson, uploadStatus);
break;
case CALLBACK_LINK_TAP:
// Extract and HTML-decode the link data from the callback params
@@ -188,7 +193,7 @@ public void executeCallback(String callbackId, String params) {
case "getSelectedText":
responseIds.add("result");
break;
- case "getFailedImages":
+ case "getFailedMedia":
responseIds.add("ids");
}
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java
index 27d9b7fa5082..58de357ea496 100755
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/OnJsEditorStateChangedListener.java
@@ -4,11 +4,13 @@
import java.util.Map;
+import static org.wordpress.android.editor.EditorFragmentAbstract.MediaType;
+
public interface OnJsEditorStateChangedListener {
void onDomLoaded();
void onSelectionChanged(Map selectionArgs);
void onSelectionStyleChanged(Map changeSet);
- void onMediaTapped(String mediaId, String url, JSONObject meta, String uploadStatus);
+ void onMediaTapped(String mediaId, MediaType mediaType, JSONObject meta, String uploadStatus);
void onLinkTapped(String url, String title);
void onVideoPressInfoRequested(String videoId);
void onGetHtmlResponse(Map responseArgs);
diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java
index c4c5665defef..62297005e542 100644
--- a/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java
+++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/Utils.java
@@ -73,6 +73,13 @@ public static String decodeHtml(String html) {
return html;
}
+ public static String escapeQuotes(String text) {
+ if (text != null) {
+ text = text.replace("'", "\\'").replace("\"", "\\\"");
+ }
+ return text;
+ }
+
/**
* Splits a delimited string into a set of strings.
* @param string the delimited string to split
diff --git a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java
index 0980b250ee40..e9525daad480 100644
--- a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java
+++ b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java
@@ -37,8 +37,10 @@ public class EditorExampleActivity extends AppCompatActivity implements EditorFr
public static final String MEDIA_REMOTE_ID_SAMPLE = "123";
- private static final int SELECT_PHOTO_MENU_POSITION = 0;
- private static final int SELECT_PHOTO_FAIL_MENU_POSITION = 1;
+ private static final int SELECT_IMAGE_MENU_POSITION = 0;
+ private static final int SELECT_IMAGE_FAIL_MENU_POSITION = 1;
+ private static final int SELECT_VIDEO_MENU_POSITION = 2;
+ private static final int SELECT_VIDEO_FAIL_MENU_POSITION = 3;
private EditorFragmentAbstract mEditorFragment;
@@ -79,8 +81,10 @@ public void onBackPressed() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
- menu.add(0, SELECT_PHOTO_MENU_POSITION, 0, getString(R.string.select_photo));
- menu.add(0, SELECT_PHOTO_FAIL_MENU_POSITION, 0, getString(R.string.select_photo_fail));
+ menu.add(0, SELECT_IMAGE_MENU_POSITION, 0, getString(R.string.select_image));
+ menu.add(0, SELECT_IMAGE_FAIL_MENU_POSITION, 0, getString(R.string.select_image_fail));
+ menu.add(0, SELECT_VIDEO_MENU_POSITION, 0, getString(R.string.select_video));
+ menu.add(0, SELECT_VIDEO_FAIL_MENU_POSITION, 0, getString(R.string.select_video_fail));
}
@Override
@@ -88,17 +92,31 @@ public boolean onContextItemSelected(MenuItem item) {
Intent intent = new Intent(Intent.ACTION_PICK);
switch (item.getItemId()) {
- case SELECT_PHOTO_MENU_POSITION:
+ case SELECT_IMAGE_MENU_POSITION:
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
- intent = Intent.createChooser(intent, getString(R.string.select_photo));
+ intent = Intent.createChooser(intent, getString(R.string.select_image));
startActivityForResult(intent, ADD_MEDIA_ACTIVITY_REQUEST_CODE);
return true;
- case SELECT_PHOTO_FAIL_MENU_POSITION:
+ case SELECT_IMAGE_FAIL_MENU_POSITION:
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
- intent = Intent.createChooser(intent, getString(R.string.select_photo_fail));
+ intent = Intent.createChooser(intent, getString(R.string.select_image_fail));
+
+ startActivityForResult(intent, ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE);
+ return true;
+ case SELECT_VIDEO_MENU_POSITION:
+ intent.setType("video/*");
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent = Intent.createChooser(intent, getString(R.string.select_video));
+
+ startActivityForResult(intent, ADD_MEDIA_ACTIVITY_REQUEST_CODE);
+ return true;
+ case SELECT_VIDEO_FAIL_MENU_POSITION:
+ intent.setType("video/*");
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent = Intent.createChooser(intent, getString(R.string.select_video_fail));
startActivityForResult(intent, ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE);
return true;
@@ -120,6 +138,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
MediaFile mediaFile = new MediaFile();
String mediaId = String.valueOf(System.currentTimeMillis());
mediaFile.setMediaId(mediaId);
+ mediaFile.setVideo(imageUri.toString().contains("video"));
switch (requestCode) {
case ADD_MEDIA_ACTIVITY_REQUEST_CODE:
@@ -217,8 +236,11 @@ public void run() {
count += 0.1;
}
- ((EditorMediaUploadListener) mEditorFragment).onMediaUploadSucceeded(mediaId,
- MEDIA_REMOTE_ID_SAMPLE, mediaUrl);
+ MediaFile mediaFile = new MediaFile();
+ mediaFile.setMediaId(MEDIA_REMOTE_ID_SAMPLE);
+ mediaFile.setFileURL(mediaUrl);
+
+ ((EditorMediaUploadListener) mEditorFragment).onMediaUploadSucceeded(mediaId, mediaFile);
if (mFailedUploads.containsKey(mediaId)) {
mFailedUploads.remove(mediaId);
diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml
index 753eff8d993f..bfffb0de8fb7 100644
--- a/example/src/main/res/values/strings.xml
+++ b/example/src/main/res/values/strings.xml
@@ -13,6 +13,8 @@
Post 2 Content
Quoted text
]]>
- Select a photo
- Select a photo (failure demo)
+ Select an image
+ Select an image (failure demo)
+ Select a video
+ Select a video (failure demo)
diff --git a/libs/editor-common/assets/ZSSRichTextEditor.js b/libs/editor-common/assets/ZSSRichTextEditor.js
index 4918e6ee6306..6cab98bca9cd 100755
--- a/libs/editor-common/assets/ZSSRichTextEditor.js
+++ b/libs/editor-common/assets/ZSSRichTextEditor.js
@@ -1079,16 +1079,19 @@ ZSSEditor.unmarkImageUploadFailed = function(imageNodeIdentifier) {
/**
* @brief Marks all in-progress images as failed to upload
*/
-ZSSEditor.markAllUploadingImagesAsFailed = function(message) {
+ZSSEditor.markAllUploadingMediaAsFailed = function(message) {
var html = ZSSEditor.getField("zss_field_content").getHTML();
var tmp = document.createElement( "div" );
var tmpDom = $( tmp ).html( html );
var matches = tmpDom.find("img.uploading");
for(var i = 0; i < matches.size(); i++) {
- var mediaId = matches[i].getAttribute("data-wpid");
- if (mediaId != null) {
+ if (matches[i].hasAttribute('data-wpid')) {
+ var mediaId = matches[i].getAttribute('data-wpid');
ZSSEditor.markImageUploadFailed(mediaId, message);
+ } else if (matches[i].hasAttribute('data-video_wpid')) {
+ var videoId = matches[i].getAttribute('data-video_wpid');
+ ZSSEditor.markVideoUploadFailed(videoId, message);
}
}
};
@@ -1096,18 +1099,24 @@ ZSSEditor.markAllUploadingImagesAsFailed = function(message) {
/**
* @brief Sends a callback with a list of failed images
*/
-ZSSEditor.getFailedImages = function() {
+ZSSEditor.getFailedMedia = function() {
var html = ZSSEditor.getField("zss_field_content").getHTML();
var tmp = document.createElement( "div" );
var tmpDom = $( tmp ).html( html );
var matches = tmpDom.find("img.failed");
- var functionArgument = "function=getFailedImages";
+ var functionArgument = "function=getFailedMedia";
var mediaIdArray = [];
- for (var i = 0; i < matches.size(); i ++) {
- var mediaId = matches[i].getAttribute("data-wpid");
- if (mediaId != null) {
+ for (var i = 0; i < matches.size(); i++) {
+ var mediaId;
+ if (matches[i].hasAttribute("data-wpid")) {
+ mediaId = matches[i].getAttribute("data-wpid");
+ } else if (matches[i].hasAttribute("data-video_wpid")) {
+ mediaId = matches[i].getAttribute("data-video_wpid");
+ }
+
+ if (mediaId.length > 0) {
mediaIdArray.push(mediaId);
}
}
@@ -1138,45 +1147,76 @@ ZSSEditor.removeImage = function(imageNodeIdentifier) {
* @brief Inserts a video tag using the videoURL as source and posterURL as the
* image to show while video is loading.
*
- * @param videoURL the url of the video to present
- * @param posterURL the url of an image to show while the video is loading
- * @param alt the alt description when the video is not supported.
+ * @param videoURL the url of the video
+ * @param posterURL the url of an image to show while the video is loading
+ * @param videoPressID the VideoPress ID of the video, when applicable
*
*/
-ZSSEditor.insertVideo = function(videoURL, posterURL, alt) {
- var html = '' + alt + '';
+ZSSEditor.insertVideo = function(videoURL, posterURL, videopressID) {
+ var html = '';
+
+ this.insertHTML(this.wrapInParagraphTags(html));
this.sendEnabledStyles();
};
/**
- * @brief Inserts a video tag marked with a identifier using only a poster image. Useful for videos that need to be uploaded.
- * @details By inserting a video with only a porter URL, we can make sure the video element is shown to the user
- * as soon as it's selected for uploading. Once the video is successfully uploaded
- * the application should call replaceLocalVideoWithRemoteVideo().
+ * @brief Inserts a placeholder image tag for in-progress video uploads, marked with an identifier.
+ * @details The image shown can be the video's poster if available - otherwise the default poster image is used.
+ * Using an image instead of a video placeholder is a departure from iOS, necessary because the original
+ * method caused occasional WebView freezes on Android.
+ * Once the video is successfully uploaded, the application should call replaceLocalVideoWithRemoteVideo().
*
* @param videoNodeIdentifier This is a unique ID provided by the caller. It exists as
* a mechanism to update the video node with the remote URL
* when replaceLocalVideoWithRemoteVideo() is called.
* @param posterURL The URL of a poster image to display while the video is being uploaded.
*/
-ZSSEditor.insertInProgressVideoWithIDUsingPosterImage = function(videoNodeIdentifier, posterURL) {
- var space = ' ';
+ZSSEditor.insertLocalVideo = function(videoNodeIdentifier, posterURL) {
var progressIdentifier = this.getVideoProgressIdentifier(videoNodeIdentifier);
var videoContainerIdentifier = this.getVideoContainerIdentifier(videoNodeIdentifier);
- var videoContainerStart = '';
+
+ if (nativeState.androidApiLevel > 18) {
+ var videoContainerClass = 'video_container';
+ var progressElement = '';
+ } else {
+ // Before API 19, the WebView didn't support progress tags. Use an upload overlay instead of a progress bar
+ var videoContainerClass = 'video_container compat';
+ var progressElement = '' + nativeState.localizedStringUploading
+ + '';
+ }
+
+ var videoContainerStart = '';
var videoContainerEnd = '';
- var progress = '';
- var video = '';
- var html = space + videoContainerStart + progress + video + videoContainerEnd + space;
- this.insertHTML(html);
+
+ if (posterURL == '') {
+ posterURL = "wpposter.svg";
+ }
+
+ var image = '';
+ var html = videoContainerStart + progressElement + image + videoContainerEnd;
+
+ this.insertHTML(this.wrapInParagraphTags(html));
this.sendEnabledStyles();
};
ZSSEditor.getVideoNodeWithIdentifier = function(videoNodeIdentifier) {
- return $('video[data-wpid="' + videoNodeIdentifier+'"]');
+ var videoNode = $('img[data-video_wpid="' + videoNodeIdentifier+'"]');
+ if (videoNode.length == 0) {
+ videoNode = $('video[data-wpid="' + videoNodeIdentifier+'"]');
+ }
+ return videoNode;
};
ZSSEditor.getVideoProgressIdentifier = function(videoNodeIdentifier) {
@@ -1195,40 +1235,41 @@ ZSSEditor.getVideoContainerNodeWithIdentifier = function(videoNodeIdentifier) {
return $('#'+this.getVideoContainerIdentifier(videoNodeIdentifier));
};
-
/**
- * @brief Replaces a local Video URL with a remote Video URL. Useful for videos that have
- * just finished uploading.
- * @details The remote Video can be available after a while, when uploading Videos. This method
- * allows for the remote URL to be loaded once the upload completes.
+ * @brief Replaces the image placeholder with a video element containing the uploaded video's attributes,
+ * and removes the upload container.
*
- * @param videoNodeIdentifier This is a unique ID provided by the caller. It exists as
- * a mechanism to update the Video node with the remote URL
- * when replaceLocalVideoWithRemoteVideo() is called.
- * @param remoteVideoUrl The URL of the remote Video to display.
- * @param remotePosterUrl The URL of thre remote poster image to display
- * @param videopressID VideoPress Guid of the video if any
+ * @param videoNodeIdentifier The unique id of the video upload
+ * @param remoteVideoUrl The URL of the remote video to display
+ * @param remotePosterUrl The URL of the remote poster image to display
+ * @param videopressID The VideoPress ID of the video, where applicable
*/
ZSSEditor.replaceLocalVideoWithRemoteVideo = function(videoNodeIdentifier, remoteVideoUrl, remotePosterUrl, videopressID) {
- var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
+ var imagePlaceholderNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
- if (videoNode.length == 0) {
- // even if the Video is not present anymore we must do callback
- this.markVideoUploadDone(videoNodeIdentifier);
- return;
- }
- videoNode.attr('src', remoteVideoUrl);
- videoNode.attr('controls', '');
- videoNode.attr('preload', 'metadata');
- if (videopressID != '') {
- videoNode.attr('data-wpvideopress', videopressID);
+ if (imagePlaceholderNode.length != 0) {
+ var videoNode = document.createElement("video");
+ videoNode.setAttribute('webkit-playsinline', '');
+ videoNode.setAttribute('onclick', '');
+ videoNode.setAttribute('src', remoteVideoUrl);
+ videoNode.setAttribute('controls', 'controls');
+ videoNode.setAttribute('preload', 'metadata');
+ if (videopressID != '') {
+ videoNode.setAttribute('data-wpvideopress', videopressID);
+ }
+ videoNode.setAttribute('poster', remotePosterUrl);
+
+ // Replace upload container and placeholder image with the uploaded video node
+ var containerNode = imagePlaceholderNode.parent();
+ containerNode.replaceWith(videoNode);
}
- videoNode.attr('poster', remotePosterUrl);
+
+ var joinedArguments = ZSSEditor.getJoinedFocusedFieldIdAndCaretArguments();
+ ZSSEditor.callback("callback-input", joinedArguments);
+ // We invoke the sendVideoReplacedCallback with a delay to avoid for
+ // it to be ignored by the webview because of the previous callback being done.
var thisObj = this;
- videoNode.on('webkitbeginfullscreen', function (event){ thisObj.sendVideoFullScreenStarted(); } );
- videoNode.on('webkitendfullscreen', function (event){ thisObj.sendVideoFullScreenEnded(); } );
- videoNode.on('error', function(event) { videoNode.load()} );
- this.markVideoUploadDone(videoNodeIdentifier);
+ setTimeout(function() { thisObj.sendVideoReplacedCallback(videoNodeIdentifier);}, 500);
};
/**
@@ -1246,6 +1287,13 @@ ZSSEditor.setProgressOnVideo = function(videoNodeIdentifier, progress) {
videoNode.addClass("uploading");
}
+ // Revert to non-compatibility video container once video upload has begun. This centers the overlays on the
+ // placeholder image (instead of the screen), while still circumventing the small container bug the compat class
+ // was added to fix
+ if (progress > 0) {
+ this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier).removeClass("compat");
+ }
+
var videoProgressNode = this.getVideoProgressNodeWithIdentifier(videoNodeIdentifier);
if (videoProgressNode.length == 0){
return;
@@ -1253,37 +1301,6 @@ ZSSEditor.setProgressOnVideo = function(videoNodeIdentifier, progress) {
videoProgressNode.attr("value",progress);
};
-/**
- * @brief Notifies that the Video upload as finished
- *
- * @param VideoNodeIdentifier The unique Video ID for the uploaded Video
- */
-ZSSEditor.markVideoUploadDone = function(videoNodeIdentifier) {
- var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier);
- if (videoNode.length > 0) {
-
- // remove identifier attributed from Video
- videoNode.removeAttr('data-wpid');
-
- // remove uploading style
- videoNode.removeClass("uploading");
- videoNode.removeAttr("class");
-
- // Remove all extra formatting nodes for progress
- if (videoNode.parent().attr("id") == this.getVideoContainerIdentifier(videoNodeIdentifier)) {
- // remove id from container to avoid to report a user removal
- videoNode.parent().attr("id", "");
- videoNode.parent().replaceWith(videoNode);
- }
- }
- var joinedArguments = ZSSEditor.getJoinedFocusedFieldIdAndCaretArguments();
- ZSSEditor.callback("callback-input", joinedArguments);
- // We invoke the sendVideoReplacedCallback with a delay to avoid for
- // it to be ignored by the webview because of the previous callback being done.
- var thisObj = this;
- setTimeout(function() { thisObj.sendVideoReplacedCallback(videoNodeIdentifier);}, 500);
-};
-
/**
* @brief Callbacks to native that the video upload as finished and the local url was replaced by the remote url
*
@@ -1297,22 +1314,6 @@ ZSSEditor.sendVideoReplacedCallback = function( videoNodeIdentifier ) {
this.callback("callback-video-replaced", joinedArguments);
};
-/**
- * @brief Callbacks to native that the video entered full screen mode
- *
- */
-ZSSEditor.sendVideoFullScreenStarted = function() {
- this.callback("callback-video-fullscreen-started", "empty");
-};
-
-/**
- * @brief Callbacks to native that the video entered full screen mode
- *
- */
-ZSSEditor.sendVideoFullScreenEnded = function() {
- this.callback("callback-video-fullscreen-ended", "empty");
-};
-
/**
* @brief Callbacks to native that the video upload as finished and the local url was replaced by the remote url
*
@@ -1360,6 +1361,9 @@ ZSSEditor.markVideoUploadFailed = function(videoNodeIdentifier, message) {
if (videoProgressNode.length != 0){
videoProgressNode.addClass('failed');
}
+
+ // Delete the compatibility overlay if present
+ videoContainerNode.find("span.upload-overlay").addClass("failed");
};
/**
@@ -1383,6 +1387,9 @@ ZSSEditor.unmarkVideoUploadFailed = function(videoNodeIdentifier) {
if (videoProgressNode.length != 0){
videoProgressNode.removeClass('failed');
}
+
+ // Display the compatibility overlay again if present
+ videoContainerNode.find("span.upload-overlay").removeClass("failed");
};
/**
@@ -1498,7 +1505,7 @@ ZSSEditor.applyVideoFormattingCallback = function( match ) {
out += ' preload="metadata"';
}
- out += ' onclick="" controls="controls">';
+ out += ' onclick="" controls="controls"> ';
return out;
}
@@ -1531,8 +1538,6 @@ ZSSEditor.setVideoPressLinks = function(videopressID, videoURL, posterURL ) {
videoNode.attr('controls', '');
videoNode.attr('poster', posterURL);
var thisObj = this;
- videoNode.on('webkitbeginfullscreen', function (event){ thisObj.sendVideoFullScreenStarted(); } );
- videoNode.on('webkitendfullscreen', function (event){ thisObj.sendVideoFullScreenEnded(); } );
videoNode.load();
};
@@ -2785,7 +2790,7 @@ ZSSField.prototype.handleTapEvent = function(e) {
if (targetNode.nodeName.toLowerCase() == 'img') {
// If the image is uploading, or is a local image do not select it.
- if ( targetNode.dataset.wpid ) {
+ if ( targetNode.dataset.wpid || targetNode.dataset.video_wpid ) {
this.sendImageTappedCallback( targetNode );
return;
}
@@ -2858,17 +2863,21 @@ ZSSField.prototype.handleTapEvent = function(e) {
}
};
-ZSSField.prototype.sendImageTappedCallback = function( imageNode ) {
- var meta = JSON.stringify( ZSSEditor.extractImageMeta( imageNode ) );
- var imageId = "";
- if ( imageNode.hasAttribute( 'data-wpid' ) ){
- imageId = imageNode.getAttribute( 'data-wpid' )
+ZSSField.prototype.sendImageTappedCallback = function(imageNode) {
+ var meta = JSON.stringify(ZSSEditor.extractImageMeta(imageNode));
+ var imageId = "", mediaType = "image";
+ if (imageNode.hasAttribute('data-wpid')){
+ imageId = imageNode.getAttribute('data-wpid');
+ } else if (imageNode.hasAttribute('data-video_wpid')){
+ imageId = imageNode.getAttribute('data-video_wpid');
+ mediaType = "video";
}
- var arguments = ['id=' + encodeURIComponent( imageId ),
- 'url=' + encodeURIComponent( imageNode.src ),
- 'meta=' + encodeURIComponent( meta )];
+ var arguments = ['id=' + encodeURIComponent(imageId),
+ 'url=' + encodeURIComponent(imageNode.src),
+ 'meta=' + encodeURIComponent(meta),
+ 'type=' + mediaType];
- var joinedArguments = arguments.join( defaultCallbackSeparator );
+ var joinedArguments = arguments.join(defaultCallbackSeparator);
var thisObj = this;
@@ -2890,22 +2899,6 @@ ZSSField.prototype.sendVideoTappedCallback = function( videoNode ) {
ZSSEditor.callback('callback-video-tap', joinedArguments);
}
-/**
- * @brief Callbacks to native that the video entered full screen mode
- *
- */
-ZSSField.prototype.sendVideoFullScreenStarted = function() {
- this.callback("callback-video-fullscreen-started", "empty");
-};
-
-/**
- * @brief Callbacks to native that the video entered full screen mode
- *
- */
-ZSSField.prototype.sendVideoFullScreenEnded = function() {
- this.callback("callback-video-fullscreen-ended", "empty");
-};
-
// MARK: - Callback Execution
ZSSField.prototype.callback = function(callbackScheme, callbackPath) {
diff --git a/libs/editor-common/assets/editor-android.css b/libs/editor-common/assets/editor-android.css
index e3d0aef8701a..34d8dd54b6fe 100644
--- a/libs/editor-common/assets/editor-android.css
+++ b/libs/editor-common/assets/editor-android.css
@@ -30,13 +30,15 @@ video::-webkit-media-controls-fullscreen-button {
}
/* Used only on older APIs (API<19), where using inline-block is buggy and sometimes displays a very small container */
-span.img_container.compat {
+span.img_container.compat,
+span.video_container.compat {
display: block;
}
/* Used on API<19 to darken the image so that the 'uploading' and 'retry' overlays can still be seen when the image is
light */
-.img_container .upload-overlay-bg {
+.img_container .upload-overlay-bg,
+.video_container .upload-overlay-bg {
position: absolute;
width: 100%;
height: 100%;
@@ -48,19 +50,22 @@ light */
than its containing image. The upload-overlay-bg is larger as well, leaving a dark line below the image. By setting
display:block on the image and setting a width limit we get around this issue. */
-.img_container .upload-overlay-bg ~ img.uploading {
+.img_container .upload-overlay-bg ~ img.uploading,
+.video_container .upload-overlay-bg ~ img.uploading {
display:block;
max-width:100%;
}
-.img_container .upload-overlay-bg ~ img.failed {
+.img_container .upload-overlay-bg ~ img.failed,
+.video_container .upload-overlay-bg ~ img.failed{
display:block;
max-width:100%;
}
/* Used only on older APIs (API<19) instead of a progress bar for uploading images, since the WebView at those API
levels does not support the progress tag */
-.img_container .upload-overlay {
+.img_container .upload-overlay,
+.video_container .upload-overlay{
position: absolute;
top: 50%;
-webkit-transform: translateY(-50%);
@@ -75,6 +80,7 @@ display:block on the image and setting a width limit we get around this issue. *
color: white;
}
-.img_container .upload-overlay.failed {
+.img_container .upload-overlay.failed,
+.video_container .upload-overlay.failed{
visibility: hidden;
}
\ No newline at end of file