Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,39 @@ dependencies {
}
```

## Modifications

You can use the API to modify Aztec behaviour.

### Toolbar items

If you want to limit the functionality the Aztec library provides, you can change it calling the `setToolbarItems` method on `AztecToolbar`.
The following example will enable only `bold`, `plugins` and `list` items in the given order.

```kotlin
aztecToolbar.setToolbarItems(ToolbarItems.BasicLayout(ToolbarAction.BOLD, ToolbarItems.PLUGINS, ToolbarAction.LIST))
```

You can set new items which are not enabled by default. `ToolbarAction.CODE` and `ToolbarAction.PRE`.
- `CODE` represents inline HTML code
- `PRE` represents a preformat block (including code block)

### Task list

There is an optional list type you can enable in the editor. In addition to ordered and unordered lists you can use `task list`.
A task list is an unordered list which shows and saves checkboxes instead of the bullets. Enable it by calling the following method.
```kotlin
aztecToolbar.enableTaskList()
```

### Nested blocks

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.
When switched, this editor will always add media and horizontal rule after the currently selected block, not in the middle of it.
```kotlin
aztecText.addMediaAfterBlocks()
```

## Code formatting

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.
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/kotlin/org/wordpress/aztec/demo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,6 @@ open class MainActivity : AppCompatActivity(),
}
})

toolbar.enableTaskList()

aztec = Aztec.with(visualEditor, sourceEditor, toolbar, this)
.setImageGetter(GlideImageLoader(this))
.setVideoThumbnailGetter(GlideVideoThumbnailLoader(this))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {

return false
}

fun applyHorizontalRule(inline: Boolean) {
val nestingLevel = if (inline) {
editor.removeInlineStylesFromRange(selectionStart, selectionEnd)
Expand All @@ -117,7 +118,6 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
val newSelectionPosition = editableText.indexOf(Constants.MAGIC_CHAR, selectionStart) + 1
editor.setSelection(newSelectionPosition)
} else {
builder.append("\n")
insertSpanAfterBlock(builder)
}
}
Expand Down Expand Up @@ -163,7 +163,6 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {

private fun insertMediaAfterBlock(span: AztecMediaSpan) {
val ssb = SpannableStringBuilder(Constants.IMG_STRING)
ssb.append("\n")
buildClickableMediaSpan(ssb, span)
insertSpanAfterBlock(ssb)
}
Expand All @@ -173,23 +172,38 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
// We need to be sure the cursor is placed correctly after media insertion
// Note that media has '\n' around them when needed
val isLastItem = position == EndOfBufferMarkerAdder.safeLength(editor)
val insertedLength = ssb.length
editableText.insert(position, ssb)
val spans = editableText.getSpans(position, position + insertedLength, IAztecBlockSpan::class.java).filter {
it !is AztecMediaSpan && editableText.getSpanStart(it) == position
}
spans.forEach {
val spanStart = editableText.getSpanStart(it)
val spanEnd = editableText.getSpanEnd(it)
val spanFlags = editableText.getSpanFlags(it)
editableText.removeSpan(it)
if (spanStart + insertedLength < spanEnd) {
editableText.setSpan(it, spanStart + insertedLength, spanEnd, spanFlags)
if (isLastItem) {
editableText.getSpans(position, editableText.length, IAztecBlockSpan::class.java).filter {
it !is AztecMediaSpan && editableText.getSpanEnd(it) == editableText.length
}.map {
SpanData(it, editableText.getSpanStart(it), position + 1, editableText.getSpanFlags(it))
}.applyWithRemovedSpans {
editableText.append(ssb)
}
} else {
ssb.append("\n")
val ssbLength = ssb.length
editableText.getSpans(position, position + ssbLength, IAztecBlockSpan::class.java).filter {
it !is AztecMediaSpan && editableText.getSpanStart(it) == position
}.map {
SpanData(it, editableText.getSpanStart(it) + ssbLength, editableText.getSpanEnd(it) + ssbLength, editableText.getSpanFlags(it))
}.applyWithRemovedSpans {
editableText.insert(position, ssb)
}
}
setSelection(isLastItem, position)
}

private fun List<SpanData>.applyWithRemovedSpans(action: () -> Unit) {
this.onEach { editableText.removeSpan(it.span) }
action()
this.onEach {
editableText.setSpan(it.span, it.spanStart, it.spanEnd, it.spanFlags)
}
}

data class SpanData(val span: IAztecBlockSpan, val spanStart: Int, val spanEnd: Int, val spanFlags: Int)

private fun setSelection(isLastItem: Boolean, position: Int) {
val newSelection = if (isLastItem) {
EndOfBufferMarkerAdder.safeLength(editor)
Expand Down Expand Up @@ -217,13 +231,20 @@ class LineBlockFormatter(editor: AztecText) : AztecFormatter(editor) {
}

private fun getEndOfBlock(): Int {
if (selectionStart == 0 && selectionEnd == 0) {
return 0
}
var position = 0
editableText.getSpans(selectionStart, selectionEnd, IAztecBlockSpan::class.java).forEach {
val spanEnd = editableText.getSpanEnd(it)
if (spanEnd > position) {
position = spanEnd
}
}
if (position <= 0 && selectionEnd != 0) {
// If the text contains "\n" return that as the position, else set the position to the end of the text
position = editableText.indexOf("\n", selectionEnd).takeIf { it >= 0 } ?: editableText.length
}
return position
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,9 @@ class AztecToolbar : FrameLayout, IAztecToolbar, OnMenuItemClickListener {
this.toolbarItems = toolbarItems
}

/**
* Call this method to enable a task list with checkboxes
*/
fun enableTaskList() {
this.tasklistEnabled = true
}
Expand Down
166 changes: 166 additions & 0 deletions aztec/src/test/kotlin/org/wordpress/aztec/ImageBlockTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package org.wordpress.aztec

import android.app.Activity
import android.view.MenuItem
import android.widget.PopupMenu
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.wordpress.aztec.source.SourceViewEditText
import org.wordpress.aztec.toolbar.AztecToolbar

@RunWith(RobolectricTestRunner::class)
@Config(sdk = intArrayOf(23))
class ImageBlockTest {
lateinit var editText: AztecText
lateinit var menuList: PopupMenu
lateinit var menuListOrdered: MenuItem
lateinit var menuListUnordered: MenuItem
lateinit var sourceText: SourceViewEditText
lateinit var toolbar: AztecToolbar

/**
* Initialize variables.
*/
@Before
fun init() {
val activity = Robolectric.buildActivity(Activity::class.java).create().visible().get()
editText = AztecText(activity)
editText.setCalypsoMode(false)
editText.addMediaAfterBlocks()
sourceText = SourceViewEditText(activity)
sourceText.setCalypsoMode(false)
toolbar = AztecToolbar(activity)
toolbar.setEditor(editText, sourceText)
menuList = toolbar.getListMenu() as PopupMenu
menuListOrdered = menuList.menu.getItem(1)
menuListUnordered = menuList.menu.getItem(0)
activity.setContentView(editText)
}

@Test
@Throws(Exception::class)
fun addImageAfterAListAtTheEnd() {
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>")

editText.setSelection(editText.editableText.indexOf("2"))
val attributes = AztecAttributes()
attributes.setValue("id", "1234")
editText.insertImage(null, attributes)

Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><img id=\"1234\" />", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addHRAfterAListAtTheEnd() {
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>")

editText.setSelection(editText.editableText.indexOf("2"))
editText.lineBlockFormatter.applyHorizontalRule(false)

Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><hr />", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addImageAfterAListInTheMiddle() {
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>\n<p>test</p>")

editText.setSelection(editText.editableText.indexOf("2"))
val attributes = AztecAttributes()
attributes.setValue("id", "1234")
editText.insertImage(null, attributes)

Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><img id=\"1234\" /><p>test</p>", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addHRAfterAListInTheMiddle() {
editText.fromHtml("<ul><li>item 1</li><li>item 2</li></ul>\n<p>test</p>")

editText.setSelection(editText.editableText.indexOf("2"))
editText.lineBlockFormatter.applyHorizontalRule(false)

Assert.assertEquals("<ul><li>item 1</li><li>item 2</li></ul><hr /><p>test</p>", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addImageAfterHeadline() {
editText.fromHtml("<h1>Headline 1</h1>")

editText.setSelection(editText.editableText.indexOf("1"))
val attributes = AztecAttributes()
attributes.setValue("id", "1234")
editText.insertImage(null, attributes)

Assert.assertEquals("<h1>Headline 1</h1><img id=\"1234\" />", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addHRAfterHeadline() {
editText.fromHtml("<h1>Headline 1</h1>")

editText.setSelection(editText.editableText.indexOf("1"))
editText.lineBlockFormatter.applyHorizontalRule(false)

Assert.assertEquals("<h1>Headline 1</h1><hr />", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addImageBetweenHeadlines() {
editText.fromHtml("<h1>Headline 1</h1><h2>Headline 2</h2>")

editText.setSelection(editText.editableText.indexOf("1"))
val attributes = AztecAttributes()
attributes.setValue("id", "1234")
editText.insertImage(null, attributes)

Assert.assertEquals("<h1>Headline 1</h1><img id=\"1234\" /><h2>Headline 2</h2>", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addHRBetweenHeadlines() {
editText.fromHtml("<h1>Headline 1</h1><h2>Headline 2</h2>")

editText.setSelection(editText.editableText.indexOf("1"))
editText.lineBlockFormatter.applyHorizontalRule(false)

Assert.assertEquals("<h1>Headline 1</h1><hr /><h2>Headline 2</h2>", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addImageAtTheEndWhenNoBlocksPresent() {
editText.fromHtml("Test 1<br>test 2<br>test 3")

editText.setSelection(editText.editableText.indexOf("3"))
val attributes = AztecAttributes()
attributes.setValue("id", "1234")
editText.insertImage(null, attributes)

Assert.assertEquals("Test 1<br>test 2<br>test 3<img id=\"1234\" />", editText.toHtml())
}

@Test
@Throws(Exception::class)
fun addImageInTheMiddleWhenNoBlocksPresent() {
editText.fromHtml("Test 1<br>test 2<br>test 3")

editText.setSelection(editText.editableText.indexOf("2"))
val attributes = AztecAttributes()
attributes.setValue("id", "1234")
editText.insertImage(null, attributes)

Assert.assertEquals("Test 1<br>test 2<img id=\"1234\" /><br>test 3", editText.toHtml())
}
}