From 8b0edc32c3de247f3a6a6eaa8b1261bc4035c68c Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 12 Jun 2019 18:45:28 +0300 Subject: [PATCH] Started with v4 documentation --- README.md | 9 - _CHANGES.md | 3 +- docs/.vuepress/.artifacts.js | 2 +- docs/.vuepress/.artifacts.v3.js | 4 + docs/.vuepress/components/ArtifactPicker.vue | 2 +- docs/.vuepress/components/ArtifactPicker4.vue | 105 +++++ docs/.vuepress/config.js | 23 +- docs/docs/v4/core/configuration.md | 173 +++++++++ docs/docs/v4/core/core-plugin.md | 141 +++++++ docs/docs/v4/core/getting-started.md | 52 +++ docs/docs/v4/core/movement-method-plugin.md | 17 + docs/docs/v4/core/plugins.md | 361 ++++++++++++++++++ docs/docs/v4/core/registry.md | 97 +++++ docs/docs/v4/core/render-props.md | 75 ++++ docs/docs/v4/core/spans-factory.md | 103 +++++ docs/docs/v4/core/theme.md | 187 +++++++++ docs/docs/v4/core/visitor.md | 73 ++++ docs/docs/v4/install.md | 34 ++ docs/docs/v4/migration-3-4.md | 3 + docs/docs/v4/recipes.md | 3 + markwon-image-gif/build.gradle | 25 -- markwon-image-gif/gradle.properties | 4 - .../src/main/AndroidManifest.xml | 1 - .../markwon/image/gif/GifMediaDecoder.java | 81 ---- .../noties/markwon/image/gif/GifPlugin.java | 37 -- markwon-image-okhttp/build.gradle | 25 -- markwon-image-okhttp/gradle.properties | 4 - .../src/main/AndroidManifest.xml | 1 - .../image/okhttp/OkHttpImagesPlugin.java | 51 --- .../image/okhttp/OkHttpSchemeHandler.java | 55 --- markwon-image-svg/build.gradle | 25 -- markwon-image-svg/gradle.properties | 4 - .../src/main/AndroidManifest.xml | 1 - .../markwon/image/svg/SvgMediaDecoder.java | 72 ---- .../noties/markwon/image/svg/SvgPlugin.java | 33 -- 35 files changed, 1454 insertions(+), 432 deletions(-) create mode 100644 docs/.vuepress/.artifacts.v3.js create mode 100644 docs/.vuepress/components/ArtifactPicker4.vue create mode 100644 docs/docs/v4/core/configuration.md create mode 100644 docs/docs/v4/core/core-plugin.md create mode 100644 docs/docs/v4/core/getting-started.md create mode 100644 docs/docs/v4/core/movement-method-plugin.md create mode 100644 docs/docs/v4/core/plugins.md create mode 100644 docs/docs/v4/core/registry.md create mode 100644 docs/docs/v4/core/render-props.md create mode 100644 docs/docs/v4/core/spans-factory.md create mode 100644 docs/docs/v4/core/theme.md create mode 100644 docs/docs/v4/core/visitor.md create mode 100644 docs/docs/v4/install.md create mode 100644 docs/docs/v4/migration-3-4.md create mode 100644 docs/docs/v4/recipes.md delete mode 100644 markwon-image-gif/build.gradle delete mode 100644 markwon-image-gif/gradle.properties delete mode 100644 markwon-image-gif/src/main/AndroidManifest.xml delete mode 100644 markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java delete mode 100644 markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java delete mode 100644 markwon-image-okhttp/build.gradle delete mode 100644 markwon-image-okhttp/gradle.properties delete mode 100644 markwon-image-okhttp/src/main/AndroidManifest.xml delete mode 100644 markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java delete mode 100644 markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java delete mode 100644 markwon-image-svg/build.gradle delete mode 100644 markwon-image-svg/gradle.properties delete mode 100644 markwon-image-svg/src/main/AndroidManifest.xml delete mode 100644 markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java delete mode 100644 markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java diff --git a/README.md b/README.md index 075186b0..ddfd65dd 100644 --- a/README.md +++ b/README.md @@ -97,15 +97,6 @@ Please visit [documentation] web-site for reference [documentation]: https://noties.github.io/Markwon ---- - -## Applications using Markwon - -* [Partiko](https://partiko.app) -* [FairNote Notepad](https://play.google.com/store/apps/details?id=com.rgiskard.fairnote) -* [Boxcryptor](https://www.boxcryptor.com) - - --- # Demo diff --git a/_CHANGES.md b/_CHANGES.md index 0c8ea7de..e20c7985 100644 --- a/_CHANGES.md +++ b/_CHANGES.md @@ -9,4 +9,5 @@ * images-plugin moved to standalone again * removed MarkwonPlugin#configureHtmlRenderer -> now part of HtmlPlugin * TagHandler now has `supportedTags()` method -* html is moved completely to html-plugin \ No newline at end of file +* html is moved completely to html-plugin +* OnTextAddedListener \ No newline at end of file diff --git a/docs/.vuepress/.artifacts.js b/docs/.vuepress/.artifacts.js index 2a1c3f43..2f41fc13 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":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"ru.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.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-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"}]; export { artifacts }; diff --git a/docs/.vuepress/.artifacts.v3.js b/docs/.vuepress/.artifacts.v3.js new file mode 100644 index 00000000..2a1c3f43 --- /dev/null +++ b/docs/.vuepress/.artifacts.v3.js @@ -0,0 +1,4 @@ + +// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script +const artifacts = [{"id":"core","name":"Core","group":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"ru.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}]; +export { artifacts }; diff --git a/docs/.vuepress/components/ArtifactPicker.vue b/docs/.vuepress/components/ArtifactPicker.vue index 453b1cd2..1c5a9ca8 100644 --- a/docs/.vuepress/components/ArtifactPicker.vue +++ b/docs/.vuepress/components/ArtifactPicker.vue @@ -29,7 +29,7 @@ + + \ No newline at end of file diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 8de47eef..91f11d14 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -18,10 +18,10 @@ module.exports = { text: 'API Version', items: [ { text: 'Current (3.x.x)', link: '/' }, + { text: 'Beta (4.x.x)', link: '/docs/v4/install.md' }, { text: 'Legacy (2.x.x)', link: '/docs/v2/' } ] }, - { text: 'Sandbox', link: '/sandbox.md' }, { text: 'Github', link: 'https://github.com/noties/Markwon' } ], sidebar: { @@ -36,6 +36,27 @@ module.exports = { '/docs/v2/html.md', '/docs/v2/view.md' ], + '/docs/v4': [ + '/docs/v4/install.md', + { + title: 'Core', + collapsable: false, + children: [ + '/docs/v4/core/getting-started.md', + '/docs/v4/core/plugins.md', + '/docs/v4/core/registry.md', + '/docs/v4/core/theme.md', + '/docs/v4/core/configuration.md', + '/docs/v4/core/visitor.md', + '/docs/v4/core/spans-factory.md', + '/docs/v4/core/core-plugin.md', + '/docs/v4/core/movement-method-plugin.md', + '/docs/v4/core/render-props.md' + ] + }, + '/docs/v4/recipes.md', + '/docs/v4/migration-3-4.md' + ], '/': [ '', { diff --git a/docs/docs/v4/core/configuration.md b/docs/docs/v4/core/configuration.md new file mode 100644 index 00000000..4c4ad645 --- /dev/null +++ b/docs/docs/v4/core/configuration.md @@ -0,0 +1,173 @@ +# Configuration + +`MarkwonConfiguration` class holds common Markwon functionality. +These are _configurable_ properties: +* `AsyncDrawableLoader` (back here since ) +* `SyntaxHighlight` +* `LinkSpan.Resolver` +* `UrlProcessor` +* `ImageSizeResolver` + +:::tip +Additionally `MarkwonConfiguration` holds: +* `MarkwonTheme` +* `MarkwonSpansFactory` + +Please note that these values can be retrieved from `MarkwonConfiguration` +instance, but their _configuration_ must be done by a `Plugin` by overriding +one of the methods: +* `Plugin#configureTheme` +* `Plugin#configureSpansFactory` +::: + +## AsyncDrawableLoader + +Allows loading and displaying of images in markdown. Please note that if one is not specified +directly (or via plugin) no images will be displayed. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.asyncDrawableLoader(AsyncDrawableLoader.noOp()); + } + }) + .build(); +``` + +Currently `Markwon` provides 3 implementations for loading images: +* [own implementation](/docs/v4/image.md) with SVG, GIF, data uri and android_assets support +* [based on Picasso](/docs/v4/image-picasso.md) +* [based on Glide](/docs/v4/image-glide.md) + +## SyntaxHighlight + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.syntaxHighlight(new SyntaxHighlightNoOp()); + } + }) + .build(); +``` + +:::tip +Use [syntax-highlight](/docs/v4/syntax-highlight/) to add syntax highlighting +to your application +::: + +## LinkSpan.Resolver + +React to a link click event. By default `LinkResolverDef` is used, +which tries to start an Activity given the `link` argument. If no +Activity can handle `link` `LinkResolverDef` silently ignores click event + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.linkResolver(new LinkSpan.Resolver() { + @Override + public void resolve(View view, @NonNull String link) { + // react to link click here + } + }); + } + }) + .build(); +``` + +:::tip +Please note that `Markwon` will apply `LinkMovementMethod` to a resulting TextView +if there is none registered. if you wish to register own instance of a `MovementMethod` +apply it directly to a TextView or use [MovementMethodPlugin](/docs/v4/core/movement-method-plugin.md) +::: + +## UrlProcessor + +Process URLs in your markdown (for links and images). If not provided explicitly, +default **no-op** implementation will be used, which does not modify URLs (keeping them as-is). + +`Markwon` provides 2 implementations of `UrlProcessor`: +* `UrlProcessorRelativeToAbsolute` +* `UrlProcessorAndroidAssets` + +### UrlProcessorRelativeToAbsolute + +`UrlProcessorRelativeToAbsolute` can be used to make relative URL absolute. For example if an image is +defined like this: `![img](./art/image.JPG)` and `UrlProcessorRelativeToAbsolute` +is created with `https://github.com/noties/Markwon/raw/master/` as the base: +`new UrlProcessorRelativeToAbsolute("https://github.com/noties/Markwon/raw/master/")`, +then final image will have `https://github.com/noties/Markwon/raw/master/art/image.JPG` +as the destination. + +### UrlProcessorAndroidAssets + +`UrlProcessorAndroidAssets` can be used to make processed links to point to Android assets folder. +So an image: `![img](./art/image.JPG)` will have `file:///android_asset/art/image.JPG` as the +destination. + +:::tip +Please note that `UrlProcessorAndroidAssets` will process only URLs that have no `scheme` information, +so a `./art/image.png` will become `file:///android_asset/art/image.JPG` whilst `https://so.me/where.png` +will be kept as-is. +::: + +## ImageSizeResolver + +`ImageSizeResolver` controls the size of an image to be displayed. Currently it +handles only HTML images (specified via `img` tag). + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.imageSizeResolver(new ImageSizeResolver() { + @NonNull + @Override + public Rect resolveImageSize( + @Nullable ImageSize imageSize, + @NonNull Rect imageBounds, + int canvasWidth, + float textSize) { + return null; + } + }); + } + }) + .build(); +``` + +If not provided explicitly, default `ImageSizeResolverDef` implementation will be used. +It handles 3 dimension units: +* `%` (percent, relative to Canvas width) +* `em` (relative to text size) +* `px` (absolute size, every dimension that is not `%` or `em` is considered to be _absolute_) + +```html + + + +``` + +`ImageSizeResolverDef` keeps the ratio of original image if one of the dimensions is missing. + +:::warning Height% +There is no support for `%` units for `height` dimension. This is due to the fact that +height of an TextView in which markdown is displayed is non-stable and changes with time +(for example when image is loaded and applied to a TextView it will _increase_ TextView's height), +so we will have no point-of-reference from which to _calculate_ image height. +::: + +:::tip +`ImageSizeResolverDef` also takes care for an image to **not** exceed +canvas width. If an image has greater width than a TextView Canvas, then +image will be _scaled-down_ to fit the canvas. Please note that this rule +applies only if image has no absolute sizes (for example width is specified +in pixels). +::: \ No newline at end of file diff --git a/docs/docs/v4/core/core-plugin.md b/docs/docs/v4/core/core-plugin.md new file mode 100644 index 00000000..750e3c59 --- /dev/null +++ b/docs/docs/v4/core/core-plugin.md @@ -0,0 +1,141 @@ +# Core plugin + +Since with introduction of _plugins_, Markwon +**core** functionality was moved to a dedicated plugin. + +```java +CorePlugin.create(); +``` + +## Node visitors + +`CorePlugin` registers these `commonmark-java` node visitors: +* `Text` +* `StrongEmphasis` +* `Emphasis` +* `BlockQuote` +* `Code` +* `Image` +* `FencedCodeBlock` +* `IndentedCodeBlock` +* `BulletList` +* `OrderedList` +* `ListItem` +* `ThematicBreak` +* `Heading` +* `SoftLineBreak` +* `HardLineBreak` +* `Paragraph` +* `Link` + +## Span factories + +`CorePlugin` adds these `SpanFactory`s: +* `StrongEmphasis` +* `Emphasis` +* `BlockQuote` +* `Code` +* `FencedCodeBlock` +* `IndentedCodeBlock` +* `ListItem` +* `Heading` +* `Link` +* `ThematicBreak` + + +:::tip +By default `CorePlugin` does not register a `Paragraph` `SpanFactory` but +this can be done in your custom plugin: + +```java +Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Paragraph.class, (configuration, props) -> + new ForegroundColorSpan(Color.RED)); + } + }) +``` +::: + +## Props +These props are exported by `CorePlugin` and can be found in `CoreProps`: +* `Prop LIST_ITEM_TYPE` (BULLET | ORDERED) +* `Prop BULLET_LIST_ITEM_LEVEL` +* `Prop ORDERED_LIST_ITEM_NUMBER` +* `Prop HEADING_LEVEL` +* `Prop LINK_DESTINATION` +* `Prop PARAGRAPH_IS_IN_TIGHT_LIST` + +:::warning List item type +Before `Markwon` had 2 distinct lists (bullet and ordered). +Since a single `SpanFactory` is used, which internally checks +for `Prop LIST_ITEM_TYPE`. +Beware of this if you would like to override only one of the list types. This is +done to correspond to `commonmark-java` implementation. +::: + +More information about props can be found [here](/docs/v4/core/render-props.md) + +--- + +:::tip Soft line break +Since Markwon core does not give an option to +insert a new line when there is a soft line break in markdown. Instead a +custom plugin can be used: + +```java +final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(SoftLineBreak.class, (visitor, softLineBreak) -> + visitor.forceNewLine()); + } + }) + .build(); +``` +::: + +:::warning +Please note that `CorePlugin` will implicitly set a `LinkMovementMethod` on a TextView +if one is not present. If you wish to customize a MovementMethod that is used, apply +one manually to a TextView (before applying markdown) or use the [MovementMethodPlugin](/docs/v4/core/movement-method-plugin.md) +which accepts a MovementMethod as an argument. +::: + +## OnTextAddedListener + +Since `4.0.0` `CorePlugin` provides ability to receive text-added event. This can +be useful in order to process raw text (for example to [linkify](/docs/v4/linkify.md) it): + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + registry.require(CorePlugin.class, new Action() { + @Override + public void apply(@NonNull CorePlugin corePlugin) { + corePlugin.addOnTextAddedListener(new CorePlugin.OnTextAddedListener() { + @Override + public void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start) { + + // NB text is already added and you are __strongly__ adviced not to + // modify visitor here, but only add spans + // + // this will make all text BLUE + visitor.builder().setSpan( + new ForegroundColorSpan(Color.BLUE), + start, + visitor.length() + ); + } + }); + } + }); + } + }) + .build(); +``` \ No newline at end of file diff --git a/docs/docs/v4/core/getting-started.md b/docs/docs/v4/core/getting-started.md new file mode 100644 index 00000000..91848f71 --- /dev/null +++ b/docs/docs/v4/core/getting-started.md @@ -0,0 +1,52 @@ +# Getting started + +:::tip Installation +Please follow [installation](/docs/v4/install.md) instructions +to learn how to add `Markwon` to your project +::: + +## Quick one + +This is the most simple way to set markdown to a `TextView` or any of its siblings: + +```java +// obtain an instance of Markwon +final Markwon markwon = Markwon.create(context); + +// set markdown +markwon.setMarkdown(textView, "**Hello there!**"); +``` + +The most simple way to obtain markdown to be applied _somewhere_ else: + +```java +// obtain an instance of Markwon +final Markwon markwon = Markwon.create(context); + +// parse markdown and create styled text +final Spanned markdown = markwon.toMarkdown("**Hello there!**"); + +// use it +Toast.makeText(context, markdown, Toast.LENGTH_LONG).show(); +``` + +## Longer one + +With explicit `parse` and `render` methods: + +```java +// obtain an instance of Markwon +final Markwon markwon = Markwon.create(context); + +// parse markdown to commonmark-java Node +final Node node = markwon.parse("Are **you** still there?"); + +// create styled text from parsed Node +final Spanned markdown = markwon.render(node); + +// use it on a TextView +markwon.setParsedMarkdown(textView, markdown); + +// or a Toast +Toast.makeText(context, markdown, Toast.LENGTH_LONG).show(); +``` diff --git a/docs/docs/v4/core/movement-method-plugin.md b/docs/docs/v4/core/movement-method-plugin.md new file mode 100644 index 00000000..6bb50c87 --- /dev/null +++ b/docs/docs/v4/core/movement-method-plugin.md @@ -0,0 +1,17 @@ +# Movement method plugin + +`MovementMethodPlugin` can be used to apply a `MovementMethod` to a TextView +(important if you have links inside your markdown). By default `CorePlugin` +will set a `LinkMovementMethod` on a TextView if one is missing. If you have +specific needs for a `MovementMethod` and `LinkMovementMethod` doesn't answer +your needs use `MovementMethodPlugin`: + +```java +Markwon.builder(context) + .usePlugin(MovementMethodPlugin.create(ScrollingMovementMethod.getInstance())) +``` + +:::tip +If you are having trouble with system `LinkMovementMethod` as an alternative +[BetterLinkMovementMethod](https://github.com/saket/Better-Link-Movement-Method) library can be used. +::: diff --git a/docs/docs/v4/core/plugins.md b/docs/docs/v4/core/plugins.md new file mode 100644 index 00000000..c44954b3 --- /dev/null +++ b/docs/docs/v4/core/plugins.md @@ -0,0 +1,361 @@ +# Plugins + +Since `MarkwonPlugin` takes the key role in +processing and rendering markdown. Even **core** functionaly is abstracted +into a `CorePlugin`. So it's still possible to use `Markwon` with a completely +own set of plugins. + +To register a plugin `Markwon.Builder` must be used: + +```java +Markwon.builder(context) + // @since 4.0.0 there is no need to register CorePlugin, as it's registered automatically +// .usePlugin(CorePlugin.create()) + .usePlugin(MyPlugin.create()) + .build(); +``` + +All the process of transforming _raw_ markdown into a styled text (Spanned) +will go through plugins. A plugin can: + +* [configure plugin registry](#registry) +* [configure commonmark-java `Parser`](#parser) +* [configure `MarkwonTheme`](#markwontheme) +* [configure `AsyncDrawableLoader` (used to display images in markdown)](#images) +* [configure `MarkwonConfiguration`](#configuration) +* [configure `MarkwonVisitor` (extensible commonmark-java Node visitor)](#visitor) +* [configure `MarkwonSpansFactory` (factory to hold spans information for each Node)](#spans-factory) + +--- + +* [process raw input markdown before parsing it](#process-markdown) +* [inspect/modify commonmark-java Node after it's been parsed, but before rendering](#inspect-modify-node) +* [inspect commonmark-java Node after it's been rendered](#inspect-node-after-render) +* [prepare TextView to display markdown _before_ markdown is applied to a TextView](#prepare-textview) +* [post-process TextView _after_ markdown was applied](#textview-after-markdown-applied) + +:::tip +if you need to override only few methods of `MarkwonPlugin` (since it is an interface), +`AbstractMarkwonPlugin` can be used. +::: + +## Registry + +Registry is a special step to pre-configure all registered plugins. It is also +used to determine the order of plugins inside `Markwon` instance. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + + final CorePlugin corePlugin = registry.require(CorePlugin.class); + + // or + registry.require(CorePlugin.class, new Action() { + @Override + public void apply(@NonNull CorePlugin corePlugin) { + + } + }); + } + }) + .build(); +``` + +More information about registry can be found [here](/docs/v4/core/registry.md) + +## Parser + +For example, let's register a new commonmark-java Parser extension: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureParser(@NonNull Parser.Builder builder) { + // no need to call `super.configureParser(builder)` + builder.extensions(Collections.singleton(StrikethroughExtension.create())); + } + }) + .build(); +``` + +There are no limitations on what to do with commonmark-java Parser. For more info +_what_ can be done please refer to . + +## MarkwonTheme + +Starting `MarkwonTheme` represents _core_ theme. Aka theme for +things core module knows of. For example it doesn't know anything about `strikethrough` +or `tables` (as they belong to different modules). + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureTheme(@NonNull MarkwonTheme.Builder builder) { + builder + .codeTextColor(Color.BLACK) + .codeBackgroundColor(Color.GREEN); + } + }) + .build(); +``` + +:::tip +`CorePlugin` has special handling - it will be added automatically +when `Markwon.builder(Context)` method is used. If you wish to create +Markwon instance _without_ CorePlugin registered - +use `Markwon.builderNoCore(Context)` method instead +::: + +More information about `MarkwonTheme` can be found [here](/docs/v4/core/theme.md). + + +## Configuration + +`MarkwonConfiguration` is a set of common tools that are used by different parts +of `Markwon`. It allows configurations of these: + +* `AsyncDrawableLoader` (image loading) +* `SyntaxHighlight` (highlighting code blocks) +* `LinkResolver` (opens links in markdown) +* `UrlProcessor` (process URLs in markdown for both links and images) +* `ImageSizeResolver` (resolve image sizes, like `fit-to-canvas`, etc) + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.linkResolver(new LinkResolverDef()); + } + }) + .build(); +``` + +More information about `MarkwonConfiguration` can be found [here](/docs/v4/core/configuration.md) + + +## Visitor + +`MarkwonVisitor` is commonmark-java Visitor that allows +configuration of how each Node is visited. There is no longer need to create +own subclass of Visitor and override required methods (like in `2.x.x` versions). +`MarkwonVisitor` also allows registration of Nodes, that `core` module knows +nothing about (instead of relying on `visit(CustomNode)` method)). + +For example, let's add `strikethrough` Node visitor: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + // please note that strike-through parser extension must be registered + // in order to receive such callback + builder + .on(Strikethrough.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Strikethrough strikethrough) { + final int length = visitor.length(); + visitor.visitChildren(strikethrough); + visitor.setSpansForNodeOptional(strikethrough, length); + } + }); + } + }) + .build(); +``` + +:::tip +`MarkwonVisitor` also allows _overriding_ already registered nodes. For example, +you can disable `Heading` Node rendering: + +```java +builder.on(Heading.class, null); +``` +::: + +More information about `MarkwonVisitor` can be found [here](/docs/v4/core/visitor.md) + + +## Spans Factory + +`MarkwonSpansFactory` is an abstract factory (factory that produces other factories) +for spans that `Markwon` uses. It controls what spans to use for certain Nodes. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // override emphasis factory to make all emphasis nodes underlined + builder.setFactory(Emphasis.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new UnderlineSpan(); + } + }); + } + }) + .build(); +``` + +:::tip +`SpanFactory` allows to return an _array_ of spans to apply multiple spans +for a Node: + +```java +@Override +public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + // make underlined and set text color to red + return new Object[]{ + new UnderlineSpan(), + new ForegroundColorSpan(Color.RED) + }; +} +``` +::: + +More information about spans factory can be found [here](/docs/v4/core/spans-factory.md) + + +## Process markdown + +A plugin can be used to _pre-process_ input markdown (this will be called before _parsing_): + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @NonNull + @Override + public String processMarkdown(@NonNull String markdown) { + return markdown.replaceAll("foo", "bar"); + } + }) + .build(); +``` + +## Inspect/modify Node + +A plugin can inspect/modify commonmark-java Node _before_ it's being rendered. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void beforeRender(@NonNull Node node) { + + // for example inspect it with custom visitor + node.accept(new MyVisitor()); + + // or modify (you know what you are doing, right?) + node.appendChild(new Text("Appended")); + } + }) + .build(); +``` + +## Inspect Node after render + +A plugin can inspect commonmark-java Node after it's been rendered. +Modifying Node at this point makes not much sense (it's already been +rendered and all modifications won't change anything). But this method can be used, +for example, to clean-up some internal state (after rendering). Generally +speaking, a plugin must be stateless, but if it cannot, then this method is +the best place to clean-up. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) { + cleanUp(); + } + }) + .build(); +``` + +## Prepare TextView + +A plugin can _prepare_ a TextView before markdown is applied. For example `images` +unschedules all previously scheduled `AsyncDrawableSpans` (if any) here. This way +when new markdown (and set of Spannables) arrives, previous set won't be kept in +memory and could be garbage-collected. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { + // clean-up previous + AsyncDrawableScheduler.unschedule(textView); + } + }) + .build(); +``` + +## TextView after markdown applied + +A plugin will receive a callback _after_ markdown is applied to a TextView. +For example `images` uses this callback to schedule new set of Spannables. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void afterSetText(@NonNull TextView textView) { + AsyncDrawableScheduler.schedule(textView); + } + }) + .build(); +``` + +:::tip +Please note that unlike `#beforeSetText`, `#afterSetText` won't receive +`Spanned` markdown. This happens because at this point spans must be +queried directly from a TextView. +::: + +## What happens underneath + +Here is what happens inside `Markwon` when `setMarkdown` method is called: + +```java +final Markwon markwon = Markwon.create(context); + +// warning: pseudo-code + +// 0. each plugin will be called to _pre-process_ raw input markdown +rawInput = plugins.reduce(rawInput, (input, plugin) -> plugin.processMarkdown(input)); + +// 1. after input is processed it's being parsed to a Node +node = parser.parse(rawInput); + +// 2. each plugin will be able to inspect or manipulate resulting Node +// before rendering +plugins.forEach(plugin -> plugin.beforeRender(node)); + +// 3. node is being visited by a visitor +node.accept(visitor); + +// 4. each plugin will be called after node is being visited (aka rendered) +plugins.forEach(plugin -> plugin.afterRender(node, visitor)); + +// 5. styled markdown ready at this point +final Spanned markdown = visitor.markdown(); + +// NB, points 6-8 are applied **only** if markdown is set to a TextView + +// 6. each plugin will be called before styled markdown is applied to a TextView +plugins.forEach(plugin -> plugin.beforeSetText(textView, markdown)); + +// 7. markdown is applied to a TextView +textView.setText(markdown); + +// 8. each plugin will be called after markdown is applied to a TextView +plugins.forEach(plugin -> plugin.afterSetText(textView)); +``` \ No newline at end of file diff --git a/docs/docs/v4/core/registry.md b/docs/docs/v4/core/registry.md new file mode 100644 index 00000000..1161600c --- /dev/null +++ b/docs/docs/v4/core/registry.md @@ -0,0 +1,97 @@ +# Registry + +`Registry` allows to pre-configure other plugins and/or declare a dependency on a plugin, +which also will modify internal order of plugins inside a `Markwon` instance. + +For example, you have a configurable plugin: + +```java +public class MyPlugin extends AbstractMarkwonPlugin { + + private boolean enabled; + + public boolean enabled() { + return enabled; + } + + @NonNull + public MyPlugin enabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + {...} +} +``` + +and other plugin that needs to access `MyPlugin` or modify/configure it: + +```java +public class MyOtherPlugin extends AbstractMarkwonPlugin { + @Override + public void configure(@NonNull Registry registry) { + registry.require(MyPlugin.class, new Action() { + @Override + public void apply(@NonNull MyPlugin myPlugin) { + myPlugin.enabled(false); + } + }); + } +} +``` + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new MyOtherPlugin()) + .usePlugin(new MyPlugin()) + .build(); +``` + +_Internal_ plugins order (in this case) will be: +* `CorePlugin` (added automatically and always the first one) +* `MyPlugin` (was required by `MyOtherPlugin`) +* `MyOtherPlugin` + +:::tip +There is no need to _require_ `CorePlugin` as it will be the first one inside +`Markwon` instance. +::: + +The order matters if you want to _override_ some plugin. For example, `CoolPlugin` +adds a `SpanFactory` for a `Cool` markdown node. Other `NotCoolPlugin` wants to +use a different `SpanFactory`, then: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(CoolPlugin.create()) + .usePlugin(new NotCoolPlugin() { + + @Override + public void configure(@NonNull MarkwonPlugin.Registry registry) { + registry.require(CoolPlugin.class); + } + + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Cool.class, new NotCoolSpanFactory()); + } + }) + .build(); +``` + +--- + +All `require` calls to the `Registry` will also validate at runtime that +_required_ plugins are registered. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configure(@NonNull Registry registry) { + // will throw an exception if `NotPresentPlugin` is not present + registry.require(NotPresentPlugin.class); + } + }) + .build(); +``` \ No newline at end of file diff --git a/docs/docs/v4/core/render-props.md b/docs/docs/v4/core/render-props.md new file mode 100644 index 00000000..9dd18004 --- /dev/null +++ b/docs/docs/v4/core/render-props.md @@ -0,0 +1,75 @@ +# RenderProps + +`RenderProps` encapsulates passing arguments from a node visitor to a node renderer. +Without hardcoding arguments into an API method calls. + +`RenderProps` is the state collection for `Props` that are set by a node visitor and +retrieved by a node renderer. + +```java +public class Prop { + + @NonNull + public static Prop of(@NonNull String name) { + return new Prop<>(name); + } + + /* ... */ +} +``` + +For example `CorePlugin` defines a _Heading level_ prop (inside `CoreProps` class): + +```java +public static final Prop HEADING_LEVEL = Prop.of("heading-level"); +``` + +Then CorePlugin registers a `Heading` node visitor and applies heading value: + +```java +@Override +public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Heading.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) { + + /* Heading node handling logic */ + + // set heading level + CoreProps.HEADING_LEVEL.set(visitor.renderProps(), heading.getLevel()); + + // a helper method to apply span(s) for a node + // (internally obtains a SpanFactory for Heading or silently ignores + // this call if no factory for a Heading is registered) + visitor.setSpansForNodeOptional(heading, start); + + /* Heading node handling logic */ + } + }); +} +``` + +And finally `HeadingSpanFactory` (which is also registered by `CorePlugin`): + +```java +public class HeadingSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new HeadingSpan( + configuration.theme(), + CoreProps.HEADING_LEVEL.require(props) + ); + } +} +``` + +--- + +`Prop` has these methods: + +* `@Nullable T get(RenderProps)` - returns value stored in RenderProps or `null` if none is present +* `@NonNull T get(RenderProps, @NonNull T defValue)` - returns value stored in RenderProps or default value (this method always return non-null value) +* `@NonNull T require(RenderProps)` - returns value stored in RenderProps or _throws an exception_ if none is present +* `void set(RenderProps, @Nullable T value)` - updates value stored in RenderProps, passing `null` as value is the same as calling `clear` +* `void clear(RenderProps)` - clears value stored in RenderProps diff --git a/docs/docs/v4/core/spans-factory.md b/docs/docs/v4/core/spans-factory.md new file mode 100644 index 00000000..9567a788 --- /dev/null +++ b/docs/docs/v4/core/spans-factory.md @@ -0,0 +1,103 @@ +# Spans Factory + +Starting with `MarkwonSpansFactory` controls what spans are displayed +for markdown nodes. + +```java +Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // passing null as second argument will remove previously added + // factory for the Link node + builder.setFactory(Link.class, null); + } + }); +``` + +## SpanFactory + +In order to create a _generic_ interface for all possible Nodes, a `SpanFactory` +was added: + +```java +builder.setFactory(Link.class, new SpanFactory() { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return null; + } +}); +``` + +All possible arguments are passed via [RenderProps](/docs/v4/core/render-props.md): + +```java +builder.setFactory(Link.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + final String href = CoreProps.LINK_DESTINATION.require(props); + return new LinkSpan(configuration.theme(), href, configuration.linkResolver()); + } +}); +``` + +`SpanFactory` allows returning `null` for a certain span (no span will be applied). +Or an array of spans (you _can_ go deeper): + +```java +builder.setFactory(Link.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new Object[]{ + new LinkSpan( + configuration.theme(), + CoreProps.LINK_DESTINATION.require(props), + configuration.linkResolver()), + new ForegroundColorSpan(Color.RED) + }; + } +}); +``` + +--- + +Since you can _add_ multiple `SpanFactory` for a single node: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // this factory will be used _along_ with all other factories for specified node + builder.addFactory(Code.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new ForegroundColorSpan(Color.GREEN); + } + }); + } + }) + .build(); +``` + +--- + +If you wish to inspect existing factory you can use: +* `builder#getFactory()` -> returns registered factory or `null` +* `builder#requireFactory()` -> returns registered factory or throws + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + final SpanFactory codeFactory = builder.requireFactory(Code.class); + final SpanFactory linkFactory = builder.getFactory(Link.class); + if (linkFactory != null) { + {...} + } + } + }) + .build(); +``` \ No newline at end of file diff --git a/docs/docs/v4/core/theme.md b/docs/docs/v4/core/theme.md new file mode 100644 index 00000000..672babc6 --- /dev/null +++ b/docs/docs/v4/core/theme.md @@ -0,0 +1,187 @@ +# Theme + +Here is the list of properties that can be configured via `MarkwonTheme.Builder` class. + +:::tip +Starting with there is no need to manually construct a `MarkwonTheme`. +Instead a `Plugin` should be used: +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureTheme(@NonNull MarkwonTheme.Builder builder) { + builder + .codeTextColor(Color.BLACK) + .codeBackgroundColor(Color.GREEN); + } + }) + .build(); +``` +::: + +## Link color + +Controls the color of a [link](#) + + + +* `TextPaint#linkColor` will be used to determine linkColor of a context + +## Block margin + +Starting margin before text content for the: +* lists +* blockquotes +* task lists + + + +## Block quote + +Customizations for the `blockquote` stripe + +> Quote + +### Stripe width + +Width of a blockquote stripe + + + +### Stripe color + +Color of a blockquote stripe + + + +## List + +### List item color + +Controls the color of a list item. For ordered list: leading number, +for unordered list: bullet. + +* UL +1. OL + + + +### Bullet item stroke width + +Border width of a bullet list item (level 2) + +* First +* * Second +* * * Third + + + +### Bullet width + +The width of the bullet item + +* First + * Second + * Third + + + +## Code + +### Inline code text color + +The color of the `code` content + + + +### Inline code background color + +The color of `background` of a code content + + + +### Block code text color + +``` +The color of code block text +``` + + + +### Block code background color + +``` +The color of background of code block text +``` + + + +### Block code leading margin + +Leading margin for the block code content + + + +### Code typeface + +Typeface of code content + + + +### Block code typeface + +Typeface of block code content + + + +### Code text size + +Text size of code content + + + +### Block code text size + +Text size of block code content + + + +## Heading + +### Break height + +The height of a brake under H1 & H2 + + + +### Break color + +The color of a brake under H1 & H2 + + + +### Typeface + +The typeface of heading elements + + + +### Text size + +Array of heading text sizes _ratio_ that is applied to text size + + + +## Thematic break + +### Color + +Color of a thematic break + + + +### Height + +Height of a thematic break + + diff --git a/docs/docs/v4/core/visitor.md b/docs/docs/v4/core/visitor.md new file mode 100644 index 00000000..d03ff848 --- /dev/null +++ b/docs/docs/v4/core/visitor.md @@ -0,0 +1,73 @@ +# Visitor + +Starting with _visiting_ of parsed markdown +nodes does not require creating own instance of commonmark-java `Visitor`, +instead a composable/configurable `MarkwonVisitor` is used. + +## Visitor.Builder +There is no need to create own instance of `MarkwonVisitor.Builder` as +it is done by `Markwon` itself. One still can configure it as one wishes: + +```java +final Markwon markwon = Markwon.builder(contex) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { + visitor.forceNewLine(); + } + }); + } + }); +``` + +--- + +`MarkwonVisitor` encapsulates most of the functionality of rendering parsed markdown. + +It holds rendering configuration: +* `MarkwonVisitor#configuration` - getter for current [MarkwonConfiguration](/docs/v4/core/configuration.md) +* `MarkwonVisitor#renderProps` - getter for current [RenderProps](/docs/v4/core/render-props.md) +* `MarkwonVisitor#builder` - getter for current `SpannableBuilder` + +It contains also a number of utility functions: +* `visitChildren(Node)` - will visit all children of supplied Node +* `hasNext(Node)` - utility function to check if supplied Node has a Node after it (useful for white-space management, so there should be no blank new line after last BlockNode) +* `ensureNewLine` - will insert a new line at current `SpannableBuilder` position only if current (last) character is not a new-line +* `forceNewLine` - will insert a new line character without any condition checking +* `length` - helper function to call `visitor.builder().length()`, returns current length of `SpannableBuilder` +* `clear` - will clear state for `RenderProps` and `SpannableBuilder`, this is done by `Markwon` automatically after each render call + +And some utility functions to control the spans: +* `setSpans(int start, Object spans)` - will apply supplied `spans` on `SpannableBuilder` starting at `start` position and ending at `SpannableBuilder#length`. `spans` can be `null` (no spans will be applied) or an array of spans (each span of this array will be applied) +* `setSpansForNodeOptional(N node, int start)` - helper method to set spans for specified `node` (internally obtains `SpanFactory` for that node and uses it to apply spans) +* `setSpansForNode(N node, int start)` - almost the same as `setSpansForNodeOptional` but instead of silently ignoring call if none `SpanFactory` is registered, this method will throw an exception. + +```java +@Override +public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Heading.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) { + + // or just `visitor.length()` + final int start = visitor.builder().length(); + + visitor.visitChildren(heading); + + // or just `visitor.setSpansForNodeOptional(heading, start)` + final SpanFactory factory = visitor.configuration().spansFactory().get(heading.getClass()); + if (factory != null) { + visitor.setSpans(start, factory.getSpans(visitor.configuration(), visitor.renderProps())); + } + + if (visitor.hasNext(heading)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } + } + }); +} +``` \ No newline at end of file diff --git a/docs/docs/v4/install.md b/docs/docs/v4/install.md new file mode 100644 index 00000000..c6de0e5a --- /dev/null +++ b/docs/docs/v4/install.md @@ -0,0 +1,34 @@ +--- +prev: false +next: /docs/v4/core/getting-started.md +--- + +# Installation + +![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable) +![snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/io.noties.markwon/core.svg?label=snapshot) + + + +## Snapshot + +In order to use latest `SNAPSHOT` version add snapshot repository +to your root project's `build.gradle` file: + +```groovy +allprojects { + repositories { + jcenter() + google() + // this one 👇 + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } // 👈 this one + // this one 👆 + } +} +``` + +:::tip Info +All official artifacts share the same version number and all +are uploaded to **release** and **snapshot** repositories +::: + diff --git a/docs/docs/v4/migration-3-4.md b/docs/docs/v4/migration-3-4.md new file mode 100644 index 00000000..5537e1e2 --- /dev/null +++ b/docs/docs/v4/migration-3-4.md @@ -0,0 +1,3 @@ +# Migration 3.x.x -> 4.x.x + +todo \ No newline at end of file diff --git a/docs/docs/v4/recipes.md b/docs/docs/v4/recipes.md new file mode 100644 index 00000000..7fc70175 --- /dev/null +++ b/docs/docs/v4/recipes.md @@ -0,0 +1,3 @@ +# Recipes + +todo \ No newline at end of file diff --git a/markwon-image-gif/build.gradle b/markwon-image-gif/build.gradle deleted file mode 100644 index bcb3fa77..00000000 --- a/markwon-image-gif/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion config['compile-sdk'] - buildToolsVersion config['build-tools'] - - defaultConfig { - minSdkVersion config['min-sdk'] - targetSdkVersion config['target-sdk'] - versionCode 1 - versionName version - } -} - -dependencies { - - api project(':markwon-core') - - deps.with { - api it['android-gif'] - } -} - -registerArtifact(this) \ No newline at end of file diff --git a/markwon-image-gif/gradle.properties b/markwon-image-gif/gradle.properties deleted file mode 100644 index 2630a2f3..00000000 --- a/markwon-image-gif/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -POM_NAME=Image GIF -POM_ARTIFACT_ID=image-gif -POM_DESCRIPTION=Adds GIF media support to Markwon markdown -POM_PACKAGING=aar \ No newline at end of file diff --git a/markwon-image-gif/src/main/AndroidManifest.xml b/markwon-image-gif/src/main/AndroidManifest.xml deleted file mode 100644 index 649a9a70..00000000 --- a/markwon-image-gif/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java deleted file mode 100644 index 7c06e13a..00000000 --- a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java +++ /dev/null @@ -1,81 +0,0 @@ -package ru.noties.markwon.image.gif; - -import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import pl.droidsonroids.gif.GifDrawable; -import ru.noties.markwon.image.DrawableUtils; - -/** - * @since 1.1.0 - */ -@SuppressWarnings("WeakerAccess") -public class GifMediaDecoder extends MediaDecoder { - - public static final String CONTENT_TYPE = "image/gif"; - - @NonNull - public static GifMediaDecoder create(boolean autoPlayGif) { - return new GifMediaDecoder(autoPlayGif); - } - - private final boolean autoPlayGif; - - protected GifMediaDecoder(boolean autoPlayGif) { - this.autoPlayGif = autoPlayGif; - } - - @Nullable - @Override - public Drawable decode(@NonNull InputStream inputStream) { - - Drawable out = null; - - final byte[] bytes = readBytes(inputStream); - if (bytes != null) { - try { - out = newGifDrawable(bytes); - DrawableUtils.applyIntrinsicBounds(out); - - if (!autoPlayGif) { - ((GifDrawable) out).pause(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - return out; - } - - @NonNull - protected Drawable newGifDrawable(@NonNull byte[] bytes) throws IOException { - return new GifDrawable(bytes); - } - - @Nullable - protected static byte[] readBytes(@NonNull InputStream stream) { - - byte[] out = null; - - try { - final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final int length = 1024 * 8; - final byte[] buffer = new byte[length]; - int read; - while ((read = stream.read(buffer, 0, length)) != -1) { - outputStream.write(buffer, 0, read); - } - out = outputStream.toByteArray(); - } catch (IOException e) { - e.printStackTrace(); - } - - return out; - } -} diff --git a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java deleted file mode 100644 index 754f91ae..00000000 --- a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.noties.markwon.image.gif; - -import androidx.annotation.NonNull; - -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.Priority; - -public class GifPlugin extends AbstractMarkwonPlugin { - - @NonNull - public static GifPlugin create() { - return create(true); - } - - @NonNull - public static GifPlugin create(boolean autoPlay) { - return new GifPlugin(autoPlay); - } - - private final boolean autoPlay; - - public GifPlugin(boolean autoPlay) { - this.autoPlay = autoPlay; - } - - @Override - public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { - builder.addMediaDecoder(GifMediaDecoder.CONTENT_TYPE, GifMediaDecoder.create(autoPlay)); - } - - @NonNull - @Override - public Priority priority() { - return Priority.after(ImagesPlugin.class); - } -} diff --git a/markwon-image-okhttp/build.gradle b/markwon-image-okhttp/build.gradle deleted file mode 100644 index 3f570553..00000000 --- a/markwon-image-okhttp/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion config['compile-sdk'] - buildToolsVersion config['build-tools'] - - defaultConfig { - minSdkVersion config['min-sdk'] - targetSdkVersion config['target-sdk'] - versionCode 1 - versionName version - } -} - -dependencies { - - api project(':markwon-core') - - deps.with { - api it['okhttp'] - } -} - -registerArtifact(this) \ No newline at end of file diff --git a/markwon-image-okhttp/gradle.properties b/markwon-image-okhttp/gradle.properties deleted file mode 100644 index 8722d5cf..00000000 --- a/markwon-image-okhttp/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -POM_NAME=Image OkHttp -POM_ARTIFACT_ID=image-okhttp -POM_DESCRIPTION=Adds OkHttp client to retrieve images data from network -POM_PACKAGING=aar \ No newline at end of file diff --git a/markwon-image-okhttp/src/main/AndroidManifest.xml b/markwon-image-okhttp/src/main/AndroidManifest.xml deleted file mode 100644 index 32240579..00000000 --- a/markwon-image-okhttp/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java b/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java deleted file mode 100644 index 6a72136a..00000000 --- a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java +++ /dev/null @@ -1,51 +0,0 @@ -package ru.noties.markwon.image.okhttp; - -import androidx.annotation.NonNull; - -import java.util.Arrays; - -import okhttp3.OkHttpClient; -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.Priority; - -/** - * Plugin to use OkHttpClient to obtain images from network (http and https schemes) - * - * @see #create() - * @see #create(OkHttpClient) - * @since 3.0.0 - */ -@SuppressWarnings("WeakerAccess") -public class OkHttpImagesPlugin extends AbstractMarkwonPlugin { - - @NonNull - public static OkHttpImagesPlugin create() { - return new OkHttpImagesPlugin(new OkHttpClient()); - } - - @NonNull - public static OkHttpImagesPlugin create(@NonNull OkHttpClient okHttpClient) { - return new OkHttpImagesPlugin(okHttpClient); - } - - private final OkHttpClient client; - - OkHttpImagesPlugin(@NonNull OkHttpClient client) { - this.client = client; - } - - @Override - public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { - builder.addSchemeHandler( - Arrays.asList(NetworkSchemeHandler.SCHEME_HTTP, NetworkSchemeHandler.SCHEME_HTTPS), - new OkHttpSchemeHandler(client) - ); - } - - @NonNull - @Override - public Priority priority() { - return Priority.after(ImagesPlugin.class); - } -} diff --git a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java b/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java deleted file mode 100644 index e1ce930b..00000000 --- a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.noties.markwon.image.okhttp; - -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.IOException; -import java.io.InputStream; - -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; - -class OkHttpSchemeHandler extends SchemeHandler { - - private static final String HEADER_CONTENT_TYPE = "Content-Type"; - - private final OkHttpClient client; - - OkHttpSchemeHandler(@NonNull OkHttpClient client) { - this.client = client; - } - - @Nullable - @Override - public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { - ImageItem out = null; - - final Request request = new Request.Builder() - .url(raw) - .tag(raw) - .build(); - - Response response = null; - try { - response = client.newCall(request).execute(); - } catch (IOException e) { - e.printStackTrace(); - } - - if (response != null) { - final ResponseBody body = response.body(); - if (body != null) { - final InputStream inputStream = body.byteStream(); - if (inputStream != null) { - final String contentType = response.header(HEADER_CONTENT_TYPE); - out = new ImageItem(contentType, inputStream); - } - } - } - - return out; - } -} diff --git a/markwon-image-svg/build.gradle b/markwon-image-svg/build.gradle deleted file mode 100644 index cfe7bcd1..00000000 --- a/markwon-image-svg/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion config['compile-sdk'] - buildToolsVersion config['build-tools'] - - defaultConfig { - minSdkVersion config['min-sdk'] - targetSdkVersion config['target-sdk'] - versionCode 1 - versionName version - } -} - -dependencies { - - api project(':markwon-core') - - deps.with { - api it['android-svg'] - } -} - -registerArtifact(this) \ No newline at end of file diff --git a/markwon-image-svg/gradle.properties b/markwon-image-svg/gradle.properties deleted file mode 100644 index 26dce9a4..00000000 --- a/markwon-image-svg/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -POM_NAME=Image SVG -POM_ARTIFACT_ID=image-svg -POM_DESCRIPTION=Adds SVG media support to Markwon markdown -POM_PACKAGING=aar \ No newline at end of file diff --git a/markwon-image-svg/src/main/AndroidManifest.xml b/markwon-image-svg/src/main/AndroidManifest.xml deleted file mode 100644 index 10432a1d..00000000 --- a/markwon-image-svg/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java deleted file mode 100644 index 8b2a9527..00000000 --- a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java +++ /dev/null @@ -1,72 +0,0 @@ -package ru.noties.markwon.image.svg; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.caverock.androidsvg.SVG; -import com.caverock.androidsvg.SVGParseException; - -import java.io.InputStream; - -import ru.noties.markwon.image.DrawableUtils; - -/** - * @since 1.1.0 - */ -public class SvgMediaDecoder extends MediaDecoder { - - public static final String CONTENT_TYPE = "image/svg+xml"; - - @NonNull - public static SvgMediaDecoder create(@NonNull Resources resources) { - return new SvgMediaDecoder(resources); - } - - private final Resources resources; - - @SuppressWarnings("WeakerAccess") - SvgMediaDecoder(Resources resources) { - this.resources = resources; - } - - @Nullable - @Override - public Drawable decode(@NonNull InputStream inputStream) { - - final Drawable out; - - SVG svg = null; - try { - svg = SVG.getFromInputStream(inputStream); - } catch (SVGParseException e) { - e.printStackTrace(); - } - - if (svg == null) { - out = null; - } else { - - final float w = svg.getDocumentWidth(); - final float h = svg.getDocumentHeight(); - final float density = resources.getDisplayMetrics().density; - - final int width = (int) (w * density + .5F); - final int height = (int) (h * density + .5F); - - final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); - final Canvas canvas = new Canvas(bitmap); - canvas.scale(density, density); - svg.renderToCanvas(canvas); - - out = new BitmapDrawable(resources, bitmap); - DrawableUtils.applyIntrinsicBounds(out); - } - - return out; - } -} diff --git a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java deleted file mode 100644 index 34573c9f..00000000 --- a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.noties.markwon.image.svg; - -import android.content.res.Resources; -import androidx.annotation.NonNull; - -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.Priority; - -public class SvgPlugin extends AbstractMarkwonPlugin { - - @NonNull - public static SvgPlugin create(@NonNull Resources resources) { - return new SvgPlugin(resources); - } - - private final Resources resources; - - public SvgPlugin(@NonNull Resources resources) { - this.resources = resources; - } - - @Override - public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { - builder.addMediaDecoder(SvgMediaDecoder.CONTENT_TYPE, SvgMediaDecoder.create(resources)); - } - - @NonNull - @Override - public Priority priority() { - return Priority.after(ImagesPlugin.class); - } -}