diff --git a/.travis.yml b/.travis.yml index ec7e3e53..b77bdebc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ # https://docs.travis-ci.com/user/languages/android/ + language: android + +# so, out of blue travis requires this now (without it build would not even execute, immediate failure when downloading jdk) +dist: trusty jdk: openjdk8 sudo: false diff --git a/docs/.vuepress/.artifacts.js b/docs/.vuepress/.artifacts.js index 2f41fc13..a0f2a8de 100644 --- a/docs/.vuepress/.artifacts.js +++ b/docs/.vuepress/.artifacts.js @@ -1,4 +1,4 @@ // this is a generated file, do not modify. To update it run 'collectArtifacts.js' script -const artifacts = [{"id":"core","name":"Core","group":"io.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"io.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"io.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"io.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"io.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"io.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image","name":"Image","group":"io.noties.markwon","description":"Markwon image loading module (with optional GIF and SVG support)"},{"id":"image-gif","name":"Image GIF","group":"io.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-glide","name":"Image Glide","group":"io.noties.markwon","description":"Markwon image loading module (based on Glide library)"},{"id":"image-okhttp","name":"Image OkHttp","group":"io.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-picasso","name":"Image Picasso","group":"io.noties.markwon","description":"Markwon image loading module (based on Picasso library)"},{"id":"image-svg","name":"Image SVG","group":"io.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"linkify","name":"Linkify","group":"io.noties.markwon","description":"Markwon plugin to linkify text (based on Android Linkify)"},{"id":"recycler","name":"Recycler","group":"io.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"io.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"io.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}]; +const artifacts = [{"id":"core","name":"Core","group":"io.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"io.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"io.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"io.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"io.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"io.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image","name":"Image","group":"io.noties.markwon","description":"Markwon image loading module (with optional GIF and SVG support)"},{"id":"image-glide","name":"Image Glide","group":"io.noties.markwon","description":"Markwon image loading module (based on Glide library)"},{"id":"image-picasso","name":"Image Picasso","group":"io.noties.markwon","description":"Markwon image loading module (based on Picasso library)"},{"id":"linkify","name":"Linkify","group":"io.noties.markwon","description":"Markwon plugin to linkify text (based on Android Linkify)"},{"id":"recycler","name":"Recycler","group":"io.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"io.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"io.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}]; export { artifacts }; diff --git a/docs/.vuepress/components/MavenBadge4.vue b/docs/.vuepress/components/MavenBadge4.vue new file mode 100644 index 00000000..4a8354cd --- /dev/null +++ b/docs/.vuepress/components/MavenBadge4.vue @@ -0,0 +1,24 @@ + + + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 91f11d14..eaa57168 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -54,6 +54,17 @@ module.exports = { '/docs/v4/core/render-props.md' ] }, + '/docs/v4/ext-latex/', + '/docs/v4/ext-strikethrough/', + '/docs/v4/ext-tables/', + '/docs/v4/ext-tasklist/', + '/docs/v4/html/', + '/docs/v4/image/', + '/docs/v4/image-glide/', + '/docs/v4/image-picasso/', + '/docs/v4/recycler/', + '/docs/v4/recycler-table/', + '/docs/v4/syntax-highlight/', '/docs/v4/recipes.md', '/docs/v4/migration-3-4.md' ], diff --git a/docs/docs/v2/README.md b/docs/docs/v2/README.md index 20275911..d237a0a1 100644 --- a/docs/docs/v2/README.md +++ b/docs/docs/v2/README.md @@ -7,6 +7,8 @@ title: 'Overview'

