From eb3a986c48453d241cad3afb2205fbba464c9382 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 28 Dec 2020 15:28:13 +0300 Subject: [PATCH] sample, add TaskListMutateNestedSample --- app-sample/samples.json | 12 ++ .../tasklist/TaskListMutateNestedSample.kt | 157 ++++++++++++++++++ .../tasklist/TaskListMutateSample.java | 56 ++++--- 3 files changed, 199 insertions(+), 26 deletions(-) create mode 100644 app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateNestedSample.kt diff --git a/app-sample/samples.json b/app-sample/samples.json index 36eb05a1..e846fcb1 100644 --- a/app-sample/samples.json +++ b/app-sample/samples.json @@ -1,4 +1,16 @@ [ + { + "javaClassName": "io.noties.markwon.app.samples.tasklist.TaskListMutateNestedSample", + "id": "20211228120444", + "title": "Task list mutate nested", + "description": "Task list mutation with nested items", + "artifacts": [ + "EXT_TASKLIST" + ], + "tags": [ + "plugin" + ] + }, { "javaClassName": "io.noties.markwon.app.samples.image.ClickImageSample", "id": "20201221130230", diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateNestedSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateNestedSample.kt new file mode 100644 index 00000000..22e28acb --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateNestedSample.kt @@ -0,0 +1,157 @@ +package io.noties.markwon.app.samples.tasklist + +import android.text.style.ClickableSpan +import android.view.View +import io.noties.debug.Debug +import io.noties.markwon.AbstractMarkwonPlugin +import io.noties.markwon.Markwon +import io.noties.markwon.MarkwonVisitor +import io.noties.markwon.SoftBreakAddsNewLinePlugin +import io.noties.markwon.SpannableBuilder +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.ext.tasklist.TaskListItem +import io.noties.markwon.ext.tasklist.TaskListPlugin +import io.noties.markwon.ext.tasklist.TaskListProps +import io.noties.markwon.ext.tasklist.TaskListSpan +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo +import org.commonmark.node.AbstractVisitor +import org.commonmark.node.Block +import org.commonmark.node.HardLineBreak +import org.commonmark.node.Node +import org.commonmark.node.Paragraph +import org.commonmark.node.SoftLineBreak +import org.commonmark.node.Text + +@MarkwonSampleInfo( + id = "20211228120444", + title = "Task list mutate nested", + description = "Task list mutation with nested items", + artifacts = [MarkwonArtifact.EXT_TASKLIST], + tags = [Tags.plugin] +) +class TaskListMutateNestedSample : MarkwonTextViewSample() { + override fun render() { + val md = """ + # Task list + - [ ] not done + - [X] done + - [ ] nested not done + and text and textand text and text + - [X] nested done + """.trimIndent() + + val markwon = Markwon.builder(context) + .usePlugin(TaskListPlugin.create(context)) + .usePlugin(SoftBreakAddsNewLinePlugin.create()) + .usePlugin(object : AbstractMarkwonPlugin() { + override fun configureVisitor(builder: MarkwonVisitor.Builder) { + builder.on(TaskListItem::class.java) { visitor, node -> + + val length = visitor.length() + + visitor.visitChildren(node) + + TaskListProps.DONE.set(visitor.renderProps(), node.isDone) + + val spans = visitor.configuration() + .spansFactory() + .get(TaskListItem::class.java) + ?.getSpans(visitor.configuration(), visitor.renderProps()) + + if (spans != null) { + + val taskListSpan = if (spans is Array<*>) { + spans.first { it is TaskListSpan } as? TaskListSpan + } else { + spans as? TaskListSpan + } + + Debug.i("#### ${visitor.builder().substring(length, length + 3)}") + val content = TaskListContextVisitor.contentLength(node) + Debug.i("#### content: $content, '${visitor.builder().subSequence(length, length + content)}'") + + if (content > 0 && taskListSpan != null) { + // maybe additionally identify this task list (for persistence) + visitor.builder().setSpan( + ToggleTaskListSpan(taskListSpan, visitor.builder().substring(length, length + content)), + length, + length + content + ) + } + } + + SpannableBuilder.setSpans( + visitor.builder(), + spans, + length, + visitor.length() + ) + + if (visitor.hasNext(node)) { + visitor.ensureNewLine() + } + } + } + }) + .build() + + markwon.setMarkdown(textView, md) + } + + class TaskListContextVisitor : AbstractVisitor() { + + companion object { + fun contentLength(node: Node): Int { + val visitor = TaskListContextVisitor() + visitor.visitChildren(node) + return visitor.contentLength + } + } + + var contentLength: Int = 0 + + override fun visit(text: Text) { + super.visit(text) + contentLength += text.literal.length + } + + // NB! if count both soft and hard breaks as having length of 1 + override fun visit(softLineBreak: SoftLineBreak?) { + super.visit(softLineBreak) + contentLength += 1 + } + + // NB! if count both soft and hard breaks as having length of 1 + override fun visit(hardLineBreak: HardLineBreak?) { + super.visit(hardLineBreak) + contentLength += 1 + } + + override fun visitChildren(parent: Node) { + var node = parent.firstChild + while (node != null) { + // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no + // node after visiting it. So get the next node before visiting. + val next = node.next + if (node is Block && node !is Paragraph) { + break + } + node.accept(this) + node = next + } + } + } + + class ToggleTaskListSpan( + val span: TaskListSpan, + val content: String + ) : ClickableSpan() { + override fun onClick(widget: View) { + span.isDone = !span.isDone + widget.invalidate() + Debug.i("task-list click, isDone: ${span.isDone}, content: '$content'") + } + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateSample.java index d60a1244..b1ae172f 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateSample.java @@ -32,6 +32,10 @@ import static io.noties.markwon.app.samples.tasklist.shared.TaskListHolder.MD; public class TaskListMutateSample extends MarkwonTextViewSample { @Override public void render() { + + // NB! this sample works for a single level task list, + // if you have multiple levels, then see the `TaskListMutateNestedSample` + final Markwon markwon = Markwon.builder(context) .usePlugin(TaskListPlugin.create(context)) .usePlugin(new AbstractMarkwonPlugin() { @@ -63,39 +67,39 @@ public class TaskListMutateSample extends MarkwonTextViewSample { markwon.setMarkdown(textView, MD); } -} -class TaskListToggleSpan extends ClickableSpan { + static class TaskListToggleSpan extends ClickableSpan { - private final TaskListSpan span; + private final TaskListSpan span; - TaskListToggleSpan(@NonNull TaskListSpan span) { - this.span = span; - } + TaskListToggleSpan(@NonNull TaskListSpan span) { + this.span = span; + } - @Override - public void onClick(@NonNull View widget) { - // toggle span (this is a mere visual change) - span.setDone(!span.isDone()); - // request visual update - widget.invalidate(); + @Override + public void onClick(@NonNull View widget) { + // toggle span (this is a mere visual change) + span.setDone(!span.isDone()); + // request visual update + widget.invalidate(); - // it must be a TextView - final TextView textView = (TextView) widget; - // it must be spanned - final Spanned spanned = (Spanned) textView.getText(); + // it must be a TextView + final TextView textView = (TextView) widget; + // it must be spanned + final Spanned spanned = (Spanned) textView.getText(); - // actual text of the span (this can be used along with the `span`) - final CharSequence task = spanned.subSequence( - spanned.getSpanStart(this), - spanned.getSpanEnd(this) - ); + // actual text of the span (this can be used along with the `span`) + final CharSequence task = spanned.subSequence( + spanned.getSpanStart(this), + spanned.getSpanEnd(this) + ); - Debug.i("task done: %s, '%s'", span.isDone(), task); - } + Debug.i("task done: %s, '%s'", span.isDone(), task); + } - @Override - public void updateDrawState(@NonNull TextPaint ds) { - // no op, so text is not rendered as a link + @Override + public void updateDrawState(@NonNull TextPaint ds) { + // no op, so text is not rendered as a link + } } }