From 873bec1d5986fcd10945555a98cc6611486cb439 Mon Sep 17 00:00:00 2001 From: houxg Date: Tue, 6 Dec 2016 15:33:46 +0800 Subject: [PATCH] accomplish link format --- .../main/assets/RichTextEditor/editor.html | 30 ++- .../org/houxg/leamonax/editor/Editor.java | 12 +- .../OnJsEditorStateChangedListener.java | 6 +- .../leamonax/editor/QuillCallbackHandler.java | 31 ++- .../houxg/leamonax/editor/RichTextEditor.java | 21 +- .../leamonax/ui/edit/EditorFragment.java | 207 +++++++++++++----- app/src/main/res/anim/popup_in.xml | 8 + app/src/main/res/anim/popup_out.xml | 8 + .../main/res/layout/format_bar_richtext.xml | 3 +- app/src/main/res/layout/pop_link.xml | 65 ++++++ app/src/main/res/values/anim_pop_up.xml | 7 + app/src/main/res/values/strings.xml | 2 + 12 files changed, 322 insertions(+), 78 deletions(-) create mode 100644 app/src/main/res/anim/popup_in.xml create mode 100644 app/src/main/res/anim/popup_out.xml create mode 100644 app/src/main/res/layout/pop_link.xml create mode 100644 app/src/main/res/values/anim_pop_up.xml diff --git a/app/src/main/assets/RichTextEditor/editor.html b/app/src/main/assets/RichTextEditor/editor.html index d276998..55acffe 100644 --- a/app/src/main/assets/RichTextEditor/editor.html +++ b/app/src/main/assets/RichTextEditor/editor.html @@ -28,7 +28,7 @@ for (var i = 0; i < delta.ops.length; i++) { var operation = delta.ops[i]; if (operation.hasOwnProperty('attributes')) { - nativeCallbackHandler.onFormatChanged(JSON.stringify(operation['attributes'])); + nativeCallbackHandler.onFormatChanged(JSON.stringify(operation['attributes'])); } } }); @@ -90,7 +90,7 @@ if (range) { if (range.length == 0) { var formats = quill.getFormat(); - nativeCallbackHandler.onCusorChanged(range.index, JSON.stringify(formats)) + nativeCallbackHandler.onCursorChanged(range.index, JSON.stringify(formats)) } else { var text = quill.getText(range.index, range.length); nativeCallbackHandler.onHighlighted(range.index, range.length, JSON.stringify(quill.getFormat(range.index, range.length))) @@ -98,14 +98,26 @@ } } + function clickHandeler(e) { + var target = e.target; + if (target.tagName === 'A') { + var link = target.getAttribute('href'); + var title = target.innerHTML; + e.preventDefault(); + nativeCallbackHandler.gotoLink(title, link); + } + } + function enable() { document.addEventListener("selectionchange", selectionChangeHandler, false); + document.removeEventListener("click", clickHandeler, false); quill.enable(); titleDiv.setAttribute("contenteditable", true); } function disable() { document.removeEventListener("selectionchange", selectionChangeHandler, false); + document.addEventListener("click", clickHandeler, false); quill.disable(); titleDiv.setAttribute("contenteditable", false); } @@ -133,5 +145,19 @@ function setTitle(title) { return titleDiv.innerHTML = title; } + + function getSelection() { + var range = quill.getSelection(); + if (range) { + var length = range.length; + if (length > 0) { + return quill.getText(range.index, range.length); + } else { + return ""; + } + } else { + return ""; + } + } diff --git a/app/src/main/java/org/houxg/leamonax/editor/Editor.java b/app/src/main/java/org/houxg/leamonax/editor/Editor.java index 386cc92..c92088d 100644 --- a/app/src/main/java/org/houxg/leamonax/editor/Editor.java +++ b/app/src/main/java/org/houxg/leamonax/editor/Editor.java @@ -22,6 +22,7 @@ public abstract class Editor { ORDER_LIST, UNORDER_LIST, HEADER, + LINK, BLOCK_QUOTE } @@ -65,12 +66,19 @@ public abstract class Editor { public abstract void toggleHeading(); + public void clearLink() {} + + public String getSelection() { + return null; + } + public interface EditorListener { void onPageLoaded(); void onClickedLink(String title, String url); + void gotoLink(String title, String url); void onStyleChanged(Style style, boolean enabled); - void onCursorChanged(int index, Map formatStatus); - void onFormatsChanged(Map formatStatus); + void onCursorChanged(int index, Map formatStatus); + void onFormatsChanged(Map formatStatus); } protected class EditorClient extends WebViewClient { diff --git a/app/src/main/java/org/houxg/leamonax/editor/OnJsEditorStateChangedListener.java b/app/src/main/java/org/houxg/leamonax/editor/OnJsEditorStateChangedListener.java index 1f91307..d538cd7 100755 --- a/app/src/main/java/org/houxg/leamonax/editor/OnJsEditorStateChangedListener.java +++ b/app/src/main/java/org/houxg/leamonax/editor/OnJsEditorStateChangedListener.java @@ -2,7 +2,6 @@ package org.houxg.leamonax.editor; import org.json.JSONObject; -import java.util.HashMap; import java.util.Map; public interface OnJsEditorStateChangedListener { @@ -11,7 +10,8 @@ public interface OnJsEditorStateChangedListener { void onSelectionStyleChanged(Map changeSet); void onMediaTapped(String mediaId, String url, JSONObject meta, String uploadStatus); void onLinkTapped(String url, String title); + void gotoLink(String title, String url); void onGetHtmlResponse(Map responseArgs); - void onFormatChanged(Map formatStatus); - void onCursorChanged(int index, Map formatStatus); + void onFormatChanged(Map formatStatus); + void onCursorChanged(int index, Map formatStatus); } diff --git a/app/src/main/java/org/houxg/leamonax/editor/QuillCallbackHandler.java b/app/src/main/java/org/houxg/leamonax/editor/QuillCallbackHandler.java index 523cbec..d7c7aab 100644 --- a/app/src/main/java/org/houxg/leamonax/editor/QuillCallbackHandler.java +++ b/app/src/main/java/org/houxg/leamonax/editor/QuillCallbackHandler.java @@ -8,6 +8,7 @@ import android.webkit.JavascriptInterface; import com.google.gson.Gson; import java.util.HashMap; +import java.util.List; import java.util.Map; public class QuillCallbackHandler { @@ -27,17 +28,17 @@ public class QuillCallbackHandler { if (mListener == null) { return; } - Map formatStatusMap = parseFormats(formats); + Map formatStatusMap = parseFormats(formats); mListener.onFormatChanged(formatStatusMap); } @JavascriptInterface - public void onCusorChanged(int index, String formats) { - Log.i(TAG, "onCusorChanged(), index=" + index + ", formats=" + formats); + public void onCursorChanged(int index, String formats) { + Log.i(TAG, "onCursorChanged(), index=" + index + ", formats=" + formats); if (mListener == null) { return; } - Map formatStatusMap = parseFormats(formats); + Map formatStatusMap = parseFormats(formats); mListener.onCursorChanged(index, formatStatusMap); } @@ -47,14 +48,22 @@ public class QuillCallbackHandler { if (mListener == null) { return; } - Map formatStatusMap = parseFormats(formats); + Map formatStatusMap = parseFormats(formats); mListener.onCursorChanged(index, formatStatusMap); } + @JavascriptInterface + public void gotoLink(String title, String link) { + if (mListener == null) { + return; + } + mListener.gotoLink(title, link); + } + @NonNull - private Map parseFormats(String formats) { + private Map parseFormats(String formats) { Map formatsMap = mGson.fromJson(formats, Map.class); - Map formatStatusMap = new HashMap<>(); + Map formatStatusMap = new HashMap<>(); for (Map.Entry format : formatsMap.entrySet()) { switch (format.getKey()) { case "bold": @@ -81,6 +90,14 @@ public class QuillCallbackHandler { case "header": Double headerLevel = ((Double)format.getValue()); formatStatusMap.put(Editor.Style.HEADER, headerLevel != null && headerLevel > 0); + break; + case "link": + Object linkValue = format.getValue(); + if (!(linkValue instanceof String) && !(linkValue instanceof List)){ + linkValue = null; + } + formatStatusMap.put(Editor.Style.LINK, linkValue); + break; } } return formatStatusMap; diff --git a/app/src/main/java/org/houxg/leamonax/editor/RichTextEditor.java b/app/src/main/java/org/houxg/leamonax/editor/RichTextEditor.java index d9f2e78..4a1ebd3 100644 --- a/app/src/main/java/org/houxg/leamonax/editor/RichTextEditor.java +++ b/app/src/main/java/org/houxg/leamonax/editor/RichTextEditor.java @@ -81,6 +81,11 @@ public class RichTextEditor extends Editor implements OnJsEditorStateChangedList return content; } + @Override + public String getSelection() { + return new JsRunner().get(mWebView, "getSelection();"); + } + @Override public void insertImage(String title, String url) { execJs(String.format(Locale.US, "insertImage('%s', '%s');", title, url)); @@ -93,7 +98,12 @@ public class RichTextEditor extends Editor implements OnJsEditorStateChangedList @Override public void updateLink(String title, String url) { - execJs(String.format(Locale.US, "ZSSEditor.updateLink('%s', '%s');", url, title)); + execJs(String.format(Locale.US, "quill.format('link', '%s');", url)); + } + + @Override + public void clearLink() { + execJs("quill.format('link', null);"); } @Override @@ -193,18 +203,23 @@ public class RichTextEditor extends Editor implements OnJsEditorStateChangedList mListener.onClickedLink(title, url); } + @Override + public void gotoLink(String title, String url) { + mListener.gotoLink(title, url); + } + @Override public void onGetHtmlResponse(Map responseArgs) { Log.i(TAG, "onSelectionChanged(), data=" + new Gson().toJson(responseArgs)); } @Override - public void onFormatChanged(Map formatStatus) { + public void onFormatChanged(Map formatStatus) { mListener.onFormatsChanged(formatStatus); } @Override - public void onCursorChanged(int index, Map formatStatus) { + public void onCursorChanged(int index, Map formatStatus) { mListener.onCursorChanged(index, formatStatus); } } diff --git a/app/src/main/java/org/houxg/leamonax/ui/edit/EditorFragment.java b/app/src/main/java/org/houxg/leamonax/ui/edit/EditorFragment.java index f9f41b6..f271265 100644 --- a/app/src/main/java/org/houxg/leamonax/ui/edit/EditorFragment.java +++ b/app/src/main/java/org/houxg/leamonax/ui/edit/EditorFragment.java @@ -8,12 +8,19 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v4.view.GravityCompat; +import android.text.TextUtils; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.webkit.WebView; +import android.widget.EditText; import android.widget.ImageView; +import android.widget.PopupWindow; +import android.widget.TextView; import com.bumptech.glide.Glide; import com.yuyh.library.imgsel.ImgSelActivity; @@ -34,6 +41,12 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Optional; +import rx.Observable; +import rx.Subscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; public class EditorFragment extends Fragment implements Editor.EditorListener { @@ -56,16 +69,17 @@ public class EditorFragment extends Fragment implements Editor.EditorListener { @BindView(R.id.btn_italic) ToggleImageButton mItalicBtn; @Nullable - @BindView(R.id.btn_heading) - ToggleImageButton mHeadingBtn; - @Nullable @BindView(R.id.btn_quote) ToggleImageButton mQuoteBtn; + @BindView(R.id.btn_heading) + ToggleImageButton mHeadingBtn; @BindView(R.id.btn_order_list) ToggleImageButton mOrderListBtn; @BindView(R.id.btn_unorder_list) ToggleImageButton mUnorderListBtn; + @BindView(R.id.btn_link) + ToggleImageButton mLinkBtn; @BindView(R.id.web_editor) WebView mWebView; @@ -167,14 +181,95 @@ public class EditorFragment extends Fragment implements Editor.EditorListener { } @OnClick(R.id.btn_link) - void insertLink() { - DialogUtils.editLink(getActivity(), "", "", new DialogUtils.ChangedListener() { + void clickedLink() { + if (mEditor instanceof MarkdownEditor) { + DialogUtils.editLink(getActivity(), "", "", new DialogUtils.ChangedListener() { + @Override + public void onChanged(String title, String link) { + Log.i(TAG, "title=" + title + ", url=" + link); + mEditor.insertLink(title, link); + } + }); + } else if (mEditor instanceof RichTextEditor) { + if (mLinkBtn.isChecked()) { + showEditLInkPanel(mLinkBtn); + } else { + Observable.create( + new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + if (!subscriber.isUnsubscribed()) { + subscriber.onNext(mEditor.getSelection()); + subscriber.onCompleted(); + } + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter(new Func1() { + @Override + public Boolean call(String s) { + return !TextUtils.isEmpty(s); + } + }) + .subscribe(new Action1() { + @Override + public void call(String s) { + showEditLInkPanel(mLinkBtn); + } + }); + } + } + } + + private void showEditLInkPanel(View anchorView) { + View contentView = LayoutInflater.from(anchorView.getContext()).inflate(R.layout.pop_link, null); + contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + final EditText linkEt = (EditText) contentView.findViewById(R.id.et_link); + TextView multipleLinksTv = (TextView) contentView.findViewById(R.id.tv_multiple_links); + TextView confirmTv = (TextView) contentView.findViewById(R.id.tv_confirm); + TextView clearTv = (TextView) contentView.findViewById(R.id.tv_clear); + final PopupWindow popupWindow = new PopupWindow(contentView); + popupWindow.setFocusable(true); + popupWindow.setOutsideTouchable(true); + popupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); + popupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT); + popupWindow.setAnimationStyle(R.style.anim_pop_up); + + if (mLinkBtn.isChecked()) { + Object status = mLinkBtn.getTag(); + if (status == null) { + return; + } + boolean canEdit = status instanceof String; + linkEt.setVisibility(canEdit ? View.VISIBLE : View.GONE); + confirmTv.setVisibility(canEdit ? View.VISIBLE : View.GONE); + multipleLinksTv.setVisibility(canEdit ? View.GONE : View.VISIBLE); + if (canEdit) { + linkEt.setText((String) mLinkBtn.getTag()); + linkEt.setSelection(linkEt.getText().length()); + } + } + confirmTv.setOnClickListener(new View.OnClickListener() { @Override - public void onChanged(String title, String link) { - Log.i(TAG, "title=" + title + ", url=" + link); - mEditor.insertLink(title, link); + public void onClick(View v) { + popupWindow.dismiss(); + mEditor.updateLink("", linkEt.getText().toString()); } }); + clearTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + popupWindow.dismiss(); + mEditor.clearLink(); + } + }); + + int[] tempLocation = new int[2]; + anchorView.getLocationOnScreen(tempLocation); + int measure = contentView.getMeasuredHeight(); + int offsetY = tempLocation[1] - measure; + popupWindow.showAtLocation(anchorView, Gravity.TOP | GravityCompat.START, 0, offsetY); } @Override @@ -266,10 +361,16 @@ public class EditorFragment extends Fragment implements Editor.EditorListener { } }); } else { - //TODO: go to link + gotoLink(title, url); } } + @Override + public void gotoLink(String title, final String url) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(intent); + } + @Override public void onStyleChanged(final Editor.Style style, final boolean enabled) { mBoldBtn.post(new Runnable() { @@ -297,7 +398,7 @@ public class EditorFragment extends Fragment implements Editor.EditorListener { } @Override - public void onCursorChanged(int index, final Map formatStatus) { + public void onCursorChanged(int index, final Map formatStatus) { mBoldBtn.post(new Runnable() { @Override public void run() { @@ -312,65 +413,51 @@ public class EditorFragment extends Fragment implements Editor.EditorListener { if (mQuoteBtn != null) { mQuoteBtn.setChecked(false); } - if (mHeadingBtn != null) { - mHeadingBtn.setChecked(false); - } - for (Map.Entry entry : formatStatus.entrySet()) { - boolean enabled = entry.getValue(); - switch (entry.getKey()) { - case BOLD: - mBoldBtn.setChecked(enabled); - break; - case ITALIC: - mItalicBtn.setChecked(enabled); - break; - case ORDER_LIST: - mOrderListBtn.setChecked(enabled); - break; - case UNORDER_LIST: - mUnorderListBtn.setChecked(enabled); - break; - case BLOCK_QUOTE: - mQuoteBtn.setChecked(enabled); - break; - case HEADER: - if (mHeadingBtn != null) { - mHeadingBtn.setChecked(enabled); - } - break; - } + mHeadingBtn.setChecked(false); + if (mLinkBtn != null) { + mLinkBtn.setChecked(false); } + refreshFormatStatus(formatStatus); } }); } + private void refreshFormatStatus(Map formatStatus) { + for (Map.Entry entry : formatStatus.entrySet()) { + switch (entry.getKey()) { + case BOLD: + mBoldBtn.setChecked((Boolean) entry.getValue()); + break; + case ITALIC: + mItalicBtn.setChecked((Boolean) entry.getValue()); + break; + case ORDER_LIST: + mOrderListBtn.setChecked((Boolean) entry.getValue()); + break; + case UNORDER_LIST: + mUnorderListBtn.setChecked((Boolean) entry.getValue()); + break; + case BLOCK_QUOTE: + mQuoteBtn.setChecked((Boolean) entry.getValue()); + break; + case HEADER: + mHeadingBtn.setChecked((Boolean) entry.getValue()); + break; + case LINK: + Object linkValue = entry.getValue(); + mLinkBtn.setChecked(linkValue != null); + mLinkBtn.setTag(linkValue); + break; + } + } + } + @Override - public void onFormatsChanged(final Map formatStatus) { + public void onFormatsChanged(final Map formatStatus) { mBoldBtn.post(new Runnable() { @Override public void run() { - for (Map.Entry entry : formatStatus.entrySet()) { - boolean enabled = entry.getValue(); - switch (entry.getKey()) { - case BOLD: - mBoldBtn.setChecked(enabled); - break; - case ITALIC: - mItalicBtn.setChecked(enabled); - break; - case ORDER_LIST: - mOrderListBtn.setChecked(enabled); - break; - case UNORDER_LIST: - mUnorderListBtn.setChecked(enabled); - break; - case HEADER: - if (mHeadingBtn != null) { - mHeadingBtn.setChecked(enabled); - } - break; - } - } + refreshFormatStatus(formatStatus); } }); } diff --git a/app/src/main/res/anim/popup_in.xml b/app/src/main/res/anim/popup_in.xml new file mode 100644 index 0000000..8c1c276 --- /dev/null +++ b/app/src/main/res/anim/popup_in.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_out.xml b/app/src/main/res/anim/popup_out.xml new file mode 100644 index 0000000..470e036 --- /dev/null +++ b/app/src/main/res/anim/popup_out.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/format_bar_richtext.xml b/app/src/main/res/layout/format_bar_richtext.xml index 4605116..f65dbde 100755 --- a/app/src/main/res/layout/format_bar_richtext.xml +++ b/app/src/main/res/layout/format_bar_richtext.xml @@ -65,7 +65,8 @@ + app:checkedDrawable="@drawable/ic_insert_link_black_enable" + app:uncheckedDrawable="@drawable/ic_insert_link_black_disable" /> diff --git a/app/src/main/res/layout/pop_link.xml b/app/src/main/res/layout/pop_link.xml new file mode 100644 index 0000000..3da6808 --- /dev/null +++ b/app/src/main/res/layout/pop_link.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/anim_pop_up.xml b/app/src/main/res/values/anim_pop_up.xml new file mode 100644 index 0000000..3c9364a --- /dev/null +++ b/app/src/main/res/values/anim_pop_up.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06d4717..cc91539 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -64,4 +64,6 @@ Change user name successful Search Notebooks + Clear + Multiple links