From 108ed89ccdc90830463e57ce4ff5ba64ef0acc8f Mon Sep 17 00:00:00 2001
From: Alex Forcier
Date: Tue, 23 Feb 2016 15:28:15 -0800
Subject: [PATCH] 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 = '';
+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