Skip to content

Commit 97e3544

Browse files
committed
Improve behaviour of pasting of other types of objects
1 parent fdcf6ce commit 97e3544

File tree

4 files changed

+128
-72
lines changed

4 files changed

+128
-72
lines changed

aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ import org.wordpress.aztec.handlers.ListItemHandler
7474
import org.wordpress.aztec.handlers.PreformatHandler
7575
import org.wordpress.aztec.handlers.QuoteHandler
7676
import org.wordpress.aztec.plugins.IAztecPlugin
77+
import org.wordpress.aztec.plugins.IClipboardPastePlugin
78+
import org.wordpress.aztec.plugins.IClipboardPastePlugin.*
7779
import org.wordpress.aztec.plugins.IOnDrawPlugin
78-
import org.wordpress.aztec.plugins.ITextPastePlugin
7980
import org.wordpress.aztec.plugins.IToolbarButton
8081
import org.wordpress.aztec.source.Format
8182
import org.wordpress.aztec.source.SourceViewEditText
@@ -259,6 +260,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
259260
var isInCalypsoMode = true
260261
var isInGutenbergMode: Boolean = false
261262
val alignmentRendering: AlignmentRendering
263+
262264
// If this field is true, the media and horizontal line are added inline. If it's false, they are added after the
263265
// current block.
264266
var shouldAddMediaInline: Boolean = true
@@ -353,8 +355,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
353355
}
354356

355357
interface OnAztecKeyListener {
356-
fun onEnterKey(text: Spannable, firedAfterTextChanged: Boolean, selStart: Int, selEnd: Int) : Boolean
357-
fun onBackspaceKey() : Boolean
358+
fun onEnterKey(text: Spannable, firedAfterTextChanged: Boolean, selStart: Int, selEnd: Int): Boolean
359+
fun onBackspaceKey(): Boolean
358360
}
359361

360362
interface OnLinkTappedListener {
@@ -435,11 +437,11 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
435437
commentsVisible = styles.getBoolean(R.styleable.AztecText_commentsVisible, commentsVisible)
436438

437439
verticalParagraphPadding = styles.getDimensionPixelSize(R.styleable.AztecText_blockVerticalPadding,
438-
resources.getDimensionPixelSize(R.dimen.block_vertical_padding))
440+
resources.getDimensionPixelSize(R.dimen.block_vertical_padding))
439441
verticalParagraphMargin = styles.getDimensionPixelSize(R.styleable.AztecText_paragraphVerticalMargin,
440-
resources.getDimensionPixelSize(R.dimen.block_vertical_margin))
442+
resources.getDimensionPixelSize(R.dimen.block_vertical_margin))
441443
verticalHeadingMargin = styles.getDimensionPixelSize(R.styleable.AztecText_headingVerticalPadding,
442-
resources.getDimensionPixelSize(R.dimen.heading_vertical_padding))
444+
resources.getDimensionPixelSize(R.dimen.heading_vertical_padding))
443445

