accomplish link format

This commit is contained in:
houxg
2016-12-06 15:33:46 +08:00
parent 2b3a645d52
commit 873bec1d59
12 changed files with 322 additions and 78 deletions

View File

@@ -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 "";
}
}
</script>
</html>

View File

@@ -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<Style, Boolean> formatStatus);
void onFormatsChanged(Map<Style, Boolean> formatStatus);
void onCursorChanged(int index, Map<Style, Object> formatStatus);
void onFormatsChanged(Map<Style, Object> formatStatus);
}
protected class EditorClient extends WebViewClient {

View File

@@ -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<String, Boolean> 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<String, String> responseArgs);
void onFormatChanged(Map<Editor.Style, Boolean> formatStatus);
void onCursorChanged(int index, Map<Editor.Style, Boolean> formatStatus);
void onFormatChanged(Map<Editor.Style, Object> formatStatus);
void onCursorChanged(int index, Map<Editor.Style, Object> formatStatus);
}

View File

@@ -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<Editor.Style, Boolean> formatStatusMap = parseFormats(formats);
Map<Editor.Style, Object> 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<Editor.Style, Boolean> formatStatusMap = parseFormats(formats);
Map<Editor.Style, Object> formatStatusMap = parseFormats(formats);
mListener.onCursorChanged(index, formatStatusMap);
}
@@ -47,14 +48,22 @@ public class QuillCallbackHandler {
if (mListener == null) {
return;
}
Map<Editor.Style, Boolean> formatStatusMap = parseFormats(formats);
Map<Editor.Style, Object> 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<Editor.Style, Boolean> parseFormats(String formats) {
private Map<Editor.Style, Object> parseFormats(String formats) {
Map<String, Object> formatsMap = mGson.fromJson(formats, Map.class);
Map<Editor.Style, Boolean> formatStatusMap = new HashMap<>();
Map<Editor.Style, Object> formatStatusMap = new HashMap<>();
for (Map.Entry<String, Object> 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;

View File

@@ -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<String, String> responseArgs) {
Log.i(TAG, "onSelectionChanged(), data=" + new Gson().toJson(responseArgs));
}
@Override
public void onFormatChanged(Map<Style, Boolean> formatStatus) {
public void onFormatChanged(Map<Style, Object> formatStatus) {
mListener.onFormatsChanged(formatStatus);
}
@Override
public void onCursorChanged(int index, Map<Style, Boolean> formatStatus) {
public void onCursorChanged(int index, Map<Style, Object> formatStatus) {
mListener.onCursorChanged(index, formatStatus);
}
}

View File

@@ -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<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(mEditor.getSelection());
subscriber.onCompleted();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return !TextUtils.isEmpty(s);
}
})
.subscribe(new Action1<String>() {
@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<Editor.Style, Boolean> formatStatus) {
public void onCursorChanged(int index, final Map<Editor.Style, Object> 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<Editor.Style, Boolean> 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<Editor.Style, Object> formatStatus) {
for (Map.Entry<Editor.Style, Object> 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<Editor.Style, Boolean> formatStatus) {
public void onFormatsChanged(final Map<Editor.Style, Object> formatStatus) {
mBoldBtn.post(new Runnable() {
@Override
public void run() {
for (Map.Entry<Editor.Style, Boolean> 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);
}
});
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="100"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="100"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>

View File

@@ -65,7 +65,8 @@
<org.houxg.leamonax.widget.ToggleImageButton
android:id="@+id/btn_link"
style="@style/FormatButton"
android:src="@drawable/ic_insert_link_black_disable" />
app:checkedDrawable="@drawable/ic_insert_link_black_enable"
app:uncheckedDrawable="@drawable/ic_insert_link_black_disable" />
</LinearLayout>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#E0E0E0"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/et_link"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="https://"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColorHint="@color/hint_text_light" />
<TextView
android:id="@+id/tv_multiple_links"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:fontFamily="sans-serif"
android:text="@string/multiple_links"
android:visibility="gone" />
<TextView
android:id="@+id/tv_clear"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:paddingBottom="8dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:text="@string/clear"
android:textColor="@color/primary_text_light"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:paddingBottom="8dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:text="@string/confirm"
android:textColor="@color/primary_text_light"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="anim_pop_up">
<item name="android:windowEnterAnimation">@anim/popup_in</item>
<item name="android:windowExitAnimation">@anim/popup_out</item>
</style>
</resources>

View File

@@ -64,4 +64,6 @@
<string name="change_user_name_successful">Change user name successful</string>
<string name="search">Search</string>
<string name="notebooks">Notebooks</string>
<string name="clear">Clear</string>
<string name="multiple_links">Multiple links</string>
</resources>