From c5b98a88c01551e8dbf44366072607d364ec01dd Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Wed, 6 Apr 2016 18:58:13 +0200 Subject: [PATCH 1/4] Squashed 'libs/editor/' changes from a35d654..0f6fba8 0f6fba8 Merge pull request #334 from wordpress-mobile/issue/292-backspace-bug 041f285 Changed ZSSEditor.getYCaretInfo to find parent contenteditable divs correctly 92cb325 Fixed an issue with losing cursor focus on API<19 0c2b5fd Made blockquote parsing handle DIVs tags as paragraphs c62ce8e Convert divs to paragraph tags when extracting the HTML from the editor f5e5d26 Changed getFocusedField to use this.focusedField, in order to avoid div targeting issues now that paragraphs are also divs c925ebd Add line height and margin styling to div tags (duplicated from p tags, and excluding the contenteditable and separator divs eb1677c Drop defaultParagraphSeparator assignment and use divs instead of ps for paragraphs (WebView default) 5e90578 Merge pull request #333 from wordpress-mobile/issue/258-remove-failed-uploads-when-switching-to-html-mode 9a0ef46 Merge commit '4c9324cf1eee00b66c76e0d5a917c86e1293a845' into develop ac96e74 fix #258: prompt the user to delete failed uploads before switching to HTML mode b856f7b update to android-gradle 2.0.0-rc1 e6f4e6b Add missing classes for images inserted from media library - also fix a bug with undefined alt text 31d2970 Add missing attributes for uploaded images ce6ab9d update to android-gradle-2.0.0-rc1 git-subtree-dir: libs/editor git-subtree-split: 0f6fba8fc434c4007c7cdd300c4dd9fa2df5a218 --- WordPressEditor/build.gradle | 2 +- .../android/editor/EditorFragment.java | 102 +++++++++++------- .../src/main/res/values/strings.xml | 3 + example/build.gradle | 2 +- .../editor-common/assets/ZSSRichTextEditor.js | 69 ++++++++---- libs/editor-common/assets/editor-android.css | 9 ++ 6 files changed, 128 insertions(+), 59 deletions(-) diff --git a/WordPressEditor/build.gradle b/WordPressEditor/build.gradle index 63cd3a5c681b..5bbf8d5b439b 100644 --- a/WordPressEditor/build.gradle +++ b/WordPressEditor/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-beta7' + classpath 'com.android.tools.build:gradle:2.0.0-rc1' } } 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 ccef283f6791..d149212e7b1d 100755 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java @@ -5,6 +5,7 @@ import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; @@ -407,55 +408,82 @@ protected void initJsEditor() { } } - @Override - public void onClick(View v) { - int id = v.getId(); - if (id == R.id.format_bar_button_html) { - mEditorFragmentListener.onTrackableEvent(TrackableEvent.HTML_BUTTON_TAPPED); + public void checkForFailedUploadAndSwitchToHtmlMode(final ToggleButton toggleButton) { + // Show an Alert Dialog asking the user if he wants to remove all failed media before upload + if (hasFailedMediaUploads()) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.editor_failed_uploads_switch_html) + .setPositiveButton(R.string.editor_remove_failed_uploads, new OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // Clear failed uploads and switch to HTML mode + removeAllFailedMediaUploads(); + toggleHtmlMode(toggleButton); + } + }).setNegativeButton(android.R.string.cancel, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + toggleButton.setChecked(false); + } + }); + builder.create().show(); + } else { + toggleHtmlMode(toggleButton); + } + } - // Don't switch to HTML mode if currently uploading media - if (!mUploadingMedia.isEmpty()) { - ((ToggleButton) v).setChecked(false); + private void toggleHtmlMode(final ToggleButton toggleButton) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.HTML_BUTTON_TAPPED); - if (isAdded()) { - ToastUtils.showToast(getActivity(), R.string.alert_html_toggle_uploading, ToastUtils.Duration.LONG); - } - return; + // Don't switch to HTML mode if currently uploading media + if (!mUploadingMedia.isEmpty()) { + toggleButton.setChecked(false); + + if (isAdded()) { + ToastUtils.showToast(getActivity(), R.string.alert_html_toggle_uploading, ToastUtils.Duration.LONG); } + return; + } - clearFormatBarButtons(); - updateFormatBarEnabledState(true); + clearFormatBarButtons(); + updateFormatBarEnabledState(true); - if (((ToggleButton) v).isChecked()) { - mSourceViewTitle.setText(getTitle()); + if (toggleButton.isChecked()) { + mSourceViewTitle.setText(getTitle()); - SpannableString spannableContent = new SpannableString(getContent()); - HtmlStyleUtils.styleHtmlForDisplay(spannableContent); - mSourceViewContent.setText(spannableContent); + SpannableString spannableContent = new SpannableString(getContent()); + HtmlStyleUtils.styleHtmlForDisplay(spannableContent); + mSourceViewContent.setText(spannableContent); - mWebView.setVisibility(View.GONE); - mSourceView.setVisibility(View.VISIBLE); + mWebView.setVisibility(View.GONE); + mSourceView.setVisibility(View.VISIBLE); - mSourceViewContent.requestFocus(); - mSourceViewContent.setSelection(0); + mSourceViewContent.requestFocus(); + mSourceViewContent.setSelection(0); - InputMethodManager imm = ((InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE)); - imm.showSoftInput(mSourceViewContent, InputMethodManager.SHOW_IMPLICIT); - } else { - mWebView.setVisibility(View.VISIBLE); - mSourceView.setVisibility(View.GONE); + InputMethodManager imm = ((InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE)); + imm.showSoftInput(mSourceViewContent, InputMethodManager.SHOW_IMPLICIT); + } else { + mWebView.setVisibility(View.VISIBLE); + mSourceView.setVisibility(View.GONE); - mTitle = mSourceViewTitle.getText().toString(); - mContentHtml = mSourceViewContent.getText().toString(); - updateVisualEditorFields(); + mTitle = mSourceViewTitle.getText().toString(); + mContentHtml = mSourceViewContent.getText().toString(); + updateVisualEditorFields(); - // Update the list of failed media uploads - mWebView.execJavaScriptFromString("ZSSEditor.getFailedMedia();"); + // Update the list of failed media uploads + mWebView.execJavaScriptFromString("ZSSEditor.getFailedMedia();"); - // Reset selection to avoid buggy cursor behavior - mWebView.execJavaScriptFromString("ZSSEditor.resetSelectionOnField('zss_field_content');"); - } + // Reset selection to avoid buggy cursor behavior + mWebView.execJavaScriptFromString("ZSSEditor.resetSelectionOnField('zss_field_content');"); + } + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.format_bar_button_html) { + checkForFailedUploadAndSwitchToHtmlMode((ToggleButton) v); } else if (id == R.id.format_bar_button_media) { mEditorFragmentListener.onTrackableEvent(TrackableEvent.MEDIA_BUTTON_TAPPED); ((ToggleButton) v).setChecked(false); diff --git a/WordPressEditor/src/main/res/values/strings.xml b/WordPressEditor/src/main/res/values/strings.xml index 5da604328659..f81a0ae87e52 100644 --- a/WordPressEditor/src/main/res/values/strings.xml +++ b/WordPressEditor/src/main/res/values/strings.xml @@ -81,4 +81,7 @@ Image thumbnail + Some media uploads have failed. You can\'t switch to HTML mode + in this state. Remove all failed uploads and continue? + Remove failed uploads diff --git a/example/build.gradle b/example/build.gradle index e91d4b7beda4..ff876849c1c0 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-beta7' + classpath 'com.android.tools.build:gradle:2.0.0-rc1' } } diff --git a/libs/editor-common/assets/ZSSRichTextEditor.js b/libs/editor-common/assets/ZSSRichTextEditor.js index 5cde6455bf63..e4e136bc3b09 100755 --- a/libs/editor-common/assets/ZSSRichTextEditor.js +++ b/libs/editor-common/assets/ZSSRichTextEditor.js @@ -26,7 +26,9 @@ const NodeName = { LI: "LI", CODE: "CODE", SPAN: "SPAN", - BR: "BR" + BR: "BR", + DIV: "DIV", + BODY: "BODY" }; // The editor object @@ -63,7 +65,7 @@ ZSSEditor.editableFields = {}; ZSSEditor.lastTappedNode = null; // The default paragraph separator -ZSSEditor.defaultParagraphSeparator = 'p'; +ZSSEditor.defaultParagraphSeparator = 'div'; // 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 @@ -92,7 +94,6 @@ ZSSEditor.init = function() { } document.execCommand('insertBrOnReturn', false, false); - document.execCommand('defaultParagraphSeparator', false, this.defaultParagraphSeparator); var editor = $('div.field').each(function() { var editableField = new ZSSField($(this)); @@ -172,7 +173,7 @@ ZSSEditor.formatNewLine = function(e) { this.formatNewLineInsideBlockquote(e); } else if (!ZSSEditor.isCommandEnabled('insertOrderedList') && !ZSSEditor.isCommandEnabled('insertUnorderedList')) { - document.execCommand('formatBlock', false, 'p'); + document.execCommand('formatBlock', false, 'div'); } } else { e.preventDefault(); @@ -192,20 +193,13 @@ ZSSEditor.getField = function(fieldId) { }; ZSSEditor.getFocusedField = function() { - var currentField = $(this.closerParentNodeWithName('div')); + var currentField = $(this.findParentContenteditableDiv()); var currentFieldId; if (currentField) { currentFieldId = currentField.attr('id'); } - while (currentField && (!currentFieldId || this.editableFields[currentFieldId] == null)) { - currentField = this.closerParentNodeStartingAtNode('div', currentField); - if (currentField) { - currentFieldId = currentField.attr('id'); - } - } - if (!currentFieldId) { ZSSEditor.resetSelectionOnField('zss_field_content'); currentFieldId = 'zss_field_content'; @@ -480,7 +474,7 @@ ZSSEditor.getYCaretInfo = function() { // if (needsToWorkAroundNewlineBug) { var closerParentNode = ZSSEditor.closerParentNode(); - var closerDiv = ZSSEditor.closerParentNodeWithName('div'); + var closerDiv = ZSSEditor.findParentContenteditableDiv(); var fontSize = $(closerParentNode).css('font-size'); var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5); @@ -654,7 +648,7 @@ ZSSEditor.setHeading = function(heading) { }; ZSSEditor.setParagraph = function() { - var formatTag = "p"; + var formatTag = "div"; var formatBlock = document.queryCommandValue('formatBlock'); if (formatBlock.length > 0 && formatBlock.toLowerCase() == formatTag) { @@ -1028,8 +1022,11 @@ ZSSEditor.updateImage = function(url, alt) { }; ZSSEditor.insertImage = function(url, remoteId, alt) { - var html = '' + alt + ''; - + var html = '' + alt
+    }
+    html += '' this.insertHTML(this.wrapInParagraphTags(html)); this.sendEnabledStyles(); }; @@ -1138,6 +1135,9 @@ ZSSEditor.finishLocalImageSwap = function(image, imageNode, imageNodeIdentifier, imageNode.attr('remoteurl', image.getAttribute("remoteurl")); } imageNode.attr('src', image.src); + // Set extra attributes and classes used by WordPress + imageNode.attr({'width': image.width, 'height': image.height}); + imageNode.addClass("alignnone size-full"); ZSSEditor.markImageUploadDone(imageNodeIdentifier); var joinedArguments = ZSSEditor.getJoinedFocusedFieldIdAndCaretArguments(); ZSSEditor.callback("callback-input", joinedArguments); @@ -2289,7 +2289,7 @@ ZSSEditor.removeVisualFormatting = function( html ) { str = ZSSEditor.replaceVideoPressVideosForShortcode( str ); str = ZSSEditor.replaceVideosForShortcode( str ); return str; -} +}; ZSSEditor.insertHTML = function(html) { document.execCommand('insertHTML', false, html); @@ -2618,7 +2618,9 @@ ZSSEditor.getAncestorElementForSettingBlockquote = function(range) { || parentElement.nodeName == NodeName.OL || parentElement.nodeName == NodeName.LI || parentElement.nodeName == NodeName.CODE - || parentElement.nodeName == NodeName.SPAN)) { + || parentElement.nodeName == NodeName.SPAN + // Include nested divs, but ignore the parent contenteditable field div + || (parentElement.nodeName == NodeName.DIV && parentElement.parentElement.nodeName != NodeName.BODY))) { parentElement = parentElement.parentNode; } @@ -2740,6 +2742,23 @@ ZSSEditor.hasPreviousSiblingWithName = function(node, siblingNodeName) { // MARK: - Parent nodes & tags +ZSSEditor.findParentContenteditableDiv = function() { + var parentNode = null; + var selection = window.getSelection(); + if (selection.rangeCount < 1) { + return null; + } + var range = selection.getRangeAt(0).cloneRange(); + + var referenceNode = this.closerParentNodeWithNameRelativeToNode('div', range.commonAncestorContainer); + + while (referenceNode.parentNode.nodeName != NodeName.BODY) { + referenceNode = this.closerParentNodeWithNameRelativeToNode('div', referenceNode.parentNode); + } + + return referenceNode; +}; + ZSSEditor.closerParentNode = function() { var parentNode = null; @@ -3246,7 +3265,7 @@ ZSSField.prototype.wrapCaretInParagraphIfNecessary = function() var range = selection.getRangeAt(0); if (range.startContainer == range.endContainer) { - var paragraph = document.createElement("p"); + var paragraph = document.createElement("div"); var textNode = document.createTextNode("​"); paragraph.appendChild(textNode); @@ -3285,7 +3304,11 @@ ZSSField.prototype.isEmpty = function() { }; ZSSField.prototype.getHTML = function() { - var html = wp.saveText(this.wrappedObject.html()); + var html = this.wrappedObject.html(); + if (ZSSEditor.defaultParagraphSeparator == 'div') { + html = html.replace(/(/igm, '

