diff --git a/docs/.vuepress/components/CommonmarkSandbox.vue b/docs/.vuepress/components/CommonmarkSandbox.vue index 26a0377f..e7082758 100644 --- a/docs/.vuepress/components/CommonmarkSandbox.vue +++ b/docs/.vuepress/components/CommonmarkSandbox.vue @@ -17,7 +17,7 @@

- ** For a little more sophisticated commonmark sandbox editor + ** For a more sophisticated commonmark sandbox editor the dingus can be used.

diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index d0943fd9..d372d9d5 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,24 +1,33 @@ module.exports = { base: '/Markwon/', title: 'Markwon', - description: 'Android markdown library based on commonmark specification', + description: 'Android markdown library based on commonmark specification that renders markdown as system-native Spannables (no WebView)', head: [ ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png?v=1' }], ['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png?v=1' }], ['link', { rel: 'icon', href: '/favicon.ico?v=1' }], ['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png?v=1' }], ['link', { rel: 'manifest', href: '/manifest.json?v=1' }], + ['meta', { name: 'keywords', content: 'android,markdown,library,spannable,markwon,commonmark' }] ], themeConfig: { nav: [ { text: 'Install', link: '/docs/install.md' }, { text: 'Changelog', link: '/CHANGELOG.md' }, + { + text: 'API Version', + items: [ + { text: 'Current (3.x.x)', link: '/' }, + { text: 'Legacy (2.x.x)', link: '/docs/v2/' } + ] + }, { text: 'Sandbox', link: '/sandbox.md' }, + { text: 'Donate', link: '/donate.md' }, { text: 'Github', link: 'https://github.com/noties/Markwon' } ], sidebar: { '/docs/v2/': [ - 'install.md', + '', 'getting-started.md', 'configure.md', 'theme.md', @@ -34,7 +43,13 @@ module.exports = { title: 'Core', children: [ '/docs/core/getting-started.md', - '/docs/core/theme.md' + '/docs/core/plugins.md', + '/docs/core/theme.md', + '/docs/core/images.md', + '/docs/core/configuration.md', + '/docs/core/visitor.md', + '/docs/core/spans-factory.md', + '/docs/core/html-renderer.md' ] }, '/docs/ext-latex/', @@ -57,8 +72,7 @@ module.exports = { }, '/docs/recycler/recycler.md', '/docs/syntax-highlight/syntax-highlight.md', - '/docs/migration-2-3.md', - ['/docs/v2/install.md', 'Legacy 2.x.x documentation'] + '/docs/migration-2-3.md' ] }, sidebarDepth: 2, diff --git a/docs/docs/configure.md b/docs/docs/configure.md deleted file mode 100644 index 1d09e5fc..00000000 --- a/docs/docs/configure.md +++ /dev/null @@ -1,226 +0,0 @@ -# Configuration - -`SpannableConfiguration` is the core component that controls how markdown is parsed and rendered. -It can be obtained via factory methods: - -```java -// creates default implementation -final SpannableConfiguration configuration = SpannableConfiguration.create(context); -``` - -```java -// creates configurablable instance via `#builder` method -final SpannableConfiguration configuration = SpannableConfiguration.builder(context) - .asyncDrawableLoader(AsyncDrawableLoader.create()) - .build(); -``` - -:::tip Note -If `#builder` factory method is used, you do not need to specify default -values as they will be applied automatically -::: - -:::warning Images -If you plan on using images inside your markdown/HTML, you will have to **explicitly** -register an implementation of `AsyncDrawable.Loader` via `#asyncDrawableLoader` builder method. -`Markwon` comes with ready implementation for that and it can be found in -`markwon-image-loader` module. Refer to module [documentation](/docs/image-loader.md) -::: - -## Theme - -`SpannableTheme` controls how markdown is rendered. It has pretty extensive number of -options that can be found [here](/docs/theme.md) - -```java -SpannableConfiguration.builder(context) - .theme(SpannableTheme) - .build(); -``` - -If `SpannableTheme` is not provided explicitly, `SpannableTheme.create(context)` will be used - -## Images - -### Async loader - -`AsyncDrawable.Loader` handles images in your markdown and HTML - -```java -SpannableConfiguration.builder(context) - .asyncDrawableLoader(AsyncDrawable.Loader) - .build(); -``` - -If `AsyncDrawable.Loader` is not provided explicitly, default **no-op** implementation will be used. - -:::tip Implementation -There are no restrictions on what implementation to use, but `Markwon` has artifact that can -answer the most common needs of displaying SVG, GIF and other image formats. It can be found [here](/docs/image-loader.md) -::: - -### Size resolver - -`ImageSizeResolver` controls the size of an image to be displayed. Currently it -handles only HTML images (specified via `img` tag). - -```java -SpannableConfiguration.builder(context) - .imageSizeResolver(ImageSizeResolver) - .build(); -``` - -If not provided explicitly, default `ImageSizeResolverDef` implementation will be used. -It handles 3 dimention units: -* `%` (percent) -* `em` (relative to text size) -* `px` (absolute size, every dimention that is not `%` or `em` is considered to be _absolute_) - -```html - - - -``` - -`ImageSizeResolverDef` keeps the ratio of original image if one of the dimentions is missing. - -:::warning Height% -There is no support for `%` units for `height` dimention. 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-refence from which to _calculate_ image height. -::: - -## Syntax highlight - -`SyntaxHighlight` controls the syntax highlight for code blocks (in markdown). - -```java -SpannableConfiguration.builder(context) - .syntaxHighlight(SyntaxHighlight) - .build(); -``` - -If not provided explicitly, default **no-op** implementation will be used. - -:::tip Syntax highlight -Although `SyntaxHighlight` interface was included with the very first version -of `Markwon` there were no ready-to-use implementations. But starting with -`Markwon` provides one. It can be found in `markwon-syntax-highlight` artifact. Refer -to module [documentation](/docs/syntax-highlight.md) -::: - -## Link resolver - -`LinkSpan.Resolver` is triggered when a link is clicked in markdown/HTML. - -```java -SpannableConfiguration.builder(context) - .linkResolver(LinkSpan.Resolver) - .build(); -``` - -If not provided explicitly, default `LinkResolverDef` implementation will be used. -Underneath it constructs an `Intent` and _tries_ to start an Activity associated with it. -It no Activity is found, it will silently fail (no runtime exceptions) - -## URL processor - -`UrlProcessor` is used to process found URLs in markdown/HTML. - -```java -SpannableConfiguration.builder(context) - .urlProcessor(UrlProcessor) - .build(); -``` - -If not provided explicitly, default **no-op** implementation will be used. - -`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 - -## Factory - -`SpannableFactory` is used to control _what_ span implementations to be used - -```java -SpannableConfiguration.builder(context) - .factory(SpannableFactory) - .build(); -``` - -If not provided explicitly, default `SpannableFactoryDef` implementation will be used. It is documented -in [this section](/docs/factory.md) - -## Soft line break - -`softBreakAddsNewLine` option controls how _soft breaks_ are treated in the final result. -If `true` -> soft break will add a new line, else it will add a ` ` (space) char. - -```java -SpannableConfiguration.builder(context) - .softBreakAddsNewLine(boolean) - .build(); -``` - -If not provided explicitly, default `false` value will be used. - - - -## HTML - -### Parser - -`MarkwonHtmlParser` is used to parse HTML content - -```java -SpannableConfiguration.builder(context) - .htmlParser(MarkwonHtmlParser) - .build(); -``` - -if not provided explicitly, default `MarkwonHtmlParserImpl` will be used -**if** it can be found in classpath, otherwise default **no-op** implementation -wiil be used. Refer to [HTML](/docs/html.md#parser) document for more information about this behavior. - -### Renderer - -`MarkwonHtmlRenderer` controls how parsed HTML content will be rendered. - -```java -SpannableConfiguration.builder(context) - .htmlRenderer(MarkwonHtmlRenderer) - .build(); -``` - -If not provided explicitly, default `MarkwonHtmlRenderer` implementation will be used. -It is documented [here](/docs/html.md#renderer) - -### HTML allow non-closed tags - -`htmlAllowNonClosedTags` option is used to control whether or not to -render non-closed HTML tags - -```java -SpannableConfiguration.builder(context) - .htmlAllowNonClosedTags(boolean) - .build(); -``` - -If not provided explicitly, default value `false` will be used (non-closed tags **won't** be rendered). diff --git a/docs/docs/core/configuration.md b/docs/docs/core/configuration.md new file mode 100644 index 00000000..af4abbfe --- /dev/null +++ b/docs/docs/core/configuration.md @@ -0,0 +1 @@ +# Configuration \ No newline at end of file diff --git a/docs/docs/core/getting-started.md b/docs/docs/core/getting-started.md index 363e22f4..75b8cf71 100644 --- a/docs/docs/core/getting-started.md +++ b/docs/docs/core/getting-started.md @@ -81,39 +81,28 @@ rawInput = plugins.reduce(rawInput, (input, plugin) -> plugin.processMarkdown(in // 1. after input is processed it's being parsed to a Node node = parser.parse(rawInput); -// 2. each plugin will configure RenderProps -plugins.forEach(plugin -> plugin.configureRenderProps(renderProps)); - -// 3. each plugin will be able to inspect or manipulate resulting Node +// 2. each plugin will be able to inspect or manipulate resulting Node // before rendering plugins.forEach(plugin -> plugin.beforeRender(node)); -// 4. node is being visited by a visitor +// 3. node is being visited by a visitor node.accept(visitor); -// 5. each plugin will be called after node is being visited (aka rendered) +// 4. each plugin will be called after node is being visited (aka rendered) plugins.forEach(plugin -> plugin.afterRender(node, visitor)); -// 6. styled markdown ready at this point +// 5. styled markdown ready at this point final Spanned markdown = visitor.markdown(); -// 7. each plugin will be called before styled markdown is applied to a TextView +// 6. each plugin will be called before styled markdown is applied to a TextView plugins.forEach(plugin -> plugin.beforeSetText(textView, markdown)); -// 8. markdown is applied to a TextView +// 7. markdown is applied to a TextView textView.setText(markdown); -// 9. each plugin will be called after markdown is applied to a TextView +// 8. each plugin will be called after markdown is applied to a TextView plugins.forEach(plugin -> plugin.afterSetText(textView)); ``` As you can see a `plugin` is what lifts the most weight. We will cover plugins next. - -:::tip Note -If you are having trouble with `LinkMovementMethod` you can use -`Markwon.setText(textView, markdown, movementMethod)` method to specify _no_ movement -method (aka `null`) or own implementation. As an alternative to the system `LinkMovementMethod` -you can use [Better-Link-Movement-Method](https://github.com/saket/Better-Link-Movement-Method). -Please note that `Markwon.setText` method expects _parsed_ markdown as the second argument. -::: \ No newline at end of file diff --git a/docs/docs/core/html-renderer.md b/docs/docs/core/html-renderer.md new file mode 100644 index 00000000..3734eff5 --- /dev/null +++ b/docs/docs/core/html-renderer.md @@ -0,0 +1 @@ +# HTML Renderer \ No newline at end of file diff --git a/docs/docs/core/images.md b/docs/docs/core/images.md new file mode 100644 index 00000000..e8c87380 --- /dev/null +++ b/docs/docs/core/images.md @@ -0,0 +1 @@ +# Images diff --git a/docs/docs/core/plugins.md b/docs/docs/core/plugins.md new file mode 100644 index 00000000..1ea64d07 --- /dev/null +++ b/docs/docs/core/plugins.md @@ -0,0 +1,424 @@ +# 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) + .usePlugin(CorePlugin.create()) + .build(); +``` + +All the process of transforming _raw_ markdown into a styled text (Spanned) +will go through plugins. A plugin can: + +* [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) +* [configure `MarkwonHtmlRenderer` (utility to properly display HTML in markdown)](#html-renderer) + +--- + +* [declare a dependency on another plugin (will be used as a runtime validator)](#priority) + +--- + +* [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. +::: + +## Parser + +For example, let's register a new commonmark-java Parser extension: + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(CorePlugin.create()) + .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(); +``` + +:::warning +`CorePlugin` has special handling - it will be **implicitly** added +if a plugin declares dependency on it. This is why in previous example we haven't +added CorePlugin _explicitly_ as `AbstractMarkwonPlugin` declares a dependency on it. +If it's not desireable override `AbstractMarkwonPlugin#priority` method to specify own rules. +::: + +More information about `MarkwonTheme` can be found [here](/docs/core/theme.md). + + +## Images + +Since core images functionality moved to the `core` module. +Now `Markwon` comes bundled with support for regular images (no `SVG` or `GIF`, they +defined in standalone modules now). And 3(4) schemes supported by default: +* http (+https; using system built-in `HttpURLConnection`) +* file (including Android assets) +* data (image inline, `data:image/svg+xml;base64,!@#$%^&*(`) + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { + // sorry, these are not bundled with the library + builder + .addSchemeHandler("ftp", new FtpSchemeHandler("root", "")) + .addMediaDecoder("text/plain", new AnsiiMediaDecoder()); + } + }) + .build(); +``` + +:::warning +Although `ImagesPlugin` is bundled with the `core` artifact, it is **not** used by default +and one must **explicitly** add it: + +```java +Markwon.builder(context) + .usePlugin(ImagesPlugin.create(context)); +``` + +Without explicit usage of `ImagesPlugin` all image configuration will be ignored (no-op'ed) +::: + +More information about dealing with images can be found [here](/docs/core/images.md) + + +## Configuration + +`MarkwonConfiguration` is a set of common tools that are used by different parts +of `Markwon`. It allows configurations of these: + +* `SyntaxHighlight` (highlighting code blocks) +* `LinkResolver` (opens links in markdown) +* `UrlProcessor` (process URLs in markdown for both links and images) +* `MarkwonHtmlParser` (HTML parser) +* `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) { + // MarkwonHtmlParserImpl is defined in `markwon-html` artifact + builder.htmlParser(MarkwonHtmlParserImpl.create()); + } + }) + .build(); +``` + +More information about `MarkwonConfiguration` can be found [here](/docs/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) { + 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, +we can disable `Heading` Node rendering: + +```java +builder.on(Heading.class, null); +``` + +Please note that `Priority` plays nicely here to ensure that your +custom Node override/disable happens _after_ some plugin defines it. +::: + +More information about `MarkwonVisitor` can be found [here](/docs/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/core/spans-factory.md) + + +## HTML Renderer + +`MarkwonHtmlRenderer` controls how HTML is rendered in markdown. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(HtmlPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) { + //
tag handling (deprecated but valid in our case) + // can be any tag name, there is no connection with _real_ HTML tags, + // + builder.addHandler("center", new SimpleTagHandler() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) { + return new AlignmentSpan() { + @Override + public Layout.Alignment getAlignment() { + return Layout.Alignment.ALIGN_CENTER; + } + }; + } + }); + } + }) + .build(); +``` + +:::danger +Although `MarkwonHtmlRenderer` is bundled with `core` artifact, actual +HTML parser is placed in a standalone artifact and must be added to your +project **explicitly** and then registered via `Markwon.Builder#usePlugin(HtmlPlugin.create())`. +If not done so, no HTML will be parsed nor rendered. +::: + +More information about HTML rendering can be found [here](/docs/core/html-renderer.md) + + +## Priority + +`Priority` is an abstraction to _state_ dependency connection between plugins. It is +also used as a runtime graph validator. If a plugin defines a dependency on other, but +_other_ is not in resulting `Markwon` instance, then a runtime exception will be thrown. +`Priority` is also defines the order in which plugins will be placed. So, if a plugin `A` +states a plugin `B` as a dependency, then plugin `A` will come **after** plugin `B`. + +```java +final Markwon markwon = Markwon.builder(context) + .usePlugin(new AbstractMarkwonPlugin() { + @NonNull + @Override + public Priority priority() { + return Priority.after(CorePlugin.class); + } + }) + .build(); +``` + +:::warning +Please note that `AbstractMarkwonPlugin` _implicitly_ defines `CorePlugin` +as a dependency (`return Priority.after(CorePlugin.class);`). This will +also add `CorePlugin` to a `Markwon` instance, because it will be added +_implicitly_ if a plugin defines it as a dependency. +::: + +Use one of the factory methods to create a `Priority` instance: + +```java +// none +Priority.none(); + +// single dependency +Priority.after(CorePlugin.class); + +// 2 dependencies +Priority.after(CorePlugin.class, ImagesPlugin.class); + +// for a number >2, use #builder +Priority.builder() + .after(CorePlugin.class) + .after(ImagesPlugin.class) + .after(StrikethroughPlugin.class) + .build(); +``` + +## 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. +::: \ No newline at end of file diff --git a/docs/docs/core/spans-factory.md b/docs/docs/core/spans-factory.md new file mode 100644 index 00000000..3b326736 --- /dev/null +++ b/docs/docs/core/spans-factory.md @@ -0,0 +1 @@ +# Spans Factory \ No newline at end of file diff --git a/docs/docs/core/visitor.md b/docs/docs/core/visitor.md new file mode 100644 index 00000000..3e8ba057 --- /dev/null +++ b/docs/docs/core/visitor.md @@ -0,0 +1 @@ +# Visitor \ No newline at end of file diff --git a/docs/docs/factory.md b/docs/docs/factory.md deleted file mode 100644 index edf4a018..00000000 --- a/docs/docs/factory.md +++ /dev/null @@ -1,61 +0,0 @@ -# Factory - -`SpannableFactory` is used to create Span implementations. - -```java -SpannableConfiguration.builder(context) - .factory(SpannableFactory) - .build(); -``` - -`Markwon` provides default `SpannableFactoryDef` implementation that is -used by default. - -Spans: -* `strongEmphasis` -* `emphasis` -* `blockQuote` -* `code` -* `orderedListItem` -* `bulletListItem` -* `thematicBreak` -* `heading` -* `strikethrough` -* `taskListItem` -* `tableRow` -* `paragraph` -* `image` -* `link` -* `superScript` (HTML content only) -* `subScript` (HTML content only) -* `underline` (HTML content only) - -:::tip -`SpannableFactory` can be used to ignore some kinds of text markup. If, for example, -you do not wish to apply _emphasis_ styling to your final result, just return `null` -from `emphasis` factory method: -```java -@Nullable -@Override -public Object emphasis() { - return null; -} -``` -::: - -:::tip -All factory methods in `SpannableFactory` return an `Object`, but you can actually -return an **array of Objects** if you wish to apply multiple Spans to a single styling node. -For example, let's make all _emphasis_ also red: - -```java -@Nullable -@Override -public Object emphasis() { - return new Object[] { - super.emphasis(), - new ForegroundColorSpan(Color.RED) - }; -} -``` -::: \ No newline at end of file diff --git a/docs/docs/migration-2-3.md b/docs/docs/migration-2-3.md index c7543794..8ca12b36 100644 --- a/docs/docs/migration-2-3.md +++ b/docs/docs/migration-2-3.md @@ -1 +1,8 @@ -# Migration 2.x.x -> 3.x.x \ No newline at end of file +# Migration 2.x.x -> 3.x.x + +* strikethrough moved to standalone module +* tables moved to standalone module +* core functionality of `AsyncDrawableLoader` moved to `core` module +* * Handling of GIF and SVG media moved to standalone modules (`gif` and `svg` respectively) +* * OkHttpClient to download images moved to standalone module +* HTML no longer _implicitly_ added to core functionality, it must be specified __explicitly__ (as an artifact) \ No newline at end of file diff --git a/docs/docs/v2/install.md b/docs/docs/v2/README.md similarity index 100% rename from docs/docs/v2/install.md rename to docs/docs/v2/README.md diff --git a/docs/donate.md b/docs/donate.md new file mode 100644 index 00000000..12b79839 --- /dev/null +++ b/docs/donate.md @@ -0,0 +1,7 @@ +# Donations + +If you find this library useful consider making a donation +to your local [environmental](https://en.wikipedia.org/wiki/List_of_environmental_organizations) +or [human rights](https://en.wikipedia.org/wiki/List_of_human_rights_organisations) organization. + +Thank you \ No newline at end of file diff --git a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java b/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java index 6f6573c3..5492d4d0 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java +++ b/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java @@ -57,11 +57,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin { } - @Override - public void configureRenderProps(@NonNull RenderProps renderProps) { - - } - @NonNull @Override public Priority priority() { diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java index 8d5211d8..a51bd62a 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java @@ -46,14 +46,7 @@ class MarkwonImpl extends Markwon { @Override public Spanned render(@NonNull Node node) { - final RenderProps renderProps = visitor.renderProps(); - for (MarkwonPlugin plugin : plugins) { - - // let plugins apply render properties before rendering (as we will clear - // renderProps after rendering) - plugin.configureRenderProps(renderProps); - plugin.beforeRender(node); } diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java index d887e619..0804848b 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java @@ -82,19 +82,6 @@ public interface MarkwonPlugin { */ void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder); - /** - * A method to store some arbitrary data in {@link RenderProps}. Although it won\'t make - * much sense to use existing {@link Prop} keys for {@link SpanFactory}, it can be helpful - * to establish a communication channel between multiple plugins in decoupled way (provide - * some initial properties for example or indicate that certain plugin is registered). - *

- * This method will be called before each rendering step (after rendering {@link RenderProps} - * will be cleared. This method won\'t be called during initialization stage. - * - * @see RenderProps - */ - void configureRenderProps(@NonNull RenderProps renderProps); - @NonNull Priority priority(); diff --git a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java index 9d641826..931fd092 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java +++ b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java @@ -3,6 +3,8 @@ package ru.noties.markwon.html; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import java.util.Collection; + import ru.noties.markwon.MarkwonVisitor; /** @@ -39,19 +41,11 @@ public abstract class MarkwonHtmlRenderer { @NonNull Builder allowNonClosedTags(boolean allowNonClosedTags); - /** - * Please note that if there is already a {@link TagHandler} registered with specified - * {@code tagName} it will be replaced with newly supplied one. - * - * @param tagHandler {@link TagHandler} - * @param tagName name of a tag - * @return self - */ @NonNull - Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName); + Builder addHandler(@NonNull String tagName, @NonNull TagHandler tagHandler); @NonNull - Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames); + Builder addHandler(@NonNull Collection tagNames, @NonNull TagHandler tagHandler); @NonNull Builder removeHandler(@NonNull String tagName); diff --git a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java index be78caec..ca94f902 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java +++ b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java @@ -3,6 +3,7 @@ package ru.noties.markwon.html; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -99,14 +100,14 @@ class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { @NonNull @Override - public Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName) { + public Builder addHandler(@NonNull String tagName, @NonNull TagHandler tagHandler) { tagHandlers.put(tagName, tagHandler); return this; } @NonNull @Override - public Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames) { + public Builder addHandler(@NonNull Collection tagNames, @NonNull TagHandler tagHandler) { for (String tagName : tagNames) { if (tagName != null) { tagHandlers.put(tagName, tagHandler); diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java index e451f74e..108c43b8 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java @@ -223,7 +223,6 @@ public class MarkwonBuilderImplTest { verify(plugin, atLeast(1)).priority(); // note, no render props -> they must be configured on render stage - verify(plugin, times(0)).configureRenderProps(any(RenderProps.class)); verify(plugin, times(0)).processMarkdown(anyString()); verify(plugin, times(0)).beforeRender(any(Node.class)); verify(plugin, times(0)).afterRender(any(Node.class), any(MarkwonVisitor.class)); diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java index f0771efa..d80cb765 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java @@ -107,8 +107,6 @@ public class MarkwonImplTest { // mark this flag (we must ensure that this method body is executed) flag.set(true); - //noinspection ConstantConditions - verify(plugin, times(1)).configureRenderProps(null); verify(plugin, times(1)).beforeRender(eq(node)); verify(plugin, times(0)).afterRender(any(Node.class), any(MarkwonVisitor.class)); @@ -175,8 +173,6 @@ public class MarkwonImplTest { flag.set(true); - verify(visitor, times(1)).renderProps(); - verify(plugin, times(1)).configureRenderProps(eq(renderProps)); verify(renderProps, times(0)).clearAll(); return null; diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java index c0481ff2..64c266b8 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java @@ -186,6 +186,7 @@ public class CorePluginTest { add("configureVisitor"); add("configureSpansFactory"); add("beforeSetText"); + add("afterSetText"); add("priority"); }}; diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java b/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java index f69b2368..032fd68e 100644 --- a/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java +++ b/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java @@ -22,6 +22,8 @@ import ru.noties.markwon.html.tag.SubScriptHandler; import ru.noties.markwon.html.tag.SuperScriptHandler; import ru.noties.markwon.html.tag.UnderlineHandler; +import static java.util.Arrays.asList; + /** * @since 3.0.0 */ @@ -41,18 +43,41 @@ public class HtmlPlugin extends AbstractMarkwonPlugin { @Override public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) { + builder - .addHandler(new EmphasisHandler(), "i", "em", "cite", "dfn") - .addHandler(new StrongEmphasisHandler(), "b", "strong") - .addHandler(new SuperScriptHandler(), "sup") - .addHandler(new SubScriptHandler(), "sub") - .addHandler(new UnderlineHandler(), "u", "ins") - .addHandler(new StrikeHandler(), "s", "del") - .addHandler(new LinkHandler(), "a") - .addHandler(new ListHandler(), "ul", "ol") - .addHandler(ImageHandler.create(), "img") - .addHandler(new BlockquoteHandler(), "blockquote") - .addHandler(new HeadingHandler(), "h1", "h2", "h3", "h4", "h5", "h6"); + .addHandler( + "img", + ImageHandler.create()) + .addHandler( + "a", + new LinkHandler()) + .addHandler( + "blockquote", + new BlockquoteHandler()) + .addHandler( + "sub", + new SubScriptHandler()) + .addHandler( + "sup", + new SuperScriptHandler()) + .addHandler( + asList("b", "strong"), + new StrongEmphasisHandler()) + .addHandler( + asList("s", "del"), + new StrikeHandler()) + .addHandler( + asList("u", "ins"), + new UnderlineHandler()) + .addHandler( + asList("ul", "ol"), + new ListHandler()) + .addHandler( + asList("i", "em", "cite", "dfn"), + new EmphasisHandler()) + .addHandler( + asList("h1", "h2", "h3", "h4", "h5", "h6"), + new HeadingHandler()); } @Override