Skip to content

Commit e9c357d

Browse files
authored
Merge pull request #970 from wordpress-mobile/feature/enable-exclusive-blocks
Enable exclusive blocks in AztecText.kt
2 parents f3fc2a6 + 7b1074f commit e9c357d

File tree

15 files changed

+132
-17
lines changed

15 files changed

+132
-17
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
434434
getPreformatBackgroundAlpha(styles),
435435
styles.getColor(R.styleable.AztecText_preformatColor, 0),
436436
verticalParagraphMargin),
437-
alignmentRendering
437+
alignmentRendering,
438+
BlockFormatter.ExclusiveBlockStyles(styles.getBoolean(R.styleable.AztecText_exclusiveBlocks, false))
438439
)
439440

440441
linkFormatter = LinkFormatter(this, LinkFormatter.LinkStyle(styles.getColor(

aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ class BlockFormatter(editor: AztecText,
4646
private val quoteStyle: QuoteStyle,
4747
private val headerStyle: HeaderStyle,
4848
private val preformatStyle: PreformatStyle,
49-
private val alignmentRendering: AlignmentRendering
49+
private val alignmentRendering: AlignmentRendering,
50+
private val exclusiveBlockStyles: ExclusiveBlockStyles
5051
) : AztecFormatter(editor) {
5152
data class ListStyle(val indicatorColor: Int, val indicatorMargin: Int, val indicatorPadding: Int, val indicatorWidth: Int, val verticalPadding: Int)
5253
data class QuoteStyle(val quoteBackground: Int, val quoteColor: Int, val quoteBackgroundAlpha: Float, val quoteMargin: Int, val quotePadding: Int, val quoteWidth: Int, val verticalPadding: Int)
5354
data class PreformatStyle(val preformatBackground: Int, val preformatBackgroundAlpha: Float, val preformatColor: Int, val verticalPadding: Int)
5455
data class HeaderStyle(val verticalPadding: Int)
56+
data class ExclusiveBlockStyles(val enabled: Boolean = false)
5557

5658
fun toggleOrderedList() {
5759
if (!containsList(AztecTextFormat.FORMAT_ORDERED_LIST, 0)) {
@@ -114,7 +116,7 @@ class BlockFormatter(editor: AztecText,
114116

115117
fun togglePreformat() {
116118
if (!containsPreformat()) {
117-
if (containsOtherHeadings(AztecTextFormat.FORMAT_PREFORMAT)) {
119+
if (containsOtherHeadings(AztecTextFormat.FORMAT_PREFORMAT) && !exclusiveBlockStyles.enabled) {
118120
switchHeadingToPreformat()
119121
} else {
120122
applyBlockStyle(AztecTextFormat.FORMAT_PREFORMAT)
@@ -133,7 +135,7 @@ class BlockFormatter(editor: AztecText,
133135
AztecTextFormat.FORMAT_HEADING_5,
134136
AztecTextFormat.FORMAT_HEADING_6 -> {
135137
if (!containsHeadingOnly(textFormat)) {
136-
if (containsPreformat()) {
138+
if (containsPreformat() && !exclusiveBlockStyles.enabled) {
137139
switchPreformatToHeading(textFormat)
138140
} else if (containsOtherHeadings(textFormat)) {
139141
switchHeaderType(textFormat)
@@ -212,6 +214,75 @@ class BlockFormatter(editor: AztecText,
212214
return changed
213215
}
214216

217+
/**
218+
* This method makes sure only one block style is ever applied to part of the text. The following block styles are
219+
* made exclusive if the option is enabled:
220+
* - all the lists
221+
* - all the headings
222+
* - quote
223+
* - preformat
224+
*/
225+
private fun removeBlockStylesFromSelectedLine(appliedClass: IAztecBlockSpan) {
226+
// We only want to remove the previous block styles if this option is enabled
227+
if (!exclusiveBlockStyles.enabled) {
228+
return
229+
}
230+
val selectionStart = editor.selectionStart
231+
232+
// try to remove block styling when pressing backspace at the beginning of the text
233+
editableText.getSpans(selectionStart, selectionEnd, IAztecBlockSpan::class.java).forEach {
234+
// We want to remove any list item span that's being converted to another block
235+
if (it is AztecListItemSpan) {
236+
editableText.removeSpan(it)
237+
return@forEach
238+
}
239+
// We don't mind the paragraph blocks which wrap everything
240+
if (it is ParagraphSpan) {
241+
return@forEach
242+
}
243+
// Only these supported blocks will be split/removed on block change
244+
val format = it.textFormat ?: return@forEach
245+
// We do not want to handle cases where the applied style is already existing on a span
246+
if (it.javaClass == appliedClass.javaClass) {
247+
return@forEach
248+
}
249+
val spanStart = editableText.getSpanStart(it)
250+
val spanEnd = editableText.getSpanEnd(it)
251+
val spanFlags = editableText.getSpanFlags(it)
252+
val nextLineLength = "\n".length
253+
// Defines end of a line in a block
254+
val previousLineBreak = editableText.indexOf("\n", selectionEnd)
255+
val lineEnd = if (previousLineBreak > -1) { previousLineBreak + nextLineLength } else spanEnd
256+
// Defines start of a line in a block
257+
val nextLineBreak = if (lineEnd == selectionStart + nextLineLength) {
258+
editableText.lastIndexOf("\n", selectionStart - 1)
259+
} else {
260+
editableText.lastIndexOf("\n", selectionStart)
261+
}
262+
val lineStart = if (nextLineBreak > -1) { nextLineBreak + nextLineLength } else spanStart
263+
val spanStartsBeforeLineStart = spanStart < lineStart
264+
val spanEndsAfterLineEnd = spanEnd > lineEnd
265+
if (spanStartsBeforeLineStart && spanEndsAfterLineEnd) {
266+
// The line is fully inside of the span so we want to split span in two around the selected line
267+
val copy = makeBlock(format, it.nestingLevel, it.attributes).first()
268+
editableText.removeSpan(it)
269+
editableText.setSpan(it, spanStart, lineStart, spanFlags)
270+
editableText.setSpan(copy, lineEnd, spanEnd, spanFlags)
271+
} else if (!spanStartsBeforeLineStart && spanEndsAfterLineEnd) {
272+
// If the selected line is at the beginning of a span, move the span start to the end of the line
273+
editableText.removeSpan(it)
274+
editableText.setSpan(it, lineEnd, spanEnd, spanFlags)
275+
} else if (spanStartsBeforeLineStart && !spanEndsAfterLineEnd) {
276+
// If the selected line is at the end of a span, move the span end to the start of the line
277+
editableText.removeSpan(it)
278+
editableText.setSpan(it, spanStart, lineStart, spanFlags)
279+
} else {
280+
// In this case the line fully covers the span so we just want to remove the span
281+
editableText.removeSpan(it)
282+
}
283+
}
284+
}
285+
215286
fun removeBlockStyle(textFormat: ITextFormat) {
216287
removeBlockStyle(textFormat, selectionStart, selectionEnd, makeBlock(textFormat, 0).map { it -> it.javaClass })
217288
}
@@ -568,6 +639,7 @@ class BlockFormatter(editor: AztecText,
568639
val boundsOfSelectedText = getBoundsOfText(editableText, start, end)
569640
val nestingLevel = IAztecNestable.getNestingLevelAt(editableText, start) + 1
570641
val spanToApply = makeBlockSpan(blockElementType, nestingLevel)
642+
removeBlockStylesFromSelectedLine(spanToApply)
571643

572644
if (start != end) {
573645
// we want to push line blocks as deep as possible, because they can't contain other block elements (e.g. headings)

aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ open class AztecHeadingSpan(
7171
override var endBeforeBleed: Int = -1
7272
override var startBeforeCollapse: Int = -1
7373

74-
var textFormat: ITextFormat = AztecTextFormat.FORMAT_HEADING_1
74+
override var textFormat: ITextFormat = AztecTextFormat.FORMAT_HEADING_1
7575
set(value) {
7676
field = value
7777
heading = textFormatToHeading(value)

aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecListItemSpan.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.wordpress.aztec.spans
33
import android.text.Layout
44
import org.wordpress.aztec.AlignmentRendering
55
import org.wordpress.aztec.AztecAttributes
6+
import org.wordpress.aztec.ITextFormat
67
import java.lang.StringBuilder
78

89
fun createListItemSpan(nestingLevel: Int,
@@ -67,6 +68,7 @@ open class AztecListItemSpan(
6768
}
6869

6970
}
71+
override val textFormat: ITextFormat? = null
7072

7173
override val TAG = "li"
7274

aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecOrderedListSpan.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import android.text.Layout
2323
import android.text.Spanned
2424
import org.wordpress.aztec.AlignmentRendering
2525
import org.wordpress.aztec.AztecAttributes
26+
import org.wordpress.aztec.AztecTextFormat
27+
import org.wordpress.aztec.ITextFormat
2628
import org.wordpress.aztec.formatting.BlockFormatter
2729

2830
fun createOrderedListSpan(
@@ -125,4 +127,6 @@ open class AztecOrderedListSpan(
125127
p.color = oldColor
126128
p.style = style
127129
}
130+
131+
override val textFormat: ITextFormat = AztecTextFormat.FORMAT_ORDERED_LIST
128132
}

aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecPreformatSpan.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ import android.text.style.LineHeightSpan
1212
import android.text.style.TypefaceSpan
1313
import org.wordpress.aztec.AlignmentRendering
1414
import org.wordpress.aztec.AztecAttributes
15+
import org.wordpress.aztec.AztecTextFormat
16+
import org.wordpress.aztec.ITextFormat
1517
import org.wordpress.aztec.formatting.BlockFormatter
1618

1719
fun createPreformatSpan(
1820
nestingLevel: Int,
1921
alignmentRendering: AlignmentRendering,
2022
attributes: AztecAttributes = AztecAttributes(),
2123
preformatStyle: BlockFormatter.PreformatStyle = BlockFormatter.PreformatStyle(0, 0f, 0, 0)
22-
) : AztecPreformatSpan =
24+
): AztecPreformatSpan =
2325
when (alignmentRendering) {
2426
AlignmentRendering.SPAN_LEVEL -> AztecPreformatSpanAligned(nestingLevel, attributes, preformatStyle)
2527
AlignmentRendering.VIEW_LEVEL -> AztecPreformatSpan(nestingLevel, attributes, preformatStyle)
@@ -44,12 +46,11 @@ open class AztecPreformatSpan(
4446
override var nestingLevel: Int,
4547
override var attributes: AztecAttributes,
4648
open var preformatStyle: BlockFormatter.PreformatStyle
47-
) : IAztecBlockSpan,
49+
) : IAztecBlockSpan,
4850
LeadingMarginSpan,
4951
LineBackgroundSpan,
5052
LineHeightSpan,
51-
TypefaceSpan("monospace")
52-
{
53+
TypefaceSpan("monospace") {
5354
override val TAG: String = "pre"
5455

5556
override var endBeforeBleed: Int = -1
@@ -105,4 +106,6 @@ open class AztecPreformatSpan(
105106
override fun getLeadingMargin(first: Boolean): Int {
106107
return MARGIN
107108
}
109+
110+
override val textFormat: ITextFormat = AztecTextFormat.FORMAT_PREFORMAT
108111
}

aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecQuoteSpan.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import android.text.style.UpdateLayout
3535
import androidx.collection.ArrayMap
3636
import org.wordpress.aztec.AlignmentRendering
3737
import org.wordpress.aztec.AztecAttributes
38+
import org.wordpress.aztec.AztecTextFormat
39+
import org.wordpress.aztec.ITextFormat
3840
import org.wordpress.aztec.formatting.BlockFormatter
3941
import java.util.Locale
4042

@@ -67,12 +69,11 @@ open class AztecQuoteSpan(
6769
override var nestingLevel: Int,
6870
override var attributes: AztecAttributes,
6971
var quoteStyle: BlockFormatter.QuoteStyle
70-
) : QuoteSpan(),
72+
) : QuoteSpan(),
7173
LineBackgroundSpan,
7274
IAztecBlockSpan,
7375
LineHeightSpan,
74-
UpdateLayout
75-
{
76+
UpdateLayout {
7677

7778
override var endBeforeBleed: Int = -1
7879
override var startBeforeCollapse: Int = -1
@@ -197,4 +198,5 @@ open class AztecQuoteSpan(
197198
return textDirectionHeuristic.isRtl(text, start, end - start)
198199
}
199200

201+
override val textFormat: ITextFormat = AztecTextFormat.FORMAT_QUOTE
200202
}

aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import android.text.Layout
2525
import android.text.Spanned
2626
import org.wordpress.aztec.AlignmentRendering
2727
import org.wordpress.aztec.AztecAttributes
28+
import org.wordpress.aztec.AztecTextFormat
29+
import org.wordpress.aztec.ITextFormat
2830
import org.wordpress.aztec.R
2931
import org.wordpress.aztec.formatting.BlockFormatter
3032
import org.wordpress.aztec.setTaskList
@@ -129,6 +131,11 @@ open class AztecTaskListSpan(
129131
private fun isChecked(text: CharSequence, lineIndex: Int): Boolean {
130132
val spanStart = (text as Spanned).getSpanStart(this)
131133
val spanEnd = text.getSpanEnd(this)
132-
return text.getSpans(spanStart, spanEnd, AztecListItemSpan::class.java).getOrNull(lineIndex - 1)?.attributes?.getValue("checked") == "true"
134+
val sortedSpans = text.getSpans(spanStart, spanEnd, AztecListItemSpan::class.java).sortedBy {
135+
text.getSpanStart(it)
136+
}
137+
return sortedSpans.getOrNull(lineIndex - 1)?.attributes?.getValue("checked") == "true"
133138
}
139+
140+
override val textFormat: ITextFormat = AztecTextFormat.FORMAT_TASK_LIST
134141
}

aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecUnorderedListSpan.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import android.text.Layout
2323
import android.text.Spanned
2424
import org.wordpress.aztec.AlignmentRendering
2525
import org.wordpress.aztec.AztecAttributes
26+
import org.wordpress.aztec.AztecTextFormat
27+
import org.wordpress.aztec.ITextFormat
2628
import org.wordpress.aztec.formatting.BlockFormatter
2729

2830
fun createUnorderedListSpan(
@@ -93,4 +95,6 @@ open class AztecUnorderedListSpan(
9395
p.color = oldColor
9496
p.style = style
9597
}
98+
99+
override val textFormat: ITextFormat = AztecTextFormat.FORMAT_UNORDERED_LIST
96100
}

aztec/src/main/kotlin/org/wordpress/aztec/spans/HiddenHtmlBlockSpan.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.wordpress.aztec.spans
33
import android.text.Layout
44
import org.wordpress.aztec.AlignmentRendering
55
import org.wordpress.aztec.AztecAttributes
6+
import org.wordpress.aztec.ITextFormat
67

78
fun createHiddenHtmlBlockSpan(tag: String,
89
alignmentRendering: AlignmentRendering,
@@ -34,4 +35,5 @@ open class HiddenHtmlBlockSpan(tag: String,
3435
override var endBeforeBleed: Int = -1
3536
override var startBeforeCollapse: Int = -1
3637
override val TAG: String = tag
38+
override val textFormat: ITextFormat? = null
3739
}

0 commit comments

Comments
 (0)