+ + **Markwon** is a markdown library for Android. It parses markdown following with the help of amazing library and renders result as _Android-native_ Spannables. **No HTML** is involved diff --git a/docs/docs/v2/configure.md b/docs/docs/v2/configure.md index 4ba81749..48a973fc 100644 --- a/docs/docs/v2/configure.md +++ b/docs/docs/v2/configure.md @@ -1,5 +1,7 @@ # Configuration + + `SpannableConfiguration` is the core component that controls how markdown is parsed and rendered. It can be obtained via factory methods: diff --git a/docs/docs/v2/factory.md b/docs/docs/v2/factory.md index edf4a018..3ad083b0 100644 --- a/docs/docs/v2/factory.md +++ b/docs/docs/v2/factory.md @@ -1,5 +1,7 @@ # Factory + + `SpannableFactory` is used to create Span implementations. ```java diff --git a/docs/docs/v2/getting-started.md b/docs/docs/v2/getting-started.md index 3361767a..f77b9ab9 100644 --- a/docs/docs/v2/getting-started.md +++ b/docs/docs/v2/getting-started.md @@ -1,5 +1,7 @@ # Getting started + + ## Quick one This is the most simple way to set markdown to a `TextView` or any of its siblings: diff --git a/docs/docs/v2/html.md b/docs/docs/v2/html.md index 6130db36..8a6fc26d 100644 --- a/docs/docs/v2/html.md +++ b/docs/docs/v2/html.md @@ -1,5 +1,7 @@ # HTML + + Starting with version `2.0.0` `Markwon` brings the whole HTML parsing/rendering stack _on-site_. The main reason for this are _special_ definitions of HTML nodes by . More specifically: diff --git a/docs/docs/v2/image-loader.md b/docs/docs/v2/image-loader.md index 6dca5991..68d1a881 100644 --- a/docs/docs/v2/image-loader.md +++ b/docs/docs/v2/image-loader.md @@ -1,5 +1,7 @@ # Images + + By default `Markwon` doesn't handle images. Although `AsyncDrawable.Loader` is defined in main artifact, it does not provide implementation. diff --git a/docs/docs/v2/install.md b/docs/docs/v2/install.md index efb9ef2a..d2edb82a 100644 --- a/docs/docs/v2/install.md +++ b/docs/docs/v2/install.md @@ -1,5 +1,7 @@ # Installation + + In order to start using `Markwon` add this to your dependencies block diff --git a/docs/docs/v2/syntax-highlight.md b/docs/docs/v2/syntax-highlight.md index 854b82b0..a65e72f7 100644 --- a/docs/docs/v2/syntax-highlight.md +++ b/docs/docs/v2/syntax-highlight.md @@ -1,5 +1,7 @@ # Syntax highlight + + This is a simple module to add **syntax highlight** functionality to your markdown rendered with `Markwon` library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance. diff --git a/docs/docs/v2/theme.md b/docs/docs/v2/theme.md index 292e3e39..07aeb26d 100644 --- a/docs/docs/v2/theme.md +++ b/docs/docs/v2/theme.md @@ -1,5 +1,7 @@ # Theme + + Here is the list of properties that can be configured via `SpannableTheme`. If you wish to control what is out of this list, you can use [SpannableFactory](/docs/v2/factory.md) abstraction which lets you to gather full control of Spans that are used to display markdown. diff --git a/docs/docs/v2/view.md b/docs/docs/v2/view.md index bd610344..c8489d94 100644 --- a/docs/docs/v2/view.md +++ b/docs/docs/v2/view.md @@ -1,5 +1,7 @@ # MarkwonView + + This is simple library containing 2 views that are able to display markdown: diff --git a/docs/docs/v4/ext-latex/README.md b/docs/docs/v4/ext-latex/README.md new file mode 100644 index 00000000..4d57371f --- /dev/null +++ b/docs/docs/v4/ext-latex/README.md @@ -0,0 +1,49 @@ +# LaTeX extension + + + +This is an extension that will help you display LaTeX formulas in your markdown. +Syntax is pretty simple: pre-fix and post-fix your latex with `$$` (double dollar sign). +`$$` should be the first characters in a line. + +```markdown +$$ +\\text{A long division \\longdiv{12345}{13} +$$ +``` + +```markdown +$$\\text{A long division \\longdiv{12345}{13}$$ +``` + +```java +Markwon.builder(context) + .use(JLatexMathPlugin.create(textSize)) + .build(); +``` + +This extension uses [jlatexmath-android](https://github.com/noties/jlatexmath-android) artifact to create LaTeX drawable. + +## Config + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(JLatexMathPlugin.create(textSize, new BuilderConfigure() { + @Override + public void configureBuilder(@NonNull Builder builder) { + builder + .background(backgroundDrawable) + .align(JLatexMathDrawable.ALIGN_CENTER) + .fitCanvas(true) + .padding(paddingPx) + // @since 4.0.0 - optional, by default cached-thread-pool will be used + .executorService(Executors.newCachedThreadPool()); + } + })) + .build(); +``` + + +:::tip +Since `JLatexMathPlugin` operates independently of `ImagesPlugin` +::: \ No newline at end of file diff --git a/docs/docs/v4/ext-strikethrough/README.md b/docs/docs/v4/ext-strikethrough/README.md new file mode 100644 index 00000000..516b5b98 --- /dev/null +++ b/docs/docs/v4/ext-strikethrough/README.md @@ -0,0 +1,29 @@ +# Strikethrough extension + + + +This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`: + +```java +Markwon.builder(context) + .usePlugin(StrikethroughPlugin.create()) +``` + +This plugin registers `SpanFactory` for `Strikethrough` node, so it's possible to customize Strikethrough Span that is used in rendering: + +```java +Markwon.builder(context) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Strikethrough.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + // will use Underline span instead of Strikethrough + return new UnderlineSpan(); + } + }); + } + }) +``` diff --git a/docs/docs/v4/ext-tables/README.md b/docs/docs/v4/ext-tables/README.md new file mode 100644 index 00000000..04df9c5e --- /dev/null +++ b/docs/docs/v4/ext-tables/README.md @@ -0,0 +1,99 @@ +# Tables extension + + + +This extension adds support for GFM tables. + +```java +final Markwon markwon = Markwon.builder(context) + // create default instance of TablePlugin + .usePlugin(TablePlugin.create(context)) +``` + +```java +final TableTheme tableTheme = TableTheme.builder() + .tableBorderColor(Color.RED) + .tableBorderWidth(0) + .tableCellPadding(0) + .tableHeaderRowBackgroundColor(Color.BLACK) + .tableEvenRowBackgroundColor(Color.GREEN) + .tableOddRowBackgroundColor(Color.YELLOW) + .build(); + +final Markwon markwon = Markwon.builder(context) + .usePlugin(TablePlugin.create(tableTheme)) +``` + +```java +Markwon.builder(context) + .usePlugin(TablePlugin.create(builder -> + builder + .tableBorderColor(Color.RED) + .tableBorderWidth(0) + .tableCellPadding(0) + .tableHeaderRowBackgroundColor(Color.BLACK) + .tableEvenRowBackgroundColor(Color.GREEN) + .tableOddRowBackgroundColor(Color.YELLOW) +)) +``` + +Please note, that _by default_ tables have limitations. For example, there is no support +for images inside table cells. And table contents won't be copied to clipboard if a TextView +has such functionality. Table will always take full width of a TextView in which it is displayed. +All columns will always be of the same width. So, _default_ implementation provides basic +functionality which can answer some needs. These all come from the limited nature of the TextView +to display such content. + +In order to provide full-fledged experience, tables must be displayed in a special widget. +Since version `3.0.0` Markwon provides a special artifact `markwon-recycler` that allows +to render markdown in a set of widgets in a RecyclerView. It also gives ability to change +display widget form TextView to any other. + +```java +final Table table = Table.parse(Markwon, TableBlock); +myTableWidget.setTable(table); +``` + +:::tip +To take advantage of this functionality and render tables without limitations (including +horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v3/recycler-table/) +module documentation that adds support for rendering `TableBlock` markdown node inside Android-native `TableLayout` widget. +::: + +## Theme + +### Cell padding + +Padding inside a table cell + + + +### Border color + +The color of table borders + + + +### Border width + +The width of table borders + + + +### Odd row background + +Background of an odd table row + + + +### Even row background + +Background of an even table row + + + +### Header row background + +Background of header table row + + diff --git a/docs/docs/v4/ext-tasklist/README.md b/docs/docs/v4/ext-tasklist/README.md new file mode 100644 index 00000000..154ec743 --- /dev/null +++ b/docs/docs/v4/ext-tasklist/README.md @@ -0,0 +1,146 @@ +# Task list extension + + + +Adds support for GFM (Github-flavored markdown) task-lists: + +```java +Markwon.builder(context) + .usePlugin(TaskListPlugin.create(context)); +``` + +--- + +Create a default instance of `TaskListPlugin` with `TaskListDrawable` initialized to use +`android.R.attr.textColorLink` as primary color and `android.R.attr.colorBackground` as background +```java +TaskListPlugin.create(context); +``` + +--- + +Create an instance of `TaskListPlugin` with exact color values to use: +```java +// obtain color values +final int checkedFillColor = /* */; +final int normalOutlineColor = /* */; +final int checkMarkColor = /* */; + +TaskListPlugin.create(checkedFillColor, normalOutlineColor, checkMarkColor); +``` + +--- + +Specify own drawable for a task list item: + +```java +// obtain drawable +final Drawable drawable = /* */; + +TaskListPlugin.create(drawable); +``` + +:::warning +Please note that custom drawable for a task list item must correctly handle state +in order to display done/not-done: + +```java +public class MyTaskListDrawable extends Drawable { + + private boolean isChecked; + + @Override + public void draw(@NonNull Canvas canvas) { + // draw accordingly to the isChecked value + } + + /* implementation omitted */ + + @Override + protected boolean onStateChange(int[] state) { + final boolean isChecked = contains(state, android.R.attr.state_checked); + final boolean result = this.isChecked != isChecked; + if (result) { + this.isChecked = isChecked; + } + return result; + } + + private static boolean contains(@Nullable int[] states, int value) { + if (states != null) { + for (int state : states) { + if (state == value) { + // NB return here + return true; + } + } + } + return false; + } +} +``` +::: + +## Task list mutation + +It is possible to mutate task list item state (toggle done/not-done). But note +that `Markwon` won't handle state change internally by any means and this change +is merely a visual one. If you need to persist state of a task list +item change you have to implement it yourself. This should get your started: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(TaskListPlugin.create(context)) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + + // obtain original SpanFactory set by TaskListPlugin + final SpanFactory origin = builder.getFactory(TaskListItem.class); + if (origin == null) { + // or throw, as it's a bit weird state and we expect + // this factory to be present + return; + } + + builder.setFactory(TaskListItem.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + // it's a bit non-secure behavior and we should validate + // the type of returned span first, but for the sake of brevity + // we skip this step + final TaskListSpan span = (TaskListSpan) origin.getSpans(configuration, props); + + if (span == null) { + // or throw + return null; + } + + // return an array of spans + return new Object[]{ + span, + new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + // toggle VISUAL state + span.setDone(!span.isDone()); + + // do not forget to invalidate widget + widget.invalidate(); + + // execute your persistence logic + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + // no-op, so appearance is not changed (otherwise + // task list item will look like a link) + } + } + }; + } + }); + } + }) + .build(); +``` \ No newline at end of file diff --git a/docs/docs/v4/html/README.md b/docs/docs/v4/html/README.md new file mode 100644 index 00000000..44b95c54 --- /dev/null +++ b/docs/docs/v4/html/README.md @@ -0,0 +1,107 @@ +# HTML + + + +This artifact encapsulates HTML parsing from the core artifact and provides +few predefined `TagHandlers` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(HtmlPlugin.create()) + .build(); +``` + +As this artifact brings modified [jsoup](https://github.com/jhy/jsoup) library +it was moved to a standalone module in order to minimize dependencies and unused code +in applications that does not require HTML render capabilities. + +Before `Markwon` used android `Html` class for parsing and +rendering. Unfortunately, according to markdown specification, markdown can contain +HTML in _unpredictable_ way if rendered _outside_ of browser. For example: + +```markdown{4} + +Hello from italics tag + +bold> +``` + +This snippet could be represented as: +* HtmlBlock (`\nHello from italics tag`) +* HtmlInline (``) +* HtmlInline (``) +* Text (`bold`) +* HtmlInline (``) + +:::tip A bit of background +
+ had brought attention to differences between HTML & commonmark implementations.