'); + } + html = wp.saveText(html); html = ZSSEditor.removeVisualFormatting( html ); return html; }; @@ -3312,6 +3335,12 @@ ZSSField.prototype.setHTML = function(html) { ZSSEditor.currentEditingImage = null; var mutatedHTML = wp.loadText(html); mutatedHTML = ZSSEditor.applyVisualFormatting(mutatedHTML); + + if (ZSSEditor.defaultParagraphSeparator == 'div') { + // Replace the paragraph tags we get from wpload with divs + mutatedHTML = mutatedHTML.replace(/(/igm, ''); + } + this.wrappedObject.html(mutatedHTML); }; diff --git a/libs/editor-common/assets/editor-android.css b/libs/editor-common/assets/editor-android.css index c1ae09e730ca..cd57c242452a 100644 --- a/libs/editor-common/assets/editor-android.css +++ b/libs/editor-common/assets/editor-android.css @@ -16,6 +16,15 @@ video::-webkit-media-controls-fullscreen-button { display: none; } +/* Duplicates paragraph tag formatting for div tags, which are needed on Android API 19+ due to autocorrect issues: +https://bugs.chromium.org/p/chromium/issues/detail?id=599890 +*/ +div:not(.field):not(#separatorDiv) { + line-height: 24px; + margin-top: 0px; + margin-bottom: 24px; +} + /* --- API<19 workarounds --- */ /* Used only on older APIs (API<19), which don't support CSS filter effects (specifically, blur). */ From d2f7ed4e05fa13e80fbe573f043fac43baf5e0b1 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Fri, 8 Apr 2016 14:34:32 +0200 Subject: [PATCH 2/4] Squashed 'libs/editor/' changes from 0f6fba8..41a1010 41a1010 Merge pull request #337 from wordpress-mobile/issue/336-media-insert-paragraph-fix 767f36f Added comment for API<19 media insertion newline hack 1678bd1 Updating to gradle 2.0 c7a583d Fixed adding newline after media insertion for API<19 2e12295 Corrected some variable names in media insertion methods 200c8d5 Modified paragraph tag wrapping for media insertion to handle div tags correctly 94aa080 Updating to rc3 19cfc2e Merge commit 'c5b98a88c01551e8dbf44366072607d364ec01dd' into develop 64a065f Updating the rest of the modules to buildToolsVersion 23.0.3 f2c8351 Merge commit '4c9324cf1eee00b66c76e0d5a917c86e1293a845' into develop 3878f51 Add missing classes for images inserted from media library - also fix a bug with undefined alt text 3a4fe83 Add missing attributes for uploaded images 61f219e update to android-gradle-2.0.0-rc1 29e1264 Merge commit 'c6efe0a9190244d40e64300efc9cca56ae5acd5c' into develop a061001 Merge branch 'develop' into issue/120editor-initial-focus 0aa44f3 Merge commit '9b0b5fab24db9f435b278ad0b9e77d5895135700' into develop 0e8a35f Merge pull request #3897 from wordpress-mobile/issue/260editor-clear-failed-images-on-upload 85e19fb Null check getParentRangeOfFocusedNode/setRange in onMutationObserved - in case editor is not in focus 778bc7a Show the software keyboard once the DOM has loaded e0b6c72 Focus on the title field when opening posts 58c156b New EditorFragment method to removeAllFailedMediaUploads() 533abee New JS function ZSSEditor.removeAllFailedMediaUploads 29db13e Merge pull request #3896 from wordpress-mobile/issue/300editor-broken-images-after-upload-2 c53cb9d remove debug logs a29070e Use remoteurl in the link wrapper 9abfb9d Merge branch 'develop' into issue/300editor-broken-images-after-upload-2 964938e Merge branch 'develop' into issue/297editor-backspace-media f6acb17 update to com.android.tools.build:gradle:2.0.0-beta7 e71320e Merge branch 'develop' into issue/297editor-backspace-media 2ff37a5 Merge commit '8db246f15ce6f4d2c7f7f7ec51c68b87e9a66c2f' into develop 7c57f6f Changed MutationObserver handling to check if the WebView supports it, rather than rely on API levels 256916c Refactor: grouped mutation observation methods together c129a3b Refactored DOM element mutation listening, delegating everything to one trackNodeForMutation method 3df3bbd Changed MutationObserver behavior to track individual media nodes instead of each contenteditable div f096312 Moved failed media methods to the generic media method group b13515d Parse for failed media when returning from HTML to visual mode f193d88 Track DOMNodeRemoved events when parsing for failed media 44b4e92 Fixed a variable name error in ZSSEditor.removeImage b2342da On API<19, use DOMNodeRemoved events to track media deletions (instead of the unsupported MutationObserver used for newer APIs) e5e7693 Merge branch 'develop' into issue/297editor-backspace-media 06b252d Merge pull request #3804 from wordpress-mobile/issue/enable-editor-debug-mode 2daf87e Consume KEYCODE_VOLUME_UP event when debug print is called 8519f0d broken retries 8bd6476 Merge branch 'issue/enable-editor-debug-mode' into issue/300editor-broken-images-after-upload-2 06d70f9 use a remoteUrl attribute to avoid seeing broken image if download failed e5c9d8a remove debug action bar button and log raw html when volume up button is pressed 19f7043 fix function call errors dae02b2 Add back image swapping onError 515e508 fix wordpress-mobile/WordPress-Editor-Android#300: Retry download onError after an upload 05936de add missing comment 316f566 Updated gradle to 2.0.0-beta6 d6871e8 Fixes an issue where manually deleting uploading/failed media will cause the caret to disappear eb05458 Notify native through a callback whenever uploading/failed media are manually deleted 4fe64ad catch a common JS exception 3d45aff Merge branch 'develop' into issue/288editor-log-js-errors-in-crashlytics 8db4d8c fix wordpress-mobile/WordPress-Editor-Android#288: new EditorWebViewAbstract.ErrorListener used to forward JS errors to Crashlytics f2ba93d Keep the format bar disabled on rotation when the title field is in focus f2f6c0f Rely on ZSSEditor to flag uploads as completed in native-side checks 1fc3ed1 Added null checking for MediaType onMediaUploadFailed 223c7f0 Wait until the ZSSEditor has replaced the local media with remote before marking it as completed 4ff61f7 Strip any trailing   when returning the title git-subtree-dir: libs/editor git-subtree-split: 41a1010939cb0af0cc43672558265000623aefd2 --- WordPressEditor/build.gradle | 4 +-- example/build.gradle | 4 +-- .../editor-common/assets/ZSSRichTextEditor.js | 36 ++++++++++++------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/WordPressEditor/build.gradle b/WordPressEditor/build.gradle index 5bbf8d5b439b..2d47277c4fa6 100644 --- a/WordPressEditor/build.gradle +++ b/WordPressEditor/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-rc1' + classpath 'com.android.tools.build:gradle:2.0.0' } } @@ -20,7 +20,7 @@ android { publishNonDefault true compileSdkVersion 23 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" defaultConfig { versionCode 8 diff --git a/example/build.gradle b/example/build.gradle index ff876849c1c0..6fdfd5842919 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-rc1' + classpath 'com.android.tools.build:gradle:2.0.0' } } @@ -15,7 +15,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" defaultConfig { applicationId "org.wordpress.editorexample" diff --git a/libs/editor-common/assets/ZSSRichTextEditor.js b/libs/editor-common/assets/ZSSRichTextEditor.js index e4e136bc3b09..e05190dd8537 100755 --- a/libs/editor-common/assets/ZSSRichTextEditor.js +++ b/libs/editor-common/assets/ZSSRichTextEditor.js @@ -740,12 +740,12 @@ ZSSEditor.setBackgroundColor = function(color) { }; /** - * @brief Wraps given HTML in paragraph tags and appends a new line + * @brief Wraps given HTML in paragraph tags, appends a new line, and inserts it into the field * @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) { +ZSSEditor.insertHTMLWrappedInParagraphTags = function(html) { var space = '
'; var paragraphOpenTag = '<' + this.defaultParagraphSeparator + '>'; var paragraphCloseTag = ''; @@ -753,9 +753,18 @@ ZSSEditor.wrapInParagraphTags = function(html) { if (this.getFocusedField().getHTML().length == 0) { html = paragraphOpenTag + html; } - html = html + paragraphCloseTag + paragraphOpenTag + space; - return html; + // Without this line, API<19 WebView will reset the caret to the start of the document, inserting the new line + // there instead of under the newly added media item + if (nativeState.androidApiLevel < 19) { + html = html + '​'; + } + + // Due to the way the WebView handles divs, we need to add a new paragraph in a separate insertion - otherwise, + // the new paragraph will be nested within the existing paragraph. + this.insertHTML(html); + + this.insertHTML(paragraphOpenTag + space + paragraphCloseTag); }; // Needs addClass method @@ -1024,10 +1033,12 @@ ZSSEditor.updateImage = function(url, alt) { ZSSEditor.insertImage = function(url, remoteId, alt) { var html = '' + alt
+        html += '' - this.insertHTML(this.wrapInParagraphTags(html)); + html += '"/>'; + + this.insertHTMLWrappedInParagraphTags(html); + this.sendEnabledStyles(); }; @@ -1064,7 +1075,7 @@ ZSSEditor.insertLocalImage = function(imageNodeIdentifier, localImageUrl) { var image = ''; var html = imgContainerStart + progressElement + image + imgContainerEnd; - this.insertHTML(this.wrapInParagraphTags(html)); + this.insertHTMLWrappedInParagraphTags(html); ZSSEditor.trackNodeForMutation(this.getImageContainerNodeWithIdentifier(imageNodeIdentifier)); @@ -1352,7 +1363,8 @@ ZSSEditor.insertVideo = function(videoURL, posterURL, videopressID) { html += '>'; - this.insertHTML(this.wrapInParagraphTags(html)); + this.insertHTMLWrappedInParagraphTags(html); + this.sendEnabledStyles(); }; @@ -1394,7 +1406,7 @@ ZSSEditor.insertLocalVideo = function(videoNodeIdentifier, posterURL) { var image = ''; var html = videoContainerStart + progressElement + image + videoContainerEnd; - this.insertHTML(this.wrapInParagraphTags(html)); + this.insertHTMLWrappedInParagraphTags(html); ZSSEditor.trackNodeForMutation(this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier)); @@ -2235,13 +2247,13 @@ ZSSEditor.insertGallery = function( imageIds, type, columns ) { shortcode = '[gallery columns="' + columns + '" ids="' + imageIds + '"]'; } - this.insertHTML(this.wrapInParagraphTags(shortcode)); + this.insertHTMLWrappedInParagraphTags(shortcode); } ZSSEditor.insertLocalGallery = function( placeholderId ) { var container = '[' + nativeState.localizedStringUploadingGallery + ']'; - this.insertHTML(this.wrapInParagraphTags(container)); + this.insertHTMLWrappedInParagraphTags(container); } ZSSEditor.replacePlaceholderGallery = function( placeholderId, imageIds, type, columns ) { From 52910dd0664cf2031a6a1c78f3cb1b170a4eeff9 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Mon, 11 Apr 2016 09:11:28 +0200 Subject: [PATCH 3/4] Squashed 'libs/editor/' changes from 41a1010..7be0b57 7be0b57 Merge pull request #342 from wordpress-mobile/issue/340-wrap-new-post-links-in-paragraphs 6decc08 Wrap links in a new paragraph when the post is empty 04e0570 Merge pull request #338 from wordpress-mobile/issue/fix-travis-and-kill-jacoco 0372a90 Merge pull request #332 from wordpress-mobile/issue/307-expand-selection-to-word-when-creating-a-link 76883ef Update travis config to use build tools 23.0.3 e0b6a7d remove jacoco code coverage 5670205 homemade selection expansion 5749a16 Merge branch 'develop' into issue/307-expand-selection-to-word-when-creating-a-link 1b4a632 Expand selection to the word when creating a link and nothing is selected git-subtree-dir: libs/editor git-subtree-split: 7be0b577ed7c57301cda9fd99f1d1ca9f2a49dd3 --- .travis.yml | 2 +- README.md | 2 +- WordPressEditor/build.gradle | 36 +------------ .../android/editor/EditorFragment.java | 4 +- .../android/editor/JsCallbackReceiver.java | 2 +- .../editor-common/assets/ZSSRichTextEditor.js | 53 ++++++++++++++++++- 6 files changed, 57 insertions(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index c065b2d6d317..b47d5ac194e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ android: - extra-android-support - platform-tools - tools - - build-tools-23.0.2 + - build-tools-23.0.3 - android-23 env: diff --git a/README.md b/README.md index a3781d021ea1..35c05f08a273 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Finally, update `[PROJECT_ROOT]\.git\info\exclude` to ignore the symlink locally This project has both unit testing and integration testing, maintained and run separately. -Unit testing is done with the [Robolectric framework](http://robolectric.org/). To run unit tests simply run `gradlew testDebug`. Code coverage reports can be generated via [JaCoCo.](http://www.eclemma.org/jacoco/) To generate them locally run `gradlew jacocoTestReport`. +Unit testing is done with the [Robolectric framework](http://robolectric.org/). To run unit tests simply run `gradlew testDebug`. Integration testing is done with the [Android testing framework](http://developer.android.com/tools/testing/testing_android.html). To run integration tests run `gradlew connectedAndroidTest`. diff --git a/WordPressEditor/build.gradle b/WordPressEditor/build.gradle index 2d47277c4fa6..7f0b2f4e2904 100644 --- a/WordPressEditor/build.gradle +++ b/WordPressEditor/build.gradle @@ -8,7 +8,6 @@ buildscript { } apply plugin: 'com.android.library' -apply plugin: 'jacoco' apply plugin: 'maven' apply plugin: 'signing' @@ -119,43 +118,10 @@ uploadArchives { } // -// Testing and code coverage +// Testing // android.testOptions.unitTests.all { include '**/*Test.class' exclude '**/ApplicationTest.class' } - -jacoco { - toolVersion = "0.7.1.201405082137" -} - -// Use these to define which classes to include and exclude from code coverage analysis -def coverageSourceDirs = [ 'src/main/java' ] -def coverageExclusions = [ '**/R.class', - '**/R$*.class', - '**/*$ViewInjector*.*', - '**/BuildConfig.*', - '**/Manifest*.*', - '**/Legacy**.class', - '**/legacy/**/*.class' ] - -task jacocoTestReport(type: JacocoReport, dependsOn: "testDebug") { - group = "Reporting" - description = "Generate Jacoco coverage reports" - - classDirectories = fileTree( - dir: 'build/intermediates/classes/debug', - excludes: coverageExclusions - ) - - additionalSourceDirs = files(coverageSourceDirs) - sourceDirectories = files(coverageSourceDirs) - executionData = files('build/jacoco/testDebug.exec') - - reports { - xml.enabled = true - html.enabled = true - } -} 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 d149212e7b1d..266e2ed4e066 100755 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java @@ -529,7 +529,7 @@ public void onClick(View v) { } else { // Visual mode mGetSelectedTextCountDownLatch = new CountDownLatch(1); - mWebView.execJavaScriptFromString("ZSSEditor.execFunctionForResult('getSelectedText');"); + mWebView.execJavaScriptFromString("ZSSEditor.execFunctionForResult('getSelectedTextToLinkify');"); try { if (mGetSelectedTextCountDownLatch.await(1, TimeUnit.SECONDS)) { dialogBundle.putString(LinkDialogFragment.LINK_DIALOG_ARG_TEXT, mJavaScriptResult); @@ -1251,7 +1251,7 @@ public void onGetHtmlResponse(Map inputArgs) { } } break; - case "getSelectedText": + case "getSelectedTextToLinkify": mJavaScriptResult = inputArgs.get("result"); mGetSelectedTextCountDownLatch.countDown(); break; 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 f7ca4c0914de..f8e9989891fb 100755 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/JsCallbackReceiver.java @@ -211,7 +211,7 @@ public void executeCallback(String callbackId, String params) { responseIds.add("id"); responseIds.add("contents"); break; - case "getSelectedText": + case "getSelectedTextToLinkify": responseIds.add("result"); break; case "getFailedMedia": diff --git a/libs/editor-common/assets/ZSSRichTextEditor.js b/libs/editor-common/assets/ZSSRichTextEditor.js index e05190dd8537..22da90e05350 100755 --- a/libs/editor-common/assets/ZSSRichTextEditor.js +++ b/libs/editor-common/assets/ZSSRichTextEditor.js @@ -400,10 +400,53 @@ ZSSEditor.resetSelectionOnField = function(fieldId) { ZSSEditor.getSelectedText = function() { var selection = window.getSelection(); - return selection.toString(); }; +ZSSEditor.canExpandBackward = function(range) { + var caretRange = range.cloneRange(); + if (range.startOffset == 0) { + return false; + } + caretRange.setStart(range.startContainer, range.startOffset - 1); + caretRange.setEnd(range.startContainer, range.startOffset); + if (!caretRange.toString().match(/\w/)) { + return false; + } + return true; +}; + +ZSSEditor.canExpandForward = function(range) { + var caretRange = range.cloneRange(); + if (range.endOffset == range.endContainer.length) { + return false; + } + caretRange.setStart(range.endContainer, range.endOffset); + caretRange.setEnd(range.endContainer, range.endOffset + 1); + if (!caretRange.toString().match(/\w/)) { + return false; + } + return true; +}; + +ZSSEditor.getSelectedTextToLinkify = function() { + var selection = window.getSelection(); + var element = ZSSEditor.getField("zss_field_content"); + // If there is no text selected, try to expand it to the word under the cursor + if (selection.rangeCount == 1) { + var range = selection.getRangeAt(0); + while (ZSSEditor.canExpandBackward(range)) { + range.setStart(range.startContainer, range.startOffset - 1); + } + while (ZSSEditor.canExpandForward(range)) { + range.setEnd(range.endContainer, range.endOffset + 1); + } + selection.removeAllRanges(); + selection.addRange(range); + } + return selection.toString(); +}; + ZSSEditor.getCaretArguments = function() { var caretInfo = this.getYCaretInfo(); @@ -770,7 +813,13 @@ ZSSEditor.insertHTMLWrappedInParagraphTags = function(html) { // Needs addClass method ZSSEditor.insertLink = function(url, title) { - this.insertHTML('' + title + ""); + var html = '' + title + ""; + + if (this.getFocusedField().getHTML().length == 0) { + html = '<' + this.defaultParagraphSeparator + '>' + html; + } + + this.insertHTML(html); }; ZSSEditor.updateLink = function(url, title) { From c0f0848c74ef959fa90d150ae3cf93259c3fd08e Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 12 Apr 2016 14:20:48 +0200 Subject: [PATCH 4/4] Squashed 'libs/editor/' changes from 7be0b57..72933eb 72933eb Merge pull request #343 from wordpress-mobile/feature/290-optimistic-upload-progress 24a391e Merge branch 'develop' into feature/290-optimistic-upload-progress 96eebb0 Merge commit '52910dd0664cf2031a6a1c78f3cb1b170a4eeff9' into issue/editor-p-to-div-update fe5df26 Merge branch 'develop' into issue/editor-p-to-div-update 31f8844 Use optimistic progress updates for retried media uploads 8726f0d Turn off automated 'optimistic' progress updates for failed media 8a535e7 Reset progress to 0% when media uploads fail 299e9e9 Moved progress setting for media retries to the ZSSEditor 98dbf21 Send 'optimistic' automatic progress updates until 15% for media uploads f673a1a Don't update media upload progress bar beyond 90% abfa1fe Use setProgressOnMedia in EditorFragment 2f058ac s/imageUri/mediaUri/ 65913c8 Added a media option to the demo app simulating an image upload over a slow connection 3e61e04 Replaced the setProgressOnImage/Video methods with the more generic setProgressOnMedia 159585b Merge branch 'develop' into feature/290-optimistic-upload-progress 06e6da8 Merge pull request #3931 from wordpress-mobile/issue/fix-placeholder-title-in-french e5c8bc9 Updating to gradle 2.0 bdfee87 Updating to rc3 3ea4753 Escape quotes on translated strings when calling execJavaScriptFromString 6861416 Move progress bar initialization inside the insertLocal methods ac2575e Merge commit 'c5b98a88c01551e8dbf44366072607d364ec01dd' into develop d6f1313 Updating the rest of the modules to buildToolsVersion 23.0.3 4361ec9 Merge commit '4c9324cf1eee00b66c76e0d5a917c86e1293a845' into develop 04edf05 Add missing classes for images inserted from media library - also fix a bug with undefined alt text f83361a Add missing attributes for uploaded images 1c031dd update to android-gradle-2.0.0-rc1 ef46180 Merge commit 'c6efe0a9190244d40e64300efc9cca56ae5acd5c' into develop 964aee7 Merge branch 'develop' into issue/120editor-initial-focus f78081f Merge commit '9b0b5fab24db9f435b278ad0b9e77d5895135700' into develop 16ca267 Merge pull request #3897 from wordpress-mobile/issue/260editor-clear-failed-images-on-upload 53c5e25 Null check getParentRangeOfFocusedNode/setRange in onMutationObserved - in case editor is not in focus a023df0 Show the software keyboard once the DOM has loaded 1eb8e1e Focus on the title field when opening posts 76e050d New EditorFragment method to removeAllFailedMediaUploads() 3e91458 New JS function ZSSEditor.removeAllFailedMediaUploads b4e2d18 Merge pull request #3896 from wordpress-mobile/issue/300editor-broken-images-after-upload-2 9ae4bbc remove debug logs 7d792ab Use remoteurl in the link wrapper 180d3ad Merge branch 'develop' into issue/300editor-broken-images-after-upload-2 4a38ef6 Merge branch 'develop' into issue/297editor-backspace-media 4f2f7cb update to com.android.tools.build:gradle:2.0.0-beta7 d826f94 Merge branch 'develop' into issue/297editor-backspace-media dccdc76 Merge commit '8db246f15ce6f4d2c7f7f7ec51c68b87e9a66c2f' into develop fee3247 Changed MutationObserver handling to check if the WebView supports it, rather than rely on API levels 8812a3d Refactor: grouped mutation observation methods together a6d25a8 Refactored DOM element mutation listening, delegating everything to one trackNodeForMutation method a6a0236 Changed MutationObserver behavior to track individual media nodes instead of each contenteditable div 2340c5c Moved failed media methods to the generic media method group 63e0210 Parse for failed media when returning from HTML to visual mode 26159ed Track DOMNodeRemoved events when parsing for failed media 284411a Fixed a variable name error in ZSSEditor.removeImage 403b61c On API<19, use DOMNodeRemoved events to track media deletions (instead of the unsupported MutationObserver used for newer APIs) 9652b4c Merge branch 'develop' into issue/297editor-backspace-media 49a5029 Merge pull request #3804 from wordpress-mobile/issue/enable-editor-debug-mode 848a974 Consume KEYCODE_VOLUME_UP event when debug print is called 764c6ee broken retries 17efb8f Merge branch 'issue/enable-editor-debug-mode' into issue/300editor-broken-images-after-upload-2 44da980 use a remoteUrl attribute to avoid seeing broken image if download failed baec233 remove debug action bar button and log raw html when volume up button is pressed 329ebf3 fix function call errors bf4771c Add back image swapping onError 6bc0d4b fix wordpress-mobile/WordPress-Editor-Android#300: Retry download onError after an upload 0aef278 add missing comment 0f369d0 Updated gradle to 2.0.0-beta6 7269fbb Fixes an issue where manually deleting uploading/failed media will cause the caret to disappear f2d0a44 Notify native through a callback whenever uploading/failed media are manually deleted 0ccc419 catch a common JS exception 42f3a10 Merge branch 'develop' into issue/288editor-log-js-errors-in-crashlytics 60a7ec7 fix wordpress-mobile/WordPress-Editor-Android#288: new EditorWebViewAbstract.ErrorListener used to forward JS errors to Crashlytics cc87834 Keep the format bar disabled on rotation when the title field is in focus 7039d9d Rely on ZSSEditor to flag uploads as completed in native-side checks 88d5ae1 Added null checking for MediaType onMediaUploadFailed 6a6081b Wait until the ZSSEditor has replaced the local media with remote before marking it as completed 2e8d1f5 Strip any trailing   when returning the title git-subtree-dir: libs/editor git-subtree-split: 72933eb1e06055b5852f011ad4980c47e24d578f --- .../android/editor/EditorFragment.java | 21 +-- .../example/EditorExampleActivity.java | 63 ++++++- example/src/main/res/values/strings.xml | 1 + .../editor-common/assets/ZSSRichTextEditor.js | 156 +++++++++++------- 4 files changed, 162 insertions(+), 79 deletions(-) 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 266e2ed4e066..f4fc4537925c 100755 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java @@ -793,12 +793,10 @@ public void run() { 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); } } @@ -901,13 +899,8 @@ public void onMediaUploadProgress(final String mediaId, final float progress) { @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 + ");"); - } + mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnMedia(" + mediaId + ", " + + progressString + ");"); } }); } @@ -972,9 +965,9 @@ public void run() { // Set title and content placeholder text mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_title').setPlaceholderText('" + - mTitlePlaceholder + "');"); + Utils.escapeQuotes(mTitlePlaceholder) + "');"); mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').setPlaceholderText('" + - mContentPlaceholder + "');"); + Utils.escapeQuotes(mContentPlaceholder) + "');"); // Load title and content into ZSSEditor updateVisualEditorFields(); @@ -982,7 +975,7 @@ 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.markAllUploadingMediaAsFailed('" - + getString(R.string.tap_to_try_again) + "');"); + + Utils.escapeQuotes(getString(R.string.tap_to_try_again)) + "');"); // Update the list of failed media uploads mWebView.execJavaScriptFromString("ZSSEditor.getFailedMedia();"); @@ -1124,14 +1117,10 @@ public void run() { 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); mUploadingMedia.put(mediaId, mediaType); diff --git a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java index e9525daad480..f2cc1aa98144 100644 --- a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java +++ b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java @@ -34,6 +34,7 @@ public class EditorExampleActivity extends AppCompatActivity implements EditorFr public static final int ADD_MEDIA_ACTIVITY_REQUEST_CODE = 1111; public static final int ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE = 1112; + public static final int ADD_MEDIA_SLOW_NETWORK_REQUEST_CODE = 1113; public static final String MEDIA_REMOTE_ID_SAMPLE = "123"; @@ -41,6 +42,7 @@ public class EditorExampleActivity extends AppCompatActivity implements EditorFr 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 static final int SELECT_IMAGE_SLOW_MENU_POSITION = 4; private EditorFragmentAbstract mEditorFragment; @@ -85,6 +87,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen 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)); + menu.add(0, SELECT_IMAGE_SLOW_MENU_POSITION, 0, getString(R.string.select_image_slow_network)); } @Override @@ -120,6 +123,13 @@ public boolean onContextItemSelected(MenuItem item) { startActivityForResult(intent, ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE); return true; + case SELECT_IMAGE_SLOW_MENU_POSITION: + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + intent = Intent.createChooser(intent, getString(R.string.select_image_slow_network)); + + startActivityForResult(intent, ADD_MEDIA_SLOW_NETWORK_REQUEST_CODE); + return true; default: return false; } @@ -133,27 +143,35 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { return; } - Uri imageUri = data.getData(); + Uri mediaUri = data.getData(); MediaFile mediaFile = new MediaFile(); String mediaId = String.valueOf(System.currentTimeMillis()); mediaFile.setMediaId(mediaId); - mediaFile.setVideo(imageUri.toString().contains("video")); + mediaFile.setVideo(mediaUri.toString().contains("video")); switch (requestCode) { case ADD_MEDIA_ACTIVITY_REQUEST_CODE: - mEditorFragment.appendMediaFile(mediaFile, imageUri.toString(), null); + mEditorFragment.appendMediaFile(mediaFile, mediaUri.toString(), null); if (mEditorFragment instanceof EditorMediaUploadListener) { - simulateFileUpload(mediaId, imageUri.toString()); + simulateFileUpload(mediaId, mediaUri.toString()); } break; case ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE: - mEditorFragment.appendMediaFile(mediaFile, imageUri.toString(), null); + mEditorFragment.appendMediaFile(mediaFile, mediaUri.toString(), null); + + if (mEditorFragment instanceof EditorMediaUploadListener) { + simulateFileUploadFail(mediaId, mediaUri.toString()); + } + break; + case ADD_MEDIA_SLOW_NETWORK_REQUEST_CODE: + mEditorFragment.appendMediaFile(mediaFile, mediaUri.toString(), null); if (mEditorFragment instanceof EditorMediaUploadListener) { - simulateFileUploadFail(mediaId, imageUri.toString()); + simulateSlowFileUpload(mediaId, mediaUri.toString()); } + break; } } @@ -280,4 +298,37 @@ public void run() { thread.start(); } + + private void simulateSlowFileUpload(final String mediaId, final String mediaUrl) { + Thread thread = new Thread() { + @Override + public void run() { + try { + sleep(5000); + float count = (float) 0.1; + while (count < 1.1) { + sleep(2000); + + ((EditorMediaUploadListener) mEditorFragment).onMediaUploadProgress(mediaId, count); + + count += 0.1; + } + + 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); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + + thread.start(); + } } diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index bfffb0de8fb7..c8ac8b7dd3da 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -17,4 +17,5 @@ Select an image (failure demo) Select a video Select a video (failure demo) + Select an image (slow network demo) diff --git a/libs/editor-common/assets/ZSSRichTextEditor.js b/libs/editor-common/assets/ZSSRichTextEditor.js index 22da90e05350..8b9bd271b726 100755 --- a/libs/editor-common/assets/ZSSRichTextEditor.js +++ b/libs/editor-common/assets/ZSSRichTextEditor.js @@ -992,6 +992,24 @@ ZSSEditor.extractMediaIdentifier = function(node) { return ""; }; +ZSSEditor.getMediaNodeWithIdentifier = function(mediaNodeIdentifier) { + var imageNode = ZSSEditor.getImageNodeWithIdentifier(mediaNodeIdentifier); + if (imageNode.length > 0) { + return imageNode; + } else { + return ZSSEditor.getVideoNodeWithIdentifier(mediaNodeIdentifier); + } +}; + +ZSSEditor.getMediaProgressNodeWithIdentifier = function(mediaNodeIdentifier) { + var imageProgressNode = ZSSEditor.getImageProgressNodeWithIdentifier(mediaNodeIdentifier); + if (imageProgressNode.length > 0) { + return imageProgressNode; + } else { + return ZSSEditor.getVideoProgressNodeWithIdentifier(mediaNodeIdentifier); + } +}; + ZSSEditor.getMediaContainerNodeWithIdentifier = function(mediaNodeIdentifier) { var imageContainerNode = ZSSEditor.getImageContainerNodeWithIdentifier(mediaNodeIdentifier); if (imageContainerNode.length > 0) { @@ -1001,6 +1019,61 @@ ZSSEditor.getMediaContainerNodeWithIdentifier = function(mediaNodeIdentifier) { } }; +/** + * @brief Update the progress indicator for the media item identified with the value in progress. + * + * @param mediaNodeIdentifier This is a unique ID provided by the caller. + * @param progress A value between 0 and 1 indicating the progress on the media upload. + */ +ZSSEditor.setProgressOnMedia = function(mediaNodeIdentifier, progress) { + var mediaNode = this.getMediaNodeWithIdentifier(mediaNodeIdentifier); + var mediaProgressNode = this.getMediaProgressNodeWithIdentifier(mediaNodeIdentifier); + + // Don't allow the progress bar to move backward + if (mediaNode.length == 0 || mediaProgressNode.length == 0 || mediaProgressNode.attr("value") > progress) { + return; + } + + if (progress == 0) { + mediaNode.addClass("uploading"); + } + + // Revert to non-compatibility image container once image upload has begun. This centers the overlays on the image + // (instead of the screen), while still circumventing the small container bug the compat class was added to fix + if (progress > 0) { + this.getMediaContainerNodeWithIdentifier(mediaNodeIdentifier).removeClass("compat"); + } + + // Sometimes the progress bar can be stuck at 100% for a long time while further processing happens + // From a UX perspective, it's better to just keep the progress bars at 90% until the upload is really complete + // and the progress bar is removed entirely + if (progress > 0.9) { + return; + } + + mediaProgressNode.attr("value", progress); +}; + +ZSSEditor.setupOptimisticProgressUpdate = function(mediaNodeIdentifier, nCall) { + setTimeout(ZSSEditor.sendOptimisticProgressUpdate, nCall * 100, mediaNodeIdentifier, nCall); +}; + +ZSSEditor.sendOptimisticProgressUpdate = function(mediaNodeIdentifier, nCall) { + if (nCall > 15) { + return; + } + + var mediaNode = ZSSEditor.getMediaNodeWithIdentifier(mediaNodeIdentifier); + + // Don't send progress updates to failed media + if (mediaNode.length != 0 && mediaNode[0].classList.contains("failed")) { + return; + } + + ZSSEditor.setProgressOnMedia(mediaNodeIdentifier, nCall / 100); + ZSSEditor.setupOptimisticProgressUpdate(mediaNodeIdentifier, nCall + 1); +}; + ZSSEditor.sendMediaRemovedCallback = function(mediaNodeIdentifier) { var arguments = ['id=' + encodeURIComponent(mediaNodeIdentifier)]; var joinedArguments = arguments.join(defaultCallbackSeparator); @@ -1128,6 +1201,12 @@ ZSSEditor.insertLocalImage = function(imageNodeIdentifier, localImageUrl) { ZSSEditor.trackNodeForMutation(this.getImageContainerNodeWithIdentifier(imageNodeIdentifier)); + this.setProgressOnMedia(imageNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, imageNodeIdentifier, 1); + } + this.sendEnabledStyles(); }; @@ -1225,34 +1304,6 @@ ZSSEditor.tryToReload = function (image, imageNode, imageNodeIdentifier, remoteI setTimeout(ZSSEditor.reloadImage, nCall * 500, image, imageNode, imageNodeIdentifier, remoteImageId, nCall); } -/** - * @brief Update the progress indicator for the image identified with the value in progress. - * - * @param imageNodeIdentifier This is a unique ID provided by the caller. - * @param progress A value between 0 and 1 indicating the progress on the image. - */ -ZSSEditor.setProgressOnImage = function(imageNodeIdentifier, progress) { - var imageNode = this.getImageNodeWithIdentifier(imageNodeIdentifier); - if (imageNode.length == 0){ - return; - } - if (progress < 1){ - imageNode.addClass("uploading"); - } - - // Revert to non-compatibility image container once image upload has begun. This centers the overlays on the image - // (instead of the screen), while still circumventing the small container bug the compat class was added to fix - if (progress > 0) { - this.getImageContainerNodeWithIdentifier(imageNodeIdentifier).removeClass("compat"); - } - - var imageProgressNode = this.getImageProgressNodeWithIdentifier(imageNodeIdentifier); - if (imageProgressNode.length == 0){ - return; - } - imageProgressNode.attr("value",progress); -}; - /** * @brief Notifies that the image upload as finished * @@ -1330,6 +1381,7 @@ ZSSEditor.markImageUploadFailed = function(imageNodeIdentifier, message) { var imageProgressNode = this.getImageProgressNodeWithIdentifier(imageNodeIdentifier); if (imageProgressNode.length != 0){ imageProgressNode.addClass('failed'); + imageProgressNode.attr("value", 0); } // Delete the compatibility overlay if present @@ -1360,6 +1412,12 @@ ZSSEditor.unmarkImageUploadFailed = function(imageNodeIdentifier) { // Display the compatibility overlay again if present imageContainerNode.find("span.upload-overlay").removeClass("failed"); + + this.setProgressOnMedia(imageNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, imageNodeIdentifier, 1); + } }; /** @@ -1459,6 +1517,12 @@ ZSSEditor.insertLocalVideo = function(videoNodeIdentifier, posterURL) { ZSSEditor.trackNodeForMutation(this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier)); + this.setProgressOnMedia(videoNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, videoNodeIdentifier, 1); + } + this.sendEnabledStyles(); }; @@ -1523,35 +1587,6 @@ ZSSEditor.replaceLocalVideoWithRemoteVideo = function(videoNodeIdentifier, remot setTimeout(function() { thisObj.sendVideoReplacedCallback(videoNodeIdentifier);}, 500); }; -/** - * @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"); - } - - // 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; - } - videoProgressNode.attr("value",progress); -}; - /** * @brief Callbacks to native that the video upload as finished and the local url was replaced by the remote url * @@ -1611,6 +1646,7 @@ ZSSEditor.markVideoUploadFailed = function(videoNodeIdentifier, message) { var videoProgressNode = this.getVideoProgressNodeWithIdentifier(videoNodeIdentifier); if (videoProgressNode.length != 0){ videoProgressNode.addClass('failed'); + videoProgressNode.attr("value", 0); } // Delete the compatibility overlay if present @@ -1641,6 +1677,12 @@ ZSSEditor.unmarkVideoUploadFailed = function(videoNodeIdentifier) { // Display the compatibility overlay again if present videoContainerNode.find("span.upload-overlay").removeClass("failed"); + + this.setProgressOnMedia(videoNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, videoNodeIdentifier, 1); + } }; /**