add new web container, support format: bold, italic, blockquote, ordered list, bullet list, header, image

remaining problems
- unfinished link format
- format button’s status
- output not equals to input
- editor’s border
This commit is contained in:
houxg
2016-12-09 19:14:42 +08:00
parent 1df309e6fd
commit f39bf4b82b
4 changed files with 209 additions and 43 deletions

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html>
<head>
<title>RichTextEditor</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<script src="./tinymce/tinymce.min.js"></script>
</head>
<body>
<div contenteditable="true" id="title"></div>
<hr>
<form method="post">
<div id="content"></div>
</form>
</body>
<script type="text/javascript">
var titleDiv = document.getElementById('title');
var nativeCallbackHandler = {};
nativeCallbackHandler.onFormatChange = function (format, msg) {
console.log(format, msg);
}
function getTitle() {
return titleDiv.innerHTML;
}
function setTitle(title) {
return titleDiv.innerHTML = title;
}
tinymce.init({
selector: 'div#content',
remove_trailing_brs: false,
element_format: 'html',
allow_unsafe_link_target: true,
plugins: "lists",
// toolbar: false,
// menubar: false,
inline: true
});
function toggleBold() {
tinyMCE.editors[0].formatter.toggle('bold');
var currentState = tinyMCE.editors[0].formatter.match('bold');
nativeCallbackHandler.onFormatChange('bold', currentState);
}
function toggleBlockquote() {
tinyMCE.editors[0].formatter.toggle('blockquote');
var currentState = tinyMCE.editors[0].formatter.match('blockquote');
nativeCallbackHandler.onFormatChange('blockquote', currentState);
}
function toggleHeader() {
var currentHeader = getCurrentHeader();
if (currentHeader) {
var currentVal = parseInt(currentHeader.substr(1));
var newVal = (currentVal + 1) % 7;
if (newVal) {
tinyMCE.editors[0].formatter.apply('h' + newVal);
} else {
tinyMCE.editors[0].formatter.remove(currentHeader);
}
} else {
tinyMCE.editors[0].formatter.apply('h1');
}
currentHeader = getCurrentHeader();
nativeCallbackHandler.onFormatChange('header', currentHeader);
}
function getCurrentHeader() {
var intrestFormats = [
'h1',
'h2',
'h3',
'h4',
'h5',
'h6'
];
var result = tinyMCE.editors[0].formatter.matchAll(intrestFormats);
if (result) {
return result[0];
} else {
return "";
}
}
function toggleItalic() {
tinyMCE.editors[0].formatter.toggle('italic');
var currentState = tinyMCE.editors[0].formatter.match('italic');
nativeCallbackHandler.onFormatChange('italic', currentState);
}
function toggleBulletList() {
tinyMCE.editors[0].execCommand("InsertUnorderedList", false)
var listState = getListState();
nativeCallbackHandler.onFormatChange('bullet', listState && listState.toLowerCase() === 'ul');
}
function toggleOrderedList() {
tinyMCE.editors[0].execCommand("InsertOrderedList", false)
var listState = getListState();
nativeCallbackHandler.onFormatChange('bullet', listState && listState.toLowerCase() === 'ol');
}
function insertImage(src) {
tinyMCE.editors[0].insertContent('<img src="' + src + '" alt=""/>');
}
function formatLink(url) {
tinyMCE.editors[0].formatter.apply('link', {
href: url
});
}
function removeLink(title, url) {
tinyMCE.editors[0].formatter.remove('link');
}
function enable() {
document.addEventListener("selectionchange", selectionChangeHandler, false);
document.removeEventListener("click", clickHandeler, false);
tinyMCE.editors[0].setMode('design');
titleDiv.setAttribute("contenteditable", true);
}
function disable() {
document.removeEventListener("selectionchange", selectionChangeHandler, false);
document.addEventListener("click", clickHandeler, false);
tinyMCE.editors[0].setMode('readonly');
titleDiv.setAttribute("contenteditable", false);
}
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 selectionChangeHandler() {
var intrestFormats = ['bold',
'italic',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'blockquote',
'link'
];
var result = tinyMCE.editors[0].formatter.matchAll(intrestFormats);
listState = getListState();
if (listState) {
result.push(listState);
}
console.log(result);
nativeCallbackHandler.onCursorChanged(JSON.stringify(result));
}
function getListState() {
var node = tinyMCE.editors[0].selection.getNode()
var parents = tinymce.dom.DomQuery(node).parents();
var lists = tinyMCE.util.Tools.grep(parents, isNodeList);
if (lists.length > 0) {
return lists[0].nodeName.toLowerCase();
} else {
return "";
}
}
function isNodeList(node) {
return node && (/^(OL|UL|DL)$/).test(node.nodeName) && isChildOfBody(node);
}
function isChildOfBody(elm) {
return tinyMCE.editors[0].$.contains(tinyMCE.editors[0].getBody(), elm);
}
</script>
</html>

View File

