@@ -25,6 +25,7 @@ import org.wordpress.aztec.AztecContentChangeWatcher
2525import org.wordpress.aztec.AztecText
2626import org.wordpress.aztec.Constants
2727import org.wordpress.aztec.Html
28+ import org.wordpress.aztec.plugins.html2visual.IHtmlPreprocessor
2829import org.wordpress.aztec.plugins.html2visual.IHtmlTagHandler
2930import org.wordpress.aztec.spans.AztecMediaClickableSpan
3031import org.xml.sax.Attributes
@@ -52,7 +53,8 @@ class PlaceholderManager(
5253 Html .MediaCallback ,
5354 AztecText .OnMediaDeletedListener ,
5455 AztecText .OnVisibilityChangeListener ,
55- CoroutineScope {
56+ CoroutineScope ,
57+ IHtmlPreprocessor {
5658 private val adapters = mutableMapOf<String , PlaceholderAdapter >()
5759 private val positionToIdMutex = Mutex ()
5860 private val positionToId = mutableSetOf<Placeholder >()
@@ -109,49 +111,42 @@ class PlaceholderManager(
109111 * @param shouldMergeItem this method should return true when the previous type is compatible and should be updated
110112 * @param updateItem function to update current parameters with new params
111113 */
112- suspend fun insertOrUpdateItem (type : String , shouldMergeItem : (currentItemType: String ) -> Boolean = { true }, updateItem : (currentAttributes: Map <String , String >? , currentType: String? ) -> Map <String , String >) {
113- val previousIndex = (aztecText.selectionStart - 1 ).coerceAtLeast(0 )
114- val indexBeforePrevious = (aztecText.selectionStart - 2 ).coerceAtLeast(0 )
115- val from = if (aztecText.editableText.length > previousIndex && aztecText.editableText[previousIndex] == Constants .IMG_CHAR ) {
116- previousIndex
117- } else if (aztecText.editableText.length > previousIndex && aztecText.editableText[previousIndex] == ' \n ' ) {
118- indexBeforePrevious
119- } else {
120- aztecText.selectionStart
121- }
122- val editableText = aztecText.editableText
123- val currentItem = editableText.getSpans(
124- from,
125- aztecText.selectionStart,
126- AztecPlaceholderSpan ::class .java
127- ).lastOrNull()
128- val currentType = currentItem?.attributes?.getValue(TYPE_ATTRIBUTE )
114+ suspend fun insertOrUpdateItem (
115+ type : String ,
116+ shouldMergeItem : (currentItemType: String ) -> Boolean = { true },
117+ updateItem : (
118+ currentAttributes: Map <String , String >? ,
119+ currentType: String? ,
120+ placeAtStart: Boolean
121+ ) -> Map <String , String >
122+ ) {
123+ val targetItem = getTargetItem()
124+ val targetSpan = targetItem?.span
125+ val currentType = targetSpan?.attributes?.getValue(TYPE_ATTRIBUTE )
129126 if (currentType != null && shouldMergeItem(currentType)) {
130- updateSpan(type, currentItem , updateItem, currentType)
127+ updateSpan(type, targetItem.span, targetItem.placeAtStart , updateItem, currentType)
131128 } else {
132- insertItem(type, * updateItem(null , null ).toList().toTypedArray())
129+ insertItem(type, * updateItem(null , null , false ).toList().toTypedArray())
133130 }
134131 }
135132
136133 private suspend fun updateSpan (
137134 type : String ,
138- currentItem : AztecPlaceholderSpan ,
139- updateItem : (currentAttributes: Map <String , String >, currentType: String ) -> Map <String , String >,
135+ targetSpan : AztecPlaceholderSpan ,
136+ placeAtStart : Boolean ,
137+ updateItem : (currentAttributes: Map <String , String >, currentType: String , placeAtStart: Boolean ) -> Map <String , String >,
140138 currentType : String
141139 ) {
142140 val adapter = adapters[type]
143141 ? : throw IllegalArgumentException (" Adapter for inserted type not found. Register it with `registerAdapter` method" )
144142 val currentAttributes = mutableMapOf<String , String >()
145- val uuid = currentItem .attributes.getValue(UUID_ATTRIBUTE )
146- for (i in 0 until currentItem .attributes.length) {
147- val name = currentItem .attributes.getQName(i)
148- val value = currentItem .attributes.getValue(name)
143+ val uuid = targetSpan .attributes.getValue(UUID_ATTRIBUTE )
144+ for (i in 0 until targetSpan .attributes.length) {
145+ val name = targetSpan .attributes.getQName(i)
146+ val value = targetSpan .attributes.getValue(name)
149147 currentAttributes[name] = value
150148 }
151- val updatedAttributes = updateItem(currentAttributes, currentType)
152- removeItem(false ) { aztecAttributes ->
153- aztecAttributes.getValue(UUID_ATTRIBUTE ) == uuid
154- }
149+ val updatedAttributes = updateItem(currentAttributes, currentType, placeAtStart)
155150 val attrs = AztecAttributes ().apply {
156151 updatedAttributes.forEach { (key, value) ->
157152 setValue(key, value)
@@ -160,8 +155,11 @@ class PlaceholderManager(
160155 attrs.setValue(UUID_ATTRIBUTE , uuid)
161156 attrs.setValue(TYPE_ATTRIBUTE , type)
162157 val drawable = buildPlaceholderDrawable(adapter, attrs)
163- aztecText.insertMediaSpan(AztecPlaceholderSpan (aztecText.context, drawable, 0 , attrs,
164- this , aztecText, WeakReference (adapter), TAG = htmlTag))
158+ val span = AztecPlaceholderSpan (aztecText.context, drawable, 0 , attrs,
159+ this , aztecText, WeakReference (adapter), TAG = htmlTag)
160+ aztecText.replaceMediaSpan(span) { attributes ->
161+ attributes.getValue(UUID_ATTRIBUTE ) == uuid
162+ }
165163 insertContentOverSpanWithId(uuid)
166164 }
167165
@@ -184,22 +182,57 @@ class PlaceholderManager(
184182 val selectionStart = aztecText.selectionStart
185183 val selectionEnd = aztecText.selectionEnd
186184 aztecText.setSelection(aztecText.editableText.getSpanStart(currentItem))
187- updateSpan(type, currentItem, updateItem = { attributes, _ ->
185+ updateSpan(type, currentItem, updateItem = { attributes, _, _ ->
188186 updateItem(attributes)
189- }, type)
187+ }, placeAtStart = false , currentType = type)
190188 aztecText.setSelection(selectionStart, selectionEnd)
191189 } else {
192190 removeItem(uuid)
193191 }
194192 return true
195193 }
196194
195+ private data class TargetItem (val span : AztecPlaceholderSpan , val placeAtStart : Boolean )
196+
197+ private fun getTargetItem (): TargetItem ? {
198+ if (aztecText.length() == 0 ) {
199+ return null
200+ }
201+ val limitLength = aztecText.length() - 1
202+ val selectionStart = aztecText.selectionStart
203+ val selectionStartMinusOne = (selectionStart - 1 ).coerceIn(0 , limitLength)
204+ val selectionStartMinusTwo = (selectionStart - 2 ).coerceIn(0 , limitLength)
205+ val selectionEnd = aztecText.selectionEnd
206+ val selectionEndPlusOne = (selectionStart + 1 ).coerceIn(0 , limitLength)
207+ val selectionEndPlusTwo = (selectionStart + 2 ).coerceIn(0 , limitLength)
208+ val editableText = aztecText.editableText
209+ var placeAtStart = false
210+ val (from, to) = if (editableText[selectionStartMinusOne] == Constants .IMG_CHAR ) {
211+ selectionStartMinusOne to selectionStart
212+ } else if (editableText[selectionStartMinusOne] == ' \n ' && editableText[selectionStartMinusTwo] == Constants .IMG_CHAR ) {
213+ selectionStartMinusTwo to selectionStart
214+ } else if (editableText[selectionEndPlusOne] == Constants .IMG_CHAR ) {
215+ placeAtStart = true
216+ selectionEndPlusOne to (selectionEndPlusOne + 1 ).coerceIn(0 , limitLength)
217+ } else if (editableText[selectionEndPlusOne] == ' \n ' && editableText[selectionEndPlusTwo] == Constants .IMG_CHAR ) {
218+ placeAtStart = true
219+ selectionEndPlusTwo to (selectionEndPlusTwo + 1 ).coerceIn(0 , limitLength)
220+ } else {
221+ selectionStart to selectionEnd
222+ }
223+ return editableText.getSpans(
224+ from,
225+ to,
226+ AztecPlaceholderSpan ::class .java
227+ ).map { TargetItem (it, placeAtStart) }.lastOrNull()
228+ }
229+
197230 /* *
198231 * Call this method to remove a placeholder from both the AztecText and the overlaying layer programmatically.
199232 * @param predicate determines whether a span should be removed
200233 */
201- fun removeItem (notifyContentChange : Boolean = true, predicate : (Attributes ) -> Boolean ) {
202- aztecText.removeMedia(notifyContentChange) { predicate(it) }
234+ fun removeItem (predicate : (Attributes ) -> Boolean ) {
235+ aztecText.removeMedia { predicate(it) }
203236 }
204237
205238 /* *
@@ -388,6 +421,12 @@ class PlaceholderManager(
388421 override fun handleTag (opening : Boolean , tag : String , output : Editable , attributes : Attributes , nestingLevel : Int ): Boolean {
389422 if (opening) {
390423 val type = attributes.getValue(TYPE_ATTRIBUTE )
424+ attributes.getValue(UUID_ATTRIBUTE )?.also { uuid ->
425+ container.findViewWithTag<View >(uuid)?.let {
426+ it.visibility = View .GONE
427+ container.removeView(it)
428+ }
429+ }
391430 val adapter = adapters[type] ? : return false
392431 val aztecAttributes = AztecAttributes (attributes)
393432 aztecAttributes.setValue(UUID_ATTRIBUTE , generateUuid())
@@ -595,4 +634,11 @@ class PlaceholderManager(
595634 private const val TYPE_ATTRIBUTE = " type"
596635 private const val EDITOR_INNER_PADDING = 20
597636 }
637+
638+ override fun beforeHtmlProcessed (source : String ): String {
639+ runBlocking {
640+ clearAllViews()
641+ }
642+ return source
643+ }
598644}
0 commit comments