+::: + +Unfortunately Android `HTML` class cannot parse a _fragment_ of HTML to later +be included in a bigger set of content. This is why the decision was made to bring +HTML parsing _in-markwon-house_ + +## Predefined TagHandlers +* `` +* `` +* `
` +* `` +* `` +* `, ` +* `, ` +* `, ` +* `
    ,
      ` +* `, , , ` +* `

      ,

      ,

      ,

      ,

      ,
      ` + +:::tip +All predefined tag handlers will use styling spans for native markdown content. +So, if your `Markwon` instance was configured to, for example, render Emphasis +nodes as a red text then HTML tag handler will +use the same span. This includes images, links, UrlResolver, LinkProcessor, etc +::: + +--- + +Staring with you can exclude all default tag handlers: + +```java +.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() { + @Override + public void configureHtml(@NonNull HtmlPlugin plugin) { + plugin.excludeDefaults(true); + } +})) +``` + +or via plugin: + +```java +.usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(HtmlPlugin.class, new Action() { + @Override + public void apply(@NonNull HtmlPlugin htmlPlugin) { + htmlPlugin.excludeDefaults(true); + } + }); + } +}) +``` + +If you wish to exclude some of them `TagHandlerNoOp` can be used: + +```java +.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() { + @Override + public void configureHtml(@NonNull HtmlPlugin plugin) { + plugin.addHandler(TagHandlerNoOp.create("h4", "h5", "h6", "img")); + } +})) +``` + +## TagHandler + + diff --git a/docs/docs/v4/image-glide/README.md b/docs/docs/v4/image-glide/README.md new file mode 100644 index 00000000..5d733b87 --- /dev/null +++ b/docs/docs/v4/image-glide/README.md @@ -0,0 +1,3 @@ +# Image Glide + + \ No newline at end of file diff --git a/docs/docs/v4/image-picasso/README.md b/docs/docs/v4/image-picasso/README.md new file mode 100644 index 00000000..852ff0e5 --- /dev/null +++ b/docs/docs/v4/image-picasso/README.md @@ -0,0 +1,3 @@ +# Image Picasso + + \ No newline at end of file diff --git a/docs/docs/v4/image/README.md b/docs/docs/v4/image/README.md new file mode 100644 index 00000000..8731af05 --- /dev/null +++ b/docs/docs/v4/image/README.md @@ -0,0 +1,172 @@ +# Image + + + +In order to display images in your markdown `ImagesPlugin` can be used. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) +``` + +:::tip +There are also modules that add image loading capabilities to markdown +based on image-loading libraries: [image-glide](/docs/v4/image-glide/) and +[image-picasso](/docs/v4/image-picasso/) +::: + +`ImagesPlugin` splits the image-loading into 2 parts: scheme-handling and media-decoding. + +## SchemeHandler + +To add a scheme-handler to `ImagesPlugin`: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(ImagesPlugin.class, new Action() { + @Override + public void apply(@NonNull ImagesPlugin imagesPlugin) { + imagesPlugin.addSchemeHandler(DataUriSchemeHandler.create()); + } + }); + } + }) +``` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + plugin.addSchemeHandler(DataUriSchemeHandler.create()); + } + })) +``` + +`ImagesPlugin` comes with a set of predefined scheme-handlers: +* `FileSchemeHandler` - `file://` +* `DataUriSchemeHandler` - `data:` +* `NetworkSchemeHandler` - `http`, `https` +* `OkHttpNetworkSchemeHandler` - `http`, `https` + +### FileSchemeHandler + +Loads images via `file://` scheme. Allows loading images from `assets` folder. + +```java +// default implementation, no assets handling +FileSchemeHandler.create(); + +// assets loading +FileSchemeHandler.createWithAssets(context); +``` + +:::warning +Assets loading will work when your URL will include `android_asset` the the path, +for example: `file:///android_asset/image.png` (mind the 3 slashes `///`). If you wish +to _assume_ all images without proper scheme to point to assets folder, then you can use +[UrlProcessorAndroidAssets](/docs/v4/core/configuration.html#urlprocessorandroidassets) +::: + +By default `ImagesPlugin` includes _plain_ `FileSchemeHandler` (without assets support), +so if you wish to change that you can explicitly specify it: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + plugin.addSchemeHandler(FileSchemeHandler.createWithAssets(context)); + } + })) +``` + +### DataUriSchemeHandler +`DataUriSchemeHandler` allows _inlining_ images with `data:` scheme (`data:image/svg+xml;base64,MTIz`). +This scheme-handler is registered by default, so you do not need to add it explicitly. + +### NetworkSchemeHandler +`NetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris +(internally it uses `HttpURLConnection`). This scheme-handler is registered by default + +### OkHttpNetworkSchemeHandler +`OkHttpNetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris +via [okhttp library](https://github.com/square/okhttp). Please note that in order to use +this scheme-handler you must explicitly add `okhttp` library to your project. + +```java +// default instance +OkHttpNetworkSchemeHandler.create(); + +// specify OkHttpClient to use +OkHttpNetworkSchemeHandler.create(new OkHttpClient()); + +// @since 4.0.0 +OkHttpNetworkSchemeHandler.create(Call.Factory); +``` + +### Custom SchemeHandler + +```java +public abstract class SchemeHandler { + + @NonNull + public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri); + + @NonNull + public abstract Collection supportedSchemes(); +} +``` + +Starting with `SchemeHandler` can return a result (when no +further decoding is required): + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + // for example to return a drawable resource + plugin.addSchemeHandler(new SchemeHandler() { + @NonNull + @Override + public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { + + final int resourceId = context.getResources().getIdentifier( + raw.substring("resources://".length()), + "drawable", + context.getPackageName()); + + // it's fine if it throws, async-image-loader will catch exception + final Drawable drawable = context.getDrawable(resourceId); + + // it's important to apply bounds to resulting drawable + DrawableUtils.applyIntrinsicBounds(drawable); + + return ImageItem.withResult(drawable); + } + + @NonNull + @Override + public Collection supportedSchemes() { + return Collections.singleton("resources"); + } + }); + } + })) +``` + +:::tip +If you are using [html](/docs/v4/html/) you do not have to additionally setup +images displayed via `` tag, as `HtmlPlugin` automatically uses configured +image loader. But images referenced in HTML come with additional support for +sizes, which is not supported natively by markdown, allowing absolute or relative sizes: + +```html + +``` +::: \ No newline at end of file diff --git a/docs/docs/v4/recipes.md b/docs/docs/v4/recipes.md index 7fc70175..46e9d86c 100644 --- a/docs/docs/v4/recipes.md +++ b/docs/docs/v4/recipes.md @@ -1,3 +1,53 @@ # Recipes -todo \ No newline at end of file + +## SpannableFactory + +Consider using `NoCopySpannableFactory` when a `TextView` will be used to display markdown +multiple times (for example in a `RecyclerView`): + +```java +// call after inflation and before setting markdown +textView.setSpannableFactory(NoCopySpannableFactory.getInstance()); +``` + + +## Autolink + +Do not use `autolink` XML attribute on your `TextView` as it will remove all links except autolinked ones. +Consider using [linkify plugin](/docs/v4/linkify.md) or commonmark-java [autolink extension](https://github.com/atlassian/commonmark-java) + + + +## Custom typeface + +When using a custom typeface on a `TextView` you might find that **bold** and *italic* nodes +are displayed incorrectly. Consider registering own `SpanFactories` for `StrongEmphasis` and `Emphasis` nodes: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder + .setFactory(StrongEmphasis.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new StyleSpan(Typeface.BOLD); + } + }) + .setFactory(Emphasis.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new StyleSpan(Typeface.ITALIC); + } + }); + } + }) + .build(); +``` + +Please check that `StyleSpan` works for you. If it doesn't consider +using `CustomTypefaceSpan` with your typeface directly. + + diff --git a/docs/docs/v4/recycler-table/README.md b/docs/docs/v4/recycler-table/README.md new file mode 100644 index 00000000..1841e832 --- /dev/null +++ b/docs/docs/v4/recycler-table/README.md @@ -0,0 +1,92 @@ +# Recycler Table + + + +Artifact that provides [MarkwonAdapter.Entry](/docs/v3/recycler/) to render `TableBlock` inside +Android-native `TableLayout` widget. + +screenshot +
      +* It's possible to wrap `TableLayout` inside a `HorizontalScrollView` to include all table content + +--- + +Register instance of `TableEntry` with `MarkwonAdapter` to render TableBlocks: +```java +final MarkwonAdapter adapter = MarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text) + .include(TableBlock.class, TableEntry.create(builder -> builder + .tableLayout(R.layout.adapter_table_block, R.id.table_layout) + .textLayoutIsRoot(R.layout.view_table_entry_cell))) + .build(); +``` + +`TableEntry` requires at least 2 arguments: +* `tableLayout` - layout with `TableLayout` inside +* `textLayout` - layout with `TextView` inside (represents independent table cell) + +In case when required view is the root of layout specific builder methods can be used: +* `tableLayoutIsRoot(int)` +* `textLayoutIsRoot(int)` + +If your layouts have different structure (for example wrap a `TableView` inside a `HorizontalScrollView`) +then you should use methods that accept ID of required view inside layout: +* `tableLayout(int, int)` +* `textLayout(int, int)` + +--- + +To display `TableBlock` as a `TableLayout` specific `MarkwonPlugin` must be used: `TableEntryPlugin`. + +:::warning +Do not use `TablePlugin` if you wish to display markdown tables via `TableEntry`. Use **TableEntryPlugin** instead +::: + +`TableEntryPlugin` can reuse existing `TablePlugin` to make appearance of tables the same in both contexts: +when rendering _natively_ in a TextView and when rendering in RecyclerView with TableEntry. + +* `TableEntryPlugin.create(Context)` - creates plugin with default `TableTheme` +* `TableEntryPlugin.create(TableTheme)` - creates plugin with provided `TableTheme` +* `TableEntryPlugin.create(TablePlugin.ThemeConfigure)` - creates plugin with theme configured by `ThemeConfigure` +* `TableEntryPlugin.create(TablePlugin)` - creates plugin with `TableTheme` used in provided `TablePlugin` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(TableEntryPlugin.create(context)) + // other plugins + .build(); +``` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(TableEntryPlugin.create(builder -> builder + .tableBorderWidth(0) + .tableHeaderRowBackgroundColor(Color.RED))) + // other plugins + .build(); +``` + +## Table with scrollable content + +To stretch table columns to fit the width of screen or to make table scrollable when content exceeds screen width +this layout can be used: + +```xml + + + + + +``` \ No newline at end of file diff --git a/docs/docs/v4/recycler/README.md b/docs/docs/v4/recycler/README.md new file mode 100644 index 00000000..2273915e --- /dev/null +++ b/docs/docs/v4/recycler/README.md @@ -0,0 +1,153 @@ +# Recycler + + + +This artifact allows displaying markdown in a set of Android widgets +inside a RecyclerView. Can be useful when displaying lengthy markdown +content or **displaying certain markdown blocks inside specific widgets**. + +```java +// create an adapter that will use a TextView for each block of markdown +// `createTextViewIsRoot` accepts a layout in which TextView is the root view +final MarkwonAdapter adapter = + MarkwonAdapter.createTextViewIsRoot(R.layout.adapter_default_entry); +``` + +```java +// `create` method accepts a layout with TextView and ID of a TextView +// which allows wrapping a TextView inside another widget or combine with other widgets +final MarkwonAdapter adapter = + MarkwonAdapter.create(R.layout.adapter_default_entry, R.id.text_view); + +// initialize RecyclerView (LayoutManager, Decorations, etc) +final RecyclerView recyclerView = obtainRecyclerView(); + +// set adapter +recyclerView.setAdapter(adapter); + +// obtain an instance of Markwon (register all required plugins) +final Markwon markwon = obtainMarkwon(); + +// set markdown to be displayed +adapter.setMarkdown(markwon, "# This is markdown!"); + +// NB, adapter does not handle updates on its own, please use +// whatever method appropriate for you. +adapter.notifyDataSetChanged(); +``` + +Initialized adapter above will use a TextView for each markdown block. +In order to tell adapter to render certain blocks differently a `builder` can be used. +For example, let's render `FencedCodeBlock` inside a `HorizontalScrollView`: + +```java +// we still need to have a _default_ entry +final MarkwonAdapter adapter = + MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry) + .include(FencedCodeBlock.class, new FencedCodeBlockEntry()) + .build(); +``` + +where `FencedCodeBlockEntry` is: + +```java +public class FencedCodeBlockEntry extends MarkwonAdapter.Entry { + + @NonNull + @Override + public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { + return new Holder(inflater.inflate(R.layout.adapter_fenced_code_block, parent, false)); + } + + @Override + public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull FencedCodeBlock node) { + markwon.setParsedMarkdown(holder.textView, markwon.render(node)); + } + + public static class Holder extends MarkwonAdapter.Holder { + + final TextView textView; + + public Holder(@NonNull View itemView) { + super(itemView); + + this.textView = requireView(R.id.text_view); + } + } +} +``` + +and its layout (`R.layout.adapter_fenced_code_block`): + +```xml + + + + + + +``` + +As we apply styling to `FencedCodeBlock` _manually_, we no longer need +`Markwon` to apply styling spans for us, so `Markwon` initialization could be: + +```java +final Markwon markwon = Markwon.builder(context) + // your other plugins + .usePlugin(new AbstractMarkwonPlugin() { + @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(); +``` + +Previously we have created a `FencedCodeBlockEntry` but all it does is apply markdown to a TextView. +For such a case there is a `SimpleEntry` that could be used instead: + +```java +final MarkwonAdapter adapter = + MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry) + .include(FencedCodeBlock.class, SimpleEntry.create(R.layout.adapter_fenced_code_block, R.id.text_view)) + .build(); +``` + +:::tip +`SimpleEntry` also takes care of _caching_ parsed markdown. So each node will be +parsed only once and each subsequent adapter binding call will reuse previously cached markdown. +::: + +:::tip Tables +There is a standalone artifact that adds support for displaying markdown tables +natively via `TableLayout`. Please refer to its [documentation](/docs/v3/recycler-table/) +::: \ No newline at end of file diff --git a/docs/docs/v4/syntax-highlight/README.md b/docs/docs/v4/syntax-highlight/README.md new file mode 100644 index 00000000..e1abcca0 --- /dev/null +++ b/docs/docs/v4/syntax-highlight/README.md @@ -0,0 +1,74 @@ +# Syntax highlight + + + +This is a module to add **syntax highlight** functionality to your markdown rendered with `Markwon` library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance. + +theme-default + + +theme-darkula + +--- + +First, we need to obtain an instance of `Prism4jSyntaxHighlight` which implements Markwon's `SyntaxHighlight`: + +```java +final SyntaxHighlight highlight = + Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme); +``` + +we also can obtain an instance of `Prism4jSyntaxHighlight` that has a _fallback_ option (if a language is not defined in `Prism4j` instance, fallback language can be used): + +```java +final SyntaxHighlight highlight = + Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme, String); +``` + +Generally obtaining a `Prism4j` instance is pretty easy: + +```java +final Prism4j prism4j = new Prism4j(new GrammarLocatorDef()); +``` + +Where `GrammarLocatorDef` is a generated grammar locator (if you use `prism4j-bundler` annotation processor) + +`Prism4jTheme` is a specific type that is defined in this module (`prism4j` doesn't know anything about rendering). It has 2 implementations: + +* `Prism4jThemeDefault` +* `Prism4jThemeDarkula` + +Both of them can be obtained via factory method `create`: + +* `Prism4jThemeDefault.create()` +* `Prism4jThemeDarkula.create()` + +But of cause nothing is stopping you from defining your own theme: + +```java +public interface Prism4jTheme { + + @ColorInt + int background(); + + @ColorInt + int textColor(); + + void apply( + @NonNull String language, + @NonNull Prism4j.Syntax syntax, + @NonNull SpannableStringBuilder builder, + int start, + int end + ); +} +``` + +:::tip +You can extend `Prism4jThemeBase` which has some helper methods +::: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) +``` \ No newline at end of file