@@ -2,9 +2,7 @@ package org.houxg.leamonax.editor;
import android.annotation.SuppressLint;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import com.google.gson.Gson;
@@ -34,9 +32,9 @@ public class RichTextEditor extends Editor implements OnJsEditorStateChangedList
mWebView.setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new EditorClient());
mWebView.setWebChromeClient(new WebChromeClient());
mWebView.setWebChromeClient(new EditorChromeClient());
mWebView.addJavascriptInterface(new JsCallbackHandler(this), JS_CALLBACK_HANDLER);
mWebView.loadUrl("file:///android_asset/android-editor.html");
mWebView.loadUrl("file:///android_asset/RichTextEditor/editor.html");
}
private void execJs(final String script) {
@@ -51,46 +49,42 @@ public class RichTextEditor extends Editor implements OnJsEditorStateChangedList
@Override
public void setEditingEnabled(boolean enabled) {
if (enabled) {
execJs("ZSSEditor.getField('zss_field_title').enableEditing();");
execJs("ZSSEditor.getField('zss_field_content').enableEditing();");
execJs("enable();");
} else {
execJs("ZSSEditor.getField('zss_field_title').disableEditing();");
execJs("ZSSEditor.getField('zss_field_content').disableEditing();");
execJs("disable()");
}
}
@Override
public void setTitle(String title) {
execJs(String.format(Locale.US, "ZSSEditor.getField('zss_field_title').setPlainText('%s');", HtmlUtils.escapeHtml(title)));
execJs(String.format(Locale.US, "setTitle('%s');", HtmlUtils.escapeHtml(title)));
}
@Override
public String getTitle() {
return HtmlUtils.unescapeHtml(new JsRunner().get(mWebView, "ZSSEditor.getField('zss_field_title').getHTML();"));
return HtmlUtils.unescapeHtml(new JsRunner().get(mWebView, "getTitle();"));
}
@Override
public void setContent(String content) {
execJs(String.format(Locale.US, "ZSSEditor.getField('zss_field_content').setHTML('%s');", HtmlUtils.escapeHtml(content)));
execJs(String.format(Locale.US, "tinyMCE.editors[0].setContent('%s');", HtmlUtils.escapeHtml(content)));
}
@Override
public String getContent() {
String content = HtmlUtils.unescapeHtml(new JsRunner().get(mWebView, "ZSSEditor.getField('zss_field_content').getHTML();"));
if (!TextUtils.isEmpty(content)) {
content = appendPTag(content);
}
String content = HtmlUtils.unescapeHtml(new JsRunner().get(mWebView, "tinyMCE.editors[0].getContent();"));
content = content.replaceAll("\\n", "");
return content;
}
@Override
public void insertImage(String title, String url) {
execJs(String.format(Locale.US, "ZSSEditor.insertImage('%s', '%s');", url, title));
execJs(String.format(Locale.US, "insertImage('%s');", url));
}
@Override
public void insertLink(String title, String url) {
execJs(String.format(Locale.US, "ZSSEditor.insertLink('%s', '%s');", url, title));
execJs(String.format(Locale.US, "formatLink('%s');", url));
}
@Override
@@ -100,60 +94,44 @@ public class RichTextEditor extends Editor implements OnJsEditorStateChangedList
@Override
public void redo() {
execJs("ZSSEditor.redo();");
execJs("tinyMCE.editors[0].undoManager.redo();");
}
@Override
public void undo() {
execJs("ZSSEditor.undo();");
execJs("tinyMCE.editors[0].undoManager.undo();");
}
@Override
public void toggleOrderList() {
execJs("ZSSEditor.setOrderedList();");
execJs("toggleOrderedList();");
}
@Override
public void toggleUnorderList() {
execJs("ZSSEditor.setUnorderedList();");
execJs("toggleBulletList();");
}
@Override
public void toggleBold() {
execJs("ZSSEditor.setBold();");
execJs("toggleBold();");
}
@Override
public void toggleItalic() {
execJs("ZSSEditor.setItalic();");
execJs("toggleItalic();");
}
@Override
public void toggleQuote() {
execJs("ZSSEditor.setBlockquote();");
execJs("toggleBlockquote();");
}
@Override
public void toggleHeading() {
execJs("ZSSEditor.setHeading();");
execJs("toggleHeader();");
}
private String appendPTag(String source) {
String[] segments = source.split("\n\n");
StringBuilder contentBuilder = new StringBuilder();
if (segments.length > 0) {
for (String segment : segments) {
contentBuilder.append("<p>");
contentBuilder.append(segment);
contentBuilder.append("</p>");
}
return contentBuilder.toString();
} else {
return source;
}
}
@Override
public void onDomLoaded() {
execJs("ZSSEditor.getField('zss_field_content').setMultiline('true');");

View File

@@ -284,7 +284,8 @@ public class Note extends BaseModel implements Serializable {
private boolean isChanged(String message, Object l, Object r) {
boolean isEqual = l.equals(r);
if (!isEqual) {
Log.i("Note", message + " changed, origin=" + l + ", modified=" + r);
Log.i("Note", message + " changed, origin =" + l);
Log.i("Note", message + " changed, modified=" + r);
}
return !isEqual;
}

View File

@@ -215,7 +215,7 @@ public class NoteEditActivity extends BaseActivity implements EditorFragment.Edi
if (!subscriber.isUnsubscribed()) {
updateNote();
if (mModified.note.isDirty()
|| mModified.note.hasChanges(mOriginal.note)
|| mOriginal.note.hasChanges(mModified.note)
|| isLocalNote(mModified.note)
|| isTitleContentEmpty(mModified.note)
|| !CollectionUtils.isTheSame(mOriginal.tags, mModified.tags)) {