Skip to content

Commit 41a0bc2

Browse files
authored
Merge pull request #975 from wordpress-mobile/release/1.5.6
Release v1.5.6
2 parents 2e5fd65 + 39bf99b commit 41a0bc2

File tree

5 files changed

+237
-16
lines changed

5 files changed

+237
-16
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,39 @@ dependencies {
119119
}
120120
```
121121

122+
## Modifications
123+
124+
You can use the API to modify Aztec behaviour.
125+
126+
### Toolbar items
127+
128+
If you want to limit the functionality the Aztec library provides, you can change it calling the `setToolbarItems` method on `AztecToolbar`.
129+
The following example will enable only `bold`, `plugins` and `list` items in the given order.
130+
131+
```kotlin
132+
aztecToolbar.setToolbarItems(ToolbarItems.BasicLayout(ToolbarAction.BOLD, ToolbarItems.PLUGINS, ToolbarAction.LIST))
133+
```
134+
135+
You can set new items which are not enabled by default. `ToolbarAction.CODE` and `ToolbarAction.PRE`.
136+
- `CODE` represents inline HTML code
137+
- `PRE` represents a preformat block (including code block)
138+
139+
### Task list
140+
141+
There is an optional list type you can enable in the editor. In addition to ordered and unordered lists you can use `task list`.
142+
A task list is an unordered list which shows and saves checkboxes instead of the bullets. Enable it by calling the following method.
143+
```kotlin
144+
aztecToolbar.enableTaskList()
145+
```
146+
147+
### Nested blocks
148+
149+
By default Aztec allows nested blocks. In certain cases this doesn't have to be the preferred behaviour. There is an option to disable nested blocks.
150+
When switched, this editor will always add media and horizontal rule after the currently selected block, not in the middle of it.
151+
```kotlin
152+
aztecText.addMediaAfterBlocks()
153+
```
154+
122155
## Code formatting
123156

124157
We use [ktlint](https://github.com/shyiko/ktlint) for Kotlin linting. You can run ktlint using `./gradlew ktlint`, and you can also run `./gradlew ktlintFormat` for auto-formatting. There is no IDEA plugin (like Checkstyle's) at this time.

app/src/main/kotlin/org/wordpress/aztec/demo/MainActivity.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,6 @@ open class MainActivity : AppCompatActivity(),
429429
}
430430
})
431431

432-
toolbar.enableTaskList()
433-
434432
aztec = Aztec.with(visualEditor, sourceEditor, toolbar, this)
435433
.setImageGetter(GlideImageLoader(this))
436434
.setVideoThumbnailGetter(GlideVideoThumbnailLoader(this))

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

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
9292

9393
return false
9494
}
95+
9596
fun applyHorizontalRule(inline: Boolean) {
9697
val nestingLevel = if (inline) {
9798
editor.removeInlineStylesFromRange(selectionStart, selectionEnd)
@@ -117,7 +118,6 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
117118
val newSelectionPosition = editableText.indexOf(Constants.MAGIC_CHAR, selectionStart) + 1
118119
editor.setSelection(newSelectionPosition)
119120
} else {
120-
builder.append("\n")
121121
insertSpanAfterBlock(builder)
122122
}
123123
}
@@ -163,7 +163,6 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
163163

164164
private fun insertMediaAfterBlock(span: AztecMediaSpan) {
165165
val ssb = SpannableStringBuilder(Constants.IMG_STRING)
166-
ssb.append("\n")
167166
buildClickableMediaSpan(ssb, span)
168167
insertSpanAfterBlock(ssb)
169168
}
@@ -173,23 +172,38 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
173172
// We need to be sure the cursor is placed correctly after media insertion
174173
// Note that media has '\n' around them when needed
175174
val isLastItem = position == EndOfBufferMarkerAdder.safeLength(editor)
176-
val insertedLength = ssb.length
177-
editableText.insert(position, ssb)
178-
val spans = editableText.getSpans(position, position + insertedLength, IAztecBlockSpan::class.java).filter {
179-
it !is AztecMediaSpan && editableText.getSpanStart(it) == position
180-
}
181-
spans.forEach {
182-
val spanStart = editableText.getSpanStart(it)
183-
val spanEnd = editableText.getSpanEnd(it)
184-
val spanFlags = editableText.getSpanFlags(it)
185-
editableText.removeSpan(it)
186-
if (spanStart + insertedLength < spanEnd) {
187-
editableText.setSpan(it, spanStart + insertedLength, spanEnd, spanFlags)
175+
if (isLastItem) {
176+
editableText.getSpans(position, editableText.length, IAztecBlockSpan::class.java).filter {
177+
it !is AztecMediaSpan && editableText.getSpanEnd(it) == editableText.length
178+
}.map {
179+
SpanData(it, editableText.getSpanStart(it), position + 1, editableText.getSpanFlags(it))
180+
}.applyWithRemovedSpans {
181+
editableText.append(ssb)
182+
}
183+
} else {
184+
ssb.append("\n")
185+
val ssbLength = ssb.length
186+
editableText.getSpans(position, position + ssbLength, IAztecBlockSpan::class.java).filter {
187+
it !is AztecMediaSpan && editableText.getSpanStart(it) == position
188+
}.map {
189+
SpanData(it, editableText.getSpanStart(it) + ssbLength, editableText.getSpanEnd(it) + ssbLength, editableText.getSpanFlags(it))
190+
}.applyWithRemovedSpans {
191+
editableText.insert(position, ssb)
188192
}
189193
}
190194
setSelection(isLastItem, position)
191195
}
192196

197+
private fun List<SpanData>.applyWithRemovedSpans(action: () -> Unit) {
198+
this.onEach { editableText.removeSpan(it.span) }
199+
action()
200+
this.onEach {
201+
editableText.setSpan(it.span, it.spanStart, it.spanEnd, it.spanFlags)
202+
}
203+
}
204+
205+
data class SpanData(val span: IAztecBlockSpan, val spanStart: Int, val spanEnd: Int, val spanFlags: Int)
206+
193207
private fun setSelection(isLastItem: Boolean, position: Int) {
194208
val newSelection = if (isLastItem) {
195209
EndOfBufferMarkerAdder.safeLength(editor)
@@ -217,13 +231,20 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
217231
}
218232

219233
private fun getEndOfBlock(): Int {
234+
if (selectionStart == 0 && selectionEnd == 0) {
235+
return 0
236+
}
220237
var position = 0
221238
editableText.getSpans(selectionStart, selectionEnd, IAztecBlockSpan::class.java).forEach {
222239
val spanEnd = editableText.getSpanEnd(it)
223240
if (spanEnd > position) {
224241
position = spanEnd
225242
}
226243
}
244+
if (position <= 0 && selectionEnd != 0) {
245+
// If the text contains "\n" return that as the position, else set the position to the end of the text
246+
position = editableText.indexOf("\n", selectionEnd).takeIf { it >= 0 } ?: editableText.length
247+
}
227248
return position
228249
}
229250
}

aztec/src/main/kotlin/org/wordpress/aztec/toolbar/AztecToolbar.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,9 @@ class AztecToolbar : FrameLayout, IAztecToolbar, OnMenuItemClickListener {
737737
this.toolbarItems = toolbarItems
738738
}
739739

740+
/**
741+
* Call this method to enable a task list with checkboxes
742+
*/
740743
fun enableTaskList() {
741744
this.tasklistEnabled = true
742745
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package org.wordpress.aztec
2+
3+
import android.app.Activity
4+
import android.view.MenuItem
5+
import android.widget.PopupMenu
6+
import org.junit.Assert
7+
import org.junit.Before
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
import org.robolectric.Robolectric
11+
import org.robolectric.RobolectricTestRunner
12+
import org.robolectric.annotation.Config
13+
import org.wordpress.aztec.source.SourceViewEditText
14+
import org.wordpress.aztec.toolbar.AztecToolbar
15+
16+
@RunWith(RobolectricTestRunner::class)
17+
@Config(sdk = intArrayOf(23))
18+
class ImageBlockTest {
19+
lateinit var editText: AztecText
20+
lateinit var menuList: PopupMenu
21+
lateinit var menuListOrdered: MenuItem
22+
lateinit var menuListUnordered: MenuItem
23+
lateinit var sourceText: SourceViewEditText
24+
lateinit var toolbar: AztecToolbar
25+
26+
/**
27+
* Initialize variables.
28+
*/
29+
@Before
30+
fun init() {
31+
val activity = Robolectric.buildActivity(Activity::class.java).create().visible().get()
32+
editText = AztecText(activity)
33+
editText.setCalypsoMode(false)
34+
editText.addMediaAfterBlocks()
35+
sourceText = SourceViewEditText(activity)
36+
sourceText.setCalypsoMode(false)
37+
toolbar = AztecToolbar(activity)
38+
toolbar.setEditor(editText, sourceText)
39+
menuList = toolbar.getListMenu() as PopupMenu
40+
menuListOrdered = menuList.menu.getItem(1)
41+
menuListUnordered = menuList.menu.getItem(0)
42+
activity.setContentView(editText)
43+
}
44+
45+
@Test
46+
@Throws(Exception::class)
47+
fun addImageAfterAListAtTheEnd() {
48+
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>")
49+
50+
editText.setSelection(editText.editableText.indexOf("2"))
51+
val attributes = AztecAttributes()
52+
attributes.setValue("id", "1234")
53+
editText.insertImage(null, attributes)
54+
55+
Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><img id=\"1234\" />", editText.toHtml())
56+
}
57+
58+
@Test
59+
@Throws(Exception::class)
60+
fun addHRAfterAListAtTheEnd() {
61+
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>")
62+
63+
editText.setSelection(editText.editableText.indexOf("2"))
64+
editText.lineBlockFormatter.applyHorizontalRule(false)
65+
66+
Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><hr />", editText.toHtml())
67+
}
68+
69+
@Test
70+
@Throws(Exception::class)
71+
fun addImageAfterAListInTheMiddle() {
72+
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>\n<p>test</p>")
73+
74+
editText.setSelection(editText.editableText.indexOf("2"))
75+
val attributes = AztecAttributes()
76+
attributes.setValue("id", "1234")
77+
editText.insertImage(null, attributes)
78+
79+
Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><img id=\"1234\" /><p>test</p>", editText.toHtml())
80+
}
81+
82+
@Test
83+
@Throws(Exception::class)
84+
fun addHRAfterAListInTheMiddle() {
85+
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>\n<p>test</p>")
86+
87+
editText.setSelection(editText.editableText.indexOf("2"))
88+
editText.lineBlockFormatter.applyHorizontalRule(false)
89+
90+
Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><hr /><p>test</p>", editText.toHtml())
91+
}
92+
93+
@Test
94+
@Throws(Exception::class)
95+
fun addImageAfterHeadline() {
96+
editText.fromHtml("<h1>Headline 1</h1>")
97+
98+
editText.setSelection(editText.editableText.indexOf("1"))
99+
val attributes = AztecAttributes()
100+
attributes.setValue("id", "1234")
101+
editText.insertImage(null, attributes)
102+
103+
Assert.assertEquals("<h1>Headline 1</h1><img id=\"1234\" />", editText.toHtml())
104+
}
105+
106+
@Test
107+
@Throws(Exception::class)
108+
fun addHRAfterHeadline() {
109+
editText.fromHtml("<h1>Headline 1</h1>")
110+
111+
editText.setSelection(editText.editableText.indexOf("1"))
112+
editText.lineBlockFormatter.applyHorizontalRule(false)
113+
114+
Assert.assertEquals("<h1>Headline 1</h1><hr />", editText.toHtml())
115+
}
116+
117+
@Test
118+
@Throws(Exception::class)
119+
fun addImageBetweenHeadlines() {
120+
editText.fromHtml("<h1>Headline 1</h1><h2>Headline 2</h2>")
121+
122+
editText.setSelection(editText.editableText.indexOf("1"))
123+
val attributes = AztecAttributes()
124+
attributes.setValue("id", "1234")
125+
editText.insertImage(null, attributes)
126+
127+
Assert.assertEquals("<h1>Headline 1</h1><img id=\"1234\" /><h2>Headline 2</h2>", editText.toHtml())
128+
}
129+
130+
@Test
131+
@Throws(Exception::class)
132+
fun addHRBetweenHeadlines() {
133+
editText.fromHtml("<h1>Headline 1</h1><h2>Headline 2</h2>")
134+
135+
editText.setSelection(editText.editableText.indexOf("1"))
136+
editText.lineBlockFormatter.applyHorizontalRule(false)
137+
138+
Assert.assertEquals("<h1>Headline 1</h1><hr /><h2>Headline 2</h2>", editText.toHtml())
139+
}
140+
141+
@Test
142+
@Throws(Exception::class)
143+
fun addImageAtTheEndWhenNoBlocksPresent() {
144+
editText.fromHtml("Test 1<br>test 2<br>test 3")
145+
146+
editText.setSelection(editText.editableText.indexOf("3"))
147+
val attributes = AztecAttributes()
148+
attributes.setValue("id", "1234")
149+
editText.insertImage(null, attributes)
150+
151+
Assert.assertEquals("Test 1<br>test 2<br>test 3<img id=\"1234\" />", editText.toHtml())
152+
}
153+
154+
@Test
155+
@Throws(Exception::class)
156+
fun addImageInTheMiddleWhenNoBlocksPresent() {
157+
editText.fromHtml("Test 1<br>test 2<br>test 3")
158+
159+
editText.setSelection(editText.editableText.indexOf("2"))
160+
val attributes = AztecAttributes()
161+
attributes.setValue("id", "1234")
162+
editText.insertImage(null, attributes)
163+
164+
Assert.assertEquals("Test 1<br>test 2<img id=\"1234\" /><br>test 3", editText.toHtml())
165+
}
166+
}

0 commit comments

Comments
 (0)