diff --git a/CHANGELOG.md b/CHANGELOG.md index 845d8b01..6bd3867c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ #### Added * `core` - `MovementMethodPlugin.none()`, `MovementMethodPlugin.link()` factory methods * `core` - `CorePlugin` `hasExplicitMovementMethod` configuration method to **not** add implicit `LinkMovementMethod` in `afterSetText` +* `ext-latex` - `JLatexMathTheme.Padding.of(int,int,int,int)` factory method #### Changed * `html` - `SimpleTagHandler` visits children tags if supplied tag is block one ([#235]) diff --git a/app-sample/README.md b/app-sample/README.md new file mode 100644 index 00000000..f9056622 --- /dev/null +++ b/app-sample/README.md @@ -0,0 +1,12 @@ +# Markwon sample app + +## Building + +When adding/removing samples _most likely_ a clean build would be required. +First, for annotation processor to create `samples.json`. And secondly, +in order for Android Gradle plugin to bundle resources references via +symbolic links (the `sample.json` itself and `io.noties.markwon.app.samples.*` directory) + +```gradle +./gradlew :app-s:clean :app-s:asDe +``` \ No newline at end of file diff --git a/app-sample/build.gradle b/app-sample/build.gradle index dc3ac690..54909a37 100644 --- a/app-sample/build.gradle +++ b/app-sample/build.gradle @@ -76,6 +76,7 @@ dependencies { implementation project(':markwon-image-glide') deps.with { +// implementation it['x-appcompat'] implementation it['x-recycler-view'] implementation it['x-cardview'] implementation it['x-fragment'] diff --git a/app-sample/samples.json b/app-sample/samples.json index 6e27d5bb..ab7eb06a 100644 --- a/app-sample/samples.json +++ b/app-sample/samples.json @@ -1,4 +1,435 @@ [ + { + "javaClassName": "io.noties.markwon.app.samples.tasklist.TaskListMutateSample", + "id": "202007184140901", + "title": "GFM task list mutate", + "description": "", + "artifacts": [ + "EXT_TASKLIST" + ], + "tags": [ + "plugin" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.tasklist.TaskListCustomDrawableSample", + "id": "202007184140749", + "title": "GFM task list custom drawable", + "description": "", + "artifacts": [ + "EXT_TASKLIST" + ], + "tags": [ + "plugin" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.tasklist.TaskListCustomColorsSample", + "id": "202007184140536", + "title": "GFM task list custom colors", + "description": "Custom colors for task list extension", + "artifacts": [ + "EXT_TASKLIST" + ], + "tags": [ + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.tasklist.TaskListSample", + "id": "202007184140352", + "title": "GFM task list", + "description": "Github Flavored Markdown (GFM) task list extension", + "artifacts": [ + "EXT_TASKLIST" + ], + "tags": [ + "plugin" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.table.TableLatexSample", + "id": "202007184140041", + "title": "LaTeX inside table", + "description": "Usage of LaTeX formulas inside markdown tables", + "artifacts": [ + "EXT_LATEX", + "EXT_TABLES", + "IMAGE" + ], + "tags": [ + "image" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.table.TableWithImagesSample", + "id": "202007184135932", + "title": "Images inside table", + "description": "Usage of images inside markdown tables", + "artifacts": [ + "EXT_TABLES", + "IMAGE" + ], + "tags": [ + "image" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.table.TableLinkifySample", + "id": "202007184135739", + "title": "Linkify table", + "description": "Automatically linkify markdown content including content inside tables", + "artifacts": [ + "EXT_TABLES", + "LINKIFY" + ], + "tags": [ + "links" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.table.TableCustomizeSample", + "id": "202007184135621", + "title": "Customize table theme", + "description": "", + "artifacts": [ + "EXT_TABLES" + ], + "tags": [ + "theme" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.RecyclerSample", + "id": "202007184101750", + "title": "RecyclerView", + "description": "Usage with `RecyclerView`", + "artifacts": [ + "RECYCLER", + "RECYCLER_TABLE" + ], + "tags": [ + "recycler-view" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.LinkRemoveUnderlineSample", + "id": "202007184101224", + "title": "Remove link underline", + "description": "", + "artifacts": [ + "CORE" + ], + "tags": [ + "links", + "rendering", + "span" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.PrecomputedFutureSample", + "id": "202007184092446", + "title": "PrecomputedFutureTextSetterCompat", + "description": "Usage of `PrecomputedFutureTextSetterCompat` inside a `RecyclerView` with appcompat", + "artifacts": [ + "RECYCLER" + ], + "tags": [ + "precomputed-text", + "recycler-view" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.PrecomputedSample", + "id": "202007184091654", + "title": "PrecomputedTextSetterCompat", + "description": "`TextSetter` to use `PrecomputedTextSetterCompat`", + "artifacts": [ + "CORE" + ], + "tags": [ + "precomputed-text" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.notification.RemoteViewsSample", + "id": "202007184090140", + "title": "RemoteViews in notification", + "description": "Display markdown with platform (system) spans in notification via `RemoteViews`", + "artifacts": [ + "CORE" + ], + "tags": [ + "hack" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.notification.NotificationSample", + "id": "202007183130729", + "title": "Markdown in Notification", + "description": "Proof of concept of using `Markwon` with `android.app.Notification`", + "artifacts": [ + "CORE" + ], + "tags": [ + "hack" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexErrorSample", + "id": "202007183122624", + "title": "LaTeX error handling", + "description": "Log error when parsing LaTeX and display error drawable", + "artifacts": [ + "EXT_LATEX" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexThemeSample", + "id": "202007183121528", + "title": "LaTeX theme", + "description": "Sample of theme customization for LaTeX", + "artifacts": [ + "EXT_LATEX", + "INLINE_PARSER" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexDefaultTextColorSample", + "id": "202007183120848", + "title": "LaTeX default text color", + "description": "LaTeX will use text color of `TextView` by default", + "artifacts": [ + "EXT_LATEX" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexDarkSample", + "id": "202007183094225", + "title": "LaTeX dark", + "description": "LaTeX automatically uses `TextView` text color if not configured explicitly", + "artifacts": [ + "EXT_LATEX" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexDifferentTextSizesSample", + "id": "202007183093504", + "title": "LaTeX inline/block different text size", + "description": "", + "artifacts": [ + "EXT_LATEX", + "INLINE_PARSER" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexOmegaSample", + "id": "202007183090618", + "title": "LaTeX omega symbol", + "description": "Bug rendering omega symbol in LaTeX", + "artifacts": [ + "EXT_LATEX", + "INLINE_PARSER" + ], + "tags": [ + "known-bug", + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexLegacySample", + "id": "202007183090335", + "title": "LaTeX blocks in legacy mode", + "description": "Sample using _legacy_ LaTeX block parsing (pre `4.3.0` Markwon version)", + "artifacts": [ + "EXT_LATEX" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexInlineSample", + "id": "202007183085820", + "title": "LaTeX inline", + "description": "Display LaTeX inline", + "artifacts": [ + "EXT_LATEX", + "INLINE_PARSER" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.latex.LatexBlockSample", + "id": "202006182200257", + "title": "LaTex block", + "description": "Render LaTeX block", + "artifacts": [ + "EXT_LATEX" + ], + "tags": [ + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.inlineparsing.InlineParsingTooltipSample", + "id": "202006182195409", + "title": "Tooltip with inline parser", + "description": "", + "artifacts": [ + "INLINE_PARSER" + ], + "tags": [ + "parsing", + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.SimpleExtensionSample", + "id": "202006182194335", + "title": "Delimiter processor simple-ext", + "description": "Custom delimiter processor implemented with a `SimpleExtPlugin`", + "artifacts": [ + "SIMPLE_EXT" + ], + "tags": [ + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.DelimiterProcessorSample", + "id": "202006182194017", + "title": "Custom delimiter processor", + "description": "Custom parsing delimiter processor with `?` character", + "artifacts": [ + "CORE" + ], + "tags": [ + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.html.HtmlDisableSanitizeSample", + "id": "202006182171424", + "title": "Disable HTML", + "description": "Disable HTML via replacing special `\u003c` and `\u003e` symbols", + "artifacts": [ + "CORE" + ], + "tags": [ + "HTML", + "parsing", + "plugin", + "rendering" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.inlineparsing.InlineParsingNoHtmlSample", + "id": "202006182171239", + "title": "Inline parsing exclude HTML", + "description": "", + "artifacts": [ + "INLINE_PARSER" + ], + "tags": [ + "block", + "inline", + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.inlineparsing.InlineParsingNoDefaultsSample", + "id": "202006182170823", + "title": "Inline parsing no defaults", + "description": "Parsing only inline code and disable all the rest", + "artifacts": [ + "INLINE_PARSER" + ], + "tags": [ + "inline", + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.inlineparsing.InlineParsingWithDefaultsSample", + "id": "202006182170723", + "title": "Inline parsing with defaults", + "description": "Parsing with all defaults except links", + "artifacts": [ + "INLINE_PARSER" + ], + "tags": [ + "inline", + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.inlineparsing.InlineParsingDisableCodeSample", + "id": "202006182170607", + "title": "Disable code inline parsing", + "description": "", + "artifacts": [ + "INLINE_PARSER" + ], + "tags": [ + "inline", + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.inlineparsing.InlineParsingLinksOnlySample", + "id": "202006182170412", + "title": "Links only inline parsing", + "description": "", + "artifacts": [ + "INLINE_PARSER" + ], + "tags": [ + "inline", + "parsing" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.image.GlidePlaceholderImageSample", + "id": "202006182170241", + "title": "Glide image with placeholder", + "description": "", + "artifacts": [ + "IMAGE_GLIDE" + ], + "tags": [ + "image" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.image.GlideImageSample", + "id": "202006182170112", + "title": "Glide image", + "description": "", + "artifacts": [ + "IMAGE_GLIDE" + ], + "tags": [ + "image" + ] + }, { "javaClassName": "io.noties.markwon.app.samples.image.ErrorImageSample", "id": "202006182165828", @@ -71,6 +502,7 @@ "IMAGE" ], "tags": [ + "HTML", "image", "rendering" ] @@ -85,6 +517,7 @@ "IMAGE" ], "tags": [ + "HTML", "rendering" ] }, @@ -97,6 +530,7 @@ "HTML" ], "tags": [ + "HTML", "rendering" ] }, @@ -110,6 +544,7 @@ "IMAGE" ], "tags": [ + "HTML", "image", "rendering" ] @@ -124,6 +559,7 @@ "IMAGE" ], "tags": [ + "HTML", "image", "rendering" ] @@ -137,6 +573,7 @@ "HTML" ], "tags": [ + "HTML", "rendering", "span" ] @@ -150,6 +587,7 @@ "HTML" ], "tags": [ + "HTML", "rendering", "span" ] @@ -163,6 +601,7 @@ "HTML" ], "tags": [ + "HTML", "rendering", "span" ] @@ -485,7 +924,7 @@ ] }, { - "javaClassName": "io.noties.markwon.app.samples.ImagesCustomSchemeSample", + "javaClassName": "io.noties.markwon.app.samples.image.ImagesCustomSchemeSample", "id": "202006181124201", "title": "Image destination custom scheme", "description": "Example of handling custom scheme (`https`, `ftp`, `whatever`, etc.) for images destination URLs with `ImagesPlugin`", diff --git a/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt b/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt index 35208d2a..595d5453 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt +++ b/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt @@ -31,4 +31,6 @@ object Tags { const val gif = "GIF" const val inline = "inline" const val html = "HTML" + const val knownBug = "known-bug" + const val precomputedText = "precomputed-text" } \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonRecyclerViewSample.kt b/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonRecyclerViewSample.kt new file mode 100644 index 00000000..9defb91b --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonRecyclerViewSample.kt @@ -0,0 +1,23 @@ +package io.noties.markwon.app.sample.ui + +import android.content.Context +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import io.noties.markwon.app.R + +abstract class MarkwonRecyclerViewSample : MarkwonSample() { + + protected lateinit var context: Context + protected lateinit var recyclerView: RecyclerView + + override fun onViewCreated(view: View) { + context = view.context + recyclerView = view.findViewById(R.id.recycler_view) + render() + } + + override val layoutResId: Int + get() = R.layout.sample_recycler_view + + abstract fun render() +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonTextViewSample.kt b/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonTextViewSample.kt index 922d103c..2594abd7 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonTextViewSample.kt +++ b/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonTextViewSample.kt @@ -2,6 +2,7 @@ package io.noties.markwon.app.sample.ui import android.content.Context import android.view.View +import android.widget.ScrollView import android.widget.TextView import io.noties.markwon.app.R @@ -9,12 +10,14 @@ import io.noties.markwon.app.R abstract class MarkwonTextViewSample : MarkwonSample() { protected lateinit var context: Context + protected lateinit var scrollView: ScrollView protected lateinit var textView: TextView override val layoutResId: Int = R.layout.sample_text_view override fun onViewCreated(view: View) { context = view.context + scrollView = view.findViewById(R.id.scroll_view) textView = view.findViewById(R.id.text_view) render() } diff --git a/app-sample/src/main/java/io/noties/markwon/app/sample/ui/SampleListFragment.kt b/app-sample/src/main/java/io/noties/markwon/app/sample/ui/SampleListFragment.kt index 5ad6e69a..bf5154e2 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/sample/ui/SampleListFragment.kt +++ b/app-sample/src/main/java/io/noties/markwon/app/sample/ui/SampleListFragment.kt @@ -54,10 +54,10 @@ class SampleListFragment : Fragment() { private val sampleManager: SampleManager get() = App.sampleManager - override fun onAttach(context: Context?) { + override fun onAttach(context: Context) { super.onAttach(context) - context?.also { + context.also { markwon = markwon(it) } } diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/DelimiterProcessorSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/DelimiterProcessorSample.java new file mode 100644 index 00000000..453a4003 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/DelimiterProcessorSample.java @@ -0,0 +1,83 @@ +package io.noties.markwon.app.samples; + +import androidx.annotation.NonNull; + +import org.commonmark.node.Emphasis; +import org.commonmark.node.Node; +import org.commonmark.node.Text; +import org.commonmark.parser.Parser; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202006182194017", + title = "Custom delimiter processor", + description = "Custom parsing delimiter processor with `?` character", + artifacts = MarkwonArtifact.CORE, + tags = Tags.parsing +) +public class DelimiterProcessorSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "?hello? there!"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureParser(@NonNull Parser.Builder builder) { + builder.customDelimiterProcessor(new QuestionDelimiterProcessor()); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } +} + +class QuestionDelimiterProcessor implements DelimiterProcessor { + + @Override + public char getOpeningCharacter() { + return '?'; + } + + @Override + public char getClosingCharacter() { + return '?'; + } + + @Override + public int getMinLength() { + return 1; + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + if (opener.length() >= 1 && closer.length() >= 1) { + return 1; + } + return 0; + } + + @Override + public void process(Text opener, Text closer, int delimiterUse) { + final Node node = new Emphasis(); + + Node tmp = opener.getNext(); + while (tmp != null && tmp != closer) { + Node next = tmp.getNext(); + node.appendChild(tmp); + tmp = next; + } + + opener.insertAfter(node); + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/HeadingNoSpaceSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/HeadingNoSpaceSample.java index 5171a16b..2fe89a46 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/HeadingNoSpaceSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/HeadingNoSpaceSample.java @@ -21,7 +21,7 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo; artifacts = MarkwonArtifact.CORE, tags = {Tags.spacing, Tags.padding, Tags.spacing, Tags.rendering} ) -class HeadingNoSpaceSample extends MarkwonTextViewSample { +public class HeadingNoSpaceSample extends MarkwonTextViewSample { @Override public void render() { final String md = "" + diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/LinkRemoveUnderlineSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/LinkRemoveUnderlineSample.java new file mode 100644 index 00000000..a2acf687 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/LinkRemoveUnderlineSample.java @@ -0,0 +1,49 @@ +package io.noties.markwon.app.samples; + +import android.text.TextPaint; +import android.text.style.CharacterStyle; +import android.text.style.UpdateAppearance; + +import androidx.annotation.NonNull; + +import org.commonmark.node.Link; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184101224", + title = "Remove link underline", + artifacts = MarkwonArtifact.CORE, + tags = {Tags.links, Tags.rendering, Tags.span} +) +public class LinkRemoveUnderlineSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "There are a lot of [links](#) [here](#)"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.appendFactory(Link.class, (configuration, props) -> new RemoveUnderlineSpan()); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } +} + +class RemoveUnderlineSpan extends CharacterStyle implements UpdateAppearance { + @Override + public void updateDrawState(TextPaint tp) { + tp.setUnderlineText(false); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/PrecomputedFutureSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/PrecomputedFutureSample.java new file mode 100644 index 00000000..a22d6fa1 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/PrecomputedFutureSample.java @@ -0,0 +1,75 @@ +package io.noties.markwon.app.samples; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.PrecomputedFutureTextSetterCompat; +import io.noties.markwon.app.R; +import io.noties.markwon.app.readme.GithubImageDestinationProcessor; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonRecyclerViewSample; +import io.noties.markwon.app.utils.SampleUtilsKtKt; +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; +import io.noties.markwon.ext.tables.TablePlugin; +import io.noties.markwon.ext.tasklist.TaskListPlugin; +import io.noties.markwon.image.ImagesPlugin; +import io.noties.markwon.recycler.MarkwonAdapter; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184092446", + title = "PrecomputedFutureTextSetterCompat", + description = "Usage of `PrecomputedFutureTextSetterCompat` " + + "inside a `RecyclerView` with appcompat", + artifacts = {MarkwonArtifact.RECYCLER}, + tags = {Tags.recyclerView, Tags.precomputedText} +) +public class PrecomputedFutureSample extends MarkwonRecyclerViewSample { + @Override + public void render() { + if (!hasAppCompat()) { + /* + PLEASE COMPILE WITH `APPCOMPAT` dependency + */ + return; + } + + final String md = SampleUtilsKtKt.loadReadMe(context); + + final Markwon markwon = Markwon.builder(context) + .textSetter(PrecomputedFutureTextSetterCompat.create()) + .usePlugin(ImagesPlugin.create()) + .usePlugin(TablePlugin.create(context)) + .usePlugin(TaskListPlugin.create(context)) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.imageDestinationProcessor(new GithubImageDestinationProcessor()); + } + }) + .build(); + + final MarkwonAdapter adapter = MarkwonAdapter + .createTextViewIsRoot(R.layout.adapter_appcompat_default_entry); + + recyclerView.setLayoutManager(new LinearLayoutManager(context)); + recyclerView.setAdapter(adapter); + + adapter.setMarkdown(markwon, md); + adapter.notifyDataSetChanged(); + } + + private static boolean hasAppCompat() { + try { + Class.forName("androidx.appcompat.widget.AppCompatTextView"); + return true; + } catch (Throwable t) { + return false; + } + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/PrecomputedSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/PrecomputedSample.java new file mode 100644 index 00000000..0cd4d9a3 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/PrecomputedSample.java @@ -0,0 +1,32 @@ +package io.noties.markwon.app.samples; + +import java.util.concurrent.Executors; + +import io.noties.markwon.Markwon; +import io.noties.markwon.PrecomputedTextSetterCompat; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184091654", + title = "PrecomputedTextSetterCompat", + description = "`TextSetter` to use `PrecomputedTextSetterCompat`", + artifacts = MarkwonArtifact.CORE, + tags = Tags.precomputedText +) +public class PrecomputedSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# Heading\n" + + "**bold** some precomputed spans via `PrecomputedTextSetterCompat`"; + + final Markwon markwon = Markwon.builder(context) + .textSetter(PrecomputedTextSetterCompat.create(Executors.newCachedThreadPool())) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/RecyclerSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/RecyclerSample.java new file mode 100644 index 00000000..2ad2160f --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/RecyclerSample.java @@ -0,0 +1,84 @@ +package io.noties.markwon.app.samples; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; + +import org.commonmark.ext.gfm.tables.TableBlock; +import org.commonmark.node.FencedCodeBlock; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.app.R; +import io.noties.markwon.app.readme.GithubImageDestinationProcessor; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonRecyclerViewSample; +import io.noties.markwon.app.utils.SampleUtilsKtKt; +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; +import io.noties.markwon.ext.tasklist.TaskListPlugin; +import io.noties.markwon.html.HtmlPlugin; +import io.noties.markwon.image.ImagesPlugin; +import io.noties.markwon.recycler.MarkwonAdapter; +import io.noties.markwon.recycler.SimpleEntry; +import io.noties.markwon.recycler.table.TableEntry; +import io.noties.markwon.recycler.table.TableEntryPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184101750", + title = "RecyclerView", + description = "Usage with `RecyclerView`", + artifacts = {MarkwonArtifact.RECYCLER, MarkwonArtifact.RECYCLER_TABLE}, + tags = Tags.recyclerView +) +public class RecyclerSample extends MarkwonRecyclerViewSample { + @Override + public void render() { + final String md = SampleUtilsKtKt.loadReadMe(context); + + final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) + .usePlugin(TableEntryPlugin.create(context)) + .usePlugin(HtmlPlugin.create()) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(TaskListPlugin.create(context)) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.imageDestinationProcessor(new GithubImageDestinationProcessor()); + } + + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> { + // we actually won't be applying code spans here, as our custom view will + // draw background and apply mono typeface + // + // NB the `trim` operation on literal (as code will have a new line at the end) + final CharSequence code = visitor.configuration() + .syntaxHighlight() + .highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim()); + visitor.builder().append(code); + }); + } + }) + .build(); + + final MarkwonAdapter adapter = MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_node) + .include(FencedCodeBlock.class, SimpleEntry.create(R.layout.adapter_node_code_block, R.id.text_view)) + .include(TableBlock.class, TableEntry.create(builder -> { + builder + .tableLayout(R.layout.adapter_node_table_block, R.id.table_layout) + .textLayoutIsRoot(R.layout.view_table_entry_cell); + })) + .build(); + + recyclerView.setLayoutManager(new LinearLayoutManager(context)); + recyclerView.setAdapter(adapter); + + adapter.setMarkdown(markwon, md); + adapter.notifyDataSetChanged(); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/SimpleExtensionSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/SimpleExtensionSample.java new file mode 100644 index 00000000..9ab1484a --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/SimpleExtensionSample.java @@ -0,0 +1,47 @@ +package io.noties.markwon.app.samples; + +import android.graphics.Color; +import android.text.style.ForegroundColorSpan; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.core.spans.EmphasisSpan; +import io.noties.markwon.core.spans.StrongEmphasisSpan; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; +import io.noties.markwon.simple.ext.SimpleExtPlugin; + +@MarkwonSampleInfo( + id = "202006182194335", + title = "Delimiter processor simple-ext", + description = "Custom delimiter processor implemented with a `SimpleExtPlugin`", + artifacts = MarkwonArtifact.SIMPLE_EXT, + tags = Tags.parsing +) +public class SimpleExtensionSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# SimpleExt\n" + + "\n" + + "+let's start with `+`, ??then we can use this, and finally @@this$$??+"; + ; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(SimpleExtPlugin.create(plugin -> { + plugin + .addExtension(1, '+', (configuration, props) -> new EmphasisSpan()) + .addExtension(2, '?', (configuration, props) -> new StrongEmphasisSpan()) + .addExtension( + 2, + '@', + '?', + (configuration, props) -> new ForegroundColorSpan(Color.RED) + ); + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/SoftBreakAddsNewLineSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/SoftBreakAddsNewLineSample.java index 60abe1a7..6bd0e6e3 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/SoftBreakAddsNewLineSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/SoftBreakAddsNewLineSample.java @@ -14,7 +14,7 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo; artifacts = MarkwonArtifact.CORE, tags = {Tags.newLine, Tags.softBreak} ) -class SoftBreakAddsNewLineSample extends MarkwonTextViewSample { +public class SoftBreakAddsNewLineSample extends MarkwonTextViewSample { @Override public void render() { final String md = "" + diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalEditSpan.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalEditSpan.java index 565593a2..42505f8d 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalEditSpan.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalEditSpan.java @@ -9,7 +9,7 @@ import androidx.annotation.NonNull; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.core.spans.StrongEmphasisSpan; import io.noties.markwon.editor.AbstractEditHandler; import io.noties.markwon.editor.MarkwonEditor; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalPluginSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalPluginSample.java index fc052264..06a17998 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalPluginSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorAdditionalPluginSample.java @@ -2,7 +2,7 @@ package io.noties.markwon.app.samples.editor; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.MarkwonEditorTextWatcher; import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorCustomPunctuationSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorCustomPunctuationSample.java index 097947e4..325d7bf2 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorCustomPunctuationSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorCustomPunctuationSample.java @@ -4,7 +4,7 @@ import android.text.style.ForegroundColorSpan; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.MarkwonEditorTextWatcher; import io.noties.markwon.sample.annotations.MarkwonArtifact; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorHeadingSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorHeadingSample.java index ed9c34b3..31bd03bc 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorHeadingSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorHeadingSample.java @@ -4,7 +4,7 @@ import java.util.concurrent.Executors; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.app.samples.editor.shared.HeadingEditHandler; import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.MarkwonEditorTextWatcher; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorMultipleEditSpansSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorMultipleEditSpansSample.java index 500ee8ee..5a8dc2f9 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorMultipleEditSpansSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorMultipleEditSpansSample.java @@ -13,7 +13,7 @@ import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.SoftBreakAddsNewLinePlugin; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.app.samples.editor.shared.BlockQuoteEditHandler; import io.noties.markwon.app.samples.editor.shared.CodeEditHandler; import io.noties.markwon.app.samples.editor.shared.LinkEditHandler; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorNewLineContinuationSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorNewLineContinuationSample.java index b1c36df9..b73b3b4c 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorNewLineContinuationSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorNewLineContinuationSample.java @@ -12,7 +12,7 @@ import java.util.regex.Pattern; import io.noties.debug.Debug; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.MarkwonEditorTextWatcher; import io.noties.markwon.sample.annotations.MarkwonArtifact; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorPreRenderSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorPreRenderSample.java index ff0c178d..f3ca8843 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorPreRenderSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorPreRenderSample.java @@ -4,7 +4,7 @@ import java.util.concurrent.Executors; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.MarkwonEditorTextWatcher; import io.noties.markwon.sample.annotations.MarkwonArtifact; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorSimpleSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorSimpleSample.java index f5a3c8bd..0e288ba6 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorSimpleSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/EditorSimpleSample.java @@ -2,7 +2,7 @@ package io.noties.markwon.app.samples.editor; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; -import io.noties.markwon.app.sample.ui.MarkwonEditTextSample; +import io.noties.markwon.app.samples.editor.shared.MarkwonEditTextSample; import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.MarkwonEditorTextWatcher; import io.noties.markwon.sample.annotations.MarkwonArtifact; diff --git a/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonEditTextSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/shared/MarkwonEditTextSample.kt similarity index 96% rename from app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonEditTextSample.kt rename to app-sample/src/main/java/io/noties/markwon/app/samples/editor/shared/MarkwonEditTextSample.kt index 0ea55776..fb90e293 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/sample/ui/MarkwonEditTextSample.kt +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/editor/shared/MarkwonEditTextSample.kt @@ -1,4 +1,4 @@ -package io.noties.markwon.app.sample.ui +package io.noties.markwon.app.samples.editor.shared import android.content.Context import android.text.SpannableStringBuilder @@ -9,6 +9,7 @@ import android.widget.Button import android.widget.EditText import android.widget.TextView import io.noties.markwon.app.R +import io.noties.markwon.app.sample.ui.MarkwonSample import io.noties.markwon.core.spans.EmphasisSpan import io.noties.markwon.core.spans.StrongEmphasisSpan import java.util.ArrayList @@ -19,7 +20,7 @@ abstract class MarkwonEditTextSample : MarkwonSample() { protected lateinit var editText: EditText override val layoutResId: Int - get() = R.layout.activity_edit_text + get() = R.layout.sample_edit_text override fun onViewCreated(view: View) { context = view.context diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/image/ErrorImageSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/image/ErrorImageSample.java index 2c798ed2..abaaec0b 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/image/ErrorImageSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/image/ErrorImageSample.java @@ -28,14 +28,15 @@ public class ErrorImageSample extends MarkwonTextViewSample { final Markwon markwon = Markwon.builder(context) // error handler additionally allows to log/inspect errors during image loading - .usePlugin(ImagesPlugin.create(plugin -> + .usePlugin(ImagesPlugin.create(plugin -> { plugin.errorHandler(new ImagesPlugin.ErrorHandler() { @Nullable @Override public Drawable handleError(@NonNull String url, @NonNull Throwable throwable) { return ContextCompat.getDrawable(context, R.drawable.ic_home_black_36dp); } - }))) + }); + })) .build(); markwon.setMarkdown(textView, md); diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/image/GifImageSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/image/GifImageSample.java index 7a8cad95..1356ea88 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/image/GifImageSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/image/GifImageSample.java @@ -23,8 +23,9 @@ public class GifImageSample extends MarkwonTextViewSample { final Markwon markwon = Markwon.builder(context) // GIF is handled by default if library is used in the app // .usePlugin(ImagesPlugin.create()) - .usePlugin(ImagesPlugin.create(plugin -> - plugin.addMediaDecoder(GifMediaDecoder.create()))) + .usePlugin(ImagesPlugin.create(plugin -> { + plugin.addMediaDecoder(GifMediaDecoder.create()); + })) .build(); markwon.setMarkdown(textView, md); diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/ImagesCustomSchemeSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/image/ImagesCustomSchemeSample.java similarity index 98% rename from app-sample/src/main/java/io/noties/markwon/app/samples/ImagesCustomSchemeSample.java rename to app-sample/src/main/java/io/noties/markwon/app/samples/image/ImagesCustomSchemeSample.java index f00c7315..d5e5a894 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/ImagesCustomSchemeSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/image/ImagesCustomSchemeSample.java @@ -1,4 +1,4 @@ -package io.noties.markwon.app.samples; +package io.noties.markwon.app.samples.image; import android.net.Uri; diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/image/PlaceholderImageSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/image/PlaceholderImageSample.java index c9195799..a633e8cb 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/image/PlaceholderImageSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/image/PlaceholderImageSample.java @@ -28,7 +28,7 @@ public class PlaceholderImageSample extends MarkwonTextViewSample { "![image](https://github.com/dcurtis/markdown-mark/raw/master/png/1664x1024-solid.png)"; final Markwon markwon = Markwon.builder(context) - .usePlugin(ImagesPlugin.create(plugin -> + .usePlugin(ImagesPlugin.create(plugin -> { plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() { @Nullable @Override @@ -37,7 +37,8 @@ public class PlaceholderImageSample extends MarkwonTextViewSample { // otherwise bounds can be applied explicitly return ContextCompat.getDrawable(context, R.drawable.ic_android_black_24dp); } - }))) + }); + })) .build(); markwon.setMarkdown(textView, md); diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/image/SvgImageSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/image/SvgImageSample.java index c18709d7..6f155d42 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/image/SvgImageSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/image/SvgImageSample.java @@ -25,11 +25,12 @@ public class SvgImageSample extends MarkwonTextViewSample { // libraries are in path (specified in dependencies block) // .usePlugin(ImagesPlugin.create()) // let's make it implicit - .usePlugin(ImagesPlugin.create(plugin -> + .usePlugin(ImagesPlugin.create(plugin -> { // there 2 svg media decoders: // - regular `SvgMediaDecoder` // - special one when SVG doesn't have width and height specified - `SvgPictureMediaDecoder` - plugin.addMediaDecoder(SvgPictureMediaDecoder.create()))) + plugin.addMediaDecoder(SvgPictureMediaDecoder.create()); + })) .build(); markwon.setMarkdown(textView, md); diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingDisableCodeSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingDisableCodeSample.java index 725e594f..2b4f2af4 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingDisableCodeSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingDisableCodeSample.java @@ -3,15 +3,11 @@ package io.noties.markwon.app.samples.inlineparsing; import androidx.annotation.NonNull; import org.commonmark.node.Block; -import org.commonmark.node.BlockQuote; -import org.commonmark.node.Heading; -import org.commonmark.node.HtmlBlock; -import org.commonmark.node.ListBlock; -import org.commonmark.node.ThematicBreak; +import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.IndentedCodeBlock; import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.Parser; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -19,6 +15,7 @@ import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.app.sample.Tags; import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.core.CorePlugin; import io.noties.markwon.inlineparser.BackticksInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParser; import io.noties.markwon.sample.annotations.MarkwonArtifact; @@ -49,16 +46,12 @@ public class InlineParsingDisableCodeSample extends MarkwonTextViewSample { .excludeInlineProcessor(BackticksInlineProcessor.class) .build(); - // unfortunately there is no _exclude_ method for parser-builder final Set> enabledBlocks = new HashSet>() {{ // IndentedCodeBlock.class and FencedCodeBlock.class are missing - // this is full list (including above) that can be passed to `enabledBlockTypes` method - addAll(Arrays.asList( - BlockQuote.class, - Heading.class, - HtmlBlock.class, - ThematicBreak.class, - ListBlock.class)); + addAll(CorePlugin.enabledBlockTypes()); + + remove(FencedCodeBlock.class); + remove(IndentedCodeBlock.class); }}; final Markwon markwon = Markwon.builder(context) diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingNoDefaultsSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingNoDefaultsSample.java index 7347c130..f7763cd2 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingNoDefaultsSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingNoDefaultsSample.java @@ -14,7 +14,8 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo; @MarkwonSampleInfo( id = "202006182170823", - title = "Inline parsing with defaults", + title = "Inline parsing no defaults", + description = "Parsing only inline code and disable all the rest", artifacts = MarkwonArtifact.INLINE_PARSER, tags = {Tags.inline, Tags.parsing} ) diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingTooltipSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingTooltipSample.java new file mode 100644 index 00000000..e1cbbf86 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingTooltipSample.java @@ -0,0 +1,202 @@ +package io.noties.markwon.app.samples.inlineparsing; + +import android.app.Activity; +import android.graphics.Point; +import android.text.Layout; +import android.text.Spannable; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.view.Gravity; +import android.view.View; +import android.view.Window; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.commonmark.node.CustomNode; +import org.commonmark.node.Node; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.inlineparser.InlineProcessor; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202006182195409", + title = "Tooltip with inline parser", + artifacts = MarkwonArtifact.INLINE_PARSER, + tags = {Tags.parsing, Tags.rendering} +) +public class InlineParsingTooltipSample extends MarkwonTextViewSample { + @Override + public void render() { + // NB! tooltip contents cannot have new lines + final String md = "" + + "\n" + + "\n" + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi vitae enim ut sem aliquet ultrices. Nunc a accumsan orci. Suspendisse tortor ante, lacinia ac scelerisque sed, dictum eget metus. Morbi ante augue, tristique eget quam in, vestibulum rutrum lacus. Nulla aliquam auctor cursus. Nulla at lacus condimentum, viverra lacus eget, sollicitudin ex. Cras efficitur leo dui, sit amet rutrum tellus venenatis et. Sed in facilisis libero. Etiam ultricies, nulla ut venenatis tincidunt, tortor erat tristique ante, non aliquet massa arcu eget nisl. Etiam gravida erat ante, sit amet lobortis mauris commodo nec. Praesent vitae sodales quam. Vivamus condimentum porta suscipit. Donec posuere id felis ac scelerisque. Vestibulum lacinia et leo id lobortis. Sed vitae dolor nec ligula dapibus finibus vel eu libero. Nam tincidunt maximus elit, sit amet tincidunt lacus laoreet malesuada.\n" + + "\n" + + "Aenean at urna leo. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla facilisi. Integer lectus elit, congue a orci sed, dignissim sagittis sem. Aenean et pretium magna, nec ornare justo. Sed quis nunc blandit, luctus justo eget, pellentesque arcu. Pellentesque porta semper tristique. Donec et odio arcu. Nullam ultrices gravida congue. Praesent vel leo sed orci tempor luctus. Vivamus eget tortor arcu. Nullam sapien nulla, iaculis sit amet semper in, mattis nec metus. In porttitor augue id elit euismod mattis. Ut est justo, dapibus suscipit erat eu, pellentesque porttitor magna.\n" + + "\n" + + "Nunc porta orci eget dictum malesuada. Donec vehicula felis sit amet leo tincidunt placerat. Cras quis elit faucibus, porta elit at, sodales tortor. Donec elit mi, eleifend et maximus vitae, pretium varius velit. Integer maximus egestas urna, at semper augue egestas vitae. Phasellus arcu tellus, tincidunt eget tellus nec, hendrerit mollis mauris. Pellentesque commodo urna quis nisi ultrices, quis vehicula felis ultricies. Vivamus eu feugiat leo.\n" + + "\n" + + "Etiam sit amet lorem et eros suscipit rhoncus a a tellus. Sed pharetra dui purus, quis molestie leo congue nec. Suspendisse sed scelerisque quam. Vestibulum non laoreet felis. Fusce interdum euismod purus at scelerisque. Vivamus tempus varius nibh, sed accumsan nisl interdum non. Pellentesque rutrum egestas eros sit amet sollicitudin. Vivamus ultrices est erat. Curabitur gravida justo non felis euismod mollis. Ut porta finibus nulla, sed pellentesque purus euismod ac.\n" + + "\n" + + "Aliquam erat volutpat. Nullam suscipit sit amet tortor vel fringilla. Nulla facilisi. Nullam lacinia ex lacus, sit amet scelerisque justo semper a. Nullam ullamcorper, erat ac malesuada porta, augue erat sagittis mi, in auctor turpis mauris nec orci. Nunc sit amet felis placerat, pharetra diam nec, dapibus metus. Proin nulla orci, iaculis vitae vulputate vel, placerat ac erat. Morbi sit amet blandit velit. Cras consectetur vehicula lacus vel sagittis. Nunc tincidunt lacus in blandit faucibus. Curabitur vestibulum auctor vehicula. Sed quis ligula sit amet quam venenatis venenatis eget id felis. Maecenas feugiat nisl elit, facilisis tempus risus malesuada quis. " + + "# Hello tooltip!\n\n" + + "This is the !{tooltip label}(and actual content comes here)\n\n" + + "what if it is !{here}(The contents can be blocks, limited though) instead?\n\n" + + "![image](#) anyway"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(MarkwonInlineParserPlugin.create(factoryBuilder -> + factoryBuilder.addInlineProcessor(new TooltipInlineProcessor()))) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(TooltipNode.class, (visitor, tooltipNode) -> { + final int start = visitor.length(); + visitor.builder().append(tooltipNode.label); + visitor.setSpans(start, new TooltipSpan(tooltipNode.contents)); + }); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } +} + +class TooltipInlineProcessor extends InlineProcessor { + + // NB! without bang + // `\\{` is required (although marked as redundant), without it - runtime crash + @SuppressWarnings("RegExpRedundantEscape") + private static final Pattern RE = Pattern.compile("\\{(.+?)\\}\\((.+?)\\)"); + + @Override + public char specialCharacter() { + return '!'; + } + + @Nullable + @Override + protected Node parse() { + final String match = match(RE); + if (match == null) { + return null; + } + + final Matcher matcher = RE.matcher(match); + if (matcher.matches()) { + final String label = matcher.group(1); + final String contents = matcher.group(2); + return new TooltipNode(label, contents); + } + + return null; + } +} + +class TooltipNode extends CustomNode { + final String label; + final String contents; + + TooltipNode(@NonNull String label, @NonNull String contents) { + this.label = label; + this.contents = contents; + } +} + +class TooltipSpan extends ClickableSpan { + final String contents; + + TooltipSpan(@NonNull String contents) { + this.contents = contents; + } + + @Override + public void onClick(@NonNull View widget) { + // just to be safe + if (!(widget instanceof TextView)) { + return; + } + + final TextView textView = (TextView) widget; + final Spannable spannable = (Spannable) textView.getText(); + + // find self ending position (can also obtain start) +// final int start = spannable.getSpanStart(this); + final int end = spannable.getSpanEnd(this); + + // weird, didn't find self + if (/*start < 0 ||*/ end < 0) { + return; + } + + final Layout layout = textView.getLayout(); + if (layout == null) { + // also weird + return; + } + + final int line = layout.getLineForOffset(end); + + // position inside TextView, these values must also be adjusted to parent widget + // also note that container can + final int y = layout.getLineBottom(line); + final int x = (int) (layout.getPrimaryHorizontal(end) + 0.5F); + + final Window window = ((Activity) widget.getContext()).getWindow(); + final View decor = window.getDecorView(); + final Point point = relativeTo(decor, widget); + +// new Tooltip.Builder(widget.getContext()) +// .anchor(x + point.x, y + point.y) +// .text(contents) +// .create() +// .show(widget, Tooltip.Gravity.TOP, false); + + // Toast is not reliable when tried to position on the screen + // but anyway, this is to showcase only + final Toast toast = Toast.makeText(widget.getContext(), contents, Toast.LENGTH_LONG); + toast.setGravity(Gravity.TOP | Gravity.START, x + point.x, y + point.y); + toast.show(); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + // can customize appearance here as spans will be rendered as links + super.updateDrawState(ds); + } + + @NonNull + private static Point relativeTo(@NonNull View parent, @NonNull View who) { + return relativeTo(parent, who, new Point()); + } + + @NonNull + private static Point relativeTo(@NonNull View parent, @NonNull View who, @NonNull Point point) { + // NB! the scroll adjustments (we are interested in screen position, + // not real position inside parent) + point.x += who.getLeft(); + point.y += who.getTop(); + point.x -= who.getScrollX(); + point.y -= who.getScrollY(); + if (who != parent + && who.getParent() instanceof View) { + relativeTo(parent, (View) who.getParent(), point); + } + return point; + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingWithDefaultsSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingWithDefaultsSample.java index a9fabd73..aca742e3 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingWithDefaultsSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/inlineparsing/InlineParsingWithDefaultsSample.java @@ -14,6 +14,7 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo; @MarkwonSampleInfo( id = "202006182170723", title = "Inline parsing with defaults", + description = "Parsing with all defaults except links", artifacts = MarkwonArtifact.INLINE_PARSER, tags = {Tags.inline, Tags.parsing} ) diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexBlockSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexBlockSample.java new file mode 100644 index 00000000..ed3cb4a9 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexBlockSample.java @@ -0,0 +1,33 @@ +package io.noties.markwon.app.samples.latex; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.latex.shared.LatexHolder; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202006182200257", + title = "LaTex block", + description = "Render LaTeX block", + artifacts = MarkwonArtifact.EXT_LATEX, + tags = {Tags.rendering} +) +public class LatexBlockSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# LaTeX\n" + + "$$\n" + + "" + LatexHolder.LATEX_ARRAY + "\n" + + "$$"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDarkSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDarkSample.java new file mode 100644 index 00000000..24298ce9 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDarkSample.java @@ -0,0 +1,37 @@ +package io.noties.markwon.app.samples.latex; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183094225", + title = "LaTeX dark", + description = "LaTeX automatically uses `TextView` text color " + + "if not configured explicitly", + artifacts = MarkwonArtifact.EXT_LATEX, + tags = Tags.rendering +) +public class LatexDarkSample extends MarkwonTextViewSample { + @Override + public void render() { + scrollView.setBackgroundColor(0xFF000000); + textView.setTextColor(0xFFffffff); + + final String md = "" + + "# LaTeX\n" + + "$$\n" + + "\\int \\frac{1}{x} dx = \\ln \\left| x \\right| + C\n" + + "$$\n" + + "text color is taken from text"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDefaultTextColorSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDefaultTextColorSample.java new file mode 100644 index 00000000..64d93f56 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDefaultTextColorSample.java @@ -0,0 +1,40 @@ +package io.noties.markwon.app.samples.latex; + +import android.graphics.Color; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.latex.shared.LatexHolder; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183120848", + title = "LaTeX default text color", + description = "LaTeX will use text color of `TextView` by default", + artifacts = MarkwonArtifact.EXT_LATEX, + tags = Tags.rendering +) +public class LatexDefaultTextColorSample extends MarkwonTextViewSample { + @Override + public void render() { + // @since 4.3.0 text color is automatically taken from textView + // (if it's not specified explicitly via configuration) + textView.setTextColor(Color.RED); + + final String md = "" + + "# LaTeX default text color\n" + + "$$\n" + + "" + LatexHolder.LATEX_LONG_DIVISION + "\n" + + "$$\n" + + ""; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize())) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDifferentTextSizesSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDifferentTextSizesSample.java new file mode 100644 index 00000000..085b1b38 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexDifferentTextSizesSample.java @@ -0,0 +1,42 @@ +package io.noties.markwon.app.samples.latex; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.latex.shared.LatexHolder; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183093504", + title = "LaTeX inline/block different text size", + artifacts = {MarkwonArtifact.EXT_LATEX, MarkwonArtifact.INLINE_PARSER}, + tags = {Tags.rendering} +) +public class LatexDifferentTextSizesSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# LaTeX different text sizes\n" + + "inline: " + LatexHolder.LATEX_BANGLE + ", okay and block:\n" + + "$$\n" + + "" + LatexHolder.LATEX_BANGLE + "\n" + + "$$\n" + + "that's it"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create( + textView.getTextSize() * 0.75F, + textView.getTextSize() * 1.50F, + builder -> { + builder.inlinesEnabled(true); + } + )) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexErrorSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexErrorSample.java new file mode 100644 index 00000000..1a6b9ecd --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexErrorSample.java @@ -0,0 +1,54 @@ +package io.noties.markwon.app.samples.latex; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import io.noties.debug.Debug; +import io.noties.markwon.Markwon; +import io.noties.markwon.app.R; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183122624", + title = "LaTeX error handling", + description = "Log error when parsing LaTeX and display error drawable", + artifacts = MarkwonArtifact.EXT_LATEX, + tags = Tags.rendering +) +public class LatexErrorSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# LaTeX with error\n" + + "$$\n" + + "\\sum_{i=0}^\\infty x \\cdot 0 \\rightarrow \\iMightNotExist{0}\n" + + "$$\n\n" + + "must **not** be rendered"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.inlinesEnabled(true); + //noinspection Convert2Lambda + builder.errorHandler(new JLatexMathPlugin.ErrorHandler() { + @Nullable + @Override + public Drawable handleError(@Nullable String latex, @NonNull Throwable error) { + Debug.e(error, latex); + return ContextCompat.getDrawable(context, R.drawable.ic_android_black_24dp); + } + }); + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexInlineSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexInlineSample.java new file mode 100644 index 00000000..b2aead2a --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexInlineSample.java @@ -0,0 +1,37 @@ +package io.noties.markwon.app.samples.latex; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.latex.shared.LatexHolder; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183085820", + title = "LaTeX inline", + description = "Display LaTeX inline", + artifacts = {MarkwonArtifact.EXT_LATEX, MarkwonArtifact.INLINE_PARSER}, + tags = Tags.rendering +) +public class LatexInlineSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# LaTeX inline\n" + + "hey = $$" + LatexHolder.LATEX_BANGLE + "$$,\n" + + "that's it!"; + + // inlines must be explicitly enabled and require `MarkwonInlineParserPlugin` + final Markwon markwon = Markwon.builder(context) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.inlinesEnabled(true); + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexLegacySample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexLegacySample.java new file mode 100644 index 00000000..6734c3a2 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexLegacySample.java @@ -0,0 +1,37 @@ +package io.noties.markwon.app.samples.latex; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.latex.shared.LatexHolder; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183090335", + title = "LaTeX blocks in legacy mode", + description = "Sample using _legacy_ LaTeX block parsing (pre `4.3.0` Markwon version)", + artifacts = MarkwonArtifact.EXT_LATEX, + tags = Tags.rendering +) +public class LatexLegacySample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# LaTeX legacy\n" + + "There are no inlines in previous versions, only blocks:\n" + + "$$\n" + + "" + LatexHolder.LATEX_BOXES + "\n" + + "$$\n" + + "yeah"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.blocksLegacy(true); + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexOmegaSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexOmegaSample.java new file mode 100644 index 00000000..727c4ef5 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexOmegaSample.java @@ -0,0 +1,38 @@ +package io.noties.markwon.app.samples.latex; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183090618", + title = "LaTeX omega symbol", + description = "Bug rendering omega symbol in LaTeX", + artifacts = {MarkwonArtifact.EXT_LATEX, MarkwonArtifact.INLINE_PARSER}, + tags = {Tags.rendering, Tags.knownBug} +) +public class LatexOmegaSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# Block\n\n" + + "$$\n" + + "\\Omega\n" + + "$$\n\n" + + "# Inline\n\n" + + "$$\\Omega$$"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.inlinesEnabled(true); + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexThemeSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexThemeSample.java new file mode 100644 index 00000000..0d4ef476 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/LatexThemeSample.java @@ -0,0 +1,53 @@ +package io.noties.markwon.app.samples.latex; + +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.latex.shared.LatexHolder; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.ext.latex.JLatexMathTheme; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183121528", + title = "LaTeX theme", + description = "Sample of theme customization for LaTeX", + artifacts = {MarkwonArtifact.EXT_LATEX, MarkwonArtifact.INLINE_PARSER}, + tags = Tags.rendering +) +public class LatexThemeSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# LaTeX theme\n" + + "Hello there $$" + LatexHolder.LATEX_BANGLE + "$$, how was it?" + + "Now, what about a _different_ approach and block:\n\n" + + "$$\n" + + "" + LatexHolder.LATEX_LONG_DIVISION + "\n" + + "$$\n\n" + + "Seems **fine**"; + + final int blockPadding = (int) (16 * context.getResources().getDisplayMetrics().density + 0.5F); + + final Markwon markwon = Markwon.builder(context) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> { + builder.inlinesEnabled(true); + builder.theme() + .inlineBackgroundProvider(() -> new ColorDrawable(0x200000ff)) + .inlineTextColor(Color.GREEN) + .blockBackgroundProvider(() -> new ColorDrawable(0x2000ff00)) + .blockPadding(JLatexMathTheme.Padding.all(blockPadding)) + .blockTextColor(Color.RED) + ; + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/latex/shared/LatexHolder.java b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/shared/LatexHolder.java new file mode 100644 index 00000000..c6d98903 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/latex/shared/LatexHolder.java @@ -0,0 +1,35 @@ +package io.noties.markwon.app.samples.latex.shared; + +public abstract class LatexHolder { + + public static final String LATEX_ARRAY; + public static final String LATEX_LONG_DIVISION = "\\text{A long division \\longdiv{12345}{13}"; + public static final String LATEX_BANGLE = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; + public static final String LATEX_BOXES; + + static { + String latex = "\\begin{array}{cc}"; + latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; + latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; + latex += "\\end{array}"; + LATEX_BOXES = latex; + } + + static { + String latex = "\\begin{array}{l}"; + latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; + latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; + latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; + latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; + latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; + latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; + latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; + latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; + latex += "\\end{array}"; + LATEX_ARRAY = latex; + } + + + private LatexHolder() { + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/notification/NotificationSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/notification/NotificationSample.java new file mode 100644 index 00000000..a27f22c5 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/notification/NotificationSample.java @@ -0,0 +1,84 @@ +package io.noties.markwon.app.samples.notification; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.style.BackgroundColorSpan; +import android.text.style.BulletSpan; +import android.text.style.QuoteSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; + +import androidx.annotation.NonNull; + +import org.commonmark.ext.gfm.strikethrough.Strikethrough; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.Code; +import org.commonmark.node.Emphasis; +import org.commonmark.node.ListItem; +import org.commonmark.node.StrongEmphasis; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.notification.shared.NotificationUtils; +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007183130729", + title = "Markdown in Notification", + description = "Proof of concept of using `Markwon` with `android.app.Notification`", + artifacts = MarkwonArtifact.CORE, + tags = Tags.hack +) +public class NotificationSample extends MarkwonTextViewSample { + @Override + public void render() { + // supports: + // * bold -> StyleSpan(BOLD) + // * italic -> StyleSpan(ITALIC) + // * quote -> QuoteSpan() + // * strikethrough -> StrikethroughSpan() + // * bullet list -> BulletSpan() + + // * link -> is styled but not clickable + // * code -> typeface monospace works, background is not + + final String md = "" + + "**bold _bold-italic_ bold** ~~strike~~ `code` [link](#)\n\n" + + "* bullet-one\n" + + "* * bullet-two\n" + + " * bullet-three\n\n" + + "> a quote\n\n" + + ""; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder + .setFactory(Emphasis.class, (configuration, props) -> new StyleSpan(Typeface.ITALIC)) + .setFactory(StrongEmphasis.class, (configuration, props) -> new StyleSpan(Typeface.BOLD)) + .setFactory(BlockQuote.class, (configuration, props) -> new QuoteSpan()) + .setFactory(Strikethrough.class, (configuration, props) -> new StrikethroughSpan()) + // NB! notification does not handle background color + .setFactory(Code.class, (configuration, props) -> new Object[]{ + new BackgroundColorSpan(Color.GRAY), + new TypefaceSpan("monospace") + }) + // NB! both ordered and bullet list items + .setFactory(ListItem.class, (configuration, props) -> new BulletSpan()); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + + NotificationUtils.display(context, markwon.toMarkdown(md)); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/notification/RemoteViewsSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/notification/RemoteViewsSample.java new file mode 100644 index 00000000..ddfad216 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/notification/RemoteViewsSample.java @@ -0,0 +1,93 @@ +package io.noties.markwon.app.samples.notification; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.style.BackgroundColorSpan; +import android.text.style.BulletSpan; +import android.text.style.QuoteSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; +import android.widget.RemoteViews; + +import androidx.annotation.NonNull; + +import org.commonmark.ext.gfm.strikethrough.Strikethrough; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.Code; +import org.commonmark.node.Emphasis; +import org.commonmark.node.Heading; +import org.commonmark.node.ListItem; +import org.commonmark.node.StrongEmphasis; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.app.R; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.app.samples.notification.shared.NotificationUtils; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184090140", + title = "RemoteViews in notification", + description = "Display markdown with platform (system) spans in notification via `RemoteViews`", + artifacts = MarkwonArtifact.CORE, + tags = Tags.hack +) +public class RemoteViewsSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# Heading 1\n" + +// "## Heading 2\n" + +// "### Heading 3\n" + +// "#### Heading 4\n" + +// "##### Heading 5\n" + +// "###### Heading 6\n" + + "**bold _italic_ bold** `code` [link](#) ~~strike~~\n" + + "* Bullet 1\n" + + "* * Bullet 2\n" + + " * Bullet 3\n" + + "> A quote **here**"; + + final float[] headingSizes = { + 2.F, 1.5F, 1.17F, 1.F, .83F, .67F, + }; + + final int bulletGapWidth = (int) (8 * context.getResources().getDisplayMetrics().density + 0.5F); + + final Markwon markwon = Markwon.builder(context) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder + .setFactory(Heading.class, (configuration, props) -> new Object[]{ + new StyleSpan(Typeface.BOLD), + new RelativeSizeSpan(headingSizes[CoreProps.HEADING_LEVEL.require(props) - 1]) + }) + .setFactory(StrongEmphasis.class, (configuration, props) -> new StyleSpan(Typeface.BOLD)) + .setFactory(Emphasis.class, (configuration, props) -> new StyleSpan(Typeface.ITALIC)) + .setFactory(Code.class, (configuration, props) -> new Object[]{ + new BackgroundColorSpan(Color.GRAY), + new TypefaceSpan("monospace") + }) + .setFactory(Strikethrough.class, (configuration, props) -> new StrikethroughSpan()) + .setFactory(ListItem.class, (configuration, props) -> new BulletSpan(bulletGapWidth)) + .setFactory(BlockQuote.class, (configuration, props) -> new QuoteSpan()); + } + }) + .build(); + + final RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.sample_remote_view); + remoteViews.setTextViewText(R.id.text_view, markwon.toMarkdown(md)); + + NotificationUtils.display(context, remoteViews); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/notification/shared/NotificationUtils.java b/app-sample/src/main/java/io/noties/markwon/app/samples/notification/shared/NotificationUtils.java new file mode 100644 index 00000000..97e7cffd --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/notification/shared/NotificationUtils.java @@ -0,0 +1,85 @@ +package io.noties.markwon.app.samples.notification.shared; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.widget.RemoteViews; + +import androidx.annotation.NonNull; + +import io.noties.markwon.app.R; + +public abstract class NotificationUtils { + + private static final int ID = 2; + private static final String CHANNEL_ID = "2"; + + public static void display(@NonNull Context context, @NonNull CharSequence cs) { + final NotificationManager manager = context.getSystemService(NotificationManager.class); + if (manager == null) { + return; + } + + ensureChannel(manager, CHANNEL_ID); + + final Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(R.drawable.ic_stat_name) + .setContentTitle(context.getString(R.string.app_name)) + .setContentText(cs) + .setStyle(new Notification.BigTextStyle().bigText(cs)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setChannelId(CHANNEL_ID); + } + + manager.notify(ID, builder.build()); + } + + public static void display(@NonNull Context context, @NonNull RemoteViews remoteViews) { + final NotificationManager manager = context.getSystemService(NotificationManager.class); + if (manager == null) { + return; + } + + ensureChannel(manager, CHANNEL_ID); + + final Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(R.drawable.ic_stat_name) + .setContentTitle(context.getString(R.string.app_name)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + builder + .setCustomContentView(remoteViews) + .setCustomBigContentView(remoteViews); + } else { + builder.setContent(remoteViews); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setChannelId(CHANNEL_ID); + } + + manager.notify(ID, builder.build()); + } + + @SuppressWarnings("SameParameterValue") + private static void ensureChannel(@NonNull NotificationManager manager, @NonNull String channelId) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return; + } + + final NotificationChannel channel = manager.getNotificationChannel(channelId); + if (channel == null) { + manager.createNotificationChannel(new NotificationChannel( + channelId, + channelId, + NotificationManager.IMPORTANCE_DEFAULT + )); + } + } + + private NotificationUtils() { + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/AnchorSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/AnchorSample.java index 5b0d555a..de5fe536 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/AnchorSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/AnchorSample.java @@ -1,10 +1,5 @@ package io.noties.markwon.app.samples.plugins; -import android.view.View; -import android.widget.ScrollView; - -import org.jetbrains.annotations.NotNull; - import io.noties.markwon.Markwon; import io.noties.markwon.app.R; import io.noties.markwon.app.sample.Tags; @@ -22,14 +17,6 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo; ) public class AnchorSample extends MarkwonTextViewSample { - private ScrollView scrollView; - - @Override - public void onViewCreated(@NotNull View view) { - scrollView = view.findViewById(R.id.scroll_view); - super.onViewCreated(view); - } - @Override public void render() { diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/TableOfContentsSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/TableOfContentsSample.java index 35f5ee66..4ef9f313 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/TableOfContentsSample.java +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/plugins/TableOfContentsSample.java @@ -1,8 +1,5 @@ package io.noties.markwon.app.samples.plugins; -import android.view.View; -import android.widget.ScrollView; - import androidx.annotation.NonNull; import org.commonmark.node.AbstractVisitor; @@ -13,7 +10,6 @@ import org.commonmark.node.Link; import org.commonmark.node.ListItem; import org.commonmark.node.Node; import org.commonmark.node.Text; -import org.jetbrains.annotations.NotNull; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; @@ -35,14 +31,6 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo; ) public class TableOfContentsSample extends MarkwonTextViewSample { - private ScrollView scrollView; - - @Override - public void onViewCreated(@NotNull View view) { - scrollView = view.findViewById(R.id.scroll_view); - super.onViewCreated(view); - } - @Override public void render() { final String lorem = context.getString(R.string.lorem); diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableCustomizeSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableCustomizeSample.java new file mode 100644 index 00000000..960a4f91 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableCustomizeSample.java @@ -0,0 +1,47 @@ +package io.noties.markwon.app.samples.table; + +import android.graphics.Color; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.tables.TablePlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; +import io.noties.markwon.utils.ColorUtils; +import io.noties.markwon.utils.Dip; + +@MarkwonSampleInfo( + id = "202007184135621", + title = "Customize table theme", + artifacts = MarkwonArtifact.EXT_TABLES, + tags = {Tags.theme} +) +public class TableCustomizeSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "| HEADER | HEADER | HEADER |\n" + + "|:----:|:----:|:----:|\n" + + "| 测试 | 测试 | 测试 |\n" + + "| 测试 | 测试 | 测测测12345试测试测试 |\n" + + "| 测试 | 测试 | 123445 |\n" + + "| 测试 | 测试 | (650) 555-1212 |\n" + + "| 测试 | 测试 | [link](#) |\n"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(TablePlugin.create(builder -> { + final Dip dip = Dip.create(context); + builder + .tableBorderWidth(dip.toPx(2)) + .tableBorderColor(Color.YELLOW) + .tableCellPadding(dip.toPx(4)) + .tableHeaderRowBackgroundColor(ColorUtils.applyAlpha(Color.RED, 80)) + .tableEvenRowBackgroundColor(ColorUtils.applyAlpha(Color.GREEN, 80)) + .tableOddRowBackgroundColor(ColorUtils.applyAlpha(Color.BLUE, 80)); + })) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableLatexSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableLatexSample.java new file mode 100644 index 00000000..2b12e2b6 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableLatexSample.java @@ -0,0 +1,45 @@ +package io.noties.markwon.app.samples.table; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.latex.JLatexMathPlugin; +import io.noties.markwon.ext.tables.TablePlugin; +import io.noties.markwon.image.ImagesPlugin; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184140041", + title = "LaTeX inside table", + description = "Usage of LaTeX formulas inside markdown tables", + artifacts = {MarkwonArtifact.EXT_LATEX, MarkwonArtifact.EXT_TABLES, MarkwonArtifact.IMAGE}, + tags = {Tags.image} +) +public class TableLatexSample extends MarkwonTextViewSample { + @Override + public void render() { + String latex = "\\begin{array}{cc}"; + latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; + latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; + latex += "\\end{array}"; + + final String md = "" + + "| HEADER | HEADER |\n" + + "|:----:|:----:|\n" + + "| ![Build](https://github.com/noties/Markwon/workflows/Build/badge.svg) | Build |\n" + + "| Stable | ![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable) |\n" + + "| BIG | $$" + latex + "$$ |\n" + + "\n"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(ImagesPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.inlinesEnabled(true))) + .usePlugin(TablePlugin.create(context)) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableLinkifySample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableLinkifySample.java new file mode 100644 index 00000000..112bcc5b --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableLinkifySample.java @@ -0,0 +1,42 @@ +package io.noties.markwon.app.samples.table; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.tables.TablePlugin; +import io.noties.markwon.linkify.LinkifyPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184135739", + title = "Linkify table", + description = "Automatically linkify markdown content " + + "including content inside tables", + artifacts = {MarkwonArtifact.EXT_TABLES, MarkwonArtifact.LINKIFY}, + tags = {Tags.links} +) +public class TableLinkifySample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "| HEADER | HEADER | HEADER |\n" + + "|:----:|:----:|:----:|\n" + + "| 测试 | 测试 | 测试 |\n" + + "| 测试 | 测试 | 测测测12345试测试测试 |\n" + + "| 测试 | 测试 | 123445 |\n" + + "| 测试 | 测试 | (650) 555-1212 |\n" + + "| 测试 | 测试 | [link](#) |\n" + + "\n" + + "测试\n" + + "\n" + + "[link link](https://link.link)"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(LinkifyPlugin.create()) + .usePlugin(TablePlugin.create(context)) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableWithImagesSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableWithImagesSample.java new file mode 100644 index 00000000..297c515b --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/table/TableWithImagesSample.java @@ -0,0 +1,36 @@ +package io.noties.markwon.app.samples.table; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.tables.TablePlugin; +import io.noties.markwon.image.ImagesPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +@MarkwonSampleInfo( + id = "202007184135932", + title = "Images inside table", + description = "Usage of images inside markdown tables", + artifacts = {MarkwonArtifact.EXT_TABLES, MarkwonArtifact.IMAGE}, + tags = Tags.image +) +public class TableWithImagesSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "| HEADER | HEADER |\n" + + "|:----:|:----:|\n" + + "| ![Build](https://github.com/noties/Markwon/workflows/Build/badge.svg) | Build |\n" + + "| Stable | ![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable) |\n" + + "| BIG | ![image](https://images.pexels.com/photos/41171/brussels-sprouts-sprouts-cabbage-grocery-41171.jpeg) |\n" + + "\n"; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) + .usePlugin(TablePlugin.create(context)) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListCustomColorsSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListCustomColorsSample.java new file mode 100644 index 00000000..306353d3 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListCustomColorsSample.java @@ -0,0 +1,34 @@ +package io.noties.markwon.app.samples.tasklist; + +import android.graphics.Color; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.tasklist.TaskListPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +import static io.noties.markwon.app.samples.tasklist.shared.TaskListHolder.MD; + +@MarkwonSampleInfo( + id = "202007184140536", + title = "GFM task list custom colors", + description = "Custom colors for task list extension", + artifacts = MarkwonArtifact.EXT_TASKLIST, + tags = Tags.parsing +) +public class TaskListCustomColorsSample extends MarkwonTextViewSample { + @Override + public void render() { + final int checkedFillColor = Color.RED; + final int normalOutlineColor = Color.GREEN; + final int checkMarkColor = Color.BLUE; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(TaskListPlugin.create(checkedFillColor, normalOutlineColor, checkMarkColor)) + .build(); + + markwon.setMarkdown(textView, MD); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListCustomDrawableSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListCustomDrawableSample.java new file mode 100644 index 00000000..fcd5aeb7 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListCustomDrawableSample.java @@ -0,0 +1,37 @@ +package io.noties.markwon.app.samples.tasklist; + +import android.graphics.drawable.Drawable; + +import androidx.core.content.ContextCompat; + +import java.util.Objects; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.R; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.tasklist.TaskListPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +import static io.noties.markwon.app.samples.tasklist.shared.TaskListHolder.MD; + +@MarkwonSampleInfo( + id = "202007184140749", + title = "GFM task list custom drawable", + artifacts = MarkwonArtifact.EXT_TASKLIST, + tags = Tags.plugin +) +public class TaskListCustomDrawableSample extends MarkwonTextViewSample { + @Override + public void render() { + final Drawable drawable = Objects.requireNonNull( + ContextCompat.getDrawable(context, R.drawable.custom_task_list)); + + final Markwon markwon = Markwon.builder(context) + .usePlugin(TaskListPlugin.create(drawable)) + .build(); + + markwon.setMarkdown(textView, MD); + } +} 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 new file mode 100644 index 00000000..c1a41e79 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListMutateSample.java @@ -0,0 +1,101 @@ +package io.noties.markwon.app.samples.tasklist; + +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import io.noties.debug.Debug; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.SpanFactory; +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.TaskListSpan; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +import static io.noties.markwon.app.samples.tasklist.shared.TaskListHolder.MD; + +@MarkwonSampleInfo( + id = "202007184140901", + title = "GFM task list mutate", + artifacts = MarkwonArtifact.EXT_TASKLIST, + tags = Tags.plugin +) +public class TaskListMutateSample extends MarkwonTextViewSample { + @Override + public void render() { + final Markwon markwon = Markwon.builder(context) + .usePlugin(TaskListPlugin.create(context)) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // obtain origin task-list-factory + final SpanFactory origin = builder.getFactory(TaskListItem.class); + if (origin == null) { + return; + } + + builder.setFactory(TaskListItem.class, (configuration, props) -> { + // maybe it's better to validate the actual type here also + // and not force cast to task-list-span + final TaskListSpan span = (TaskListSpan) origin.getSpans(configuration, props); + if (span == null) { + return null; + } + + // NB, toggle click will intercept possible links inside task-list-item + return new Object[]{ + span, + new TaskListToggleSpan(span) + }; + }); + } + }) + .build(); + + markwon.setMarkdown(textView, MD); + } +} + +class TaskListToggleSpan extends ClickableSpan { + + private final TaskListSpan 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(); + + // 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) + ); + + 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 + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListSample.java new file mode 100644 index 00000000..47a4d4df --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/TaskListSample.java @@ -0,0 +1,28 @@ +package io.noties.markwon.app.samples.tasklist; + +import io.noties.markwon.Markwon; +import io.noties.markwon.app.sample.Tags; +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; +import io.noties.markwon.ext.tasklist.TaskListPlugin; +import io.noties.markwon.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; + +import static io.noties.markwon.app.samples.tasklist.shared.TaskListHolder.MD; + +@MarkwonSampleInfo( + id = "202007184140352", + title = "GFM task list", + description = "Github Flavored Markdown (GFM) task list extension", + artifacts = MarkwonArtifact.EXT_TASKLIST, + tags = Tags.plugin +) +public class TaskListSample extends MarkwonTextViewSample { + @Override + public void render() { + final Markwon markwon = Markwon.builder(context) + .usePlugin(TaskListPlugin.create(context)) + .build(); + + markwon.setMarkdown(textView, MD); + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/shared/TaskListHolder.java b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/shared/TaskListHolder.java new file mode 100644 index 00000000..19f4b7a7 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/tasklist/shared/TaskListHolder.java @@ -0,0 +1,17 @@ +package io.noties.markwon.app.samples.tasklist.shared; + +public abstract class TaskListHolder { + + public static final String MD = "" + + "- [ ] Not done here!\n" + + "- [x] and done\n" + + "- [X] and again!\n" + + "* [ ] **and** syntax _included_ `code`\n" + + "- [ ] [link](#)\n" + + "- [ ] [a check box](https://examp.le)\n" + + "- [x] [test]()\n" + + "- [List](https://examp.le) 3"; + + private TaskListHolder() { + } +} diff --git a/app-sample/src/main/res/drawable/custom_task_list.xml b/app-sample/src/main/res/drawable/custom_task_list.xml new file mode 100644 index 00000000..43c2e2a8 --- /dev/null +++ b/app-sample/src/main/res/drawable/custom_task_list.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app-sample/src/main/res/drawable/ic_stat_name.xml b/app-sample/src/main/res/drawable/ic_stat_name.xml new file mode 100644 index 00000000..a24cc224 --- /dev/null +++ b/app-sample/src/main/res/drawable/ic_stat_name.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/app-sample/src/main/res/layout/adapter_appcompat_default_entry.xml b/app-sample/src/main/res/layout/adapter_appcompat_default_entry.xml new file mode 100644 index 00000000..d4cb2745 --- /dev/null +++ b/app-sample/src/main/res/layout/adapter_appcompat_default_entry.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app-sample/src/main/res/layout/activity_edit_text.xml b/app-sample/src/main/res/layout/sample_edit_text.xml similarity index 100% rename from app-sample/src/main/res/layout/activity_edit_text.xml rename to app-sample/src/main/res/layout/sample_edit_text.xml diff --git a/app-sample/src/main/res/layout/sample_recycler_view.xml b/app-sample/src/main/res/layout/sample_recycler_view.xml new file mode 100644 index 00000000..1096ae44 --- /dev/null +++ b/app-sample/src/main/res/layout/sample_recycler_view.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app-sample/src/main/res/layout/sample_remote_view.xml b/app-sample/src/main/res/layout/sample_remote_view.xml new file mode 100644 index 00000000..5062f7be --- /dev/null +++ b/app-sample/src/main/res/layout/sample_remote_view.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app-sample/src/main/res/layout/sample_text_view.xml b/app-sample/src/main/res/layout/sample_text_view.xml index 5ff75fc9..21c2cb3e 100644 --- a/app-sample/src/main/res/layout/sample_text_view.xml +++ b/app-sample/src/main/res/layout/sample_text_view.xml @@ -13,6 +13,7 @@ android:id="@+id/text_view" android:layout_width="match_parent" android:layout_height="wrap_content" + android:lineSpacingExtra="2dip" android:padding="@dimen/content_padding_double" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" diff --git a/art/markwon-icon-foreground-stroked.svg b/art/markwon-icon-foreground-stroked.svg new file mode 100644 index 00000000..4bf8e70c --- /dev/null +++ b/art/markwon-icon-foreground-stroked.svg @@ -0,0 +1,95 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java index d882e9c1..5e97fd96 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathTheme.java @@ -81,6 +81,14 @@ public abstract class JLatexMathTheme { public static Padding symmetric(int vertical, int horizontal) { return new Padding(horizontal, vertical, horizontal, vertical); } + + /** + * @since $nap; + */ + @NonNull + public static Padding of(int left, int top, int right, int bottom) { + return new Padding(left, top, right, bottom); + } } /** @@ -175,6 +183,10 @@ public abstract class JLatexMathTheme { return this; } + /** + * Configure if `LaTeX` formula should take all available widget width. + * By default - `true` + */ @NonNull public Builder blockFitCanvas(boolean blockFitCanvas) { this.blockFitCanvas = blockFitCanvas;