blob: 9ff8df7d8133f056440ad7bed6099ae74565eed1 [file] [log] [blame]
package org.wordpress.android.editor;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ContentResolver;
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;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.SystemClock;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.URLUtil;
import android.webkit.WebView;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.ToggleButton;
import com.android.volley.toolbox.ImageLoader;
import org.json.JSONException;
import org.json.JSONObject;
import org.wordpress.android.editor.EditorWebViewAbstract.ErrorListener;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.DisplayUtils;
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;
import org.wordpress.android.util.helpers.MediaFile;
import org.wordpress.android.util.helpers.MediaGallery;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class EditorFragment extends EditorFragmentAbstract implements View.OnClickListener, View.OnTouchListener,
OnJsEditorStateChangedListener, OnImeBackListener, EditorWebViewAbstract.AuthHeaderRequestListener,
EditorMediaUploadListener {
private static final String ARG_PARAM_TITLE = "param_title";
private static final String ARG_PARAM_CONTENT = "param_content";
private static final String JS_CALLBACK_HANDLER = "nativeCallbackHandler";
private static final String KEY_TITLE = "title";
private static final String KEY_CONTENT = "content";
private static final String TAG_FORMAT_BAR_BUTTON_MEDIA = "media";
private static final String TAG_FORMAT_BAR_BUTTON_LINK = "link";
private static final float TOOLBAR_ALPHA_ENABLED = 1;
private static final float TOOLBAR_ALPHA_DISABLED = 0.5f;
private static final List<String> DRAGNDROP_SUPPORTED_MIMETYPES_TEXT = Arrays.asList(ClipDescription.MIMETYPE_TEXT_PLAIN,
ClipDescription.MIMETYPE_TEXT_HTML);
private static final List<String> DRAGNDROP_SUPPORTED_MIMETYPES_IMAGE = Arrays.asList("image/jpeg", "image/png");
public static final int MAX_ACTION_TIME_MS = 2000;
private String mTitle = "";
private String mContentHtml = "";
private EditorWebViewAbstract mWebView;
private View mSourceView;
private SourceViewEditText mSourceViewTitle;
private SourceViewEditText mSourceViewContent;
private int mSelectionStart;
private int mSelectionEnd;
private String mFocusedFieldId;
private String mTitlePlaceholder = "";
private String mContentPlaceholder = "";
private boolean mDomHasLoaded = false;
private boolean mIsKeyboardOpen = false;
private boolean mEditorWasPaused = false;
private boolean mHideActionBarOnSoftKeyboardUp = false;
private boolean mIsFormatBarDisabled = false;
private ConcurrentHashMap<String, MediaFile> mWaitingMediaFiles;
private Set<MediaGallery> mWaitingGalleries;
private Map<String, MediaType> mUploadingMedia;
private Set<String> mFailedMediaIds;
private MediaGallery mUploadingMediaGallery;
private String mJavaScriptResult = "";
private CountDownLatch mGetTitleCountDownLatch;
private CountDownLatch mGetContentCountDownLatch;
private CountDownLatch mGetSelectedTextCountDownLatch;
private final Map<String, ToggleButton> mTagToggleButtonMap = new HashMap<>();
private long mActionStartedAt = -1;
private final View.OnDragListener mOnDragListener = new View.OnDragListener() {
private long lastSetCoordsTimestamp;
private boolean isSupported(ClipDescription clipDescription, List<String> mimeTypesToCheck) {
if (clipDescription == null) {
return false;
}
for (String supportedMimeType : mimeTypesToCheck) {
if (clipDescription.hasMimeType(supportedMimeType)) {
return true;
}
}
return false;
}
@Override
public boolean onDrag(View view, DragEvent dragEvent) {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return isSupported(dragEvent.getClipDescription(), DRAGNDROP_SUPPORTED_MIMETYPES_TEXT) ||
isSupported(dragEvent.getClipDescription(), DRAGNDROP_SUPPORTED_MIMETYPES_IMAGE);
case DragEvent.ACTION_DRAG_ENTERED:
// would be nice to start marking the place the item will drop
break;
case DragEvent.ACTION_DRAG_LOCATION:
int x = DisplayUtils.pxToDp(getActivity(), (int) dragEvent.getX());
int y = DisplayUtils.pxToDp(getActivity(), (int) dragEvent.getY());
// don't call into JS too often
long currentTimestamp = SystemClock.uptimeMillis();
if ((currentTimestamp - lastSetCoordsTimestamp) > 150) {
lastSetCoordsTimestamp = currentTimestamp;
mWebView.execJavaScriptFromString("ZSSEditor.moveCaretToCoords(" + x + ", " + y + ");");
}
break;
case DragEvent.ACTION_DRAG_EXITED:
// clear any drop marking maybe
break;
case DragEvent.ACTION_DROP:
if (mSourceView.getVisibility() == View.VISIBLE) {
if (isSupported(dragEvent.getClipDescription(), DRAGNDROP_SUPPORTED_MIMETYPES_IMAGE)) {
// don't allow dropping images into the HTML source
ToastUtils.showToast(getActivity(), R.string.editor_dropped_html_images_not_allowed,
ToastUtils.Duration.LONG);
return true;
} else {
// let the system handle the text drop
return false;
}
}
if (isSupported(dragEvent.getClipDescription(), DRAGNDROP_SUPPORTED_MIMETYPES_IMAGE) &&
("zss_field_title".equals(mFocusedFieldId))) {
// don't allow dropping images into the title field
ToastUtils.showToast(getActivity(), R.string.editor_dropped_title_images_not_allowed,
ToastUtils.Duration.LONG);
return true;
}
if (isAdded()) {
mEditorDragAndDropListener.onRequestDragAndDropPermissions(dragEvent);
}
ClipDescription clipDescription = dragEvent.getClipDescription();
if (clipDescription.getMimeTypeCount() < 1) {
break;
}
ContentResolver contentResolver = getActivity().getContentResolver();
ArrayList<Uri> uris = new ArrayList<>();
boolean unsupportedDropsFound = false;
for (int i = 0; i < dragEvent.getClipData().getItemCount(); i++) {
ClipData.Item item = dragEvent.getClipData().getItemAt(i);
Uri uri = item.getUri();
final String uriType = uri != null ? contentResolver.getType(uri) : null;
if (uriType != null && DRAGNDROP_SUPPORTED_MIMETYPES_IMAGE.contains(uriType)) {
uris.add(uri);
continue;
} else if (item.getText() != null) {
insertTextToEditor(item.getText().toString());
continue;
} else if (item.getHtmlText() != null) {
insertTextToEditor(item.getHtmlText());
continue;
}
// any other drop types are not supported, including web URLs. We cannot proactively
// determine their mime type for filtering
unsupportedDropsFound = true;
}
if (unsupportedDropsFound) {
ToastUtils.showToast(getActivity(), R.string.editor_dropped_unsupported_files, ToastUtils
.Duration.LONG);
}
if (uris.size() > 0) {
mEditorDragAndDropListener.onMediaDropped(uris);
}
break;
case DragEvent.ACTION_DRAG_ENDED:
// clear any drop marking maybe
default:
break;
}
return true;
}
private void insertTextToEditor(String text) {
if (text != null) {
mWebView.execJavaScriptFromString("ZSSEditor.insertText('" + Utils.escapeHtml(text) + "', true);");
} else {
ToastUtils.showToast(getActivity(), R.string.editor_dropped_text_error, ToastUtils.Duration.SHORT);
AppLog.d(T.EDITOR, "Dropped text was null!");
}
}
};
public static EditorFragment newInstance(String title, String content) {
EditorFragment fragment = new EditorFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM_TITLE, title);
args.putString(ARG_PARAM_CONTENT, content);
fragment.setArguments(args);
return fragment;
}
public EditorFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ProfilingUtils.start("Visual Editor Startup");
ProfilingUtils.split("EditorFragment.onCreate");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_editor, container, false);
// Setup hiding the action bar when the soft keyboard is displayed for narrow viewports
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
&& !getResources().getBoolean(R.bool.is_large_tablet_landscape)) {
mHideActionBarOnSoftKeyboardUp = true;
}
mWaitingMediaFiles = new ConcurrentHashMap<>();
mWaitingGalleries = Collections.newSetFromMap(new ConcurrentHashMap<MediaGallery, Boolean>());
mUploadingMedia = new HashMap<>();
mFailedMediaIds = new HashSet<>();
// -- WebView configuration
mWebView = (EditorWebViewAbstract) view.findViewById(R.id.webview);
// Revert to compatibility WebView for custom ROMs using a 4.3 WebView in Android 4.4
if (mWebView.shouldSwitchToCompatibilityMode()) {
ViewGroup parent = (ViewGroup) mWebView.getParent();
int index = parent.indexOfChild(mWebView);
parent.removeView(mWebView);
mWebView = new EditorWebViewCompatibility(getActivity(), null);
mWebView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
parent.addView(mWebView, index);
}
mWebView.setOnTouchListener(this);
mWebView.setOnImeBackListener(this);
mWebView.setAuthHeaderRequestListener(this);
mWebView.setOnDragListener(mOnDragListener);
if (mCustomHttpHeaders != null && mCustomHttpHeaders.size() > 0) {
for (Map.Entry<String, String> entry : mCustomHttpHeaders.entrySet()) {
mWebView.setCustomHeader(entry.getKey(), entry.getValue());
}
}
// Ensure that the content field is always filling the remaining screen space
mWebView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
mWebView.post(new Runnable() {
@Override
public void run() {
mWebView.execJavaScriptFromString("try {ZSSEditor.refreshVisibleViewportSize();} catch (e) " +
"{console.log(e)}");
}
});
}
});
mEditorFragmentListener.onEditorFragmentInitialized();
initJsEditor();
if (savedInstanceState != null) {
setTitle(savedInstanceState.getCharSequence(KEY_TITLE));
setContent(savedInstanceState.getCharSequence(KEY_CONTENT));
}
// -- HTML mode configuration
mSourceView = view.findViewById(R.id.sourceview);
mSourceViewTitle = (SourceViewEditText) view.findViewById(R.id.sourceview_title);
mSourceViewContent = (SourceViewEditText) view.findViewById(R.id.sourceview_content);
// Toggle format bar on/off as user changes focus between title and content in HTML mode
mSourceViewTitle.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
updateFormatBarEnabledState(!hasFocus);
}
});
mSourceViewTitle.setOnTouchListener(this);
mSourceViewContent.setOnTouchListener(this);
mSourceViewTitle.setOnImeBackListener(this);
mSourceViewContent.setOnImeBackListener(this);
mSourceViewContent.addTextChangedListener(new HtmlStyleTextWatcher());
mSourceViewTitle.setHint(mTitlePlaceholder);
mSourceViewContent.setHint("<p>" + mContentPlaceholder + "</p>");
// attach drag-and-drop handler
mSourceViewTitle.setOnDragListener(mOnDragListener);
mSourceViewContent.setOnDragListener(mOnDragListener);
// -- Format bar configuration
setupFormatBarButtonMap(view);
return view;
}
@Override
public void onPause() {
super.onPause();
mEditorWasPaused = true;
mIsKeyboardOpen = false;
}
@Override
public void onResume() {
super.onResume();
// If the editor was previously paused and the current orientation is landscape,
// hide the actionbar because the keyboard is going to appear (even if it was hidden
// prior to being paused).
if (mEditorWasPaused
&& (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
&& !getResources().getBoolean(R.bool.is_large_tablet_landscape)) {
mIsKeyboardOpen = true;
mHideActionBarOnSoftKeyboardUp = true;
hideActionBarIfNeeded();
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mEditorDragAndDropListener = (EditorDragAndDropListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement EditorDragAndDropListener");
}
}
@Override
public void onDetach() {
// Soft cancel (delete flag off) all media uploads currently in progress
for (String mediaId : mUploadingMedia.keySet()) {
mEditorFragmentListener.onMediaUploadCancelClicked(mediaId, false);
}
super.onDetach();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (mDomHasLoaded) {
mWebView.notifyVisibilityChanged(isVisibleToUser);
}
super.setUserVisibleHint(isVisibleToUser);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putCharSequence(KEY_TITLE, getTitle());
outState.putCharSequence(KEY_CONTENT, getContent());
}
private ActionBar getActionBar() {
if (!isAdded()) {
return null;
}
if (getActivity() instanceof AppCompatActivity) {
return ((AppCompatActivity) getActivity()).getSupportActionBar();
} else {
return null;
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getView() != null) {
// Reload the format bar to make sure the correct one for the new screen width is being used
View formatBar = getView().findViewById(R.id.format_bar);
if (formatBar != null) {
// Remember the currently active format bar buttons so they can be re-activated after the reload
ArrayList<String> activeTags = new ArrayList<>();
for (Map.Entry<String, ToggleButton> entry : mTagToggleButtonMap.entrySet()) {
if (entry.getValue().isChecked()) {
activeTags.add(entry.getKey());
}
}
ViewGroup parent = (ViewGroup) formatBar.getParent();
parent.removeView(formatBar);
formatBar = getActivity().getLayoutInflater().inflate(R.layout.format_bar, parent, false);
formatBar.setId(R.id.format_bar);
parent.addView(formatBar);
setupFormatBarButtonMap(formatBar);
if (mIsFormatBarDisabled) {
updateFormatBarEnabledState(false);
}
// Restore the active format bar buttons
for (String tag : activeTags) {
mTagToggleButtonMap.get(tag).setChecked(true);
}
if (mSourceView.getVisibility() == View.VISIBLE) {
ToggleButton htmlButton = (ToggleButton) formatBar.findViewById(R.id.format_bar_button_html);
htmlButton.setChecked(true);
}
}
// Reload HTML mode margins
View sourceViewTitle = getView().findViewById(R.id.sourceview_title);
View sourceViewContent = getView().findViewById(R.id.sourceview_content);
if (sourceViewTitle != null && sourceViewContent != null) {
int sideMargin = (int) getActivity().getResources().getDimension(R.dimen.sourceview_side_margin);
ViewGroup.MarginLayoutParams titleParams =
(ViewGroup.MarginLayoutParams) sourceViewTitle.getLayoutParams();
ViewGroup.MarginLayoutParams contentParams =
(ViewGroup.MarginLayoutParams) sourceViewContent.getLayoutParams();
titleParams.setMargins(sideMargin, titleParams.topMargin, sideMargin, titleParams.bottomMargin);
contentParams.setMargins(sideMargin, contentParams.topMargin, sideMargin, contentParams.bottomMargin);
}
}
// Toggle action bar auto-hiding for the new orientation
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
&& !getResources().getBoolean(R.bool.is_large_tablet_landscape)) {
mHideActionBarOnSoftKeyboardUp = true;
hideActionBarIfNeeded();
} else {
mHideActionBarOnSoftKeyboardUp = false;
showActionBarIfNeeded();
}
}
private void setupFormatBarButtonMap(View view) {
ToggleButton boldButton = (ToggleButton) view.findViewById(R.id.format_bar_button_bold);
mTagToggleButtonMap.put(getString(R.string.format_bar_tag_bold), boldButton);
ToggleButton italicButton = (ToggleButton) view.findViewById(R.id.format_bar_button_italic);
mTagToggleButtonMap.put(getString(R.string.format_bar_tag_italic), italicButton);
ToggleButton quoteButton = (ToggleButton) view.findViewById(R.id.format_bar_button_quote);
mTagToggleButtonMap.put(getString(R.string.format_bar_tag_blockquote), quoteButton);
ToggleButton ulButton = (ToggleButton) view.findViewById(R.id.format_bar_button_ul);
mTagToggleButtonMap.put(getString(R.string.format_bar_tag_unorderedList), ulButton);
ToggleButton olButton = (ToggleButton) view.findViewById(R.id.format_bar_button_ol);
mTagToggleButtonMap.put(getString(R.string.format_bar_tag_orderedList), olButton);
// Tablet-only
ToggleButton strikethroughButton = (ToggleButton) view.findViewById(R.id.format_bar_button_strikethrough);
if (strikethroughButton != null) {
mTagToggleButtonMap.put(getString(R.string.format_bar_tag_strikethrough), strikethroughButton);
}
ToggleButton mediaButton = (ToggleButton) view.findViewById(R.id.format_bar_button_media);
mTagToggleButtonMap.put(TAG_FORMAT_BAR_BUTTON_MEDIA, mediaButton);
registerForContextMenu(mediaButton);
ToggleButton linkButton = (ToggleButton) view.findViewById(R.id.format_bar_button_link);
mTagToggleButtonMap.put(TAG_FORMAT_BAR_BUTTON_LINK, linkButton);
ToggleButton htmlButton = (ToggleButton) view.findViewById(R.id.format_bar_button_html);
htmlButton.setOnClickListener(this);
for (ToggleButton button : mTagToggleButtonMap.values()) {
button.setOnClickListener(this);
}
}
protected void initJsEditor() {
if (!isAdded()) {
return;
}
ProfilingUtils.split("EditorFragment.initJsEditor");
String htmlEditor = Utils.getHtmlFromFile(getActivity(), "android-editor.html");
if (htmlEditor != null) {
htmlEditor = htmlEditor.replace("%%TITLE%%", getString(R.string.visual_editor));
htmlEditor = htmlEditor.replace("%%ANDROID_API_LEVEL%%", String.valueOf(Build.VERSION.SDK_INT));
htmlEditor = htmlEditor.replace("%%LOCALIZED_STRING_INIT%%",
"nativeState.localizedStringEdit = '" + getString(R.string.edit) + "';\n" +
"nativeState.localizedStringUploading = '" + getString(R.string.uploading) + "';\n" +
"nativeState.localizedStringUploadingGallery = '" + getString(R.string.uploading_gallery_placeholder) + "';\n");
}
// To avoid reflection security issues with JavascriptInterface on API<17, we use an iframe to make URL requests
// for callbacks from JS instead. These are received by WebViewClient.shouldOverrideUrlLoading() and then
// passed on to the JsCallbackReceiver
if (Build.VERSION.SDK_INT < 17) {
mWebView.setJsCallbackReceiver(new JsCallbackReceiver(this));
} else {
mWebView.addJavascriptInterface(new JsCallbackReceiver(this), JS_CALLBACK_HANDLER);
}
mWebView.loadDataWithBaseURL("file:///android_asset/", htmlEditor, "text/html", "utf-8", "");
if (mDebugModeEnabled) {
enableWebDebugging(true);
}
}
public void checkForFailedUploadAndSwitchToHtmlMode(final ToggleButton toggleButton) {
if (!isAdded()) {
return;
}
// 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);
}
}
public boolean isActionInProgress() {
return System.currentTimeMillis() - mActionStartedAt < MAX_ACTION_TIME_MS;
}
private void toggleHtmlMode(final ToggleButton toggleButton) {
if (!isAdded()) {
return;
}
mEditorFragmentListener.onTrackableEvent(TrackableEvent.HTML_BUTTON_TAPPED);
// Don't switch to HTML mode if currently uploading media
if (!mUploadingMedia.isEmpty() || isActionInProgress()) {
toggleButton.setChecked(false);
ToastUtils.showToast(getActivity(), R.string.alert_action_while_uploading, ToastUtils.Duration.LONG);
return;
}
clearFormatBarButtons();
updateFormatBarEnabledState(true);
if (toggleButton.isChecked()) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (!isAdded()) {
return;
}
// Update mTitle and mContentHtml with the latest state from the ZSSEditor
getTitle();
getContent();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Set HTML mode state
mSourceViewTitle.setText(mTitle);
SpannableString spannableContent = new SpannableString(mContentHtml);
HtmlStyleUtils.styleHtmlForDisplay(spannableContent);
mSourceViewContent.setText(spannableContent);
mWebView.setVisibility(View.GONE);
mSourceView.setVisibility(View.VISIBLE);
mSourceViewContent.requestFocus();
mSourceViewContent.setSelection(0);
InputMethodManager imm = ((InputMethodManager) getActivity()
.getSystemService(Context.INPUT_METHOD_SERVICE));
imm.showSoftInput(mSourceViewContent, InputMethodManager.SHOW_IMPLICIT);
}
});
}
});
thread.start();
} else {
mWebView.setVisibility(View.VISIBLE);
mSourceView.setVisibility(View.GONE);
mTitle = mSourceViewTitle.getText().toString();
mContentHtml = mSourceViewContent.getText().toString();
updateVisualEditorFields();
// 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');");
}
}
private void displayLinkDialog() {
final LinkDialogFragment linkDialogFragment = new LinkDialogFragment();
linkDialogFragment.setTargetFragment(this, LinkDialogFragment.LINK_DIALOG_REQUEST_CODE_ADD);
final Bundle dialogBundle = new Bundle();
// Pass potential URL from user clipboard
String clipboardUri = Utils.getUrlFromClipboard(getActivity());
if (clipboardUri != null) {
dialogBundle.putString(LinkDialogFragment.LINK_DIALOG_ARG_URL, clipboardUri);
}
// Pass selected text to dialog
if (mSourceView.getVisibility() == View.VISIBLE) {
// HTML mode
mSelectionStart = mSourceViewContent.getSelectionStart();
mSelectionEnd = mSourceViewContent.getSelectionEnd();
String selectedText = mSourceViewContent.getText().toString().substring(mSelectionStart, mSelectionEnd);
dialogBundle.putString(LinkDialogFragment.LINK_DIALOG_ARG_TEXT, selectedText);
linkDialogFragment.setArguments(dialogBundle);
linkDialogFragment.show(getFragmentManager(), LinkDialogFragment.class.getSimpleName());
} else {
// Visual mode
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (!isAdded()) {
return;
}
mGetSelectedTextCountDownLatch = new CountDownLatch(1);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.execJavaScriptFromString(
"ZSSEditor.execFunctionForResult('getSelectedTextToLinkify');");
}
});
try {
if (mGetSelectedTextCountDownLatch.await(1, TimeUnit.SECONDS)) {
dialogBundle.putString(LinkDialogFragment.LINK_DIALOG_ARG_TEXT, mJavaScriptResult);
}
} catch (InterruptedException e) {
AppLog.d(T.EDITOR, "Failed to obtain selected text from JS editor.");
}
linkDialogFragment.setArguments(dialogBundle);
linkDialogFragment.show(getFragmentManager(), LinkDialogFragment.class.getSimpleName());
}
});
thread.start();
}
}
@Override
public void onClick(View v) {
if (!isAdded()) {
return;
}
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);
if (isActionInProgress()) {
ToastUtils.showToast(getActivity(), R.string.alert_action_while_uploading, ToastUtils.Duration.LONG);
return;
}
if (mSourceView.getVisibility() == View.VISIBLE) {
ToastUtils.showToast(getActivity(), R.string.alert_insert_image_html_mode, ToastUtils.Duration.LONG);
} else {
mEditorFragmentListener.onAddMediaClicked();
getActivity().openContextMenu(mTagToggleButtonMap.get(TAG_FORMAT_BAR_BUTTON_MEDIA));
}
} else if (id == R.id.format_bar_button_link) {
if (!((ToggleButton) v).isChecked()) {
// The link button was checked when it was pressed; remove the current link
mWebView.execJavaScriptFromString("ZSSEditor.unlink();");
mEditorFragmentListener.onTrackableEvent(TrackableEvent.UNLINK_BUTTON_TAPPED);
return;
}
mEditorFragmentListener.onTrackableEvent(TrackableEvent.LINK_BUTTON_TAPPED);
((ToggleButton) v).setChecked(false);
displayLinkDialog();
} else {
if (v instanceof ToggleButton) {
onFormattingButtonClicked((ToggleButton) v);
}
}
}
@Override
public boolean onTouch(View view, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// If the WebView or EditText has received a touch event, the keyboard will be displayed and the action bar
// should hide
mIsKeyboardOpen = true;
hideActionBarIfNeeded();
}
return false;
}
/**
* Intercept back button press while soft keyboard is visible.
*/
@Override
public void onImeBack() {
mIsKeyboardOpen = false;
showActionBarIfNeeded();
}
@Override
public String onAuthHeaderRequested(String url) {
return mEditorFragmentListener.onAuthHeaderRequested(url);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if ((requestCode == LinkDialogFragment.LINK_DIALOG_REQUEST_CODE_ADD ||
requestCode == LinkDialogFragment.LINK_DIALOG_REQUEST_CODE_UPDATE)) {
if (resultCode == LinkDialogFragment.LINK_DIALOG_REQUEST_CODE_DELETE) {
mWebView.execJavaScriptFromString("ZSSEditor.unlink();");
return;
}
if (data == null) {
return;
}
Bundle extras = data.getExtras();
if (extras == null) {
return;
}
String linkUrl = extras.getString(LinkDialogFragment.LINK_DIALOG_ARG_URL);
String linkText = extras.getString(LinkDialogFragment.LINK_DIALOG_ARG_TEXT);
if (linkText == null || linkText.equals("")) {
linkText = linkUrl;
}
if (TextUtils.isEmpty(Uri.parse(linkUrl).getScheme())) linkUrl = "http://" + linkUrl;
if (mSourceView.getVisibility() == View.VISIBLE) {
Editable content = mSourceViewContent.getText();
if (content == null) {
return;
}
if (mSelectionStart < mSelectionEnd) {
content.delete(mSelectionStart, mSelectionEnd);
}
String urlHtml = "<a href=\"" + linkUrl + "\">" + linkText + "</a>";
content.insert(mSelectionStart, urlHtml);
mSourceViewContent.setSelection(mSelectionStart + urlHtml.length());
} else {
String jsMethod;
if (requestCode == LinkDialogFragment.LINK_DIALOG_REQUEST_CODE_ADD) {
jsMethod = "ZSSEditor.insertLink";
} else {
jsMethod = "ZSSEditor.updateLink";
}
mWebView.execJavaScriptFromString(jsMethod + "('" + Utils.escapeHtml(linkUrl) + "', '" +
Utils.escapeHtml(linkText) + "');");
}
} else if (requestCode == ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_REQUEST_CODE) {
if (data == null) {
mWebView.execJavaScriptFromString("ZSSEditor.clearCurrentEditingImage();");
return;
}
Bundle extras = data.getExtras();
if (extras == null) {
return;
}
final String imageMeta = Utils.escapeQuotes(StringUtils.notNullStr(extras.getString("imageMeta")));
final int imageRemoteId = extras.getInt("imageRemoteId");
final boolean isFeaturedImage = extras.getBoolean("isFeatured");
mWebView.post(new Runnable() {
@Override
public void run() {
mWebView.execJavaScriptFromString("ZSSEditor.updateCurrentImageMeta('" + imageMeta + "');");
}
});
if (imageRemoteId != 0) {
if (isFeaturedImage) {
mFeaturedImageId = imageRemoteId;
mEditorFragmentListener.onFeaturedImageChanged(mFeaturedImageId);
} else {
// If this image was unset as featured, clear the featured image id
if (mFeaturedImageId == imageRemoteId) {
mFeaturedImageId = 0;
mEditorFragmentListener.onFeaturedImageChanged(mFeaturedImageId);
}
}
}
}
}
@SuppressLint("NewApi")
private void enableWebDebugging(boolean enable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
AppLog.i(T.EDITOR, "Enabling web debugging");
WebView.setWebContentsDebuggingEnabled(enable);
}
mWebView.setDebugModeEnabled(mDebugModeEnabled);
}
@Override
public void setTitle(CharSequence text) {
mTitle = text.toString();
}
@Override
public void setContent(CharSequence text) {
mContentHtml = text.toString();
}
/**
* Returns the contents of the title field from the JavaScript editor. Should be called from a background thread
* where possible.
*/
@Override
public CharSequence getTitle() {
if (!isAdded()) {
return "";
}
if (mSourceView != null && mSourceView.getVisibility() == View.VISIBLE) {
mTitle = mSourceViewTitle.getText().toString();
return StringUtils.notNullStr(mTitle);
}
if (Looper.myLooper() == Looper.getMainLooper()) {
AppLog.d(T.EDITOR, "getTitle() called from UI thread");
}
mGetTitleCountDownLatch = new CountDownLatch(1);
// All WebView methods must be called from the UI thread
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_title').getHTMLForCallback();");
}
});
try {
mGetTitleCountDownLatch.await(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
AppLog.e(T.EDITOR, e);
Thread.currentThread().interrupt();
}
return StringUtils.notNullStr(mTitle.replaceAll("&nbsp;$", ""));
}
/**
* Returns the contents of the content field from the JavaScript editor. Should be called from a background thread
* where possible.
*/
@Override
public CharSequence getContent() {
if (!isAdded()) {
return "";
}
if (mSourceView != null && mSourceView.getVisibility() == View.VISIBLE) {
mContentHtml = mSourceViewContent.getText().toString();
return StringUtils.notNullStr(mContentHtml);
}
if (Looper.myLooper() == Looper.getMainLooper()) {
AppLog.d(T.EDITOR, "getContent() called from UI thread");
}
mGetContentCountDownLatch = new CountDownLatch(1);
// All WebView methods must be called from the UI thread
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').getHTMLForCallback();");
}
});
try {
mGetContentCountDownLatch.await(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
AppLog.e(T.EDITOR, e);
Thread.currentThread().interrupt();
}
return StringUtils.notNullStr(mContentHtml);
}
@Override
public void appendMediaFile(final MediaFile mediaFile, final String mediaUrl, ImageLoader imageLoader) {
if (!mDomHasLoaded) {
// If the DOM hasn't loaded yet, we won't be able to add media to the ZSSEditor
// Place them in a queue to be handled when the DOM loaded callback is received
mWaitingMediaFiles.put(mediaUrl, mediaFile);
return;
}
final String safeMediaUrl = Utils.escapeQuotes(mediaUrl);
mWebView.post(new Runnable() {
@Override
public void run() {
if (URLUtil.isNetworkUrl(mediaUrl)) {
String mediaId = mediaFile.getMediaId();
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 +
"');");
}
mActionStartedAt = System.currentTimeMillis();
} else {
String id = mediaFile.getMediaId();
if (mediaFile.isVideo()) {
String posterUrl = Utils.escapeQuotes(StringUtils.notNullStr(mediaFile.getThumbnailURL()));
mWebView.execJavaScriptFromString("ZSSEditor.insertLocalVideo(" + id + ", '" + posterUrl +
"');");
mUploadingMedia.put(id, MediaType.VIDEO);
} else {
mWebView.execJavaScriptFromString("ZSSEditor.insertLocalImage(" + id + ", '" + safeMediaUrl +
"');");
mUploadingMedia.put(id, MediaType.IMAGE);
}
}
}
});
}
@Override
public void appendGallery(MediaGallery mediaGallery) {
if (!mDomHasLoaded) {
// If the DOM hasn't loaded yet, we won't be able to add a gallery to the ZSSEditor
// Place it in a queue to be handled when the DOM loaded callback is received
mWaitingGalleries.add(mediaGallery);
return;
}
if (mediaGallery.getIds().isEmpty()) {
mUploadingMediaGallery = mediaGallery;
mWebView.execJavaScriptFromString("ZSSEditor.insertLocalGallery('" + mediaGallery.getUniqueId() + "');");
} else {
// Ensure that the content field is in focus (it may not be if we're adding a gallery to a new post by a
// share action and not via the format bar button)
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').focus();");
mWebView.execJavaScriptFromString("ZSSEditor.insertGallery('" + mediaGallery.getIdsStr() + "', '" +
mediaGallery.getType() + "', " + mediaGallery.getNumColumns() + ");");
}
}
@Override
public void setUrlForVideoPressId(final String videoId, final String videoUrl, final String posterUrl) {
mWebView.post(new Runnable() {
@Override
public void run() {
mWebView.execJavaScriptFromString("ZSSEditor.setVideoPressLinks('" + videoId + "', '" +
Utils.escapeQuotes(videoUrl) + "', '" + Utils.escapeQuotes(posterUrl) + "');");
}
});
}
@Override
public boolean isUploadingMedia() {
return (mUploadingMedia.size() > 0);
}
@Override
public boolean hasFailedMediaUploads() {
return (mFailedMediaIds.size() > 0);
}
@Override
public void removeAllFailedMediaUploads() {
mWebView.execJavaScriptFromString("ZSSEditor.removeAllFailedMediaUploads();");
}
@Override
public Spanned getSpannedContent() {
return null;
}
@Override
public void setTitlePlaceholder(CharSequence placeholderText) {
mTitlePlaceholder = placeholderText.toString();
}
@Override
public void setContentPlaceholder(CharSequence placeholderText) {
mContentPlaceholder = placeholderText.toString();
}
@Override
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 + "');");
}
}
});
}
}
@Override
public void onMediaUploadProgress(final String mediaId, final float progress) {
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);
mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnMedia(" + mediaId + ", " +
progressString + ");");
}
});
}
}
@Override
public void onMediaUploadFailed(final String mediaId, final String errorMessage) {
mWebView.post(new Runnable() {
@Override
public void run() {
MediaType mediaType = mUploadingMedia.get(mediaId);
if (mediaType != null) {
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);
mUploadingMedia.remove(mediaId);
}
}
});
}
@Override
public void onGalleryMediaUploadSucceeded(final long galleryId, String remoteMediaId, int remaining) {
if (galleryId == mUploadingMediaGallery.getUniqueId()) {
ArrayList<String> mediaIds = mUploadingMediaGallery.getIds();
mediaIds.add(remoteMediaId);
mUploadingMediaGallery.setIds(mediaIds);
if (remaining == 0) {
mWebView.post(new Runnable() {
@Override
public void run() {
mWebView.execJavaScriptFromString("ZSSEditor.replacePlaceholderGallery('" + galleryId + "', '" +
mUploadingMediaGallery.getIdsStr() + "', '" +
mUploadingMediaGallery.getType() + "', " +
mUploadingMediaGallery.getNumColumns() + ");");
}
});
}
}
}
public void onDomLoaded() {
ProfilingUtils.split("EditorFragment.onDomLoaded");
mWebView.post(new Runnable() {
public void run() {
if (!isAdded()) {
return;
}
mDomHasLoaded = true;
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').setMultiline('true');");
// Set title and content placeholder text
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_title').setPlaceholderText('" +
Utils.escapeQuotes(mTitlePlaceholder) + "');");
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').setPlaceholderText('" +
Utils.escapeQuotes(mContentPlaceholder) + "');");
// Load title and content into ZSSEditor
updateVisualEditorFields();
// 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('"
+ Utils.escapeQuotes(getString(R.string.tap_to_try_again)) + "');");
// Update the list of failed media uploads
mWebView.execJavaScriptFromString("ZSSEditor.getFailedMedia();");
hideActionBarIfNeeded();
// Reset all format bar buttons (in case they remained active through activity re-creation)
ToggleButton htmlButton = (ToggleButton) getActivity().findViewById(R.id.format_bar_button_html);
htmlButton.setChecked(false);
for (ToggleButton button : mTagToggleButtonMap.values()) {
button.setChecked(false);
}
boolean editorHasFocus = false;
// Add any media files that were placed in a queue due to the DOM not having loaded yet
if (mWaitingMediaFiles.size() > 0) {
// Image insertion will only work if the content field is in focus
// (for a new post, no field is in focus until user action)
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').focus();");
editorHasFocus = true;
for (Map.Entry<String, MediaFile> entry : mWaitingMediaFiles.entrySet()) {
appendMediaFile(entry.getValue(), entry.getKey(), null);
}
mWaitingMediaFiles.clear();
}
// Add any galleries that were placed in a queue due to the DOM not having loaded yet
if (mWaitingGalleries.size() > 0) {
// Gallery insertion will only work if the content field is in focus
// (for a new post, no field is in focus until user action)
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').focus();");
editorHasFocus = true;
for (MediaGallery mediaGallery : mWaitingGalleries) {
appendGallery(mediaGallery);
}
mWaitingGalleries.clear();
}
if (!editorHasFocus) {
mWebView.execJavaScriptFromString("ZSSEditor.focusFirstEditableField();");
}
// Show the keyboard
((InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE))
.showSoftInput(mWebView, InputMethodManager.SHOW_IMPLICIT);
ProfilingUtils.split("EditorFragment.onDomLoaded completed");
ProfilingUtils.dump();
ProfilingUtils.stop();
}
});
}
public void onSelectionStyleChanged(final Map<String, Boolean> changeMap) {
mWebView.post(new Runnable() {
public void run() {
for (Map.Entry<String, Boolean> entry : changeMap.entrySet()) {
// Handle toggling format bar style buttons
ToggleButton button = mTagToggleButtonMap.get(entry.getKey());
if (button != null) {
button.setChecked(entry.getValue());
}
}
}
});
}
public void onSelectionChanged(final Map<String, String> selectionArgs) {
mFocusedFieldId = selectionArgs.get("id"); // The field now in focus
mWebView.post(new Runnable() {
@Override
public void run() {
if (!mFocusedFieldId.isEmpty()) {
switch (mFocusedFieldId) {
case "zss_field_title":
updateFormatBarEnabledState(false);
break;
case "zss_field_content":
updateFormatBarEnabledState(true);
break;
}
}
}
});
}
public void onMediaTapped(final String mediaId, final MediaType mediaType, final JSONObject meta, String uploadStatus) {
if (mediaType == null || !isAdded()) {
return;
}
switch (uploadStatus) {
case "uploading":
// Display 'cancel upload' dialog
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getString(R.string.stop_upload_dialog_title));
builder.setPositiveButton(R.string.stop_upload_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mEditorFragmentListener.onMediaUploadCancelClicked(mediaId, true);
mWebView.post(new Runnable() {
@Override
public void run() {
switch (mediaType) {
case IMAGE:
mWebView.execJavaScriptFromString("ZSSEditor.removeImage(" + mediaId + ");");
break;
case VIDEO:
mWebView.execJavaScriptFromString("ZSSEditor.removeVideo(" + mediaId + ");");
}
mUploadingMedia.remove(mediaId);
}
});
dialog.dismiss();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.show();
break;
case "failed":
// Retry media upload
mEditorFragmentListener.onMediaRetryClicked(mediaId);
mWebView.post(new Runnable() {
@Override
public void run() {
switch (mediaType) {
case IMAGE:
mWebView.execJavaScriptFromString("ZSSEditor.unmarkImageUploadFailed(" + mediaId
+ ");");
break;
case VIDEO:
mWebView.execJavaScriptFromString("ZSSEditor.unmarkVideoUploadFailed(" + mediaId
+ ");");
}
mFailedMediaIds.remove(mediaId);
mUploadingMedia.put(mediaId, mediaType);
}
});
break;
default:
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) {
return;
}
mEditorFragmentListener.onTrackableEvent(TrackableEvent.IMAGE_EDITED);
ImageSettingsDialogFragment imageSettingsDialogFragment = new ImageSettingsDialogFragment();
imageSettingsDialogFragment.setTargetFragment(this,
ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_REQUEST_CODE);
Bundle dialogBundle = new Bundle();
dialogBundle.putString("maxWidth", mBlogSettingMaxImageWidth);
dialogBundle.putBoolean("featuredImageSupported", mFeaturedImageSupported);
// Request and add an authorization header for HTTPS images
// Use https:// when requesting the auth header, in case the image is incorrectly using http://.
// If an auth header is returned, force https:// for the actual HTTP request.
HashMap<String, String> headerMap = new HashMap<>();
if (mCustomHttpHeaders != null) {
headerMap.putAll(mCustomHttpHeaders);
}
try {
final String imageSrc = meta.getString("src");
String authHeader = mEditorFragmentListener.onAuthHeaderRequested(UrlUtils.makeHttps(imageSrc));
if (authHeader.length() > 0) {
meta.put("src", UrlUtils.makeHttps(imageSrc));
headerMap.put("Authorization", authHeader);
}
} catch (JSONException e) {
AppLog.e(T.EDITOR, "Could not retrieve image url from JSON metadata");
}
dialogBundle.putSerializable("headerMap", headerMap);
dialogBundle.putString("imageMeta", meta.toString());
String imageId = JSONUtils.getString(meta, "attachment_id");
if (!imageId.isEmpty()) {
dialogBundle.putBoolean("isFeatured", mFeaturedImageId == Integer.parseInt(imageId));
}
imageSettingsDialogFragment.setArguments(dialogBundle);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.add(android.R.id.content, imageSettingsDialogFragment,
ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_TAG)
.addToBackStack(null)
.commit();
mWebView.notifyVisibilityChanged(false);
break;
}
}
public void onLinkTapped(String url, String title) {
LinkDialogFragment linkDialogFragment = new LinkDialogFragment();
linkDialogFragment.setTargetFragment(this, LinkDialogFragment.LINK_DIALOG_REQUEST_CODE_UPDATE);
Bundle dialogBundle = new Bundle();
dialogBundle.putString(LinkDialogFragment.LINK_DIALOG_ARG_URL, url);
dialogBundle.putString(LinkDialogFragment.LINK_DIALOG_ARG_TEXT, title);
linkDialogFragment.setArguments(dialogBundle);
linkDialogFragment.show(getFragmentManager(), "LinkDialogFragment");
}
@Override
public void onMediaRemoved(String mediaId) {
mUploadingMedia.remove(mediaId);
mFailedMediaIds.remove(mediaId);
mEditorFragmentListener.onMediaUploadCancelClicked(mediaId, true);
}
@Override
public void onMediaReplaced(String mediaId) {
mUploadingMedia.remove(mediaId);
}
@Override
public void onVideoPressInfoRequested(final String videoId) {
mEditorFragmentListener.onVideoPressInfoRequested(videoId);
}
public void onGetHtmlResponse(Map<String, String> inputArgs) {
String functionId = inputArgs.get("function");
if (functionId.isEmpty()) {
return;
}
switch (functionId) {
case "getHTMLForCallback":
String fieldId = inputArgs.get("id");
String fieldContents = inputArgs.get("contents");
if (!fieldId.isEmpty()) {
switch (fieldId) {
case "zss_field_title":
mTitle = fieldContents;
mGetTitleCountDownLatch.countDown();
break;
case "zss_field_content":
mContentHtml = fieldContents;
mGetContentCountDownLatch.countDown();
break;
}
}
break;
case "getSelectedTextToLinkify":
mJavaScriptResult = inputArgs.get("result");
mGetSelectedTextCountDownLatch.countDown();
break;
case "getFailedMedia":
String[] mediaIds = inputArgs.get("ids").split(",");
for (String mediaId : mediaIds) {
if (!mediaId.equals("")) {
mFailedMediaIds.add(mediaId);
}
}
}
}
public void setWebViewErrorListener(ErrorListener errorListener) {
mWebView.setErrorListener(errorListener);
}
private void updateVisualEditorFields() {
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_title').setPlainText('" +
Utils.escapeHtml(mTitle) + "');");
mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').setHTML('" +
Utils.escapeHtml(mContentHtml) + "');");
}
/**
* Hide the action bar if needed.
*/
private void hideActionBarIfNeeded() {
ActionBar actionBar = getActionBar();
if (actionBar != null
&& !isHardwareKeyboardPresent()
&& mHideActionBarOnSoftKeyboardUp
&& mIsKeyboardOpen
&& actionBar.isShowing()) {
getActionBar().hide();
}
}
/**
* Show the action bar if needed.
*/
private void showActionBarIfNeeded() {
ActionBar actionBar = getActionBar();
if (actionBar != null && !actionBar.isShowing()) {
actionBar.show();
}
}
/**
* Returns true if a hardware keyboard is detected, otherwise false.
*/
private boolean isHardwareKeyboardPresent() {
Configuration config = getResources().getConfiguration();
boolean returnValue = false;
if (config.keyboard != Configuration.KEYBOARD_NOKEYS) {
returnValue = true;
}
return returnValue;
}
void updateFormatBarEnabledState(boolean enabled) {
float alpha = (enabled ? TOOLBAR_ALPHA_ENABLED : TOOLBAR_ALPHA_DISABLED);
for(ToggleButton button : mTagToggleButtonMap.values()) {
button.setEnabled(enabled);
button.setAlpha(alpha);
}
mIsFormatBarDisabled = !enabled;
}
private void clearFormatBarButtons() {
for (ToggleButton button : mTagToggleButtonMap.values()) {
if (button != null) {
button.setChecked(false);
}
}
}
private void onFormattingButtonClicked(ToggleButton toggleButton) {
String tag = toggleButton.getTag().toString();
buttonTappedListener(toggleButton);
if (mWebView.getVisibility() == View.VISIBLE) {
mWebView.execJavaScriptFromString("ZSSEditor.set" + StringUtils.capitalize(tag) + "();");
} else {
applyFormattingHtmlMode(toggleButton, tag);
}
}
private void buttonTappedListener(ToggleButton toggleButton) {
int id = toggleButton.getId();
if (id == R.id.format_bar_button_bold) {
mEditorFragmentListener.onTrackableEvent(TrackableEvent.BOLD_BUTTON_TAPPED);
} else if (id == R.id.format_bar_button_italic) {
mEditorFragmentListener.onTrackableEvent(TrackableEvent.ITALIC_BUTTON_TAPPED);
} else if (id == R.id.format_bar_button_ol) {
mEditorFragmentListener.onTrackableEvent(TrackableEvent.OL_BUTTON_TAPPED);
} else if (id == R.id.format_bar_button_ul) {
mEditorFragmentListener.onTrackableEvent(TrackableEvent.UL_BUTTON_TAPPED);
} else if (id == R.id.format_bar_button_quote) {
mEditorFragmentListener.onTrackableEvent(TrackableEvent.BLOCKQUOTE_BUTTON_TAPPED);
} else if (id == R.id.format_bar_button_strikethrough) {
mEditorFragmentListener.onTrackableEvent(TrackableEvent.STRIKETHROUGH_BUTTON_TAPPED);
}
}
/**
* In HTML mode, applies formatting to selected text, or inserts formatting tag at current cursor position
* @param toggleButton format bar button which was clicked
* @param tag identifier tag
*/
private void applyFormattingHtmlMode(ToggleButton toggleButton, String tag) {
if (mSourceViewContent == null) {
return;
}
// Replace style tags with their proper HTML tags
String htmlTag;
if (tag.equals(getString(R.string.format_bar_tag_bold))) {
htmlTag = "b";
} else if (tag.equals(getString(R.string.format_bar_tag_italic))) {
htmlTag = "i";
} else if (tag.equals(getString(R.string.format_bar_tag_strikethrough))) {
htmlTag = "del";
} else if (tag.equals(getString(R.string.format_bar_tag_unorderedList))) {
htmlTag = "ul";
} else if (tag.equals(getString(R.string.format_bar_tag_orderedList))) {
htmlTag = "ol";
} else {
htmlTag = tag;
}
int selectionStart = mSourceViewContent.getSelectionStart();
int selectionEnd = mSourceViewContent.getSelectionEnd();
if (selectionStart > selectionEnd) {
int temp = selectionEnd;
selectionEnd = selectionStart;
selectionStart = temp;
}
boolean textIsSelected = selectionEnd > selectionStart;
String startTag = "<" + htmlTag + ">";
String endTag = "</" + htmlTag + ">";
// Add li tags together with ul and ol tags
if (htmlTag.equals("ul") || htmlTag.equals("ol")) {
startTag = startTag + "\n\t<li>";
endTag = "</li>\n" + endTag;
}
Editable content = mSourceViewContent.getText();
if (textIsSelected) {
// Surround selected text with opening and closing tags
content.insert(selectionStart, startTag);
content.insert(selectionEnd + startTag.length(), endTag);
toggleButton.setChecked(false);
mSourceViewContent.setSelection(selectionEnd + startTag.length() + endTag.length());
} else if (toggleButton.isChecked()) {
// Insert opening tag
content.insert(selectionStart, startTag);
mSourceViewContent.setSelection(selectionEnd + startTag.length());
} else {
// Insert closing tag
content.insert(selectionEnd, endTag);
mSourceViewContent.setSelection(selectionEnd + endTag.length());
}
}
@Override
public void onActionFinished() {
mActionStartedAt = -1;
}
}