444446
inlineFormatter = InlineFormatter(this,
445447
InlineFormatter.CodeStyle(
@@ -597,7 +599,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
597599
isViewInitialized = true
598600
}
599601

600-
private fun <T>selectionHasExactlyOneMarker(start: Int, end: Int, type: Class<T>): Boolean {
602+
private fun <T> selectionHasExactlyOneMarker(start: Int, end: Int, type: Class<T>): Boolean {
601603
val spanFound: Array<T> = editableText.getSpans(
602604
start,
603605
end,
@@ -670,11 +672,11 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
670672
// problem is fixed at the Android OS level as described in the following url
671673
// https://android-review.googlesource.com/c/platform/frameworks/base/+/634929
672674
val dynamicLayoutCrashPreventer = InputFilter { source, start, end, dest, dstart, dend ->
673-
var temp : CharSequence? = null
675+
var temp: CharSequence? = null
674676
if (!bypassCrashPreventerInputFilter && dend < dest.length && source != Constants.NEWLINE_STRING) {
675677

676678
// if there are any images right after the destination position, hack the text
677-
val spans = dest.getSpans(dend, dend+1, AztecImageSpan::class.java)
679+
val spans = dest.getSpans(dend, dend + 1, AztecImageSpan::class.java)
678680
if (spans.isNotEmpty()) {
679681

680682
// prevent this filter from running recursively
@@ -730,7 +732,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
730732
}
731733

732734
private fun isCleanStringEmpty(text: CharSequence): Boolean {
733-
if ( isInGutenbergMode ) {
735+
if (isInGutenbergMode) {
734736
return (text.count() == 1 && text[0] == Constants.END_OF_BUFFER_MARKER)
735737
} else {
736738
return text.count() == 0
@@ -1045,7 +1047,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
10451047
this.onSelectionChangedListener = onSelectionChangedListener
10461048
}
10471049

1048-
fun getAztecKeyListener() : OnAztecKeyListener? {
1050+
fun getAztecKeyListener(): OnAztecKeyListener? {
10491051
return this.onAztecKeyListener
10501052
}
10511053

@@ -1823,7 +1825,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
18231825
} else {
18241826
return super.onTextContextMenuItem(id)
18251827
}
1826-
} else -> return super.onTextContextMenuItem(id)
1828+
}
1829+
else -> return super.onTextContextMenuItem(id)
18271830
}
18281831

18291832
return true
@@ -1900,21 +1903,42 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
19001903
enableTextChangedListener()
19011904

19021905
if (clip.itemCount > 0) {
1903-
val textToPaste = if (asPlainText) clip.getItemAt(0).coerceToText(context).toString()
1904-
else clip.getItemAt(0).coerceToHtmlText(AztecParser(alignmentRendering, plugins))
1905-
1906-
val oldHtml = toPlainHtml().replace("<aztec_cursor>", "")
1907-
val pastedHtmlText = plugins.filterIsInstance<ITextPastePlugin>().fold(textToPaste) { acc, plugin ->
1908-
if (selectedText.isNullOrEmpty()) {
1909-
plugin.toHtml(acc)
1910-
} else {
1911-
plugin.toHtml(selectedText, acc)
1906+
val firstItem = clip.getItemAt(0)
1907+
val itemToPaste = when {
1908+
firstItem.text.isNotBlank() -> {
1909+
val textToPaste = if (asPlainText) clip.getItemAt(0).coerceToText(context).toString()
1910+
else clip.getItemAt(0).coerceToHtmlText(AztecParser(alignmentRendering, plugins))
1911+
PastedItem.HtmlText(textToPaste)
1912+
}
1913+
firstItem.uri != null -> {
1914+
PastedItem.Url(firstItem.uri)
1915+
}
1916+
firstItem.intent != null -> {
1917+
PastedItem.PastedIntent(firstItem.intent)
1918+
}
1919+
else -> {
1920+
null
19121921
}
19131922
}
1914-
val newHtml = oldHtml.replace(Constants.REPLACEMENT_MARKER_STRING, pastedHtmlText + "<" + AztecCursorSpan.AZTEC_CURSOR_TAG + ">")
1923+
if (itemToPaste != null) {
1924+
val oldHtml = toPlainHtml().replace("<aztec_cursor>", "")
1925+
val pastedHtmlText: String = plugins.filterIsInstance<IClipboardPastePlugin<*>>()
1926+
.fold(null as? String?) { acc, plugin ->
1927+
plugin.itemToHtml(itemToPaste, acc ?: selectedText?.takeIf { it.isNotBlank() }) ?: acc
1928+
} ?: when (itemToPaste) {
1929+
is PastedItem.HtmlText -> itemToPaste.text
1930+
is PastedItem.Url -> itemToPaste.uri.path
1931+
is PastedItem.PastedIntent -> itemToPaste.intent.toString()
1932+
}
19151933

1916-
fromHtml(newHtml, false)
1917-
inlineFormatter.joinStyleSpans(0, length())
1934+
val newHtml = oldHtml.replace(
1935+
Constants.REPLACEMENT_MARKER_STRING,
1936+
pastedHtmlText + "<" + AztecCursorSpan.AZTEC_CURSOR_TAG + ">"
1937+
)
1938+
1939+
fromHtml(newHtml, false)
1940+
inlineFormatter.joinStyleSpans(0, length())
1941+
}
19181942
}
19191943
contentChangeWatcher.notifyContentChanged()
19201944
}
@@ -1964,7 +1988,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
19641988
}
19651989

19661990
@SuppressLint("InflateParams")
1967-
fun showLinkDialog(presetUrl: String = "", presetAnchor: String = "", presetOpenInNewWindow: String = "" ) {
1991+
fun showLinkDialog(presetUrl: String = "", presetAnchor: String = "", presetOpenInNewWindow: String = "") {
19681992
val urlAndAnchor = linkFormatter.getSelectedUrlWithAnchor()
19691993

19701994
val url = if (TextUtils.isEmpty(presetUrl)) urlAndAnchor.first else presetUrl
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.wordpress.aztec.plugins
2+
3+
import android.content.Intent
4+
import android.net.Uri
5+
6+
/**
7+
* Use this plugin in order to override the default item paste behaviour. An example is overriding the paste so that
8+
* you can handle pasted image URLs as images over the selected text.
9+
*/
10+
interface IClipboardPastePlugin<T : IClipboardPastePlugin.PastedItem> : IAztecPlugin {
11+
fun toHtml(pastedItem: T, selectedText: String? = null): String
12+
13+
/**
14+
* This method is called when text is pasted into the editor. If text is selected, the default behaviour
15+
* is to replace the selected text with the pasted item but it can be changed by overriding this method.
16+
* If text is not selected, this returned object of this method is inserted into the text.
17+
* This method should return HTML (plain text is OK if you don't apply any changes to the pasted text).
18+
* @param pastedItem clipboard item pasted over selected text
19+
* @param selectedText currently selected text
20+
* @return html of the result
21+
*/
22+
fun itemToHtml(pastedItem: PastedItem, selectedText: String? = null): String? {
23+
return when {
24+
pastedItem is PastedItem.HtmlText && this is ITextPastePlugin -> this.toHtml(pastedItem, selectedText)
25+
pastedItem is PastedItem.Url && this is IUriPastePlugin -> this.toHtml(pastedItem, selectedText)
26+
pastedItem is PastedItem.PastedIntent && this is IIntentPastePlugin -> this.toHtml(pastedItem, selectedText)
27+
else -> null
28+
}
29+
}
30+
31+
interface ITextPastePlugin : IClipboardPastePlugin<PastedItem.HtmlText> {
32+
/**
33+
* Override this method if you only need to handle the pasted text and not other types. If returned value is
34+
* null, it will be ignored and the default behaviour will take over.
35+
* @param pastedItem pasted text
36+
* @return value of the pasted HTML
37+
*/
38+
override fun toHtml(pastedItem: PastedItem.HtmlText, selectedText: String?): String
39+
}
40+
41+
interface IUriPastePlugin : IClipboardPastePlugin<PastedItem.Url> {
42+
/**
43+
* Override this method to handle pasted URIs. If returned value is null, it will be ignored and the default
44+
* behaviour will take over.
45+
* @param pastedItem pasted URI
46+
* @return HTML representation of an URI
47+
*/
48+
override fun toHtml(pastedItem: PastedItem.Url, selectedText: String?): String
49+
}
50+
51+
interface IIntentPastePlugin : IClipboardPastePlugin<PastedItem.PastedIntent> {
52+
/**
53+
* Override this method to handle pasted intents. If returned value is null, it will be ignored and the default
54+
* behaviour will take over.
55+
* @param pastedItem Pasted intent
56+
* @return HTML representation of an intent
57+
*/
58+
override fun toHtml(pastedItem: PastedItem.PastedIntent, selectedText: String?): String
59+
}
60+
61+
/**
62+
* Pasted items supported by Clipboard
63+
*/
64+
sealed class PastedItem {
65+
data class HtmlText(val text: String) : PastedItem()
66+
data class Url(val uri: Uri) : PastedItem()
67+
data class PastedIntent(val intent: Intent) : PastedItem()
68+
}
69+
}
70+

aztec/src/main/kotlin/org/wordpress/aztec/plugins/ITextPastePlugin.kt

Lines changed: 0 additions & 31 deletions
This file was deleted.

aztec/src/main/kotlin/org/wordpress/aztec/plugins/UrlPastePlugin.kt

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,19 @@ import android.util.Patterns
66
* This plugin overrides the paste logic of URLs in the AztecText. The purpose is to make sure inserted links are
77
* treated as HTML links.
88
*/
9-
class UrlPastePlugin : ITextPastePlugin {
9+
class UrlPastePlugin : IClipboardPastePlugin.ITextPastePlugin {
1010
/**
1111
* If the pasted text is a link, make sure it's wrapped with the `a` tag so that it's rendered as a link.
1212
*/
13-
override fun toHtml(pastedText: String): String {
14-
return if (Patterns.WEB_URL.matcher(pastedText).matches()) {
15-
"<a href=\"$pastedText\">$pastedText</a>"
13+
override fun toHtml(pastedItem: IClipboardPastePlugin.PastedItem.HtmlText, selectedText: String?): String {
14+
return if (Patterns.WEB_URL.matcher(pastedItem.text).matches()) {
15+
if (selectedText != null) {
16+
"<a href=\"${pastedItem.text}\">$selectedText</a>"
17+
} else {
18+
"<a href=\"${pastedItem.text}\">${pastedItem.text}</a>"
19+
}
1620
} else {
17-
pastedText
18-
}
19-
}
20-
21-
/**
22-
* If the pasted text is a link, make sure the selected text is wrapped with `a` tag and not removed.
23-
*/
24-
override fun toHtml(selectedText: String, pastedText: String): String {
25-
return if (Patterns.WEB_URL.matcher(pastedText).matches()) {
26-
"<a href=\"$pastedText\">$selectedText</a>"
27-
} else {
28-
pastedText
21+
pastedItem.text
2922
}
3023
}
3124
}

0 commit comments

Comments
 (0)