From e0563dca435c3378b07ef7f040964eabdd15fa1f Mon Sep 17 00:00:00 2001 From: Dimitry Date: Mon, 17 Sep 2018 13:15:58 +0300 Subject: [PATCH] V2.0.0 (#66) * Add `html-parser-api` and `html-parser-impl` modules * Add `HtmlEmptyTagReplacement` * Implement Appendable and CharSequence in SpannableBuilder * Renamed library modules to reflect maven artifact names * Rename `markwon-syntax` to `markwon-syntax-highlight` * Add HtmlRenderer asbtraction * Add CssInlineStyleParser * Fix Theme#listItemColor and OL * Fix task list block parser to revert parsing state when line is not matching * Defined test format files * image-loader add datauri parser * image-loader add support for inline data uri image references * Add travis configuration * Fix image with width greater than canvas scaled * Fix blockquote span * Dealing with white spaces at the end of a document * image-loader add SchemeHandler abstraction * Add sample-latex-math module --- .gitignore | 4 +- .travis.yml | 21 + README.md | 206 +- app/build.gradle | 34 +- app/src/main/res/layout/activity_main.xml | 5 +- art/favicon.svg | 109 + .../art => art}/markwon-syntax-darkula.png | Bin .../art => art}/markwon-syntax-default.png | Bin build.gradle | 84 +- docs/.vuepress/components/GithubIssue.vue | 34 + docs/.vuepress/components/GithubPull.vue | 27 + docs/.vuepress/components/GithubUser.vue | 21 + docs/.vuepress/components/Link.vue | 56 + docs/.vuepress/components/MavenBadge.vue | 19 + docs/.vuepress/components/MavenBadges.vue | 20 + docs/.vuepress/components/ThemeProperty.vue | 15 + docs/.vuepress/config.js | 37 + docs/.vuepress/override.styl | 2 + .../public/android-chrome-192x192.png | Bin 0 -> 7928 bytes .../public/android-chrome-512x512.png | Bin 0 -> 17037 bytes docs/.vuepress/public/apple-touch-icon.png | Bin 0 -> 7368 bytes docs/.vuepress/public/art | 1 + docs/.vuepress/public/favicon-16x16.png | Bin 0 -> 712 bytes docs/.vuepress/public/favicon-32x32.png | Bin 0 -> 1224 bytes docs/.vuepress/public/favicon.ico | Bin 0 -> 15086 bytes docs/.vuepress/public/manifest.json | 17 + docs/.vuepress/style.styl | 0 docs/AsyncDrawableLoader.md | 54 - docs/CHANGELOG.md | 72 + docs/HtmlParser.md | 60 - docs/LinkResolver.md | 28 - docs/README.md | 70 + docs/SpannableConfiguration.md | 43 - docs/SpannableTheme.md | 136 - docs/SyntaxHighlight.md | 37 - docs/UrlProcessor.md | 40 - docs/deploy.sh | 19 + docs/docs/configure.md | 226 + docs/docs/factory.md | 61 + docs/docs/getting-started.md | 97 + docs/docs/html.md | 303 + docs/docs/image-loader.md | 243 + docs/docs/install.md | 109 + docs/docs/syntax-highlight.md | 69 + docs/docs/theme.md | 212 + docs/docs/view.md | 41 + docs/package-lock.json | 10685 ++++++++++++++++ docs/package.json | 9 + gradle.properties | 2 +- library-image-loader/build.gradle | 34 - library-syntax/build.gradle | 28 - library-syntax/gradle.properties | 3 - library-view/build.gradle | 27 - library/build.gradle | 29 - .../markwon/renderer/html/BoldProvider.java | 22 - .../renderer/html/ImageProviderImpl.java | 224 - .../renderer/html/ItalicsProvider.java | 22 - .../markwon/renderer/html/LinkProvider.java | 49 - .../renderer/html/SpannableHtmlParser.java | 332 - .../markwon/renderer/html/StrikeProvider.java | 22 - .../renderer/html/SubScriptProvider.java | 22 - .../renderer/html/SuperScriptProvider.java | 22 - .../markwon/renderer/html/TagParser.java | 155 - .../renderer/html/UnderlineProvider.java | 22 - markwon-html-parser-api/build.gradle | 23 + markwon-html-parser-api/gradle.properties | 3 + .../src/main/AndroidManifest.xml | 1 + .../ru/noties/markwon/html/api/HtmlTag.java | 94 + .../markwon/html/api/MarkwonHtmlParser.java | 60 + .../html/api/MarkwonHtmlParserNoOp.java | 32 + markwon-html-parser-impl/build.gradle | 31 + markwon-html-parser-impl/gradle.properties | 3 + .../src/main/AndroidManifest.xml | 1 + .../markwon/html/impl/AppendableUtils.java | 35 + .../html/impl/HtmlEmptyTagReplacement.java | 55 + .../noties/markwon/html/impl/HtmlTagImpl.java | 210 + .../html/impl/MarkwonHtmlParserImpl.java | 486 + .../markwon/html/impl/TrimmingAppender.java | 68 + .../html/impl/jsoup/UncheckedIOException.java | 13 + .../html/impl/jsoup/helper/Normalizer.java | 18 + .../html/impl/jsoup/helper/Validate.java | 112 + .../html/impl/jsoup/nodes/Attribute.java | 202 + .../html/impl/jsoup/nodes/Attributes.java | 441 + .../impl/jsoup/nodes/CommonMarkEntities.java | 50 + .../html/impl/jsoup/nodes/DocumentType.java | 104 + .../impl/jsoup/parser/CharacterReader.java | 501 + .../html/impl/jsoup/parser/ParseError.java | 41 + .../impl/jsoup/parser/ParseErrorList.java | 34 + .../markwon/html/impl/jsoup/parser/Token.java | 398 + .../html/impl/jsoup/parser/Tokeniser.java | 295 + .../impl/jsoup/parser/TokeniserState.java | 1737 +++ .../impl/HtmlEmptyTagReplacementTest.java | 47 + .../html/impl/MarkwonHtmlParserImplTest.java | 885 ++ .../html/impl/TrimmingAppenderTest.java | 43 + .../jsoup/nodes/CommonMarkEntitiesTest.java | 22 + markwon-image-loader/build.gradle | 37 + .../gradle.properties | 0 .../src/main/AndroidManifest.xml | 0 .../markwon/il/AsyncDrawableLoader.java | 332 +- .../java/ru/noties/markwon/il/DataUri.java | 60 + .../ru/noties/markwon/il/DataUriDecoder.java | 41 + .../ru/noties/markwon/il/DataUriParser.java | 79 + .../markwon/il/DataUriSchemeHandler.java | 70 + .../ru/noties/markwon/il/DrawableUtils.java | 0 .../noties/markwon/il/FileSchemeHandler.java | 109 + .../ru/noties/markwon/il/GifMediaDecoder.java | 1 + .../java/ru/noties/markwon/il/ImageItem.java | 39 + .../noties/markwon/il/ImageMediaDecoder.java | 2 + .../ru/noties/markwon/il/MediaDecoder.java | 0 .../markwon/il/NetworkSchemeHandler.java | 89 + .../ru/noties/markwon/il/SchemeHandler.java | 25 + .../ru/noties/markwon/il/SvgMediaDecoder.java | 1 + .../noties/markwon/il/DataUriParserTest.java | 119 + .../markwon/il/DataUriSchemeHandlerTest.java | 85 + .../README.md | 4 +- markwon-syntax-highlight/build.gradle | 26 + markwon-syntax-highlight/gradle.properties | 3 + .../src/main/AndroidManifest.xml | 0 .../syntax/Prism4jSyntaxHighlight.java | 0 .../markwon/syntax/Prism4jSyntaxVisitor.java | 0 .../noties/markwon/syntax/Prism4jTheme.java | 0 .../markwon/syntax/Prism4jThemeBase.java | 0 .../markwon/syntax/Prism4jThemeDarkula.java | 0 .../markwon/syntax/Prism4jThemeDefault.java | 0 {library-view => markwon-view}/README.md | 0 markwon-view/build.gradle | 25 + .../gradle.properties | 0 .../debug/DebugConfigurationProvider.java | 0 .../res/layout/debug_markwon_preview.xml | 0 .../layout/debug_markwon_preview_compat.xml | 0 .../src/debug/res/values/debug_strings.xml | 0 .../src/main/AndroidManifest.xml | 0 .../ru/noties/markwon/view/IMarkwonView.java | 0 .../ru/noties/markwon/view/MarkwonView.java | 0 .../markwon/view/MarkwonViewCompat.java | 0 .../markwon/view/MarkwonViewHelper.java | 0 .../src/main/res/values/attrs.xml | 0 markwon/build.gradle | 40 + {library => markwon}/gradle.properties | 0 .../src/main/AndroidManifest.xml | 0 .../markwon/AsyncDrawableLoaderNoOp.java | 0 .../ru/noties/markwon/DrawablesScheduler.java | 0 .../ru/noties/markwon/LinkResolverDef.java | 0 .../main/java/ru/noties/markwon/Markwon.java | 0 .../ru/noties/markwon/SpannableBuilder.java | 100 +- .../markwon/SpannableConfiguration.java | 101 +- .../ru/noties/markwon/SpannableFactory.java | 0 .../noties/markwon/SpannableFactoryDef.java | 0 .../markwon/SpannableStringBuilderImpl.java | 0 .../ru/noties/markwon/SpannedReversed.java | 0 .../ru/noties/markwon/SyntaxHighlight.java | 0 .../noties/markwon/SyntaxHighlightNoOp.java | 0 .../ru/noties/markwon/TableRowsScheduler.java | 0 .../java/ru/noties/markwon/UrlProcessor.java | 0 .../markwon/UrlProcessorAndroidAssets.java | 8 +- .../ru/noties/markwon/UrlProcessorNoOp.java | 0 .../UrlProcessorRelativeToAbsolute.java | 0 .../ru/noties/markwon/renderer/ImageSize.java | 0 .../markwon/renderer/ImageSizeResolver.java | 0 .../renderer/ImageSizeResolverDef.java | 19 +- .../renderer/SpannableMarkdownVisitor.java | 193 +- .../markwon/renderer/SpannableRenderer.java | 0 .../renderer/html2/CssInlineStyleParser.java | 171 + .../markwon/renderer/html2/CssProperty.java | 42 + .../renderer/html2/MarkwonHtmlRenderer.java | 101 + .../html2/MarkwonHtmlRendererImpl.java | 88 + .../renderer/html2/tag/BlockquoteHandler.java | 28 + .../renderer/html2/tag/EmphasisHandler.java | 15 + .../renderer/html2/tag/HeadingHandler.java | 22 + .../renderer/html2/tag/ImageHandler.java | 59 + .../html2/tag/ImageSizeParserImpl.java | 110 + .../renderer/html2/tag/LinkHandler.java | 24 + .../renderer/html2/tag/ListHandler.java | 66 + .../renderer/html2/tag/SimpleTagHandler.java | 22 + .../renderer/html2/tag/StrikeHandler.java | 28 + .../html2/tag/StrongEmphasisHandler.java | 15 + .../renderer/html2/tag/SubScriptHandler.java | 15 + .../html2/tag/SuperScriptHandler.java | 15 + .../renderer/html2/tag/TagHandler.java | 38 + .../renderer/html2/tag/UnderlineHandler.java | 31 + .../noties/markwon/spans/AsyncDrawable.java | 36 +- .../markwon/spans/AsyncDrawableSpan.java | 0 .../noties/markwon/spans/BlockQuoteSpan.java | 2 + .../markwon/spans/BulletListItemSpan.java | 0 .../ru/noties/markwon/spans/CanvasUtils.java | 0 .../ru/noties/markwon/spans/CodeSpan.java | 0 .../ru/noties/markwon/spans/ColorUtils.java | 0 .../ru/noties/markwon/spans/EmphasisSpan.java | 0 .../ru/noties/markwon/spans/HeadingSpan.java | 0 .../markwon/spans/LeadingMarginUtils.java | 0 .../ru/noties/markwon/spans/LinkSpan.java | 0 .../ru/noties/markwon/spans/ObjectsPool.java | 0 .../markwon/spans/OrderedListItemSpan.java | 7 +- .../noties/markwon/spans/SpannableTheme.java | 0 .../markwon/spans/StrongEmphasisSpan.java | 0 .../noties/markwon/spans/SubScriptSpan.java | 0 .../noties/markwon/spans/SuperScriptSpan.java | 0 .../ru/noties/markwon/spans/TableRowSpan.java | 0 .../markwon/spans/TaskListDrawable.java | 0 .../ru/noties/markwon/spans/TaskListSpan.java | 0 .../markwon/spans/ThematicBreakSpan.java | 0 .../markwon/tasklist/TaskListBlock.java | 0 .../markwon/tasklist/TaskListBlockParser.java | 4 +- .../markwon/tasklist/TaskListExtension.java | 0 .../noties/markwon/tasklist/TaskListItem.java | 0 .../src/main/res/values/ids.xml | 0 .../UrlProcessorAndroidAssetsTest.java | 49 + .../UrlProcessorRelativeToAbsoluteTest.java | 61 + .../renderer/ImageSizeResolverDefTest.java | 147 + .../html2/CssInlineStyleParserTest.java | 239 + .../html2/tag/ImageSizeParserImplTest.java | 186 + .../visitor/SpannableMarkdownVisitorTest.java | 95 + .../markwon/renderer/visitor/TestConfig.java | 31 + .../markwon/renderer/visitor/TestData.java | 45 + .../renderer/visitor/TestDataReader.java | 351 + .../markwon/renderer/visitor/TestFactory.java | 193 + .../markwon/renderer/visitor/TestNode.java | 140 + .../markwon/renderer/visitor/TestSpan.java | 59 + .../renderer/visitor/TestValidator.java | 199 + .../ru/noties/markwon/test/TestUtils.java | 17 + .../src/test/resources/tests/bold-italic.yaml | 5 + .../src/test/resources/tests/code-blocks.yaml | 17 + .../test/resources/tests/deeply-nested.yaml | 15 + markwon/src/test/resources/tests/first.yaml | 23 + .../tests/html-allow-non-closed-tags.yaml | 18 + .../tests/html-non-closed-ignore.yaml | 12 + markwon/src/test/resources/tests/html.yaml | 105 + .../resources/tests/nested-blockquotes.yaml | 12 + .../test/resources/tests/no-paragraphs.yaml | 12 + .../src/test/resources/tests/ol-2-spaces.yaml | 16 + .../resources/tests/ol-starts-with-5.yaml | 14 + markwon/src/test/resources/tests/ol.yaml | 14 + .../src/test/resources/tests/paragraph.yaml | 12 + markwon/src/test/resources/tests/second.yaml | 32 + .../src/test/resources/tests/single-a.yaml | 5 + .../src/test/resources/tests/single-b.yaml | 4 + .../resources/tests/single-blockquote.yaml | 4 + .../resources/tests/single-code-block.yaml | 7 + .../src/test/resources/tests/single-code.yaml | 4 + .../src/test/resources/tests/single-h1.yaml | 4 + .../src/test/resources/tests/single-h2.yaml | 4 + .../src/test/resources/tests/single-h3.yaml | 4 + .../src/test/resources/tests/single-h4.yaml | 4 + .../src/test/resources/tests/single-h5.yaml | 4 + .../src/test/resources/tests/single-h6.yaml | 4 + .../src/test/resources/tests/single-hr.yaml | 7 + .../src/test/resources/tests/single-i.yaml | 4 + .../src/test/resources/tests/single-img.yaml | 7 + .../src/test/resources/tests/single-ol.yaml | 5 + .../src/test/resources/tests/single-s.yaml | 4 + .../src/test/resources/tests/single-sub.yaml | 7 + .../src/test/resources/tests/single-sup.yaml | 7 + .../resources/tests/single-task-list.yaml | 6 + .../src/test/resources/tests/single-tr.yaml | 13 + .../src/test/resources/tests/single-u.yaml | 7 + .../src/test/resources/tests/single-ul.yaml | 5 + .../tests/soft-break-adds-new-line.yaml | 10 + .../src/test/resources/tests/soft-break.yaml | 7 + markwon/src/test/resources/tests/table.yaml | 51 + .../src/test/resources/tests/ul-levels.yaml | 20 + markwon/src/test/resources/tests/ul.yaml | 14 + sample-custom-extension/build.gradle | 8 +- sample-latex-math/build.gradle | 23 + .../src/main/AndroidManifest.xml | 18 + .../sample/jlatexmath/JLatexMathBlock.java | 16 + .../jlatexmath/JLatexMathBlockParser.java | 79 + .../sample/jlatexmath/JLatexMathMedia.java | 138 + .../sample/jlatexmath/MainActivity.java | 105 + .../src/main/res/layout/activity_main.xml | 14 + .../src/main/res/values/strings.xml | 3 + .../src/main/res/values/styles.xml | 6 + settings.gradle | 4 +- 272 files changed, 23789 insertions(+), 1899 deletions(-) create mode 100644 .travis.yml create mode 100644 art/favicon.svg rename {library-syntax/art => art}/markwon-syntax-darkula.png (100%) rename {library-syntax/art => art}/markwon-syntax-default.png (100%) create mode 100644 docs/.vuepress/components/GithubIssue.vue create mode 100644 docs/.vuepress/components/GithubPull.vue create mode 100644 docs/.vuepress/components/GithubUser.vue create mode 100644 docs/.vuepress/components/Link.vue create mode 100644 docs/.vuepress/components/MavenBadge.vue create mode 100644 docs/.vuepress/components/MavenBadges.vue create mode 100644 docs/.vuepress/components/ThemeProperty.vue create mode 100644 docs/.vuepress/config.js create mode 100644 docs/.vuepress/override.styl create mode 100644 docs/.vuepress/public/android-chrome-192x192.png create mode 100644 docs/.vuepress/public/android-chrome-512x512.png create mode 100644 docs/.vuepress/public/apple-touch-icon.png create mode 120000 docs/.vuepress/public/art create mode 100644 docs/.vuepress/public/favicon-16x16.png create mode 100644 docs/.vuepress/public/favicon-32x32.png create mode 100644 docs/.vuepress/public/favicon.ico create mode 100644 docs/.vuepress/public/manifest.json create mode 100644 docs/.vuepress/style.styl delete mode 100644 docs/AsyncDrawableLoader.md create mode 100644 docs/CHANGELOG.md delete mode 100644 docs/HtmlParser.md delete mode 100644 docs/LinkResolver.md create mode 100644 docs/README.md delete mode 100644 docs/SpannableConfiguration.md delete mode 100644 docs/SpannableTheme.md delete mode 100644 docs/SyntaxHighlight.md delete mode 100644 docs/UrlProcessor.md create mode 100755 docs/deploy.sh create mode 100644 docs/docs/configure.md create mode 100644 docs/docs/factory.md create mode 100644 docs/docs/getting-started.md create mode 100644 docs/docs/html.md create mode 100644 docs/docs/image-loader.md create mode 100644 docs/docs/install.md create mode 100644 docs/docs/syntax-highlight.md create mode 100644 docs/docs/theme.md create mode 100644 docs/docs/view.md create mode 100644 docs/package-lock.json create mode 100644 docs/package.json delete mode 100644 library-image-loader/build.gradle delete mode 100644 library-syntax/build.gradle delete mode 100644 library-syntax/gradle.properties delete mode 100644 library-view/build.gradle delete mode 100644 library/build.gradle delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/BoldProvider.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/ItalicsProvider.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/LinkProvider.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/StrikeProvider.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/SubScriptProvider.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/SuperScriptProvider.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/TagParser.java delete mode 100644 library/src/main/java/ru/noties/markwon/renderer/html/UnderlineProvider.java create mode 100644 markwon-html-parser-api/build.gradle create mode 100644 markwon-html-parser-api/gradle.properties create mode 100644 markwon-html-parser-api/src/main/AndroidManifest.xml create mode 100644 markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java create mode 100644 markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java create mode 100644 markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParserNoOp.java create mode 100644 markwon-html-parser-impl/build.gradle create mode 100644 markwon-html-parser-impl/gradle.properties create mode 100644 markwon-html-parser-impl/src/main/AndroidManifest.xml create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/AppendableUtils.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/TrimmingAppender.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/UncheckedIOException.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Normalizer.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Validate.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attribute.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attributes.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntities.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/DocumentType.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/CharacterReader.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseError.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseErrorList.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Tokeniser.java create mode 100644 markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/TokeniserState.java create mode 100644 markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacementTest.java create mode 100644 markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java create mode 100644 markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/TrimmingAppenderTest.java create mode 100644 markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntitiesTest.java create mode 100644 markwon-image-loader/build.gradle rename {library-image-loader => markwon-image-loader}/gradle.properties (100%) rename {library-image-loader => markwon-image-loader}/src/main/AndroidManifest.xml (100%) rename {library-image-loader => markwon-image-loader}/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java (50%) create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUri.java create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriDecoder.java create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriParser.java create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java rename {library-image-loader => markwon-image-loader}/src/main/java/ru/noties/markwon/il/DrawableUtils.java (100%) create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/FileSchemeHandler.java rename {library-image-loader => markwon-image-loader}/src/main/java/ru/noties/markwon/il/GifMediaDecoder.java (98%) create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/ImageItem.java rename {library-image-loader => markwon-image-loader}/src/main/java/ru/noties/markwon/il/ImageMediaDecoder.java (94%) rename {library-image-loader => markwon-image-loader}/src/main/java/ru/noties/markwon/il/MediaDecoder.java (100%) create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/NetworkSchemeHandler.java create mode 100644 markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java rename {library-image-loader => markwon-image-loader}/src/main/java/ru/noties/markwon/il/SvgMediaDecoder.java (98%) create mode 100644 markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriParserTest.java create mode 100644 markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java rename {library-syntax => markwon-syntax-highlight}/README.md (94%) create mode 100644 markwon-syntax-highlight/build.gradle create mode 100644 markwon-syntax-highlight/gradle.properties rename {library-syntax => markwon-syntax-highlight}/src/main/AndroidManifest.xml (100%) rename {library-syntax => markwon-syntax-highlight}/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxHighlight.java (100%) rename {library-syntax => markwon-syntax-highlight}/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxVisitor.java (100%) rename {library-syntax => markwon-syntax-highlight}/src/main/java/ru/noties/markwon/syntax/Prism4jTheme.java (100%) rename {library-syntax => markwon-syntax-highlight}/src/main/java/ru/noties/markwon/syntax/Prism4jThemeBase.java (100%) rename {library-syntax => markwon-syntax-highlight}/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java (100%) rename {library-syntax => markwon-syntax-highlight}/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDefault.java (100%) rename {library-view => markwon-view}/README.md (100%) create mode 100644 markwon-view/build.gradle rename {library-view => markwon-view}/gradle.properties (100%) rename {library-view => markwon-view}/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java (100%) rename {library-view => markwon-view}/src/debug/res/layout/debug_markwon_preview.xml (100%) rename {library-view => markwon-view}/src/debug/res/layout/debug_markwon_preview_compat.xml (100%) rename {library-view => markwon-view}/src/debug/res/values/debug_strings.xml (100%) rename {library-view => markwon-view}/src/main/AndroidManifest.xml (100%) rename {library-view => markwon-view}/src/main/java/ru/noties/markwon/view/IMarkwonView.java (100%) rename {library-view => markwon-view}/src/main/java/ru/noties/markwon/view/MarkwonView.java (100%) rename {library-view => markwon-view}/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java (100%) rename {library-view => markwon-view}/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java (100%) rename {library-view => markwon-view}/src/main/res/values/attrs.xml (100%) create mode 100644 markwon/build.gradle rename {library => markwon}/gradle.properties (100%) rename {library => markwon}/src/main/AndroidManifest.xml (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/DrawablesScheduler.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/LinkResolverDef.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/Markwon.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/SpannableBuilder.java (74%) rename {library => markwon}/src/main/java/ru/noties/markwon/SpannableConfiguration.java (71%) rename {library => markwon}/src/main/java/ru/noties/markwon/SpannableFactory.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/SpannableFactoryDef.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/SpannedReversed.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/SyntaxHighlight.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/SyntaxHighlightNoOp.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/TableRowsScheduler.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/UrlProcessor.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java (80%) rename {library => markwon}/src/main/java/ru/noties/markwon/UrlProcessorNoOp.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/UrlProcessorRelativeToAbsolute.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/renderer/ImageSize.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/renderer/ImageSizeResolver.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/renderer/ImageSizeResolverDef.java (76%) rename {library => markwon}/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java (81%) rename {library => markwon}/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java (100%) create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/CssInlineStyleParser.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/CssProperty.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/BlockquoteHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImpl.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ListHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SimpleTagHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java rename {library => markwon}/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java (80%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java (98%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/CanvasUtils.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/CodeSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/ColorUtils.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/EmphasisSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/HeadingSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/LinkSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/ObjectsPool.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java (91%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/SpannableTheme.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/StrongEmphasisSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/SubScriptSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/SuperScriptSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/TableRowSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/TaskListSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java (95%) rename {library => markwon}/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java (100%) rename {library => markwon}/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java (100%) rename {library => markwon}/src/main/res/values/ids.xml (100%) create mode 100644 markwon/src/test/java/ru/noties/markwon/UrlProcessorAndroidAssetsTest.java create mode 100644 markwon/src/test/java/ru/noties/markwon/UrlProcessorRelativeToAbsoluteTest.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/ImageSizeResolverDefTest.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/html2/CssInlineStyleParserTest.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImplTest.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestConfig.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestValidator.java create mode 100644 markwon/src/test/java/ru/noties/markwon/test/TestUtils.java create mode 100644 markwon/src/test/resources/tests/bold-italic.yaml create mode 100644 markwon/src/test/resources/tests/code-blocks.yaml create mode 100644 markwon/src/test/resources/tests/deeply-nested.yaml create mode 100644 markwon/src/test/resources/tests/first.yaml create mode 100644 markwon/src/test/resources/tests/html-allow-non-closed-tags.yaml create mode 100644 markwon/src/test/resources/tests/html-non-closed-ignore.yaml create mode 100644 markwon/src/test/resources/tests/html.yaml create mode 100644 markwon/src/test/resources/tests/nested-blockquotes.yaml create mode 100644 markwon/src/test/resources/tests/no-paragraphs.yaml create mode 100644 markwon/src/test/resources/tests/ol-2-spaces.yaml create mode 100644 markwon/src/test/resources/tests/ol-starts-with-5.yaml create mode 100644 markwon/src/test/resources/tests/ol.yaml create mode 100644 markwon/src/test/resources/tests/paragraph.yaml create mode 100644 markwon/src/test/resources/tests/second.yaml create mode 100644 markwon/src/test/resources/tests/single-a.yaml create mode 100644 markwon/src/test/resources/tests/single-b.yaml create mode 100644 markwon/src/test/resources/tests/single-blockquote.yaml create mode 100644 markwon/src/test/resources/tests/single-code-block.yaml create mode 100644 markwon/src/test/resources/tests/single-code.yaml create mode 100644 markwon/src/test/resources/tests/single-h1.yaml create mode 100644 markwon/src/test/resources/tests/single-h2.yaml create mode 100644 markwon/src/test/resources/tests/single-h3.yaml create mode 100644 markwon/src/test/resources/tests/single-h4.yaml create mode 100644 markwon/src/test/resources/tests/single-h5.yaml create mode 100644 markwon/src/test/resources/tests/single-h6.yaml create mode 100644 markwon/src/test/resources/tests/single-hr.yaml create mode 100644 markwon/src/test/resources/tests/single-i.yaml create mode 100644 markwon/src/test/resources/tests/single-img.yaml create mode 100644 markwon/src/test/resources/tests/single-ol.yaml create mode 100644 markwon/src/test/resources/tests/single-s.yaml create mode 100644 markwon/src/test/resources/tests/single-sub.yaml create mode 100644 markwon/src/test/resources/tests/single-sup.yaml create mode 100644 markwon/src/test/resources/tests/single-task-list.yaml create mode 100644 markwon/src/test/resources/tests/single-tr.yaml create mode 100644 markwon/src/test/resources/tests/single-u.yaml create mode 100644 markwon/src/test/resources/tests/single-ul.yaml create mode 100644 markwon/src/test/resources/tests/soft-break-adds-new-line.yaml create mode 100644 markwon/src/test/resources/tests/soft-break.yaml create mode 100644 markwon/src/test/resources/tests/table.yaml create mode 100644 markwon/src/test/resources/tests/ul-levels.yaml create mode 100644 markwon/src/test/resources/tests/ul.yaml create mode 100644 sample-latex-math/build.gradle create mode 100644 sample-latex-math/src/main/AndroidManifest.xml create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java create mode 100644 sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java create mode 100644 sample-latex-math/src/main/res/layout/activity_main.xml create mode 100644 sample-latex-math/src/main/res/values/strings.xml create mode 100644 sample-latex-math/src/main/res/values/styles.xml diff --git a/.gitignore b/.gitignore index c4627857..5059ac5d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ .DS_Store /captures .externalNativeBuild -**/build \ No newline at end of file +**/build +**/dist +**/node_modules \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..93df6e80 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +# https://docs.travis-ci.com/user/languages/android/ +language: android +jdk: openjdk8 +sudo: false + +android: + components: + - tools + - platform-tools + - tools + + - build-tools-27.0.3 + - android-27 + +branches: + except: + - gh-pages + +cache: + directories: + - $HOME/.m2 \ No newline at end of file diff --git a/README.md b/README.md index 1bd0c545..7545d2b4 100644 --- a/README.md +++ b/README.md @@ -4,43 +4,40 @@ [![markwon](https://img.shields.io/maven-central/v/ru.noties/markwon.svg?label=markwon)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon%22) [![markwon-image-loader](https://img.shields.io/maven-central/v/ru.noties/markwon-image-loader.svg?label=markwon-image-loader)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-image-loader%22) -[![markwon-syntax](https://img.shields.io/maven-central/v/ru.noties/markwon-syntax.svg?label=markwon-syntax)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-syntax%22) +[![markwon-syntax-highlight](https://img.shields.io/maven-central/v/ru.noties/markwon-syntax-highlight.svg?label=markwon-syntax-highlight)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-syntax-highlight%22) [![markwon-view](https://img.shields.io/maven-central/v/ru.noties/markwon-view.svg?label=markwon-view)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-view%22) -**Markwon** is a library for Android that renders markdown as system-native Spannables. It gives ability to display markdown in all TextView widgets (**TextView**, **Button**, **Switch**, **CheckBox**, etc), **Notifications**, **Toasts**, etc. **No WebView is required**. Library provides reasonable defaults for display style of markdown but also gives all the means to tweak the appearance if desired. All markdown features are supported (including limited support for inlined HTML code, markdown tables and images). +**Markwon** is a markdown library for Android. It parses markdown +following [commonmark-spec] with the help of amazing [commonmark-java] +library and renders result as _Android-native_ Spannables. **No HTML** +is involved as an intermediate step. **No WebView** is required. +It's extremely fast, feature-rich and extensible. -**This file is displayed by default in the [sample-apk] application. Which is a generic markdown viewer with support to display markdown via `http`, `https` & `file` schemes and 2 themes included: Light & Dark* +It gives ability to display markdown in all TextView widgets +(**TextView**, **Button**, **Switch**, **CheckBox**, etc), **Toasts** +and all other places that accept **Spanned content**. Library provides +reasonable defaults to display style of a markdown content but also +gives all the means to tweak the appearance if desired. All markdown +features listed in [commonmark-spec] are supported +(including support for **inlined/block HTML code**, **markdown tables**, +**images** and **syntax highlight**). + +[commonmark-spec]: https://spec.commonmark.org/0.28/ +[commonmark-java]: https://github.com/atlassian/commonmark-java/blob/master/README.md + +**This file is displayed by default in the [sample-apk] (`markwon-sample-{latest-version}-debug.apk`) application. Which is a generic markdown viewer with support to display markdown via `http`, `https` & `file` schemes and 2 themes included: Light & Dark* + +[sample-apk]: https://github.com/noties/Markwon/releases ## Installation ```groovy -implementation 'ru.noties:markwon:1.1.0' -implementation 'ru.noties:markwon-image-loader:1.1.0' // optional -implementation 'ru.noties:markwon-syntax:1.1.0' // optional -implementation 'ru.noties:markwon-view:1.1.0' // optional +implementation "ru.noties:markwon:${markwonVersion}" +implementation "ru.noties:markwon-image-loader:${markwonVersion}" // optional +implementation "ru.noties:markwon-syntax-highlight:${markwonVersion}" // optional +implementation "ru.noties:markwon-view:${markwonVersion}" // optional ``` -### Snapshot -![markwon-snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/ru.noties/markwon.svg?label=markwon) - -In order to use latest `SNAPSHOT` version add snapshot repository to your root project's `build.gradle` file: - -```groovy -allprojects { - repositories { - jcenter() - google() - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } - } -} -``` - -and then in your module `build.gradle`: - -```groovy -implementation 'ru.noties:markwon:1.1.1-SNAPSHOT' -``` - -Please note that `markwon-image-loader`, `markwon-syntax` and `markwon-view` are also present in `SNAPSHOT` repository and share the same version as main `markwon` artifact. +Please visit [documentation] web-site for further reference ## Supported markdown features: * Emphasis (`*`, `_`) @@ -48,27 +45,31 @@ Please note that `markwon-image-loader`, `markwon-syntax` and `markwon-view` are * Strike-through (`~~`) * Headers (`#{1,6}`) * Links (`[]()` && `[][]`) -* Images (_requires special handling_) +* Images * Thematic break (`---`, `***`, `___`) * Quotes & nested quotes (`>{1,}`) * Ordered & non-ordered lists & nested ones * Inline code * Code blocks * Tables (*with limitations*) -* Small subset of inline-html (which is rendered by this library): -* * Emphasis (``, ``, ``, ``) -* * Strong emphasis (``, ``) -* * SuperScript (``) -* * SubScript (``) -* * Underline (``) -* * Strike-through (``, ``, ``) - * other inline html is rendered via (`Html.fromHtml(...)`) +* Syntax highlight +* HTML + * Emphasis (``, ``, ``, ``) + * Strong emphasis (``, ``) + * SuperScript (``) + * SubScript (``) + * Underline (``, `ins`) + * Strike-through (``, ``, ``) + * Link (`a`) + * Lists (`ul`, `ol`) + * Images (`img` will require configured image loader) + * Blockquote (`blockquote`) + * Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`) + * there is support to render any HTML tag * Task lists: - - [ ] Not _done_ - [X] **Done** with `X` - [x] ~~and~~ **or** small `x` - --- ## Screenshots @@ -84,72 +85,11 @@ By default configuration uses TextView textColor for styling, so changing textCo --- -## Quick start -This is the most simple way to set markdown to a TextView or any of its siblings: -```java -Markwon.setMarkdown(textView, markdown); -``` +## Documentation -It's just a helper method, that does underneath: -* constructs a `Parser` (see: [commonmark-java][commonmark-java]) and parses markdown -* constructs a `SpannableConfiguration` -* *renders* parsed markdown to Spannable (via `SpannableRenderer`) -* prepares TextView to display images, tables and links -* sets text +Please visit [documentation] web-site for reference -This flow answers the most simple usage of displaying markdown: one shot parsing & configuration of relatively small markdown chunks. If your markdown contains a lot of text or you plan to display multiple UI widgets with markdown you might consider *stepping in* and taking control of this flow. - -The candidate requirements to *step in*: -* parsing and processing of parsed markdown in background thread -* reusing `Parser` and/or `SpannableConfiguration` between multiple calls -* ignore images and tables specific logic (you know that markdown won't contain them) - -So, if we expand `Markwon.setMarkdown(textView, markdown)` method we will see the following: -```java -// create a Parser instance (can be done manually) -// internally creates default Parser instance & registers `strike-through` & `tables` extension -final Parser parser = Markwon.createParser(); - -// core class to display markdown, can be obtained via this method, -// which creates default instance (no images handling though), -// or via `builder` method, which lets you to configure this instance -// -// `this` refers to a Context instance -final SpannableConfiguration configuration = SpannableConfiguration.create(this); - -// it's better **not** to re-use this class between multiple calls -final SpannableRenderer renderer = new SpannableRenderer(); - -final Node node = parser.parse(markdown); -final CharSequence text = renderer.render(configuration, node); - -// for links in markdown to be clickable -textView.setMovementMethod(LinkMovementMethod.getInstance()); - -// we need these due to the limited nature of Spannables to invalidate TextView -Markwon.unscheduleDrawables(textView); -Markwon.unscheduleTableRows(textView); - -textView.setText(text); - -Markwon.scheduleDrawables(textView); -Markwon.scheduleTableRows(textView); -``` - -Please note that if you are having trouble with `LinkMovementMethod` you can use -`Markwon.setText(textView, markdown, movementMethod)` method (`@since 1.0.6`) to specify _no_ movement -method (aka `null`) or own implementation. As an alternative to the system `LinkMovementMethod` -you can use [Better-Link-Movement-Method][better-link-movement-method]. - -Please refer to [SpannableConfiguration] document for more info - -## Syntax highlight - -Starting with version `1.1.0` there is an artifact (`markwon-syntax`) that allows you to have syntax highlight functionality. -It is based on [Prism4j](https://github.com/noties/Prism4j) project. It contains 2 builtin themes: -`Default` (light, `Prism4jThemeDefault`) and `Darkula` (dark, `Prism4jThemeDarkula`). - -[library-syntax](./library-syntax/) +[documentation]: https://noties.github.io/Markwon --- @@ -218,8 +158,6 @@ Or leave it empty and use the [link text itself]. Inline `code` has `back-ticks around` it. -**Please note, that syntax highlighting is supported but library provides no means to do it automatically* - ```javascript var s = "JavaScript syntax highlighting"; alert(s); @@ -230,6 +168,46 @@ s = "Python syntax highlighting" print s ``` +```java +/** + * Helper method to obtain a Parser with registered strike-through & table extensions + * & task lists (added in 1.0.1) + * + * @return a Parser instance that is supported by this library + * @since 1.0.0 + */ +@NonNull +public static Parser createParser() { + return new Parser.Builder() + .extensions(Arrays.asList( + StrikethroughExtension.create(), + TablesExtension.create(), + TaskListExtension.create() + )) + .build(); +} +``` + +```xml + + + + + +``` + ``` No language indicated, so no syntax highlighting. But let's throw in a tag. @@ -275,16 +253,10 @@ Nested quotes ## Inline HTML -**As Android doesn't support HTML out of box, **Markwon** library supports only a small subset of it. Everything else is rendered via `Html.fromHtml()`* +```html +HTML +``` -* Emphasis (``, ``, ``, ``) -* Strong emphasis (``, ``) -* SuperScript (``) -* SubScript (``) -* Underline (``) -* Strike-through (``, ``, ``) - -Let's use it: HTML --- @@ -330,11 +302,7 @@ Underscores (`_`) limitations under the License. ``` -[sample-apk]: https://github.com/noties/Markwon/releases/download/v1.0.0/markwon-sample-1.0.0.apk -[commonmark-java]: https://github.com/atlassian/commonmark-java/blob/master/README.md [cheatsheet]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet -[SpannableConfiguration]: ./docs/SpannableConfiguration.md -[better-link-movement-method]: https://github.com/saket/Better-Link-Movement-Method [arbitrary case-insensitive reference text]: https://www.mozilla.org [1]: http://slashdot.org diff --git a/app/build.gradle b/app/build.gradle index 9fb02274..a8154b4a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion TARGET_SDK - buildToolsVersion BUILD_TOOLS + compileSdkVersion config['compile-sdk'] + buildToolsVersion config['build-tools'] defaultConfig { applicationId "ru.noties.markwon" - minSdkVersion MIN_SDK - targetSdkVersion TARGET_SDK + minSdkVersion config['min-sdk'] + targetSdkVersion config['target-sdk'] versionCode 1 versionName version setProperty("archivesBaseName", "markwon-sample-$versionName") @@ -28,18 +28,20 @@ android { dependencies { - implementation project(':library') - implementation project(':library-image-loader') - implementation project(':library-syntax') + implementation project(':markwon') + implementation project(':markwon-image-loader') + implementation project(':markwon-syntax-highlight') - implementation 'ru.noties:debug:3.0.0@jar' - implementation 'me.saket:better-link-movement-method:2.2.0' + deps.with { + implementation it['okhttp'] + implementation it['prism4j'] + implementation it['debug'] + implementation it['better-link-movement'] + implementation it['dagger'] + } - implementation OK_HTTP - - implementation 'com.google.dagger:dagger:2.10' - annotationProcessor 'com.google.dagger:dagger-compiler:2.10' - - implementation PRISM_4J - annotationProcessor PRISM_4J_BUNDLER + deps['annotationProcessor'].with { + annotationProcessor it['prism4j-bundler'] + annotationProcessor it['dagger-compiler'] + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b24d050e..97c5fb1b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -8,13 +8,16 @@ android:id="@+id/scroll_view" android:layout_width="match_parent" android:layout_height="match_parent" + android:padding="16dip" + android:clipToPadding="false" + android:clipChildren="false" + android:scrollbarStyle="outsideOverlay" android:layout_marginTop="?android:attr/actionBarSize"> + + + + + + + + + image/svg+xml + + + + + + + + M + ** + ** + + diff --git a/library-syntax/art/markwon-syntax-darkula.png b/art/markwon-syntax-darkula.png similarity index 100% rename from library-syntax/art/markwon-syntax-darkula.png rename to art/markwon-syntax-darkula.png diff --git a/library-syntax/art/markwon-syntax-default.png b/art/markwon-syntax-default.png similarity index 100% rename from library-syntax/art/markwon-syntax-default.png rename to art/markwon-syntax-default.png diff --git a/build.gradle b/build.gradle index 21394ff7..a99710f4 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' } } @@ -39,25 +40,76 @@ if (hasProperty('local')) { ext { - // Config - BUILD_TOOLS = '27.0.3' - TARGET_SDK = 27 - MIN_SDK = 16 + config = [ + 'build-tools' : '27.0.3', + 'compile-sdk' : 27, + 'target-sdk' : 27, + 'min-sdk' : 16, + 'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle' + ] - // Dependencies final def supportVersion = '27.1.1' - SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$supportVersion" - SUPPORT_APP_COMPAT = "com.android.support:appcompat-v7:$supportVersion" - final def commonMarkVersion = '0.11.0' - COMMON_MARK = "com.atlassian.commonmark:commonmark:$commonMarkVersion" - COMMON_MARK_STRIKETHROUGHT = "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion" - COMMON_MARK_TABLE = "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion" + final def daggerVersion = '2.10' - ANDROID_SVG = 'com.caverock:androidsvg:1.2.1' - ANDROID_GIF = 'pl.droidsonroids.gif:android-gif-drawable:1.2.14' - OK_HTTP = 'com.squareup.okhttp3:okhttp:3.9.0' + deps = [ + 'support-annotations' : "com.android.support:support-annotations:$supportVersion", + 'support-app-compat' : "com.android.support:appcompat-v7:$supportVersion", + 'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion", + 'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion", + 'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion", + 'android-svg' : 'com.caverock:androidsvg:1.2.1', + 'android-gif' : 'pl.droidsonroids.gif:android-gif-drawable:1.2.14', + 'okhttp' : 'com.squareup.okhttp3:okhttp:3.9.0', + 'prism4j' : 'ru.noties:prism4j:1.1.0', + 'debug' : 'ru.noties:debug:3.0.0@jar', + 'better-link-movement' : 'me.saket:better-link-movement-method:2.2.0', + 'dagger' : "com.google.dagger:dagger:$daggerVersion" + ] - PRISM_4J = 'ru.noties:prism4j:1.1.0' - PRISM_4J_BUNDLER = 'ru.noties:prism4j-bundler:1.1.0' + deps['annotationProcessor'] = [ + 'prism4j-bundler': 'ru.noties:prism4j-bundler:1.1.0', + 'dagger-compiler': "com.google.dagger:dagger-compiler:$daggerVersion" + ] + + deps['test'] = [ + 'junit' : 'junit:junit:4.12', + 'robolectric' : 'org.robolectric:robolectric:3.8', + 'ix-java' : 'com.github.akarnokd:ixjava:1.0.0', + 'jackson-yaml' : 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.0', + 'jackson-databind': 'com.fasterxml.jackson.core:jackson-databind:2.9.6', + 'gson' : 'com.google.code.gson:gson:2.8.5', + 'commons-io' : 'commons-io:commons-io:2.6', + 'mockito' : 'org.mockito:mockito-core:2.21.0' + ] + + registerArtifact = this.®isterArtifact +} + +task checkUpdates { + apply plugin: 'com.github.ben-manes.versions' + dependsOn 'dependencyUpdates' +} + +def registerArtifact(project) { + + if (hasProperty('release')) { + project.apply from: config['push-aar-gradle'] + } + + project.afterEvaluate { + + // disable generation of BuildConfig files + project.generateDebugBuildConfig.enabled = false + project.generateReleaseBuildConfig.enabled = false + + // print test status (for CI) + project.android.testOptions.unitTests.all { + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "short" + showStandardStreams = true + } + } + } } diff --git a/docs/.vuepress/components/GithubIssue.vue b/docs/.vuepress/components/GithubIssue.vue new file mode 100644 index 00000000..419fb683 --- /dev/null +++ b/docs/.vuepress/components/GithubIssue.vue @@ -0,0 +1,34 @@ + + + + diff --git a/docs/.vuepress/components/GithubPull.vue b/docs/.vuepress/components/GithubPull.vue new file mode 100644 index 00000000..0a0dff24 --- /dev/null +++ b/docs/.vuepress/components/GithubPull.vue @@ -0,0 +1,27 @@ + + + + diff --git a/docs/.vuepress/components/GithubUser.vue b/docs/.vuepress/components/GithubUser.vue new file mode 100644 index 00000000..ece2c01a --- /dev/null +++ b/docs/.vuepress/components/GithubUser.vue @@ -0,0 +1,21 @@ + + + + diff --git a/docs/.vuepress/components/Link.vue b/docs/.vuepress/components/Link.vue new file mode 100644 index 00000000..07e549ef --- /dev/null +++ b/docs/.vuepress/components/Link.vue @@ -0,0 +1,56 @@ + + + + diff --git a/docs/.vuepress/components/MavenBadge.vue b/docs/.vuepress/components/MavenBadge.vue new file mode 100644 index 00000000..7c920bfc --- /dev/null +++ b/docs/.vuepress/components/MavenBadge.vue @@ -0,0 +1,19 @@ + + + + diff --git a/docs/.vuepress/components/MavenBadges.vue b/docs/.vuepress/components/MavenBadges.vue new file mode 100644 index 00000000..14a0549c --- /dev/null +++ b/docs/.vuepress/components/MavenBadges.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/docs/.vuepress/components/ThemeProperty.vue b/docs/.vuepress/components/ThemeProperty.vue new file mode 100644 index 00000000..f4e30500 --- /dev/null +++ b/docs/.vuepress/components/ThemeProperty.vue @@ -0,0 +1,15 @@ + + + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js new file mode 100644 index 00000000..3e7cdaf0 --- /dev/null +++ b/docs/.vuepress/config.js @@ -0,0 +1,37 @@ +module.exports = { + base: '/Markwon/', + title: 'Markwon', + description: 'Android markdown library based on commonmark specification', + 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'}], + ], + themeConfig: { + nav: [ + { text: 'Install', link: '/docs/install.md' }, + { text: 'Changelog', link: '/CHANGELOG.md' }, + { text: 'Github', link: 'https://github.com/noties/Markwon' } + ], + sidebar: [ + '/', + '/docs/getting-started.md', + '/docs/configure.md', + '/docs/theme.md', + '/docs/factory.md', + '/docs/image-loader.md', + '/docs/syntax-highlight.md', + '/docs/html.md', + '/docs/view.md' + ], + sidebarDepth: 2, + lastUpdated: true + }, + markdown: { + config: md => { + md.use(require('markdown-it-task-lists')); + } + } +} \ No newline at end of file diff --git a/docs/.vuepress/override.styl b/docs/.vuepress/override.styl new file mode 100644 index 00000000..ed8161b4 --- /dev/null +++ b/docs/.vuepress/override.styl @@ -0,0 +1,2 @@ +$textColor = #000000 +$accentColor = #4CAF50 \ No newline at end of file diff --git a/docs/.vuepress/public/android-chrome-192x192.png b/docs/.vuepress/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..004d02b85542f0cab06da1294452af12ab786d21 GIT binary patch literal 7928 zcmZ`;byU<}u>Z0w$kHqbEag&5cXuycyR?Wby@1k& zHA6K3Xh|cx#t_}`a7P`KApnFT000*a0KaZTxP1T!5CwoQ_5dKC2LKG-Mct-~Hwuu0 zzP2XtFD`BJ?F|R<*0J`xiM06NB2ZegI=K0sb&`Z z{#!BDo5j+1?}@n^^~IxO5F}C)U8adrR7F*4Cel&8W1Cum?j<&lu!n7*B!_zr4yP`+ zw4_q3m9o%c;znBEQ*nm%+l{=>aomR8b<_uooInZEIUtGA-qc(q)LaiGkC1n~RQ$_L zErS?v&%g91|GkVns(&6Xa%TP}{7U>x*D4aocjz#gknX_8DvF=L@BaQdqtp-1#}wHq z?@$_s4~t^P=8S1Sas#JzJK$TZ2J9tf7fU-N;(E-vwR^-B2{4w6Zoq1)Av{}&uCm*8 zg51$|uQa%n=OmxytN`HMrSrmP{xG|6$>-=5`uKT-e8|N=`8!diPx`-U8gbNzB9ajsE~jbtOfMo;8n2VEM!kT;Cr0l^s4hPx=e4XIrvS&Nu02Q5n<4$M$a*A+Vo$$K zVJ*c8K`WCxG;Y=Yi41$M@nvZn4EDSg3sx2S)?`?b&H9ruW~+2UKBg2RHEyDyHuqJDCPTp zklAVU#=mNgdh5{IVMq$5J9C}pE0xQ_4&x^E3hS!sly$Qdz8lK*mZBv8QS$yEsyuns z^YGSCv(e#!wYbVA5iDy}uPKjw@uz>`5G!F#a8g^B#b`zYfKUZZ?3Yo!3kBvc2y)%rMsP^H7hO)l00-$)!CNeVx_7fD@eRVGQDpq!a zTBykW^#NWXS8+H?Ao5vW=B6k^mjTfygal=~*!1jxk`cY$2)?m4C$%DM#=hazfzO=bB zRn=cKuOD;uIZmHK(4#tFw~eb#uk~jwkup~><6chSO|P&*tt#0EQhjdl#s02V>i&=L zkSuPz8riSTLGIX$3S(?}(ig@B`F%4}k1es^vh@;5ds>39+7!R+G{)$CwEVt(gHq$_ zP{Xy!1_9}1fzt5HQlyKyqm4`Cd^nPI&0NHmk4+s1Pl7JKaP1m^u~AznsW}uOr3=1X zz*7u7Y7Ko#q*Gh(?#u}}6SV@}e6l)ZKig<5Ub92zB~2Wu{N}Fszi(zn*tmZy1B(6k z5q6N^(N8C7Kag()Qs1kE(D8kOu$@p{v$B#5&=!O^=rtA654?sdU90>pd0ty(u1Xmh z-?>NhZbIcmrY+Cp)d*);9%0xD1shlD+SX&pY;Wb5RrZNr3}q@l*Rw301raYx%#pd7 z_l%S)^S(a0-@xE~ez$&CJ)tr&g3~c)65XBHyltkvgIj2KM@f8N$&L-dh}>RzR#c!O zEAoIlh5BjVbrw$fPWZjMxv4Q4bs%caIPKXOTRFmo&ugn4`I=Bo+&VbAC3t zPYx2{U~*=nN@Q36&6*7GtVTlrWvjfDm}8%0{LnVvhekjS89CFBpH3^mQ(O-!sQiOS zxbRlGx9yhDEH7Iyi2d>u$`VnGUQ?oC0YQRL@;eVbp-f-tjjE^6u%N2Qa7(DlCWQia zGQhJy`Dh*~Qvo&=AdP4=$WR|h72RMjZ8t+tG5JDA-f4;%suZ`K#M6M}-BCYOB=4$4 zN}fJjIHgy80J+TIVp`n-SCK@LpQKx`iH?%zO;Y()h(@7q0wq4xdO@Q>KCi`m0ka=2 z%h|0{Bjz^StV$Gjt)OP&KFO0ZLPf{hgc>jxD`NVaHL^zdrA-ZqLUdb{`wfosDCL9u zWihPnwib9Ak=xrIwu_XXTe!l-lu*(G#tfESMyh8I=)+bgepQAcL5j~Jh+QNNpIOvRzG1e-EEtmWO($!)5Zss&aJm@lf7&IprxNfq~y$F$6|EC)FH?ej7Yedvze05>JvjeYU{&YLIr8b)UW3T8;hf zpx=VuT3Is3!rZDyL-9k8Hxt6oHDC7b!PBi~d;*aVQZO#*@tu!HMnO)}Eji3Z70l$J z;Yw=;bxJc@p%hH4OWs*ZrWq+>btcRs+5~XZF`3{J2}Q|TRd*a%g{T)Gyq)ZzDo7>VC;g)h(tH{Kh=c`l?gzKDd){_;Fm6Db7W-p z*JNEU8B16KDsy!bQZgUl1DD?+K3KNp+cfXcA_O>Qc<~;sqNIiBBmTN_8c0N0@&F=& z@AG~o38v)B^YP%io9fPKXu`fFOK>bV7nA9z<~dNFJRN%_u4)AXB5UNcIa?w=mF0(a zQ|pj)tR3;TS_5q7vyAS2j4hiKdL_iq?ml@1p4r5qPpIAawssLV34(YAxXXieB79o4cTrII4{wc+6(okQP0vbi*(OsGycgHB5G(nLs;?8|P0UBHY9+H7(?_W%OziKm^Ei_g@gn)<-coX8WHdZiyI_K!fnCkRGbt^>dmt7D5REcW6nbN~ua8EFfS+ zw}lwfLLJaCbPT0@+W{Iyk&imGDc{!TpQOGDtW7as9cnVbBFn+5uzPQ-V2fdq%dY+aE_a&(gCV?tKNm)lFWDKu<3 zoeZl^{G5`SErxvh9VGC=e*;;kPts{@u4I&Lq6@on$@J+}(?g5@jgZdC4ZbkZFwIh1 z4a`Q-rrK*>m+LSHUVb+hYu6lSb=Zom8{ZTX+a`C6*2wf`e=?Y%b^SbHi5vB%J03;& z>heyiIof}clz+1!$%^!+&qG}^h8mn*YmhBH^}C>{w1c_u>5gOi-Ii&&YFR$qZQ}dm{DC~zi*Q#4L(>NTMS0=^M*34o3*{wfN?;cI02EEyQM$XKW>ijyOej;t(^f%cqC*vRSLz0@Q*3Wksx0tzBG!aHWRT2$@657@pc4#VwNRZ zS*L%1giLyafmWY^rK#hUG8|=9cSE&K7qCGj&E2!dRw{U0m*VfP{JpI~@+0&!h``K> zv5+>rv&=QNPKFnLVXeABN-(-$?X6o>;YBd>bqBxem!G0dDAax-+^gIJ`Hro^5*SPJ zm${-oj4^u002X%{OXtJwSL5jx4)row1J0XEh(ra6rKVO(87tA7m`0Ac@Ewq!4 za)WS61yG`0x0=4q`2w>zNx;)pzg~fnR+vV$D~3?-7aBqwggoltQZtJG)CLXKx7F4Qf+vltJG%(iv-1> zalh9f-(sMo-MiXBgu%hKt1hYPan2t4LwU4!KHyucADp%^z{o>fI%^gkHBeAN^8u0z z0qk158&3k}8zTrl6lP4!ngtDmYccBIZ_N;s8?Kkx0K83h#12`b51yEZLgyC3ZIaqvtYgQ8$*1 zC|#{MhUCwDcVmP^vWN29Mg`rqrW@OhvY5t*wSvY7_L~FPAxKB7K(*p>oX9kqFXp8M z0e+Fo>Zh3s8+rtd%~?|3$ZRQZi<)%Gp(gk@L?;S3hGs*)cMS;byrr4;c<$so`B}4K zXrKyY<;CY=Zv>I@v-GP9UFC%@llD6Dd2A%3JpMF3e}ANAQPWMB^BG0Z`&b&$Qxj9z z8$T_U(dTm!H~}noEQBug*2ojJ&5m*Q=aMgzs_s_a#^|8M*;_s{wE<{T++ zo0s5;jl4nyXI#>nJGX~@;&<(oSrX}HnMI2G)V6|vhc%&LH`ftEold|21DJsfaAzp8 z%9{4k)_)+#`kcOZoJd>BOy0(m59ZwtYz%_@UQx>aFV_VGo_qxzoqKaEy5a>Hg&9;2 zBCmgC)32{QjG7<ed?)JJF}6Q5hvD%=u%P@eZEOL%nFPL>y^rg3Fny0ZmyzvE z%%VA>%qscbCcsMy5XrGb(jJ$ykGN6oqI(kkJ3$%2b*cO;bZ%q67(w!h2!iR~qsI;> z7bke)K=gwHSPH!{B5bBFsF(dl%TOJ|^C&#`ZHxb;eLzuD&XyDR^`m&?-}sswo3+VO z!CK@N%61H!!vN2|8oTK1^-n8}^$u?n0It-bU$m0_u^djd6NsqFkT=a)#ZVU_p%(oJ++hncr=VSx?hAfoY_x@u%E9<i{K$UWS2gCol)WA6t^}tAINC@?wk}A zS}=92|KK+^Qo|d$lKE@{;_BwWTDWOcIAB|yJ5%e}fL+G(Il=XsM5~7uN934zT`Im= zJpBgh920$~E^{k*s4_2*GR9xvADNyZXE>sBnCMr8u+1hhpY$1B0%hpxgjnVkSEj>a zt+04|XLaYaTquD`sZH&AyX1->&h#NQ66x|(qRlswRsPjL1wP8jf(bDM7h0G%B7x*p zMLZDOQsr^uh)$lAyfZs3<{qHniFcX1kKKP9ANU2!9uEd%?Q^GM>u>t_EW4@!=)0kR zJHjK@4VbeY?x)qvt&z&pv!h&i-!x6;3rB?4%eks&=Xp=I*?4LfOf$2Cdfj{|yd%?jAe%d@`aWadV+%}d$!wYGC$y{6>V zBvZ0_f`z3o>?CB$%#Kj+7 z$2kbQm>P&cX-0Y_4HGFGoZ?ggtfIrZy{eI@9=(KEt)YprK)_dc6pBsM)#vIey49a^ z)8x>3VYtG?lkNryJVNn}@aia&FKw{b^$)*NX*RL`Mo7Ck-Mds(2f_;Sd=q_6 zas7^y_{+P@k<-vZ)%<)k9ay!T8bG?L$GF&F<$+u6<#m zQ%$I@Zl}2*CD>Z~O{>&i_|tt%Qzji}UU{j}`;gJ}>Cf3nzHBqJR#!}a)6zywXovJtHtXmapX&;27K7$FHqIF zRUIMoj_o&A>SfX&XkZn?tED4=T_taUlCV zg5BDygYr0!?bcZLdTWhTG;@sFkKnJqWtH&N3Y-|5%kH6Ps-) z>6+$>o<{aG){M^SKqQQ0SpD-FRlxX7vPeO+d&WyIi9aqL%rWe-4 zAvyw7FjQV6YYav&IRDnVCbF7RWtp5ZCO#qWKl}`zku6pc0wSN)5};yS$nW-BAe9@1 z-{By-am@;Kdmb(Fl@9NTyurxhD;5>bHCE`f-T*u;(Q`?bP%jl8zDx`s5(X( z%}oK%suJlb8`=J0>L_yWBu@)`%vNku7WVm^I+b!H&hweKqGwI&&ug7>w(SYU5$qAo zJ0fD$cucGtqEOsj_F-<-;{-J-lbM%%I)CQ}Y-=(lX_vI=)edE+g~gA>o{2V4_4#?0 zW%&JwNR6S%F`6^#sB3zh6y{Ugw8q9|EL87v6=$9|Kgs$PFS0Lh z7+4gRGh^3n__BRq8(+$qXz3ytO0ow?Y^M*dtq9TkcE*AJkZ32`UW#1^YZ2I zW))Aqj~NROyNqQ1C-f6C5<1%sx6{Df-_&VBDD>xmxS-{^IRv__J{W?B=Ogz zQ@7J073dJfkHSYxXdb~swQ-N9vTajO>ElLIQ@>tv`tnrGxSL z$Qfe)xXIWxs{pQwWzXWe42k0b@Zwk3D?ie|a61a!rO~YJ(|gk=WA&o&tNOULJt^2h+!qj(l!W6x9 z1+z$SB6-{QITuNBh}~u}=Cw~#^1#)Er+x1FNV6)l!=bYAg!{>feSPV6a-$5Ik+pqCy`iT*NJqg?F8)g?ZbboU#qf+a*(9dYdf$pcaD~{} zspW|j=v;Q}6wu`35nDs$*K?iIAJxnYN(TMc)pHK$14$CMp7Uu-$~m-l zW39;2V$|J|dT;;D%eazu`_>WoS2aj5_Z#EFwl47&-symG&V)?P0w$bz_nP7T;{94M zoM3%J!6*@4i681)7uQAn3NZfu(qvRBp>yi)`a5|gNiQ7OJ4NQ3iRLZ|quF#B-ULsm zM(f@o_+-*2+v~LD<6foT6JD=A=Id6aX^0XcfQMi}XF(z>+~5Eu!!<0JJ!t>}^-M7T znx#TSSVKYYuPCRkAD=E0g(izNkjdn`Om@h&=~nA3H}|H^BnX`-|1Ee4>l=(s!Yo2` zw%|`j1*BnB6Ma&aB^lgkwV zt#8NmjCgw@NA4|?;LLl*Ems{qaN9GS;gTmaJu~x{21ff|sDZ1m&r+j2J32-FCMCl; z+k6t{*X;$xqJ(}GfJf=j8bWKqsieMRdLdN|7jT1gEMot&aFE-xY)t=>)$e}l{&hj=@~vEKfU01#ZXbxKa+W5LTiZagumyMqD${#LB|x;N&37;H?RqPMMxC@P&cK|5RNn`z8dSt7WA5T-`qM Fe*mqEL^S{a literal 0 HcmV?d00001 diff --git a/docs/.vuepress/public/android-chrome-512x512.png b/docs/.vuepress/public/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..419270937ece4dc504fdcfeb527083cede7559e4 GIT binary patch literal 17037 zcma)kbx>SS5awfHaR?gR-QC>@7J_SnyE}ovLI`fbU4kdLTX6S4@ZcWY5}bSa-Bn#( z)m_#7k=pFcbk9t8&-Z%zeM^M8ngRwYF)9E67_Ss%H2?qt{ucruBY+=oT}fTR4+Lu| zRVe_di9>rZg@WHH%@s9N0pLA70Kh^3;0|np?E`=(Cjk650RZ7N03dM5YJDRLc0kOO z6=Z?uu(+{K@EeMYqP{yg)9k;0aAI?M=U^j}$17Diq)ilr7YL9fJVasuki2;%E2ZVL zbe!!ABj`{6D$1DBA7ECZ^U*`77$X;~V}xchu|x zKVB(C+T6M%-J9$tn#fE!O5kbHKhs@MJaP~_1jm%@^o+AkZ0*Fc92K?}?Cj1bBiM5y zgDm~3HjHj;{l#j4UsE+b5MUTEM2C*!{0>Vxe#1Ct&w5o&WY*AE>~ZebaPn2tB71N= zwfOd{VxP8gT?ZrJVfWWC-&NEXZ3HU0@^!A0OS>^0J3Zi`=GO@4$QOHjDMbu%AA)l7P0HHmm8{DVpc21V`)g5*V-iZ`{5W)9^9V)ic(euvWvFb zyqg(s`0;rrmVas9!_(12R4A0`sq-FgZCf(+7GHE2qjod*g)tl>8YR)N!ryd}silTt z*P>t&pXnD@;)qy4Cy#rx(aXn+g3SF=_up24WM}fUfYNkc7E9We_jITFJ>Q=;?kIO7 z3Fr3Dl@-DD38oV}zVDWq=J9}M*_b29&$u`hbwcr zpGYfOjFKlR*&rrCQlclhrS3tYz|A1{$DqnwPLw3uVsRDgd8aDAsH};IX#m}DXLa*nZRf_wfsrpBzHhY2T^k3wbQ9Y}g;BSeL&^mbHd<3`#OT zlEWUTl~th7uD>@0Gg|BHCuY*W4Y|K&&Vcj8Pb6%H5p+(;T$ou3r0*`^9~{R z0aoYm9h|Ge4eb*O4+&*Q+&TqIjb)fxg#M8$D%RXDV;7@1_Rf3#14n3pfJu*v$4&%E zd~co?nn<C0YqE}U7W9z9}BxTzDoqV$m0_P;;)R`1_7)M#O-;>6}o*J^t z9#T4q%e3X_RU4)a0=l)l0M0o00Yko45V;fF7q2&S@*j>y4BS-RXd}}a&zFl16T!oh0^oOk1GIJzy zEVry?+?OQ7vrz+C-(}UiD*`YBdFGMVCp_pLGZZQ{UkaSt7l#t7d~>+Om~Uv}PJVap zo49srQnSGyOyK_H#rBum_#Q9h4vzpQ6x+02a% ziRi>Hqf&fx{nGGlCYsX#gu|ufw2?;#Mval}c}M5fH`AsmE(BFjV($gpA`YMUu*gcS z1q1FFHuFb$ByK)(xQFgSUi zuFO+DAU3umyi;A6(E8--gS@763;FVM0q0C6UXS@EtXJYE3T!PlzmhE~p_(IXDK?1H zxXo+NAnkPKA>Qz9PY0>Ck_ci?{j}6Bm(cb}AAyWKA_1ppPC9w2qgJ2sHj($q12j*AYQ})!gy(nD+5~O zob{d}mQvlEU;a>p^^`wOpnExLCbgThI>$~@>$u?ex;-_JRa{2%UX(!edw4%Gefz49 z?c=D+22Ox}S!QNNuH3r0^SR8)_t3Db4w2w2rB3R;`9jvcHn6@41v3ulNUoeo9S?;$ zSu?-bUgV0e(s%bhj(H$?K{BC7aCKOW3lvH}k-=98a!>NL7#?Vz@Nk}0#pO2UtyeyL z_(RbEaS64L`Xb8#D8|hG>MbWdM7_;|6&w2nc4ymn>kltU&VM&&j4u_x^+MmizvFi- zGFI7pchlEwi8hnWn6wm~YY7BEAB~rB>F)B`n16C+<_a8Se5C=hfjOI;c{P@uRjxex z#D+s^ejHy?MMHI$=cBpxBhs!N($l3JMk19^e4UG)$}RCwcm3}?r&_IlP7@sqE8cs-u-AL?HjD!RO2--$4QYvxpC zl3eFiqv;*gdZX=OSiw&8c9X4gBS}VL?_?=VGwA0TghTJ{!n=?n`RYZIdO@_iP%5!_ zVPw(3-?7;<-oTB+ET8?oUt5I}o++nRFF6b&7eqpp?p!r85=`KeT+dD6_P3UZ2`1N~ z-bP#UjOtC?2NgivYm(PAhh>FeUnr6hcN;Qi;sR9iGIm(E z%nLrA+68p056oovZxtRf4qJ1*4v7u7Q9^sq9jNZx?-v)E0-P^lYZJ>SK@vF!m5vce`b{RuQm^Y6=6O;DG9Cg*$tOdU ziWlR#TY18jVxziJM_Zw%gu(oOg%-|@(ee0I*E7bN ztD|!Jf+GItZI|M_y?hDj4?DJ)?+=cIU6gk$&TRaH1}SyXuVb2_6*j3fyrV`e4c1uj z2fEH~Iso}q$6?SjtGSUOj>4as4Ca)6uMdJEzO#d_sA1Xd_CMJ;qXu0+$3)`{@vP#L z-#E{!>Z7R+Ri`kg7#hK*>0%Rg_4f|4yKWQ5XEHjj%w4*ZSj(&k^oC1jXL4KPBoe@+HrzX*zfmCX8IM9YRgvA4LNZIEq=Y^$348LtkaKFZq@xt2okW z(8SUdq$O@ZCOZ>z+Zt(XW%pjzTf&kpzcYqUu!TPlF*g1>877u=Ick9x@%ma$vJJ&X z-xagOuCR9gc~EGXYrr2KC^uC9nyA~R2RiLvZwX685eE7BgOJhoQlv)LUR}JC6fu2O zty6bx7mBD@Yyn5|6>HGvH>_z}w^7r{=s)=l_3{s17P?+21tQ6NS!ksTc!pzzf3n1s z9#SICeUx*D3Uoj)`*`CAUfv6J1ytwA za$0TZC!SnWWSG~XC8_(isvowFSAlz;(B-A0-rpAb<-VVavrm3;dZgGaqDLpS14?SRQtQnd@q;T>5r;xI&Qwj!r?6v?)0}TGk3~wYQH{)IPB>E zlnD;u9rEcICC>)FkPLmrQJ54U*|X*@GSH({(Zj%owfC9AyHG8;X`qX~%f!v6-&d5l zqw^5a#tR2{Hz+BpvzTQ{kQxj1V`tzx%~%WG3W*W*by1h>XOx5{vs$<0M@%&yjbn>j zRwif6gax60F%Mx&LYD=8n+8xC4)Z;54=TIi8>bDt{MfiWg7gb-rX@%$)Q!%2mr&+c z7hEq&_Y9UpQ5Z5{VokZ6!$LG5-jQj(=|Y&eFiEX z6N|t!Yx0e<&u$J7Wn=q6+<}doHK(B(oY{j0))D&t}Ma0x@ z^d?S*RL)A!VGt_43w>Pe!1{;3bqUF1jOixdI19ZHOx{x(=AIb6Yw9-R3LJa$g{ZU4 z@I%EW*A^s+&QE%3m`X!@DGOOF;_!DYf?tlcThe}?SlH7}UE1KHZBcRpsRMqFR^%F5 zJO0_w(PsUo+e8(C!RPyBFIbEx(G&AQufr~pSQe`?28;f%%KNhq$8^2uuWQB(gsZ%w z?Omupoaij|b;y_##+jm<(4(>SnI08HGK;EhWTxcsPZVkZjrx9%EG>KDchD`xRXlZ1FL<{E@*rEGVBb|A3 zRDgU13DM06w*2vaZyZtLWmKYsjLC_P-8h2Nj^4*czAgWP$qZ5rW`JR8MqO2bGe_jI zvTg5lDf2SmTG%~G$~uo?5Zd7tUNh9?qIi?V-Gj@atN^ zdY<)9gmiS-NwL8C_Eh?mu$$%&4$u6n`9rj~z6iKCru(i+0(W7pPgzrjF|j+jQEJOj z=OSDU%=%exTszzf4O6!JnSmF*OOso+b9k#@I_ldk_`6TvUtX0_W` z+#4xMYm*ST56+YEfG*iEt&^5f0IR=Jd?fI5_1rIrUoO)aTZx$IB|xa=KF$*AB0!TA zHv6gVk6O$OLYuot;)Pn+PBH!-u?@{5K_7T`=2U6%-#{=s#QgRu^C>*R zRHFUVJ1p!fe+4*qUQd3Ce0S*<>YloLSVaxIx5y6KOtjOF;^3T?&g)282(EdMyu9Y% zhfFya{Q=z`d3jeMG$VOWo1TOwsas@O`}QFm1x{K{6%CN|e$pQ%5?nE9jS5n#(xmg| zVU`T5{h{>XhHDG|Db6^48d&cwT!I44)r!)wsPa6b>t-1-l@-6y)6gBy3A<^a%JtTe z62!0fl$jOv36VTg(N)9_MHUI~;r12&LNY&wxm4`_TsHkfpnCa6m3|$oTrVdJ&O=~H z$@;=!m1)$UB>(5lmwjq;PB>UOj|=_Gg)otI|DTXg`-&}Sz-?2z$L7lp?3?Sb7&+Y{ z`->BNtqsABH4Ae`w&JFX-De%Iy?bAN>9RKtNeGz>6g?LmzS(db>--1P(g-jfw~)&e z9_Fh`(d0|l;J_8iLv)qljXYB2cfF2D%ZB2?=MR+I2N(TNO5BUWd^dR)?^w&>ZYE4X zJQ^9;QEd$VvpX0)8;fldtt6i90s$5)YP|X1qbTSL@|5z)kUSPA3`Zlc1Vy63B6NOt z(LR;2!<(k?A^{Zg2!LSK>U;&?y!*G$&L1DKCD|}8w>^;Y+H|u%Ut1K3t#Q{|3K7Er z`=fD$#tetM{2>gPKVL&2S}rfqhO#Tz1%G%Rhx)q7Yd94p3-g3C*Cqz&5*ms})h1`~ zgz}^hxUHM9I3|h80s!GKi^s0;$7N}5C50$QD+xCAl^Q-=!k(G@Kd1P+(q9EXAIdm) zijCbFn5{9eQ)sc`5i^PQOK@Pd6Ny#}Ro4dWQj1shtuGf_p0O0T35x^TWK>mHme zQ3I9OaMdBd^NIiWmuNsMUdJs48Ahko>A1YOv7}{vq%H2;ywsBpgMFf2w5=*0h5OY@Tj%JXI#1PZ-^ zl@YrBwND)^-AB^*GplkN9H{d%f6_r~1(oL&+i+Kh7G55vJnvr_S=7sFtAhn62npYd z3#AfZzhmmafo>TbC)wJFI<4d?3HG%b8(t@Mf$>9$`LCnN-dSk}Ar)DY6br5z<}{C) z`IkR^zj1mF7S@9H2@8%hwyGWfhV$UQ6<=IfOyIzt?%pTBpg_HRqfX&IV>drBo*^b$ zgTE`ZJ|5j`02>Y~1doOtMp;OR4M-_+Q3Jv8X0iYRRev}D7Ek|YH-r+INghEGOCuWj z|1z-9!fzHAMdkZS`lV5(N%Y4t56577a@{t5s*F9AL8mUaao%j#Jh9xF=?3i3v`OT7 zdK--asd;+Yt1M}i#gV8=Rn7t2N(`(EN(h#DaT|XZ7Ez9uV{kkYHjMDnBf_HFk1bi~ zMPd-LWb|VHB{(`*5IC1Zpqxtz#*KgV3so`$3oe{*vSz#iCExd>-8?z^kiKs98}e{0O4C41g1x zx007t!U-;|NrXN~E=$e)wDMqIe6fXp$zp@s8-d_r*r*)?0d`l&ndJ)?GBovP=N$#}S zRHgRT73 z#o=i2VOFA~bpz?UO90zuJ%m&L?Q{DOavp%|JJx`5y+4eEVvt4Lg*s19x@jYx-_w9y zL-&@VCGDJT9+7vQW)(CFH;dWjT`#TgBN`<{9p*074K7)yr3|L@zqLEI+yp~Uh-E&% z&?`2Y>uCSuwTOoziI9h!U9aTb)M3f+qz1b~FfmkXbQtZvU!j(sB^ zD>J0AuL--j=C&@Er=}gv`^d3j)TH_PPsF2t{D(<(RrC7>+bfLz*d5N1?$k2jfboiL zV3R^&@?nQ^RSnh(uinjgI5&jZWd^F13Pcv_u9cgtT~^g(%s&C5V<}~IbvtIlK%uLq zwS||kY|*`9CcGP2^UfY^?1hg#%ySj& zyXIQ>DQ+AjNyb`<_v2Iln+7sVd5A_R;bVy;% zln|3o1r?_A#SY+yU|{b<=`7KKzYFn^0s*CnucHcgNhp!nGp#K=|3>m)sK}P z?X#N~^PeCYvFwKne?bkT?3;+?nzU!Z7?NJ=I1N}<#^C=35iQh_!GtQeIiT--X`Di+ zy>rdWBPxhV%xFUs&fx}6?={U_&FA^#(MWhK1`u+8%)PHx_RoT)k6=m)={EE0l!hV0 zFm=)sf8eapAG>E||u~BrLq3C7BD#ONu-su$$$!${`?9gd)SVGu(@I zL{v0agYBbAlF5MAq_72WsYJ_aU4!ihHz>1YFjLaK1)Y2lR8xqY9vz6A)Vg$bSyJ(l zC$kv+E#he@cx?fEt(+%G_dG z>(0ilPEJkww|DTRX89J^#043-ZcdoMc-@gEI}7ArD|HvR`d8-E6Rf}WVQTrQG$fy- z!vWbY+p9)@BmuQ9%(q(XENjm`)DSEbFl2c6LN|APQofH^$^n}Z$da@w^+wM+yMjK< zo=7H?AIKIM@DvazaqhULF37%kL%c46-Slst2Jx|B;nl|oqP>;jxi!=u0V8;KFPAY#rz=z=jZdbrCbnjl}DhI z{x4c_ek49bWd9daJmB=)K~_44RX>o&QNmi`U4pNNUsIuj&2GGVh8;4zYF*+nbc5g6 zI08{OK&^P7r$)6D&hR*@jsIyq2eoQce)%+e$3y(_a=mn|fJBC-S^*gZkV21j>QV3N zQ-mY)6gPd)|6p4m80;@EB6dXDOR-&OF(rS2;GQ-r)MNK3&ciAZbf+XkaQOL!v>xb` zVzzg6{zz?Vk};w1F8Dhsx!TC!X175Ft5!w*OM4f(|3xGPXcQtDkx_4-UuG-6F(nly z8XmHweLw{z_~xu@8(j8$^EM8Gts`!~3QQo_01~J`=Z$6A?k#Z}MjxCagc`tXPOH-E z_*fAkd5x7<(A;&wgG!GD=OXETIB22P+oXOdg@Ls0<_duD1j7X_NO4*G$$+5x)xZ4@ zgK>`1d*E=3$)5#yBISwEuu0^aAYuayNrtbJ#A2d1h+kYJxh;;eI={zJp@OU!#vtUYUfHU<8kviXE7H9ucP;o657tACJkZ$OEL&*1F-AmlWm|cVh`T_ji zc+|iI7yu4t9o`;>(~d}*2jnLi3{_q~fW4a(+*D0AS0Md8`fQSE zvD@O0`o;&NU?EVg*)qz)2&oJoo5lD4HvWPN)R4Ntu6lTjNI z_wQiGgDG^@%%@AXHL7S_5QTZSxJM_c(!eu__#d+4)*HA4l z`NvJHz&4rAf5;L6W-TyGBb!@HDbNQSj3B6e2Ad$@{L(QE4|k`p*I&FrY16@Qr$>y<8_ILozA1yoZW_3J^dA8aj6G3fKYbAG5WO z!DO<4Sr6foe#gg35HIon9YIA90l4y$uLa*5Ge~~e=>q4j0Ywo(QRWoeWL<9{)Gf<$ z8EObNhDKF57yXDp^vU)9hvxA{pgI}%%4+{G+nW(fPb+jVo+>H+aAw39Ja8x(f= zV%Y}u1BJt}JwQ0qW>`0mxdqWUAsWmL8JnKCRk!jqgt8a^S5x`F_*(ZYas#2%4n#;Ea)`4;aeHhu{JiL3OBc-`LhRTrnAw-n?V0cC0cX0CPf9Fu2CrzgU8S z2zOQI@IM<}5x=vp<)`6Rr!*xUCY%ONgFYbJVw-=)yGMQc5E=?k$pgFsz2yaZtMNj* zyYrg}z`(^A{0GYcG^_|Sk;yIQr!1H!t4^i05$!S6f-C}MC^$C9RCl%Wpd1K?kM{=+ zG31$YxHf*&9;o>me(7Ukfh}e?HE4@Mh7yAg8F2;^c(mk0LhD;2b1GDDbi{E)*WO~v z<=?o(Hy8Mo z7lfZ_7Zqq$5>|_#e5Npua%5P?pA)#bef24(ry(3(of$N1ifetfL16KZ_;=tE#un8K zWI5xIB^?gB6i2;FPZ1n;@pldf403X@zQ`a@4uFezdH!mpGZzPmG8vRkWBCWA0pGd6 z)M@I+-BlqL8EArHwp6_inF_#vcywgm=oODH1~nmQ?>SI9r4|QPvQXt8BM20QfQ}|o zB^mV|%7H6A0qXZ4VaHTyAimFOxuk;+ZTLoJ3)IRTrO~KF>3VRof5($K<-A@HD3jZD zH=F49|I}&lz1Jg!CDm~dc^;fZP9ESYfu@y-<1i1>6T*Qoq^7v3)&w|1klxSy?P321(GNV3c5^7p0n`8JzRY$585lPNQn} zg0L57AJSv7f)@}hBN4bwAc>wDKr!WfBwPIsO>K1ox>2ct2-Egnzjv}o^Kl${QV5h> z3#+*5$gY4A_z3Tt3ezNAX=%BxrwwM21#m*XMRaZW_g@#FrC`A+V$BN+Cm4be0$#PG zNcXl?R^1MnULkmg1GK0FYF)P;ZjTGWe4toA0#eCE1ibnP*b~W~MQ~0w+T3$3gZ2^| zPLc492;81q^r6B_R%%E*=YX6)XBg-n@SfFkqg0rFD+aCv+-Dzl5v~jUim^nc$-i~E zT86av_QNrHg4z{C*kkwAa*!p5X&~{EU)&&Sdp`4V%zB_NgQD|QY5#sc4LtbQ#Y~7h z{6Q8_0w1EXIG=D`n8l1GicQGCZ*H#;M6P!3a}hyxucD9}_hUUDc8u>ffkJp}SjhC)1iqw&Rw4 z_{i3GXpFxIXYoc*%2EwbQLHPwL^2pL;^3N`LU@d0bQf9sTf%+usr-r+l zqz9Gc;f<=S_fkIxjo;VpjazPogfLf8?3*R-VlH-W$n>!f{-3XF@^@gm6NJE)9 z7Z^#}P-yrO-xh-G7T~aBKpwfR7Iqu|83!*5*A*_KIy7*TNXD~agW`srL9fY|$5`g{ zgGA({t<~a$n92_M@tGZu$A-7j!S)20sOFK2=yw&2@sf`2aV3okh0WMl{`jNpLuG*x zI2ov4e`@~n5>j_Fy(t#S6HU?*?WN`zPQZi>dmd`bYf4>lutcJ`5YN?*Rh7t?@F+kd zvmL;$|03iF>nth?YJKJiB&ZGDYw3fLUEUxMRmXgE*rR+`o#I*E&~dJt%^kZ%`~FAD zAFXYC%QzAv5@Hlk@daT?*e|o~q}*c<%o?oGR+@?+^`gdL4Ogk7pSh=FT3U2UqTtefh2VNXiWcOOC4RtkQ*>i zq!GL!;Deh56zZd^f8k*aEilSt7nDZG3$Yo-52_@Q`D8x>RR9I?y0ZXRU>Zz=UvVX< zEzN74@y%PIO@PtF1im=B&rr5GUUKLr0u_aJ;DU)&1~@s_L_wy`ZHm$FC5vtntV z0%qvx2jz%prY|CtK7&QDyJ3+Iysf&9Ec6j%%om=zRPy^P#_aOF^LF$=p=O6cV}Eo=pK``&I<`@hT&DX@mvLtI-(qj+WFVJJ3K(;IIMwR=bqf z)+~!4=@DYgi}dZ_Xo(vV4i8>~ZOVj?i{D*p`ZQ?a}z8K!K+x7RJlc6J7E8UZ zx+AEG)9>l(U9kG@)AIKH$m7_yYRANRzP&bzF&9`Xz93{d3|Jb|2qko;Ni%d_Xa|XM z>4y!McYHg9t5K!N;_F;<^N&~J_WP#!$qAv&0O|Uye5d~Cca;~%%HLFsKAkSzr}Zbj zuafABl3!*R#awl`$^=PZ`uPmdg3rUoAl>TvZ6GW)y79HfXRz4u0wen{(NtVp#X>Ut zz4k89<8{nTzB5YS`55xzz_LEZBp{Y{m2Yg9tb?4{urpF6)DZqme+=D=@Ah%op}1gC zUWt+$ViJ(E=DaIKZl$pPe%W32GtLI}ug-NT@{#j98L!p}T0T|zR}JM`Z^?zWb&>L$ zRYH}a7es%#_y!Mi@qMYTX<&c#bl0nt^@jB(Pz2FFpi>Y$_?>A{f*e~kXz1GL0@k`m z@B=F==jrJs@n}n^CS=w~s*RMU`VN7D?ieJW5C|3>c(SX0$jSd12kheC-aCODIPn)+ z-Zi?MHuIGriIIK(!@Yc`Q@{NvdwxKfmZ5nh#@WY)gh}cXvw&`yr9bBxHT+7Pu4nx}p0mE9*zvCVj z<`>|5pu8nh`?T?8|7_d_JAp?>kuFgyrkqmUUY2OCpGsdZcl^@2aVqDt>Ur1_{d^kN zyE?@UZ_%uYYzqn#eq(oGzO+xO+$HcWPP{N}w6EGEx3VKGy^Z{{k$Icrmn?q}cT<*< z+%FHb;ua}KVm^>?3+zaBmyyHmcTl%-xhVXomE;XlV~(!?U%-u*EO$#!@JuPSRt-t* zRwqSVX|lf413WZ>c+tZ(3NrBXU-~7gMJfPDK6G8$%jsX+&AV7`t~Fv@B!)!k?K_`L zb`R%3LfR*en}&8QMxoDo9(`|NQhqrcnbw4!SzfkkH`qVo_U_n1J-qi^u6%zvqdr1M5uA&Ntz4UWyuS3b+q&TTOe4mX*Z12KS&uIf^H0o#pNZf5Ym92}LpyZ-5zcx*<{FyhQ^YjKE&Pj`#rO}!xIJ`X zNvPnt(+x}onUOo?HLRzxx4+h&mjE62IZJ8(e$~4H@hUE%*V7FT%f)U@8eYcRjvwca zcRW8f&_!sW!rlV$sJGrAcdT{i_tIjveety8?1gp3dQtB_6ex>#9+Z6AnKr7Z&+tn# z6}{YOr@q81QeuX7X+;~!xSbNJbwP;+_An z;Y+(1iLz+;8DOK@FVbGDPnL`gK8y$4Jt-i7`9K zs2Wj7$*E(5FSP(FAjaS0X0>X@);oH7lXw!iJRQ~hWrRjN)>m5t1G@_Hsi{$ad=Ai> z=5X-}rA^d;D=qqu1Ge>Xg@(wF5%B~N*jdPMLzjRvp_t^-H9w4GCLjEfs`Q>%vGn+o zNSjp`*>AcCcm4+fZ2W<9a1F@~7g+X9=zmF?!2O;xtpgFKi#`>6r<_98u7H0^r0*@E zLN|YbZ{ij^+&$AAYLP9Gf2SJm^E-o@e^W@<;x zP%BpnU&~%LPE}r-f{@zY%O#iuuaiTBD|MRee?%^m`Te=~ma}g{31r4{p@I|r~{lsgK4mY|;JiM~xhLfzNv2jwm0(R_|xHG8gJssbN zcoeaeoS~xeexZunDH96KZE2qi3IJ8!77v(&Zh0z-Kj6{nAok4h4DC=MLblG_bI4HgVYD#-}$a{Y-r7= z&?b+U*JE92*u4`LK`XY+6sqaUTqPQZc~S$dAw)x8e?Bqga*NCbbuurky!yOBBmU)T zUX8VuJrj3Ijf4Ho7=}eu-A6grulLh*wxU=&Z%4-42kgVPK)6`sPo?#6sFwPxP}!Kj7&w>v$!3cAc!Nb6qtWLPRv?XHh`gKd?Qo5Z33{fU#ng#Sr$;e^GA~ zw5H?4asDF4n`Q~r=>W_=spOSxZc#Bo4s^Ie37Kv`ec|S-GX~KIO|89$7FAx@1yJ+) zMURM|jGZ^RMi)j7qEB`#C}-s`iksy%{hj(@vg#CQX#l0vOocA6lC`7+bq!=C)RKd& zCEs*wbf?cN3i#ST0}fQIaH^9LcJwN@WG^aYtB^njSX8@-dYX4l2%V_X-z0>0d#0b7 z2rzRon&Ycl8X4`5D)v)d#dS$hRfdnLs*v)su+cY|3MPOqM1Ib_sZ{o(SYJQfe75Fr zWlYbmUo`m+EDaXu)`SvrR3r|34BuNq-i|))#XsDVl|`gv8C;ey2HB5f z)VYf6^;)M`CswirscfCV&yp-(m$Ngyr$7(YGz{3QmNiW<|B1b%^Qe>t_)YtoD z{f@qStnCu>IJ0rO=GOo%T1s|8@fMf(wuIfYJ_E4owdMYXYoL57l_!gWhjQ>;<6gFz zQSN2a;|+EJhQePc*(vYVH~C|mZoPo`ORUI;eAyNQklp6Z^Xts6qvDMzt8eeJ{{7@# z#SO)02iGX5HK7s9>3CdPyfu&AQ{{K6IF98ku7?n*fpU>Zs-UXJuTo53x1Hp7d~L50 zfV^Oo2UyNP`C)&=!0$-0w%aO@bbl|g$+qq|-zX>7l3!&A-D+uA%?dQXcwI1C&2+jZ zd_m?^Sb&YD5PGB0$j8g_BgfgzJji9|0a9Jo)o0`FWSx+Iwn|w?8?nQ7I@>G3dqg%< zk?n>p(qTs#)Q45fn|@`lnBSFtjrS!Xm(T@~S^DInmD3|lg{ZfsO>rRo0kTf;%XXbB znh7y0XhJKroJz_f?cXmM9RDJvv^8=tGYhi(yv}a^5FsX*wgq@EeF{8V#+U#FE;ZuhVzU`z@ORAU!*zn>R; z?iXIS7?oM40%Sr$Q-uo7m73_pi<7DU1~=?a%y*>UokugWG9Y`xW zV7#%O+q5trL@`*Q6WQ^o4DHlww!b&XZ_IAipCKZ&U~Kjb*J=IC4bt1@#^g41mL@P` zT4T3z(`89wSSIF}vt3qqRtGxmsDV`{a^*<_*quNvZj+5yg_*J8-eC$|8+TsDRqxMf z9irakQSwPLUvp|O@lUqXMf_>e%Bl+2MMQp{L28z5Pk!|FX>)0=R4}~&Uy09}H}T+` zahomNkTASAlMwvi%(5aHT-G-aZYeOxc)GaAb`8t;aGNzv@37Uh@1EaLv+h0>caBj3 zt8gclsrL2-QaflaI1*+mc@~DdZTynfPV9s_iE@DfG!hkpB{u0w^aK$RLt)uE!Z{*o zvWL_~8dr~N{f}>BeYdk7m;43-)0K@4NinQ>zQL9nw!U^n$)xD6(`mdD1tD zGku`#BR;pXi%eR`=j)!0Pl;y2io&7?1PRD41}>6$jD9c;tDZ+pVNhu#)LTKkw>=Rx z>f!czI#Tan_})b{g+6 zgJRU}@SKZ2K;b_{x#2gXLNhE$O4nF^-V?vWe%xo*)+o(ZAlh#u$&x2PmjSD;w|J}LQ zd<%Dmd2w7%O7i~A$4bulHh8r=K3d}cd^R_lI1_!TDu6ZRgRbuXPe%7aUp0`QI{n$sk{`1yiW z*uWOl@uu-V+Nq3Tq6RL|o{Q8DF?>Ls!1sLyiB!jNp=c-)2lIrIf zDEjJuwfjV#mf~sR035yZ_AhYpeySfYWiFh8ggy&Y3wft-WEg$bndfU;%}%qS=BBV3 zTVX+3R=yGIrOe!QCCtpEnJh=N8hh$bth;uv&jqb_#AsHn25Ns^C(?b2Ar{h}zW3B5 zqYEwSNZ1DoPCdM{&gCH zM~5T$(^6)VAiG!2EVgg@1z+3KbnZ5~I%A|3GnneR{QH$Hw{04o(eLk7-Vt7_$W<9X zYN^%Hv%4$at}bgxzXnu}GZ^*XnkG%GODMzoxK`=eH_w4RtwO}kfAm4EHvR>a<$8$9 zKYC)f>n`PM*9d~V2#96u#UD4-3{2d4%c$L@0O7DE;k7_Tdlsges5N)bnWQyl1QoJ9 z%l5!+{<+qG3m@wLs%|pndxNXqKYIc)aHzct|DHx$ryrKY(M&gJ$LnzSa96UmZ*_9! zCk(!iBJAd2inSd7=rqrt`jYllI1pk*@!BN56kjw|;Jn~|(7(^f$)B-jh!JZq`k?w;Z*jpi!0_-wQj5~Tgk1IH>BCLR^k z$Ymx%&zbijYn*yQWWy4{BF$OdZ|Wl{3t=yOvi!6X{r63deRw0B=VqZqCNAH= zApi5W;X`J)n&O}3GmKDorQa!SP^nbA6z;e%vE;fPY1{+sgQqtS@O#s7xE5BDSj3-A zF<5-FpU<>3l}IQx;D$DvHZ|DuUx@Ah-h2wiV)37=3!Qz4*mE4aBpBF0+i$X@-E$XR z*_XE_!bL8l?%mU>%XzKeB|53E)2!zo^w^j+rrzqxOJ=KJzoaf@pGTz7+^C8rT^j~} zb7UnTve@s%Uz2{#YL?zM|HS!BQLkIA&hlF%6a2FXbRKeg9u}q^mcr(4mf#1#&B-Om z#wo77W zW(jzEd$Zd**}I#Yx>&M1yIE(Rh=PBL0nmM~rRSkx>P_k5=4@r_U`grW>tac1>*8S! z06tk;7wE{YI*g1XuSdsT?Pvh-G-&MRXt*@E%87XJxRkOm-d+oMnmR4Y(S@HwePey3 heMqA)#XKc;;5Pxb$AvUsGB^eBN={9-QraZ=e*o2^oh<+W literal 0 HcmV?d00001 diff --git a/docs/.vuepress/public/apple-touch-icon.png b/docs/.vuepress/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..14e254fc555990d1e3c6d321aeb1c591d82e69ce GIT binary patch literal 7368 zcmZ`;Ra_Lnw_cj1q+4X^r6d+XN@9s6mj!8%h6TiBSsIbW1n7Kx;bnwIkV0%H%fdc-;r%%7ViGV0wE5942_x~{>l_ks5n{g>4>q>hTB3j!mHZY@Ix&cdqB?T5S zvWNSeaM^G1^`Btky7>X-r^IFMCT~8RQbdKm8JvQ|RxWN^5Mc*R!{XnYSL}cUAgBCv z(oWi5<9CV6^S_-k@6W#Xh@yylgqR2@F9s>cJ!&hy?V8 z|KQzILefcl@|*&Emf-+%kTEb**1k3o7r74kQuA52(Z-0x?2M`QD>27aQ2EOwhbI1= zX;+bVqK(liJREPYFyG4U15g8)uoEzbb7=LdkXu{r0J|x+% zGrAp14OuGNrM}FqMCaan@nAp0xb3)J%G>ySes-l}hXy4BwJMIW`GwA`1%*TuIet}i zR>rXj|GscZw;%32JT~jQzI*qaRkJ4W`@Af88ZOc`&TI?8&rVA1Ko-)d*>@CX%vuY~ z_WvsHlFD5Y538{6(g+jCZLjKThyJ#1Qgd%|cae1kt%Wb(S$w?E6F5qLh+jl%73nu& zvTAn^^2J5Kab={mE$Oaf=lu~^x)w9}hvNdt;YU62$zdHPxsLVa z;~tO70(5Vp4p!=K4dTpIqol%}8e`V2BaZq}v(%!=B!-;PaiRp=+-r;O- zj`^_d7o0*ZvO4qjG@fASMjY{z^F?^!i)vu=^B+nceMwy$Z2Ne~~&k1!SQz!Y` zFtc+pd9k_{`9~+2!;HV!NoGdNg*O=GS7vy2GiRRi=4gx}*VzMkt1^fqle=A{nKdp* z_33Fgse@;6QgXJHg{jQg6gG894vb%MNE~FpIi9L(f@rkk?yR;MyduFp2p3KgG0EGB ztjRIbhSOrGne|rYz>?451H$*`;z7ikWSJ9gK-UHbRX>AY<$0=W$eI%SQR#m39L(#M z+()tr4oa@vdy?!2d~jXHmdGRG?t5RLoz235*Y!nWefM;Emp*0C@!R!?$D zq;dq>8vyW6A~+PB?*Qs(e2ZAE2`&O7P32evo++yd^qK#!fQu7!8C?_J=nfNTr%TtJ zNKC{TXw_=-@f#fa%-egzl)Wq7lT5ra#dUk8{()w>@t5-0fDoG82eIO;2o1273vPbZ)0W|{ zWj33~FS)gSR~pB6bi|Y*mR90!WYyv+2n`ZSWsGl$n@=oFzbTRwtIczBll<$I3)#1P z-tt47MW10sumv(INwb02cZM+}iaLs9%#M1+(5GHG4)a%d77ckU%L<}<1ck~O_^oWD@p%CwwO-#usYDaTH9 zqD;rENPwo$X-8GTL&wMspS$kJjeF5ohA|m>LP{^Tg__qjtE2Y1v?UrdK}ea$YThiZ zrFLnQOgc-VWZ&NyGbVw3V-KAvI6k@p$YP~K>ej)NFWz%VkWjvppY;E=!ho3NeMPe= zBQb`vuD8-+OkIOirvoR!8xUcF^c!}z@_FOCK0JO=jP40Wu3Uy!n&B2OXQqdRw~r%t zUv5`ZpxPsyW>LjFDfX&VRO$Ja-*Q{x;;^5=^xNkO>7KJPDr@UX1N2rd2%PnS#|YC| z2=n~v2KEHy@%`y-eGge(%~&ToI8TS*h3Ct#D9Oi5_ww|XP}%38Oi1*RP*$?bYd@Im zDHuqjMy$LvA`pXcgLUvWukj1QR6=#o}sLzAV zv=spjB}JJ}wI*pZ&03P27SQHxQB#>h?b9upTfio4=sw-wLIW}}1>A>=gP4S(qf49P zVnNQy;CQ2%nv9VUI=Ikx#M`9>wGE=-9wHH|tg0szvAUukhQr<=y(_Jl5TaJfllIYb z3sRcuTsmwa&$`6xt#od)K|mF4G2e~w8{Bv@vlAiWj={MVQy~KN-)C4N5)~p?NVc}N zDO@Ao--xp zLnIXwCcdEtEWV4Jd{OPZc1y8H0)w4X4KV5^+keM(@dHk9w>-0rf`QPPov_!>$euCO7y)5jwvyNd?8hGRbp$*xa#W z*l4xLLU!dH;DAnE)=Y^s8=c0xm$F%+G%MeJkPk>jXX@^2ry56ZXHqdPFt<_ z5O&vkC-8a8$kv7-DJBblySUh&hiNfioOX;2sMC?o-~PE}LH+5QA_ig|Xgqb^#BQwa zDbG=R!*J-wQpZnrYjm8ywc?MWHx{zJp3Ld9z5*piCn>++o=D zdZ}KIE1XT?<&L!|X0{0 z>Bl6|r{vGZ?8h3tq}gc#Pg_OGx~392@^+K0QQj4k!HiDo7|susVHzj_ffVbiuEBkg zvXuk)1s8|3$+uk2B-pEz_x`!N9JzwjY1xz&QH=>?WUZi(rom4BFEWza!ej1cndzKh zwMjkwKRA}Gzuf#4Su`O?848IKV7in{bfym{>DXuN0`W`}X`Ky~HC&BDeqhw{!LE?$V6nFMCh{1*h1q?wB2$P#N%T!8<58cBy?70I0@rUiHcQ^Gg_tPz_x(u9S zg5P`jf=G0opGB3BFj!QyOY9J&o@ixfnV_;;1WRdU)r;BOUmD1NBls%w#eeR7BWCwZ zd?PJAQ5OGo#UrC4(T`z!fftx&R=@eFg?^-4wZQD=6gv|7k1eas*JMn*lWhw65`HH) zcR*Z9TWodSy3=#_G8&--0C+F)@1I!p1M}PGoqVVs0$sN9p&t`Vox#eZ3Nno|8gMZj zv5gO-hTz`>C49>q+Vga_qf|$G@U#v_nRCTYlwgm%k9lR-+x9fg{L@3U231-NuVQL6 zYkLO2E@4KW8)rwh4Tmk=D|nxAVLo=6_6#)k@@ALVXjZh`n`KO+fI_ZN-MTJ)g=6Oq#k0vpub$-2>aRzwNqBBK z#u&m*?%wlHG9tc25WOh=#CrDbVQ4xjNU)6>Z-e+r;N{^RpeZt%!0pymZa|L#FAc`!|?ZB`*-$Fkw$D!*fb(PlZBp=ZJ}revGa zDtjP`KQbY%B)4?3ZMnk)<^AfPfGAgn8kDW|1D}@g7{-FFnnaML-YL*>kOr#;?N#sy zE*!B@bTp||+M;FE>N6EuYSicTuAJt>WiMlz)luGwc-F)MzUK+{<6ErYkm$7ZZ~AEm zR1aGp*a(UWFyr+|?HA;%MTQh7LPH{}!_ws$lRWrN3j4?7)yi(rum5%F6lFL-+1lp# zv>x~m2sRPIP3zDEK;Unz160%h22H}9-bWc~I zqOq@r;-~xYaqgeA+mQ};7}GqwnvsJ_^SQ~ymm|HC47@~%uP+O8pPq_ZvV0TOi69kF z976<+*QGc(KfTU~fDVW2H}8YDt&Rr`X#@oDUnTl#YT>hmK^F3SH=&iuh{?w%F9LrzJ{SjgchL&371@)TRD7p zVl`iAISl!Gw@hnV&J)V~>37EInJRQ>N7A&I+j(=M7tFu;?vvX1)y;=X$Ws1tR#*2< z=zqKtDI?z9A2WurUnpPzrah-j^DZG7^yU*1rVg%|lz3&-WNAnoS;eX@9!oey8A zKFAyW1D;oehl(cGoch=g{OIPMHpQyU+}k$m*Vt(YO+OgW-ivpi9i;@`t$ZLFq>D*8 z`QM*Xqj>1mRW-Je7p2se9!mUZoI~mqi7rm)ALC+tFUrCoKB}E=TAYhE++6ToGiMgA z_=LgNR;ryO@Sm2F9raAa^UC@}nr;eIwvW^CC?5=I$xz=0*J7FNz*MWge((e)Jb3@)8Q5~?bIcejfILa(PIZ~%85 zB)1H!AbY$Ze#xf0g}N#gB6OlUPv3dNS!>sGuN$&zrJIeQ{#`~FC@g`9?p!9-Q8^Ml zP8q(q5xOSz>e`G->`v0l>*D1urqK?o^7h}ZF{@nsT{B+XnS5!hv=r}qr#_C3z4Wf0 z93hj>GC1}UEeSS0$3tl)f{^79@AQ-T0la%lI`78EN0a~Ffaq?H_JubfT&s!qy9Jp9w5eVLqgG6{R z;SxV!+42N@rC_WS8f2ief1~La$RbbtQmqS5^ks3{0R=vH4z@ETOOlEHroZHNv1@Vk z8^>?H=oKWA)94EroidY%M^gUeV=N{V*~%Hbu)8?tYC>VPeu z>YN29Y(8Dc85%7wE;9f&UUPj|X{K$-dX&a1sM}E{j(E_nOi~i#e6x;%o-9QjZYpN~%6T}($Ke9fwrum^@`cv`UJQ~o-gwa74 z=a~!{F`J*&IMt-3!E62isMb|UQAf~zWwhIW1ZnpdAQhZIE;C-P!$=fa{3MP4_L9Y=Y0(?Jv75wI#AlCx4$u#1}1DG9FcyadqI?+FG6Ri>d&zRdYRg zz)?a?Dil40T$2ke3f6VJsEEA+4Dr(PUy!JPEu=+p? zxrKKEHJA^KhMlyv>p<=OsvI{TS@w$dm)r13*HMOs5iQwz_0$-bUF4t8e$!}Yr81OX z33UL|QEfD$p@S-HUQsdy2P zAqBi(_@1bh8rv;6oZU6zyLe^k7ACsMm@MIBe!@=G#e@+oc5{sv$*a{qJwN-ekxo-3n>tn05VV3Ev4j>1i>SK zJ(1mJZE=dTvYt+92uq|>hW(_BDnw4bx;^JlVYBXCQAuVVDP8j9;6k)#wlz7qmVNrYFW%5${#@fX7i7&}Pg?pB^MqGiMTbLSjX0*q!!!!bD9F-91_#l$YmzMSuhcr;3>uMb19;A%1> zACwK*N9HAh1D3jmL5nbkwehb3dv6PZIUf?MGd$Y9E0S@M_Idr^@9IP?p@(77W4gC8 z*WE(d7hG9wFfTvCytji@B`3}{)-`hL`8kJ<U@CnZ4 z@5eCGnDN>P9mlVbfM#M*ILuxCi$f<2@Jsb}_aC8BE-SNh|A0)5dp1W)6)M*YE*^}5 z!`#U(6z*!lLtQ;G4EL&{3YerYaeF)1i|hy z03{a_-{H9T?n&_3<3)w(4Vb@weE|P8$;(6KWwF?~Om1`aw{tN*8Xsdl3lWasWQpu+ zzEWB*#Bw?*70)kA&YC8h;Y9~lg^347w5m-LdVt$uI*M9uOS&aB2Ys0Z9l{MiieD`k zdKKnSHw$EkscWHycA$wb!}2a)>|00$l9iXTX&Sj>DEkz@8*W5m!cw2I0}r?XQOE3L zk1BK6UM$a{{hLjQ6eg)JFFpMXbX5!;$3c%~)%VwB2&KDItqc}F6?HP=%=bg_b>X>L zMsKgAH3#Z|kN>*KAInOv8u73v1OlRd1m!A9DO&5kBOTBC85o+Q!cWewQBMBv4Mq`R zB+t}?FeB%qoVzC$6qIv!N`pUwzC>4Djp7*&+&}DDn|o(|MG&Wiu<~xhDu#+zO*+*2 zp>8FYC*JYRXE3m?hzTXPoH$p;@$GqDW(?Wc@9o)A>+C~HbV+O6=S~E0r)%j~6hse> zu5&eQN7zb@lA9l@F;3NO^RkE=oV0T|r%NB>b!m0>DJ>yG{Cuq6QHMs z;4eD2MeUW9%VS?cP>})38Q80Tt+#|Gp&A|Vn~s_wM#~c8;)roobVj@0IDm|lw1R}x zeF-UunY5gu6hu)i=N1?5BB!OI>eDAVmIO3CdZN6etDFDPVhxU841JFhf;N>*aEe~Yxs+|Ar|aYk zf9m-W~#^d*r)tg5uGKr=X%j?*811-1(#*>Yc2)MVP8>| zbo<1w$_F9W#mm?3em{Go^78-9&ewa_R=sRoKksIN1^Z`bU!FI+o^3kha9-E6d%>cB zv^UM`V!wa5$bVe!tV7_n#uZW9n8I{cpUQDQ{^suGKVI5@eT+Qq*Y+6A-T&KM;m^E% zyLL?b3k*@!64!{5l*E!$tK_0oAjM#0U}&LhV5w_h5@Kj-Wnf}uV5DteWMyDbE847q zq9HdwB{QuOw+0>$>nT7Dk{}y`^V3So6N^$A%FE03GV`*FlM@S4_413-XTP%n#v(&_ zRY*ihP-3}4K~a8MW=^U?No7H*LS{ipG6O@!oX4MdI10lwG*0=SKI8c`h=Ey|TQ8Yg zSXtP6vIw)Vf=h$R;S^@&%^?b>Z(KQXk4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SV3rmG;#XnQTLYxY{FZzzW=v%rW;o&SZL+@XffL*IB07TNE1=v&Cue+9nN;%tg7Vox6-rL}|co7$m*rZd1tJm9n*;JkO^EA`@hu;4qp2Qd5 z33(s;S=)R@glUk`?b}^^mB;Vr&1`a8t@~wWui1`_SyETxaxG6jeRKM)%HfkIwwwO` z^!r4P>8hZPu)keZr8BcHUe0+u?RH#9P+0mtCs&40roVPy_U5q~9v7e1^Pzn2`(2Ax zY(MF~ICQ=5!a3V&<{lGKYOVP-<-b|5@}&vRnX5e)uTo`pkh9m-`!FN_6qM8G85L z?P5#Y(4$pTPG{C{+dKV{eMsT-SAQdGvw=BEwZt`|BqgyV)hf9t6-Y4{85mmV8d&NY zn1mRbS{ayF85n6B7+Dz@)QUE1plHa=PsvQH#I1qH!+Hu(gCxj?;QX|b^2DN4hVt@q zz0ADq;^f4FRK5J7^x5xhfVr3Jc1ICAEQ%n|m} e4IT@;^cY@=3zmFxGMx&vg2B_(&t;ucLK6TE=NJnB literal 0 HcmV?d00001 diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9174558464954b8f7599a66e1e0179a479249c8d GIT binary patch literal 15086 zcmeI(XN*&|i26Z{kwh`d-q}P@!C)s z)2~t)SgBMR8s`1(S*c8q&w9qI_d~l?Dx)Go|A-U2REEUnd4j(GTx$IdA@quEcx;nm z`z^LsVeZ_yVb-i!;g3K52!H+cSF58EhjRab@c&O0wmpFX{=K9FY4oEgqP|NL;}l~;xzfBdoTei0FE6Ji@wUW4DUO^dBn zz$<*qJp29k-@|FAofZx|?6C03C!Z93XV0D;{{H*#;2OV@! zxbemtgY}S~yYId`jiLVm2ON;bHb9C=&-Xcz5YeDOsf3-rxVM;#STI_acv_St7Add@iGjBxnjhlkToKRw)b+il_9 zci%0@A!m2oaYq<6YE;5G^UO0-pR>+7EA8EpM;;l@J@?#j-+lLm&p-dX=vT*0UCK9) zgXcqs4o!^=efHUBi7sd1>#x5K_uhMNK+8V+>=Pb+^wHccJpAy(>6!K2bI&~qm;B## z*Ij9h>#n;l$-cAo#v5-G{TB5NU3beZw}cm8d@=m_=buUcTz1)IiHBr^E}1rMTDbMr zTf>)MepyV9r)ab%aNv@8x{D5*GiOeiJb7~B#RAGvF;`nR=tcvu9_ROvLk_7ruk35a zf#)8L8R(xw4?Q$AHa1r2%l6ptJl4Wo>3qDVJIeZ|tug5%cEn!Vi~pc)4$vWFmi|8W z*kg<4HyNUvGT<-S;2)Hq=P~qWJG7=_nFHRU26k(mb=CVt8@!JF?jIcH2*5AtdjH! zer4awb&oyvDCiq9WJux5(Jk-2_g>mBYs<8|Zn@=_;pLZK4j+E_Vd2v)>NVu?$cs6* z+itsNcJs|Q-weHa^(tuV*RNmlcXD4>cEsAt^pl?;D}cinZ@WCzOgUUJDLRdg-6 zH`|Ys$>Z@1bvD=FaYSGUx+GUqrs_3##zE0U5bXNLbd+oJmd_{l%{{2Ha z9_Xy}^Xtkoz0yi6g$*{?Adn$`PlnD&zqo0~9e1pv-}x9ldUSer;DHAgGSnIApEz-1 zSb61@tLPUq(nqVVx@y>Hqm2sRxHHnvw=c`inrp6^d|~!r=bd*>HtYWT?@uDsnds+N z^5eGMcH1iYci(;Yl;1({1s7b9-ieJtXQH3~E+$-Y#T5(s`2un~8R#XlwaF%%q}b)1 zciw5Hr~WPd&u1SQ4Lj|$Q}TzzWqgRvV83?TZMVeFt+v{#ivGR! z+N+se7T;Q<__DLe4;g>ri6_GH%P(Khzr+$tgzK-rKC`~KzVgZ|fj!u4v(18B)f#K8 zQS`B{e?xxo|LCKSuA-x%p&|LH1>y5<_^&(cutQjS>7`TdiXUB;xqpLxKJTWRZdygh z-h1y|GZ!t_f@b>k-{im83LWB&?%lf=^z-S(!?j@2q)AB+Ew|iqRsD?B(f%_XB1bNM z*=Cz<3YyC@AP$oouLXRJ?YG~)#lC|E4GPw0j*iHoi0S(D>67$)IeskDFK^2pv)0Db7pAAb0u?tTl4NVerN@ejDl zA+rHF-w8)9SDrBgA5_1bAA~D^t#2L+j(+7hGxuLqSL3TW$nVHenM)p8o{G-EOF3}z z%}2-}25>&qTj0<`bTB(@46&vBuDxt${UT3M*OecM*Wz*e&v&5j)m)riYx7Q>s9ZmC zFURYh+zVVjwVHr2)NQ=u8>x|X6xd^Yr{CmX=?Zdi#1TiNJfJ!j|I%3~*Wq&ROAX0d z=^K6U!Wj0GPb3GN<*Cj!NQAem218$v-|^$er`T4_L2Rp@h|e=-%&4lBW;)P?KD7+; zrH@(&dw_Q5qkLv^zc#LDue*LncdNDF8@k1}_JMD#_9>^Yk>h8deYTidT};kM&6gah zQ=W6qIjOIFm$Ar>{6#r#CKI-eL>eUxnByWB3j;l2GMQ~ZZI0nO^Cy<@AZerHHU7cp;39b?0xs`NpHVfR48x;{JB}nrLhG{YGgG`Fn!N z5$~T5E9#=C!;#Q}CTF$k`rB_k=gfECX*I+)IJV+Dt@tfhOAz;N+1Q50HZ`{S4qE(?;rs8uPdU%B{mt+4m9FIz z!L0$tHpj5omPLzeY@=gq_MrqHD%)+f9X8#!0ibLXGY(q`2X4lx;Wzqd$(-g!ON(cU zU#J#Of6`;@7#sie(@&@GZP*CsoR9b9lTW7Dfz4Da6VLhvtQGxXPIFI*twk_Do607k zgPn5q`_4P>O!mrt@ITo%-yFcX;)*MhU1qz*GOd7*f@arx&o5^`e78fNXCHu$vo1b8 z{?dDFCI0{p{mq}TS8dpxnscLxUt}z603Zu@P*b`;j9@R^hPy`nIS3-;@vb zmK`vMIN7|`il_2!_@k+hl0EO>z-0r;O*`#CQxb~bd|&&T}bJ_;U*jIesDc z4HGVWO?}T8?a2XTqR?zl)z{?H)IY_37LIh!)r19Uo%=3iL-Luc|m_%?J*&aJ!3>of&9mSvV%rq~~J z@R5wMAo{l@`-8WeZ@zhpIazj@9$0IwwUVuZ%XTe{{b4(uDRHs=7FV*XE!7gNk!^$n zSD%HkznQ<8cgAFMv;SSDA>XrgaPqgdxo;k;y;pe+xtQ!v@O$}K&be0>=s!Lw zoV<4X&n_=0SgYJ4-NO&(+xyO*k0(FI?rL(yp7FQg@B{RrpEJK&p`rR9l@Rfde)WG1 z@=)@(d~7}`|I2#~4RS-?!GX&z;8ot6(y--QbJvVN*k193wcs&c;3>b_JsLE*SI@lj z9Iig-F-9vO{-_y$S(kH-hvZ&e13$=v7>qB*|CIk_`@Mq$*SUY}vB!e{4XFV+{>t&^ z{C{LAb;vU8J5X^38E*<1$DDA=y16Otyx&Fq8Fgijf$^bc{7ufua%=IFcW|2O<`NC% z_`8R>XI3iR8)J$y!J&+mK{dJ;7P#--}r^_nvu2u1jEyqJT>-;gb9O{l5P0J@XDu z35vd+*&n`?T=a}s7cduHm-V-Gw6cfkn`C;9eldgf45wsBi*8nADLrC z_FcZ54qgmtl)Wn6Z=r<<2TTg#_+V>jhRogRl0nbJec3n8W5;VDgLER^%V+VWjA8w3nrHNP z6TPXgGmwD1(!cy&^y{y=_e>3@h4YioU{sfLQ{IH0q_3P+&An%GD)V$|aef;YLObdE Ee>S(w6aWAK literal 0 HcmV?d00001 diff --git a/docs/.vuepress/public/manifest.json b/docs/.vuepress/public/manifest.json new file mode 100644 index 00000000..e645b070 --- /dev/null +++ b/docs/.vuepress/public/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "Markwon documentation", + "short_name": "Markwon", + "icons": [ + { + "src": "/android-chrome-192x192.png?v=1", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png?v=1", + "sizes": "512x512", + "type": "image/png" + } + ], + "display": "standalone" +} diff --git a/docs/.vuepress/style.styl b/docs/.vuepress/style.styl new file mode 100644 index 00000000..e69de29b diff --git a/docs/AsyncDrawableLoader.md b/docs/AsyncDrawableLoader.md deleted file mode 100644 index dc7c5db2..00000000 --- a/docs/AsyncDrawableLoader.md +++ /dev/null @@ -1,54 +0,0 @@ -# AsyncDrawable.Loader - -By default this library does not render any of the images. It's done to simplify rendering of text-based markdown. But if images must be supported, then the `AsyncDrawable.Loader` can be specified whilst building a `SpannableConfiguration` instance: - -```java -final AsyncDrawable.Loader loader = new AsyncDrawable.Loader() { - @Override - public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) { - // `download` method is here for demonstration purposes, it's not included in this interface - download(destination, new Callback() { - @Override - public void onDownloaded(Drawable d) { - // additionally we can call `drawable.isAttached()` - // to ensure if AsyncDrawable is in layout - drawable.setResult(d); - } - }); - } - - @Override - public void cancel(@NonNull String destination) { - // cancel download here - } -}; - -// `this` here referrs to a Context instance -final SpannableConfiguration configuration = SpannableConfiguration.builder(this) - .asyncDrawableLoader(loader) - .build(); -``` - -There is also standalone artifact that supports image loading *out-of-box* (including support for **SVG** & **GIF**), but provides little to none configuration and could be somewhat not optimal. Please refer to the [README][mil-readme] of the module. - - -### Contents - -* [SpannableConfiguration] -* * [SpannableTheme] -* * [AsyncDrawableLoader] -* * [SyntaxHighlight] -* * [LinkResolver] -* * [UrlProcessor] -* * [HtmlParser] - - -[SpannableConfiguration]: ./SpannableConfiguration.md -[SpannableTheme]: ./SpannableTheme.md -[AsyncDrawableLoader]: ./AsyncDrawableLoader.md -[SyntaxHighlight]: ./SyntaxHighlight.md -[LinkResolver]: ./LinkResolver.md -[UrlProcessor]: ./UrlProcessor.md -[HtmlParser]: ./HtmlParser.md - -[mil-readme]: ../library-image-loader/README.md diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 00000000..09a2c676 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,72 @@ +# Changelog + +# 2.0.0 +* Add `html-parser-api` and `html-parser-impl` modules +* Add `HtmlEmptyTagReplacement` +* Implement Appendable and CharSequence in SpannableBuilder +* Renamed library modules to reflect maven artifact names +* Rename `markwon-syntax` to `markwon-syntax-highlight` +* Add HtmlRenderer asbtraction +* Add CssInlineStyleParser +* Fix Theme#listItemColor and OL +* Fix task list block parser to revert parsing state when line is not matching +* Defined test format files +* image-loader add datauri parser +* image-loader add support for inline data uri image references +* Add travis configuration +* Fix image with width greater than canvas scaled +* Fix blockquote span +* Dealing with white spaces at the end of a document +* image-loader add SchemeHandler abstraction +* Add sample-latex-math module + +## v1.1.1 +* Fix OrderedListItemSpan text position (baseline) () +* Add softBreakAddsNewLine option for SpannableConfiguration () +* Paragraph text can now explicitly be spanned ()
Thanks to +* Fix table border color if odd background is specified () +* Add table customizations (even and header rows) + +## v1.1.0 +* Update commonmark to 0.11.0 and android-gif to 1.2.14 +* Add syntax highlight functionality (`library-syntax` module and `markwon-syntax` artifact) +* Add headingTypeface, headingTextSizes to SpannableTheme
Thanks to +* Introduce `MediaDecoder` abstraction to `image-loader` module +* Introduce `SpannableFactory`
Thanks for idea to +* Update sample application to use syntax-highlight +* Update sample application to use clickable placeholder for GIF media + +## v1.0.6 +* Fix bullet list item size (depend on text size and not top-bottom arguments) +* Add ability to specify MovementMethod when applying markdown to a TextView +* Markdown images size is also resolved via ImageSizeResolver +* Moved `ImageSize`, `ImageSizeResolver` and `ImageSizeResolverDef` +to `ru.noties.markwon.renderer` package (one level up, previously `ru.noties.markwon.renderer.html`) + +## v1.0.5 +* Change LinkSpan to extend URLSpan. Allow default linkColor (if not set explicitly) +* Fit an image without dimensions to canvas width (and keep ratio) +* Add support for separate color for code blocks ()
Thanks to + +## v1.0.4 +* Fixes (tables are not rendered when at the end of the markdown) +* Adds support for `indented code blocks`
Thanks to + +## v1.0.3 +* Fixed ordered lists (when number width is greater than block margin) + +## v1.0.2 +* Fixed additional white spaces at the end of parsed markdown +* Fixed headings with no underline (levels 1 & 2) +* Tables can have no borders + +## v1.0.1 +* Support for task-lists () +* Spans now are applied in reverse order ( ) +* Added `SpannableBuilder` to follow the reverse order of spans +* Updated `commonmark-java` to `0.10.0` +* Fixes + +## v1.0.0 + +Initial release \ No newline at end of file diff --git a/docs/HtmlParser.md b/docs/HtmlParser.md deleted file mode 100644 index 01f545fe..00000000 --- a/docs/HtmlParser.md +++ /dev/null @@ -1,60 +0,0 @@ -# HtmlParser - -As markdown supports HTML to be inlined, we need to introduce another entity that does (limited) parsing. Obtain an instance of `SpannableHtmlParser` via one of these factory methods: - -```java -SpannableHtmlParser.create(SpannableTheme, AsyncDrawable.Loader) -SpannableHtmlParser.create(SpannableTheme, AsyncDrawable.Loader, UrlProcessor, LinkSpan.Resolver) -``` - -Or, if further tweaking is requered builder methods: -```java -// creates empty builder -SpannableHtmlParser.builder(); - -// creates builder that is set-up to default values -SpannableHtmlParser.builderWithDefaults( - @NonNull SpannableTheme theme, - @Nullable AsyncDrawable.Loader asyncDrawableLoader, - @Nullable UrlProcessor urlProcessor, - @Nullable LinkSpan.Resolver resolver -) -``` - -Builder with defaults additionally handles these HTML tags: -* `b`, `strong` -* `i`, `em`, `cite`, `dfn` -* `sup` -* `sub` -* `u` -* `del`, `s`, `strike` -* `a` -* `img` (only if `AsyncDrawable.Loader` was provided) - -You can add own simple tags handling (or override default) via: -```java -SpannableHtmlParser.Builder.simpleTag(String, SpanProvider) -``` - -Please note, that not all tags are possible to handle via this. These are so called `void` tags ([link](https://www.w3.org/TR/html51/syntax.html#void-elements)) and so-called `html-blocks` ([link](http://spec.commonmark.org/0.18/#html-blocks)). An exception is made only for `img` tag -> it's possible to handle it via `imageProvider` property in `Builder` - - - -### Contents - -* [SpannableConfiguration] -* * [SpannableTheme] -* * [AsyncDrawableLoader] -* * [SyntaxHighlight] -* * [LinkResolver] -* * [UrlProcessor] -* * [HtmlParser] - - -[SpannableConfiguration]: ./SpannableConfiguration.md -[SpannableTheme]: ./SpannableTheme.md -[AsyncDrawableLoader]: ./AsyncDrawableLoader.md -[SyntaxHighlight]: ./SyntaxHighlight.md -[LinkResolver]: ./LinkResolver.md -[UrlProcessor]: ./UrlProcessor.md -[HtmlParser]: ./HtmlParser.md \ No newline at end of file diff --git a/docs/LinkResolver.md b/docs/LinkResolver.md deleted file mode 100644 index 67c14f85..00000000 --- a/docs/LinkResolver.md +++ /dev/null @@ -1,28 +0,0 @@ -# LinkResolver - -Link resolver is used to navigate to clicked link. By default `LinkResolverDef` is used and it just constructs an `Intent` and launches activity that can handle it, or silently fails if activity cannot be resolved. The main interface: -```java -public interface Resolver { - void resolve(View view, @NonNull String link); -} -``` - - -### Contents - -* [SpannableConfiguration] -* * [SpannableTheme] -* * [AsyncDrawableLoader] -* * [SyntaxHighlight] -* * [LinkResolver] -* * [UrlProcessor] -* * [HtmlParser] - - -[SpannableConfiguration]: ./SpannableConfiguration.md -[SpannableTheme]: ./SpannableTheme.md -[AsyncDrawableLoader]: ./AsyncDrawableLoader.md -[SyntaxHighlight]: ./SyntaxHighlight.md -[LinkResolver]: ./LinkResolver.md -[UrlProcessor]: ./UrlProcessor.md -[HtmlParser]: ./HtmlParser.md \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..0a8e8ba5 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,70 @@ +--- +title: 'Overview' +--- + +Markwon Logo + +

+ + +**Markwon** is a markdown library for Android. It parses markdown following + with the help of amazing library +and renders result as _Android-native_ Spannables. **No HTML** is involved +as an intermediate step. **No WebView** is required. It's extremely fast, +feature-rich and extensible. + +It gives ability to display markdown in all TextView widgets (**TextView**, +**Button**, **Switch**, **CheckBox**, etc), **Toasts** and all other places that accept +**Spanned content**. Library provides reasonable defaults to display style of a markdown content +but also gives all the means to tweak the appearance if desired. All markdown features +listed in are supported (including support for **inlined/block HTML code**, +**markdown tables**, **images** and **syntax highlight**). + +## Supported markdown features: + +* Emphasis (`*`, `_`) +* Strong emphasis (`**`, `__`) +* Strike-through (`~~`) +* Headers (`#{1,6}`) +* Links (`[]()` && `[][]`) +* [Images](/docs/image-loader.md) +* Thematic break (`---`, `***`, `___`) +* Quotes & nested quotes (`>{1,}`) +* Ordered & non-ordered lists & nested ones +* Inline code +* Code blocks +* Tables (*with limitations*) +* [Syntax highlight](/docs/syntax-highlight.md) +* [HTML](/docs/html.md) + * Emphasis (``, ``, ``, ``) + * Strong emphasis (``, ``) + * SuperScript (``) + * SubScript (``) + * Underline (``, `ins`) + * Strike-through (``, ``, ``) + * Link (`a`) + * Lists (`ul`, `ol`) + * Images (`img` will require configured image loader) + * Blockquote (`blockquote`) + * Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`) + * there is support to render any HTML tag, but it will require to create a special `TagHandler`, + more information can be found in [HTML section](/docs/html.md#custom-tag-handler) +* Task lists: +- [ ] Not _done_ + - [X] **Done** with `X` + - [x] ~~and~~ **or** small `x` + +## Screenshots + +screenshot light #1 +screenshot light #2 +screenshot light #3 +screenshot dark #2 + +By default configuration uses TextView textColor for styling, so changing textColor changes style + +:::tip Sample application +Screenshots are taken from sample application. It is a generic markdown viewer +with support to display markdown content via `http`, `https` & `file` schemes +and 2 themes included: Light & Dark. It can be downloaded from [releases](https://github.com/noties/Markwon/releases) +::: diff --git a/docs/SpannableConfiguration.md b/docs/SpannableConfiguration.md deleted file mode 100644 index d8b3bfee..00000000 --- a/docs/SpannableConfiguration.md +++ /dev/null @@ -1,43 +0,0 @@ -# SpannableConfiguration - -In order to render correctly markdown, this library needs a `SpannableConfiguration` instance. It has 2 factory methods: - -```java -// creates default instance -SpannableConfiguration.create(Context); - -// returns configurable Builder -SpannableConfiguration.builder(Context); -``` - -`SpannableConfiguration.Builder` class has these configurable properties (which are described in more detail further): -```java -public Builder theme(SpannableTheme theme); -public Builder asyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader); -public Builder syntaxHighlight(SyntaxHighlight syntaxHighlight); -public Builder linkResolver(LinkSpan.Resolver linkResolver); -public Builder urlProcessor(UrlProcessor urlProcessor); -public Builder htmlParser(SpannableHtmlParser htmlParser); - -// and obviously: -public SpannableConfiguration build(); -``` - -### Contents - -* [SpannableConfiguration] -* * [SpannableTheme] -* * [AsyncDrawableLoader] -* * [SyntaxHighlight] -* * [LinkResolver] -* * [UrlProcessor] -* * [HtmlParser] - - -[SpannableConfiguration]: ./SpannableConfiguration.md -[SpannableTheme]: ./SpannableTheme.md -[AsyncDrawableLoader]: ./AsyncDrawableLoader.md -[SyntaxHighlight]: ./SyntaxHighlight.md -[LinkResolver]: ./LinkResolver.md -[UrlProcessor]: ./UrlProcessor.md -[HtmlParser]: ./HtmlParser.md \ No newline at end of file diff --git a/docs/SpannableTheme.md b/docs/SpannableTheme.md deleted file mode 100644 index b4fd7dee..00000000 --- a/docs/SpannableTheme.md +++ /dev/null @@ -1,136 +0,0 @@ -# SpannableTheme - -`SpannableTheme` controls the appearance of rendered markdown. It has pretty reasonable defaults, which are established based on style of a TextView to which it is applied. It has some factory methods: -```java -// creates ready-to-use SpannableThemeObject -SpannableTheme.create(Context); - -// can be used to tweak default appearance -SpannableTheme.builderWithDefaults(Context); - -// returns empty builder (no default values are set) -SpannableTheme.builder(); - -// returns a builder that is instantiated with all values from specified SpannableTheme -SpannableTheme.builder(SpannableTheme copyFrom); -``` - -`SpannableTheme.Builder` have these configurations: -#### Link -```java -public Builder linkColor(@ColorInt int linkColor); -``` - -#### Block -```java -// left margin for: lists & quotes (text is shifted) -public Builder blockMargin(@Dimension int blockMargin); -``` - -#### Quote -```java -// width of quote indication (the `|`) -public Builder blockQuoteWidth(@Dimension int blockQuoteWidth); - -// color of `|` quote indication -public Builder blockQuoteColor(@ColorInt int blockQuoteColor); -``` - -#### Lists -```java -// color of list item bullets(●, ○, ■)/numbers -public Builder listItemColor(@ColorInt int listItemColor); - -// stroke width for list bullet (2nd level - `○`) -public Builder bulletListItemStrokeWidth(@Dimension int bulletListItemStrokeWidth); - -// width of list bullet (●, ○, ■) -public Builder bulletWidth(@Dimension int bulletWidth); -``` - -#### Code -```java -// text color for `code` blocks -public Builder codeTextColor(@ColorInt int codeTextColor); - -// background color for `code` blocks -public Builder codeBackgroundColor(@ColorInt int codeBackgroundColor); - -// left margin for multiline `code` blocks -public Builder codeMultilineMargin(@Dimension int codeMultilineMargin); - -// typeface of `code` block -public Builder codeTypeface(@NonNull Typeface codeTypeface); - -// text size for `code` block -public Builder codeTextSize(@Dimension int codeTextSize); -``` - -#### Headings -```java -// height of the `break` line under h1 & h2 -public Builder headingBreakHeight(@Dimension int headingBreakHeight); - -// color of the `break` line under h1 & h2 -public Builder headingBreakColor(@ColorInt int headingBreakColor); -``` - -#### SuperScript & SupScript -```java -// ratio for & text size (calculated based on TextView text size) -public Builder scriptTextSizeRatio(@FloatRange(from = .0F, to = Float.MAX_VALUE) float scriptTextSizeRatio); -``` - -#### Thematic break -```java -// the `---` thematic break color -public Builder thematicBreakColor(@ColorInt int thematicBreakColor); - -// the `---` thematic break height -public Builder thematicBreakHeight(@Dimension int thematicBreakHeight); -``` - -#### Tables -```java -// padding inside a table cell -public Builder tableCellPadding(@Dimension int tableCellPadding); - -// color of table borders -public Builder tableBorderColor(@ColorInt int tableBorderColor); - -// the `stroke` width of table border -public Builder tableBorderWidth(@Dimension int tableBorderWidth); - -// the background of odd table rows -public Builder tableOddRowBackgroundColor(@ColorInt int tableOddRowBackgroundColor); -``` - -#### Task lists - -Task lists are supported but with some limitations. First of all, task list cannot be nested -(in a list, quote, etc). By default (if used factory method `builderWithDefaults`) TaskListDrawable -will be used with `linkColor` as the primary color and `windowBackground` as the checkMarkColor. - -```java -public Builder taskListDrawable(@NonNull Drawable taskListDrawable); -``` - - -### Contents - -* [SpannableConfiguration] -* * [SpannableTheme] -* * [AsyncDrawableLoader] -* * [SyntaxHighlight] -* * [LinkResolver] -* * [UrlProcessor] -* * [HtmlParser] - - -[SpannableConfiguration]: ./SpannableConfiguration.md -[SpannableTheme]: ./SpannableTheme.md -[AsyncDrawableLoader]: ./AsyncDrawableLoader.md -[SyntaxHighlight]: ./SyntaxHighlight.md -[LinkResolver]: ./LinkResolver.md -[UrlProcessor]: ./UrlProcessor.md -[HtmlParser]: ./HtmlParser.md \ No newline at end of file diff --git a/docs/SyntaxHighlight.md b/docs/SyntaxHighlight.md deleted file mode 100644 index 86f85731..00000000 --- a/docs/SyntaxHighlight.md +++ /dev/null @@ -1,37 +0,0 @@ -# Syntax highlight - -This library does not provide ready-to-be-used implementation of syntax highlight, but it can be added via `SyntaxHighlight` interface whilst building `SpannableConfiguration`: - -```java -final SyntaxHighlight syntaxHighlight = new SyntaxHighlight() { - @NonNull - @Override - public CharSequence highlight(@Nullable String info, @NonNull String code) { - // create Spanned of highlight here - return null; // must not return `null` here - } -}; - -final SpannableConfiguration configuration = SpannableConfiguration.builder(this) - .syntaxHighlight(syntaxHighlight) - .build(); -``` - -### Contents - -* [SpannableConfiguration] -* * [SpannableTheme] -* * [AsyncDrawableLoader] -* * [SyntaxHighlight] -* * [LinkResolver] -* * [UrlProcessor] -* * [HtmlParser] - - -[SpannableConfiguration]: ./SpannableConfiguration.md -[SpannableTheme]: ./SpannableTheme.md -[AsyncDrawableLoader]: ./AsyncDrawableLoader.md -[SyntaxHighlight]: ./SyntaxHighlight.md -[LinkResolver]: ./LinkResolver.md -[UrlProcessor]: ./UrlProcessor.md -[HtmlParser]: ./HtmlParser.md \ No newline at end of file diff --git a/docs/UrlProcessor.md b/docs/UrlProcessor.md deleted file mode 100644 index 6a8f7dc4..00000000 --- a/docs/UrlProcessor.md +++ /dev/null @@ -1,40 +0,0 @@ -# UrlProcessor - -If you wish to process urls (links & images) that markdown contains, the `UrlProcessor` can be used: -```java -final UrlProcessor urlProcessor = new UrlProcessor() { - @NonNull - @Override - public String process(@NonNull String destination) { - // modify the `destination` or return as-is - return null; - } -}; - -final SpannableConfiguration configuration = SpannableConfiguration.builder(this) - .urlProcessor(urlProcessor) - .build(); -``` -The primary goal of additing this abstraction is to give ability to convert relative urls to absolute ones. If it fits your purpose, then `UrlProcessorRelativeToAbsolute` can be used: -```java -final UrlProcessor urlProcessor = new UrlProcessorRelativeToAbsolute("https://this-is-base.org"); -``` - -### Contents - -* [SpannableConfiguration] -* * [SpannableTheme] -* * [AsyncDrawableLoader] -* * [SyntaxHighlight] -* * [LinkResolver] -* * [UrlProcessor] -* * [HtmlParser] - - -[SpannableConfiguration]: ./SpannableConfiguration.md -[SpannableTheme]: ./SpannableTheme.md -[AsyncDrawableLoader]: ./AsyncDrawableLoader.md -[SyntaxHighlight]: ./SyntaxHighlight.md -[LinkResolver]: ./LinkResolver.md -[UrlProcessor]: ./UrlProcessor.md -[HtmlParser]: ./HtmlParser.md \ No newline at end of file diff --git a/docs/deploy.sh b/docs/deploy.sh new file mode 100755 index 00000000..1741b78f --- /dev/null +++ b/docs/deploy.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh + +# abort on errors +set -e + +# build +npm run docs:build + +# navigate into the build output directory +cd .vuepress/dist + +git init +git add -A +git commit -m 'deploy' + +# if you are deploying to https://.github.io/ +git push -f git@github.com:noties/Markwon.git master:gh-pages + +cd - \ No newline at end of file diff --git a/docs/docs/configure.md b/docs/docs/configure.md new file mode 100644 index 00000000..1d09e5fc --- /dev/null +++ b/docs/docs/configure.md @@ -0,0 +1,226 @@ +# 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/factory.md b/docs/docs/factory.md new file mode 100644 index 00000000..edf4a018 --- /dev/null +++ b/docs/docs/factory.md @@ -0,0 +1,61 @@ +# 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/getting-started.md b/docs/docs/getting-started.md new file mode 100644 index 00000000..1e42c485 --- /dev/null +++ b/docs/docs/getting-started.md @@ -0,0 +1,97 @@ +# Getting started + +:::tip Installation +Please follow [installation](/docs/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 +Markwon.setMarkdown(textView, "**Hello there!**"); +``` + +The most simple way to obtain markdown to be applied _somewhere_ else: + +```java +// parsed and styled markdown +final CharSequence markdown = Markwon.markdown(context, "**Hello there!**"); + +// use it +Toast.makeText(context, markdown, Toast.LENGTH_LONG).show(); +``` + +## Longer one + +When you need to customize markdown parsing/rendering you can use [SpannableConfiguration](/docs/configure.md): + +```java +final SpannableConfiguration configuration = SpannableConfiguration.builder(context) + .asyncDrawableLoader(AsyncDrawableLoader.create()) + .build(); + +Markwon.setMarkdown(textView, configuration, "Are **you** still there?"); + +final CharSequence markdown = Markwon.markdown(configuration, "Are **you** still there?"); +Toast.makeText(context, markdown, Toast.LENGTH_LONG).show(); +``` + +## No magic one + +In order to understand how previous examples work, let's break them down: + +* construct a `Parser` (see: ) and parse markdown +* construct a `SpannableConfiguration` (if it's not provided) +* *render* parsed markdown to Spannable (via `SpannableRenderer`) +* prepares TextView to display images, tables and links +* sets text + +This flow answers the most simple usage of displaying markdown: one shot parsing +& configuration of relatively small markdown chunks. If your markdown contains +a lot of text or you plan to display multiple UI widgets with markdown you might +consider *stepping in* and taking control of this flow. + +The candidate requirements to *step in*: +* parsing and processing of parsed markdown in a background thread +* reusing `Parser` and/or `SpannableConfiguration` between multiple calls +* ignore images or tables specific logic (you know that markdown won't contain them) + +So, if we expand `Markwon.setMarkdown(textView, markdown)` method we will see the following: + +```java +// create a Parser instance (can be done manually) +// internally creates default Parser instance & registers `strike-through` & `tables` extension +final Parser parser = Markwon.createParser(); + +// core class to display markdown, can be obtained via this method, +// which creates default instance (no images handling though), +// or via `builder` method, which lets you to configure this instance +final SpannableConfiguration configuration = SpannableConfiguration.create(context); + +final SpannableRenderer renderer = new SpannableRenderer(); + +final Node node = parser.parse(markdown); +final CharSequence text = renderer.render(configuration, node); + +// for links in markdown to be clickable +textView.setMovementMethod(LinkMovementMethod.getInstance()); + +// we need these due to the limited nature of Spannables to invalidate TextView +Markwon.unscheduleDrawables(textView); +Markwon.unscheduleTableRows(textView); + +textView.setText(text); + +Markwon.scheduleDrawables(textView); +Markwon.scheduleTableRows(textView); +``` + +:::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/html.md b/docs/docs/html.md new file mode 100644 index 00000000..6130db36 --- /dev/null +++ b/docs/docs/html.md @@ -0,0 +1,303 @@ +# HTML + +Starting with version `2.0.0` `Markwon` brings the whole HTML parsing/rendering +stack _on-site_. The main reason for this are _special_ definitions of HTML nodes +by . More specifically: +and . +These two are _a bit_ different from _native_ HTML understanding. +Well, they are _completely_ different and share only the same names as + and +elements. This leads to situations when for example an `` tag is considered +a block when it's used like this: + +```markdown + +Hello from italics tag + +``` + +:::tip A bit of background +
+ had brought attention to differences between HTML & commonmark implementations.

+::: + +Let's modify code snippet above _a bit_: + +```markdown{3} + +Hello from italics tag + + +``` + +We have just added a `new-line` before closing `
` tag. And this +changes everything as now, according to the , +we have 2 HtmlBlocks: one before `new-line` (containing open `` tag and text content) +and one after (containing as little as closing `` tag). + +If we modify code snippet _a bit_ again: + +```markdown{4} + +Hello from italics tag + +bold> +``` + +We will have 1 HtmlBlock (from previous snippet) and a bunch of HtmlInlines: +* HtmlInline (``) +* HtmlInline (``) +* Text (`bold`) +* HtmlInline (``) + +Those _little_ differences render `Html.fromHtml` (which was used in `1.x.x` versions) +useless. And actually it renders most of the HTML parsers implementations useless, +as most of them do not allow processing of HTML fragments in a raw fashion +without _fixing_ content on-the-fly. + +Both `TagSoup` and `Jsoup` HTML parsers (that were considered for this project) are built to deal with +_malicious_ HTML code (*all HTML code*? :no_mouth:). So, when supplied +with a `italic` fragment they will make it `italic`. +And it's a good thing, but consider these fragments for the sake of markdown: + +* `italic ` +* `bold italic` +* `` + +We will get: + +* `italic ` +* `bold italic` + +_* Or to be precise: `italic ` & +`bold italic`_ + +Which will be rendered in a final document: + + +|expected|actual| +|---|---| +|italic bold italic|italic bold italic| + +This might seem like a minor problem, but add more tags to a document, +introduce some deeply nested structures, spice openning and closing tags up +by adding markdown markup between them and finally write _malicious_ HTML code :laughing:! + +There is no such problem on the _frontend_ for which commonmark specification is mostly +aimed as _frontend_ runs in a web-browser environment. After all _parsed_ markdown +will become HTML tags (most common usage). And web-browser will know how to render final result. + +We, on the other hand, do not posess HTML heritage (*thank :robot:!*), but still +want to display some HTML to style resulting markdown a bit. That's why `Markwon` +incorporated own HTML parsing logic. It is based on the project. +And makes usage of the `Tokekiser` class that allows to _tokenise_ input HTML. +All other code that doesn't follow this purpose was removed. It's safe to use +in projects that already have `jsoup` dependency as `Markwon` repackaged **jsoup** source classes +(which could be found ) + +## Parser + +There are no additional steps to configure HTML parsing. It's enabled by default. +If you wish to _exclude_ it, please follow the [exclude](#exclude-html-parsing) section below. + +The key class here is: `MarkwonHtmlParser` that is defined in `markwon-html-parser-api` module. +`markwon-html-parser-api` is a simple module that defines HTML parsing contract and +does not provide implementation. + +To change what implementation `Markwon` should use, `SpannableConfiguration` can be used: + +```java{2} +SpannableConfiguration.builder(context) + .htmlParser(MarkwonHtmlParser) + .build(); +``` + +`markwon-html-parser-impl` on the other hand provides `MarkwonHtmlParser` implementation. +It's called `MarkwonHtmlParserImpl`. It can be created like this: + +```java +final MarkwonHtmlParser htmlParser = MarkwonHtmlParserImpl.create(); +// or +final MarkwonHtmlParser htmlParser = MarkwonHtmlParserImpl.create(HtmlEmptyTagReplacement); +``` + +### Empty tag replacement + +In order to append text content for self-closing, void or just _empty_ HTML tags, +`HtmlEmptyTagReplacement` can be used. As we cannot set Span for empty content, +we must represent empty tag with text during parsing stage (if we want it to be represented). + +Consider this: +* `` +* `
` +* `` + +By default (`HtmlEmptyTagReplacement.create()`) will handle `img` and `br` tags. +`img` will be replaced with `alt` property if it is present and `\uFFFC` if it is not. +And `br` will insert a new line. + +### Non-closed tags + +It's possible that your HTML can contain non-closed tags. By default `Markwon` will ignore them, +but if you wish to get a bit closer to a web-browser experience, you can allow this behaviour: + +```java{2} +SpannableConfiguration.builder(context) + .htmlAllowNonClosedTags(true) + .build(); +``` + +:::warning Note +If there is (for example) an `` tag at the start of a document and it's not closed +and `Markwon` is configured to **not** ignore non-closed tags (`.htmlAllowNonClosedTags(true)`), +it will make the whole document in italics +::: + +### Implementation note + +`MarkwonHtmlParserImpl` does not create a unified HTML node. Instead it creates +2 collections: inline tags and block tags. Inline tags are represented as a `List` +of inline tags (). And +block tags are structured in a tree. This helps to achieve _browser_-like behaviour, +when open inline tag is applied to all content (even if inside blocks) until closing tag. +All tags that are not _inline_ are considered to be _block_ ones. + +## Renderer + +Unlike `MarkwonHtmlParser` `Markwon` comes with a `MarkwonHtmlRenderer` by default. + +Default implementation can be obtain like this: + +```java +MarkwonHtmlRenderer.create(); +``` + +Default instance have these tags _handled_: +* emphasis + * `i` + * `em` + * `cite` + * `dfn` +* strong emphasis + * `b` + * `strong` +* `sup` (super script) +* `sub` (sub script) +* underline + * `u` + * `ins` +* strike through + * `del` + * `s` + * `strike` +* `a` (link) +* `ul` (unordered list) +* `ol` (ordered list) +* `img` (image) +* `blockquote` (block quote) +* `h{1-6}` (heading) + +If you wish to _extend_ default handling (or override existing), +`#builderWithDefaults` factory method can be used: + +```java +MarkwonHtmlRenderer.builderWithDefaults(); +``` + +For a completely _clean_ configurable instance `#builder` method can be used: + +```java +MarkwonHtmlRenderer.builder(); +``` + +### Custom tag handler + +To configure `MarkwonHtmlRenderer` to handle tags differently or +create a new tag handler - `TagHandler` can be used + +```java +public abstract class TagHandler { + + public abstract void handle( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull HtmlTag tag + ); +} +``` + +For the most simple _inline_ tag handler a `SimpleTagHandler` can be used: + +```java +public abstract class SimpleTagHandler extends TagHandler { + + @Nullable + public abstract Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag); +} +``` + +For example, `EmphasisHandler`: + +```java +public class EmphasisHandler extends SimpleTagHandler { + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + return configuration.factory().emphasis(); + } +} +``` + +If you wish to handle a _block_ HTML node (for example `
  • First
  • Second
`) refer +to `ListHandler` source code for reference. + +:::warning +The most important thing when implementing custom `TagHandler` is to know +what type of `HtmlTag` we are dealing with. There are 2: inline & block. +Inline tag cannot contain children. Block _can_ contain children. And they +_most likely_ should also be visited and _handled_ by registered `TagHandler` (if any) +accordingly. See `TagHandler#visitChildren(configuration, builder, child);` +::: + +#### Css inline style parser + +When implementing own `TagHandler` you might want to inspect inline CSS styles +of a HTML element. `Markwon` provides an utility parser for that purpose: + +```java +final CssInlineStyleParser inlineStyleParser = CssInlineStyleParser.create(); +for (CssProperty property: inlineStyleParser.parse("width: 100%; height: 100%;")) { + // [0] = CssProperty({width=100%}), + // [1] = CssProperty({height=100%}) +} +``` + +## Exclude HTML parsing + +If you wish to exclude HTML parsing altogether, you can manually +exclude `markwon-html-parser-impl` artifact from your projects compile classpath. +This can be beneficial if you know that markdown input won't contain +HTML and/or you wish to ignore it. Excluding HTML parsing +can speed up `Markwon` parsing and will decrease final size of +`Markwon` dependency by around `100kb`. + + + +```groovy +dependencies { + implementation("ru.noties:markwon:${markwonVersion}") { + exclude module: 'markwon-html-parser-impl' + } +} +``` + +Excluding `markwon-html-parser-impl` this way will result in +`MarkwonHtmlParser#noOp` implementation. No further steps are +required. + +:::warning Note +Excluding `markwon-html-parser-impl` won't remove *all* the content between +HTML tags. It will if `commonmark` decides that a specific fragment is a +`HtmlBlock`, but it won't if fragment is considered a `HtmlInline` as `HtmlInline` +does not contain content (just a tag definition). +::: \ No newline at end of file diff --git a/docs/docs/image-loader.md b/docs/docs/image-loader.md new file mode 100644 index 00000000..f9642215 --- /dev/null +++ b/docs/docs/image-loader.md @@ -0,0 +1,243 @@ +# Images + +By default `Markwon` doesn't handle images. Although `AsyncDrawable.Loader` is +defined in main artifact, it does not provide implementation. + +The interface is pretty simple: + +```java +public interface Loader { + + void load(@NonNull String destination, @NonNull AsyncDrawable drawable); + + void cancel(@NonNull String destination); +} +``` + +## AsyncDrawableLoader + + + +`AsyncDrawableLoader` from `markwon-image-loader` artifact can be used. + +:::tip Install +[Learn how to add](/docs/install.md#image-loader) `markwon-image-loader` to your project +::: + +Default instance of `AsyncDrawableLoader` can be obtain like this: + +```java +AsyncDrawableLoader.create(); +``` + +### Scheme support + +By default `AsyncDrawableLoader` handles these URL schemes: +* `file` (including reference to `android_assets`) +* `data` ([wiki](https://en.wikipedia.org/wiki/Data_URI_scheme)) + for inline image references +* all other schemes are considered to be network related and will be tried to obtain + from network + +#### Data + +`data` scheme handler supports both `base64` encoded content and `plain`: + +```html +Red dot +``` + +```html + +``` + +:::warning Note +Data uri works with native markdown images, but only in base64 mode: +```markdown +![svg](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGFyaWEtaGlkZGVuPSJ0cnVlIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgY2xhc3M9Imljb24gb3V0Ym91bmQiPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTE4LjgsODUuMWg1NmwwLDBjMi4yLDAsNC0xLjgsNC00di0zMmgtOHYyOGgtNDh2LTQ4aDI4di04aC0zMmwwLDBjLTIuMiwwLTQsMS44LTQsNHY1NkMxNC44LDgzLjMsMTYuNiw4NS4xLDE4LjgsODUuMXoiPjwvcGF0aD4gPHBvbHlnb24gZmlsbD0iY3VycmVudENvbG9yIiBwb2ludHM9IjQ1LjcsNDguNyA1MS4zLDU0LjMgNzcuMiwyOC41IDc3LjIsMzcuMiA4NS4yLDM3LjIgODUuMiwxNC45IDYyLjgsMTQuOSA2Mi44LDIyLjkgNzEuNSwyMi45Ij48L3BvbHlnb24+PC9zdmc+) +``` +::: + +## Configuration + +If you wish to configure `AsyncDrawableLoader` `#builder` factory method can be used: + +```java +AsyncDrawableLoader.builder() + .build(); +``` + +### OkHttp client + +```java +AsyncDrawableLoader.builder() + .client(OkHttpClient) + .build(); +``` + +If not provided explicitly, default `new OkHttpClient()` will be used + +:::warning +This configuration option is scheduled to be removed in `3.0.0` version, +use `NetworkSchemeHandler.create(OkHttpClient)` directly by calling +`build.addSchemeHandler()` +::: + +### Resources + +`android.content.res.Resources` to be used when obtaining an image +from Android assets folder **and** to create Bitmaps. + +```java +AsyncDrawableLoader.builder() + .resources(Resources) + .build(); +``` + +If not provided explicitly, default `Resources.getSystem()` will be used. + +:::warning +`Resources.getSystem()` can have unexpected side-effects (plus loading from +assets won't work). As a rule of thumb +always provide `AsyncDrawableLoader` with your Application's `Resources`. +To quote Android documentation for `#getSystem` method: + +> Return a global shared Resources object that provides access to only + system resources (no application resources), and is not configured + for the current screen (can not use dimension units, does not + change based on orientation, etc). + +::: + +:::warning +This configuration option is scheduled to be removed in `3.0.0`. Construct +your `MediaDecoder`s and `SchemeHandler`s appropriately and add them via +`build.addMediaDecoder()` and `builder.addSchemeHandler` +::: + +### Executor service + +`ExecutorService` to be used to download images in background thread + +```java +AsyncDrawableLoader.builder() + .executorService(ExecutorService) + .build(); +``` + +If not provided explicitly, default `Executors.newCachedThreadPool()` will be used + +### Error drawable + +`errorDrawable` to be used when image loader encountered an error loading image + +```java +AsyncDrawableLoader.builder() + .errorDrawable(Drawable) + .build(); +``` + +if not provided explicitly, default `null` value will be used. + +### Media decoder + +`MediaDecoder` is a simple asbtraction that encapsulates handling +of a specific image type. + +```java +AsyncDrawableLoader.builder() + .addMediaDecoder(MediaDecoder) + .addMediaDecoders(MediaDecoder...) + .addMediaDecoders(Iterable) + .build(); +``` + +If not provided explicitly, default `MediaDecoder`s will be used (SVG, GIF, plain) with +provided `Resources` and `gif-autoplay=true` + +`markwon-image-loader` comes with 3 `MediaDecoder` implementations: +* `SvgMediaDecoder` (based on [androidsvg](https://github.com/BigBadaboom/androidsvg)) +* `GifMediaDecoder` (based on [android-gif-drawable](https://github.com/koral--/android-gif-drawable)) +* `ImageMediaDecoder` (handling all _plain_ images) + +:::tip +Always add a _generic_ `MediaDecoder` instance at the end of the list. +Order does matter. For example: +```java{5} +AsyncDrawableLoader.builder() + .mediaDecoders( + SvgMediaDecoder.create(Resources), + GifMediaDecoder.create(boolean), + ImageMediaDecoder.create(Resources) + ) +.build(); +``` +::: + +#### SvgMediaDecoder + +```java +SvgMediaDecoder.create(Resources) +``` + +#### GifMediaDecoder + +```java +GifMediaDecoder.create(boolean) +``` + +`boolean` argument stands for `autoPlayGif` + +#### ImageMediaDecoder + +```java +ImageMediaDecoder.create(Resources) +``` + +### Scheme handler + +Starting with `2.0.0` `image-loader` module introduced +`SchemeHandler` abstraction + +```java +AsyncDrawableLoader.builder() + .addSchemeHandler(SchemeHandler) + .build() +``` + +Currently there are 3 `SchemeHandler`s that are bundled with this module: +* `NetworkSchemeHandler` (`http` and `https`) +* `FileSchemeHandler` (`file`) +* `DataUriSchemeHandler` (`data`) + +#### NetworkSchemeHandler + +```java +NetworkSchemeHandler.create(OkHttpClient); +``` + +#### FileSchemeHandler + +Simple file handler +```java +FileSchemeHandler.create(); +``` + +File handler that additionally allows access to Android `assets` folder +```java +FileSchemeHandler.createWithAssets(AssetManager); +``` + +#### DataUriSchemeHandler + +```java +DataUriSchemeHandler.create(); +``` + +--- + +::: warning +Note that currently if no `SchemeHandler`s were provided via `builder.addSchemeHandler()` +call then all 3 default scheme handlers will be added. The same goes for `MediaDecoder`s +(`builder.addMediaDecoder`). This behavior is scheduled to be removed in `3.0.0` +::: \ No newline at end of file diff --git a/docs/docs/install.md b/docs/docs/install.md new file mode 100644 index 00000000..730a6511 --- /dev/null +++ b/docs/docs/install.md @@ -0,0 +1,109 @@ +--- +prev: false +next: /docs/getting-started.md +--- + +# Installation + + + +In order to start using `Markwon` add this to your dependencies block +in your projects `build.gradle`: + +```groovy +implementation "ru.noties:markwon:${markwonVersion}" +``` + +This is core artifact that is sufficient to start displaying markdown in your Android applications. + +`Markwon` comes with more artifacts that cover additional functionality, but they are +**not** required to be used, as most of them provide implementations for functionality +that is _interfaced_ in the core artifact + +```groovy +implementation "ru.noties:markwon-image-loader:${markwonVersion}" +implementation "ru.noties:markwon-syntax-highlight:${markwonVersion}" +implementation "ru.noties:markwon-view:${markwonVersion}" +``` + +These artifacts share the same _version_ as the core artifact + +### Image loader + +```groovy +implementation "ru.noties:markwon-image-loader:${markwonVersion}" +``` + +Provides implementation of `AsyncDrawable.Loader` and comes with support for: +* SVG +* GIF +* Other image formats + +Please refer to documentation for [image loader](/docs/image-loader.md) module + +### Syntax highlight + +```groovy +implementation "ru.noties:markwon-syntax-highlight:${markwonVersion}" +``` + +Provides implementation of `SyntaxHighlight` and allows various syntax highlighting +in your markdown based Android applications. Comes with 2 ready-to-be-used themes: `light` and `dark`. +Please refer to documentation for [syntax highlight](/docs/syntax-highlight.md) module + +### View + +```groovy +implementation "ru.noties:markwon-view:${markwonVersion}" +``` + +Provides 2 widgets to display markdown: `MarkwonView` and `MarkwonViewCompat` (subclasses +of `TextView` and `AppCompatTextView` respectively). +Please refer to documentation for [view](/docs/view.md) module + +## Proguard + +When using `markwon-image-loader` artifact and Proguard is enabled, add these rules +to your proguard configuration: + +```proguard +-dontwarn okhttp3.** +-dontwarn okio.** + +-keep class com.caverock.androidsvg.** { *; } +-dontwarn com.caverock.androidsvg.** +``` + +They come from dependencies that `markwon-image-loader` is using. + +:::tip Other artifacts +Other artifacts do not require special Proguard rules +::: + +## Snapshot + +![markwon-snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/ru.noties/markwon.svg?label=markwon) + +In order to use latest `SNAPSHOT` version add snapshot repository +to your root project's `build.gradle` file: + +```groovy +allprojects { + repositories { + jcenter() + google() + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } +} +``` + +and then in your module `build.gradle`: + +```groovy +implementation "ru.noties:markwon:${markwonSnapshotVersion}" +``` + +Please note that `markwon-image-loader`, `markwon-syntax-highlight` +and `markwon-view` are also present in `SNAPSHOT` repository and +share the same version as main `markwon` artifact. + diff --git a/docs/docs/syntax-highlight.md b/docs/docs/syntax-highlight.md new file mode 100644 index 00000000..64454903 --- /dev/null +++ b/docs/docs/syntax-highlight.md @@ -0,0 +1,69 @@ +# Syntax highlight + + + +This is a simple module to add **syntax highlight** functionality to your markdown rendered with `Markwon` library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance. + +theme-default + + +theme-darkula + +--- + +First, we need to obtain an instance of `Prism4jSyntaxHighlight` which implements Markwon's `SyntaxHighlight`: + +```java +final SyntaxHighlight highlight = + Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme); +``` + +we also can obtain an instance of `Prism4jSyntaxHighlight` that has a _fallback_ option (if a language is not defined in `Prism4j` instance, fallback language can be used): + +```java +final SyntaxHighlight highlight = + Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme, String); +``` + +Generally obtaining a `Prism4j` instance is pretty easy: + +```java +final Prism4j prism4j = new Prism4j(new GrammarLocatorDef()); +``` + +Where `GrammarLocatorDef` is a generated grammar locator (if you use `prism4j-bundler` annotation processor) + +`Prism4jTheme` is a specific type that is defined in this module (`prism4j` doesn't know anything about rendering). It has 2 implementations: + +* `Prism4jThemeDefault` +* `Prism4jThemeDarkula` + +Both of them can be obtained via factory method `create`: + +* `Prism4jThemeDefault.create()` +* `Prism4jThemeDarkula.create()` + +But of cause nothing is stopping you from defining your own theme: + +```java +public interface Prism4jTheme { + + @ColorInt + int background(); + + @ColorInt + int textColor(); + + void apply( + @NonNull String language, + @NonNull Prism4j.Syntax syntax, + @NonNull SpannableStringBuilder builder, + int start, + int end + ); +} +``` + +:::tip +You can extend `Prism4jThemeBase` which has some helper methods +::: \ No newline at end of file diff --git a/docs/docs/theme.md b/docs/docs/theme.md new file mode 100644 index 00000000..b01f7f31 --- /dev/null +++ b/docs/docs/theme.md @@ -0,0 +1,212 @@ +# Theme + +Here is the list of properties that can be configured via `SpannableTheme#builder` factory +method. If you wish to control what is out of this list, you can use [SpannableFactory](/docs/factory.md) +abstraction which lets you to gather full control of Spans that are used to display markdown. + +* factory methods + +## 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 + + + +### Code text size + +Text size of 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 + + + +## Script ratio + +Ratio to be applied for `sup` (super script) & `sub` (sub script) + + + +## Thematic break + +### Color + +Color of a thematic break + + + +### Height + +Height of a thematic break + + + +## Table + +### Cell padding + +Padding inside a table cell + + + +### Border color + +The color of table borders + + + +### Border width + +The width of table borders + + + +### Odd row background + +Background of an odd table row + + + +### Even row background + +Background of an even table row + + + +### Header row background + +Background of header table row + + + +## Task list drawable + +Drawable of task list item + + diff --git a/docs/docs/view.md b/docs/docs/view.md new file mode 100644 index 00000000..c43b4e9d --- /dev/null +++ b/docs/docs/view.md @@ -0,0 +1,41 @@ +# MarkwonView + + + +This is simple library containing 2 views that are able to display markdown: +* MarkwonView - extends `android.view.TextView` +* MarkwonViewCompat - extends `android.support.v7.widget.AppCompatTextView` + +Both of them implement common `IMarkwonView` interface: +```java +public interface IMarkwonView { + + interface ConfigurationProvider { + @NonNull + SpannableConfiguration provide(@NonNull Context context); + } + + void setConfigurationProvider(@NonNull ConfigurationProvider provider); + + void setMarkdown(@Nullable String markdown); + void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown); + + @Nullable + String getMarkdown(); +} +``` + +Both views support layout-preview in Android Studio (with some exceptions, for example, bold span is not rendered due to some limitations of layout preview). +These are XML attributes: +``` +app:mv_markdown="string" +app:mv_configurationProvider="string" +``` + +`mv_markdown` accepts a string and represents raw markdown + +`mv_configurationProvider` accepts a string and represents a full class name of a class of type `ConfigurationProvider`, +for example: `com.example.my.package.MyConfigurationProvider` (this class must have an empty constructor +in order to be instantiated via reflection). + +Please note that those views parse markdown in main thread, so their usage must be for relatively small markdown portions only diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 00000000..5cc23227 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,10685 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.47.tgz", + "integrity": "sha512-W7IeG4MoVf4oUvWfHUx9VG9if3E0xSUDf1urrnNYtC2ow1dz2ptvQ6YsJfyVXDuPTFXz66jkHhzMW7a5Eld7TA==", + "requires": { + "@babel/highlight": "7.0.0-beta.47" + } + }, + "@babel/core": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-beta.47.tgz", + "integrity": "sha512-7EIuAX0UVnCgZ0E9tz9rFK0gd+aovwMA9bul+dnkmBQYLrJdas2EHMUSmaK67i1cyZpvgVvXhHtXJxC7wo3rlQ==", + "requires": { + "@babel/code-frame": "7.0.0-beta.47", + "@babel/generator": "7.0.0-beta.47", + "@babel/helpers": "7.0.0-beta.47", + "@babel/template": "7.0.0-beta.47", + "@babel/traverse": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47", + "babylon": "7.0.0-beta.47", + "convert-source-map": "^1.1.0", + "debug": "^3.1.0", + "json5": "^0.5.0", + "lodash": "^4.17.5", + "micromatch": "^2.3.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.47.tgz", + "integrity": "sha512-fJP+9X+gqgTTZzTqrKJHwebPwt6S/e/4YuyRyKyWHAIirGgUwjRoZgbFci24wwGYMJW7nlkCSwWG7QvCVsG0eg==", + "requires": { + "@babel/types": "7.0.0-beta.47", + "jsesc": "^2.5.1", + "lodash": "^4.17.5", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.47.tgz", + "integrity": "sha512-Pjxb/PrxyKWc7jcAXlawvNAQMxxY+tSSNC5wxJstJjpO10mocmGzBOqNYjxdvVhMb3d0BEPQ8mR+D65fFpZ+TA==", + "requires": { + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.47.tgz", + "integrity": "sha512-nv8d6TcDBb1CJMQzwab/e0rqyqoP9d2AQBjr4GdSiVRpJX4aiLEiLBm2XprdEb/sVIRmmBnVxPXJaHDsS/K2fw==", + "requires": { + "@babel/helper-explode-assignable-expression": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-call-delegate": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.47.tgz", + "integrity": "sha512-Rx9TRmCCEP0pWau9gfR6ubcbbX3nVc4ImNY143ftC70jrKdSv5rS20yz2cmCilDzhexwGZQ3PFwOLKe3C/5aEg==", + "requires": { + "@babel/helper-hoist-variables": "7.0.0-beta.47", + "@babel/traverse": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-define-map": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.0.0-beta.47.tgz", + "integrity": "sha512-pLB9RY7GZKcc/frzgfDY/HwdqxWPe60qMAvNUef1V1bDZ8i4AUgxAANgltFzj61t100WGhqaS0xGkALD+9VA+g==", + "requires": { + "@babel/helper-function-name": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47", + "lodash": "^4.17.5" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.47.tgz", + "integrity": "sha512-1mwk27zmhSuMUcVWxw5ZKiPYfuWXviZNqgA4OvFBloPf9R+dKDhNgP2uUrkHh68ltVVc3Bup1nsbd/2KM5AxEw==", + "requires": { + "@babel/traverse": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-function-name": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.47.tgz", + "integrity": "sha512-0LSvt95XCYaOrDA5K68KkTyldKXizDwBnKACdYzQszp1GdbtzmSeGwFU5Ecw86fU6bkYXtDvkFTOQwk/WQSJPw==", + "requires": { + "@babel/helper-get-function-arity": "7.0.0-beta.47", + "@babel/template": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.47.tgz", + "integrity": "sha512-63j0i3YUW8CO//uQc3ACffJdIlYcIlysuHjMF0yzQhqKoQ/CUPv0hf3nBwdRGjiWrr3JcL6++NF4XmXdwSU+fA==", + "requires": { + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.47.tgz", + "integrity": "sha512-5BcKFhyzrsInlrfO/tGoe6khUuJzGfROD7oozF/5MWsKo/c3gVJfQ5y83lZ4XoTKJt/x4PQlLU0aHd/SJpYONA==", + "requires": { + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-beta.47.tgz", + "integrity": "sha512-gpipslnZw2hcVGADUtqQII9KF8FPpRZsVUXwKP/0EnWwtujRFSVL+u2Fh+VXODRAxFmTLo6eGcOr/Vfan0MqYw==", + "requires": { + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.47.tgz", + "integrity": "sha512-Rk43Ty+a6npu9znK22IqFlseStRGWxEHi2cjmLbbi63VGiseofdUtIJI65F9MTCuMTXAX7VbY/ghef1Jp5qpvw==", + "requires": { + "@babel/types": "7.0.0-beta.47", + "lodash": "^4.17.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.47.tgz", + "integrity": "sha512-CziMe30ZunAhe6j05oNOFOg7im1lcv3dYuMxrwBYVe9YdP4NHPU7a1wrDBUhaPmyqTIZDwGnFne7k1KP79SeGQ==", + "requires": { + "@babel/helper-module-imports": "7.0.0-beta.47", + "@babel/helper-simple-access": "7.0.0-beta.47", + "@babel/helper-split-export-declaration": "7.0.0-beta.47", + "@babel/template": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47", + "lodash": "^4.17.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.47.tgz", + "integrity": "sha512-NhnGhjwrhzGas4A/PoBDEtEPCGJHrzhaT6qGmo1hmkA2orG4UNi7KENC38DhJII0n2oUrKUuzTwgCvxKOTiHbw==", + "requires": { + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.47.tgz", + "integrity": "sha512-GR67o8boOKVJRKM5Nhk7oVEHpxYy8R00lwu0F82WxxBH+iiT26DqW1e/4w/mo7Bdn1A6l0pNaOlNk1PdM2Hgag==" + }, + "@babel/helper-regex": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-beta.47.tgz", + "integrity": "sha512-dafidvVkjJP5AIWkJspV+7RGj1jeNts0qYvlmVzqAGb6BmQzEldJr6ZPzrmlpW/AW1YJGdw7br2yiwvlCRqDvQ==", + "requires": { + "lodash": "^4.17.5" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.47.tgz", + "integrity": "sha512-Nmj3lUHQscD160asav2bZ3sMIjGwGY9r6Vrriy9TqH7bmaClKUKUs5Twv0htFWfOKNFLEeY/MaqiAXylr1GS2w==", + "requires": { + "@babel/helper-annotate-as-pure": "7.0.0-beta.47", + "@babel/helper-wrap-function": "7.0.0-beta.47", + "@babel/template": "7.0.0-beta.47", + "@babel/traverse": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-replace-supers": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.47.tgz", + "integrity": "sha512-yf2JAD1+xNTjavqazqknRgPfd6MbGfvfIcAkxWsPURynAwOMSs4zThED8ImT2d5a97rGPysRJcq1jNh2L0WYxg==", + "requires": { + "@babel/helper-member-expression-to-functions": "7.0.0-beta.47", + "@babel/helper-optimise-call-expression": "7.0.0-beta.47", + "@babel/traverse": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-simple-access": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.47.tgz", + "integrity": "sha512-sd2t3QDKjd+hHkJKaC2AX39l6oIil1N548oMZAtV5YHlVGoWWkAVGnPMxRg7ICEjIftCU3ZI6UeaogyEhF8t7Q==", + "requires": { + "@babel/template": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47", + "lodash": "^4.17.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.47.tgz", + "integrity": "sha512-jx8GmxryT6Qy4+24W6M6TnVL9T8bxqdyg5UKHjxBdw0Y2Sano1n0WphUS2seuOugn04W2ZQLqGc0ut8nGe/taA==", + "requires": { + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helper-wrap-function": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.47.tgz", + "integrity": "sha512-SAasvh80Mz5q9x15dqH6z8jpM0WTBmxQSNZATSwJwhmWdme6r2gxpufIMr8LwQIJHmXmgNLmvh0zdWSbE/PR4Q==", + "requires": { + "@babel/helper-function-name": "7.0.0-beta.47", + "@babel/template": "7.0.0-beta.47", + "@babel/traverse": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/helpers": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-beta.47.tgz", + "integrity": "sha512-uWk7gIua2COEWLwZGxfF5Wq1bgXOt1V6xzWxqeFznrA6F1TUPiAhkK5zORiZEa5RAILp6Mswsn3xFjDyCpp3rQ==", + "requires": { + "@babel/template": "7.0.0-beta.47", + "@babel/traverse": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.47.tgz", + "integrity": "sha512-d505K3Hth1eg0b2swfEF7oFMw3J9M8ceFg0s6dhCSxOOF+07WDvJ0HKT/YbK/Jk9wn8Wyr6HIRAUPKJ9Wfv8Rg==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.47.tgz", + "integrity": "sha512-TQMLYVIqQ0MqVS1Z7jsuv3HlEetLo/7EnDY9mGBZ4c4/WLD/mu+tFuLiK2/2QH5wgi5viRfJGs/+L5TaDzxWng==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-remap-async-to-generator": "7.0.0-beta.47", + "@babel/plugin-syntax-async-generators": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.0.0-beta.47.tgz", + "integrity": "sha512-6yuCiF+ZZHPLgAa+0a6/teNeAMsWqY6AVtZA4NhCWnwP4OH0JrRaY7rwvFCJSqNGurf8rF65W9IucM/l0+HOCg==", + "requires": { + "@babel/helper-function-name": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-replace-supers": "7.0.0-beta.47", + "@babel/plugin-syntax-class-properties": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.0.0-beta.47.tgz", + "integrity": "sha512-sI+cO1okrlOyV4I63HDXf/SFsCr492HLjzNsMsd7Lk9WrViA+eQIboIiI9wHicozdgD1WrpZGJTjz7Z3xwl2Qw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-syntax-decorators": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.0.0-beta.47.tgz", + "integrity": "sha512-tTYnPZzCrOm8NK+7lRi4LGxPaw6lErDsozNInM/FWOXGe7s2EpQnTa40S7/gLLNGvpNshYHdykJtKgfiar9qkA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-syntax-export-namespace-from": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-function-sent": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.0.0-beta.47.tgz", + "integrity": "sha512-253O7PlXtV1LjJkRSMCzG7L16JaQE62AWFVUbbd/8LZI7evyGE5pP90d4R7V+ir/Ra17tL+nk9GfUpEKG7KT4A==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-wrap-function": "7.0.0-beta.47", + "@babel/plugin-syntax-function-sent": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.0.0-beta.47.tgz", + "integrity": "sha512-WSS8EVGwHF1g0nQ7IFjiCtBOSyONLqVA3yOriJR1oJS+0HVjfvbee5XwfwsIvdtEIh6YKEnHik5yh54qu7C/PQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-syntax-numeric-separator": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.47.tgz", + "integrity": "sha512-ujUjQUyTxUWHfixRD7Y5Nm8VCgHSf6YgbM37LEnojKp5lPahZO42qJfDty+Kh0tEanpI5H8BLPkJbFSzx6TNEw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0-beta.47.tgz", + "integrity": "sha512-XMZavW6uior8KD4BeMavxF3PGrwVpIFlZk/GJQGkd3lOeOHil8nhHDuTWvsbsJptKFWMPkhGR18boNdxgmxyFQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-syntax-optional-catch-binding": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-throw-expressions": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.0.0-beta.47.tgz", + "integrity": "sha512-BGJ96kIVi9NlojyZkpJNkdkxABt+VQT6hLQfILpNfFBJUzdOnZbVecXJUmoTAzg56Wse29XbY9bpNmocdNmtFQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-syntax-throw-expressions": "7.0.0-beta.47" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0-beta.47.tgz", + "integrity": "sha512-TCQqSmPrgVB0Txr6WzCpxJyLuVTb9KoT0+oECKELtF717bvHOI4woR7o8D8DFkXzVQeb7Kqfu5w05gsvPq591g==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-regex": "7.0.0-beta.47", + "regexpu-core": "^4.1.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.47.tgz", + "integrity": "sha512-LCNGYTLpQAsvTmVmT/I2pz7dIh3Bu+9BLxqL7b3QfplsMQxNsoBBMend33Arb4EtPt5dX7KeeVof8tL0trDRRA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0-beta.47.tgz", + "integrity": "sha512-vLoAuLSjHSenX3TQmri6ttQWZp3rEtGcRp4LgYEBQ012fN5h+KmcssvkCAqm6V6ozS5KzUWpBlZ6t7YhZG6oBw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.0.0-beta.47.tgz", + "integrity": "sha512-Lv10BM3C+0Ki53lI7T9xZsSsgzQqfoQZq4pZj0F6tkK54E5BBSFfUxGavRE43CFXHbK0Hd9uMqmpGWPGtCvgAw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0-beta.47.tgz", + "integrity": "sha512-J2y7RAH2NwQ+ahJahj2eS1PqS2NWNWTDaEibqrE55VTJU7nPL8AhthRwIQfQkCH+8UIeL/T3Jh1iHIRkvJ6dXA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.0.0-beta.47.tgz", + "integrity": "sha512-mCNj425dtBdO95z1jMKoW0H3nZnTy9tjsdIuLw94uS+y97hvmFkFQtffqH+WIwEGxGBWq1Pn0OGfk3E8GfkhgQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-function-sent": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-function-sent/-/plugin-syntax-function-sent-7.0.0-beta.47.tgz", + "integrity": "sha512-S/jMyfNd+a/MbtUCvQ3wDC/Wt9Cftj/Q59bVROOX79DrWpKE9VL/kEeiIJzCNKetvDesUdDH+4xcXt3D0/5Rog==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.0.0-beta.47.tgz", + "integrity": "sha512-ISVXJZw9Q2Q0pwcXNv3nfA1p9Ia0clDMTtYxlqIGE5/3WaIQoHkkeaOYsckBKiIIL0hfU+GEjL1g5hRBYPlMUA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0-beta.47.tgz", + "integrity": "sha512-5scuJzIYZY8M+A1ra8mcKANIwB5TtsRD6Aw94xZxfvnjhhVMFR5RYE9HshVlBrZVY+r3cJDNIQLJMC/fGJHImA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.0.0-beta.47.tgz", + "integrity": "sha512-qAGv7jHtZWNWy4n23OzKLrVk+xfaEO4LYRK3zCMyudXRfB3FPaer6NJNjU5rebvJzC4wB2EVb2nPwVENNNh2jQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.47.tgz", + "integrity": "sha512-UOGQCmzvNlZMQOuys7xPiTa2EjTT3xHuhUghcdJnYikqGV43obpIIaP+VDCWPvIT8g0QDIvmRWx5UefvkWXN+w==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0-beta.47.tgz", + "integrity": "sha512-Dmq+REMR95Syg+VjV3bh63DD/tDxdraNQ4ErAOXFobfSCDbfov9YGkqSJ4K61LHTQwinQ0+dIUlgdFL2kbedIw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-syntax-throw-expressions": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.0.0-beta.47.tgz", + "integrity": "sha512-v9DcO3cmp0maysKG7MMjHqHeGTY0/UY4cOuPckIZK3HHmUtQliSLeAw6eaLXGRUv4ZgaqX/T3dbi8YH7YeK4YQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.47.tgz", + "integrity": "sha512-xiU+7RJAsqx+iZqWSQQWBu9ZDTruWimkg4puDSdRVfEwgZQdOtiU2LuO0+xGFyitJPHkKuje0WvK1tFu1dmxCw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.47.tgz", + "integrity": "sha512-/TXK3v6ipvmhMO81Y2Vjc7RYROkS2PcmRc+kvmU3CWA7r5I73KWg10UEW/fpWqCuoTCHHHXu1ZcZ5u+nduJeFw==", + "requires": { + "@babel/helper-module-imports": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-remap-async-to-generator": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.47.tgz", + "integrity": "sha512-8KPxKyE6kb9VRbHwQ8XKsg/IPlxHaOMRyg2WfyV5C4dCmXbRdHDJy4hZKB3o4rGxjggmC/Bx8Fh51/P7UNtTcA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.47.tgz", + "integrity": "sha512-V/u3Zdy40KjVQeyYUaQnCGiHQbRNJoc6IEtNDERltuW9vYPHS1n6YGc+EHKi8JVYT4kE6UHOjD+BrbCCV4kjRw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "lodash": "^4.17.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0-beta.47.tgz", + "integrity": "sha512-hzW/jL6TPBMHJXeXwzuxMN0PFAfjVD0UzATHrFSejY5A7SvhWWrv1cZ3K0/SzCXJ9LpMdxCNiREvVjeD/Tyx2g==", + "requires": { + "@babel/helper-annotate-as-pure": "7.0.0-beta.47", + "@babel/helper-define-map": "7.0.0-beta.47", + "@babel/helper-function-name": "7.0.0-beta.47", + "@babel/helper-optimise-call-expression": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-replace-supers": "7.0.0-beta.47", + "@babel/helper-split-export-declaration": "7.0.0-beta.47", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.47.tgz", + "integrity": "sha512-V78qyzmjj4aq/tjpkMFbV5gPtrx7xdclW1Rn6vV9hIwMSMbtstYEXF4msy614MofvYj6gYbPbNfyhXFIUvz/xw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.47.tgz", + "integrity": "sha512-3AaXC9H7qPybJbSs/QMhhj9EZF9MYrb/HRytwki1tckaYifqCJquENIZxDAYmwsWIGIHiq34WqwPRMIsz/b5uQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0-beta.47.tgz", + "integrity": "sha512-ofB5GwipMoaOH3Qyr5g5FpXWePhIAaD4zMDOoAHDYBPuLWxzAME8YQCa0S3HJf3eTu/HTN/c/G1gDwDB8Z/gKQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-regex": "7.0.0-beta.47", + "regexpu-core": "^4.1.3" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.47.tgz", + "integrity": "sha512-r3xNVYTLVasjqTowIr6s+27oc5n7A5TKbB0/4u9FHjF7ONTWaggO8UFbbj07DOJ4Ll2RkigrZA8/D+w2nJ+XlA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.47.tgz", + "integrity": "sha512-vyGG3kLIXpMuaPL485aqowdWFrxCxXtbzMXy9p1QTK5Q/+9UHpK9XoAVJZGknnsm091m0Ss7spo8uHaxbzYVog==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.47.tgz", + "integrity": "sha512-tfH5OMzV9fWLYJTzWDhoRJKr8kvBZWH26jiCgM0ayNq75ES/X947MqMNAgBjJdTAVEV2kOyks2ItgNAJT4rOUw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.47.tgz", + "integrity": "sha512-/5I/f8NCouugsRT6ORB1UjCP3N+Rgv/OB6SzmaeIUEpYYPM6D7WQ+4BaRYXQn4eqtOJmTgxDXYa8FgYtoeqP9A==", + "requires": { + "@babel/helper-function-name": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.47.tgz", + "integrity": "sha512-PxBw+52qWypwR76YfS2FlW4wZfp61SjIyt3OSPZeWnf0zVQWNVrlRRunJ7lBYudDYvyMwStAE/VynZ0fHtPgng==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.47.tgz", + "integrity": "sha512-zW84YqQ5Kt8+t5pYrnFhjWQP2w2wq6Nxz9pozxpnvXP+lhqyJPqNdWM1lcVApORpWL1BF7BlgP08yk+5MVRfGA==", + "requires": { + "@babel/helper-module-transforms": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.47.tgz", + "integrity": "sha512-MYoLyexybBJ9ODWWMsMFzxAQey68RzhQNPjfNAYPhPPB3X160EZ5qOjWxRS2rYNvuYAxs6guy5OdrDpESqFSrQ==", + "requires": { + "@babel/helper-module-transforms": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-simple-access": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0-beta.47.tgz", + "integrity": "sha512-bMQy3/jEZRpoUg7RdOouphBO8+7Sfjl7XrO84PtgBx4ck+ZPc4xOlBQyr2rkmsJNmmGLi42nnMI1cZZJT3LVnQ==", + "requires": { + "@babel/helper-hoist-variables": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.0.0-beta.47.tgz", + "integrity": "sha512-rG7KioAFCLxZ33wNBqUoxPhtXOmVEvnZNIy9wv0fSbNIQr8lO1avZ7SeBL3OZduNvLocqrESt9Xhh1nzb/zOvA==", + "requires": { + "@babel/helper-module-transforms": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0-beta.47.tgz", + "integrity": "sha512-2IRZtdoPXJn7KSwrmp0xtLTZ0PnhlQxhcTZ0XZ5wfFXmsZ9vi6AK4whIZ2IXI/c2qrYK9FEYLwR5QRfL5Qe6eQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.47.tgz", + "integrity": "sha512-JN6ox2rH1xe6hlsd6/7d2kPjZSA97wH4oOdNOSaNqaFGEFuaP/Je4+ojHMgyHKU2nx9QHNBCTxHEj+ko+Ij6HQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-replace-supers": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.47.tgz", + "integrity": "sha512-UzQG8draO+30Y8eNEREuGBfmEHLL7WFxOjmTBbaTrbdOrm/znCUThqcuNz8cyn2nrZbln7M/loQ3stjf9Pt9fQ==", + "requires": { + "@babel/helper-call-delegate": "7.0.0-beta.47", + "@babel/helper-get-function-arity": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.47.tgz", + "integrity": "sha512-JEPIiJyqYRfjOYUTZguLkb2HTwudReqLyOljpOXnJ/1ymwsiof4D6ul611DGlMxJMZJGQ6TBi59iY9GoJ6j4Iw==", + "requires": { + "regenerator-transform": "^0.12.3" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.0.0-beta.47.tgz", + "integrity": "sha512-RhnhjYsOxmKDGa1ePM5RQWFBYe1PnEZAhXTNEeIwtw1jluEoLL+PCTZDbt/aAcAkZvqwIWccjkNM/FwKTd5Sxw==", + "requires": { + "@babel/helper-module-imports": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.47.tgz", + "integrity": "sha512-+o7/yb0Nrk4Gg/tnBgfBf+G1uGZbtkSluUnj8RyD37ajpDlWmysDjFEHSfktKcuD8YHeGz2M9AYNGcClk1fr/g==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.47.tgz", + "integrity": "sha512-LFAozFdfT4bE2AQw2BnjzLufTX4GBsTUHUGRhT8XNoDYuGnV+7k9Yj6JU3/7csJc9u6W91PArYgoO+D56CMw6Q==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.47.tgz", + "integrity": "sha512-+Rc6NihGoXcwAqAxbiumvzOYxRR0aUg1ZExfyHnI5QnQf0sf4xAfgT/YpGvEgLd5Ci0rka+IWSj54PhzZkhuTg==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-regex": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.47.tgz", + "integrity": "sha512-ORfrfN/gQoRuI+xf+kOa2i/yvXfedFRgH+KtgoIrpUQom7OhexxzD280x80LMCIkdaVGzYhvlC3kdJkFMWAfUg==", + "requires": { + "@babel/helper-annotate-as-pure": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.47.tgz", + "integrity": "sha512-PhPy5NUY5MT++fEr7/sxN1/ERSM2siHSbGgdkbqgDlyvf1NlU1HeqyfomHJEFE1Y4PX0hj+XmtjSAali/6XqYA==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.47.tgz", + "integrity": "sha512-44nWn421tMVZ/A4+1uppzoAO7nrlwWzefMr9JUi5G+tXl0DLEtWy+F7L6zCVw19C4OAOA6WlolVro5CEs6g6AQ==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/helper-regex": "7.0.0-beta.47", + "regexpu-core": "^4.1.3" + } + }, + "@babel/preset-env": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.0.0-beta.47.tgz", + "integrity": "sha512-ZFUgKdQDqw2H5TCMaWq6iDDO9+16RZPdDfOuoPID8Agm8I2MBlrqTI1MKWwqgEHICdZIOXkVszAMuuDTBwdzHA==", + "requires": { + "@babel/helper-module-imports": "7.0.0-beta.47", + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-proposal-async-generator-functions": "7.0.0-beta.47", + "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.47", + "@babel/plugin-proposal-optional-catch-binding": "7.0.0-beta.47", + "@babel/plugin-proposal-unicode-property-regex": "7.0.0-beta.47", + "@babel/plugin-syntax-async-generators": "7.0.0-beta.47", + "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.47", + "@babel/plugin-syntax-optional-catch-binding": "7.0.0-beta.47", + "@babel/plugin-transform-arrow-functions": "7.0.0-beta.47", + "@babel/plugin-transform-async-to-generator": "7.0.0-beta.47", + "@babel/plugin-transform-block-scoped-functions": "7.0.0-beta.47", + "@babel/plugin-transform-block-scoping": "7.0.0-beta.47", + "@babel/plugin-transform-classes": "7.0.0-beta.47", + "@babel/plugin-transform-computed-properties": "7.0.0-beta.47", + "@babel/plugin-transform-destructuring": "7.0.0-beta.47", + "@babel/plugin-transform-dotall-regex": "7.0.0-beta.47", + "@babel/plugin-transform-duplicate-keys": "7.0.0-beta.47", + "@babel/plugin-transform-exponentiation-operator": "7.0.0-beta.47", + "@babel/plugin-transform-for-of": "7.0.0-beta.47", + "@babel/plugin-transform-function-name": "7.0.0-beta.47", + "@babel/plugin-transform-literals": "7.0.0-beta.47", + "@babel/plugin-transform-modules-amd": "7.0.0-beta.47", + "@babel/plugin-transform-modules-commonjs": "7.0.0-beta.47", + "@babel/plugin-transform-modules-systemjs": "7.0.0-beta.47", + "@babel/plugin-transform-modules-umd": "7.0.0-beta.47", + "@babel/plugin-transform-new-target": "7.0.0-beta.47", + "@babel/plugin-transform-object-super": "7.0.0-beta.47", + "@babel/plugin-transform-parameters": "7.0.0-beta.47", + "@babel/plugin-transform-regenerator": "7.0.0-beta.47", + "@babel/plugin-transform-shorthand-properties": "7.0.0-beta.47", + "@babel/plugin-transform-spread": "7.0.0-beta.47", + "@babel/plugin-transform-sticky-regex": "7.0.0-beta.47", + "@babel/plugin-transform-template-literals": "7.0.0-beta.47", + "@babel/plugin-transform-typeof-symbol": "7.0.0-beta.47", + "@babel/plugin-transform-unicode-regex": "7.0.0-beta.47", + "browserslist": "^3.0.0", + "invariant": "^2.2.2", + "semver": "^5.3.0" + } + }, + "@babel/preset-stage-2": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/preset-stage-2/-/preset-stage-2-7.0.0-beta.47.tgz", + "integrity": "sha512-IabxIY3AAAHRy3GCGJQ3KBfRMR59OZJSMLke24Kwpwlvk09WEP5ERReRbfmUGJeEPeZK3rAt0oZBdF83Ri+C5Q==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-proposal-decorators": "7.0.0-beta.47", + "@babel/plugin-proposal-export-namespace-from": "7.0.0-beta.47", + "@babel/plugin-proposal-function-sent": "7.0.0-beta.47", + "@babel/plugin-proposal-numeric-separator": "7.0.0-beta.47", + "@babel/plugin-proposal-throw-expressions": "7.0.0-beta.47", + "@babel/preset-stage-3": "7.0.0-beta.47" + } + }, + "@babel/preset-stage-3": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/preset-stage-3/-/preset-stage-3-7.0.0-beta.47.tgz", + "integrity": "sha512-JOOsIaC3sbcBPbRpELUij3xLE8ObIr7TjanKTExlMwc/Hvz0YG3/ioXmDbphvR9L2cb9a+QNIdjAyOebqDyaFw==", + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.47", + "@babel/plugin-proposal-async-generator-functions": "7.0.0-beta.47", + "@babel/plugin-proposal-class-properties": "7.0.0-beta.47", + "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.47", + "@babel/plugin-proposal-optional-catch-binding": "7.0.0-beta.47", + "@babel/plugin-proposal-unicode-property-regex": "7.0.0-beta.47", + "@babel/plugin-syntax-dynamic-import": "7.0.0-beta.47", + "@babel/plugin-syntax-import-meta": "7.0.0-beta.47" + } + }, + "@babel/runtime": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.47.tgz", + "integrity": "sha512-3IaakAC5B4bHJ0aCUKVw0pt+GruavdgWDFbf7TfKh7ZJ8yQuUp7af7MNwf3e+jH8776cjqYmMO1JNDDAE9WfrA==", + "requires": { + "core-js": "^2.5.3", + "regenerator-runtime": "^0.11.1" + } + }, + "@babel/template": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.47.tgz", + "integrity": "sha512-mAzrOCLwOb4jAobHi0kTwIkoamP1Do28c6zxvrDXjYSJFZHz6KGuzMaT0AV7ZCq7M3si7QypVVMVX2bE6IsuOg==", + "requires": { + "@babel/code-frame": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47", + "babylon": "7.0.0-beta.47", + "lodash": "^4.17.5" + } + }, + "@babel/traverse": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.47.tgz", + "integrity": "sha512-kYGGs//OnUnei+9TTldxlgf7llprj7VUeDKtG50+g+0k1g0yZyrkEgbyFheYFdnudR8IDEHOEXVsUuY82r5Aiw==", + "requires": { + "@babel/code-frame": "7.0.0-beta.47", + "@babel/generator": "7.0.0-beta.47", + "@babel/helper-function-name": "7.0.0-beta.47", + "@babel/helper-split-export-declaration": "7.0.0-beta.47", + "@babel/types": "7.0.0-beta.47", + "babylon": "7.0.0-beta.47", + "debug": "^3.1.0", + "globals": "^11.1.0", + "invariant": "^2.2.0", + "lodash": "^4.17.5" + } + }, + "@babel/types": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.47.tgz", + "integrity": "sha512-MOP5pOosg7JETrVGg8OQyzmUmbyoSopT5j2HlblHsto89mPz3cmxzn1IA4UNUmnWKgeticSwfhS+Gdy25IIlBQ==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.5", + "to-fast-properties": "^2.0.0" + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz", + "integrity": "sha512-KU/VDjC5RwtDUZiz3d+DHXJF2lp5hB9dn552TXIyptj8SH1vXmR40mG0JgGq03IlYsOgGfcv8xrLpSQ0YUMQdA==" + }, + "@shellscape/koa-send": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@shellscape/koa-send/-/koa-send-4.1.3.tgz", + "integrity": "sha512-akNxJetq2ak8aj7U6ys+EYXfWY4k8keleDZJbHWvpuVDj0/PUbbOuPkeBYaie7C6d5fRNLK+0M1Puu8ywTlj3w==", + "requires": { + "debug": "^2.6.3", + "http-errors": "^1.6.1", + "mz": "^2.6.0", + "resolve-path": "^1.3.3" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@shellscape/koa-static": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@shellscape/koa-static/-/koa-static-4.0.5.tgz", + "integrity": "sha512-0T2g2NtaO2zhbqR8EBACIGtBy+haodKb8PuJ17RGDXAJwhjkgghUKLrLEnm05zuiwupfYm2APIax6D2TwLoflA==", + "requires": { + "@shellscape/koa-send": "^4.1.0", + "debug": "^2.6.8" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@vue/babel-preset-app": { + "version": "3.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-3.0.0-beta.11.tgz", + "integrity": "sha1-yLiJqnNGQFD5zT+dxiGVHYXCRQg=", + "requires": { + "@babel/plugin-syntax-jsx": "7.0.0-beta.47", + "@babel/plugin-transform-runtime": "7.0.0-beta.47", + "@babel/preset-env": "7.0.0-beta.47", + "@babel/preset-stage-2": "7.0.0-beta.47", + "@babel/runtime": "7.0.0-beta.47", + "babel-helper-vue-jsx-merge-props": "^2.0.3", + "babel-plugin-dynamic-import-node": "^1.2.0", + "babel-plugin-transform-vue-jsx": "^4.0.1" + } + }, + "@vue/component-compiler-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-2.2.0.tgz", + "integrity": "sha512-pS4zlcdD7BvedyB+IfiTfrbi6C977UMIfulSk8r6uL0BU46ZE2+fUj/zbSNSfVxeaj9ElmnSni5OMwF9np+b+w==", + "requires": { + "consolidate": "^0.15.1", + "hash-sum": "^1.0.2", + "lru-cache": "^4.1.2", + "merge-source-map": "^1.1.0", + "postcss": "^6.0.20", + "postcss-selector-parser": "^3.1.1", + "prettier": "1.13.7", + "source-map": "^0.5.6", + "vue-template-es2015-compiler": "^1.6.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "@webassemblyjs/ast": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", + "integrity": "sha512-49nwvW/Hx9i+OYHg+mRhKZfAlqThr11Dqz8TsrvqGKMhdI2ijy3KBJOun2Z4770TPjrIJhR6KxChQIDaz8clDA==", + "requires": { + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "debug": "^3.1.0", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz", + "integrity": "sha512-vrvvB18Kh4uyghSKb0NTv+2WZx871WL2NzwMj61jcq2bXkyhRC+8Q0oD7JGVf0+5i/fKQYQSBCNMMsDMRVAMqA==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz", + "integrity": "sha512-dBh2CWYqjaDlvMmRP/kudxpdh30uXjIbpkLj9HQe+qtYlwvYjPRjdQXrq1cTAAOUSMTtzqbXIxEdEZmyKfcwsg==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz", + "integrity": "sha512-v7igWf1mHcpJNbn4m7e77XOAWXCDT76Xe7Is1VQFXc4K5jRcFrl9D0NrqM4XifQ0bXiuTSkTKMYqDxu5MhNljA==", + "requires": { + "debug": "^3.1.0" + } + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz", + "integrity": "sha512-yN6ScQQDFCiAXnVctdVO/J5NQRbwyTbQzsGzEgXsAnrxhjp0xihh+nNHQTMrq5UhOqTb5LykpJAvEv9AT0jnAQ==", + "requires": { + "@webassemblyjs/wast-printer": "1.5.13" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz", + "integrity": "sha512-hSIKzbXjVMRvy3Jzhgu+vDd/aswJ+UMEnLRCkZDdknZO3Z9e6rp1DAs0tdLItjCFqkz9+0BeOPK/mk3eYvVzZg==" + }, + "@webassemblyjs/helper-module-context": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz", + "integrity": "sha512-zxJXULGPLB7r+k+wIlvGlXpT4CYppRz8fLUM/xobGHc9Z3T6qlmJD9ySJ2jknuktuuiR9AjnNpKYDECyaiX+QQ==", + "requires": { + "debug": "^3.1.0", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz", + "integrity": "sha512-0n3SoNGLvbJIZPhtMFq0XmmnA/YmQBXaZKQZcW8maGKwLpVcgjNrxpFZHEOLKjXJYVN5Il8vSfG7nRX50Zn+aw==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz", + "integrity": "sha512-IJ/goicOZ5TT1axZFSnlAtz4m8KEjYr12BNOANAwGFPKXM4byEDaMNXYowHMG0yKV9a397eU/NlibFaLwr1fbw==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "debug": "^3.1.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz", + "integrity": "sha512-TseswvXEPpG5TCBKoLx9tT7+/GMACjC1ruo09j46ULRZWYm8XHpDWaosOjTnI7kr4SRJFzA6MWoUkAB+YCGKKg==", + "requires": { + "ieee754": "^1.1.11" + } + }, + "@webassemblyjs/leb128": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.5.13.tgz", + "integrity": "sha512-0NRMxrL+GG3eISGZBmLBLAVjphbN8Si15s7jzThaw1UE9e5BY1oH49/+MA1xBzxpf1OW5sf9OrPDOclk9wj2yg==", + "requires": { + "long": "4.0.0" + }, + "dependencies": { + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + } + } + }, + "@webassemblyjs/utf8": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.5.13.tgz", + "integrity": "sha512-Ve1ilU2N48Ew0lVGB8FqY7V7hXjaC4+PeZM+vDYxEd+R2iQ0q+Wb3Rw8v0Ri0+rxhoz6gVGsnQNb4FjRiEH/Ng==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz", + "integrity": "sha512-X7ZNW4+Hga4f2NmqENnHke2V/mGYK/xnybJSIXImt1ulxbCOEs/A+ZK/Km2jgihjyVxp/0z0hwIcxC6PrkWtgw==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/helper-wasm-section": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "@webassemblyjs/wast-printer": "1.5.13", + "debug": "^3.1.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz", + "integrity": "sha512-yfv94Se8R73zmr8GAYzezFHc3lDwE/lBXQddSiIZEKZFuqy7yWtm3KMwA1uGbv5G1WphimJxboXHR80IgX1hQA==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz", + "integrity": "sha512-IkXSkgzVhQ0QYAdIayuCWMmXSYx0dHGU8Ah/AxJf1gBvstMWVnzJnBwLsXLyD87VSBIcsqkmZ28dVb0mOC3oBg==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "debug": "^3.1.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz", + "integrity": "sha512-XnYoIcu2iqq8/LrtmdnN3T+bRjqYFjRHqWbqK3osD/0r/Fcv4d9ecRzjVtC29ENEuNTK4mQ9yyxCBCbK8S/cpg==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz", + "integrity": "sha512-Lbz65T0LQ1LgzKiUytl34CwuhMNhaCLgrh0JW4rJBN6INnBB8NMwUfQM+FxTnLY9qJ+lHJL/gCM5xYhB9oWi4A==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/floating-point-hex-parser": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-code-frame": "1.5.13", + "@webassemblyjs/helper-fsm": "1.5.13", + "long": "^3.2.0", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz", + "integrity": "sha512-QcwogrdqcBh8Z+eUF8SG+ag5iwQSXxQJELBEHmLkk790wgQgnIMmntT2sMAMw53GiFNckArf5X0bsCA44j3lWQ==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "long": "^3.2.0" + } + }, + "@webpack-contrib/config-loader": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@webpack-contrib/config-loader/-/config-loader-1.2.1.tgz", + "integrity": "sha512-C7XsS6bXft0aRlyt7YCLg+fm97Mb3tWd+i5fVVlEl0NW5HKy8LoXVKj3mB7ECcEHNEEdHhgzg8gxP+Or8cMj8Q==", + "requires": { + "@webpack-contrib/schema-utils": "^1.0.0-beta.0", + "chalk": "^2.1.0", + "cosmiconfig": "^5.0.2", + "is-plain-obj": "^1.1.0", + "loud-rejection": "^1.6.0", + "merge-options": "^1.0.1", + "minimist": "^1.2.0", + "resolve": "^1.6.0", + "webpack-log": "^1.1.2" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", + "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "@webpack-contrib/schema-utils": { + "version": "1.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@webpack-contrib/schema-utils/-/schema-utils-1.0.0-beta.0.tgz", + "integrity": "sha512-LonryJP+FxQQHsjGBi6W786TQB1Oym+agTpY0c+Kj8alnIw+DLUJb6SI8Y1GHGhLCH1yPRrucjObUmxNICQ1pg==", + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chalk": "^2.3.2", + "strip-ansi": "^4.0.0", + "text-table": "^0.2.0", + "webpack-log": "^1.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==" + }, + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "requires": { + "acorn": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz", + "integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8=" + }, + "ajv": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", + "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=" + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" + }, + "algoliasearch": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-3.30.0.tgz", + "integrity": "sha512-FuinyPgNn0MeAHm9pan6rLgY6driY3mcTo4AWNBMY1MUReeA5PQA8apV/3SNXqA5bbsuvMvmA0ZrVzrOmEeQTA==", + "requires": { + "agentkeepalive": "^2.2.0", + "debug": "^2.6.8", + "envify": "^4.0.0", + "es6-promise": "^4.1.0", + "events": "^1.1.0", + "foreach": "^2.0.5", + "global": "^4.3.2", + "inherits": "^2.0.1", + "isarray": "^2.0.1", + "load-script": "^1.0.0", + "object-keys": "^1.0.11", + "querystring-es3": "^0.2.1", + "reduce": "^1.0.1", + "semver": "^5.1.0", + "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz", + "integrity": "sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==" + } + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "app-root-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.1.0.tgz", + "integrity": "sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "arch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", + "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "autocomplete.js": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/autocomplete.js/-/autocomplete.js-0.29.0.tgz", + "integrity": "sha512-pvR95T2OVglWEmh+MiIF4kDzBS8EfxsyDTJo0G7DR3BcFXTYUsJ5EyXnEMfUMjTgv7ytnwH9mdEYNCJ+p2ZlHQ==", + "requires": { + "immediate": "^3.2.3" + } + }, + "autoprefixer": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.6.5.tgz", + "integrity": "sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig==", + "requires": { + "browserslist": "^3.2.8", + "caniuse-lite": "^1.0.30000864", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.23", + "postcss-value-parser": "^3.2.3" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "babel-helper-vue-jsx-merge-props": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz", + "integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg==" + }, + "babel-loader": { + "version": "8.0.0-beta.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.0-beta.3.tgz", + "integrity": "sha512-yvaAx7cBEjh+R2oGL2vIPmveO6daS5TYP2FSPq4b6CUYjU/ilD4HHyfLIa9KUj6OKBcR9fQcl1NvUOTWNaJ6mw==", + "requires": { + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "util.promisify": "^1.0.0" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-1.2.0.tgz", + "integrity": "sha512-yeDwKaLgGdTpXL7RgGt5r6T4LmnTza/hUn5Ul8uZSGGMtEjYo13Nxai7SQaGCTEzUtg9Zq9qJn0EjEr7SeSlTQ==", + "requires": { + "babel-plugin-syntax-dynamic-import": "^6.18.0" + } + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=" + }, + "babel-plugin-transform-vue-jsx": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-4.0.1.tgz", + "integrity": "sha512-wbOz7ITB5cloLSjKUU1hWn8zhR+Dwah/RZiTiJY/CQliCwhowmzu6m7NEF+y5EJX/blDzGjRtZvC10Vdb3Q7vw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=" + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "requires": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "cache-loader": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-1.2.2.tgz", + "integrity": "sha512-rsGh4SIYyB9glU+d0OcHwiXHXBoUgDhHZaQ1KAbiXqfz1CDPxtTboh1gPbJ0q2qdO8a9lfcjgC5CJ2Ms32y5bw==", + "requires": { + "loader-utils": "^1.1.0", + "mkdirp": "^0.5.1", + "neo-async": "^2.5.0", + "schema-utils": "^0.4.2" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "caniuse-api": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", + "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", + "requires": { + "browserslist": "^1.3.6", + "caniuse-db": "^1.0.30000529", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + }, + "dependencies": { + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + } + } + }, + "caniuse-db": { + "version": "1.0.30000877", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000877.tgz", + "integrity": "sha512-9RcqvE8HYgdZZzFW6xBmj/CeCaTyCJdUhgkueBCq47AK//w/Yzlg0zcfV1GTlh3jyYEbresGfY2vDEG/AaK/dQ==" + }, + "caniuse-lite": { + "version": "1.0.30000877", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000877.tgz", + "integrity": "sha512-h04kV/lcuhItU1CZTJOxUEk/9R+1XeJqgc67E+XC8J9TjPM8kzVgOn27ZtRdDUo8O5F8U4QRCzDWJrVym3w3Cg==" + }, + "capture-stack-trace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=" + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + }, + "chrome-trace-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", + "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ci-info": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.3.1.tgz", + "integrity": "sha512-l4wK/SFEN8VVTQ9RO1I5yzIL2vw1w6My29qA6Gwaec80QeHxfXbruuUWqn1knyMoJn/X5kav3zVY1TlRHSKeIA==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "clap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", + "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", + "requires": { + "chalk": "^1.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "clipboard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.1.tgz", + "integrity": "sha512-7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ==", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "clipboardy": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", + "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", + "requires": { + "arch": "^2.1.0", + "execa": "^0.8.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "coa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "requires": { + "q": "^1.1.2" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "requires": { + "clone": "^1.0.2", + "color-convert": "^1.3.0", + "color-string": "^0.3.0" + } + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" + }, + "color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "requires": { + "color-name": "^1.0.0" + } + }, + "colormin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", + "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", + "requires": { + "color": "^0.11.0", + "css-color-names": "0.0.4", + "has": "^1.0.1" + } + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=" + }, + "consola": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-1.4.3.tgz", + "integrity": "sha512-PIbVeO9JVVeJ9eY2n8PrkL+hXBGnmaD5x4yJxp2K9nWR7zgtAzRn7rmWxu/d0Iyyr92v8s5AM0qax6xQZ5rSeQ==", + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "lodash": "^4.17.5", + "std-env": "^1.1.0" + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "requires": { + "date-now": "^0.1.4" + } + }, + "consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "requires": { + "bluebird": "^3.1.1" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" + }, + "cookies": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", + "integrity": "sha1-fIphX1SBxhq58WyDNzG8uPZjuZs=", + "requires": { + "depd": "~1.1.1", + "keygrip": "~1.0.2" + } + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "copy-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-zmC33E8FFSq3AbflTvqvPvBo621H36Afsxlui91d+QyZxPIuXghfnTsa1CuqiAaCPgJoSUWfTFbKJnadZpKEbQ==", + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + }, + "dependencies": { + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "requires": { + "is-extglob": "^2.1.1" + } + } + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0", + "require-from-string": "^2.0.1" + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + }, + "css-loader": { + "version": "0.28.11", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", + "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", + "requires": { + "babel-code-frame": "^6.26.0", + "css-selector-tokenizer": "^0.7.0", + "cssnano": "^3.10.0", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash.camelcase": "^4.3.0", + "object-assign": "^4.1.1", + "postcss": "^5.0.6", + "postcss-modules-extract-imports": "^1.2.0", + "postcss-modules-local-by-default": "^1.2.0", + "postcss-modules-scope": "^1.1.0", + "postcss-modules-values": "^1.3.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=" + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=" + }, + "cssnano": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "requires": { + "autoprefixer": "^6.3.1", + "decamelize": "^1.1.2", + "defined": "^1.0.0", + "has": "^1.0.1", + "object-assign": "^4.0.1", + "postcss": "^5.0.14", + "postcss-calc": "^5.2.0", + "postcss-colormin": "^2.1.8", + "postcss-convert-values": "^2.3.4", + "postcss-discard-comments": "^2.0.4", + "postcss-discard-duplicates": "^2.0.1", + "postcss-discard-empty": "^2.0.1", + "postcss-discard-overridden": "^0.1.1", + "postcss-discard-unused": "^2.2.1", + "postcss-filter-plugins": "^2.0.0", + "postcss-merge-idents": "^2.1.5", + "postcss-merge-longhand": "^2.0.1", + "postcss-merge-rules": "^2.0.3", + "postcss-minify-font-values": "^1.0.2", + "postcss-minify-gradients": "^1.0.1", + "postcss-minify-params": "^1.0.4", + "postcss-minify-selectors": "^2.0.4", + "postcss-normalize-charset": "^1.1.0", + "postcss-normalize-url": "^3.0.7", + "postcss-ordered-values": "^2.1.0", + "postcss-reduce-idents": "^2.2.2", + "postcss-reduce-initial": "^1.0.0", + "postcss-reduce-transforms": "^1.0.3", + "postcss-svgo": "^2.1.1", + "postcss-unique-selectors": "^2.0.2", + "postcss-value-parser": "^3.2.3", + "postcss-zindex": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "autoprefixer": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", + "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "requires": { + "browserslist": "^1.7.6", + "caniuse-db": "^1.0.30000634", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^5.2.16", + "postcss-value-parser": "^3.2.3" + } + }, + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "csso": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", + "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "requires": { + "clap": "^1.0.9", + "source-map": "^0.5.3" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "^0.10.9" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + } + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deepmerge": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", + "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "diacritics": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", + "integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E=" + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } + }, + "docsearch.js": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/docsearch.js/-/docsearch.js-2.5.2.tgz", + "integrity": "sha512-bf84/poKgLgeNg45SqabZ9lHdK55QcMtUHvpVj8NLMCpAE6IPaxzeNAg5UfHRPVMwFRKMU0NNZt9yFyNYmVGrg==", + "requires": { + "algoliasearch": "^3.24.5", + "autocomplete.js": "^0.29.0", + "hogan.js": "^3.0.2", + "to-factory": "^1.0.0" + } + }, + "dom-converter": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", + "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", + "requires": { + "utila": "~0.3" + }, + "dependencies": { + "utila": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=" + } + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "duplexify": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "electron-to-chromium": { + "version": "1.3.58", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.58.tgz", + "integrity": "sha512-AGJxlBEn2wOohxqWZkISVsOjZueKTQljfEODTDSEiMqSpH0S+xzV+/5oEM9AGaqhu7DzrpKOgU7ocQRjj0nJmg==" + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "envify": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/envify/-/envify-4.1.0.tgz", + "integrity": "sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw==", + "requires": { + "esprima": "^4.0.0", + "through": "~2.3.4" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + } + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=" + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "requires": { + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" + } + }, + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "^2.1.0" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", + "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.0.1", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.1", + "micromatch": "^3.1.10" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "^2.0.0" + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "requires": { + "ini": "^1.3.4" + } + }, + "globals": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==" + }, + "globby": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "optional": true, + "requires": { + "delegate": "^3.1.2" + } + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "gray-matter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.1.tgz", + "integrity": "sha512-p0MADBEBl1CohV7nRZ8sVinBexEe3CKVhh0A0QIHKpcbRoxB0VgeMpRPjW/HBHIPLAKrpIIIm5mZ6hKu3E+iQg==", + "requires": { + "js-yaml": "^3.11.0", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=" + }, + "hash.js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", + "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "hogan.js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz", + "integrity": "sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=", + "requires": { + "mkdirp": "0.3.0", + "nopt": "1.0.10" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + }, + "html-comment-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", + "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=" + }, + "html-minifier": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", + "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.1.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "htmlparser2": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "requires": { + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" + }, + "dependencies": { + "domutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "requires": { + "domelementtype": "1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "http-assert": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.3.0.tgz", + "integrity": "sha1-oxpc+IyHPsu1eWkH1NbxMujAHko=", + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.6.1" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + } + } + }, + "http-errors": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.0.tgz", + "integrity": "sha512-hz3BtSHB7Z6dNWzYc+gUbWqG4dIpJedwwOhe1cvGUq5tGmcTTIRkPiAbyh/JlZx+ksSJyGJlgcHo5jGahiXnKw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "requires": { + "postcss": "^6.0.1" + } + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + }, + "immediate": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", + "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "requires": { + "import-from": "^2.1.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-ci": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.0.tgz", + "integrity": "sha512-plgvKjQtalH2P3Gytb7L61Lmz95g2DlpzFiQyRSFew8WoJKxtKRzrZMeyRN2supblm3Psc8OQGy7Xjb6XG11jw==", + "requires": { + "ci-info": "^1.3.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-svg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", + "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isemail": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.3.tgz", + "integrity": "sha512-5xbsG5wYADIcB+mfLsd+nst1V/D+I7EU7LEZPo2GOIMu4JzfcRs5yQoypP4avA7QtUqgxYLKBYNv4IdzBmbhdw==", + "requires": { + "punycode": "2.x.x" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + }, + "javascript-stringify": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-1.6.0.tgz", + "integrity": "sha1-FC0RHzpuPa6PSpr9d9RYVbWpzOM=" + }, + "joi": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-11.4.0.tgz", + "integrity": "sha512-O7Uw+w/zEWgbL6OcHbyACKSj0PkQeUgmehdoXVSxt92QFCq4+1390Rwh5moI2K/OgC7D8RHRZqHZxT2husMJHA==", + "requires": { + "hoek": "4.x.x", + "isemail": "3.x.x", + "topo": "2.x.x" + } + }, + "js-base64": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.8.tgz", + "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==" + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + } + } + }, + "jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "keygrip": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.2.tgz", + "integrity": "sha1-rTKXxVcGneqLz+ek+kkbdcXd65E=" + }, + "killable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", + "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "koa": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.5.2.tgz", + "integrity": "sha512-MoVGWre9g3p35pCqXNhOT/a4trwK5CGvalIoPi7qOA2RCZaep3GCsa/G/tD9QMjQI7bmVWn3XF3SOau8RkPh6w==", + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.7.1", + "debug": "^3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "koa-is-json": "^1.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "dependencies": { + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + } + } + }, + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "requires": { + "any-promise": "^1.1.0" + } + }, + "koa-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/koa-connect/-/koa-connect-2.0.1.tgz", + "integrity": "sha512-MNaiK5og8aj4I+tx8l+jSW24QX7aaQyZemV821VPY+AOJ8XUbrrAj9AzrpZKDQp5jTmylAZW2sXhTz2+SRqZog==" + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + } + }, + "koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=" + }, + "koa-mount": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-3.0.0.tgz", + "integrity": "sha1-CMqzuD0xRC7Yt+dcVLGr65IuwZc=", + "requires": { + "debug": "^2.6.1", + "koa-compose": "^3.2.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "koa-range": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/koa-range/-/koa-range-0.3.0.tgz", + "integrity": "sha1-NYjjSWRzqDmhvSZNKkKx2FvX/qw=", + "requires": { + "stream-slice": "^0.1.2" + } + }, + "koa-send": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-4.1.3.tgz", + "integrity": "sha512-3UetMBdaXSiw24qM2Mx5mKmxLKw5ZTPRjACjfhK6Haca55RKm9hr/uHDrkrxhSl5/S1CKI/RivZVIopiatZuTA==", + "requires": { + "debug": "^2.6.3", + "http-errors": "^1.6.1", + "mz": "^2.6.0", + "resolve-path": "^1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "koa-static": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-4.0.3.tgz", + "integrity": "sha512-JGmxTuPWy4bH7bt6gD/OMWkhprawvRmzJSr8TWKmTL4N7+IMv3s0SedeQi5S4ilxM9Bo6ptkCyXj/7wf+VS5tg==", + "requires": { + "debug": "^3.1.0", + "koa-send": "^4.1.3" + } + }, + "koa-webpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-webpack/-/koa-webpack-4.0.0.tgz", + "integrity": "sha512-P+j2TzeZAqFwscd/dlLykk/sxwr6wQ5Tp3FYDhqv1+y9aRIffTPnu2zK+1BhfM+Kyh8bepU1jFR420maE+Vajw==", + "requires": { + "app-root-path": "^2.0.1", + "merge-options": "^1.0.0", + "webpack-dev-middleware": "^3.0.0", + "webpack-hot-client": "^3.0.0", + "webpack-log": "^1.1.1" + } + }, + "last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "requires": { + "lodash": "^4.17.5", + "webpack-sources": "^1.1.0" + } + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "requires": { + "package-json": "^4.0.0" + } + }, + "linkify-it": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", + "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", + "requires": { + "uc.micro": "^1.0.1" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=" + }, + "loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=" + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "lodash.template": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", + "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "requires": { + "lodash._reinterpolate": "~3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", + "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "requires": { + "lodash._reinterpolate": "~3.0.0" + } + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "requires": { + "chalk": "^2.0.1" + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + } + }, + "loglevelnext": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.5.tgz", + "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", + "requires": { + "es6-symbol": "^3.1.1", + "object.assign": "^4.1.0" + } + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==" + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "markdown-it": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", + "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", + "requires": { + "argparse": "^1.0.7", + "entities": "~1.1.1", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.0.2.tgz", + "integrity": "sha512-AFM/woBI8QDJMS/9+MmsBMT5/AR+ImfOsunQZTZhzcTmna3rIzAzbOh5E0l6mlFM/i9666BpUtkqQ9bS7WApCg==" + }, + "markdown-it-container": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-2.0.0.tgz", + "integrity": "sha1-ABm0P9Au7+zi8ZYKKJX7qBpARpU=" + }, + "markdown-it-emoji": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", + "integrity": "sha1-m+4OmpkKljupbfaYDE/dsF37Tcw=" + }, + "markdown-it-table-of-contents": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.3.tgz", + "integrity": "sha512-x/OdaRzLYxAjmB+jIVlXuE3nX7tZTLDQxm58RkgjTLyQ+I290jYQvPS9cJjVN6SM3U6K6CHKYNgUtPNZmLblYQ==" + }, + "markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==" + }, + "math-expression-evaluator": { + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", + "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=" + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=" + }, + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + } + }, + "merge-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", + "integrity": "sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==", + "requires": { + "is-plain-obj": "^1.1" + } + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "merge2": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", + "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==" + }, + "mime-db": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" + }, + "mime-types": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "requires": { + "mime-db": "~1.35.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "mini-css-extract-plugin": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.1.tgz", + "integrity": "sha512-XWuB3G61Rtasq/gLe7cp5cuozehE6hN+E4sxCamRR/WDiHTg+f7ZIAS024r8UJQffY+e2gGELXQZgQoFDfNDCg==", + "requires": { + "@webpack-contrib/schema-utils": "^1.0.0-beta.0", + "loader-utils": "^1.1.0", + "webpack-sources": "^1.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "optional": true + }, + "nanoassert": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", + "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" + }, + "nanobus": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/nanobus/-/nanobus-4.3.3.tgz", + "integrity": "sha512-4/uzl+LkMGoVv/9eMzH2QFvefmlJErT0KR7EmuYbmht2QvxSEqTjhFFOZ/KHE6chH58fKL3njrOcEwbYV0h9Yw==", + "requires": { + "nanotiming": "^7.2.0", + "remove-array-items": "^1.0.0" + } + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "nanoscheduler": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/nanoscheduler/-/nanoscheduler-1.0.3.tgz", + "integrity": "sha512-jBbrF3qdU9321r8n9X7yu18DjP31Do2ItJm3mWrt90wJTrnDO+HXpoV7ftaUglAtjgj9s+OaCxGufbvx6pvbEQ==", + "requires": { + "nanoassert": "^1.1.0" + } + }, + "nanotiming": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/nanotiming/-/nanotiming-7.3.1.tgz", + "integrity": "sha512-l3lC7v/PfOuRWQa8vV29Jo6TG10wHtnthLElFXs4Te4Aas57Fo4n1Q8LH9n+NDh9riOzTVvb2QNBhTS4JUKNjw==", + "requires": { + "nanoassert": "^1.1.0", + "nanoscheduler": "^1.0.2" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "neo-async": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz", + "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==" + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E=" + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" + }, + "opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimize-css-assets-webpack-plugin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-4.0.3.tgz", + "integrity": "sha512-iOfMsuGMPbM/bZZ731gwtAXfXjIkR97BXqUXsPGIzBaQzpvqajsoIFlR+z+Q7FLcq2TmV4JFGo80d98ttfRzhA==", + "requires": { + "cssnano": "^3.10.0", + "last-call-webpack-plugin": "^3.0.0" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pbkdf2": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", + "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "requires": { + "find-up": "^2.1.0" + } + }, + "portfinder": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz", + "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==", + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-calc": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", + "requires": { + "postcss": "^5.0.2", + "postcss-message-helpers": "^2.0.0", + "reduce-css-calc": "^1.2.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-colormin": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", + "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", + "requires": { + "colormin": "^1.0.5", + "postcss": "^5.0.13", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-convert-values": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", + "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", + "requires": { + "postcss": "^5.0.11", + "postcss-value-parser": "^3.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-comments": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", + "requires": { + "postcss": "^5.0.14" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-duplicates": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", + "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-empty": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", + "requires": { + "postcss": "^5.0.14" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-overridden": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", + "requires": { + "postcss": "^5.0.16" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-unused": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "requires": { + "postcss": "^5.0.14", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-filter-plugins": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", + "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-load-config": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz", + "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==", + "requires": { + "cosmiconfig": "^4.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.6.tgz", + "integrity": "sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==", + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^0.4.0" + } + }, + "postcss-merge-idents": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.10", + "postcss-value-parser": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", + "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-merge-rules": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", + "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", + "requires": { + "browserslist": "^1.5.2", + "caniuse-api": "^1.5.2", + "postcss": "^5.0.4", + "postcss-selector-parser": "^2.2.2", + "vendors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-message-helpers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", + "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=" + }, + "postcss-minify-font-values": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "requires": { + "object-assign": "^4.0.1", + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-gradients": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "requires": { + "postcss": "^5.0.12", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-params": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.2", + "postcss-value-parser": "^3.0.2", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-selectors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", + "requires": { + "alphanum-sort": "^1.0.2", + "has": "^1.0.1", + "postcss": "^5.0.14", + "postcss-selector-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", + "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", + "requires": { + "postcss": "^6.0.1" + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + } + }, + "postcss-normalize-charset": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", + "requires": { + "postcss": "^5.0.5" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-normalize-url": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^1.4.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-ordered-values": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", + "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-idents": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-initial": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-transforms": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.8", + "postcss-value-parser": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "requires": { + "is-svg": "^2.0.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3", + "svgo": "^0.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-unique-selectors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-value-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", + "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=" + }, + "postcss-zindex": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "prettier": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz", + "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==" + }, + "pretty-bytes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=" + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" + }, + "prismjs": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.15.0.tgz", + "integrity": "sha512-Lf2JrFYx8FanHrjoV5oL8YHCclLQgbJcVZR+gikGGMqz6ub5QVWDTM6YIwm3BuPxM/LOV+rKns3LssXNLIf+DA==", + "requires": { + "clipboard": "^2.0.0" + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "public-encrypt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", + "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + }, + "randomatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "requires": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce/-/reduce-1.0.1.tgz", + "integrity": "sha1-FPouX/H8VgcDoCDLtfuqtpFWWAQ=", + "requires": { + "object-keys": "~1.0.0" + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + } + } + }, + "reduce-function-call": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", + "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "requires": { + "balanced-match": "^0.4.2" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + } + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", + "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "regenerator-transform": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.12.4.tgz", + "integrity": "sha512-p2I0fY+TbSLD2/VFTFb/ypEHxs3e3AjU0DzttdPqk2bSmDhfSh5E54b86Yc6XhUa5KykK1tgbvZ4Nr82oCJWkQ==", + "requires": { + "private": "^0.1.6" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz", + "integrity": "sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.4.0", + "regjsparser": "^0.3.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "register-service-worker": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/register-service-worker/-/register-service-worker-1.5.2.tgz", + "integrity": "sha512-XNqSZHJsFGnvEGkg/2IrCp6G8Ya3qLj4mq0bSHil/dfdO82LOxGnMnJjAD9MYCvf/8cDCO8pL+1i65yzmP7rPQ==" + }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, + "regjsgen": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz", + "integrity": "sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA==" + }, + "regjsparser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.3.0.tgz", + "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" + }, + "remove-array-items": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remove-array-items/-/remove-array-items-1.0.0.tgz", + "integrity": "sha1-B79CyzMvTPboXq2DteToltIyayE=" + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "renderkid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", + "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", + "requires": { + "css-select": "^1.1.0", + "dom-converter": "~0.1", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "~0.3" + }, + "dependencies": { + "utila": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=" + } + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "requires": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + } + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "requires": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", + "optional": true + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "requires": { + "semver": "^5.0.3" + } + }, + "serialize-javascript": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", + "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==" + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "std-env": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-1.3.1.tgz", + "integrity": "sha512-KI2F2pPJpd3lHjng+QLezu0eq+QDtXcv1um016mhOPAJFHKL+09ykK5PUBWta2pZDC8BVV0VPya08A15bUXSLQ==", + "requires": { + "is-ci": "^1.1.0" + } + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "stream-slice": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", + "integrity": "sha1-LcT04bk2+xPz6zmi3vGTJ5jQeks=" + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "stylus-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", + "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "requires": { + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", + "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "requires": { + "coa": "~1.0.1", + "colors": "~1.1.2", + "csso": "~2.3.1", + "js-yaml": "~3.7.0", + "mkdirp": "~0.5.1", + "sax": "~1.2.1", + "whet.extend": "~0.9.9" + }, + "dependencies": { + "js-yaml": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "requires": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + } + } + } + }, + "table": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "requires": { + "ajv": "^6.0.1", + "ajv-keywords": "^3.0.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "tapable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", + "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==" + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "time-fix-plugin": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/time-fix-plugin/-/time-fix-plugin-2.0.3.tgz", + "integrity": "sha512-5StGdiXCiBsq2+RdBQ0NrunFeKqVX0f2Myn8s1YqOxRS+kmuiHcdpPm27ucbM6FYawBKGTOOYxPnqxIBZyiqEg==" + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tiny-emitter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", + "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==", + "optional": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "to-factory": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-factory/-/to-factory-1.0.0.tgz", + "integrity": "sha1-hzivi9lxIK0dQEeXKtpVY7+UebE=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "toml": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.3.tgz", + "integrity": "sha512-O7L5hhSQHxuufWUdcTRPfuTh3phKfAZ/dqfxZFoxPCj2RYmpaSGLEIs016FCXItQwNr08yefUB5TSjzRYnajTA==" + }, + "topo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", + "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", + "requires": { + "hoek": "4.x.x" + } + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=" + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "uc.micro": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz", + "integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==" + }, + "uglify-js": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.7.tgz", + "integrity": "sha512-J0M2i1mQA+ze3EdN9SBi751DNdAXmeFLfJrd/MDIkRc3G3Gbb9OPVSx7GIQvVwfWxQARcYV2DTxIkMyDAk3o9Q==", + "requires": { + "commander": "~2.16.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + } + } + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", + "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", + "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==" + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + }, + "unique-filename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", + "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", + "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==" + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-join": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-3.0.0.tgz", + "integrity": "sha1-JugROs4ZXqMND8OBhuRUAPnOpnI=" + }, + "url-loader": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.1.tgz", + "integrity": "sha512-vugEeXjyYFBCUOpX+ZuaunbK3QXMKaQ3zUnRfIpRBlGkY7QizCnzyyn2ASfcxsvyU3ef+CJppVywnl3Kgf13Gg==", + "requires": { + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "v8-compile-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", + "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "vendors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", + "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==" + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "requires": { + "indexof": "0.0.1" + } + }, + "vue": { + "version": "2.5.17", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.17.tgz", + "integrity": "sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ==" + }, + "vue-hot-reload-api": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz", + "integrity": "sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA==" + }, + "vue-loader": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.4.0.tgz", + "integrity": "sha512-qhc1fnflEVhFq5dYLDXXTXf3PoRiGxeMhBmGDeLqlg0XAaikZEU224ZLqsTkQQVUT2uA2PN9haYlBUqq/6iEtA==", + "requires": { + "@vue/component-compiler-utils": "^2.0.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "vue-hot-reload-api": "^2.3.0", + "vue-style-loader": "^4.1.0" + } + }, + "vue-router": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.0.1.tgz", + "integrity": "sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w==" + }, + "vue-server-renderer": { + "version": "2.5.17", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.5.17.tgz", + "integrity": "sha512-n62Fg4xv9ouxNloW2U3Bru2Jj+DkbnnrlzwuTkaU1o7CIDifG+r0+ILLMW0eVjgCjhKefHTYjwJ49RJ3bCjv1Q==", + "requires": { + "chalk": "^1.1.3", + "hash-sum": "^1.0.2", + "he": "^1.1.0", + "lodash.template": "^4.4.0", + "lodash.uniq": "^4.5.0", + "resolve": "^1.2.0", + "serialize-javascript": "^1.3.0", + "source-map": "0.5.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "vue-style-loader": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz", + "integrity": "sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==", + "requires": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "vue-template-compiler": { + "version": "2.5.17", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.5.17.tgz", + "integrity": "sha512-63uI4syCwtGR5IJvZM0LN5tVsahrelomHtCxvRkZPJ/Tf3ADm1U1wG6KWycK3qCfqR+ygM5vewUvmJ0REAYksg==", + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "vue-template-es2015-compiler": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz", + "integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==" + }, + "vuepress": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-0.14.2.tgz", + "integrity": "sha512-E2cV2kSBgIBOhp56Z9bS2LXGtvf9Neb+eELvEJpDyASehs4isKLcwQVTD/pIVeik2oAEvfpaYy5omVGbj8yvZQ==", + "requires": { + "@babel/core": "7.0.0-beta.47", + "@vue/babel-preset-app": "3.0.0-beta.11", + "autoprefixer": "^8.2.0", + "babel-loader": "8.0.0-beta.3", + "cache-loader": "^1.2.2", + "chalk": "^2.3.2", + "chokidar": "^2.0.3", + "commander": "^2.15.1", + "connect-history-api-fallback": "^1.5.0", + "copy-webpack-plugin": "^4.5.1", + "cross-spawn": "^6.0.5", + "css-loader": "^0.28.11", + "diacritics": "^1.3.0", + "docsearch.js": "^2.5.2", + "escape-html": "^1.0.3", + "file-loader": "^1.1.11", + "fs-extra": "^5.0.0", + "globby": "^8.0.1", + "gray-matter": "^4.0.1", + "js-yaml": "^3.11.0", + "koa-connect": "^2.0.1", + "koa-mount": "^3.0.0", + "koa-range": "^0.3.0", + "koa-static": "^4.0.2", + "loader-utils": "^1.1.0", + "lodash.throttle": "^4.1.1", + "lru-cache": "^4.1.2", + "markdown-it": "^8.4.1", + "markdown-it-anchor": "^5.0.2", + "markdown-it-container": "^2.0.0", + "markdown-it-emoji": "^1.4.0", + "markdown-it-table-of-contents": "^0.4.0", + "mini-css-extract-plugin": "^0.4.1", + "nprogress": "^0.2.0", + "optimize-css-assets-webpack-plugin": "^4.0.0", + "portfinder": "^1.0.13", + "postcss-loader": "^2.1.5", + "prismjs": "^1.13.0", + "register-service-worker": "^1.5.1", + "semver": "^5.5.0", + "stylus": "^0.54.5", + "stylus-loader": "^3.0.2", + "toml": "^2.3.3", + "url-loader": "^1.0.1", + "vue": "^2.5.16", + "vue-loader": "^15.2.4", + "vue-router": "^3.0.1", + "vue-server-renderer": "^2.5.16", + "vue-template-compiler": "^2.5.16", + "vuepress-html-webpack-plugin": "^3.2.0", + "webpack": "^4.8.1", + "webpack-chain": "^4.6.0", + "webpack-merge": "^4.1.2", + "webpack-serve": "^1.0.2", + "webpackbar": "^2.6.1", + "workbox-build": "^3.1.0" + } + }, + "vuepress-html-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vuepress-html-webpack-plugin/-/vuepress-html-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-BebAEl1BmWlro3+VyDhIOCY6Gef2MCBllEVAP3NUAtMguiyOwo/dClbwJ167WYmcxHJKLl7b0Chr9H7fpn1d0A==", + "requires": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "webpack": { + "version": "4.16.5", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.5.tgz", + "integrity": "sha512-i5cHYHonzSc1zBuwB5MSzW4v9cScZFbprkHK8ZgzPDCRkQXGGpYzPmJhbus5bOrZ0tXTcQp+xyImRSvKb0b+Kw==", + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/wasm-edit": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.0.0", + "uglifyjs-webpack-plugin": "^1.2.4", + "watchpack": "^1.5.0", + "webpack-sources": "^1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "webpack-chain": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-4.9.0.tgz", + "integrity": "sha512-DQbqFAVEQg1+u9kXDMNyA4yZzQIf/tZD34GQ4ev97G3DrKtGYMyUyyUYZxt50qBPG1MMlKG12PwMrUjlth3uxg==", + "requires": { + "deepmerge": "^1.5.2", + "javascript-stringify": "^1.6.0" + } + }, + "webpack-dev-middleware": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.1.3.tgz", + "integrity": "sha512-I6Mmy/QjWU/kXwCSFGaiOoL5YEQIVmbb0o45xMoCyQAg/mClqZVTcsX327sPfekDyJWpCxb+04whNyLOIxpJdQ==", + "requires": { + "loud-rejection": "^1.6.0", + "memory-fs": "~0.4.1", + "mime": "^2.1.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "url-join": "^4.0.0", + "webpack-log": "^1.0.1" + }, + "dependencies": { + "url-join": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", + "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=" + } + } + }, + "webpack-hot-client": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-hot-client/-/webpack-hot-client-3.0.0.tgz", + "integrity": "sha512-6k91015hZ4Okkz8u6OzRgJygEL+3J3ay6HVZhWBF3tT2P0rZJ0mgca39dotJxngggUm3S8707c0vrcynn1IzEQ==", + "requires": { + "json-stringify-safe": "^5.0.1", + "loglevelnext": "^1.0.2", + "strip-ansi": "^4.0.0", + "uuid": "^3.1.0", + "webpack-log": "^1.1.1", + "ws": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "webpack-log": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", + "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", + "requires": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "loglevelnext": "^1.0.1", + "uuid": "^3.1.0" + } + }, + "webpack-merge": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz", + "integrity": "sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==", + "requires": { + "lodash": "^4.17.5" + } + }, + "webpack-serve": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/webpack-serve/-/webpack-serve-1.0.4.tgz", + "integrity": "sha512-WhI9PMY2YLFliZhDsQFE5Os/On5Py6DGZpeBJyDM8xl0cspxgvXmWFywACn2YWWDgowqIxRqveyGh2RwdFWTNQ==", + "requires": { + "@shellscape/koa-static": "^4.0.4", + "@webpack-contrib/config-loader": "^1.1.1", + "chalk": "^2.3.0", + "clipboardy": "^1.2.2", + "cosmiconfig": "^5.0.2", + "debug": "^3.1.0", + "find-up": "^2.1.0", + "get-port": "^3.2.0", + "import-local": "^1.0.0", + "killable": "^1.0.0", + "koa": "^2.4.1", + "koa-webpack": "^4.0.0", + "lodash": "^4.17.5", + "loud-rejection": "^1.6.0", + "meow": "^5.0.0", + "nanobus": "^4.3.1", + "opn": "^5.1.0", + "resolve": "^1.6.0", + "time-fix-plugin": "^2.0.0", + "update-notifier": "^2.3.0", + "url-join": "3.0.0", + "v8-compile-cache": "^2.0.0", + "webpack-hot-client": "^3.0.0", + "webpack-log": "^1.1.2" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", + "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + } + } + }, + "webpack-sources": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", + "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "webpackbar": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-2.6.3.tgz", + "integrity": "sha512-UlTm7Yz4meJV0THhZMrgRTE9v/vZ0xfUoJ/eOig98TvzsqNiW+FLSv5WaZeML3uJUPrMQ6K5jo1FJJFXNCc8+g==", + "requires": { + "chalk": "^2.4.1", + "consola": "^1.4.3", + "figures": "^2.0.0", + "loader-utils": "^1.1.0", + "lodash": "^4.17.10", + "log-update": "^2.3.0", + "pretty-time": "^1.1.0", + "schema-utils": "^1.0.0", + "std-env": "^1.3.1", + "table": "^4.0.3" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "when": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=" + }, + "whet.extend": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", + "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "requires": { + "string-width": "^2.1.1" + } + }, + "workbox-background-sync": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-3.4.1.tgz", + "integrity": "sha512-Ksb2nCg/2wOyBMhSBqSbtCEwuKaf5sHgTY8HdCxbLIQSzDh9/qZqg+1P11CKlgJmHtje3EK3B8EsrzukZo10xA==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-broadcast-cache-update": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-broadcast-cache-update/-/workbox-broadcast-cache-update-3.4.1.tgz", + "integrity": "sha512-+WPqHFk4ER4RICAMOYrP88yBbiUQ9ZOFNruqwbl9YxGfbADV16OEGmYpIs+Az6HT6DNDCx8eQqtFiaG8N3O11Q==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-build": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-3.4.1.tgz", + "integrity": "sha512-Qi04XdHjkXbRN0CV5XO1oqDWbJSIm7VYhxmxjtnVcKK8PrMT6rOUFUi9ziDI+8UQgcXbLK4ZChWf2ptZS1/MbA==", + "requires": { + "babel-runtime": "^6.26.0", + "common-tags": "^1.4.0", + "fs-extra": "^4.0.2", + "glob": "^7.1.2", + "joi": "^11.1.1", + "lodash.template": "^4.4.0", + "pretty-bytes": "^4.0.2", + "workbox-background-sync": "^3.4.1", + "workbox-broadcast-cache-update": "^3.4.1", + "workbox-cache-expiration": "^3.4.1", + "workbox-cacheable-response": "^3.4.1", + "workbox-core": "^3.4.1", + "workbox-google-analytics": "^3.4.1", + "workbox-navigation-preload": "^3.4.1", + "workbox-precaching": "^3.4.1", + "workbox-range-requests": "^3.4.1", + "workbox-routing": "^3.4.1", + "workbox-strategies": "^3.4.1", + "workbox-streams": "^3.4.1", + "workbox-sw": "^3.4.1" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "workbox-cache-expiration": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-cache-expiration/-/workbox-cache-expiration-3.4.1.tgz", + "integrity": "sha512-AzOPB+dwfxg13v4+q5jWkxsw/oim9mPIzew1anu8ALA3vB8qySaJJToXp+ZlVh/Co+sDK0tgjlB76bvSFHgZ4g==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-cacheable-response": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-3.4.1.tgz", + "integrity": "sha512-SO2k830JT93GitPwc5tzJI49d9VwyVxXwiCbyvo+Sqo+dcvWSrmpsyuXdzy6zuasbPrWUF0vsFj1uGtZbOym8Q==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-core": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-3.4.1.tgz", + "integrity": "sha512-RqMV2so9/KLAu9aUxJ/85pvrZMUn835B8zoHmqRyGNetiDr8B1zSBeKXPZAjFlX/88KdhizNwiRlJtqlXtM4tA==" + }, + "workbox-google-analytics": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-3.4.1.tgz", + "integrity": "sha512-w6Osz2Rr1/4+W0gram6Yzg6NNWLvHP51RwFCNAZSpEnipr0qSEtD+yvwrdaHfiJHWhcK2yH/V6E1MV8Hrczmvw==", + "requires": { + "workbox-background-sync": "^3.4.1", + "workbox-core": "^3.4.1", + "workbox-routing": "^3.4.1", + "workbox-strategies": "^3.4.1" + } + }, + "workbox-navigation-preload": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-3.4.1.tgz", + "integrity": "sha512-P3FHAcyZ8db2QiW/BpMkuosC1OkRsEoUaT7U3QOgg7JSjjsJoEbF7G5olNe+P+PQYdVhJA7TCuptI6dy2gLS/g==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-precaching": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-3.4.1.tgz", + "integrity": "sha512-ykU2mly9xmRrCW6iMeUWYydWiso/WSE16+7wponhI0WC53jiQSt2JvykWm0VpWFJSs6ZTSZZ1WK2gs/brRnPug==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-range-requests": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-3.4.1.tgz", + "integrity": "sha512-ktgjl6liZrRTmQjPw1pBblC5umHnTb8XcvFVitdGz17B23jj6cUV4EXzEU2ilGn6jO6+MLV1Vn9SWajtLSc2Gg==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-routing": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-3.4.1.tgz", + "integrity": "sha512-6j6cXMUYfMPYTycmElxVOfBTr6WV5zAn/JUFJ7GJ5pYFIE9cqztprnrcOsWJ42+AiNIeHPbKfyIWE/rZVviMxQ==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-strategies": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-3.4.1.tgz", + "integrity": "sha512-7mJuzFsgejflzjfnChXCFma1S0mi9WC6wlSU2wE50M7bJmEuf9A3j3MojpKcsTEM58hbhbnU6QF/u9iIV7+opw==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-streams": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-3.4.1.tgz", + "integrity": "sha512-krw+5bp+oe9Za5c6WlTWM3SgZGfExYcqRSn1gsyYgKeXmgzTwf+DOb5Lwult0KSWlJfq8B3Wk7sW8Sl7lRzSbA==", + "requires": { + "workbox-core": "^3.4.1" + } + }, + "workbox-sw": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-3.4.1.tgz", + "integrity": "sha512-nnm2by5oaQGXRH7x4M5/n2KqjUGVmP4P8azUmJITnYa3DWVYn/ghDg3LJ5+h4A28vYq9V6ePgATaEPfb6B5pug==" + }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", + "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "requires": { + "camelcase": "^4.1.0" + } + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==" + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..d469413c --- /dev/null +++ b/docs/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "docs:build": "vuepress build" + }, + "dependencies": { + "markdown-it-task-lists": "^2.1.1", + "vuepress": "^0.14.2" + } +} diff --git a/gradle.properties b/gradle.properties index 624bcc50..7f10785b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.configureondemand=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=1.1.1 +VERSION_NAME=2.0.0 GROUP=ru.noties POM_DESCRIPTION=Markwon diff --git a/library-image-loader/build.gradle b/library-image-loader/build.gradle deleted file mode 100644 index 9d655ea3..00000000 --- a/library-image-loader/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion TARGET_SDK - buildToolsVersion BUILD_TOOLS - - defaultConfig { - minSdkVersion MIN_SDK - targetSdkVersion TARGET_SDK - versionCode 1 - versionName version - } - - lintOptions { - // okio.... - disable 'InvalidPackage' - } -} - -dependencies { - api project(':library') - api ANDROID_SVG - api ANDROID_GIF - api OK_HTTP -} - -afterEvaluate { - generateReleaseBuildConfig.enabled = false -} - -if (hasProperty('release')) { - apply from: 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle' -} diff --git a/library-syntax/build.gradle b/library-syntax/build.gradle deleted file mode 100644 index 2d6a2a67..00000000 --- a/library-syntax/build.gradle +++ /dev/null @@ -1,28 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion TARGET_SDK - buildToolsVersion BUILD_TOOLS - - defaultConfig { - minSdkVersion MIN_SDK - targetSdkVersion TARGET_SDK - versionCode 1 - versionName version - } -} - -dependencies { - api SUPPORT_ANNOTATIONS - api PRISM_4J - api project(':library') -} - -afterEvaluate { - generateReleaseBuildConfig.enabled = false -} - -if (hasProperty('release')) { - apply from: 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle' -} diff --git a/library-syntax/gradle.properties b/library-syntax/gradle.properties deleted file mode 100644 index ba94baa1..00000000 --- a/library-syntax/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -POM_NAME=Markwon -POM_ARTIFACT_ID=markwon-syntax -POM_PACKAGING=aar \ No newline at end of file diff --git a/library-view/build.gradle b/library-view/build.gradle deleted file mode 100644 index fb709db9..00000000 --- a/library-view/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion TARGET_SDK - buildToolsVersion BUILD_TOOLS - - defaultConfig { - minSdkVersion MIN_SDK - targetSdkVersion TARGET_SDK - versionCode 1 - versionName version - } -} - -dependencies { - api project(':library') - compileOnly SUPPORT_APP_COMPAT -} - -afterEvaluate { - generateReleaseBuildConfig.enabled = false -} - -if (hasProperty('release')) { - apply from: 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle' -} diff --git a/library/build.gradle b/library/build.gradle deleted file mode 100644 index a9bc3ee2..00000000 --- a/library/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion TARGET_SDK - buildToolsVersion BUILD_TOOLS - - defaultConfig { - minSdkVersion MIN_SDK - targetSdkVersion TARGET_SDK - versionCode 1 - versionName version - } -} - -dependencies { - api SUPPORT_ANNOTATIONS - api COMMON_MARK - api COMMON_MARK_STRIKETHROUGHT - api COMMON_MARK_TABLE -} - -afterEvaluate { - generateReleaseBuildConfig.enabled = false -} - -if (hasProperty('release')) { - apply from: 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle' -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/BoldProvider.java b/library/src/main/java/ru/noties/markwon/renderer/html/BoldProvider.java deleted file mode 100644 index fbd42fc4..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/BoldProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; - -import ru.noties.markwon.SpannableFactory; - -class BoldProvider implements SpannableHtmlParser.SpanProvider { - - private final SpannableFactory factory; - - /** - * @since 1.1.0 - */ - BoldProvider(@NonNull SpannableFactory factory) { - this.factory = factory; - } - - @Override - public Object provide(@NonNull SpannableHtmlParser.Tag tag) { - return factory.strongEmphasis(); - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java b/library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java deleted file mode 100644 index 10f62c41..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/ImageProviderImpl.java +++ /dev/null @@ -1,224 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import ru.noties.markwon.SpannableFactory; -import ru.noties.markwon.UrlProcessor; -import ru.noties.markwon.renderer.ImageSize; -import ru.noties.markwon.renderer.ImageSizeResolver; -import ru.noties.markwon.spans.AsyncDrawable; -import ru.noties.markwon.spans.SpannableTheme; - -class ImageProviderImpl implements SpannableHtmlParser.ImageProvider { - - private final SpannableFactory factory; - private final SpannableTheme theme; - private final AsyncDrawable.Loader loader; - private final UrlProcessor urlProcessor; - private final ImageSizeResolver imageSizeResolver; - - ImageProviderImpl( - @NonNull SpannableFactory factory, - @NonNull SpannableTheme theme, - @NonNull AsyncDrawable.Loader loader, - @NonNull UrlProcessor urlProcessor, - @NonNull ImageSizeResolver imageSizeResolver - ) { - this.factory = factory; - this.theme = theme; - this.loader = loader; - this.urlProcessor = urlProcessor; - this.imageSizeResolver = imageSizeResolver; - } - - @Override - public Spanned provide(@NonNull SpannableHtmlParser.Tag tag) { - - final Spanned spanned; - - final Map attributes = tag.attributes(); - final String src = attributes.get("src"); - final String alt = attributes.get("alt"); - - if (!TextUtils.isEmpty(src)) { - - final String destination = urlProcessor.process(src); - - final String replacement; - if (!TextUtils.isEmpty(alt)) { - replacement = alt; - } else { - replacement = "\uFFFC"; - } - - final Object span = factory.image( - theme, - destination, - loader, - imageSizeResolver, - parseImageSize(attributes), - false); - - final SpannableString string = new SpannableString(replacement); - - if (span != null) { - final int length = string.length(); - if (span.getClass().isArray()) { - for (Object o : ((Object[]) span)) { - string.setSpan(o, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - string.setSpan(span, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - - spanned = string; - } else { - spanned = null; - } - - return spanned; - } - - @Nullable - private static ImageSize parseImageSize(@NonNull Map attributes) { - - final ImageSize imageSize; - - final StyleProvider styleProvider = new StyleProvider(attributes.get("style")); - - final ImageSize.Dimension width = parseDimension(extractDimension("width", attributes, styleProvider)); - final ImageSize.Dimension height = parseDimension(extractDimension("height", attributes, styleProvider)); - - if (width == null - && height == null) { - imageSize = null; - } else { - imageSize = new ImageSize(width, height); - } - - return imageSize; - } - - @Nullable - private static String extractDimension(@NonNull String name, @NonNull Map attributes, @NonNull StyleProvider styleProvider) { - - final String out; - - final String inline = attributes.get(name); - if (!TextUtils.isEmpty(inline)) { - out = inline; - } else { - out = extractDimensionFromStyle(name, styleProvider); - } - - return out; - } - - @Nullable - private static String extractDimensionFromStyle(@NonNull String name, @NonNull StyleProvider styleProvider) { - return styleProvider.attributes().get(name); - } - - @Nullable - private static ImageSize.Dimension parseDimension(@Nullable String raw) { - - // a set of digits, then dimension unit (allow floating) - - final ImageSize.Dimension dimension; - - final int length = raw != null - ? raw.length() - : 0; - - if (length == 0) { - dimension = null; - } else { - - // first digit to find -> unit is finished (can be null) - - int index = -1; - - for (int i = length - 1; i >= 0; i--) { - if (Character.isDigit(raw.charAt(i))) { - index = i; - break; - } - } - - // no digits -> no dimension - if (index == -1) { - dimension = null; - } else { - - final String value; - final String unit; - - // no unit is specified - if (index == length - 1) { - value = raw; - unit = null; - } else { - value = raw.substring(0, index + 1); - unit = raw.substring(index + 1); - } - - ImageSize.Dimension inner; - try { - final float floatValue = Float.parseFloat(value); - inner = new ImageSize.Dimension(floatValue, unit); - } catch (NumberFormatException e) { - inner = null; - } - - dimension = inner; - } - } - - return dimension; - } - - private static class StyleProvider { - - private final String style; - private Map attributes; - - StyleProvider(@Nullable String style) { - this.style = style; - } - - @NonNull - Map attributes() { - final Map out; - if (attributes != null) { - out = attributes; - } else { - if (TextUtils.isEmpty(style)) { - out = attributes = Collections.emptyMap(); - } else { - final String[] split = style.split(";"); - final Map map = new HashMap<>(split.length); - String[] parts; - for (String s : split) { - if (!TextUtils.isEmpty(s)) { - parts = s.split(":"); - if (parts.length == 2) { - map.put(parts[0].trim(), parts[1].trim()); - } - } - } - out = attributes = map; - } - } - return out; - } - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/ItalicsProvider.java b/library/src/main/java/ru/noties/markwon/renderer/html/ItalicsProvider.java deleted file mode 100644 index 51f33ff7..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/ItalicsProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; - -import ru.noties.markwon.SpannableFactory; - -class ItalicsProvider implements SpannableHtmlParser.SpanProvider { - - private final SpannableFactory factory; - - /** - * @since 1.1.0 - */ - ItalicsProvider(@NonNull SpannableFactory factory) { - this.factory = factory; - } - - @Override - public Object provide(@NonNull SpannableHtmlParser.Tag tag) { - return factory.emphasis(); - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/LinkProvider.java b/library/src/main/java/ru/noties/markwon/renderer/html/LinkProvider.java deleted file mode 100644 index d15668bd..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/LinkProvider.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; -import android.text.TextUtils; - -import java.util.Map; - -import ru.noties.markwon.SpannableFactory; -import ru.noties.markwon.UrlProcessor; -import ru.noties.markwon.spans.LinkSpan; -import ru.noties.markwon.spans.SpannableTheme; - -class LinkProvider implements SpannableHtmlParser.SpanProvider { - - private final SpannableFactory factory; - private final SpannableTheme theme; - private final UrlProcessor urlProcessor; - private final LinkSpan.Resolver resolver; - - LinkProvider( - @NonNull SpannableFactory factory, - @NonNull SpannableTheme theme, - @NonNull UrlProcessor urlProcessor, - @NonNull LinkSpan.Resolver resolver) { - this.factory = factory; - this.theme = theme; - this.urlProcessor = urlProcessor; - this.resolver = resolver; - } - - @Override - public Object provide(@NonNull SpannableHtmlParser.Tag tag) { - - final Object span; - - final Map attributes = tag.attributes(); - final String href = attributes.get("href"); - if (!TextUtils.isEmpty(href)) { - - final String destination = urlProcessor.process(href); - span = factory.link(theme, destination, resolver); - - } else { - span = null; - } - - return span; - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java b/library/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java deleted file mode 100644 index 118e0a1c..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java +++ /dev/null @@ -1,332 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.annotation.TargetApi; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.Html; -import android.text.Spanned; - -import java.util.HashMap; -import java.util.Map; - -import ru.noties.markwon.LinkResolverDef; -import ru.noties.markwon.SpannableFactory; -import ru.noties.markwon.UrlProcessor; -import ru.noties.markwon.UrlProcessorNoOp; -import ru.noties.markwon.renderer.ImageSizeResolver; -import ru.noties.markwon.renderer.ImageSizeResolverDef; -import ru.noties.markwon.spans.AsyncDrawable; -import ru.noties.markwon.spans.LinkSpan; -import ru.noties.markwon.spans.SpannableTheme; - -@SuppressWarnings("WeakerAccess") -public class SpannableHtmlParser { - - /** - * @since 1.1.0 - */ - @NonNull - public static SpannableHtmlParser create( - @NonNull SpannableFactory factory, - @NonNull SpannableTheme theme, - @NonNull AsyncDrawable.Loader loader, - @NonNull UrlProcessor urlProcessor, - @NonNull LinkSpan.Resolver resolver, - @NonNull ImageSizeResolver imageSizeResolver - ) { - return builderWithDefaults(factory, theme, loader, urlProcessor, resolver, imageSizeResolver).build(); - } - - @NonNull - public static Builder builder() { - return new Builder(); - } - - /** - * @since 1.1.0 - */ - @NonNull - public static Builder builderWithDefaults(@NonNull SpannableFactory factory, @NonNull SpannableTheme theme) { - return builderWithDefaults( - factory, - theme, - null, - null, - null, - null); - } - - /** - * Updated in 1.0.1: added imageSizeResolverArgument - * Updated in 1.1.0: add SpannableFactory - */ - @NonNull - public static Builder builderWithDefaults( - @NonNull SpannableFactory factory, - @NonNull SpannableTheme theme, - @Nullable AsyncDrawable.Loader asyncDrawableLoader, - @Nullable UrlProcessor urlProcessor, - @Nullable LinkSpan.Resolver resolver, - @Nullable ImageSizeResolver imageSizeResolver - ) { - - if (urlProcessor == null) { - urlProcessor = new UrlProcessorNoOp(); - } - - if (resolver == null) { - resolver = new LinkResolverDef(); - } - - final BoldProvider boldProvider = new BoldProvider(factory); - final ItalicsProvider italicsProvider = new ItalicsProvider(factory); - final StrikeProvider strikeProvider = new StrikeProvider(factory); - - final ImageProvider imageProvider; - if (asyncDrawableLoader != null) { - - if (imageSizeResolver == null) { - imageSizeResolver = new ImageSizeResolverDef(); - } - - imageProvider = new ImageProviderImpl( - factory, - theme, - asyncDrawableLoader, - urlProcessor, - imageSizeResolver); - } else { - imageProvider = null; - } - - return new Builder() - .simpleTag("b", boldProvider) - .simpleTag("strong", boldProvider) - .simpleTag("i", italicsProvider) - .simpleTag("em", italicsProvider) - .simpleTag("cite", italicsProvider) - .simpleTag("dfn", italicsProvider) - .simpleTag("sup", new SuperScriptProvider(factory, theme)) - .simpleTag("sub", new SubScriptProvider(factory, theme)) - .simpleTag("u", new UnderlineProvider(factory)) - .simpleTag("del", strikeProvider) - .simpleTag("s", strikeProvider) - .simpleTag("strike", strikeProvider) - .simpleTag("a", new LinkProvider(factory, theme, urlProcessor, resolver)) - .imageProvider(imageProvider); - } - - // for simple tags without arguments - // , , etc - public interface SpanProvider { - Object provide(@NonNull Tag tag); - } - - public interface ImageProvider { - Spanned provide(@NonNull Tag tag); - } - - public interface HtmlParser { - - // returns span for a simple content - Object getSpan(@NonNull String html); - - Spanned parse(@NonNull String html); - } - - private final Map simpleTags; - private final ImageProvider imageProvider; - private final HtmlParser parser; - private final TagParser tagParser; - - private SpannableHtmlParser(Builder builder) { - this.simpleTags = builder.simpleTags; - this.imageProvider = builder.imageProvider; - this.parser = builder.parser; - this.tagParser = new TagParser(); - } - - @Nullable - public Tag parseTag(String html) { - return tagParser.parse(html); - } - - @Nullable - public Object getSpanForTag(@NonNull Tag tag) { - - // check if we have specific handler for tag.name - - final Object out; - - final SpanProvider provider = simpleTags.get(tag.name); - if (provider != null) { - out = provider.provide(tag); - } else { - // let's prepare mock content & extract spans from it - // actual content doesn't matter, here it's just `abc` - final String mock = tag.raw + "abc" + ""; - out = parser.getSpan(mock); - } - - return out; - } - - // if tag is NULL, then it's HtmlBlock... else just a void tag - public Spanned getSpanned(@Nullable Tag tag, String html) { - final Spanned spanned; - if (tag != null && "img".equals(tag.name) && imageProvider != null) { - spanned = imageProvider.provide(tag); - } else { - spanned = parser.parse(html); - } - return spanned; - } - - public static class Builder { - - private final Map simpleTags = new HashMap<>(3); - - private ImageProvider imageProvider; - private HtmlParser parser; - - @NonNull - Builder simpleTag(@NonNull String tag, @NonNull SpanProvider provider) { - simpleTags.put(tag, provider); - return this; - } - - @NonNull - public Builder imageProvider(@Nullable ImageProvider imageProvider) { - this.imageProvider = imageProvider; - return this; - } - - @NonNull - public Builder parser(@NonNull HtmlParser parser) { - this.parser = parser; - return this; - } - - @NonNull - public SpannableHtmlParser build() { - if (parser == null) { - parser = DefaultHtmlParser.create(); - } - return new SpannableHtmlParser(this); - } - } - - public static class Tag { - - private final String raw; - private final String name; - private final Map attributes; - - private final boolean opening; - private final boolean voidTag; - - public Tag(String raw, String name, @NonNull Map attributes, boolean opening, boolean voidTag) { - this.raw = raw; - this.name = name; - this.attributes = attributes; - this.opening = opening; - this.voidTag = voidTag; - } - - public String raw() { - return raw; - } - - public String name() { - return name; - } - - @NonNull - public Map attributes() { - return attributes; - } - - public boolean opening() { - return opening; - } - - public boolean voidTag() { - return voidTag; - } - - @Override - public String toString() { - return "Tag{" + - "raw='" + raw + '\'' + - ", name='" + name + '\'' + - ", attributes=" + attributes + - ", opening=" + opening + - ", voidTag=" + voidTag + - '}'; - } - } - - public static abstract class DefaultHtmlParser implements HtmlParser { - - public static DefaultHtmlParser create() { - final DefaultHtmlParser parser; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - parser = new Parser24(); - } else { - parser = new ParserPre24(); - } - return parser; - } - - Object getSpan(Spanned spanned) { - - final Object out; - - final Object[] spans; - final int length = spanned != null ? spanned.length() : 0; - if (length == 0) { - spans = null; - } else { - spans = spanned.getSpans(0, length, Object.class); - } - - if (spans != null - && spans.length > 0) { - out = spans[0]; - } else { - out = null; - } - - return out; - } - - @SuppressWarnings("deprecation") - private static class ParserPre24 extends DefaultHtmlParser { - - @Override - public Object getSpan(@NonNull String html) { - return getSpan(parse(html)); - } - - @Override - public Spanned parse(@NonNull String html) { - return Html.fromHtml(html, null, null); - } - } - - @TargetApi(Build.VERSION_CODES.N) - private static class Parser24 extends DefaultHtmlParser { - - @Override - public Object getSpan(@NonNull String html) { - return getSpan(parse(html)); - } - - @Override - public Spanned parse(@NonNull String html) { - return Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT, null, null); - } - } - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/StrikeProvider.java b/library/src/main/java/ru/noties/markwon/renderer/html/StrikeProvider.java deleted file mode 100644 index c2d3fbc8..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/StrikeProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; - -import ru.noties.markwon.SpannableFactory; - -class StrikeProvider implements SpannableHtmlParser.SpanProvider { - - private final SpannableFactory factory; - - /** - * @since 1.1.0 - */ - StrikeProvider(@NonNull SpannableFactory factory) { - this.factory = factory; - } - - @Override - public Object provide(@NonNull SpannableHtmlParser.Tag tag) { - return factory.strikethrough(); - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/SubScriptProvider.java b/library/src/main/java/ru/noties/markwon/renderer/html/SubScriptProvider.java deleted file mode 100644 index 451ae299..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/SubScriptProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; - -import ru.noties.markwon.SpannableFactory; -import ru.noties.markwon.spans.SpannableTheme; - -class SubScriptProvider implements SpannableHtmlParser.SpanProvider { - - private final SpannableFactory factory; - private final SpannableTheme theme; - - SubScriptProvider(@NonNull SpannableFactory factory, @NonNull SpannableTheme theme) { - this.factory = factory; - this.theme = theme; - } - - @Override - public Object provide(@NonNull SpannableHtmlParser.Tag tag) { - return factory.subScript(theme); - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/SuperScriptProvider.java b/library/src/main/java/ru/noties/markwon/renderer/html/SuperScriptProvider.java deleted file mode 100644 index db747e3d..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/SuperScriptProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; - -import ru.noties.markwon.SpannableFactory; -import ru.noties.markwon.spans.SpannableTheme; - -class SuperScriptProvider implements SpannableHtmlParser.SpanProvider { - - private final SpannableFactory factory; - private final SpannableTheme theme; - - SuperScriptProvider(@NonNull SpannableFactory factory, @NonNull SpannableTheme theme) { - this.factory = factory; - this.theme = theme; - } - - @Override - public Object provide(@NonNull SpannableHtmlParser.Tag tag) { - return factory.superScript(theme); - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/TagParser.java b/library/src/main/java/ru/noties/markwon/renderer/html/TagParser.java deleted file mode 100644 index 8d9b57d9..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/TagParser.java +++ /dev/null @@ -1,155 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.Nullable; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -class TagParser { - - - private static final Set VOID_TAGS; - static { - final String[] tags = { - "area", "base", "br", "col", "embed", "hr", "img", "input", - "keygen", "link", "meta", "param", "source", "track", "wbr" - }; - final Set set = new HashSet<>(tags.length); - Collections.addAll(set, tags); - VOID_TAGS = Collections.unmodifiableSet(set); - } - - - TagParser() { - } - - @Nullable - SpannableHtmlParser.Tag parse(String html) { - - final SpannableHtmlParser.Tag tag; - - final int length = html != null - ? html.length() - : 0; - - // absolutely minimum (``) - if (length < 3) { - tag = null; - } else { - -// // okay, we will consider a tag a void one if it's in our void list tag - - final boolean closing = '<' == html.charAt(0) && '/' == html.charAt(1); - final boolean voidTag; - - Map attributes = null; - - final StringBuilder builder = new StringBuilder(); - - String name = null; - String pendingAttribute = null; - - char c; - char valueDelimiter = '\0'; - - for (int i = 0; i < length; i++) { - - c = html.charAt(i); - - // no more handling - if ('>' == c - || '\\' == c) { - break; - } - - if (name == null) { - if (Character.isSpaceChar(c)) { - //noinspection StatementWithEmptyBody - if (builder.length() == 0) { - // ignore it, we must wait until we have tagName - } else { - - name = builder.toString(); - - // clear buffer - builder.setLength(0); - } - } else { - if (Character.isLetterOrDigit(c)) { - builder.append(c); - } /*else { - // we allow non-letter-digit only if builder.length == 0 - // if we have already started - }*/ - } - } else if (pendingAttribute == null) { - // we start checking for attribute - // ignore non-letter-digits before - if (Character.isLetterOrDigit(c)) { - builder.append(c); - } else /*if ('=' == c)*/ { - - // attribute name is finished (only if we have already added something) - // else it's trailing chars that we are not interested in - if (builder.length() > 0) { - pendingAttribute = builder.toString(); - builder.setLength(0); - } - } - } else { - // first char that we will meet will be the delimiter - if (valueDelimiter == '\0') { - valueDelimiter = c; - } else { - if (c == valueDelimiter) { - if (attributes == null) { - attributes = new HashMap<>(3); - } - attributes.put(pendingAttribute, builder.toString()); - pendingAttribute = null; - valueDelimiter = '\0'; - builder.setLength(0); - } else { - builder.append(c); - } - } - } - } - - if (builder.length() > 0) { - if (name == null) { - name = builder.toString(); - } else if (pendingAttribute != null) { - if (attributes == null) { - attributes = new HashMap<>(3); - } - attributes.put(pendingAttribute, builder.toString()); - } - } - - // in case of wrong parsing - if (name == null) { - tag = null; - } else { - - voidTag = !closing && VOID_TAGS.contains(name); - - final Map attributesMap; - if (attributes == null - || attributes.size() == 0) { - //noinspection unchecked - attributesMap = Collections.EMPTY_MAP; - } else { - attributesMap = Collections.unmodifiableMap(attributes); - } - - tag = new SpannableHtmlParser.Tag(html, name, attributesMap, !closing, voidTag); - } - } - - return tag; - } -} diff --git a/library/src/main/java/ru/noties/markwon/renderer/html/UnderlineProvider.java b/library/src/main/java/ru/noties/markwon/renderer/html/UnderlineProvider.java deleted file mode 100644 index f44fc913..00000000 --- a/library/src/main/java/ru/noties/markwon/renderer/html/UnderlineProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.noties.markwon.renderer.html; - -import android.support.annotation.NonNull; - -import ru.noties.markwon.SpannableFactory; - -class UnderlineProvider implements SpannableHtmlParser.SpanProvider { - - private final SpannableFactory factory; - - /** - * @since 1.1.0 - */ - UnderlineProvider(@NonNull SpannableFactory factory) { - this.factory = factory; - } - - @Override - public Object provide(@NonNull SpannableHtmlParser.Tag tag) { - return factory.underline(); - } -} diff --git a/markwon-html-parser-api/build.gradle b/markwon-html-parser-api/build.gradle new file mode 100644 index 00000000..8e38acbb --- /dev/null +++ b/markwon-html-parser-api/build.gradle @@ -0,0 +1,23 @@ +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 { + + deps.with { + api it['support-annotations'] + } +} + +registerArtifact(this) diff --git a/markwon-html-parser-api/gradle.properties b/markwon-html-parser-api/gradle.properties new file mode 100644 index 00000000..5be4658d --- /dev/null +++ b/markwon-html-parser-api/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Markwon +POM_ARTIFACT_ID=markwon-html-parser-api +POM_PACKAGING=aar \ No newline at end of file diff --git a/markwon-html-parser-api/src/main/AndroidManifest.xml b/markwon-html-parser-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..872543b3 --- /dev/null +++ b/markwon-html-parser-api/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java new file mode 100644 index 00000000..f3245876 --- /dev/null +++ b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java @@ -0,0 +1,94 @@ +package ru.noties.markwon.html.api; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * @see Inline + * @see Block + * @since 2.0.0 + */ +public interface HtmlTag { + + int NO_END = -1; + + /** + * @return normalized tag name (lower-case) + */ + @NonNull + String name(); + + /** + * @return index at which this tag starts + */ + int start(); + + /** + * @return index at which this tag ends + */ + int end(); + + /** + * @return flag indicating if this tag has no content (when start == end) + */ + boolean isEmpty(); + + /** + * @return flag indicating if this tag is closed (has valid start and end) + * @see #NO_END + */ + boolean isClosed(); + + @NonNull + Map attributes(); + + /** + * @see Inline + */ + boolean isInline(); + + /** + * @see Block + */ + boolean isBlock(); + + @NonNull + Inline getAsInline(); + + @NonNull + Block getAsBlock(); + + /** + * Represents really inline HTML tags (unlile commonmark definitions) + */ + interface Inline extends HtmlTag { + } + + /** + * Represents HTML block tags. Please note that all tags that are not inline should be + * considered as block tags + */ + interface Block extends HtmlTag { + + /** + * @return parent {@link Block} or null if there is no parent (this block is at root level) + */ + @Nullable + Block parent(); + + /** + * @return list of children + */ + @NonNull + List children(); + + /** + * @return a flag indicating if this {@link Block} is at the root level (shortcut to calling: + * {@code parent() == null} + */ + boolean isRoot(); + } +} diff --git a/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java new file mode 100644 index 00000000..8d168a72 --- /dev/null +++ b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java @@ -0,0 +1,60 @@ +package ru.noties.markwon.html.api; + +import android.support.annotation.NonNull; + +import java.util.List; + +/** + * @since 2.0.0 + */ +public abstract class MarkwonHtmlParser { + + /** + * Factory method to create a `no-op` implementation (no parsing) + */ + @NonNull + public static MarkwonHtmlParser noOp() { + return new MarkwonHtmlParserNoOp(); + } + + public interface FlushAction { + void apply(@NonNull List tags); + } + + public abstract void processFragment( + @NonNull T output, + @NonNull String htmlFragment); + + /** + * After this method exists a {@link MarkwonHtmlParser} will clear internal state for stored tags. + * If you wish to process them further after this method exists create own copy of supplied + * collection. + * + * @param documentLength known document length. This value is used to close all non-closed tags. + * If you wish to keep them open (do not force close at the end of a + * document pass here {@link HtmlTag#NO_END}. Later non-closed tags + * can be detected by calling {@link HtmlTag#isClosed()} + * @param action {@link FlushAction} to be called with resulting tags ({@link ru.noties.markwon.html.api.HtmlTag.Inline}) + */ + public abstract void flushInlineTags( + int documentLength, + @NonNull FlushAction action); + + /** + * After this method exists a {@link MarkwonHtmlParser} will clear internal state for stored tags. + * If you wish to process them further after this method exists create own copy of supplied + * collection. + * + * @param documentLength known document length. This value is used to close all non-closed tags. + * If you wish to keep them open (do not force close at the end of a + * document pass here {@link HtmlTag#NO_END}. Later non-closed tags + * can be detected by calling {@link HtmlTag#isClosed()} + * @param action {@link FlushAction} to be called with resulting tags ({@link ru.noties.markwon.html.api.HtmlTag.Block}) + */ + public abstract void flushBlockTags( + int documentLength, + @NonNull FlushAction action); + + public abstract void reset(); + +} diff --git a/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParserNoOp.java b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParserNoOp.java new file mode 100644 index 00000000..0a024865 --- /dev/null +++ b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParserNoOp.java @@ -0,0 +1,32 @@ +package ru.noties.markwon.html.api; + +import android.support.annotation.NonNull; + +import java.util.Collections; + +/** + * @see MarkwonHtmlParser + * @since 2.0.0 + */ +class MarkwonHtmlParserNoOp extends MarkwonHtmlParser { + + @Override + public void processFragment(@NonNull T output, @NonNull String htmlFragment) { + + } + + @Override + public void flushInlineTags(int documentLength, @NonNull FlushAction action) { + action.apply(Collections.emptyList()); + } + + @Override + public void flushBlockTags(int documentLength, @NonNull FlushAction action) { + action.apply(Collections.emptyList()); + } + + @Override + public void reset() { + + } +} diff --git a/markwon-html-parser-impl/build.gradle b/markwon-html-parser-impl/build.gradle new file mode 100644 index 00000000..2af46373 --- /dev/null +++ b/markwon-html-parser-impl/build.gradle @@ -0,0 +1,31 @@ +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-html-parser-api') + + deps.with { + api it['support-annotations'] + api it['commonmark'] + } + + deps.test.with { + testImplementation it['junit'] + testImplementation it['robolectric'] + } +} + +registerArtifact(this) diff --git a/markwon-html-parser-impl/gradle.properties b/markwon-html-parser-impl/gradle.properties new file mode 100644 index 00000000..5db86376 --- /dev/null +++ b/markwon-html-parser-impl/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Markwon +POM_ARTIFACT_ID=markwon-html-parser-impl +POM_PACKAGING=aar \ No newline at end of file diff --git a/markwon-html-parser-impl/src/main/AndroidManifest.xml b/markwon-html-parser-impl/src/main/AndroidManifest.xml new file mode 100644 index 00000000..14bee867 --- /dev/null +++ b/markwon-html-parser-impl/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/AppendableUtils.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/AppendableUtils.java new file mode 100644 index 00000000..39b6bf73 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/AppendableUtils.java @@ -0,0 +1,35 @@ +package ru.noties.markwon.html.impl; + +import android.support.annotation.NonNull; + +import java.io.IOException; + +abstract class AppendableUtils { + + static void appendQuietly(@NonNull Appendable appendable, char c) { + try { + appendable.append(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static void appendQuietly(@NonNull Appendable appendable, @NonNull CharSequence cs) { + try { + appendable.append(cs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static void appendQuietly(@NonNull Appendable appendable, @NonNull CharSequence cs, int start, int end) { + try { + appendable.append(cs, start, end); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private AppendableUtils() { + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java new file mode 100644 index 00000000..c0d304dc --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java @@ -0,0 +1,55 @@ +package ru.noties.markwon.html.impl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.html.api.HtmlTag; + +/** + * This class will be used to append some text to output in order to + * apply a Span for this tag. Please note that this class will be used for + * _void_ tags and tags that are self-closed (even if HTML spec doesn\'t specify + * a tag as self-closed). This is due to the fact that underlying parser does not + * validate context and does not check if a tag is correctly used. Plus it will be + * used for tags without content, for example: {@code } + * + * @since 2.0.0 + */ +public class HtmlEmptyTagReplacement { + + @NonNull + public static HtmlEmptyTagReplacement create() { + return new HtmlEmptyTagReplacement(); + } + + private static final String IMG_REPLACEMENT = "\uFFFC"; + + /** + * @return replacement for supplied startTag or null if no replacement should occur (which will + * lead to `Inline` tag have start & end the same value, thus not applicable for applying a Span) + */ + @Nullable + public String replace(@NonNull HtmlTag tag) { + + final String replacement; + + final String name = tag.name(); + + if ("br".equals(name)) { + replacement = "\n"; + } else if ("img".equals(name)) { + final String alt = tag.attributes().get("alt"); + if (alt == null + || alt.length() == 0) { + // no alt is provided + replacement = IMG_REPLACEMENT; + } else { + replacement = alt; + } + } else { + replacement = null; + } + + return replacement; + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java new file mode 100644 index 00000000..01466106 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java @@ -0,0 +1,210 @@ +package ru.noties.markwon.html.impl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import ru.noties.markwon.html.api.HtmlTag; + +abstract class HtmlTagImpl implements HtmlTag { + + final String name; + final int start; + final Map attributes; + int end = NO_END; + + protected HtmlTagImpl(@NonNull String name, int start, @NonNull Map attributes) { + this.name = name; + this.start = start; + this.attributes = attributes; + } + + @NonNull + @Override + public String name() { + return name; + } + + @Override + public int start() { + return start; + } + + @Override + public int end() { + return end; + } + + @Override + public boolean isEmpty() { + return start == end; + } + + @NonNull + @Override + public Map attributes() { + return attributes; + } + + @Override + public boolean isClosed() { + return end > NO_END; + } + + abstract void closeAt(int end); + + + static class InlineImpl extends HtmlTagImpl implements Inline { + + InlineImpl(@NonNull String name, int start, @NonNull Map attributes) { + super(name, start, attributes); + } + + @Override + void closeAt(int end) { + if (!isClosed()) { + super.end = end; + } + } + + @Override + public String toString() { + return "InlineImpl{" + + "name='" + name + '\'' + + ", start=" + start + + ", end=" + end + + ", attributes=" + attributes + + '}'; + } + + @Override + public boolean isInline() { + return true; + } + + @Override + public boolean isBlock() { + return false; + } + + @NonNull + @Override + public Inline getAsInline() { + return this; + } + + @NonNull + @Override + public Block getAsBlock() { + throw new ClassCastException("Cannot cast Inline instance to Block"); + } + } + + static class BlockImpl extends HtmlTagImpl implements Block { + + @NonNull + static BlockImpl root() { + return new BlockImpl("", 0, Collections.emptyMap(), null); + } + + @NonNull + static BlockImpl create( + @NonNull String name, + int start, + @NonNull Map attributes, + @Nullable BlockImpl parent) { + return new BlockImpl(name, start, attributes, parent); + } + + final BlockImpl parent; + List children; + + @SuppressWarnings("NullableProblems") + BlockImpl( + @NonNull String name, + int start, + @NonNull Map attributes, + @Nullable BlockImpl parent) { + super(name, start, attributes); + this.parent = parent; + } + + @Override + void closeAt(int end) { + if (!isClosed()) { + super.end = end; + if (children != null) { + for (BlockImpl child : children) { + child.closeAt(end); + } + } + } + } + + @Override + public boolean isRoot() { + return parent == null; + } + + @Nullable + @Override + public Block parent() { + return parent; + } + + @NonNull + @Override + public List children() { + final List list; + if (children == null) { + list = Collections.emptyList(); + } else { + list = Collections.unmodifiableList((List) children); + } + return list; + } + + @NonNull + @Override + public Map attributes() { + return attributes; + } + + @Override + public boolean isInline() { + return false; + } + + @Override + public boolean isBlock() { + return true; + } + + @NonNull + @Override + public Inline getAsInline() { + throw new ClassCastException("Cannot cast Block instance to Inline"); + } + + @NonNull + @Override + public Block getAsBlock() { + return this; + } + + @Override + public String toString() { + return "BlockImpl{" + + "name='" + name + '\'' + + ", start=" + start + + ", end=" + end + + ", attributes=" + attributes + + ", parent=" + (parent != null ? parent.name : null) + + ", children=" + children + + '}'; + } + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java new file mode 100644 index 00000000..86f985a2 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java @@ -0,0 +1,486 @@ +package ru.noties.markwon.html.impl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import ru.noties.markwon.html.api.HtmlTag; +import ru.noties.markwon.html.api.HtmlTag.Block; +import ru.noties.markwon.html.api.HtmlTag.Inline; +import ru.noties.markwon.html.api.MarkwonHtmlParser; +import ru.noties.markwon.html.impl.jsoup.nodes.Attribute; +import ru.noties.markwon.html.impl.jsoup.nodes.Attributes; +import ru.noties.markwon.html.impl.jsoup.parser.CharacterReader; +import ru.noties.markwon.html.impl.jsoup.parser.ParseErrorList; +import ru.noties.markwon.html.impl.jsoup.parser.Token; +import ru.noties.markwon.html.impl.jsoup.parser.Tokeniser; + +import static ru.noties.markwon.html.impl.AppendableUtils.appendQuietly; + +/** + * @since 2.0.0 + */ +public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { + + @NonNull + public static MarkwonHtmlParserImpl create() { + return create(HtmlEmptyTagReplacement.create()); + } + + @NonNull + public static MarkwonHtmlParserImpl create(@NonNull HtmlEmptyTagReplacement inlineTagReplacement) { + return new MarkwonHtmlParserImpl(inlineTagReplacement, TrimmingAppender.create()); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements + @VisibleForTesting + static final Set INLINE_TAGS; + + private static final Set VOID_TAGS; + + // these are the tags that are considered _block_ ones + // this parser will ensure that these blocks are started on a new line + // other tags that are NOT inline are considered as block tags, but won't have new line + // inserted before them + // https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements + private static final Set BLOCK_TAGS; + + private static final String TAG_PARAGRAPH = "p"; + private static final String TAG_LIST_ITEM = "li"; + + static { + INLINE_TAGS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "a", "abbr", "acronym", + "b", "bdo", "big", "br", "button", + "cite", "code", + "dfn", + "em", + "i", "img", "input", + "kbd", + "label", + "map", + "object", + "q", + "samp", "script", "select", "small", "span", "strong", "sub", "sup", + "textarea", "time", "tt", + "var" + ))); + VOID_TAGS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "area", + "base", "br", + "col", + "embed", + "hr", + "img", "input", + "keygen", + "link", + "meta", + "param", + "source", + "track", + "wbr" + ))); + BLOCK_TAGS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "address", "article", "aside", + "blockquote", + "canvas", + "dd", "div", "dl", "dt", + "fieldset", "figcaption", "figure", "footer", "form", + "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", + "li", + "main", + "nav", "noscript", + "ol", "output", + "p", "pre", + "section", + "table", "tfoot", + "ul", + "video" + ))); + } + + private final HtmlEmptyTagReplacement emptyTagReplacement; + + private final TrimmingAppender trimmingAppender; + + private final List inlineTags = new ArrayList<>(0); + + private HtmlTagImpl.BlockImpl currentBlock = HtmlTagImpl.BlockImpl.root(); + + private boolean isInsidePreTag; + + // the thing is: we ensure a new line BEFORE block tag + // but not after, so another tag will be placed on the same line (which is wrong) + private boolean previousIsBlock; + + + MarkwonHtmlParserImpl( + @NonNull HtmlEmptyTagReplacement replacement, + @NonNull TrimmingAppender trimmingAppender) { + this.emptyTagReplacement = replacement; + this.trimmingAppender = trimmingAppender; + } + + @Override + public void processFragment( + @NonNull T output, + @NonNull String htmlFragment) { + + // we might want to reuse tokeniser (at least when the same output is involved) + // as CharacterReader does a bit of initialization (cache etc) as it's + // primary usage is parsing a document in one run (not parsing _fragments_) + final Tokeniser tokeniser = new Tokeniser(new CharacterReader(htmlFragment), ParseErrorList.noTracking()); + + while (true) { + + final Token token = tokeniser.read(); + final Token.TokenType tokenType = token.type; + + if (Token.TokenType.EOF == tokenType) { + break; + } + + switch (tokenType) { + + case StartTag: { + + final Token.StartTag startTag = (Token.StartTag) token; + + if (isInlineTag(startTag.normalName)) { + processInlineTagStart(output, startTag); + } else { + processBlockTagStart(output, startTag); + } + } + break; + + case EndTag: { + + final Token.EndTag endTag = (Token.EndTag) token; + + if (isInlineTag(endTag.normalName)) { + processInlineTagEnd(output, endTag); + } else { + processBlockTagEnd(output, endTag); + } + } + break; + + case Character: { + processCharacter(output, ((Token.Character) token)); + } + break; + } + + // do not forget to reset processed token (even if it's not processed) + token.reset(); + } + } + + @Override + public void flushInlineTags(int documentLength, @NonNull FlushAction action) { + if (inlineTags.size() > 0) { + + if (documentLength > HtmlTag.NO_END) { + for (HtmlTagImpl.InlineImpl inline : inlineTags) { + inline.closeAt(documentLength); + } + } + + //noinspection unchecked + action.apply(Collections.unmodifiableList((List) inlineTags)); + inlineTags.clear(); + } else { + action.apply(Collections.emptyList()); + } + } + + @Override + public void flushBlockTags(int documentLength, @NonNull FlushAction action) { + + HtmlTagImpl.BlockImpl block = currentBlock; + while (block.parent != null) { + block = block.parent; + } + + if (documentLength > HtmlTag.NO_END) { + block.closeAt(documentLength); + } + + final List children = block.children(); + if (children.size() > 0) { + action.apply(children); + } else { + action.apply(Collections.emptyList()); + } + + currentBlock = HtmlTagImpl.BlockImpl.root(); + } + + @Override + public void reset() { + inlineTags.clear(); + currentBlock = HtmlTagImpl.BlockImpl.root(); + } + + + protected void processInlineTagStart( + @NonNull T output, + @NonNull Token.StartTag startTag) { + + final String name = startTag.normalName; + + final HtmlTagImpl.InlineImpl inline = new HtmlTagImpl.InlineImpl(name, output.length(), extractAttributes(startTag)); + + ensureNewLineIfPreviousWasBlock(output); + + if (isVoidTag(name) + || startTag.selfClosing) { + + final String replacement = emptyTagReplacement.replace(inline); + if (replacement != null + && replacement.length() > 0) { + appendQuietly(output, replacement); + } + + // the thing is: we will keep this inline tag in the list, + // but in case of void-tag that has no replacement, there will be no + // possibility to set a span (requires at least one char) + inline.closeAt(output.length()); + } + + inlineTags.add(inline); + } + + protected void processInlineTagEnd( + @NonNull T output, + @NonNull Token.EndTag endTag) { + + // try to find it, if none found -> ignore + final HtmlTagImpl.InlineImpl openInline = findOpenInlineTag(endTag.normalName); + if (openInline != null) { + + // okay, if this tag is empty -> call replacement + if (isEmpty(output, openInline)) { + appendEmptyTagReplacement(output, openInline); + } + + // close open inline tag + openInline.closeAt(output.length()); + } + } + + + protected void processBlockTagStart( + @NonNull T output, + @NonNull Token.StartTag startTag) { + + final String name = startTag.normalName; + + // block tags (all that are NOT inline -> blocks + // there is only one strong rule -> paragraph cannot contain anything + // except inline tags + + if (TAG_PARAGRAPH.equals(currentBlock.name)) { + // it must be closed here not matter what we are as here we _assume_ + // that it's a block tag + currentBlock.closeAt(output.length()); + appendQuietly(output, '\n'); + currentBlock = currentBlock.parent; + } else if (TAG_LIST_ITEM.equals(name) + && TAG_LIST_ITEM.equals(currentBlock.name)) { + // close previous list item if in the same parent + currentBlock.closeAt(output.length()); + currentBlock = currentBlock.parent; + } + + if (isBlockTag(name)) { + isInsidePreTag = "pre".equals(name); + ensureNewLine(output); + } else { + ensureNewLineIfPreviousWasBlock(output); + } + + final int start = output.length(); + + final HtmlTagImpl.BlockImpl block = HtmlTagImpl.BlockImpl.create(name, start, extractAttributes(startTag), currentBlock); + + final boolean isVoid = isVoidTag(name) || startTag.selfClosing; + if (isVoid) { + final String replacement = emptyTagReplacement.replace(block); + if (replacement != null + && replacement.length() > 0) { + appendQuietly(output, replacement); + } + block.closeAt(output.length()); + } + + //noinspection ConstantConditions + appendBlockChild(block.parent, block); + + // if not void start filling-in children + if (!isVoid) { + this.currentBlock = block; + } + } + + protected void processBlockTagEnd( + @NonNull T output, + @NonNull Token.EndTag endTag) { + + final String name = endTag.normalName; + + final HtmlTagImpl.BlockImpl block = findOpenBlockTag(endTag.normalName); + if (block != null) { + + if ("pre".equals(name)) { + isInsidePreTag = false; + } + + // okay, if this tag is empty -> call replacement + if (isEmpty(output, block)) { + appendEmptyTagReplacement(output, block); + } + + block.closeAt(output.length()); + + // if it's empty -> we do no care about if it's block or not + if (!block.isEmpty()) { + previousIsBlock = isBlockTag(block.name); + } + + if (TAG_PARAGRAPH.equals(name)) { + appendQuietly(output, '\n'); + } + + this.currentBlock = block.parent; + } + } + + protected void processCharacter( + @NonNull T output, + @NonNull Token.Character character) { + + // there are tags: BUTTON, INPUT, SELECT, SCRIPT, TEXTAREA, STYLE + // that might have character data that we do not want to display + + if (isInsidePreTag) { + appendQuietly(output, character.getData()); + } else { + ensureNewLineIfPreviousWasBlock(output); + trimmingAppender.append(output, character.getData()); + } + } + + protected void appendBlockChild(@NonNull HtmlTagImpl.BlockImpl parent, @NonNull HtmlTagImpl.BlockImpl child) { + List children = parent.children; + if (children == null) { + children = new ArrayList<>(2); + parent.children = children; + } + children.add(child); + } + + @Nullable + protected HtmlTagImpl.InlineImpl findOpenInlineTag(@NonNull String name) { + + HtmlTagImpl.InlineImpl inline; + + for (int i = inlineTags.size() - 1; i > -1; i--) { + inline = inlineTags.get(i); + if (name.equals(inline.name) + && inline.end < 0) { + return inline; + } + } + + return null; + } + + @Nullable + protected HtmlTagImpl.BlockImpl findOpenBlockTag(@NonNull String name) { + + HtmlTagImpl.BlockImpl blockTag = currentBlock; + + while (blockTag != null + && !name.equals(blockTag.name) && !blockTag.isClosed()) { + blockTag = blockTag.parent; + } + + return blockTag; + } + + protected void ensureNewLineIfPreviousWasBlock(@NonNull T output) { + if (previousIsBlock) { + ensureNewLine(output); + previousIsBlock = false; + } + } + + // name here must lower case + protected static boolean isInlineTag(@NonNull String name) { + return INLINE_TAGS.contains(name); + } + + protected static boolean isVoidTag(@NonNull String name) { + return VOID_TAGS.contains(name); + } + + protected static boolean isBlockTag(@NonNull String name) { + return BLOCK_TAGS.contains(name); + } + + protected static void ensureNewLine(@NonNull T output) { + final int length = output.length(); + if (length > 0 + && '\n' != output.charAt(length - 1)) { + appendQuietly(output, '\n'); + } + } + + @NonNull + protected static Map extractAttributes(@NonNull Token.StartTag startTag) { + + Map map; + + final Attributes attributes = startTag.attributes; + final int size = attributes.size(); + + if (size > 0) { + map = new HashMap<>(size); + for (Attribute attribute : attributes) { + map.put(attribute.getKey().toLowerCase(Locale.US), attribute.getValue()); + } + map = Collections.unmodifiableMap(map); + } else { + map = Collections.emptyMap(); + } + + return map; + } + + protected static boolean isEmpty( + @NonNull T output, + @NonNull HtmlTagImpl tag) { + return tag.start == output.length(); + } + + protected void appendEmptyTagReplacement( + @NonNull T output, + @NonNull HtmlTagImpl tag) { + final String replacement = emptyTagReplacement.replace(tag); + if (replacement != null) { + appendQuietly(output, replacement); + } + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/TrimmingAppender.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/TrimmingAppender.java new file mode 100644 index 00000000..c29c93b9 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/TrimmingAppender.java @@ -0,0 +1,68 @@ +package ru.noties.markwon.html.impl; + +import android.support.annotation.NonNull; + +import static ru.noties.markwon.html.impl.AppendableUtils.appendQuietly; + +abstract class TrimmingAppender { + + abstract void append( + @NonNull T output, + @NonNull String data + ); + + @NonNull + static TrimmingAppender create() { + return new Impl(); + } + + static class Impl extends TrimmingAppender { + + // if data is fully empty (consists of white spaces) -> do not add anything + // leading ws: + // - trim to one space (if at all present) append to output only if previous is ws + // trailing ws: + // - if present trim to single space + + @Override + void append( + @NonNull T output, + @NonNull String data + ) { + + final int startLength = output.length(); + + char c; + + boolean previousIsWhiteSpace = false; + + for (int i = 0, length = data.length(); i < length; i++) { + + c = data.charAt(i); + + if (Character.isWhitespace(c)) { + previousIsWhiteSpace = true; + continue; + } + + if (previousIsWhiteSpace) { + // validate that output has ws as last char + final int outputLength = output.length(); + if (outputLength > 0 + && !Character.isWhitespace(output.charAt(outputLength - 1))) { + appendQuietly(output, ' '); + } + } + + previousIsWhiteSpace = false; + appendQuietly(output, c); + } + + // additionally check if previousIsWhiteSpace is true (if data ended with ws) + // BUT only if we have added something (otherwise the whole data is empty (white)) + if (previousIsWhiteSpace && (startLength < output.length())) { + appendQuietly(output, ' '); + } + } + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/UncheckedIOException.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/UncheckedIOException.java new file mode 100644 index 00000000..c59b725b --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/UncheckedIOException.java @@ -0,0 +1,13 @@ +package ru.noties.markwon.html.impl.jsoup; + +import java.io.IOException; + +public class UncheckedIOException extends RuntimeException { + public UncheckedIOException(IOException cause) { + super(cause); + } + + public IOException ioException() { + return (IOException) getCause(); + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Normalizer.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Normalizer.java new file mode 100644 index 00000000..024e5350 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Normalizer.java @@ -0,0 +1,18 @@ +package ru.noties.markwon.html.impl.jsoup.helper; + +import java.util.Locale; + +/** + * Util methods for normalizing strings. Jsoup internal use only, please don't depend on this API. + */ +public final class Normalizer { + + public static String lowerCase(final String input) { + return input != null ? input.toLowerCase(Locale.ENGLISH) : ""; + } + + public static String normalize(final String input) { + return lowerCase(input).trim(); + } +} + diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Validate.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Validate.java new file mode 100644 index 00000000..e8effd8c --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/helper/Validate.java @@ -0,0 +1,112 @@ +package ru.noties.markwon.html.impl.jsoup.helper; + +/** + * Simple validation methods. Designed for jsoup internal use + */ +public final class Validate { + + private Validate() {} + + /** + * Validates that the object is not null + * @param obj object to test + */ + public static void notNull(Object obj) { + if (obj == null) + throw new IllegalArgumentException("Object must not be null"); + } + + /** + * Validates that the object is not null + * @param obj object to test + * @param msg message to output if validation fails + */ + public static void notNull(Object obj, String msg) { + if (obj == null) + throw new IllegalArgumentException(msg); + } + + /** + * Validates that the value is true + * @param val object to test + */ + public static void isTrue(boolean val) { + if (!val) + throw new IllegalArgumentException("Must be true"); + } + + /** + * Validates that the value is true + * @param val object to test + * @param msg message to output if validation fails + */ + public static void isTrue(boolean val, String msg) { + if (!val) + throw new IllegalArgumentException(msg); + } + + /** + * Validates that the value is false + * @param val object to test + */ + public static void isFalse(boolean val) { + if (val) + throw new IllegalArgumentException("Must be false"); + } + + /** + * Validates that the value is false + * @param val object to test + * @param msg message to output if validation fails + */ + public static void isFalse(boolean val, String msg) { + if (val) + throw new IllegalArgumentException(msg); + } + + /** + * Validates that the array contains no null elements + * @param objects the array to test + */ + public static void noNullElements(Object[] objects) { + noNullElements(objects, "Array must not contain any null objects"); + } + + /** + * Validates that the array contains no null elements + * @param objects the array to test + * @param msg message to output if validation fails + */ + public static void noNullElements(Object[] objects, String msg) { + for (Object obj : objects) + if (obj == null) + throw new IllegalArgumentException(msg); + } + + /** + * Validates that the string is not empty + * @param string the string to test + */ + public static void notEmpty(String string) { + if (string == null || string.length() == 0) + throw new IllegalArgumentException("String must not be empty"); + } + + /** + * Validates that the string is not empty + * @param string the string to test + * @param msg message to output if validation fails + */ + public static void notEmpty(String string, String msg) { + if (string == null || string.length() == 0) + throw new IllegalArgumentException(msg); + } + + /** + Cause a failure. + @param msg message to output. + */ + public static void fail(String msg) { + throw new IllegalArgumentException(msg); + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attribute.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attribute.java new file mode 100644 index 00000000..3ece793d --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attribute.java @@ -0,0 +1,202 @@ +package ru.noties.markwon.html.impl.jsoup.nodes; + +import java.util.Map; + +import ru.noties.markwon.html.impl.jsoup.helper.Validate; + +/** + A single key + value attribute. (Only used for presentation.) + */ +public class Attribute implements Map.Entry, Cloneable { +// private static final String[] booleanAttributes = { +// "allowfullscreen", "async", "autofocus", "checked", "compact", "declare", "default", "defer", "disabled", +// "formnovalidate", "hidden", "inert", "ismap", "itemscope", "multiple", "muted", "nohref", "noresize", +// "noshade", "novalidate", "nowrap", "open", "readonly", "required", "reversed", "seamless", "selected", +// "sortable", "truespeed", "typemustmatch" +// }; + + private String key; + private String val; + Attributes parent; // used to update the holding Attributes when the key / value is changed via this interface + + /** + * Create a new attribute from unencoded (raw) key and value. + * @param key attribute key; case is preserved. + * @param value attribute value + */ + public Attribute(String key, String value) { + this(key, value, null); + } + + /** + * Create a new attribute from unencoded (raw) key and value. + * @param key attribute key; case is preserved. + * @param val attribute value + * @param parent the containing Attributes (this Attribute is not automatically added to said Attributes) + */ + public Attribute(String key, String val, Attributes parent) { + Validate.notNull(key); + this.key = key.trim(); + Validate.notEmpty(key); // trimming could potentially make empty, so validate here + this.val = val; + this.parent = parent; + } + + /** + Get the attribute key. + @return the attribute key + */ + public String getKey() { + return key; + } + + /** + Set the attribute key; case is preserved. + @param key the new key; must not be null + */ + public void setKey(String key) { + Validate.notNull(key); + key = key.trim(); + Validate.notEmpty(key); // trimming could potentially make empty, so validate here + if (parent != null) { + int i = parent.indexOfKey(this.key); + if (i != Attributes.NotFound) + parent.keys[i] = key; + } + this.key = key; + } + + /** + Get the attribute value. + @return the attribute value + */ + public String getValue() { + return val; + } + + /** + Set the attribute value. + @param val the new attribute value; must not be null + */ + public String setValue(String val) { + String oldVal = parent.get(this.key); + if (parent != null) { + int i = parent.indexOfKey(this.key); + if (i != Attributes.NotFound) + parent.vals[i] = val; + } + this.val = val; + return oldVal; + } + +// /** +// Get the HTML representation of this attribute; e.g. {@code href="index.html"}. +// @return HTML +// */ +// public String html() { +// StringBuilder accum = new StringBuilder(); +// +// try { +// html(accum, (new Document("")).outputSettings()); +// } catch(IOException exception) { +// throw new SerializationException(exception); +// } +// return accum.toString(); +// } +// +// protected static void html(String key, String val, Appendable accum, Document.OutputSettings out) throws IOException { +// accum.append(key); +// if (!shouldCollapseAttribute(key, val, out)) { +// accum.append("=\""); +// Entities.escape(accum, Attributes.checkNotNull(val) , out, true, false, false); +// accum.append('"'); +// } +// } +// +// protected void html(Appendable accum, Document.OutputSettings out) throws IOException { +// html(key, val, accum, out); +// } + +// /** +// Get the string representation of this attribute, implemented as {@link #html()}. +// @return string +// */ +// @Override +// public String toString() { +// return html(); +// } + +// /** +// * Create a new Attribute from an unencoded key and a HTML attribute encoded value. +// * @param unencodedKey assumes the key is not encoded, as can be only run of simple \w chars. +// * @param encodedValue HTML attribute encoded value +// * @return attribute +// */ +// public static Attribute createFromEncoded(String unencodedKey, String encodedValue) { +// String value = Entities.unescape(encodedValue, true); +// return new Attribute(unencodedKey, value, null); // parent will get set when Put +// } + +// protected boolean isDataAttribute() { +// return isDataAttribute(key); +// } +// +// protected static boolean isDataAttribute(String key) { +// return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length(); +// } + +// /** +// * Collapsible if it's a boolean attribute and value is empty or same as name +// * +// * @param out output settings +// * @return Returns whether collapsible or not +// */ +// protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { +// return shouldCollapseAttribute(key, val, out); +// } + +// protected static boolean shouldCollapseAttribute(final String key, final String val, final Document.OutputSettings out) { +// return ( +// out.syntax() == Document.OutputSettings.Syntax.html && +// (val == null || ("".equals(val) || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); +// } + +// /** +// * @deprecated +// */ +// protected boolean isBooleanAttribute() { +// return Arrays.binarySearch(booleanAttributes, key) >= 0 || val == null; +// } +// +// /** +// * Checks if this attribute name is defined as a boolean attribute in HTML5 +// */ +// protected static boolean isBooleanAttribute(final String key) { +// return Arrays.binarySearch(booleanAttributes, key) >= 0; +// } + + @Override + public boolean equals(Object o) { // note parent not considered + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Attribute attribute = (Attribute) o; + if (key != null ? !key.equals(attribute.key) : attribute.key != null) return false; + return val != null ? val.equals(attribute.val) : attribute.val == null; + } + + @Override + public int hashCode() { // note parent not considered + int result = key != null ? key.hashCode() : 0; + result = 31 * result + (val != null ? val.hashCode() : 0); + return result; + } + + @Override + public Attribute clone() { + try { + return (Attribute) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attributes.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attributes.java new file mode 100644 index 00000000..71494812 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/Attributes.java @@ -0,0 +1,441 @@ +package ru.noties.markwon.html.impl.jsoup.nodes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import ru.noties.markwon.html.impl.jsoup.helper.Validate; + +import static ru.noties.markwon.html.impl.jsoup.helper.Normalizer.lowerCase; + +/** + * The attributes of an Element. + *

+ * Attributes are treated as a map: there can be only one value associated with an attribute key/name. + *

+ *

+ * Attribute name and value comparisons are generally case sensitive. By default for HTML, attribute names are + * normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by + * name. + *

+ * + * @author Jonathan Hedley, jonathan@hedley.net + */ +public class Attributes implements Iterable, Cloneable { +// protected static final String dataPrefix = "data-"; + private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting + + // manages the key/val arrays + private static final int GrowthFactor = 2; + private static final String[] Empty = {}; + static final int NotFound = -1; + private static final String EmptyString = ""; + + private int size = 0; // number of slots used (not capacity, which is keys.length + String[] keys = Empty; + String[] vals = Empty; + + // check there's room for more + private void checkCapacity(int minNewSize) { + Validate.isTrue(minNewSize >= size); + int curSize = keys.length; + if (curSize >= minNewSize) + return; + + int newSize = curSize >= InitialCapacity ? size * GrowthFactor : InitialCapacity; + if (minNewSize > newSize) + newSize = minNewSize; + + keys = copyOf(keys, newSize); + vals = copyOf(vals, newSize); + } + + // simple implementation of Arrays.copy, for support of Android API 8. + private static String[] copyOf(String[] orig, int size) { + final String[] copy = new String[size]; + System.arraycopy(orig, 0, copy, 0, + Math.min(orig.length, size)); + return copy; + } + + int indexOfKey(String key) { + Validate.notNull(key); + for (int i = 0; i < size; i++) { + if (key.equals(keys[i])) + return i; + } + return NotFound; + } + + private int indexOfKeyIgnoreCase(String key) { + Validate.notNull(key); + for (int i = 0; i < size; i++) { + if (key.equalsIgnoreCase(keys[i])) + return i; + } + return NotFound; + } + + // we track boolean attributes as null in values - they're just keys. so returns empty for consumers + static String checkNotNull(String val) { + return val == null ? EmptyString : val; + } + + /** + Get an attribute value by key. + @param key the (case-sensitive) attribute key + @return the attribute value if set; or empty string if not set (or a boolean attribute). + @see #hasKey(String) + */ + public String get(String key) { + int i = indexOfKey(key); + return i == NotFound ? EmptyString : checkNotNull(vals[i]); + } + + /** + * Get an attribute's value by case-insensitive key + * @param key the attribute name + * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). + */ + public String getIgnoreCase(String key) { + int i = indexOfKeyIgnoreCase(key); + return i == NotFound ? EmptyString : checkNotNull(vals[i]); + } + + // adds without checking if this key exists + private void add(String key, String value) { + checkCapacity(size + 1); + keys[size] = key; + vals[size] = value; + size++; + } + + /** + * Set a new attribute, or replace an existing one by key. + * @param key case sensitive attribute key + * @param value attribute value + * @return these attributes, for chaining + */ + public Attributes put(String key, String value) { + int i = indexOfKey(key); + if (i != NotFound) + vals[i] = value; + else + add(key, value); + return this; + } + + void putIgnoreCase(String key, String value) { + int i = indexOfKeyIgnoreCase(key); + if (i != NotFound) { + vals[i] = value; + if (!keys[i].equals(key)) // case changed, update + keys[i] = key; + } + else + add(key, value); + } + + /** + * Set a new boolean attribute, remove attribute if value is false. + * @param key case insensitive attribute key + * @param value attribute value + * @return these attributes, for chaining + */ + public Attributes put(String key, boolean value) { + if (value) + putIgnoreCase(key, null); + else + remove(key); + return this; + } + + /** + Set a new attribute, or replace an existing one by key. + @param attribute attribute with case sensitive key + @return these attributes, for chaining + */ + public Attributes put(Attribute attribute) { + Validate.notNull(attribute); + put(attribute.getKey(), attribute.getValue()); + attribute.parent = this; + return this; + } + + // removes and shifts up + private void remove(int index) { + Validate.isFalse(index >= size); + int shifted = size - index - 1; + if (shifted > 0) { + System.arraycopy(keys, index + 1, keys, index, shifted); + System.arraycopy(vals, index + 1, vals, index, shifted); + } + size--; + keys[size] = null; // release hold + vals[size] = null; + } + + /** + Remove an attribute by key. Case sensitive. + @param key attribute key to remove + */ + public void remove(String key) { + int i = indexOfKey(key); + if (i != NotFound) + remove(i); + } + + /** + Remove an attribute by key. Case insensitive. + @param key attribute key to remove + */ + public void removeIgnoreCase(String key) { + int i = indexOfKeyIgnoreCase(key); + if (i != NotFound) + remove(i); + } + + /** + Tests if these attributes contain an attribute with this key. + @param key case-sensitive key to check for + @return true if key exists, false otherwise + */ + public boolean hasKey(String key) { + return indexOfKey(key) != NotFound; + } + + /** + Tests if these attributes contain an attribute with this key. + @param key key to check for + @return true if key exists, false otherwise + */ + public boolean hasKeyIgnoreCase(String key) { + return indexOfKeyIgnoreCase(key) != NotFound; + } + + /** + Get the number of attributes in this set. + @return size + */ + public int size() { + return size; + } + + /** + Add all the attributes from the incoming set to this set. + @param incoming attributes to add to these attributes. + */ + public void addAll(Attributes incoming) { + if (incoming.size() == 0) + return; + checkCapacity(size + incoming.size); + + for (Attribute attr : incoming) { + // todo - should this be case insensitive? + put(attr); + } + + } + + public Iterator iterator() { + return new Iterator() { + int i = 0; + + @Override + public boolean hasNext() { + return i < size; + } + + @Override + public Attribute next() { + final String val = vals[i]; + final Attribute attr = new Attribute(keys[i], val == null ? "" : val, Attributes.this); + i++; + return attr; + } + + @Override + public void remove() { + Attributes.this.remove(--i); // next() advanced, so rewind + } + }; + } + +// /** +// Get the attributes as a List, for iteration. +// @return an view of the attributes as an unmodifialbe List. +// */ +// public List asList() { +// ArrayList list = new ArrayList<>(size); +// for (int i = 0; i < size; i++) { +//// Attribute attr = vals[i] == null ? +//// new BooleanAttribute(keys[i]) : // deprecated class, but maybe someone still wants it +//// new Attribute(keys[i], vals[i], Attributes.this); +//// list.add(attr); +// list.add(new Attribute(keys[i], vals[i], Attributes.this)); +// } +// return Collections.unmodifiableList(list); +// } + +// /** +// * Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys +// * starting with {@code data-}. +// * @return map of custom data attributes. +// */ +// public Map dataset() { +// return new Dataset(this); +// } + +// /** +// Get the HTML representation of these attributes. +// @return HTML +// @throws SerializationException if the HTML representation of the attributes cannot be constructed. +// */ +// public String html() { +// StringBuilder accum = new StringBuilder(); +// try { +// html(accum, (new Document("")).outputSettings()); // output settings a bit funky, but this html() seldom used +// } catch (IOException e) { // ought never happen +// throw new SerializationException(e); +// } +// return accum.toString(); +// } +// +// final void html(final Appendable accum, final Document.OutputSettings out) throws IOException { +// final int sz = size; +// for (int i = 0; i < sz; i++) { +// // inlined from Attribute.html() +// final String key = keys[i]; +// final String val = vals[i]; +// accum.append(' ').append(key); +// +// // collapse checked=null, checked="", checked=checked; write out others +// if (!Attribute.shouldCollapseAttribute(key, val, out)) { +// accum.append("=\""); +// Entities.escape(accum, val == null ? EmptyString : val, out, true, false, false); +// accum.append('"'); +// } +// } +// } +// +// @Override +// public String toString() { +// return html(); +// } + + /** + * Checks if these attributes are equal to another set of attributes, by comparing the two sets + * @param o attributes to compare with + * @return if both sets of attributes have the same content + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Attributes that = (Attributes) o; + + if (size != that.size) return false; + if (!Arrays.equals(keys, that.keys)) return false; + return Arrays.equals(vals, that.vals); + } + + /** + * Calculates the hashcode of these attributes, by iterating all attributes and summing their hashcodes. + * @return calculated hashcode + */ + @Override + public int hashCode() { + int result = size; + result = 31 * result + Arrays.hashCode(keys); + result = 31 * result + Arrays.hashCode(vals); + return result; + } + + @Override + public Attributes clone() { + Attributes clone; + try { + clone = (Attributes) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + clone.size = size; + keys = copyOf(keys, size); + vals = copyOf(vals, size); + return clone; + } + + /** + * Internal method. Lowercases all keys. + */ + public void normalize() { + for (int i = 0; i < size; i++) { + keys[i] = lowerCase(keys[i]); + } + } + +// private static class Dataset extends AbstractMap { +// private final Attributes attributes; +// +// private Dataset(Attributes attributes) { +// this.attributes = attributes; +// } +// +// @Override +// public Set> entrySet() { +// return new EntrySet(); +// } +// +// @Override +// public String put(String key, String value) { +// String dataKey = dataKey(key); +// String oldValue = attributes.hasKey(dataKey) ? attributes.get(dataKey) : null; +// attributes.put(dataKey, value); +// return oldValue; +// } +// +// private class EntrySet extends AbstractSet> { +// +// @Override +// public Iterator> iterator() { +// return new DatasetIterator(); +// } +// +// @Override +// public int size() { +// int count = 0; +// Iterator iter = new DatasetIterator(); +// while (iter.hasNext()) +// count++; +// return count; +// } +// } +// +// private class DatasetIterator implements Iterator> { +// private Iterator attrIter = attributes.iterator(); +// private Attribute attr; +// public boolean hasNext() { +// while (attrIter.hasNext()) { +// attr = attrIter.next(); +// if (attr.isDataAttribute()) return true; +// } +// return false; +// } +// +// public Entry next() { +// return new Attribute(attr.getKey().substring(dataPrefix.length()), attr.getValue()); +// } +// +// public void remove() { +// attributes.remove(attr.getKey()); +// } +// } +// } + +// private static String dataKey(String key) { +// return dataPrefix + key; +// } +} \ No newline at end of file diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntities.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntities.java new file mode 100644 index 00000000..abb49575 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntities.java @@ -0,0 +1,50 @@ +package ru.noties.markwon.html.impl.jsoup.nodes; + +import android.support.annotation.NonNull; + +import org.commonmark.internal.util.Html5Entities; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Map; + +public abstract class CommonMarkEntities { + + public static boolean isNamedEntity(@NonNull String name) { + return COMMONMARK_NAMED_ENTITIES.containsKey(name); + } + + public static int codepointsForName(@NonNull String name, @NonNull int[] codepoints) { + final String value = COMMONMARK_NAMED_ENTITIES.get(name); + if (value != null) { + final int length = value.length(); + if (length == 1) { + codepoints[0] = value.charAt(0); + } else { + codepoints[0] = value.charAt(0); + codepoints[1] = value.charAt(1); + } + return length; + } + return 0; + } + + private static final Map COMMONMARK_NAMED_ENTITIES; + + static { + Map map; + try { + final Field field = Html5Entities.class.getDeclaredField("NAMED_CHARACTER_REFERENCES"); + field.setAccessible(true); + //noinspection unchecked + map = (Map) field.get(null); + } catch (Throwable t) { + map = Collections.emptyMap(); + t.printStackTrace(); + } + COMMONMARK_NAMED_ENTITIES = map; + } + + private CommonMarkEntities() { + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/DocumentType.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/DocumentType.java new file mode 100644 index 00000000..0b1240df --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/nodes/DocumentType.java @@ -0,0 +1,104 @@ +package ru.noties.markwon.html.impl.jsoup.nodes; + +/** + * A {@code } node. + */ +public class DocumentType /*extends LeafNode*/ { + // todo needs a bit of a chunky cleanup. this level of detail isn't needed + public static final String PUBLIC_KEY = "PUBLIC"; + public static final String SYSTEM_KEY = "SYSTEM"; +// private static final String NAME = "name"; +// private static final String PUB_SYS_KEY = "pubSysKey"; // PUBLIC or SYSTEM +// private static final String PUBLIC_ID = "publicId"; +// private static final String SYSTEM_ID = "systemId"; + // todo: quirk mode from publicId and systemId + +// /** +// * Create a new doctype element. +// * @param name the doctype's name +// * @param publicId the doctype's public ID +// * @param systemId the doctype's system ID +// */ +// public DocumentType(String name, String publicId, String systemId) { +// Validate.notNull(name); +// Validate.notNull(publicId); +// Validate.notNull(systemId); +// attr(NAME, name); +// attr(PUBLIC_ID, publicId); +// if (has(PUBLIC_ID)) { +// attr(PUB_SYS_KEY, PUBLIC_KEY); +// } +// attr(SYSTEM_ID, systemId); +// } +// +// /** +// * Create a new doctype element. +// * @param name the doctype's name +// * @param publicId the doctype's public ID +// * @param systemId the doctype's system ID +// * @param baseUri unused +// * @deprecated +// */ +// public DocumentType(String name, String publicId, String systemId, String baseUri) { +// attr(NAME, name); +// attr(PUBLIC_ID, publicId); +// if (has(PUBLIC_ID)) { +// attr(PUB_SYS_KEY, PUBLIC_KEY); +// } +// attr(SYSTEM_ID, systemId); +// } +// +// /** +// * Create a new doctype element. +// * @param name the doctype's name +// * @param publicId the doctype's public ID +// * @param systemId the doctype's system ID +// * @param baseUri unused +// * @deprecated +// */ +// public DocumentType(String name, String pubSysKey, String publicId, String systemId, String baseUri) { +// attr(NAME, name); +// if (pubSysKey != null) { +// attr(PUB_SYS_KEY, pubSysKey); +// } +// attr(PUBLIC_ID, publicId); +// attr(SYSTEM_ID, systemId); +// } +// public void setPubSysKey(String value) { +// if (value != null) +// attr(PUB_SYS_KEY, value); +// } +// +// @Override +// public String nodeName() { +// return "#doctype"; +// } +// +// @Override +// void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { +// if (out.syntax() == Syntax.html && !has(PUBLIC_ID) && !has(SYSTEM_ID)) { +// // looks like a html5 doctype, go lowercase for aesthetics +// accum.append("'); +// } +// +// @Override +// void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { +// } +// +// private boolean has(final String attribute) { +// return !StringUtil.isBlank(attr(attribute)); +// } +} + diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/CharacterReader.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/CharacterReader.java new file mode 100644 index 00000000..f7b7d892 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/CharacterReader.java @@ -0,0 +1,501 @@ +package ru.noties.markwon.html.impl.jsoup.parser; + +import android.support.annotation.NonNull; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Arrays; +import java.util.Locale; + +import ru.noties.markwon.html.impl.jsoup.UncheckedIOException; +import ru.noties.markwon.html.impl.jsoup.helper.Validate; + +/** + * CharacterReader consumes tokens off a string. Used internally by jsoup. API subject to changes. + */ +public final class CharacterReader { + static final char EOF = (char) -1; + private static final int maxStringCacheLen = 12; + static final int maxBufferLen = 1024 * 4; // visible for testing + private static final int readAheadLimit = (int) (maxBufferLen * 0.75); + + private final char[] charBuf; + private final Reader reader; + private int bufLength; + private int bufSplitPoint; + private int bufPos; + private int readerPos; + private int bufMark; + private final String[] stringCache = new String[128]; // holds reused strings in this doc, to lessen garbage + + public CharacterReader(Reader input, int sz) { + Validate.notNull(input); + Validate.isTrue(input.markSupported()); + reader = input; + charBuf = new char[maxBufferLen]; + bufferUp(); + } + + public CharacterReader(Reader input) { + this(input, maxBufferLen); + } + + public CharacterReader(String input) { + this(new StringReader(input), input.length()); + } + +// public void swapInput(@NonNull String input) { +// reader = new StringReader(input); +// bufLength = 0; +// bufSplitPoint = 0; +// bufPos = 0; +// readerPos = 0; +// bufferUp(); +// } + + private void bufferUp() { + if (bufPos < bufSplitPoint) + return; + + try { + reader.skip(bufPos); + reader.mark(maxBufferLen); + final int read = reader.read(charBuf); + reader.reset(); + if (read != -1) { + bufLength = read; + readerPos += bufPos; + bufPos = 0; + bufMark = 0; + bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Gets the current cursor position in the content. + * + * @return current position + */ + public int pos() { + return readerPos + bufPos; + } + + /** + * Tests if all the content has been read. + * + * @return true if nothing left to read. + */ + public boolean isEmpty() { + bufferUp(); + return bufPos >= bufLength; + } + + private boolean isEmptyNoBufferUp() { + return bufPos >= bufLength; + } + + /** + * Get the char at the current position. + * + * @return char + */ + public char current() { + bufferUp(); + return isEmptyNoBufferUp() ? EOF : charBuf[bufPos]; + } + + char consume() { + bufferUp(); + char val = isEmptyNoBufferUp() ? EOF : charBuf[bufPos]; + bufPos++; + return val; + } + + void unconsume() { + bufPos--; + } + + /** + * Moves the current position by one. + */ + public void advance() { + bufPos++; + } + + void mark() { + bufMark = bufPos; + } + + void rewindToMark() { + bufPos = bufMark; + } + + /** + * Returns the number of characters between the current position and the next instance of the input char + * + * @param c scan target + * @return offset between current position and next instance of target. -1 if not found. + */ + int nextIndexOf(char c) { + // doesn't handle scanning for surrogates + bufferUp(); + for (int i = bufPos; i < bufLength; i++) { + if (c == charBuf[i]) + return i - bufPos; + } + return -1; + } + + /** + * Returns the number of characters between the current position and the next instance of the input sequence + * + * @param seq scan target + * @return offset between current position and next instance of target. -1 if not found. + */ + int nextIndexOf(CharSequence seq) { + bufferUp(); + // doesn't handle scanning for surrogates + char startChar = seq.charAt(0); + for (int offset = bufPos; offset < bufLength; offset++) { + // scan to first instance of startchar: + if (startChar != charBuf[offset]) + while (++offset < bufLength && startChar != charBuf[offset]) { /* empty */ } + int i = offset + 1; + int last = i + seq.length() - 1; + if (offset < bufLength && last <= bufLength) { + for (int j = 1; i < last && seq.charAt(j) == charBuf[i]; i++, j++) { /* empty */ } + if (i == last) // found full sequence + return offset - bufPos; + } + } + return -1; + } + + /** + * Reads characters up to the specific char. + * + * @param c the delimiter + * @return the chars read + */ + public String consumeTo(char c) { + int offset = nextIndexOf(c); + if (offset != -1) { + String consumed = cacheString(charBuf, stringCache, bufPos, offset); + bufPos += offset; + return consumed; + } else { + return consumeToEnd(); + } + } + + String consumeTo(String seq) { + int offset = nextIndexOf(seq); + if (offset != -1) { + String consumed = cacheString(charBuf, stringCache, bufPos, offset); + bufPos += offset; + return consumed; + } else { + return consumeToEnd(); + } + } + + /** + * Read characters until the first of any delimiters is found. + * + * @param chars delimiters to scan for + * @return characters read up to the matched delimiter. + */ + public String consumeToAny(final char... chars) { + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; + + OUTER: + while (bufPos < remaining) { + for (char c : chars) { + if (val[bufPos] == c) + break OUTER; + } + bufPos++; + } + + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos - start) : ""; + } + + String consumeToAnySorted(final char... chars) { + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; + + while (bufPos < remaining) { + if (Arrays.binarySearch(chars, val[bufPos]) >= 0) + break; + bufPos++; + } + + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos - start) : ""; + } + + String consumeData() { + // &, <, null + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; + + while (bufPos < remaining) { + final char c = val[bufPos]; + if (c == '&' || c == '<' || c == TokeniserState.nullChar) + break; + bufPos++; + } + + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos - start) : ""; + } + + String consumeTagName() { + // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; + + while (bufPos < remaining) { + final char c = val[bufPos]; + if (c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == ' ' || c == '/' || c == '>' || c == TokeniserState.nullChar) + break; + bufPos++; + } + + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos - start) : ""; + } + + String consumeToEnd() { + bufferUp(); + String data = cacheString(charBuf, stringCache, bufPos, bufLength - bufPos); + bufPos = bufLength; + return data; + } + + String consumeLetterSequence() { + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) + bufPos++; + else + break; + } + + return cacheString(charBuf, stringCache, start, bufPos - start); + } + + String consumeLetterThenDigitSequence() { + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) + bufPos++; + else + break; + } + while (!isEmptyNoBufferUp()) { + char c = charBuf[bufPos]; + if (c >= '0' && c <= '9') + bufPos++; + else + break; + } + + return cacheString(charBuf, stringCache, start, bufPos - start); + } + + String consumeHexSequence() { + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) + bufPos++; + else + break; + } + return cacheString(charBuf, stringCache, start, bufPos - start); + } + + String consumeDigitSequence() { + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; + if (c >= '0' && c <= '9') + bufPos++; + else + break; + } + return cacheString(charBuf, stringCache, start, bufPos - start); + } + + boolean matches(char c) { + return !isEmpty() && charBuf[bufPos] == c; + + } + + boolean matches(String seq) { + bufferUp(); + int scanLength = seq.length(); + if (scanLength > bufLength - bufPos) + return false; + + for (int offset = 0; offset < scanLength; offset++) + if (seq.charAt(offset) != charBuf[bufPos + offset]) + return false; + return true; + } + + boolean matchesIgnoreCase(String seq) { + bufferUp(); + int scanLength = seq.length(); + if (scanLength > bufLength - bufPos) + return false; + + for (int offset = 0; offset < scanLength; offset++) { + char upScan = Character.toUpperCase(seq.charAt(offset)); + char upTarget = Character.toUpperCase(charBuf[bufPos + offset]); + if (upScan != upTarget) + return false; + } + return true; + } + + boolean matchesAny(char... seq) { + if (isEmpty()) + return false; + + bufferUp(); + char c = charBuf[bufPos]; + for (char seek : seq) { + if (seek == c) + return true; + } + return false; + } + + boolean matchesAnySorted(char[] seq) { + bufferUp(); + return !isEmpty() && Arrays.binarySearch(seq, charBuf[bufPos]) >= 0; + } + + boolean matchesLetter() { + if (isEmpty()) + return false; + char c = charBuf[bufPos]; + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c); + } + + boolean matchesDigit() { + if (isEmpty()) + return false; + char c = charBuf[bufPos]; + return (c >= '0' && c <= '9'); + } + + boolean matchConsume(String seq) { + bufferUp(); + if (matches(seq)) { + bufPos += seq.length(); + return true; + } else { + return false; + } + } + + boolean matchConsumeIgnoreCase(String seq) { + if (matchesIgnoreCase(seq)) { + bufPos += seq.length(); + return true; + } else { + return false; + } + } + + boolean containsIgnoreCase(String seq) { + // used to check presence of , . only finds consistent case. + String loScan = seq.toLowerCase(Locale.ENGLISH); + String hiScan = seq.toUpperCase(Locale.ENGLISH); + return (nextIndexOf(loScan) > -1) || (nextIndexOf(hiScan) > -1); + } + + @Override + public String toString() { + return new String(charBuf, bufPos, bufLength - bufPos); + } + + /** + * Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks. + *

+ * Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list. + * That saves both having to create objects as hash keys, and running through the entry list, at the expense of + * some more duplicates. + */ + private static String cacheString(final char[] charBuf, final String[] stringCache, final int start, final int count) { + // limit (no cache): + if (count > maxStringCacheLen) + return new String(charBuf, start, count); + if (count < 1) + return ""; + + // calculate hash: + int hash = 0; + int offset = start; + for (int i = 0; i < count; i++) { + hash = 31 * hash + charBuf[offset++]; + } + + // get from cache + final int index = hash & stringCache.length - 1; + String cached = stringCache[index]; + + if (cached == null) { // miss, add + cached = new String(charBuf, start, count); + stringCache[index] = cached; + } else { // hashcode hit, check equality + if (rangeEquals(charBuf, start, count, cached)) { // hit + return cached; + } else { // hashcode conflict + cached = new String(charBuf, start, count); + stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again + } + } + return cached; + } + + /** + * Check if the value of the provided range equals the string. + */ + static boolean rangeEquals(final char[] charBuf, final int start, int count, final String cached) { + if (count == cached.length()) { + int i = start; + int j = 0; + while (count-- != 0) { + if (charBuf[i++] != cached.charAt(j++)) + return false; + } + return true; + } + return false; + } + + // just used for testing + boolean rangeEquals(final int start, final int count, final String cached) { + return rangeEquals(charBuf, start, count, cached); + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseError.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseError.java new file mode 100644 index 00000000..f43c3007 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseError.java @@ -0,0 +1,41 @@ +package ru.noties.markwon.html.impl.jsoup.parser; + +/** + * A Parse Error records an error in the input HTML that occurs in either the tokenisation or the tree building phase. + */ +public class ParseError { + private int pos; + private String errorMsg; + + ParseError(int pos, String errorMsg) { + this.pos = pos; + this.errorMsg = errorMsg; + } + + ParseError(int pos, String errorFormat, Object... args) { + this.errorMsg = String.format(errorFormat, args); + this.pos = pos; + } + + /** + * Retrieve the error message. + * @return the error message. + */ + public String getErrorMessage() { + return errorMsg; + } + + /** + * Retrieves the offset of the error. + * @return error offset within input + */ + public int getPosition() { + return pos; + } + + @Override + public String toString() { + return pos + ": " + errorMsg; + } +} + diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseErrorList.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseErrorList.java new file mode 100644 index 00000000..032b7571 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/ParseErrorList.java @@ -0,0 +1,34 @@ +package ru.noties.markwon.html.impl.jsoup.parser; + +import java.util.ArrayList; + +/** + * A container for ParseErrors. + * + * @author Jonathan Hedley + */ +public class ParseErrorList extends ArrayList{ + private static final int INITIAL_CAPACITY = 16; + private final int maxSize; + + ParseErrorList(int initialCapacity, int maxSize) { + super(initialCapacity); + this.maxSize = maxSize; + } + + boolean canAddError() { + return size() < maxSize; + } + + int getMaxSize() { + return maxSize; + } + + public static ParseErrorList noTracking() { + return new ParseErrorList(0, 0); + } + + public static ParseErrorList tracking(int maxSize) { + return new ParseErrorList(INITIAL_CAPACITY, maxSize); + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java new file mode 100644 index 00000000..a70da0e7 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java @@ -0,0 +1,398 @@ +package ru.noties.markwon.html.impl.jsoup.parser; + +import android.support.annotation.NonNull; + +import ru.noties.markwon.html.impl.jsoup.helper.Validate; +import ru.noties.markwon.html.impl.jsoup.nodes.Attributes; + +import static ru.noties.markwon.html.impl.jsoup.helper.Normalizer.lowerCase; + +/** + * Parse tokens for the Tokeniser. + */ +public abstract class Token { + + public final TokenType type; + + protected Token(@NonNull TokenType tokenType) { + this.type = tokenType; + } + +// String tokenType() { +// return this.getClass().getSimpleName(); +// } + + /** + * Reset the data represent by this token, for reuse. Prevents the need to create transfer objects for every + * piece of data, which immediately get GCed. + */ + public abstract Token reset(); + + static void reset(StringBuilder sb) { + if (sb != null) { + sb.delete(0, sb.length()); + } + } + + public static final class Doctype extends Token { + final StringBuilder name = new StringBuilder(); + String pubSysKey = null; + final StringBuilder publicIdentifier = new StringBuilder(); + final StringBuilder systemIdentifier = new StringBuilder(); + boolean forceQuirks = false; + + Doctype() { + super(TokenType.Doctype); + } + + @Override + public Token reset() { + reset(name); + pubSysKey = null; + reset(publicIdentifier); + reset(systemIdentifier); + forceQuirks = false; + return this; + } + + String getName() { + return name.toString(); + } + + String getPubSysKey() { + return pubSysKey; + } + + String getPublicIdentifier() { + return publicIdentifier.toString(); + } + + public String getSystemIdentifier() { + return systemIdentifier.toString(); + } + + public boolean isForceQuirks() { + return forceQuirks; + } + } + + public static abstract class Tag extends Token { + + public String tagName; + public String normalName; // lc version of tag name, for case insensitive tree build + private String pendingAttributeName; // attribute names are generally caught in one hop, not accumulated + private StringBuilder pendingAttributeValue = new StringBuilder(); // but values are accumulated, from e.g. & in hrefs + private String pendingAttributeValueS; // try to get attr vals in one shot, vs Builder + private boolean hasEmptyAttributeValue = false; // distinguish boolean attribute from empty string value + private boolean hasPendingAttributeValue = false; + public boolean selfClosing = false; + public Attributes attributes; // start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used). + + protected Tag(@NonNull TokenType tokenType) { + super(tokenType); + } + + @Override + public Tag reset() { + tagName = null; + normalName = null; + pendingAttributeName = null; + reset(pendingAttributeValue); + pendingAttributeValueS = null; + hasEmptyAttributeValue = false; + hasPendingAttributeValue = false; + selfClosing = false; + attributes = null; + return this; + } + + final void newAttribute() { + if (attributes == null) + attributes = new Attributes(); + + if (pendingAttributeName != null) { + // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here + pendingAttributeName = pendingAttributeName.trim(); + if (pendingAttributeName.length() > 0) { + String value; + if (hasPendingAttributeValue) + value = pendingAttributeValue.length() > 0 ? pendingAttributeValue.toString() : pendingAttributeValueS; + else if (hasEmptyAttributeValue) + value = ""; + else + value = null; + attributes.put(pendingAttributeName, value); + } + } + pendingAttributeName = null; + hasEmptyAttributeValue = false; + hasPendingAttributeValue = false; + reset(pendingAttributeValue); + pendingAttributeValueS = null; + } + + final void finaliseTag() { + // finalises for emit + if (pendingAttributeName != null) { + // todo: check if attribute name exists; if so, drop and error + newAttribute(); + } + } + + final String name() { // preserves case, for input into Tag.valueOf (which may drop case) + Validate.isFalse(tagName == null || tagName.length() == 0); + return tagName; + } + + final String normalName() { // loses case, used in tree building for working out where in tree it should go + return normalName; + } + + final Tag name(String name) { + tagName = name; + normalName = lowerCase(name); + return this; + } + + final boolean isSelfClosing() { + return selfClosing; + } + + @SuppressWarnings({"TypeMayBeWeakened"}) + final Attributes getAttributes() { + return attributes; + } + + // these appenders are rarely hit in not null state-- caused by null chars. + final void appendTagName(String append) { + tagName = tagName == null ? append : tagName.concat(append); + normalName = lowerCase(tagName); + } + + final void appendTagName(char append) { + appendTagName(String.valueOf(append)); + } + + final void appendAttributeName(String append) { + pendingAttributeName = pendingAttributeName == null ? append : pendingAttributeName.concat(append); + } + + final void appendAttributeName(char append) { + appendAttributeName(String.valueOf(append)); + } + + final void appendAttributeValue(String append) { + ensureAttributeValue(); + if (pendingAttributeValue.length() == 0) { + pendingAttributeValueS = append; + } else { + pendingAttributeValue.append(append); + } + } + + final void appendAttributeValue(char append) { + ensureAttributeValue(); + pendingAttributeValue.append(append); + } + + final void appendAttributeValue(char[] append) { + ensureAttributeValue(); + pendingAttributeValue.append(append); + } + + final void appendAttributeValue(int[] appendCodepoints) { + ensureAttributeValue(); + for (int codepoint : appendCodepoints) { + pendingAttributeValue.appendCodePoint(codepoint); + } + } + + final void setEmptyAttributeValue() { + hasEmptyAttributeValue = true; + } + + private void ensureAttributeValue() { + hasPendingAttributeValue = true; + // if on second hit, we'll need to move to the builder + if (pendingAttributeValueS != null) { + pendingAttributeValue.append(pendingAttributeValueS); + pendingAttributeValueS = null; + } + } + } + + public final static class StartTag extends Tag { + public StartTag() { + super(TokenType.StartTag); + attributes = new Attributes(); + } + + @Override + public Tag reset() { + super.reset(); + attributes = new Attributes(); + // todo - would prefer these to be null, but need to check Element assertions + return this; + } + + StartTag nameAttr(String name, Attributes attributes) { + this.tagName = name; + this.attributes = attributes; + normalName = lowerCase(tagName); + return this; + } + + @Override + public String toString() { + if (attributes != null && attributes.size() > 0) + return "<" + name() + " " + attributes.toString() + ">"; + else + return "<" + name() + ">"; + } + } + + public final static class EndTag extends Tag{ + EndTag() { + super(TokenType.EndTag); + } + + @Override + public String toString() { + return ""; + } + } + + public final static class Comment extends Token { + final StringBuilder data = new StringBuilder(); + boolean bogus = false; + + @Override + public Token reset() { + reset(data); + bogus = false; + return this; + } + + Comment() { + super(TokenType.Comment); + } + + String getData() { + return data.toString(); + } + + @Override + public String toString() { + return ""; + } + } + + public static class Character extends Token { + private String data; + + Character() { + super(TokenType.Character); + } + + @Override + public Token reset() { + data = null; + return this; + } + + Character data(String data) { + this.data = data; + return this; + } + + public String getData() { + return data; + } + + @Override + public String toString() { + return getData(); + } + } + + public final static class CData extends Character { + CData(String data) { + super(); + this.data(data); + } + + @Override + public String toString() { + return ""; + } + + } + + public final static class EOF extends Token { + EOF() { + super(Token.TokenType.EOF); + } + + @Override + public Token reset() { + return this; + } + } + +// final boolean isDoctype() { +// return type == TokenType.Doctype; +// } +// +// final Doctype asDoctype() { +// return (Doctype) this; +// } +// +// final boolean isStartTag() { +// return type == TokenType.StartTag; +// } +// +// final StartTag asStartTag() { +// return (StartTag) this; +// } +// +// final boolean isEndTag() { +// return type == TokenType.EndTag; +// } +// +// final EndTag asEndTag() { +// return (EndTag) this; +// } +// +// final boolean isComment() { +// return type == TokenType.Comment; +// } +// +// final Comment asComment() { +// return (Comment) this; +// } +// +// final boolean isCharacter() { +// return type == TokenType.Character; +// } +// +// final boolean isCData() { +// return this instanceof CData; +// } +// +// final Character asCharacter() { +// return (Character) this; +// } +// +// final boolean isEOF() { +// return type == TokenType.EOF; +// } + + public enum TokenType { + Doctype, + StartTag, + EndTag, + Comment, + Character, // note no CData - treated in builder as an extension of Character + EOF + } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Tokeniser.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Tokeniser.java new file mode 100644 index 00000000..77df2b7c --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Tokeniser.java @@ -0,0 +1,295 @@ +package ru.noties.markwon.html.impl.jsoup.parser; + +import java.util.Arrays; + +import ru.noties.markwon.html.impl.jsoup.helper.Validate; +import ru.noties.markwon.html.impl.jsoup.nodes.CommonMarkEntities; + +/** + * Readers the input stream into tokens. + */ +public final class Tokeniser { + static final char replacementChar = '\uFFFD'; // replaces null character + private static final char[] notCharRefCharsSorted = new char[]{'\t', '\n', '\r', '\f', ' ', '<', '&'}; + + // Some illegal character escapes are parsed by browsers as windows-1252 instead. See issue #1034 + // https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state + static final int win1252ExtensionsStart = 0x80; + static final int[] win1252Extensions = new int[] { + // we could build this manually, but Windows-1252 is not a standard java charset so that could break on + // some platforms - this table is verified with a test + 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, + 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, + }; + + static { + Arrays.sort(notCharRefCharsSorted); + } + + private final CharacterReader reader; // html input + private final ParseErrorList errors; // errors found while tokenising + + private TokeniserState state = TokeniserState.Data; // current tokenisation state + private Token emitPending; // the token we are about to emit on next read + private boolean isEmitPending = false; + private String charsString = null; // characters pending an emit. Will fall to charsBuilder if more than one + private StringBuilder charsBuilder = new StringBuilder(1024); // buffers characters to output as one token, if more than one emit per read + StringBuilder dataBuffer = new StringBuilder(1024); // buffers data looking for + + Token.Tag tagPending; // tag we are building up + Token.StartTag startPending = new Token.StartTag(); + Token.EndTag endPending = new Token.EndTag(); + Token.Character charPending = new Token.Character(); + Token.Doctype doctypePending = new Token.Doctype(); // doctype building up + Token.Comment commentPending = new Token.Comment(); // comment building up + private String lastStartTag; // the last start tag emitted, to test appropriate end tag + + public Tokeniser(CharacterReader reader, ParseErrorList errors) { + this.reader = reader; + this.errors = errors; + } + + public Token read() { + while (!isEmitPending) + state.read(this, reader); + + // if emit is pending, a non-character token was found: return any chars in buffer, and leave token for next read: + if (charsBuilder.length() > 0) { + String str = charsBuilder.toString(); + charsBuilder.delete(0, charsBuilder.length()); + charsString = null; + return charPending.data(str); + } else if (charsString != null) { + Token token = charPending.data(charsString); + charsString = null; + return token; + } else { + isEmitPending = false; + return emitPending; + } + } + + void emit(Token token) { + Validate.isFalse(isEmitPending, "There is an unread token pending!"); + + emitPending = token; + isEmitPending = true; + + if (token.type == Token.TokenType.StartTag) { + Token.StartTag startTag = (Token.StartTag) token; + lastStartTag = startTag.tagName; + } else if (token.type == Token.TokenType.EndTag) { + Token.EndTag endTag = (Token.EndTag) token; + if (endTag.attributes != null) + error("Attributes incorrectly present on end tag"); + } + } + + void emit(final String str) { + // buffer strings up until last string token found, to emit only one token for a run of character refs etc. + // does not set isEmitPending; read checks that + if (charsString == null) { + charsString = str; + } + else { + if (charsBuilder.length() == 0) { // switching to string builder as more than one emit before read + charsBuilder.append(charsString); + } + charsBuilder.append(str); + } + } + + void emit(char[] chars) { + emit(String.valueOf(chars)); + } + + void emit(int[] codepoints) { + emit(new String(codepoints, 0, codepoints.length)); + } + + void emit(char c) { + emit(String.valueOf(c)); + } + + TokeniserState getState() { + return state; + } + + void transition(TokeniserState state) { + this.state = state; + } + + void advanceTransition(TokeniserState state) { + reader.advance(); + this.state = state; + } + + final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays + final private int[] multipointHolder = new int[2]; + int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { + if (reader.isEmpty()) + return null; + if (additionalAllowedCharacter != null && additionalAllowedCharacter == reader.current()) + return null; + if (reader.matchesAnySorted(notCharRefCharsSorted)) + return null; + + final int[] codeRef = codepointHolder; + reader.mark(); + if (reader.matchConsume("#")) { // numbered + boolean isHexMode = reader.matchConsumeIgnoreCase("X"); + String numRef = isHexMode ? reader.consumeHexSequence() : reader.consumeDigitSequence(); + if (numRef.length() == 0) { // didn't match anything + characterReferenceError("numeric reference with no numerals"); + reader.rewindToMark(); + return null; + } + if (!reader.matchConsume(";")) + characterReferenceError("missing semicolon"); // missing semi + int charval = -1; + try { + int base = isHexMode ? 16 : 10; + charval = Integer.valueOf(numRef, base); + } catch (NumberFormatException ignored) { + } // skip + if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) { + characterReferenceError("character outside of valid range"); + codeRef[0] = replacementChar; + return codeRef; + } else { + // fix illegal unicode characters to match browser behavior + if (charval >= win1252ExtensionsStart && charval < win1252ExtensionsStart + win1252Extensions.length) { + characterReferenceError("character is not a valid unicode code point"); + charval = win1252Extensions[charval - win1252ExtensionsStart]; + } + + // todo: implement number replacement table + // todo: check for extra illegal unicode points as parse errors + codeRef[0] = charval; + return codeRef; + } + } else { // named + // get as many letters as possible, and look for matching entities. + String nameRef = reader.consumeLetterThenDigitSequence(); + boolean looksLegit = reader.matches(';'); + // found if a base named entity without a ;, or an extended entity with the ;. + boolean found = (CommonMarkEntities.isNamedEntity(nameRef) && looksLegit); + + if (!found) { + reader.rewindToMark(); + if (looksLegit) // named with semicolon + characterReferenceError(String.format("invalid named referenece '%s'", nameRef)); + return null; + } + if (inAttribute && (reader.matchesLetter() || reader.matchesDigit() || reader.matchesAny('=', '-', '_'))) { + // don't want that to match + reader.rewindToMark(); + return null; + } + if (!reader.matchConsume(";")) + characterReferenceError("missing semicolon"); // missing semi + int numChars = CommonMarkEntities.codepointsForName(nameRef, multipointHolder); + if (numChars == 1) { + codeRef[0] = multipointHolder[0]; + return codeRef; + } else if (numChars ==2) { + return multipointHolder; + } else { + Validate.fail("Unexpected characters returned for " + nameRef); + return multipointHolder; + } + } + } + + Token.Tag createTagPending(boolean start) { + tagPending = start ? startPending.reset() : endPending.reset(); + return tagPending; + } + + void emitTagPending() { + tagPending.finaliseTag(); + emit(tagPending); + } + + void createCommentPending() { + commentPending.reset(); + } + + void emitCommentPending() { + emit(commentPending); + } + + void createDoctypePending() { + doctypePending.reset(); + } + + void emitDoctypePending() { + emit(doctypePending); + } + + void createTempBuffer() { + Token.reset(dataBuffer); + } + + boolean isAppropriateEndTagToken() { + return lastStartTag != null && tagPending.name().equalsIgnoreCase(lastStartTag); + } + + String appropriateEndTagName() { + return lastStartTag; // could be null + } + + void error(TokeniserState state) { + if (errors.canAddError()) + errors.add(new ParseError(reader.pos(), "Unexpected character '%s' in input state [%s]", reader.current(), state)); + } + + void eofError(TokeniserState state) { + if (errors.canAddError()) + errors.add(new ParseError(reader.pos(), "Unexpectedly reached end of file (EOF) in input state [%s]", state)); + } + + private void characterReferenceError(String message) { + if (errors.canAddError()) + errors.add(new ParseError(reader.pos(), "Invalid character reference: %s", message)); + } + + void error(String errorMsg) { + if (errors.canAddError()) + errors.add(new ParseError(reader.pos(), errorMsg)); + } + + boolean currentNodeInHtmlNS() { + // todo: implement namespaces correctly + return true; + // Element currentNode = currentNode(); + // return currentNode != null && currentNode.namespace().equals("HTML"); + } + +// /** +// * Utility method to consume reader and unescape entities found within. +// * @param inAttribute if the text to be unescaped is in an attribute +// * @return unescaped string from reader +// */ +// String unescapeEntities(boolean inAttribute) { +// StringBuilder builder = StringUtil.stringBuilder(); +// while (!reader.isEmpty()) { +// builder.append(reader.consumeTo('&')); +// if (reader.matches('&')) { +// reader.consume(); +// int[] c = consumeCharacterReference(null, inAttribute); +// if (c == null || c.length==0) +// builder.append('&'); +// else { +// builder.appendCodePoint(c[0]); +// if (c.length == 2) +// builder.appendCodePoint(c[1]); +// } +// +// } +// } +// return builder.toString(); +// } +} diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/TokeniserState.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/TokeniserState.java new file mode 100644 index 00000000..4c3e4405 --- /dev/null +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/TokeniserState.java @@ -0,0 +1,1737 @@ +package ru.noties.markwon.html.impl.jsoup.parser; + +import ru.noties.markwon.html.impl.jsoup.nodes.DocumentType; + +/** + * States and transition activations for the Tokeniser. + */ +enum TokeniserState { + Data { + // in data state, gather characters until a character reference or tag is found + void read(Tokeniser t, CharacterReader r) { + switch (r.current()) { + case '&': + t.advanceTransition(CharacterReferenceInData); + break; + case '<': + t.advanceTransition(TagOpen); + break; + case nullChar: + t.error(this); // NOT replacement character (oddly?) + t.emit(r.consume()); + break; + case eof: + t.emit(new Token.EOF()); + break; + default: + String data = r.consumeData(); + t.emit(data); + break; + } + } + }, + CharacterReferenceInData { + // from & in data + void read(Tokeniser t, CharacterReader r) { + readCharRef(t, Data); + } + }, + Rcdata { + /// handles data in title, textarea etc + void read(Tokeniser t, CharacterReader r) { + switch (r.current()) { + case '&': + t.advanceTransition(CharacterReferenceInRcdata); + break; + case '<': + t.advanceTransition(RcdataLessthanSign); + break; + case nullChar: + t.error(this); + r.advance(); + t.emit(replacementChar); + break; + case eof: + t.emit(new Token.EOF()); + break; + default: + String data = r.consumeToAny('&', '<', nullChar); + t.emit(data); + break; + } + } + }, + CharacterReferenceInRcdata { + void read(Tokeniser t, CharacterReader r) { + readCharRef(t, Rcdata); + } + }, + Rawtext { + void read(Tokeniser t, CharacterReader r) { + readData(t, r, this, RawtextLessthanSign); + } + }, + ScriptData { + void read(Tokeniser t, CharacterReader r) { + readData(t, r, this, ScriptDataLessthanSign); + } + }, + PLAINTEXT { + void read(Tokeniser t, CharacterReader r) { + switch (r.current()) { + case nullChar: + t.error(this); + r.advance(); + t.emit(replacementChar); + break; + case eof: + t.emit(new Token.EOF()); + break; + default: + String data = r.consumeTo(nullChar); + t.emit(data); + break; + } + } + }, + TagOpen { + // from < in data + void read(Tokeniser t, CharacterReader r) { + switch (r.current()) { + case '!': + t.advanceTransition(MarkupDeclarationOpen); + break; + case '/': + t.advanceTransition(EndTagOpen); + break; + case '?': + t.advanceTransition(BogusComment); + break; + default: + if (r.matchesLetter()) { + t.createTagPending(true); + t.transition(TagName); + } else { + t.error(this); + t.emit('<'); // char that got us here + t.transition(Data); + } + break; + } + } + }, + EndTagOpen { + void read(Tokeniser t, CharacterReader r) { + if (r.isEmpty()) { + t.eofError(this); + t.emit("')) { + t.error(this); + t.advanceTransition(Data); + } else { + t.error(this); + t.advanceTransition(BogusComment); + } + } + }, + TagName { + // from < or ': + t.emitTagPending(); + t.transition(Data); + break; + case nullChar: // replacement + t.tagPending.appendTagName(replacementStr); + break; + case eof: // should emit pending tag? + t.eofError(this); + t.transition(Data); + break; + default: // buffer underrun + t.tagPending.appendTagName(c); + } + } + }, + RcdataLessthanSign { + // from < in rcdata + void read(Tokeniser t, CharacterReader r) { + if (r.matches('/')) { + t.createTempBuffer(); + t.advanceTransition(RCDATAEndTagOpen); + } else if (r.matchesLetter() && t.appropriateEndTagName() != null && !r.containsIgnoreCase("), so rather than + // consuming to EOF; break out here + t.tagPending = t.createTagPending(false).name(t.appropriateEndTagName()); + t.emitTagPending(); + r.unconsume(); // undo "<" + t.transition(Data); + } else { + t.emit("<"); + t.transition(Rcdata); + } + } + }, + RCDATAEndTagOpen { + void read(Tokeniser t, CharacterReader r) { + if (r.matchesLetter()) { + t.createTagPending(false); + t.tagPending.appendTagName(r.current()); + t.dataBuffer.append(r.current()); + t.advanceTransition(RCDATAEndTagName); + } else { + t.emit("': + if (t.isAppropriateEndTagToken()) { + t.emitTagPending(); + t.transition(Data); + } + else + anythingElse(t, r); + break; + default: + anythingElse(t, r); + } + } + + private void anythingElse(Tokeniser t, CharacterReader r) { + t.emit("': + t.emit(c); + t.transition(ScriptData); + break; + case nullChar: + t.error(this); + t.emit(replacementChar); + t.transition(ScriptDataEscaped); + break; + default: + t.emit(c); + t.transition(ScriptDataEscaped); + } + } + }, + ScriptDataEscapedLessthanSign { + void read(Tokeniser t, CharacterReader r) { + if (r.matchesLetter()) { + t.createTempBuffer(); + t.dataBuffer.append(r.current()); + t.emit("<" + r.current()); + t.advanceTransition(ScriptDataDoubleEscapeStart); + } else if (r.matches('/')) { + t.createTempBuffer(); + t.advanceTransition(ScriptDataEscapedEndTagOpen); + } else { + t.emit('<'); + t.transition(ScriptDataEscaped); + } + } + }, + ScriptDataEscapedEndTagOpen { + void read(Tokeniser t, CharacterReader r) { + if (r.matchesLetter()) { + t.createTagPending(false); + t.tagPending.appendTagName(r.current()); + t.dataBuffer.append(r.current()); + t.advanceTransition(ScriptDataEscapedEndTagName); + } else { + t.emit("': + t.emit(c); + t.transition(ScriptData); + break; + case nullChar: + t.error(this); + t.emit(replacementChar); + t.transition(ScriptDataDoubleEscaped); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + default: + t.emit(c); + t.transition(ScriptDataDoubleEscaped); + } + } + }, + ScriptDataDoubleEscapedLessthanSign { + void read(Tokeniser t, CharacterReader r) { + if (r.matches('/')) { + t.emit('/'); + t.createTempBuffer(); + t.advanceTransition(ScriptDataDoubleEscapeEnd); + } else { + t.transition(ScriptDataDoubleEscaped); + } + } + }, + ScriptDataDoubleEscapeEnd { + void read(Tokeniser t, CharacterReader r) { + handleDataDoubleEscapeTag(t,r, ScriptDataEscaped, ScriptDataDoubleEscaped); + } + }, + BeforeAttributeName { + // from tagname ': + t.emitTagPending(); + t.transition(Data); + break; + case nullChar: + t.error(this); + t.tagPending.newAttribute(); + r.unconsume(); + t.transition(AttributeName); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + case '"': + case '\'': + case '<': + case '=': + t.error(this); + t.tagPending.newAttribute(); + t.tagPending.appendAttributeName(c); + t.transition(AttributeName); + break; + default: // A-Z, anything else + t.tagPending.newAttribute(); + r.unconsume(); + t.transition(AttributeName); + } + } + }, + AttributeName { + // from before attribute name + void read(Tokeniser t, CharacterReader r) { + String name = r.consumeToAnySorted(attributeNameCharsSorted); + t.tagPending.appendAttributeName(name); + + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(AfterAttributeName); + break; + case '/': + t.transition(SelfClosingStartTag); + break; + case '=': + t.transition(BeforeAttributeValue); + break; + case '>': + t.emitTagPending(); + t.transition(Data); + break; + case nullChar: + t.error(this); + t.tagPending.appendAttributeName(replacementChar); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + case '"': + case '\'': + case '<': + t.error(this); + t.tagPending.appendAttributeName(c); + break; + default: // buffer underrun + t.tagPending.appendAttributeName(c); + } + } + }, + AfterAttributeName { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + // ignore + break; + case '/': + t.transition(SelfClosingStartTag); + break; + case '=': + t.transition(BeforeAttributeValue); + break; + case '>': + t.emitTagPending(); + t.transition(Data); + break; + case nullChar: + t.error(this); + t.tagPending.appendAttributeName(replacementChar); + t.transition(AttributeName); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + case '"': + case '\'': + case '<': + t.error(this); + t.tagPending.newAttribute(); + t.tagPending.appendAttributeName(c); + t.transition(AttributeName); + break; + default: // A-Z, anything else + t.tagPending.newAttribute(); + r.unconsume(); + t.transition(AttributeName); + } + } + }, + BeforeAttributeValue { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + // ignore + break; + case '"': + t.transition(AttributeValue_doubleQuoted); + break; + case '&': + r.unconsume(); + t.transition(AttributeValue_unquoted); + break; + case '\'': + t.transition(AttributeValue_singleQuoted); + break; + case nullChar: + t.error(this); + t.tagPending.appendAttributeValue(replacementChar); + t.transition(AttributeValue_unquoted); + break; + case eof: + t.eofError(this); + t.emitTagPending(); + t.transition(Data); + break; + case '>': + t.error(this); + t.emitTagPending(); + t.transition(Data); + break; + case '<': + case '=': + case '`': + t.error(this); + t.tagPending.appendAttributeValue(c); + t.transition(AttributeValue_unquoted); + break; + default: + r.unconsume(); + t.transition(AttributeValue_unquoted); + } + } + }, + AttributeValue_doubleQuoted { + void read(Tokeniser t, CharacterReader r) { + String value = r.consumeToAny(attributeDoubleValueCharsSorted); + if (value.length() > 0) + t.tagPending.appendAttributeValue(value); + else + t.tagPending.setEmptyAttributeValue(); + + char c = r.consume(); + switch (c) { + case '"': + t.transition(AfterAttributeValue_quoted); + break; + case '&': + int[] ref = t.consumeCharacterReference('"', true); + if (ref != null) + t.tagPending.appendAttributeValue(ref); + else + t.tagPending.appendAttributeValue('&'); + break; + case nullChar: + t.error(this); + t.tagPending.appendAttributeValue(replacementChar); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + default: // hit end of buffer in first read, still in attribute + t.tagPending.appendAttributeValue(c); + } + } + }, + AttributeValue_singleQuoted { + void read(Tokeniser t, CharacterReader r) { + String value = r.consumeToAny(attributeSingleValueCharsSorted); + if (value.length() > 0) + t.tagPending.appendAttributeValue(value); + else + t.tagPending.setEmptyAttributeValue(); + + char c = r.consume(); + switch (c) { + case '\'': + t.transition(AfterAttributeValue_quoted); + break; + case '&': + int[] ref = t.consumeCharacterReference('\'', true); + if (ref != null) + t.tagPending.appendAttributeValue(ref); + else + t.tagPending.appendAttributeValue('&'); + break; + case nullChar: + t.error(this); + t.tagPending.appendAttributeValue(replacementChar); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + default: // hit end of buffer in first read, still in attribute + t.tagPending.appendAttributeValue(c); + } + } + }, + AttributeValue_unquoted { + void read(Tokeniser t, CharacterReader r) { + String value = r.consumeToAnySorted(attributeValueUnquoted); + if (value.length() > 0) + t.tagPending.appendAttributeValue(value); + + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(BeforeAttributeName); + break; + case '&': + int[] ref = t.consumeCharacterReference('>', true); + if (ref != null) + t.tagPending.appendAttributeValue(ref); + else + t.tagPending.appendAttributeValue('&'); + break; + case '>': + t.emitTagPending(); + t.transition(Data); + break; + case nullChar: + t.error(this); + t.tagPending.appendAttributeValue(replacementChar); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + case '"': + case '\'': + case '<': + case '=': + case '`': + t.error(this); + t.tagPending.appendAttributeValue(c); + break; + default: // hit end of buffer in first read, still in attribute + t.tagPending.appendAttributeValue(c); + } + + } + }, + // CharacterReferenceInAttributeValue state handled inline + AfterAttributeValue_quoted { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(BeforeAttributeName); + break; + case '/': + t.transition(SelfClosingStartTag); + break; + case '>': + t.emitTagPending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + default: + t.error(this); + r.unconsume(); + t.transition(BeforeAttributeName); + } + + } + }, + SelfClosingStartTag { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '>': + t.tagPending.selfClosing = true; + t.emitTagPending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.transition(Data); + break; + default: + t.error(this); + r.unconsume(); + t.transition(BeforeAttributeName); + } + } + }, + BogusComment { + void read(Tokeniser t, CharacterReader r) { + // todo: handle bogus comment starting from eof. when does that trigger? + // rewind to capture character that lead us here + r.unconsume(); + Token.Comment comment = new Token.Comment(); + comment.bogus = true; + comment.data.append(r.consumeTo('>')); + // todo: replace nullChar with replaceChar + t.emit(comment); + t.advanceTransition(Data); + } + }, + MarkupDeclarationOpen { + void read(Tokeniser t, CharacterReader r) { + if (r.matchConsume("--")) { + t.createCommentPending(); + t.transition(CommentStart); + } else if (r.matchConsumeIgnoreCase("DOCTYPE")) { + t.transition(Doctype); + } else if (r.matchConsume("[CDATA[")) { + // todo: should actually check current namepspace, and only non-html allows cdata. until namespace + // is implemented properly, keep handling as cdata + //} else if (!t.currentNodeInHtmlNS() && r.matchConsume("[CDATA[")) { + t.createTempBuffer(); + t.transition(CdataSection); + } else { + t.error(this); + t.advanceTransition(BogusComment); // advance so this character gets in bogus comment data's rewind + } + } + }, + CommentStart { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '-': + t.transition(CommentStartDash); + break; + case nullChar: + t.error(this); + t.commentPending.data.append(replacementChar); + t.transition(Comment); + break; + case '>': + t.error(this); + t.emitCommentPending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.emitCommentPending(); + t.transition(Data); + break; + default: + t.commentPending.data.append(c); + t.transition(Comment); + } + } + }, + CommentStartDash { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '-': + t.transition(CommentStartDash); + break; + case nullChar: + t.error(this); + t.commentPending.data.append(replacementChar); + t.transition(Comment); + break; + case '>': + t.error(this); + t.emitCommentPending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.emitCommentPending(); + t.transition(Data); + break; + default: + t.commentPending.data.append(c); + t.transition(Comment); + } + } + }, + Comment { + void read(Tokeniser t, CharacterReader r) { + char c = r.current(); + switch (c) { + case '-': + t.advanceTransition(CommentEndDash); + break; + case nullChar: + t.error(this); + r.advance(); + t.commentPending.data.append(replacementChar); + break; + case eof: + t.eofError(this); + t.emitCommentPending(); + t.transition(Data); + break; + default: + t.commentPending.data.append(r.consumeToAny('-', nullChar)); + } + } + }, + CommentEndDash { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '-': + t.transition(CommentEnd); + break; + case nullChar: + t.error(this); + t.commentPending.data.append('-').append(replacementChar); + t.transition(Comment); + break; + case eof: + t.eofError(this); + t.emitCommentPending(); + t.transition(Data); + break; + default: + t.commentPending.data.append('-').append(c); + t.transition(Comment); + } + } + }, + CommentEnd { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '>': + t.emitCommentPending(); + t.transition(Data); + break; + case nullChar: + t.error(this); + t.commentPending.data.append("--").append(replacementChar); + t.transition(Comment); + break; + case '!': + t.error(this); + t.transition(CommentEndBang); + break; + case '-': + t.error(this); + t.commentPending.data.append('-'); + break; + case eof: + t.eofError(this); + t.emitCommentPending(); + t.transition(Data); + break; + default: + t.error(this); + t.commentPending.data.append("--").append(c); + t.transition(Comment); + } + } + }, + CommentEndBang { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '-': + t.commentPending.data.append("--!"); + t.transition(CommentEndDash); + break; + case '>': + t.emitCommentPending(); + t.transition(Data); + break; + case nullChar: + t.error(this); + t.commentPending.data.append("--!").append(replacementChar); + t.transition(Comment); + break; + case eof: + t.eofError(this); + t.emitCommentPending(); + t.transition(Data); + break; + default: + t.commentPending.data.append("--!").append(c); + t.transition(Comment); + } + } + }, + Doctype { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(BeforeDoctypeName); + break; + case eof: + t.eofError(this); + // note: fall through to > case + case '>': // catch invalid + t.error(this); + t.createDoctypePending(); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.transition(BeforeDoctypeName); + } + } + }, + BeforeDoctypeName { + void read(Tokeniser t, CharacterReader r) { + if (r.matchesLetter()) { + t.createDoctypePending(); + t.transition(DoctypeName); + return; + } + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + break; // ignore whitespace + case nullChar: + t.error(this); + t.createDoctypePending(); + t.doctypePending.name.append(replacementChar); + t.transition(DoctypeName); + break; + case eof: + t.eofError(this); + t.createDoctypePending(); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.createDoctypePending(); + t.doctypePending.name.append(c); + t.transition(DoctypeName); + } + } + }, + DoctypeName { + void read(Tokeniser t, CharacterReader r) { + if (r.matchesLetter()) { + String name = r.consumeLetterSequence(); + t.doctypePending.name.append(name); + return; + } + char c = r.consume(); + switch (c) { + case '>': + t.emitDoctypePending(); + t.transition(Data); + break; + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(AfterDoctypeName); + break; + case nullChar: + t.error(this); + t.doctypePending.name.append(replacementChar); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.doctypePending.name.append(c); + } + } + }, + AfterDoctypeName { + void read(Tokeniser t, CharacterReader r) { + if (r.isEmpty()) { + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + return; + } + if (r.matchesAny('\t', '\n', '\r', '\f', ' ')) + r.advance(); // ignore whitespace + else if (r.matches('>')) { + t.emitDoctypePending(); + t.advanceTransition(Data); + } else if (r.matchConsumeIgnoreCase(DocumentType.PUBLIC_KEY)) { + t.doctypePending.pubSysKey = DocumentType.PUBLIC_KEY; + t.transition(AfterDoctypePublicKeyword); + } else if (r.matchConsumeIgnoreCase(DocumentType.SYSTEM_KEY)) { + t.doctypePending.pubSysKey = DocumentType.SYSTEM_KEY; + t.transition(AfterDoctypeSystemKeyword); + } else { + t.error(this); + t.doctypePending.forceQuirks = true; + t.advanceTransition(BogusDoctype); + } + + } + }, + AfterDoctypePublicKeyword { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(BeforeDoctypePublicIdentifier); + break; + case '"': + t.error(this); + // set public id to empty string + t.transition(DoctypePublicIdentifier_doubleQuoted); + break; + case '\'': + t.error(this); + // set public id to empty string + t.transition(DoctypePublicIdentifier_singleQuoted); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.doctypePending.forceQuirks = true; + t.transition(BogusDoctype); + } + } + }, + BeforeDoctypePublicIdentifier { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + break; + case '"': + // set public id to empty string + t.transition(DoctypePublicIdentifier_doubleQuoted); + break; + case '\'': + // set public id to empty string + t.transition(DoctypePublicIdentifier_singleQuoted); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.doctypePending.forceQuirks = true; + t.transition(BogusDoctype); + } + } + }, + DoctypePublicIdentifier_doubleQuoted { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '"': + t.transition(AfterDoctypePublicIdentifier); + break; + case nullChar: + t.error(this); + t.doctypePending.publicIdentifier.append(replacementChar); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.doctypePending.publicIdentifier.append(c); + } + } + }, + DoctypePublicIdentifier_singleQuoted { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\'': + t.transition(AfterDoctypePublicIdentifier); + break; + case nullChar: + t.error(this); + t.doctypePending.publicIdentifier.append(replacementChar); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.doctypePending.publicIdentifier.append(c); + } + } + }, + AfterDoctypePublicIdentifier { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(BetweenDoctypePublicAndSystemIdentifiers); + break; + case '>': + t.emitDoctypePending(); + t.transition(Data); + break; + case '"': + t.error(this); + // system id empty + t.transition(DoctypeSystemIdentifier_doubleQuoted); + break; + case '\'': + t.error(this); + // system id empty + t.transition(DoctypeSystemIdentifier_singleQuoted); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.doctypePending.forceQuirks = true; + t.transition(BogusDoctype); + } + } + }, + BetweenDoctypePublicAndSystemIdentifiers { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + break; + case '>': + t.emitDoctypePending(); + t.transition(Data); + break; + case '"': + t.error(this); + // system id empty + t.transition(DoctypeSystemIdentifier_doubleQuoted); + break; + case '\'': + t.error(this); + // system id empty + t.transition(DoctypeSystemIdentifier_singleQuoted); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.doctypePending.forceQuirks = true; + t.transition(BogusDoctype); + } + } + }, + AfterDoctypeSystemKeyword { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(BeforeDoctypeSystemIdentifier); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case '"': + t.error(this); + // system id empty + t.transition(DoctypeSystemIdentifier_doubleQuoted); + break; + case '\'': + t.error(this); + // system id empty + t.transition(DoctypeSystemIdentifier_singleQuoted); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + } + } + }, + BeforeDoctypeSystemIdentifier { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + break; + case '"': + // set system id to empty string + t.transition(DoctypeSystemIdentifier_doubleQuoted); + break; + case '\'': + // set public id to empty string + t.transition(DoctypeSystemIdentifier_singleQuoted); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.doctypePending.forceQuirks = true; + t.transition(BogusDoctype); + } + } + }, + DoctypeSystemIdentifier_doubleQuoted { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '"': + t.transition(AfterDoctypeSystemIdentifier); + break; + case nullChar: + t.error(this); + t.doctypePending.systemIdentifier.append(replacementChar); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.doctypePending.systemIdentifier.append(c); + } + } + }, + DoctypeSystemIdentifier_singleQuoted { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\'': + t.transition(AfterDoctypeSystemIdentifier); + break; + case nullChar: + t.error(this); + t.doctypePending.systemIdentifier.append(replacementChar); + break; + case '>': + t.error(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.doctypePending.systemIdentifier.append(c); + } + } + }, + AfterDoctypeSystemIdentifier { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + break; + case '>': + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.eofError(this); + t.doctypePending.forceQuirks = true; + t.emitDoctypePending(); + t.transition(Data); + break; + default: + t.error(this); + t.transition(BogusDoctype); + // NOT force quirks + } + } + }, + BogusDoctype { + void read(Tokeniser t, CharacterReader r) { + char c = r.consume(); + switch (c) { + case '>': + t.emitDoctypePending(); + t.transition(Data); + break; + case eof: + t.emitDoctypePending(); + t.transition(Data); + break; + default: + // ignore char + break; + } + } + }, + CdataSection { + void read(Tokeniser t, CharacterReader r) { + String data = r.consumeTo("]]>"); + t.dataBuffer.append(data); + if (r.matchConsume("]]>") || r.isEmpty()) { + t.emit(new Token.CData(t.dataBuffer.toString())); + t.transition(Data); + }// otherwise, buffer underrun, stay in data section + } + }; + + + abstract void read(Tokeniser t, CharacterReader r); + + static final char nullChar = '\u0000'; + // char searches. must be sorted, used in inSorted. MUST update TokenisetStateTest if more arrays are added. + static final char[] attributeSingleValueCharsSorted = new char[]{nullChar, '&', '\''}; + static final char[] attributeDoubleValueCharsSorted = new char[]{nullChar, '"', '&'}; + static final char[] attributeNameCharsSorted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '\'', '/', '<', '=', '>'}; + static final char[] attributeValueUnquoted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '&', '\'', '<', '=', '>', '`'}; + + private static final char replacementChar = Tokeniser.replacementChar; + private static final String replacementStr = String.valueOf(Tokeniser.replacementChar); + private static final char eof = CharacterReader.EOF; + + /** + * Handles RawtextEndTagName, ScriptDataEndTagName, and ScriptDataEscapedEndTagName. Same body impl, just + * different else exit transitions. + */ + private static void handleDataEndTag(Tokeniser t, CharacterReader r, TokeniserState elseTransition) { + if (r.matchesLetter()) { + String name = r.consumeLetterSequence(); + t.tagPending.appendTagName(name); + t.dataBuffer.append(name); + return; + } + + boolean needsExitTransition = false; + if (t.isAppropriateEndTagToken() && !r.isEmpty()) { + char c = r.consume(); + switch (c) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + t.transition(BeforeAttributeName); + break; + case '/': + t.transition(SelfClosingStartTag); + break; + case '>': + t.emitTagPending(); + t.transition(Data); + break; + default: + t.dataBuffer.append(c); + needsExitTransition = true; + } + } else { + needsExitTransition = true; + } + + if (needsExitTransition) { + t.emit("': + if (t.dataBuffer.toString().equals("script")) + t.transition(primary); + else + t.transition(fallback); + t.emit(c); + break; + default: + r.unconsume(); + t.transition(fallback); + } + } +} diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacementTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacementTest.java new file mode 100644 index 00000000..eab3a9f1 --- /dev/null +++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacementTest.java @@ -0,0 +1,47 @@ +package ru.noties.markwon.html.impl; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; + +import ru.noties.markwon.html.api.HtmlTag; +import ru.noties.markwon.html.impl.HtmlTagImpl.InlineImpl; + +import static org.junit.Assert.assertEquals; + +public class HtmlEmptyTagReplacementTest { + + private HtmlEmptyTagReplacement replacement; + + @Before + public void before() { + replacement = HtmlEmptyTagReplacement.create(); + } + + @Test + public void imageReplacementNoAlt() { + final HtmlTag.Inline img = new InlineImpl("img", -1, Collections.emptyMap()); + assertEquals("\uFFFC", replacement.replace(img)); + } + + @Test + public void imageReplacementAlt() { + final HtmlTag.Inline img = new InlineImpl( + "img", + -1, + Collections.singletonMap("alt", "alternative27") + ); + assertEquals("alternative27", replacement.replace(img)); + } + + @Test + public void brAddsNewLine() { + final HtmlTag.Inline br = new InlineImpl( + "br", + -1, + Collections.emptyMap() + ); + assertEquals("\n", replacement.replace(br)); + } +} \ No newline at end of file diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java new file mode 100644 index 00000000..ad0669b3 --- /dev/null +++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java @@ -0,0 +1,885 @@ +package ru.noties.markwon.html.impl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import ru.noties.markwon.html.api.HtmlTag; +import ru.noties.markwon.html.api.MarkwonHtmlParser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class MarkwonHtmlParserImplTest { + + @Test + public void inlineTags() { + + // all inline tags are correctly parsed + + // a simple replacement that will return tag name as replacement (for this test purposes) + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() { + @Nullable + @Override + public String replace(@NonNull HtmlTag tag) { + return tag.name(); + } + }); + + // all inline tags are parsed as ones + final List tags = new ArrayList<>(MarkwonHtmlParserImpl.INLINE_TAGS); + + final StringBuilder html = new StringBuilder(); + for (String tag : tags) { + html.append('<') + .append(tag) + .append('>') + .append(tag) + .append("'); + } + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, html.toString()); + + final CaptureInlineTagsAction action = new CaptureInlineTagsAction(); + + impl.flushInlineTags(output.length(), action); + + assertTrue(action.called); + + final List inlines = action.tags; + + if (tags.size() != inlines.size()) { + final Set missing = new HashSet<>(tags); + for (HtmlTag.Inline inline : inlines) { + missing.remove(inline.name()); + } + assertTrue("Missing inline tags: " + missing, false); + } + + final Set set = new HashSet<>(tags); + + for (HtmlTag.Inline inline : inlines) { + assertTrue(set.remove(inline.name())); + assertEquals(inline.name(), output.substring(inline.start(), inline.end())); + } + + assertEquals(0, set.size()); + } + + @Test + public void inlineVoidTags() { + + // all inline void tags are correctly parsed + + final List tags = Arrays.asList( + "br", + "img", "input" + ); + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() { + @Nullable + @Override + public String replace(@NonNull HtmlTag tag) { + return null; + } + }); + + final StringBuilder html = new StringBuilder(); + for (String tag : tags) { + html.append('<') + .append(tag) + .append('>'); + } + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, html.toString()); + + assertEquals(0, output.length()); + + final CaptureInlineTagsAction action = new CaptureInlineTagsAction(); + + impl.flushInlineTags(output.length(), action); + + assertTrue(action.called); + + final List inlines = action.tags; + + assertEquals(inlines.toString(), tags.size(), inlines.size()); + + final Set set = new HashSet<>(tags); + + for (HtmlTag.Inline inline : inlines) { + assertEquals(inline.name(), inline.start(), inline.end()); + assertTrue(inline.name(), inline.isEmpty()); + assertTrue(set.remove(inline.name())); + } + + assertEquals(set.toString(), 0, set.size()); + } + + @Test + public void blockVoidTags() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() { + @Nullable + @Override + public String replace(@NonNull HtmlTag tag) { + return null; + } + }); + + final List tags = Arrays.asList( + "area", + "base", + "col", + "embed", + "hr", + "keygen", + "link", + "meta", + "param", + "source", + "track", + "wbr" + ); + + final StringBuilder html = new StringBuilder(); + for (String tag : tags) { + html.append('<') + .append(tag) + .append('>'); + } + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, html.toString()); + + assertEquals(0, output.length()); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + + final List blocks = action.tags; + + assertEquals(blocks.toString(), tags.size(), blocks.size()); + + final Set set = new HashSet<>(tags); + + for (HtmlTag.Block block : blocks) { + assertEquals(block.name(), block.start(), block.end()); + assertTrue(block.name(), block.isEmpty()); + assertTrue(set.remove(block.name())); + } + + assertEquals(set.toString(), 0, set.size()); + } + + @Test + public void selfClosingTags() { + + // self-closing tags (grammatically) must be replaced (no checks for real html) + + final List tags = Arrays.asList( + "one", + "two-two", + "three-three-three", + "four-four-four-four", + "FiveFiveFiveFiveFive" + ); + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() { + @Nullable + @Override + public String replace(@NonNull HtmlTag tag) { + return null; + } + }); + + final StringBuilder html = new StringBuilder(); + for (String tag : tags) { + html.append('<') + .append(tag) + .append(" />"); + } + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, html.toString()); + + assertEquals(output.toString(), 0, output.length()); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + + final List blocks = action.tags; + + assertEquals(blocks.toString(), tags.size(), blocks.size()); + + // tag names must be lower cased + final Set set = new HashSet<>(tags.size()); + for (String tag : tags) { + set.add(tag.toLowerCase()); + } + + for (HtmlTag.Block block : blocks) { + assertTrue(block.name(), block.isEmpty()); + assertTrue(set.remove(block.name())); + } + + assertEquals(set.toString(), 0, set.size()); + } + + @Test + public void blockTags() { + + // the tags that will require a new line before them + + final List tags = Arrays.asList( + "address", "article", "aside", + "blockquote", + "canvas", + "dd", "div", "dl", "dt", + "fieldset", "figcaption", "figure", "footer", "form", + "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", + "li", + "main", + "nav", "noscript", + "ol", "output", + "p", "pre", + "section", + "table", "tfoot", + "ul", + "video" + ); + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() { + @Override + public String replace(@NonNull HtmlTag tag) { + return tag.name(); + } + }); + + final StringBuilder html = new StringBuilder(); + for (String tag : tags) { + html.append('<') + .append(tag) + .append('>') + .append(tag) + .append("'); + } + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, html.toString()); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + + final List blocks = action.tags; + assertEquals(blocks.toString(), tags.size(), blocks.size()); + + final Set set = new HashSet<>(tags); + + boolean first = true; + for (HtmlTag.Block block : blocks) { + assertEquals(block.name(), block.name(), output.substring(block.start(), block.end())); + if (first) { + first = false; + } else { + assertEquals('\n', output.charAt(block.start() - 1)); + } + assertTrue(set.remove(block.name())); + } + + assertEquals(set.toString(), 0, set.size()); + } + + @Test + public void multipleFragmentsContinuation() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement()); + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, ""); + output.append("italic "); + impl.processFragment(output, ""); + + final CaptureInlineTagsAction action = new CaptureInlineTagsAction(); + impl.flushInlineTags(output.length(), action); + + assertTrue(action.called); + + final List inlines = action.tags; + assertEquals(inlines.toString(), 1, inlines.size()); + + final HtmlTag.Inline inline = inlines.get(0); + assertEquals("i", inline.name()); + assertEquals(0, inline.start()); + assertEquals(output.length(), inline.end()); + assertEquals("italic ", output.toString()); + } + + @Test + public void paragraphCannotContainAnythingButInlines() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "

italic bold italic

in-div
"); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + final List inlines = inlineTagsAction.tags; + final List blocks = blockTagsAction.tags; + + assertEquals(2, inlines.size()); + assertEquals(2, blocks.size()); + + // inlines will be closed at the end of the document + // P will be closed right before
+ + with(inlines.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + assertEquals("i", inline.name()); + assertEquals(0, inline.start()); + assertEquals(output.length(), inline.end()); + } + }); + + with(inlines.get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + assertEquals("b", inline.name()); + assertEquals("italic ".length(), inline.start()); + assertEquals(output.length(), inline.end()); + } + }); + + with(blocks.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("p", block.name()); + assertEquals(0, block.start()); + assertEquals(output.indexOf("in-div") - 1, block.end()); + } + }); + + with(blocks.get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div", block.name()); + assertEquals(output.indexOf("in-div"), block.start()); + assertEquals(output.length(), block.end()); + } + }); + } + + @Test + public void blockCloseClosesChildren() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + final String html = "12hello!"; + impl.processFragment(output, html); + + assertEquals(output.toString(), "12hello!", output.toString()); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + assertEquals(1, action.tags.size()); + + with(action.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + + final int end = output.length(); + + assertEquals("div-1", block.name()); + assertEquals(0, block.start()); + assertEquals(end, block.end()); + assertEquals(1, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div-2", block.name()); + assertEquals(1, block.start()); + assertEquals(end, block.end()); + assertEquals(1, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div-3", block.name()); + assertEquals(2, block.start()); + assertEquals(end, block.end()); + assertEquals(0, block.children().size()); + } + }); + } + }); + } + }); + } + + @Test + public void allTagsAreLowerCase() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + impl.processFragment(output, "
italic emphasis italic
"); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + with(inlineTagsAction.tags, new Action>() { + @Override + public void apply(@NonNull List inlines) { + + assertEquals(2, inlines.size()); + + with(inlines.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + assertEquals("i", inline.name()); + assertEquals(0, inline.start()); + assertEquals(output.length(), inline.end()); + } + }); + + with(inlines.get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + + assertEquals("em", inline.name()); + + final int start = "italic ".length(); + assertEquals(start, inline.start()); + assertEquals(start + ("emphasis".length()), inline.end()); + } + }); + } + }); + + assertEquals(1, blockTagsAction.tags.size()); + + with(blockTagsAction.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div", block.name()); + assertEquals(0, block.start()); + assertEquals(output.length(), block.end()); + } + }); + } + + @Test + public void previousListItemClosed() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + final String html = "
  • UL-First
  • UL-Second
    1. OL-First
    2. OL-Second
  • UL-Third"; + + impl.processFragment(output, html); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + assertEquals(1, action.tags.size()); + + with(action.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + + assertEquals("ul", block.name()); + assertEquals(3, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("UL-First", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + + with(block.children().get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + + // this block will contain nested block text also + assertEquals("UL-Second\nOL-First\nOL-Second", output.substring(block.start(), block.end())); + assertEquals(1, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("ol", block.name()); + assertEquals(2, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("OL-First", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + + with(block.children().get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("OL-Second", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + } + }); + } + }); + + with(block.children().get(2), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("UL-Third", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + } + }); + } + + @Test + public void attributes() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "my-content"); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + assertEquals(1, action.tags.size()); + + with(action.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + + assertEquals("my-tag", block.name()); + + with(block.attributes(), new Action>() { + @Override + public void apply(@NonNull Map attributes) { + assertEquals(5, attributes.size()); + assertEquals("no-name", attributes.get("name")); + assertEquals("doSomething", attributes.get(":click")); + assertEquals("focus", attributes.get("@focus")); + assertEquals("blur", attributes.get("@blur.native")); + assertEquals("@id/id", attributes.get("android:id")); + } + }); + } + }); + } + + @Test + public void flushCloseTagsIfRequested() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "
    divibemstrong"); + + final int end = output.length(); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(end, inlineTagsAction); + impl.flushBlockTags(end, blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + with(inlineTagsAction.tags, new Action>() { + @Override + public void apply(@NonNull List inlines) { + assertEquals(4, inlines.size()); + for (HtmlTag.Inline inline : inlines) { + assertTrue(inline.isClosed()); + assertEquals(end, inline.end()); + } + } + }); + + assertEquals(1, blockTagsAction.tags.size()); + with(blockTagsAction.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertTrue(block.isClosed()); + assertEquals(end, block.end()); + } + }); + } + + @Test + public void flushDoesNotCloseTagsIfNoEndRequested() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "
    divibemstrong"); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(HtmlTag.NO_END, inlineTagsAction); + impl.flushBlockTags(HtmlTag.NO_END, blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + with(inlineTagsAction.tags, new Action>() { + @Override + public void apply(@NonNull List inlines) { + assertEquals(4, inlines.size()); + for (HtmlTag.Inline inline : inlines) { + assertFalse(inline.isClosed()); + assertEquals(HtmlTag.NO_END, inline.end()); + } + } + }); + + assertEquals(1, blockTagsAction.tags.size()); + + with(blockTagsAction.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertFalse(block.isClosed()); + assertEquals(HtmlTag.NO_END, block.end()); + } + }); + } + + @Test + public void flushClearsInternalState() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + impl.processFragment(output, "

    italic bold italic

    paragraph

    and a div
    "); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + assertEquals(2, inlineTagsAction.tags.size()); + assertEquals(3, blockTagsAction.tags.size()); + + final CaptureInlineTagsAction captureInlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction captureBlockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), captureInlineTagsAction); + impl.flushBlockTags(output.length(), captureBlockTagsAction); + + assertTrue(captureInlineTagsAction.called); + assertTrue(captureBlockTagsAction.called); + + assertEquals(0, captureInlineTagsAction.tags.size()); + assertEquals(0, captureBlockTagsAction.tags.size()); + } + + @Test + public void resetClearsBothInlinesAndBlocks() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "

    paragraph italic

    div
    "); + + impl.reset(); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + assertEquals(0, inlineTagsAction.tags.size()); + assertEquals(0, blockTagsAction.tags.size()); + } + + @Test + public void blockTagNewLine() { + + // we should make sure that a block tag will have a new line for it's + // content (white spaces before should be ignored) + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final String html = "
      " + + "
    • ul-first" + + "
    • ul-second" + + "
        " + + "
      1. ol-first" + + "
      2. ol-second" + + "
      " + + "
    • ul-third" + + "
    "; + + final StringBuilder output = new StringBuilder(); + impl.processFragment(output, html); + + final String[] split = output.toString().split("\n"); + assertEquals(Arrays.toString(split), 5, split.length); + } + + @Test + public void attributesAreLowerCase() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, ""); + + final CaptureInlineTagsAction action = new CaptureInlineTagsAction(); + impl.flushInlineTags(output.length(), action); + + assertTrue(action.called); + assertEquals(1, action.tags.size()); + + with(action.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + + assertEquals("i", inline.name()); + + with(inline.attributes(), new Action>() { + @Override + public void apply(@NonNull Map map) { + assertEquals(3, map.size()); + assertEquals("my-class", map.get("class")); + assertEquals("", map.get("disabled")); + assertEquals("there", map.get("@hello")); + } + }); + } + }); + } + + @Test + public void newLineAfterBlockTag() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + final String[] fragments = { + "

    head #1

    just text", + "

    head #2

    in span tag", + "

    head #3

    in custom-tag" + }; + + for (String fragment: fragments) { + impl.processFragment(output, fragment); + } + + final String expected = "" + + "head #1\njust text\n" + + "head #2\nin span tag\n" + + "head #3\nin custom-tag"; + + assertEquals(expected, output.toString()); + } + + private static class CaptureTagsAction implements MarkwonHtmlParser.FlushAction { + + boolean called; + List tags; + + @Override + public void apply(@NonNull List tags) { + this.called = true; + this.tags = new ArrayList<>(tags); + } + } + + private static class CaptureInlineTagsAction extends CaptureTagsAction { + } + + private static class CaptureBlockTagsAction extends CaptureTagsAction { + } + + private interface Action { + void apply(@NonNull T t); + } + + private static void with(@NonNull T t, @NonNull Action action) { + action.apply(t); + } +} \ No newline at end of file diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/TrimmingAppenderTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/TrimmingAppenderTest.java new file mode 100644 index 00000000..87923c33 --- /dev/null +++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/TrimmingAppenderTest.java @@ -0,0 +1,43 @@ +package ru.noties.markwon.html.impl; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class TrimmingAppenderTest { + + private TrimmingAppender.Impl impl; + + @Before + public void before() { + impl = new TrimmingAppender.Impl(); + } + + @Test + public void singlePart() { + final String input = " html body \n\ndiv hey "; + final StringBuilder builder = new StringBuilder(); + impl.append(builder, input); + assertEquals("html body div hey ", builder.toString()); + } + + @Test + public void multiParts() { + final String[] inputs = { + "\n\n\n\n\nhtml\t body\n\ndiv ", + " span and go" + }; + final StringBuilder builder = new StringBuilder(); + for (String input : inputs) { + impl.append(builder, input); + } + + assertEquals("html body div span and go", builder.toString()); + } +} \ No newline at end of file diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntitiesTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntitiesTest.java new file mode 100644 index 00000000..ab013578 --- /dev/null +++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/jsoup/nodes/CommonMarkEntitiesTest.java @@ -0,0 +1,22 @@ +package ru.noties.markwon.html.impl.jsoup.nodes; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class CommonMarkEntitiesTest { + + @Test + public void can_access_field() { + assertTrue("&", CommonMarkEntities.isNamedEntity("amp")); + final int[] codepoints = new int[1]; + CommonMarkEntities.codepointsForName("amp", codepoints); + assertEquals('&', codepoints[0]); + } +} \ No newline at end of file diff --git a/markwon-image-loader/build.gradle b/markwon-image-loader/build.gradle new file mode 100644 index 00000000..fd4293c8 --- /dev/null +++ b/markwon-image-loader/build.gradle @@ -0,0 +1,37 @@ +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 + } + + lintOptions { + // okio.... + disable 'InvalidPackage' + } +} + +dependencies { + + api project(':markwon') + + deps.with { + api it['android-svg'] + api it['android-gif'] + api it['okhttp'] + } + + deps['test'].with { + testImplementation it['junit'] + testImplementation it['robolectric'] + } +} + +registerArtifact(this) diff --git a/library-image-loader/gradle.properties b/markwon-image-loader/gradle.properties similarity index 100% rename from library-image-loader/gradle.properties rename to markwon-image-loader/gradle.properties diff --git a/library-image-loader/src/main/AndroidManifest.xml b/markwon-image-loader/src/main/AndroidManifest.xml similarity index 100% rename from library-image-loader/src/main/AndroidManifest.xml rename to markwon-image-loader/src/main/AndroidManifest.xml diff --git a/library-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java similarity index 50% rename from library-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java rename to markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java index 7eade398..432e8797 100644 --- a/library-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java @@ -8,26 +8,18 @@ import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; -import okhttp3.Call; import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; import ru.noties.markwon.spans.AsyncDrawable; public class AsyncDrawableLoader implements AsyncDrawable.Loader { @@ -42,25 +34,19 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { return new Builder(); } - private static final String HEADER_CONTENT_TYPE = "Content-Type"; - - private static final String FILE_ANDROID_ASSETS = "android_asset"; - - private final OkHttpClient client; - private final Resources resources; private final ExecutorService executorService; private final Handler mainThread; private final Drawable errorDrawable; + private final Map schemeHandlers; private final List mediaDecoders; private final Map> requests; AsyncDrawableLoader(Builder builder) { - this.client = builder.client; - this.resources = builder.resources; this.executorService = builder.executorService; this.mainThread = new Handler(Looper.getMainLooper()); this.errorDrawable = builder.errorDrawable; + this.schemeHandlers = builder.schemeHandlers; this.mediaDecoders = builder.mediaDecoders; this.requests = new HashMap<>(3); } @@ -80,56 +66,64 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { request.cancel(true); } - final List calls = client.dispatcher().queuedCalls(); - if (calls != null) { - for (Call call : calls) { - if (!call.isCanceled()) { - if (destination.equals(call.request().tag())) { - call.cancel(); - } - } - } + for (SchemeHandler schemeHandler : schemeHandlers.values()) { + schemeHandler.cancel(destination); } } private Future execute(@NonNull final String destination, @NonNull AsyncDrawable drawable) { + final WeakReference reference = new WeakReference(drawable); - // todo, if not a link -> show placeholder + + // todo: should we cancel pending request for the same destination? + // we _could_ but there is possibility that one resource is request in multiple places + + // todo: error handing (simply applying errorDrawable is not a good solution + // as reason for an error is unclear (no scheme handler, no input data, error decoding, etc) + + // todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal + // for big images for sure. We _could_ introduce internal Drawable that will check for + // image bounds (but we will need to cache inputStream in order to inspect and optimize + // input image...) + return executorService.submit(new Runnable() { @Override public void run() { - final Item item; - final boolean isFromFile; + final ImageItem item; final Uri uri = Uri.parse(destination); - if ("file".equals(uri.getScheme())) { - item = fromFile(uri); - isFromFile = true; + + final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme()); + if (schemeHandler != null) { + item = schemeHandler.handle(destination, uri); } else { - item = fromNetwork(destination); - isFromFile = false; + item = null; } + final InputStream inputStream = item != null + ? item.inputStream() + : null; + Drawable result = null; - if (item != null - && item.inputStream != null) { + if (inputStream != null) { try { - final MediaDecoder mediaDecoder = isFromFile - ? mediaDecoderFromFile(item.fileName) - : mediaDecoderFromContentType(item.contentType); + final String fileName = item.fileName(); + final MediaDecoder mediaDecoder = fileName != null + ? mediaDecoderFromFile(fileName) + : mediaDecoderFromContentType(item.contentType()); if (mediaDecoder != null) { - result = mediaDecoder.decode(item.inputStream); + result = mediaDecoder.decode(inputStream); } } finally { try { - item.inputStream.close(); + inputStream.close(); } catch (IOException e) { - // no op + // ignored } } } @@ -157,88 +151,6 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { }); } - @Nullable - private Item fromFile(@NonNull Uri uri) { - - final List segments = uri.getPathSegments(); - if (segments == null - || segments.size() == 0) { - // pointing to file & having no path segments is no use - return null; - } - - final Item out; - final InputStream inputStream; - - final boolean assets = FILE_ANDROID_ASSETS.equals(segments.get(0)); - final String fileName = uri.getLastPathSegment(); - - if (assets) { - final StringBuilder path = new StringBuilder(); - for (int i = 1, size = segments.size(); i < size; i++) { - if (i != 1) { - path.append('/'); - } - path.append(segments.get(i)); - } - // load assets - InputStream inner = null; - try { - inner = resources.getAssets().open(path.toString()); - } catch (IOException e) { - e.printStackTrace(); - } - inputStream = inner; - } else { - InputStream inner = null; - try { - inner = new BufferedInputStream(new FileInputStream(new File(uri.getPath()))); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - inputStream = inner; - } - - if (inputStream != null) { - out = new Item(fileName, null, inputStream); - } else { - out = null; - } - - return out; - } - - @Nullable - private Item fromNetwork(@NonNull String destination) { - - Item out = null; - - final Request request = new Request.Builder() - .url(destination) - .tag(destination) - .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 Item(null, contentType, inputStream); - } - } - } - - return out; - } - @Nullable private MediaDecoder mediaDecoderFromFile(@NonNull String fileName) { @@ -269,18 +181,38 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { return out; } + // todo: as now we have different layers of abstraction (for scheme handling and media decoding) + // we no longer should add dependencies implicitly, it would be way better to allow adding + // multiple artifacts (file, data, network, svg, gif)... at least, maybe we can extract API + // for this module (without implementations), but keep _all-in_ (fat) artifact with all of these. public static class Builder { + /** + * @deprecated 2.0.0 add {@link NetworkSchemeHandler} directly + */ + @Deprecated private OkHttpClient client; + + /** + * @deprecated 2.0.0 construct {@link MediaDecoder} and {@link SchemeHandler} appropriately + */ + @Deprecated private Resources resources; + private ExecutorService executorService; private Drawable errorDrawable; // @since 1.1.0 private final List mediaDecoders = new ArrayList<>(3); + // @since 2.0.0 + private final Map schemeHandlers = new HashMap<>(3); + /** + * @deprecated 2.0.0 add {@link NetworkSchemeHandler} directly + */ @NonNull + @Deprecated public Builder client(@NonNull OkHttpClient client) { this.client = client; return this; @@ -310,40 +242,148 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { return this; } + /** + * @since 2.0.0 + */ + @SuppressWarnings("UnusedReturnValue") @NonNull - public Builder mediaDecoders(@NonNull List mediaDecoders) { - this.mediaDecoders.clear(); - this.mediaDecoders.addAll(mediaDecoders); + public Builder addSchemeHandler(@NonNull SchemeHandler schemeHandler) { + + SchemeHandler previous; + + for (String scheme : schemeHandler.schemes()) { + previous = schemeHandlers.put(scheme, schemeHandler); + if (previous != null) { + throw new IllegalStateException(String.format("Multiple scheme handlers handle " + + "the same scheme: `%s`, %s %s", scheme, previous, schemeHandler)); + } + } + return this; } + /** + * @see #addMediaDecoder(MediaDecoder) + * @see #addMediaDecoders(MediaDecoder...) + * @see #addMediaDecoders(Iterable) + * @since 1.1.0 + * @deprecated 2.0.0 + */ + @Deprecated @NonNull - public Builder mediaDecoders(MediaDecoder... mediaDecoders) { - this.mediaDecoders.clear(); - if (mediaDecoders != null - && mediaDecoders.length > 0) { - Collections.addAll(this.mediaDecoders, mediaDecoders); + public Builder mediaDecoders(@NonNull List mediaDecoders) { + + // previously it was clearing before adding + + for (MediaDecoder mediaDecoder : mediaDecoders) { + this.mediaDecoders.add(requireNonNull(mediaDecoder)); } + + return this; + } + + /** + * @see #addMediaDecoder(MediaDecoder) + * @see #addMediaDecoders(MediaDecoder...) + * @see #addMediaDecoders(Iterable) + * @since 1.1.0 + * @deprecated 2.0.0 + */ + @NonNull + @Deprecated + public Builder mediaDecoders(MediaDecoder... mediaDecoders) { + + // previously it was clearing before adding + + final int length = mediaDecoders != null + ? mediaDecoders.length + : 0; + + if (length > 0) { + for (int i = 0; i < length; i++) { + this.mediaDecoders.add(requireNonNull(mediaDecoders[i])); + } + } + + return this; + } + + /** + * @see SvgMediaDecoder + * @see GifMediaDecoder + * @see ImageMediaDecoder + * @since 2.0.0 + */ + @NonNull + public Builder addMediaDecoder(@NonNull MediaDecoder mediaDecoder) { + mediaDecoders.add(mediaDecoder); + return this; + } + + /** + * @see SvgMediaDecoder + * @see GifMediaDecoder + * @see ImageMediaDecoder + * @since 2.0.0 + */ + @NonNull + public Builder addMediaDecoders(@NonNull Iterable mediaDecoders) { + for (MediaDecoder mediaDecoder : mediaDecoders) { + this.mediaDecoders.add(requireNonNull(mediaDecoder)); + } + return this; + } + + /** + * @see SvgMediaDecoder + * @see GifMediaDecoder + * @see ImageMediaDecoder + * @since 2.0.0 + */ + @NonNull + public Builder addMediaDecoders(MediaDecoder... mediaDecoders) { + + final int length = mediaDecoders != null + ? mediaDecoders.length + : 0; + + if (length > 0) { + for (int i = 0; i < length; i++) { + this.mediaDecoders.add(requireNonNull(mediaDecoders[i])); + } + } + return this; } @NonNull public AsyncDrawableLoader build() { - if (client == null) { - client = new OkHttpClient(); - } - + // I think we should deprecate this... if (resources == null) { resources = Resources.getSystem(); } if (executorService == null) { - // we will use executor from okHttp - executorService = client.dispatcher().executorService(); + // @since 2.0.0 we are using newCachedThreadPool instead + // of `okHttpClient.dispatcher().executorService()` + executorService = Executors.newCachedThreadPool(); + } + + // @since 2.0.0 + // put default scheme handlers (to mimic previous behavior) + // remove in 3.0.0 with plugins + if (schemeHandlers.size() == 0) { + if (client == null) { + client = new OkHttpClient(); + } + addSchemeHandler(NetworkSchemeHandler.create(client)); + addSchemeHandler(FileSchemeHandler.createWithAssets(resources.getAssets())); + addSchemeHandler(DataUriSchemeHandler.create()); } // add default media decoders if not specified + // remove in 3.0.0 with plugins if (mediaDecoders.size() == 0) { mediaDecoders.add(SvgMediaDecoder.create(resources)); mediaDecoders.add(GifMediaDecoder.create(true)); @@ -354,16 +394,12 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { } } - private static class Item { - - final String fileName; - final String contentType; - final InputStream inputStream; - - Item(@Nullable String fileName, @Nullable String contentType, @Nullable InputStream inputStream) { - this.fileName = fileName; - this.contentType = contentType; - this.inputStream = inputStream; + // @since 2.0.0 + @NonNull + private static T requireNonNull(@Nullable T t) { + if (t == null) { + throw new NullPointerException(); } + return t; } } diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUri.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUri.java new file mode 100644 index 00000000..697b7b2e --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUri.java @@ -0,0 +1,60 @@ +package ru.noties.markwon.il; + +import android.support.annotation.Nullable; + +public class DataUri { + + private final String contentType; + private final boolean base64; + private final String data; + + public DataUri(@Nullable String contentType, boolean base64, @Nullable String data) { + this.contentType = contentType; + this.base64 = base64; + this.data = data; + } + + @Nullable + public String contentType() { + return contentType; + } + + public boolean base64() { + return base64; + } + + @Nullable + public String data() { + return data; + } + + @Override + public String toString() { + return "DataUri{" + + "contentType='" + contentType + '\'' + + ", base64=" + base64 + + ", data='" + data + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DataUri dataUri = (DataUri) o; + + if (base64 != dataUri.base64) return false; + if (contentType != null ? !contentType.equals(dataUri.contentType) : dataUri.contentType != null) + return false; + return data != null ? data.equals(dataUri.data) : dataUri.data == null; + } + + @Override + public int hashCode() { + int result = contentType != null ? contentType.hashCode() : 0; + result = 31 * result + (base64 ? 1 : 0); + result = 31 * result + (data != null ? data.hashCode() : 0); + return result; + } +} diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriDecoder.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriDecoder.java new file mode 100644 index 00000000..ffb0d840 --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriDecoder.java @@ -0,0 +1,41 @@ +package ru.noties.markwon.il; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Base64; + +public abstract class DataUriDecoder { + + @Nullable + public abstract byte[] decode(@NonNull DataUri dataUri); + + @NonNull + public static DataUriDecoder create() { + return new Impl(); + } + + static class Impl extends DataUriDecoder { + + @Nullable + @Override + public byte[] decode(@NonNull DataUri dataUri) { + + final String data = dataUri.data(); + + if (!TextUtils.isEmpty(data)) { + try { + if (dataUri.base64()) { + return Base64.decode(data.getBytes("UTF-8"), Base64.DEFAULT); + } else { + return data.getBytes("UTF-8"); + } + } catch (Throwable t) { + return null; + } + } else { + return null; + } + } + } +} diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriParser.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriParser.java new file mode 100644 index 00000000..63744a42 --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriParser.java @@ -0,0 +1,79 @@ +package ru.noties.markwon.il; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public abstract class DataUriParser { + + @Nullable + public abstract DataUri parse(@NonNull String input); + + + @NonNull + public static DataUriParser create() { + return new Impl(); + } + + static class Impl extends DataUriParser { + + @Nullable + @Override + public DataUri parse(@NonNull String input) { + + final int index = input.indexOf(','); + // we expect exactly one comma + if (index < 0) { + return null; + } + + final String contentType; + final boolean base64; + + if (index > 0) { + final String part = input.substring(0, index); + final String[] parts = part.split(";"); + final int length = parts.length; + if (length > 0) { + // if one: either content-type or base64 + if (length == 1) { + final String value = parts[0]; + if ("base64".equals(value)) { + contentType = null; + base64 = true; + } else { + contentType = value.indexOf('/') > -1 + ? value + : null; + base64 = false; + } + } else { + contentType = parts[0].indexOf('/') > -1 + ? parts[0] + : null; + base64 = "base64".equals(parts[length - 1]); + } + } else { + contentType = null; + base64 = false; + } + } else { + contentType = null; + base64 = false; + } + + final String data; + if (index < input.length()) { + final String value = input.substring(index + 1, input.length()).replaceAll("\n", ""); + if (value.length() == 0) { + data = null; + } else { + data = value; + } + } else { + data = null; + } + + return new DataUri(contentType, base64, data); + } + } +} diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java new file mode 100644 index 00000000..73f415af --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java @@ -0,0 +1,70 @@ +package ru.noties.markwon.il; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import java.io.ByteArrayInputStream; +import java.util.Collection; +import java.util.Collections; + +/** + * @since 2.0.0 + */ +public class DataUriSchemeHandler extends SchemeHandler { + + @NonNull + public static DataUriSchemeHandler create() { + return new DataUriSchemeHandler(DataUriParser.create(), DataUriDecoder.create()); + } + + private static final String START = "data://"; + + private final DataUriParser uriParser; + private final DataUriDecoder uriDecoder; + + @SuppressWarnings("WeakerAccess") + DataUriSchemeHandler(@NonNull DataUriParser uriParser, @NonNull DataUriDecoder uriDecoder) { + this.uriParser = uriParser; + this.uriDecoder = uriDecoder; + } + + @Nullable + @Override + public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { + + if (!raw.startsWith(START)) { + return null; + } + + final String part = raw.substring(START.length()); + + final DataUri dataUri = uriParser.parse(part); + if (dataUri == null) { + return null; + } + + final byte[] bytes = uriDecoder.decode(dataUri); + if (bytes == null) { + return null; + } + + return new ImageItem( + dataUri.contentType(), + new ByteArrayInputStream(bytes), + null + ); + } + + @Override + public void cancel(@NonNull String raw) { + // no op + } + + @NonNull + @Override + public Collection schemes() { + return Collections.singleton("data"); + } +} diff --git a/library-image-loader/src/main/java/ru/noties/markwon/il/DrawableUtils.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DrawableUtils.java similarity index 100% rename from library-image-loader/src/main/java/ru/noties/markwon/il/DrawableUtils.java rename to markwon-image-loader/src/main/java/ru/noties/markwon/il/DrawableUtils.java diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/FileSchemeHandler.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/FileSchemeHandler.java new file mode 100644 index 00000000..437bbf7a --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/FileSchemeHandler.java @@ -0,0 +1,109 @@ +package ru.noties.markwon.il; + +import android.content.res.AssetManager; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * @since 2.0.0 + */ +public class FileSchemeHandler extends SchemeHandler { + + @NonNull + public static FileSchemeHandler createWithAssets(@NonNull AssetManager assetManager) { + return new FileSchemeHandler(assetManager); + } + + @NonNull + public static FileSchemeHandler create() { + return new FileSchemeHandler(null); + } + + private static final String FILE_ANDROID_ASSETS = "android_asset"; + + @Nullable + private final AssetManager assetManager; + + @SuppressWarnings("WeakerAccess") + FileSchemeHandler(@Nullable AssetManager assetManager) { + this.assetManager = assetManager; + } + + @Nullable + @Override + public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { + + final List segments = uri.getPathSegments(); + if (segments == null + || segments.size() == 0) { + // pointing to file & having no path segments is no use + return null; + } + + final ImageItem out; + + InputStream inputStream = null; + + final boolean assets = FILE_ANDROID_ASSETS.equals(segments.get(0)); + final String fileName = uri.getLastPathSegment(); + + if (assets) { + + // no handling of assets here if we have no assetsManager + if (assetManager != null) { + + final StringBuilder path = new StringBuilder(); + for (int i = 1, size = segments.size(); i < size; i++) { + if (i != 1) { + path.append('/'); + } + path.append(segments.get(i)); + } + // load assets + + try { + inputStream = assetManager.open(path.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } else { + try { + inputStream = new BufferedInputStream(new FileInputStream(new File(uri.getPath()))); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + if (inputStream != null) { + out = new ImageItem(fileName, inputStream, fileName); + } else { + out = null; + } + + return out; + } + + @Override + public void cancel(@NonNull String raw) { + // no op + } + + @NonNull + @Override + public Collection schemes() { + return Collections.singleton("file"); + } +} diff --git a/library-image-loader/src/main/java/ru/noties/markwon/il/GifMediaDecoder.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/GifMediaDecoder.java similarity index 98% rename from library-image-loader/src/main/java/ru/noties/markwon/il/GifMediaDecoder.java rename to markwon-image-loader/src/main/java/ru/noties/markwon/il/GifMediaDecoder.java index ac2a7cbf..8342e7d5 100644 --- a/library-image-loader/src/main/java/ru/noties/markwon/il/GifMediaDecoder.java +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/GifMediaDecoder.java @@ -13,6 +13,7 @@ import pl.droidsonroids.gif.GifDrawable; /** * @since 1.1.0 */ +@SuppressWarnings("WeakerAccess") public class GifMediaDecoder extends MediaDecoder { protected static final String CONTENT_TYPE_GIF = "image/gif"; diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/ImageItem.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/ImageItem.java new file mode 100644 index 00000000..3ac9e9ec --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/ImageItem.java @@ -0,0 +1,39 @@ +package ru.noties.markwon.il; + +import android.support.annotation.Nullable; + +import java.io.InputStream; + +/** + * @since 2.0.0 + */ +public class ImageItem { + + private final String contentType; + private final InputStream inputStream; + private final String fileName; + + public ImageItem( + @Nullable String contentType, + @Nullable InputStream inputStream, + @Nullable String fileName) { + this.contentType = contentType; + this.inputStream = inputStream; + this.fileName = fileName; + } + + @Nullable + public String contentType() { + return contentType; + } + + @Nullable + public InputStream inputStream() { + return inputStream; + } + + @Nullable + public String fileName() { + return fileName; + } +} diff --git a/library-image-loader/src/main/java/ru/noties/markwon/il/ImageMediaDecoder.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/ImageMediaDecoder.java similarity index 94% rename from library-image-loader/src/main/java/ru/noties/markwon/il/ImageMediaDecoder.java rename to markwon-image-loader/src/main/java/ru/noties/markwon/il/ImageMediaDecoder.java index b59ea65a..c36545ea 100644 --- a/library-image-loader/src/main/java/ru/noties/markwon/il/ImageMediaDecoder.java +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/ImageMediaDecoder.java @@ -25,6 +25,7 @@ public class ImageMediaDecoder extends MediaDecoder { private final Resources resources; + @SuppressWarnings("WeakerAccess") ImageMediaDecoder(Resources resources) { this.resources = resources; } @@ -45,6 +46,7 @@ public class ImageMediaDecoder extends MediaDecoder { final Drawable out; + // absolutely not optimal... thing final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); if (bitmap != null) { out = new BitmapDrawable(resources, bitmap); diff --git a/library-image-loader/src/main/java/ru/noties/markwon/il/MediaDecoder.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/MediaDecoder.java similarity index 100% rename from library-image-loader/src/main/java/ru/noties/markwon/il/MediaDecoder.java rename to markwon-image-loader/src/main/java/ru/noties/markwon/il/MediaDecoder.java diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/NetworkSchemeHandler.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/NetworkSchemeHandler.java new file mode 100644 index 00000000..d87c4019 --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/NetworkSchemeHandler.java @@ -0,0 +1,89 @@ +package ru.noties.markwon.il; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * @since 2.0.0 + */ +public class NetworkSchemeHandler extends SchemeHandler { + + @NonNull + public static NetworkSchemeHandler create(@NonNull OkHttpClient client) { + return new NetworkSchemeHandler(client); + } + + private static final String HEADER_CONTENT_TYPE = "Content-Type"; + + private final OkHttpClient client; + + @SuppressWarnings("WeakerAccess") + NetworkSchemeHandler(@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, null); + } + } + } + + return out; + } + + @Override + public void cancel(@NonNull String raw) { + final List calls = client.dispatcher().queuedCalls(); + if (calls != null) { + for (Call call : calls) { + if (!call.isCanceled()) { + if (raw.equals(call.request().tag())) { + call.cancel(); + } + } + } + } + } + + @NonNull + @Override + public Collection schemes() { + return Arrays.asList("http", "https"); + } +} diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java new file mode 100644 index 00000000..6d8a44d1 --- /dev/null +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java @@ -0,0 +1,25 @@ +package ru.noties.markwon.il; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Collection; + +/** + * @since 2.0.0 + */ +public abstract class SchemeHandler { + + @Nullable + public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri); + + public abstract void cancel(@NonNull String raw); + + /** + * Will be called only once during initialization, should return schemes that are + * handled by this handler + */ + @NonNull + public abstract Collection schemes(); +} diff --git a/library-image-loader/src/main/java/ru/noties/markwon/il/SvgMediaDecoder.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/SvgMediaDecoder.java similarity index 98% rename from library-image-loader/src/main/java/ru/noties/markwon/il/SvgMediaDecoder.java rename to markwon-image-loader/src/main/java/ru/noties/markwon/il/SvgMediaDecoder.java index 0bd62644..ff32255a 100644 --- a/library-image-loader/src/main/java/ru/noties/markwon/il/SvgMediaDecoder.java +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/SvgMediaDecoder.java @@ -28,6 +28,7 @@ public class SvgMediaDecoder extends MediaDecoder { private final Resources resources; + @SuppressWarnings("WeakerAccess") SvgMediaDecoder(Resources resources) { this.resources = resources; } diff --git a/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriParserTest.java b/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriParserTest.java new file mode 100644 index 00000000..6de01af5 --- /dev/null +++ b/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriParserTest.java @@ -0,0 +1,119 @@ +package ru.noties.markwon.il; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class DataUriParserTest { + + private DataUriParser.Impl impl; + + @Before + public void before() { + impl = new DataUriParser.Impl(); + } + + @Test + public void test() { + + final Map data = new LinkedHashMap() {{ + put(",", new DataUri(null, false, null)); + put("image/svg+xml;base64,!@#$%^&*(", new DataUri("image/svg+xml", true, "!@#$%^&*(")); + put("text/vnd-example+xyz;foo=bar;base64,R0lGODdh", new DataUri("text/vnd-example+xyz", true, "R0lGODdh")); + put("text/plain;charset=UTF-8;page=21,the%20data:1234,5678", new DataUri("text/plain", false, "the%20data:1234,5678")); + }}; + + for (Map.Entry entry : data.entrySet()) { + assertEquals(entry.getKey(), entry.getValue(), impl.parse(entry.getKey())); + } + } + + @Test + public void data_new_lines_are_ignored() { + + final String input = "image/png;base64,iVBORw0KGgoAAA\n" + + "ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4\n" + + "//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU\n" + + "5ErkJggg=="; + + assertEquals( + new DataUri("image/png", true, "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="), + impl.parse(input) + ); + } + + @Test + public void no_comma_returns_null() { + + final String[] inputs = { + "", + "what-ever", + ";;;;;;;", + "some crazy data" + }; + + for (String input : inputs) { + assertNull(input, impl.parse(input)); + } + } + + @Test + public void two_commas() { + final String input = ",,"; // <- second one would be considered data... + assertEquals( + input, + new DataUri(null, false, ","), + impl.parse(input) + ); + } + + @Test + public void more_commas() { + final String input = "first,second,third"; // <- first is just a value (will be ignored) + assertEquals( + input, + new DataUri(null, false, "second,third"), + impl.parse(input) + ); + } + + @Test + public void base64_no_content_type() { + final String input = ";base64,12345"; + assertEquals( + input, + new DataUri(null, true, "12345"), + impl.parse(input) + ); + } + + @Test + public void not_base64_no_content_type() { + final String input = ",qweRTY"; + assertEquals( + input, + new DataUri(null, false, "qweRTY"), + impl.parse(input) + ); + } + + @Test + public void content_type_data_no_base64() { + final String input = "image/png,aSdFg"; + assertEquals( + input, + new DataUri("image/png", false, "aSdFg"), + impl.parse(input) + ); + } +} \ No newline at end of file diff --git a/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java b/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java new file mode 100644 index 00000000..5274c5fb --- /dev/null +++ b/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java @@ -0,0 +1,85 @@ +package ru.noties.markwon.il; + +import android.net.Uri; +import android.support.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class DataUriSchemeHandlerTest { + + private DataUriSchemeHandler handler; + + @Before + public void before() { + handler = DataUriSchemeHandler.create(); + } + + @Test + public void scheme_specific_part_is_empty() { + assertNull(handler.handle("data:", Uri.parse("data:"))); + } + + @Test + public void data_uri_is_empty() { + assertNull(handler.handle("data://whatever", Uri.parse("data://whatever"))); + } + + @Test + public void no_data() { + assertNull(handler.handle("data://,", Uri.parse("data://,"))); + } + + @Test + public void correct() { + + final class Item { + + final String contentType; + final String data; + + Item(String contentType, String data) { + this.contentType = contentType; + this.data = data; + } + } + + final Map expected = new HashMap() {{ + put("data://text/plain;,123", new Item("text/plain", "123")); + put("data://image/svg+xml;base64,MTIz", new Item("image/svg+xml", "123")); + }}; + + for (Map.Entry entry : expected.entrySet()) { + final ImageItem item = handler.handle(entry.getKey(), Uri.parse(entry.getKey())); + assertNotNull(entry.getKey(), item); + assertEquals(entry.getKey(), entry.getValue().contentType, item.contentType()); + assertEquals(entry.getKey(), entry.getValue().data, readStream(item.inputStream())); + } + } + + @NonNull + private static String readStream(@NonNull InputStream stream) { + try { + final Scanner scanner = new Scanner(stream, "UTF-8").useDelimiter("\\A"); + return scanner.hasNext() + ? scanner.next() + : ""; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } +} \ No newline at end of file diff --git a/library-syntax/README.md b/markwon-syntax-highlight/README.md similarity index 94% rename from library-syntax/README.md rename to markwon-syntax-highlight/README.md index 38d65358..4c7a0c74 100644 --- a/library-syntax/README.md +++ b/markwon-syntax-highlight/README.md @@ -2,9 +2,9 @@ This is a simple module to add **syntax-highlight** functionality to your markdown rendered with Markwon library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance. -![theme-default](./art/markwon-syntax-default.png) +![theme-default](../art/markwon-syntax-default.png) -![theme-darkula](./art/markwon-syntax-darkula.png) +![theme-darkula](../art/markwon-syntax-darkula.png) --- diff --git a/markwon-syntax-highlight/build.gradle b/markwon-syntax-highlight/build.gradle new file mode 100644 index 00000000..daa29c7c --- /dev/null +++ b/markwon-syntax-highlight/build.gradle @@ -0,0 +1,26 @@ +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') + + deps.with { + api it['support-annotations'] + api it['prism4j'] + } +} + +registerArtifact(this) diff --git a/markwon-syntax-highlight/gradle.properties b/markwon-syntax-highlight/gradle.properties new file mode 100644 index 00000000..93358b27 --- /dev/null +++ b/markwon-syntax-highlight/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Markwon +POM_ARTIFACT_ID=markwon-syntax-highlight +POM_PACKAGING=aar \ No newline at end of file diff --git a/library-syntax/src/main/AndroidManifest.xml b/markwon-syntax-highlight/src/main/AndroidManifest.xml similarity index 100% rename from library-syntax/src/main/AndroidManifest.xml rename to markwon-syntax-highlight/src/main/AndroidManifest.xml diff --git a/library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxHighlight.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxHighlight.java similarity index 100% rename from library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxHighlight.java rename to markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxHighlight.java diff --git a/library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxVisitor.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxVisitor.java similarity index 100% rename from library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxVisitor.java rename to markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jSyntaxVisitor.java diff --git a/library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jTheme.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jTheme.java similarity index 100% rename from library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jTheme.java rename to markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jTheme.java diff --git a/library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jThemeBase.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeBase.java similarity index 100% rename from library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jThemeBase.java rename to markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeBase.java diff --git a/library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java similarity index 100% rename from library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java rename to markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDarkula.java diff --git a/library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDefault.java b/markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDefault.java similarity index 100% rename from library-syntax/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDefault.java rename to markwon-syntax-highlight/src/main/java/ru/noties/markwon/syntax/Prism4jThemeDefault.java diff --git a/library-view/README.md b/markwon-view/README.md similarity index 100% rename from library-view/README.md rename to markwon-view/README.md diff --git a/markwon-view/build.gradle b/markwon-view/build.gradle new file mode 100644 index 00000000..5e4e72ab --- /dev/null +++ b/markwon-view/build.gradle @@ -0,0 +1,25 @@ +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') + + deps.with { + compileOnly it['support-app-compat'] + } +} + +registerArtifact(this) diff --git a/library-view/gradle.properties b/markwon-view/gradle.properties similarity index 100% rename from library-view/gradle.properties rename to markwon-view/gradle.properties diff --git a/library-view/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java b/markwon-view/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java similarity index 100% rename from library-view/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java rename to markwon-view/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java diff --git a/library-view/src/debug/res/layout/debug_markwon_preview.xml b/markwon-view/src/debug/res/layout/debug_markwon_preview.xml similarity index 100% rename from library-view/src/debug/res/layout/debug_markwon_preview.xml rename to markwon-view/src/debug/res/layout/debug_markwon_preview.xml diff --git a/library-view/src/debug/res/layout/debug_markwon_preview_compat.xml b/markwon-view/src/debug/res/layout/debug_markwon_preview_compat.xml similarity index 100% rename from library-view/src/debug/res/layout/debug_markwon_preview_compat.xml rename to markwon-view/src/debug/res/layout/debug_markwon_preview_compat.xml diff --git a/library-view/src/debug/res/values/debug_strings.xml b/markwon-view/src/debug/res/values/debug_strings.xml similarity index 100% rename from library-view/src/debug/res/values/debug_strings.xml rename to markwon-view/src/debug/res/values/debug_strings.xml diff --git a/library-view/src/main/AndroidManifest.xml b/markwon-view/src/main/AndroidManifest.xml similarity index 100% rename from library-view/src/main/AndroidManifest.xml rename to markwon-view/src/main/AndroidManifest.xml diff --git a/library-view/src/main/java/ru/noties/markwon/view/IMarkwonView.java b/markwon-view/src/main/java/ru/noties/markwon/view/IMarkwonView.java similarity index 100% rename from library-view/src/main/java/ru/noties/markwon/view/IMarkwonView.java rename to markwon-view/src/main/java/ru/noties/markwon/view/IMarkwonView.java diff --git a/library-view/src/main/java/ru/noties/markwon/view/MarkwonView.java b/markwon-view/src/main/java/ru/noties/markwon/view/MarkwonView.java similarity index 100% rename from library-view/src/main/java/ru/noties/markwon/view/MarkwonView.java rename to markwon-view/src/main/java/ru/noties/markwon/view/MarkwonView.java diff --git a/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java b/markwon-view/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java similarity index 100% rename from library-view/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java rename to markwon-view/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java diff --git a/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java b/markwon-view/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java similarity index 100% rename from library-view/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java rename to markwon-view/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java diff --git a/library-view/src/main/res/values/attrs.xml b/markwon-view/src/main/res/values/attrs.xml similarity index 100% rename from library-view/src/main/res/values/attrs.xml rename to markwon-view/src/main/res/values/attrs.xml diff --git a/markwon/build.gradle b/markwon/build.gradle new file mode 100644 index 00000000..fc3dc8ff --- /dev/null +++ b/markwon/build.gradle @@ -0,0 +1,40 @@ +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-html-parser-api') + api project(':markwon-html-parser-impl') + + deps.with { + api it['support-annotations'] + api it['commonmark'] + api it['commonmark-strikethrough'] + api it['commonmark-table'] + } + + deps['test'].with { + testImplementation it['junit'] + testImplementation it['robolectric'] + testImplementation it['ix-java'] + testImplementation it['jackson-yaml'] + testImplementation it['jackson-databind'] + testImplementation it['gson'] + testImplementation it['commons-io'] + testImplementation it['mockito'] + } +} + +registerArtifact(this) \ No newline at end of file diff --git a/library/gradle.properties b/markwon/gradle.properties similarity index 100% rename from library/gradle.properties rename to markwon/gradle.properties diff --git a/library/src/main/AndroidManifest.xml b/markwon/src/main/AndroidManifest.xml similarity index 100% rename from library/src/main/AndroidManifest.xml rename to markwon/src/main/AndroidManifest.xml diff --git a/library/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java b/markwon/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java rename to markwon/src/main/java/ru/noties/markwon/AsyncDrawableLoaderNoOp.java diff --git a/library/src/main/java/ru/noties/markwon/DrawablesScheduler.java b/markwon/src/main/java/ru/noties/markwon/DrawablesScheduler.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/DrawablesScheduler.java rename to markwon/src/main/java/ru/noties/markwon/DrawablesScheduler.java diff --git a/library/src/main/java/ru/noties/markwon/LinkResolverDef.java b/markwon/src/main/java/ru/noties/markwon/LinkResolverDef.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/LinkResolverDef.java rename to markwon/src/main/java/ru/noties/markwon/LinkResolverDef.java diff --git a/library/src/main/java/ru/noties/markwon/Markwon.java b/markwon/src/main/java/ru/noties/markwon/Markwon.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/Markwon.java rename to markwon/src/main/java/ru/noties/markwon/Markwon.java diff --git a/library/src/main/java/ru/noties/markwon/SpannableBuilder.java b/markwon/src/main/java/ru/noties/markwon/SpannableBuilder.java similarity index 74% rename from library/src/main/java/ru/noties/markwon/SpannableBuilder.java rename to markwon/src/main/java/ru/noties/markwon/SpannableBuilder.java index 5cc2420f..9e3ec713 100644 --- a/library/src/main/java/ru/noties/markwon/SpannableBuilder.java +++ b/markwon/src/main/java/ru/noties/markwon/SpannableBuilder.java @@ -14,17 +14,44 @@ import java.util.Iterator; * is using an array to store all the information about spans. So, a span that is added first * will be drawn first, which leads to subtle bugs (spans receive wrong `x` values when * requested to draw itself) + *

    + * since 2.0.0 implements Appendable and CharSequence * * @since 1.0.1 */ @SuppressWarnings({"WeakerAccess", "unused"}) -public class SpannableBuilder { +public class SpannableBuilder implements Appendable, CharSequence { - // do not implement CharSequence (or any of Spanned interfaces) + /** + * @since 2.0.0 + */ + public static void setSpans(@NonNull SpannableBuilder builder, @Nullable Object spans, int start, int end) { + if (spans != null) { - // we will be using SpannableStringBuilder anyway as a backing store - // as it has tight connection with system (implements some hidden methods, etc) - private final SpannableStringBuilder builder; + // setting a span for an invalid position can lead to silent fail (no exception, + // but execution is stopped) + if (!isPositionValid(builder.length(), start, end)) { + return; + } + + if (spans.getClass().isArray()) { + for (Object o : ((Object[]) spans)) { + builder.setSpan(o, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } else { + builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + + private static boolean isPositionValid(int length, int start, int end) { + return end > start + && start >= 0 + && end <= length; + } + + + private final StringBuilder builder; // actually we might be just using ArrayList private final Deque spans = new ArrayDeque<>(8); @@ -34,7 +61,7 @@ public class SpannableBuilder { } public SpannableBuilder(@NonNull CharSequence cs) { - this.builder = new SpannableStringBuilderImpl(cs.toString()); + this.builder = new StringBuilder(cs); copySpans(0, cs); } @@ -51,17 +78,34 @@ public class SpannableBuilder { } @NonNull + @Override public SpannableBuilder append(char c) { builder.append(c); return this; } @NonNull + @Override public SpannableBuilder append(@NonNull CharSequence cs) { copySpans(length(), cs); - builder.append(cs.toString()); + builder.append(cs); + + return this; + } + + /** + * @since 2.0.0 to follow Appendable interface + */ + @NonNull + @Override + public SpannableBuilder append(CharSequence csq, int start, int end) { + + final CharSequence cs = csq.subSequence(start, end); + copySpans(length(), cs); + + builder.append(cs); return this; } @@ -98,14 +142,24 @@ public class SpannableBuilder { return this; } + @Override public int length() { return builder.length(); } + @Override public char charAt(int index) { return builder.charAt(index); } + /** + * @since 2.0.0 to follow CharSequence interface + */ + @Override + public CharSequence subSequence(int start, int end) { + return builder.subSequence(start, end); + } + public char lastChar() { return builder.charAt(length() - 1); } @@ -145,6 +199,19 @@ public class SpannableBuilder { @NonNull public CharSequence text() { + // @since 2.0.0 redirects this call to `#spannableStringBuilder()` + return spannableStringBuilder(); + } + + /** + * Simple method to create a SpannableStringBuilder, which is created anyway. Unlike {@link #text()} + * method which returns the same SpannableStringBuilder there is no need to cast the resulting + * CharSequence + * + * @since 2.0.0 + */ + @NonNull + public SpannableStringBuilder spannableStringBuilder() { // okay, in order to not allow external modification and keep our spans order // we should not return our builder @@ -154,30 +221,13 @@ public class SpannableBuilder { // so, we will defensively copy builder // as we do not expose builder and do no apply spans to it, we are safe to NOT to convert to String + final SpannableStringBuilderImpl impl = new SpannableStringBuilderImpl(builder); for (Span span : spans) { impl.setSpan(span.what, span.start, span.end, span.flags); } - // now, let's remove trailing newLines (so small amounts of text are displayed correctly) - // @since 1.0.2 - - final int length = impl.length(); - if (length > 0) { - int amount = 0; - for (int i = length - 1; i >= 0; i--) { - if (Character.isWhitespace(impl.charAt(i))) { - amount += 1; - } else { - break; - } - } - if (amount > 0) { - impl.replace(length - amount, length, ""); - } - } - return impl; } diff --git a/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java b/markwon/src/main/java/ru/noties/markwon/SpannableConfiguration.java similarity index 71% rename from library/src/main/java/ru/noties/markwon/SpannableConfiguration.java rename to markwon/src/main/java/ru/noties/markwon/SpannableConfiguration.java index 013f9989..cb2a34bc 100644 --- a/library/src/main/java/ru/noties/markwon/SpannableConfiguration.java +++ b/markwon/src/main/java/ru/noties/markwon/SpannableConfiguration.java @@ -3,9 +3,10 @@ package ru.noties.markwon; import android.content.Context; import android.support.annotation.NonNull; +import ru.noties.markwon.html.api.MarkwonHtmlParser; import ru.noties.markwon.renderer.ImageSizeResolver; import ru.noties.markwon.renderer.ImageSizeResolverDef; -import ru.noties.markwon.renderer.html.SpannableHtmlParser; +import ru.noties.markwon.renderer.html2.MarkwonHtmlRenderer; import ru.noties.markwon.spans.AsyncDrawable; import ru.noties.markwon.spans.LinkSpan; import ru.noties.markwon.spans.SpannableTheme; @@ -29,10 +30,12 @@ public class SpannableConfiguration { private final SyntaxHighlight syntaxHighlight; private final LinkSpan.Resolver linkResolver; private final UrlProcessor urlProcessor; - private final SpannableHtmlParser htmlParser; private final ImageSizeResolver imageSizeResolver; private final SpannableFactory factory; // @since 1.1.0 private final boolean softBreakAddsNewLine; // @since 1.1.1 + private final MarkwonHtmlParser htmlParser; // @since 2.0.0 + private final MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0 + private final boolean htmlAllowNonClosedTags; // @since 2.0.0 private SpannableConfiguration(@NonNull Builder builder) { this.theme = builder.theme; @@ -40,10 +43,12 @@ public class SpannableConfiguration { this.syntaxHighlight = builder.syntaxHighlight; this.linkResolver = builder.linkResolver; this.urlProcessor = builder.urlProcessor; - this.htmlParser = builder.htmlParser; this.imageSizeResolver = builder.imageSizeResolver; this.factory = builder.factory; this.softBreakAddsNewLine = builder.softBreakAddsNewLine; + this.htmlParser = builder.htmlParser; + this.htmlRenderer = builder.htmlRenderer; + this.htmlAllowNonClosedTags = builder.htmlAllowNonClosedTags; } @NonNull @@ -71,11 +76,6 @@ public class SpannableConfiguration { return urlProcessor; } - @NonNull - public SpannableHtmlParser htmlParser() { - return htmlParser; - } - @NonNull public ImageSizeResolver imageSizeResolver() { return imageSizeResolver; @@ -95,6 +95,29 @@ public class SpannableConfiguration { return softBreakAddsNewLine; } + /** + * @since 2.0.0 + */ + @NonNull + public MarkwonHtmlParser htmlParser() { + return htmlParser; + } + + /** + * @since 2.0.0 + */ + @NonNull + public MarkwonHtmlRenderer htmlRenderer() { + return htmlRenderer; + } + + /** + * @since 2.0.0 + */ + public boolean htmlAllowNonClosedTags() { + return htmlAllowNonClosedTags; + } + @SuppressWarnings("unused") public static class Builder { @@ -104,10 +127,12 @@ public class SpannableConfiguration { private SyntaxHighlight syntaxHighlight; private LinkSpan.Resolver linkResolver; private UrlProcessor urlProcessor; - private SpannableHtmlParser htmlParser; private ImageSizeResolver imageSizeResolver; private SpannableFactory factory; // @since 1.1.0 private boolean softBreakAddsNewLine; // @since 1.1.1 + private MarkwonHtmlParser htmlParser; // @since 2.0.0 + private MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0 + private boolean htmlAllowNonClosedTags; // @since 2.0.0 Builder(@NonNull Context context) { this.context = context; @@ -143,12 +168,6 @@ public class SpannableConfiguration { return this; } - @NonNull - public Builder htmlParser(@NonNull SpannableHtmlParser htmlParser) { - this.htmlParser = htmlParser; - return this; - } - /** * @since 1.0.1 */ @@ -171,7 +190,7 @@ public class SpannableConfiguration { * @param softBreakAddsNewLine a flag indicating if soft break should be treated as a hard * break and thus adding a new line instead of adding a white space * @return self - * @see spec + * @see spec * @since 1.1.1 */ @NonNull @@ -180,6 +199,37 @@ public class SpannableConfiguration { return this; } + /** + * @since 2.0.0 + */ + @NonNull + public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) { + this.htmlParser = htmlParser; + return this; + } + + /** + * @since 2.0.0 + */ + @NonNull + public Builder htmlRenderer(@NonNull MarkwonHtmlRenderer htmlRenderer) { + this.htmlRenderer = htmlRenderer; + return this; + } + + /** + * @param htmlAllowNonClosedTags that indicates if non-closed html tags should be rendered. + * If this argument is true then all non-closed HTML tags + * will be closed at the end of a document. Otherwise they will + * be delivered non-closed {@code HtmlTag#isClosed()} + * @since 2.0.0 + */ + @NonNull + public Builder htmlAllowNonClosedTags(boolean htmlAllowNonClosedTags) { + this.htmlAllowNonClosedTags = htmlAllowNonClosedTags; + return this; + } + @NonNull public SpannableConfiguration build() { @@ -212,14 +262,19 @@ public class SpannableConfiguration { factory = SpannableFactoryDef.create(); } + // @since 2.0.0 if (htmlParser == null) { - htmlParser = SpannableHtmlParser.create( - factory, - theme, - asyncDrawableLoader, - urlProcessor, - linkResolver, - imageSizeResolver); + try { + // if impl artifact was excluded -> fallback to no-op implementation + htmlParser = ru.noties.markwon.html.impl.MarkwonHtmlParserImpl.create(); + } catch (Throwable t) { + htmlParser = MarkwonHtmlParser.noOp(); + } + } + + // @since 2.0.0 + if (htmlRenderer == null) { + htmlRenderer = MarkwonHtmlRenderer.create(); } return new SpannableConfiguration(this); diff --git a/library/src/main/java/ru/noties/markwon/SpannableFactory.java b/markwon/src/main/java/ru/noties/markwon/SpannableFactory.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/SpannableFactory.java rename to markwon/src/main/java/ru/noties/markwon/SpannableFactory.java diff --git a/library/src/main/java/ru/noties/markwon/SpannableFactoryDef.java b/markwon/src/main/java/ru/noties/markwon/SpannableFactoryDef.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/SpannableFactoryDef.java rename to markwon/src/main/java/ru/noties/markwon/SpannableFactoryDef.java diff --git a/library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java b/markwon/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java rename to markwon/src/main/java/ru/noties/markwon/SpannableStringBuilderImpl.java diff --git a/library/src/main/java/ru/noties/markwon/SpannedReversed.java b/markwon/src/main/java/ru/noties/markwon/SpannedReversed.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/SpannedReversed.java rename to markwon/src/main/java/ru/noties/markwon/SpannedReversed.java diff --git a/library/src/main/java/ru/noties/markwon/SyntaxHighlight.java b/markwon/src/main/java/ru/noties/markwon/SyntaxHighlight.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/SyntaxHighlight.java rename to markwon/src/main/java/ru/noties/markwon/SyntaxHighlight.java diff --git a/library/src/main/java/ru/noties/markwon/SyntaxHighlightNoOp.java b/markwon/src/main/java/ru/noties/markwon/SyntaxHighlightNoOp.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/SyntaxHighlightNoOp.java rename to markwon/src/main/java/ru/noties/markwon/SyntaxHighlightNoOp.java diff --git a/library/src/main/java/ru/noties/markwon/TableRowsScheduler.java b/markwon/src/main/java/ru/noties/markwon/TableRowsScheduler.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/TableRowsScheduler.java rename to markwon/src/main/java/ru/noties/markwon/TableRowsScheduler.java diff --git a/library/src/main/java/ru/noties/markwon/UrlProcessor.java b/markwon/src/main/java/ru/noties/markwon/UrlProcessor.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/UrlProcessor.java rename to markwon/src/main/java/ru/noties/markwon/UrlProcessor.java diff --git a/library/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java b/markwon/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java similarity index 80% rename from library/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java rename to markwon/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java index b2b09006..2a1544a9 100644 --- a/library/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java +++ b/markwon/src/main/java/ru/noties/markwon/UrlProcessorAndroidAssets.java @@ -8,8 +8,12 @@ import android.text.TextUtils; @SuppressWarnings({"unused", "WeakerAccess"}) public class UrlProcessorAndroidAssets implements UrlProcessor { + + static final String MOCK = "https://android.asset/"; + static final String BASE = "file:///android_asset/"; + private final UrlProcessorRelativeToAbsolute assetsProcessor - = new UrlProcessorRelativeToAbsolute("file:///android_asset/"); + = new UrlProcessorRelativeToAbsolute(MOCK); private final UrlProcessor processor; @@ -27,7 +31,7 @@ public class UrlProcessorAndroidAssets implements UrlProcessor { final String out; final Uri uri = Uri.parse(destination); if (TextUtils.isEmpty(uri.getScheme())) { - out = assetsProcessor.process(destination); + out = assetsProcessor.process(destination).replace(MOCK, BASE); } else { if (processor != null) { out = processor.process(destination); diff --git a/library/src/main/java/ru/noties/markwon/UrlProcessorNoOp.java b/markwon/src/main/java/ru/noties/markwon/UrlProcessorNoOp.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/UrlProcessorNoOp.java rename to markwon/src/main/java/ru/noties/markwon/UrlProcessorNoOp.java diff --git a/library/src/main/java/ru/noties/markwon/UrlProcessorRelativeToAbsolute.java b/markwon/src/main/java/ru/noties/markwon/UrlProcessorRelativeToAbsolute.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/UrlProcessorRelativeToAbsolute.java rename to markwon/src/main/java/ru/noties/markwon/UrlProcessorRelativeToAbsolute.java diff --git a/library/src/main/java/ru/noties/markwon/renderer/ImageSize.java b/markwon/src/main/java/ru/noties/markwon/renderer/ImageSize.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/renderer/ImageSize.java rename to markwon/src/main/java/ru/noties/markwon/renderer/ImageSize.java diff --git a/library/src/main/java/ru/noties/markwon/renderer/ImageSizeResolver.java b/markwon/src/main/java/ru/noties/markwon/renderer/ImageSizeResolver.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/renderer/ImageSizeResolver.java rename to markwon/src/main/java/ru/noties/markwon/renderer/ImageSizeResolver.java diff --git a/library/src/main/java/ru/noties/markwon/renderer/ImageSizeResolverDef.java b/markwon/src/main/java/ru/noties/markwon/renderer/ImageSizeResolverDef.java similarity index 76% rename from library/src/main/java/ru/noties/markwon/renderer/ImageSizeResolverDef.java rename to markwon/src/main/java/ru/noties/markwon/renderer/ImageSizeResolverDef.java index 60710f71..c825002a 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/ImageSizeResolverDef.java +++ b/markwon/src/main/java/ru/noties/markwon/renderer/ImageSizeResolverDef.java @@ -24,7 +24,22 @@ public class ImageSizeResolverDef extends ImageSizeResolver { ) { if (imageSize == null) { - return imageBounds; + // @since 2.0.0 post process bounds to fit canvasWidth (previously was inside AsyncDrawable) + // must be applied only if imageSize is null + final Rect rect; + final int w = imageBounds.width(); + if (w > canvasWidth) { + final float reduceRatio = (float) w / canvasWidth; + rect = new Rect( + 0, + 0, + canvasWidth, + (int) (imageBounds.height() / reduceRatio + .5F) + ); + } else { + rect = imageBounds; + } + return rect; } final Rect rect; @@ -78,7 +93,7 @@ public class ImageSizeResolverDef extends ImageSizeResolver { if (UNIT_EM.equals(dimension.unit)) { out = (int) (dimension.value * textSize + .5F); } else { - out = original; + out = (int) (dimension.value + .5F); } return out; } diff --git a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java b/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java similarity index 81% rename from library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java rename to markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java index 06db051f..967784e5 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java +++ b/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java @@ -2,12 +2,11 @@ package ru.noties.markwon.renderer; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.Spanned; -import android.text.TextUtils; import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.ext.gfm.tables.TableBody; import org.commonmark.ext.gfm.tables.TableCell; +import org.commonmark.ext.gfm.tables.TableHead; import org.commonmark.ext.gfm.tables.TableRow; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.BlockQuote; @@ -15,6 +14,7 @@ import org.commonmark.node.BulletList; import org.commonmark.node.Code; import org.commonmark.node.CustomBlock; import org.commonmark.node.CustomNode; +import org.commonmark.node.Document; import org.commonmark.node.Emphasis; import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.HardLineBreak; @@ -34,15 +34,13 @@ import org.commonmark.node.StrongEmphasis; import org.commonmark.node.Text; import org.commonmark.node.ThematicBreak; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.List; import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.SpannableConfiguration; import ru.noties.markwon.SpannableFactory; -import ru.noties.markwon.renderer.html.SpannableHtmlParser; +import ru.noties.markwon.html.api.MarkwonHtmlParser; import ru.noties.markwon.spans.SpannableTheme; import ru.noties.markwon.spans.TableRowSpan; import ru.noties.markwon.tasklist.TaskListBlock; @@ -53,7 +51,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { private final SpannableConfiguration configuration; private final SpannableBuilder builder; - private final Deque htmlInlineItems; + private final MarkwonHtmlParser htmlParser; private final SpannableTheme theme; private final SpannableFactory factory; @@ -71,12 +69,19 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { ) { this.configuration = configuration; this.builder = builder; - this.htmlInlineItems = new ArrayDeque<>(2); + this.htmlParser = configuration.htmlParser(); this.theme = configuration.theme(); this.factory = configuration.factory(); } + @Override + public void visit(Document document) { + super.visit(document); + + configuration.htmlRenderer().render(configuration, builder, htmlParser); + } + @Override public void visit(Text text) { builder.append(text.getLiteral()); @@ -114,9 +119,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { blockQuoteIndent -= 1; - newLine(); - if (blockQuoteIndent == 0) { - builder.append('\n'); + if (hasNext(blockQuote)) { + newLine(); + if (blockQuoteIndent == 0) { + builder.append('\n'); + } } } @@ -137,7 +144,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(FencedCodeBlock fencedCodeBlock) { // @since 1.0.4 - visitCodeBlock(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral()); + visitCodeBlock(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral(), fencedCodeBlock); } /** @@ -145,7 +152,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { */ @Override public void visit(IndentedCodeBlock indentedCodeBlock) { - visitCodeBlock(null, indentedCodeBlock.getLiteral()); + visitCodeBlock(null, indentedCodeBlock.getLiteral(), indentedCodeBlock); } /** @@ -153,7 +160,8 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { * @param code content of a code block * @since 1.0.4 */ - private void visitCodeBlock(@Nullable String info, @NonNull String code) { + private void visitCodeBlock(@Nullable String info, @NonNull String code, @NonNull Node node) { + newLine(); final int length = builder.length(); @@ -164,12 +172,16 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { configuration.syntaxHighlight() .highlight(info, code) ); - builder.append('\u00a0').append('\n'); + + newLine(); + builder.append('\u00a0'); setSpan(length, factory.code(theme, true)); - newLine(); - builder.append('\n'); + if (hasNext(node)) { + newLine(); + builder.append('\n'); + } } @Override @@ -183,11 +195,16 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { } private void visitList(Node node) { + newLine(); + visitChildren(node); - newLine(); - if (listLevel == 0 && blockQuoteIndent == 0) { - builder.append('\n'); + + if (hasNext(node)) { + newLine(); + if (listLevel == 0 && blockQuoteIndent == 0) { + builder.append('\n'); + } } } @@ -222,7 +239,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { blockQuoteIndent -= 1; listLevel -= 1; - newLine(); + if (hasNext(listItem)) { + newLine(); + } } @Override @@ -231,12 +250,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { newLine(); final int length = builder.length(); - builder.append(' '); // without space it won't render + builder.append('\u00a0'); // without space it won't render setSpan(length, factory.thematicBreak(theme)); - newLine(); - builder.append('\n'); + if (hasNext(thematicBreak)) { + newLine(); + builder.append('\n'); + } } @Override @@ -248,10 +269,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { visitChildren(heading); setSpan(length, factory.heading(theme, heading.getLevel())); - newLine(); - - // after heading we add another line anyway (no additional checks) - builder.append('\n'); + if (hasNext(heading)) { + newLine(); + // after heading we add another line anyway (no additional checks) + builder.append('\n'); + } } @Override @@ -274,12 +296,17 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { */ @Override public void visit(CustomBlock customBlock) { + if (customBlock instanceof TaskListBlock) { blockQuoteIndent += 1; visitChildren(customBlock); blockQuoteIndent -= 1; - newLine(); - builder.append('\n'); + + if (hasNext(customBlock)) { + newLine(); + builder.append('\n'); + } + } else { super.visit(customBlock); } @@ -308,7 +335,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { setSpan(length, factory.taskListItem(theme, blockQuoteIndent, listItem.done())); - newLine(); + if (hasNext(customNode)) { + newLine(); + } blockQuoteIndent -= listItem.indent(); @@ -322,18 +351,37 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { final boolean handled; if (node instanceof TableBody) { + visitChildren(node); tableRows = 0; handled = true; - newLine(); - builder.append('\n'); - } else if (node instanceof TableRow) { + + if (hasNext(node)) { + newLine(); + builder.append('\n'); + } + + } else if (node instanceof TableRow || node instanceof TableHead) { final int length = builder.length(); + visitChildren(node); if (pendingTableRow != null) { + // @since 2.0.0 + // we cannot rely on hasNext(TableHead) as it's not reliable + // we must apply new line manually and then exclude it from tableRow span + final boolean addNewLine; + { + final int builderLength = builder.length(); + addNewLine = builderLength > 0 + && '\n' != builder.charAt(builderLength - 1); + } + if (addNewLine) { + builder.append('\n'); + } + // @since 1.0.4 Replace table char with non-breakable space // we need this because if table is at the end of the text, then it will be // trimmed from the final result @@ -349,12 +397,13 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { ? 0 : tableRows + 1; - setSpan(length, span); - newLine(); + setSpan(addNewLine ? length + 1 : length, span); + pendingTableRow = null; } handled = true; + } else if (node instanceof TableCell) { final TableCell cell = (TableCell) node; @@ -375,11 +424,13 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { } else { handled = false; } + return handled; } @Override public void visit(Paragraph paragraph) { + final boolean inTightList = isInTightList(paragraph); if (!inTightList) { @@ -392,9 +443,8 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { // @since 1.1.1 apply paragraph span setSpan(length, factory.paragraph(inTightList)); - if (!inTightList) { + if (hasNext(paragraph) && !inTightList) { newLine(); - if (blockQuoteIndent == 0) { builder.append('\n'); } @@ -435,47 +485,17 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { @Override public void visit(HtmlBlock htmlBlock) { - // http://spec.commonmark.org/0.18/#html-blocks - final Spanned spanned = configuration.htmlParser().getSpanned(null, htmlBlock.getLiteral()); - if (!TextUtils.isEmpty(spanned)) { - builder.append(spanned); - } + visitHtml(htmlBlock.getLiteral()); } @Override public void visit(HtmlInline htmlInline) { + visitHtml(htmlInline.getLiteral()); + } - final SpannableHtmlParser htmlParser = configuration.htmlParser(); - final SpannableHtmlParser.Tag tag = htmlParser.parseTag(htmlInline.getLiteral()); - - if (tag != null) { - - final boolean voidTag = tag.voidTag(); - if (!voidTag && tag.opening()) { - // push in stack - htmlInlineItems.push(new HtmlInlineItem(tag, builder.length())); - visitChildren(htmlInline); - } else { - - if (!voidTag) { - if (htmlInlineItems.size() > 0) { - final HtmlInlineItem item = htmlInlineItems.pop(); - final Object span = htmlParser.getSpanForTag(item.tag); - setSpan(item.start, span); - } - } else { - - final Spanned html = htmlParser.getSpanned(tag, htmlInline.getLiteral()); - if (!TextUtils.isEmpty(html)) { - builder.append(html); - } - - } - } - } else { - // todo, should we append just literal? -// builder.append(htmlInline.getLiteral()); - visitChildren(htmlInline); + private void visitHtml(@Nullable String html) { + if (html != null) { + htmlParser.processFragment(builder, html); } } @@ -488,18 +508,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { } private void setSpan(int start, @Nullable Object span) { - if (span != null) { - - final int length = builder.length(); - - if (span.getClass().isArray()) { - for (Object o : ((Object[]) span)) { - builder.setSpan(o, start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - builder.setSpan(span, start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } + SpannableBuilder.setSpans(builder, span, start, builder.length()); } private void newLine() { @@ -542,14 +551,10 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { return out; } - private static class HtmlInlineItem { - - final SpannableHtmlParser.Tag tag; - final int start; - - HtmlInlineItem(SpannableHtmlParser.Tag tag, int start) { - this.tag = tag; - this.start = start; - } + /** + * @since 2.0.0 + */ + protected static boolean hasNext(@NonNull Node node) { + return node.getNext() != null; } } diff --git a/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java b/markwon/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java rename to markwon/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/CssInlineStyleParser.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/CssInlineStyleParser.java new file mode 100644 index 00000000..9670d018 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/CssInlineStyleParser.java @@ -0,0 +1,171 @@ +package ru.noties.markwon.renderer.html2; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public abstract class CssInlineStyleParser { + + @NonNull + public abstract Iterable parse(@NonNull String inlineStyle); + + @NonNull + public static CssInlineStyleParser create() { + return new Impl(); + } + + static class Impl extends CssInlineStyleParser { + + @NonNull + @Override + public Iterable parse(@NonNull String inlineStyle) { + return new CssIterable(inlineStyle); + } + + private static class CssIterable implements Iterable { + + private final String input; + + CssIterable(@NonNull String input) { + this.input = input; + } + + @NonNull + @Override + public Iterator iterator() { + return new CssIterator(); + } + + private class CssIterator implements Iterator { + + private final CssProperty cssProperty = new CssProperty(); + + private final StringBuilder builder = new StringBuilder(); + + private final int length = input.length(); + + private int index; + + @Override + public boolean hasNext() { + + prepareNext(); + + return hasNextPrepared(); + } + + @Override + public CssProperty next() { + if (!hasNextPrepared()) { + throw new NoSuchElementException(); + } + return cssProperty; + } + + private void prepareNext() { + + // clear first + cssProperty.set("", ""); + + builder.setLength(0); + + String key = null; + String value = null; + + char c; + + boolean keyHasWhiteSpace = false; + + for (int i = index; i < length; i++) { + + c = input.charAt(i); + + // if we are building KEY, then when we encounter WS (white-space) we finish + // KEY and wait for the ':', if we do not find it and we find EOF or ';' + // we start creating KEY again after the ';' + + if (key == null) { + + if (':' == c) { + + // we have no key yet, but we might have started creating it already + if (builder.length() > 0) { + key = builder.toString().trim(); + } + + builder.setLength(0); + + } else { + // if by any chance we have here the ';' -> reset key and try to match next + if (';' == c) { + builder.setLength(0); + } else { + + // key cannot have WS gaps (but leading and trailing are OK) + if (Character.isWhitespace(c)) { + if (builder.length() > 0) { + keyHasWhiteSpace = true; + } + } else { + // if not a WS and we have found WS before, start a-new + // else append + if (keyHasWhiteSpace) { + // start new filling + builder.setLength(0); + builder.append(c); + // clear this flag + keyHasWhiteSpace = false; + } else { + builder.append(c); + } + } + } + } + } else if (value == null) { + + if (Character.isWhitespace(c)) { + if (builder.length() > 0) { + builder.append(c); + } + } else if (';' == c) { + + value = builder.toString().trim(); + builder.setLength(0); + + // check if we have valid values -> if yes -> return it + if (hasValues(key, value)) { + index = i + 1; + cssProperty.set(key, value); + return; + } + + } else { + builder.append(c); + } + } + } + + // here we must additionally check for EOF (we might be tracking value here) + if (key != null + && builder.length() > 0) { + value = builder.toString().trim(); + cssProperty.set(key, value); + index = length; + } + } + + private boolean hasNextPrepared() { + return hasValues(cssProperty.key(), cssProperty.value()); + } + + private boolean hasValues(@Nullable String key, @Nullable String value) { + return !TextUtils.isEmpty(key) + && !TextUtils.isEmpty(value); + } + } + } + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/CssProperty.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/CssProperty.java new file mode 100644 index 00000000..aa490361 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/CssProperty.java @@ -0,0 +1,42 @@ +package ru.noties.markwon.renderer.html2; + +import android.support.annotation.NonNull; + +public class CssProperty { + + private String key; + private String value; + + CssProperty() { + } + + void set(@NonNull String key, @NonNull String value) { + this.key = key; + this.value = value; + } + + @NonNull + public String key() { + return key; + } + + @NonNull + public String value() { + return value; + } + + @NonNull + public CssProperty mutate() { + final CssProperty cssProperty = new CssProperty(); + cssProperty.set(this.key, this.value); + return cssProperty; + } + + @Override + public String toString() { + return "CssProperty{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java new file mode 100644 index 00000000..bd69445a --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java @@ -0,0 +1,101 @@ +package ru.noties.markwon.renderer.html2; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.MarkwonHtmlParser; +import ru.noties.markwon.renderer.html2.tag.BlockquoteHandler; +import ru.noties.markwon.renderer.html2.tag.EmphasisHandler; +import ru.noties.markwon.renderer.html2.tag.HeadingHandler; +import ru.noties.markwon.renderer.html2.tag.ImageHandler; +import ru.noties.markwon.renderer.html2.tag.LinkHandler; +import ru.noties.markwon.renderer.html2.tag.ListHandler; +import ru.noties.markwon.renderer.html2.tag.StrikeHandler; +import ru.noties.markwon.renderer.html2.tag.StrongEmphasisHandler; +import ru.noties.markwon.renderer.html2.tag.SubScriptHandler; +import ru.noties.markwon.renderer.html2.tag.SuperScriptHandler; +import ru.noties.markwon.renderer.html2.tag.TagHandler; +import ru.noties.markwon.renderer.html2.tag.UnderlineHandler; + +/** + * @since 2.0.0 + */ +public abstract class MarkwonHtmlRenderer { + + public abstract void render( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull MarkwonHtmlParser parser + ); + + @Nullable + public abstract TagHandler tagHandler(@NonNull String tagName); + + @NonNull + public static MarkwonHtmlRenderer create() { + return builderWithDefaults().build(); + } + + @NonNull + public static Builder builderWithDefaults() { + + final EmphasisHandler emphasisHandler = new EmphasisHandler(); + final StrongEmphasisHandler strongEmphasisHandler = new StrongEmphasisHandler(); + final StrikeHandler strikeHandler = new StrikeHandler(); + final UnderlineHandler underlineHandler = new UnderlineHandler(); + final ListHandler listHandler = new ListHandler(); + + return builder() + .handler("i", emphasisHandler) + .handler("em", emphasisHandler) + .handler("cite", emphasisHandler) + .handler("dfn", emphasisHandler) + .handler("b", strongEmphasisHandler) + .handler("strong", strongEmphasisHandler) + .handler("sup", new SuperScriptHandler()) + .handler("sub", new SubScriptHandler()) + .handler("u", underlineHandler) + .handler("ins", underlineHandler) + .handler("del", strikeHandler) + .handler("s", strikeHandler) + .handler("strike", strikeHandler) + .handler("a", new LinkHandler()) + .handler("ul", listHandler) + .handler("ol", listHandler) + .handler("img", ImageHandler.create()) + .handler("blockquote", new BlockquoteHandler()) + .handler("h1", new HeadingHandler(1)) + .handler("h2", new HeadingHandler(2)) + .handler("h3", new HeadingHandler(3)) + .handler("h4", new HeadingHandler(4)) + .handler("h5", new HeadingHandler(5)) + .handler("h6", new HeadingHandler(6)); + } + + @NonNull + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final Map tagHandlers = new HashMap<>(2); + + public Builder handler(@NonNull String tagName, @NonNull TagHandler tagHandler) { + tagHandlers.put(tagName.toLowerCase(Locale.US), tagHandler); + return this; + } + + @NonNull + public MarkwonHtmlRenderer build() { + return new MarkwonHtmlRendererImpl(Collections.unmodifiableMap(tagHandlers)); + } + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java new file mode 100644 index 00000000..6de698f5 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java @@ -0,0 +1,88 @@ +package ru.noties.markwon.renderer.html2; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; +import java.util.Map; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; +import ru.noties.markwon.html.api.MarkwonHtmlParser; +import ru.noties.markwon.renderer.html2.tag.TagHandler; + +class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { + + private final Map tagHandlers; + + MarkwonHtmlRendererImpl(@NonNull Map tagHandlers) { + this.tagHandlers = tagHandlers; + } + + @Override + public void render( + @NonNull final SpannableConfiguration configuration, + @NonNull final SpannableBuilder builder, + @NonNull MarkwonHtmlParser parser) { + + final int end; + if (!configuration.htmlAllowNonClosedTags()) { + end = HtmlTag.NO_END; + } else { + end = builder.length(); + } + + parser.flushInlineTags(end, new MarkwonHtmlParser.FlushAction() { + @Override + public void apply(@NonNull List tags) { + + TagHandler handler; + + for (HtmlTag.Inline inline : tags) { + + // if tag is not closed -> do not render + if (!inline.isClosed()) { + continue; + } + + handler = tagHandler(inline.name()); + if (handler != null) { + handler.handle(configuration, builder, inline); + } + } + } + }); + + parser.flushBlockTags(end, new MarkwonHtmlParser.FlushAction() { + @Override + public void apply(@NonNull List tags) { + + TagHandler handler; + + for (HtmlTag.Block block : tags) { + + if (!block.isClosed()) { + continue; + } + + handler = tagHandler(block.name()); + if (handler != null) { + handler.handle(configuration, builder, block); + } else { + // see if any of children can be handled + apply(block.children()); + } + } + } + }); + + parser.reset(); + } + + @Nullable + @Override + public TagHandler tagHandler(@NonNull String tagName) { + return tagHandlers.get(tagName); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/BlockquoteHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/BlockquoteHandler.java new file mode 100644 index 00000000..99ddf153 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/BlockquoteHandler.java @@ -0,0 +1,28 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class BlockquoteHandler extends TagHandler { + + @Override + public void handle( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull HtmlTag tag) { + + if (tag.isBlock()) { + visitChildren(configuration, builder, tag.getAsBlock()); + } + + SpannableBuilder.setSpans( + builder, + configuration.factory().blockQuote(configuration.theme()), + tag.start(), + tag.end() + ); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java new file mode 100644 index 00000000..d34218de --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java @@ -0,0 +1,15 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class EmphasisHandler extends SimpleTagHandler { + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + return configuration.factory().emphasis(); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java new file mode 100644 index 00000000..e2138b05 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java @@ -0,0 +1,22 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class HeadingHandler extends SimpleTagHandler { + + private final int level; + + public HeadingHandler(int level) { + this.level = level; + } + + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + return configuration.factory().heading(configuration.theme(), level); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageHandler.java new file mode 100644 index 00000000..ed5f7f3f --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageHandler.java @@ -0,0 +1,59 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import java.util.Map; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; +import ru.noties.markwon.renderer.ImageSize; +import ru.noties.markwon.renderer.html2.CssInlineStyleParser; + +public class ImageHandler extends SimpleTagHandler { + + interface ImageSizeParser { + @Nullable + ImageSize parse(@NonNull Map attributes); + } + + @NonNull + public static ImageHandler create() { + return new ImageHandler(new ImageSizeParserImpl(CssInlineStyleParser.create())); + } + + private final ImageSizeParser imageSizeParser; + + ImageHandler(@NonNull ImageSizeParser imageSizeParser) { + this.imageSizeParser = imageSizeParser; + } + + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + + final Map attributes = tag.attributes(); + final String src = attributes.get("src"); + if (TextUtils.isEmpty(src)) { + return null; + } + + final String destination = configuration.urlProcessor().process(src); + + // todo: replacement text is link... as we are not at block level + // and cannot inspect the parent of this node... (img and a are both inlines) + // + // but we can look and see if we are inside a LinkSpan (will have to extend TagHandler + // to obtain an instance SpannableBuilder for inspection) + + return configuration.factory().image( + configuration.theme(), + destination, + configuration.asyncDrawableLoader(), + configuration.imageSizeResolver(), + imageSizeParser.parse(tag.attributes()), + false + ); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImpl.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImpl.java new file mode 100644 index 00000000..56ad13c0 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImpl.java @@ -0,0 +1,110 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; + +import java.util.Map; + +import ru.noties.markwon.renderer.ImageSize; +import ru.noties.markwon.renderer.html2.CssInlineStyleParser; +import ru.noties.markwon.renderer.html2.CssProperty; + +class ImageSizeParserImpl implements ImageHandler.ImageSizeParser { + + private final CssInlineStyleParser inlineStyleParser; + + ImageSizeParserImpl(@NonNull CssInlineStyleParser inlineStyleParser) { + this.inlineStyleParser = inlineStyleParser; + } + + @Override + public ImageSize parse(@NonNull Map attributes) { + + // strictly speaking percents when specified directly on an attribute + // are not part of the HTML spec (I couldn't find any reference) + + ImageSize.Dimension width = null; + ImageSize.Dimension height = null; + + // okay, let's first check styles + final String style = attributes.get("style"); + + if (!TextUtils.isEmpty(style)) { + + String key; + + for (CssProperty cssProperty : inlineStyleParser.parse(style)) { + + key = cssProperty.key(); + + if ("width".equals(key)) { + width = dimension(cssProperty.value()); + } else if ("height".equals(key)) { + height = dimension(cssProperty.value()); + } + + if (width != null + && height != null) { + break; + } + } + } + + if (width != null + && height != null) { + return new ImageSize(width, height); + } + + // check tag attributes + if (width == null) { + width = dimension(attributes.get("width")); + } + + if (height == null) { + height = dimension(attributes.get("height")); + } + + if (width == null + && height == null) { + return null; + } + + return new ImageSize(width, height); + } + + @Nullable + @VisibleForTesting + ImageSize.Dimension dimension(@Nullable String value) { + + if (TextUtils.isEmpty(value)) { + return null; + } + + final int length = value.length(); + + for (int i = length - 1; i > -1; i--) { + + if (Character.isDigit(value.charAt(i))) { + + try { + final float val = Float.parseFloat(value.substring(0, i + 1)); + final String unit; + if (i == length - 1) { + // no unit info + unit = null; + } else { + unit = value.substring(i + 1, length); + } + return new ImageSize.Dimension(val, unit); + } catch (NumberFormatException e) { + // value cannot not be represented as a float + return null; + } + } + } + + return null; + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java new file mode 100644 index 00000000..134874b9 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java @@ -0,0 +1,24 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class LinkHandler extends SimpleTagHandler { + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + final String destination = tag.attributes().get("href"); + if (!TextUtils.isEmpty(destination)) { + return configuration.factory().link( + configuration.theme(), + configuration.urlProcessor().process(destination), + configuration.linkResolver() + ); + } + return null; + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ListHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ListHandler.java new file mode 100644 index 00000000..fca098e7 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ListHandler.java @@ -0,0 +1,66 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class ListHandler extends TagHandler { + + @Override + public void handle( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull HtmlTag tag) { + + if (!tag.isBlock()) { + return; + } + + final HtmlTag.Block block = tag.getAsBlock(); + final boolean ol = "ol".equals(block.name()); + final boolean ul = "ul".equals(block.name()); + + if (!ol && !ul) { + return; + } + + int number = 1; + final int bulletLevel = currentBulletListLevel(block); + + Object spans; + + for (HtmlTag.Block child : block.children()) { + + visitChildren(configuration, builder, child); + + if ("li".equals(child.name())) { + // insert list item here + if (ol) { + spans = configuration.factory().orderedListItem( + configuration.theme(), + number++ + ); + } else { + spans = configuration.factory().bulletListItem( + configuration.theme(), + bulletLevel + ); + } + SpannableBuilder.setSpans(builder, spans, child.start(), child.end()); + } + } + } + + private static int currentBulletListLevel(@NonNull HtmlTag.Block block) { + int level = 0; + while ((block = block.parent()) != null) { + if ("ul".equals(block.name()) + || "ol".equals(block.name())) { + level += 1; + } + } + return level; + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SimpleTagHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SimpleTagHandler.java new file mode 100644 index 00000000..e5940cc7 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SimpleTagHandler.java @@ -0,0 +1,22 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public abstract class SimpleTagHandler extends TagHandler { + + @Nullable + public abstract Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag); + + @Override + public void handle(@NonNull SpannableConfiguration configuration, @NonNull SpannableBuilder builder, @NonNull HtmlTag tag) { + final Object spans = getSpans(configuration, tag); + if (spans != null) { + SpannableBuilder.setSpans(builder, spans, tag.start(), tag.end()); + } + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java new file mode 100644 index 00000000..965ddfea --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java @@ -0,0 +1,28 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class StrikeHandler extends TagHandler { + + @Override + public void handle( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull HtmlTag tag) { + + if (tag.isBlock()) { + visitChildren(configuration, builder, tag.getAsBlock()); + } + + SpannableBuilder.setSpans( + builder, + configuration.factory().strikethrough(), + tag.start(), + tag.end() + ); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java new file mode 100644 index 00000000..04d18a25 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java @@ -0,0 +1,15 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class StrongEmphasisHandler extends SimpleTagHandler { + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + return configuration.factory().strongEmphasis(); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java new file mode 100644 index 00000000..a96f34bc --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java @@ -0,0 +1,15 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class SubScriptHandler extends SimpleTagHandler { + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + return configuration.factory().subScript(configuration.theme()); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java new file mode 100644 index 00000000..c5eee815 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java @@ -0,0 +1,15 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class SuperScriptHandler extends SimpleTagHandler { + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + return configuration.factory().superScript(configuration.theme()); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java new file mode 100644 index 00000000..818c4a98 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java @@ -0,0 +1,38 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public abstract class TagHandler { + + public abstract void handle( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull HtmlTag tag + ); + + protected static void visitChildren( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull HtmlTag.Block block) { + + TagHandler handler; + + for (HtmlTag.Block child : block.children()) { + + if (!child.isClosed()) { + continue; + } + + handler = configuration.htmlRenderer().tagHandler(child.name()); + if (handler != null) { + handler.handle(configuration, builder, child); + } else { + visitChildren(configuration, builder, child); + } + } + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java new file mode 100644 index 00000000..ff870ef6 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java @@ -0,0 +1,31 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; + +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class UnderlineHandler extends TagHandler { + + @Override + public void handle( + @NonNull SpannableConfiguration configuration, + @NonNull SpannableBuilder builder, + @NonNull HtmlTag tag) { + + // as parser doesn't treat U tag as an inline one, + // thus doesn't allow children, we must visit them first + + if (tag.isBlock()) { + visitChildren(configuration, builder, tag.getAsBlock()); + } + + SpannableBuilder.setSpans( + builder, + configuration.factory().underline(), + tag.start(), + tag.end() + ); + } +} diff --git a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java b/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java similarity index 80% rename from library/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java rename to markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java index 15ae4483..588b8a04 100644 --- a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java +++ b/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java @@ -33,14 +33,6 @@ public class AsyncDrawable extends Drawable { private int canvasWidth; private float textSize; - /** - * @deprecated 1.0.6 markdown images are also processed with {@link ImageSizeResolver} - */ - @Deprecated - public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) { - this(destination, loader, null, null); - } - /** * @since 1.0.1 */ @@ -178,27 +170,11 @@ public class AsyncDrawable extends Drawable { @NonNull private Rect resolveBounds() { - final Rect rect; - - if (imageSizeResolver == null) { - - // @since 1.0.5 - final Rect bounds = result.getBounds(); - if (bounds.width() > canvasWidth) { - - // let's scale image down, as we do not want to expand beyond canvas width - final float ratio = (float) bounds.width() / bounds.height(); - final int height = (int) (canvasWidth / ratio + .5F); - rect = new Rect(0, 0, canvasWidth, height); - - } else { - rect = bounds; - } - - } else { - rect = imageSizeResolver.resolveImageSize(imageSize, result.getBounds(), canvasWidth, textSize); - } - - return rect; + // @since 2.0.0 previously we were checking if image is greater than canvas width here + // but as imageSizeResolver won't be null anymore, we should transfer this logic + // there + return imageSizeResolver != null + ? imageSizeResolver.resolveImageSize(imageSize, result.getBounds(), canvasWidth, textSize) + : result.getBounds(); } } diff --git a/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java similarity index 98% rename from library/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java index 04dfe78a..aa85c33c 100644 --- a/library/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java +++ b/markwon/src/main/java/ru/noties/markwon/spans/BlockQuoteSpan.java @@ -39,6 +39,8 @@ public class BlockQuoteSpan implements LeadingMarginSpan { final int width = theme.getBlockQuoteWidth(); + paint.set(p); + theme.applyBlockQuoteStyle(paint); final int left; diff --git a/library/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/BulletListItemSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/CanvasUtils.java b/markwon/src/main/java/ru/noties/markwon/spans/CanvasUtils.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/CanvasUtils.java rename to markwon/src/main/java/ru/noties/markwon/spans/CanvasUtils.java diff --git a/library/src/main/java/ru/noties/markwon/spans/CodeSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/CodeSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/CodeSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/CodeSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/ColorUtils.java b/markwon/src/main/java/ru/noties/markwon/spans/ColorUtils.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/ColorUtils.java rename to markwon/src/main/java/ru/noties/markwon/spans/ColorUtils.java diff --git a/library/src/main/java/ru/noties/markwon/spans/EmphasisSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/EmphasisSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/EmphasisSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/EmphasisSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/HeadingSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/HeadingSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java b/markwon/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java rename to markwon/src/main/java/ru/noties/markwon/spans/LeadingMarginUtils.java diff --git a/library/src/main/java/ru/noties/markwon/spans/LinkSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/LinkSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/LinkSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/LinkSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/ObjectsPool.java b/markwon/src/main/java/ru/noties/markwon/spans/ObjectsPool.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/ObjectsPool.java rename to markwon/src/main/java/ru/noties/markwon/spans/ObjectsPool.java diff --git a/library/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java similarity index 91% rename from library/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java index 16e9895f..29f68e9e 100644 --- a/library/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java +++ b/markwon/src/main/java/ru/noties/markwon/spans/OrderedListItemSpan.java @@ -10,6 +10,7 @@ public class OrderedListItemSpan implements LeadingMarginSpan { private final SpannableTheme theme; private final String number; + private final Paint paint = ObjectsPool.paint(); // we will use this variable to check if our order number text exceeds block margin, // so we will use it instead of block margin @@ -39,7 +40,9 @@ public class OrderedListItemSpan implements LeadingMarginSpan { return; } - theme.applyListItemStyle(p); + paint.set(p); + + theme.applyListItemStyle(paint); final int numberWidth = (int) (p.measureText(number) + .5F); @@ -60,6 +63,6 @@ public class OrderedListItemSpan implements LeadingMarginSpan { } // @since 1.1.1 we are using `baseline` argument to position text - c.drawText(number, left, baseline, p); + c.drawText(number, left, baseline, paint); } } diff --git a/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java b/markwon/src/main/java/ru/noties/markwon/spans/SpannableTheme.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java rename to markwon/src/main/java/ru/noties/markwon/spans/SpannableTheme.java diff --git a/library/src/main/java/ru/noties/markwon/spans/StrongEmphasisSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/StrongEmphasisSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/StrongEmphasisSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/StrongEmphasisSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/SubScriptSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/SubScriptSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/SubScriptSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/SubScriptSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/SuperScriptSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/SuperScriptSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/SuperScriptSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/SuperScriptSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/TableRowSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/TableRowSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/TableRowSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/TableRowSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java b/markwon/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java rename to markwon/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java diff --git a/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/TaskListSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/TaskListSpan.java diff --git a/library/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java b/markwon/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java rename to markwon/src/main/java/ru/noties/markwon/spans/ThematicBreakSpan.java diff --git a/library/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java b/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java rename to markwon/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java diff --git a/library/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java b/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java similarity index 95% rename from library/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java rename to markwon/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java index 9dab83f1..4c2ab99a 100644 --- a/library/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java +++ b/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java @@ -60,7 +60,9 @@ class TaskListBlockParser extends AbstractBlockParser { && PATTERN.matcher(line).matches()) { blockContinue = BlockContinue.atIndex(parserState.getIndex()); } else { - blockContinue = BlockContinue.finished(); + // @since 2.0.0, previously called `BlockContinue.finished()` + // that was swallowing non-matching lines + blockContinue = BlockContinue.none(); } return blockContinue; diff --git a/library/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java b/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java rename to markwon/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java diff --git a/library/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java b/markwon/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java similarity index 100% rename from library/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java rename to markwon/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java diff --git a/library/src/main/res/values/ids.xml b/markwon/src/main/res/values/ids.xml similarity index 100% rename from library/src/main/res/values/ids.xml rename to markwon/src/main/res/values/ids.xml diff --git a/markwon/src/test/java/ru/noties/markwon/UrlProcessorAndroidAssetsTest.java b/markwon/src/test/java/ru/noties/markwon/UrlProcessorAndroidAssetsTest.java new file mode 100644 index 00000000..ecf31fd2 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/UrlProcessorAndroidAssetsTest.java @@ -0,0 +1,49 @@ +package ru.noties.markwon; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; +import static ru.noties.markwon.UrlProcessorAndroidAssets.BASE; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class UrlProcessorAndroidAssetsTest { + + private UrlProcessorAndroidAssets processor; + + @Before + public void before() { + processor = new UrlProcessorAndroidAssets(); + } + + @Test + public void access_root() { + final String path = "/whoam.i"; + assertEquals( + BASE.substring(0, BASE.length() - 1) + path, + processor.process(path) + ); + } + + @Test + public void access_folder_without_modifier() { + final String path = "first/second/thi.rd"; + assertEquals( + BASE + path, + processor.process(path) + ); + } + + @Test + public void change_directory_inside_path() { + final String path = "first/../second/./thi.rd"; + assertEquals( + BASE + "second/thi.rd", + processor.process(path) + ); + } +} \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/UrlProcessorRelativeToAbsoluteTest.java b/markwon/src/test/java/ru/noties/markwon/UrlProcessorRelativeToAbsoluteTest.java new file mode 100644 index 00000000..05956f3b --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/UrlProcessorRelativeToAbsoluteTest.java @@ -0,0 +1,61 @@ +package ru.noties.markwon; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.*; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class UrlProcessorRelativeToAbsoluteTest { + + @Test + public void malformed_base_do_not_process() { + final UrlProcessorRelativeToAbsolute processor = new UrlProcessorRelativeToAbsolute("!@#$%^&*("); + final String destination = "../hey.there.html"; + assertEquals(destination, processor.process(destination)); + } + + @Test + public void access_root() { + final UrlProcessorRelativeToAbsolute processor = new UrlProcessorRelativeToAbsolute("https://ro.ot/hello/"); + final String url = "/index.html"; + assertEquals("https://ro.ot/index.html", processor.process(url)); + } + + @Test + public void access_same_directory() { + final UrlProcessorRelativeToAbsolute processor = new UrlProcessorRelativeToAbsolute("https://ro.ot/hello/"); + final String url = "./.htaccess"; + assertEquals("https://ro.ot/hello/.htaccess", processor.process(url)); + } + + @Test + public void asset_directory_up() { + final UrlProcessorRelativeToAbsolute processor = new UrlProcessorRelativeToAbsolute("http://ro.ot/first/second/"); + final String url = "../cat.JPG"; + assertEquals("http://ro.ot/first/cat.JPG", processor.process(url)); + } + + @Test + public void change_directory_inside_destination() { + final UrlProcessorRelativeToAbsolute processor = new UrlProcessorRelativeToAbsolute("http://ro.ot/first/"); + final String url = "../first/../second/./thi.rd"; + assertEquals( + "http://ro.ot/second/thi.rd", + processor.process(url) + ); + } + + @Test + public void with_query_arguments() { + final UrlProcessorRelativeToAbsolute processor = new UrlProcessorRelativeToAbsolute("http://ro.ot/first/"); + final String url = "../index.php?ROOT=1"; + assertEquals( + "http://ro.ot/index.php?ROOT=1", + processor.process(url) + ); + } +} \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/ImageSizeResolverDefTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/ImageSizeResolverDefTest.java new file mode 100644 index 00000000..f0945554 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/ImageSizeResolverDefTest.java @@ -0,0 +1,147 @@ +package ru.noties.markwon.renderer; + +import android.graphics.Rect; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import ru.noties.markwon.renderer.ImageSize.Dimension; + +import static org.junit.Assert.assertEquals; +import static ru.noties.markwon.renderer.ImageSizeResolverDef.UNIT_EM; +import static ru.noties.markwon.renderer.ImageSizeResolverDef.UNIT_PERCENT; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class ImageSizeResolverDefTest { + + private ImageSizeResolverDef def; + + @Before + public void before() { + def = new ImageSizeResolverDef(); + } + + @Test + public void no_image_size() { + // no image size returns image original bounds + final Rect rect = new Rect(0, 0, 15, 43); + assertEquals(rect, def.resolveImageSize(null, rect, 15, Float.NaN)); + } + + @Test + public void no_image_size_width_greater_than_canvas() { + // image must be scaled (with ratio) wo fit canvas width + final Rect rect = new Rect(0, 0, 10, 20); + assertEquals( + new Rect(0, 0, 8, 16), + def.resolveImageSize( + null, + rect, + 8, + Float.NaN + ) + ); + } + + @Test + public void height_percent_not_used() { + final Rect rect = new Rect(1, 2, 3, 4); + assertEquals( + rect, + def.resolveImageSize( + new ImageSize(null, new Dimension(100.F, UNIT_PERCENT)), + rect, + -1, + Float.NaN + ) + ); + } + + @Test + public void width_percent_scales_keeps_ratio() { + final Rect rect = new Rect(0, 0, 10, 20); + assertEquals( + new Rect(0, 0, 50, 100), + def.resolveImageSize( + new ImageSize(new Dimension(50.F, UNIT_PERCENT), null), + rect, + 100, + Float.NaN + ) + ); + } + + @Test + public void unknown_dimension_considered_absolute() { + final Rect rect = new Rect(0, 0, 22, 33); + assertEquals( + new Rect(0, 0, 7, 9), + def.resolveImageSize( + new ImageSize(new Dimension(7, "width"), new Dimension(9, "height")), + rect, + 90, + Float.NaN + ) + ); + } + + @Test + public void width_height_text_size_relative() { + final Rect rect = new Rect(0, 0, 100, 200); + assertEquals( + new Rect(0, 0, 20, 40), + def.resolveImageSize( + new ImageSize(new Dimension(2.f, UNIT_EM), new Dimension(4.F, UNIT_EM)), + rect, + 999, + 10.F + ) + ); + } + + @Test + public void width_text_size_relative_height_keeps_ratio() { + final Rect rect = new Rect(0, 0, 15, 30); + assertEquals( + new Rect(0, 0, 10, 20), + def.resolveImageSize( + new ImageSize(new Dimension(1.F, UNIT_EM), null), + rect, + 42, + 10.F + ) + ); + } + + @Test + public void absolute_height_keeps_width_ratio() { + final Rect rect = new Rect(0, 0, 50, 25); + assertEquals( + new Rect(0, 0, 100, 50), + def.resolveImageSize( + new ImageSize(null, new Dimension(50, "px")), + rect, + 200, + Float.NaN + ) + ); + } + + @Test + public void relative_text_size_height_keeps_width_ratio() { + final Rect rect = new Rect(0, 0, 4, 12); + assertEquals( + new Rect(0, 0, 10, 30), + def.resolveImageSize( + new ImageSize(null, new Dimension(3.F, UNIT_EM)), + rect, + 40, + 10.F + ) + ); + } +} \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/html2/CssInlineStyleParserTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/html2/CssInlineStyleParserTest.java new file mode 100644 index 00000000..4ba3fffb --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/html2/CssInlineStyleParserTest.java @@ -0,0 +1,239 @@ +package ru.noties.markwon.renderer.html2; + +import android.support.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ix.Ix; +import ix.IxFunction; +import ru.noties.markwon.test.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static ru.noties.markwon.test.TestUtils.with; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class CssInlineStyleParserTest { + + private CssInlineStyleParser.Impl impl; + + @Before + public void before() { + impl = new CssInlineStyleParser.Impl(); + } + + @Test + public void simple_single_pair() { + + final String input = "key: value;"; + + final List list = listProperties(input); + + assertEquals(1, list.size()); + + with(list.get(0), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key", cssProperty.key()); + assertEquals("value", cssProperty.value()); + } + }); + } + + @Test + public void simple_two_pairs() { + + final String input = "key1: value1; key2: value2;"; + + final List list = listProperties(input); + + assertEquals(2, list.size()); + + with(list.get(0), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key1", cssProperty.key()); + assertEquals("value1", cssProperty.value()); + } + }); + + with(list.get(1), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key2", cssProperty.key()); + assertEquals("value2", cssProperty.value()); + } + }); + } + + @Test + public void one_pair_eof() { + + final String input = "key: value"; + final List list = listProperties(input); + assertEquals(1, list.size()); + + with(list.get(0), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key", cssProperty.key()); + assertEquals("value", cssProperty.value()); + } + }); + } + + @Test + public void one_pair_eof_whitespaces() { + + final String input = "key: value \n\n\t"; + final List list = listProperties(input); + assertEquals(1, list.size()); + + with(list.get(0), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key", cssProperty.key()); + assertEquals("value", cssProperty.value()); + } + }); + } + + @Test + public void white_spaces() { + + final String input = "\n\n\n\t \t key1 \n\n\n\t : \n\n\n\n \t value1 \n\n\n\n ; \n key2\n : \n value2 \n ; "; + final List list = listProperties(input); + assertEquals(2, list.size()); + + with(list.get(0), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key1", cssProperty.key()); + assertEquals("value1", cssProperty.value()); + } + }); + + with(list.get(1), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key2", cssProperty.key()); + assertEquals("value2", cssProperty.value()); + } + }); + } + + @Test + public void list_of_keys() { + + final String input = "key1 key2 key3 key4"; + final List list = listProperties(input); + + assertEquals(0, list.size()); + } + + @Test + public void list_of_keys_and_value() { + + final String input = "key1 key2 key3 key4: value4"; + final List list = listProperties(input); + assertEquals(1, list.size()); + + with(list.get(0), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key4", cssProperty.key()); + assertEquals("value4", cssProperty.value()); + } + }); + } + + @Test + public void list_of_keys_separated_by_semi_colon() { + + final String input = "key1;key2;key3;key4;"; + final List list = listProperties(input); + assertEquals(0, list.size()); + } + + @Test + public void key_value_with_invalid_between() { + + final String input = "key1: value1; key2 key3: value3;"; + final List list = listProperties(input); + + assertEquals(2, list.size()); + + with(list.get(0), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key1", cssProperty.key()); + assertEquals("value1", cssProperty.value()); + } + }); + + with(list.get(1), new TestUtils.Action() { + @Override + public void apply(@NonNull CssProperty cssProperty) { + assertEquals("key3", cssProperty.key()); + assertEquals("value3", cssProperty.value()); + } + }); + } + + @Test + public void css_functions() { + + final Map map = new HashMap() {{ + put("attr", "\" (\" attr(href) \")\""); + put("calc", "calc(100% - 100px)"); + put("cubic-bezier", "cubic-bezier(0.1, 0.7, 1.0, 0.1)"); + put("hsl", "hsl(120,100%,50%)"); + put("hsla", "hsla(120,100%,50%,0.3)"); + put("linear-gradient", "linear-gradient(red, yellow, blue)"); + put("radial-gradient", "radial-gradient(red, green, blue)"); + put("repeating-linear-gradient", "repeating-linear-gradient(red, yellow 10%, green 20%)"); + put("repeating-radial-gradient", "repeating-radial-gradient(red, yellow 10%, green 15%)"); + put("rgb", "rgb(255,0,0)"); + put("rgba", "rgba(255,0,0,0.3)"); + put("var", "var(--some-variable)"); + put("url", "url(\"url.gif\")"); + }}; + + final StringBuilder builder = new StringBuilder(); + for (Map.Entry entry: map.entrySet()) { + builder.append(entry.getKey()) + .append(':') + .append(entry.getValue()) + .append(';'); + } + + for (CssProperty cssProperty: impl.parse(builder.toString())) { + final String value = map.remove(cssProperty.key()); + assertNotNull(cssProperty.key(), value); + assertEquals(cssProperty.key(), value, cssProperty.value()); + } + + assertEquals(0, map.size()); + } + + @NonNull + private List listProperties(@NonNull String input) { + return Ix.from(impl.parse(input)) + .map(new IxFunction() { + @Override + public CssProperty apply(CssProperty cssProperty) { + return cssProperty.mutate(); + } + }) + .toList(); + } +} \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImplTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImplTest.java new file mode 100644 index 00000000..23d7eb93 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/html2/tag/ImageSizeParserImplTest.java @@ -0,0 +1,186 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import ru.noties.markwon.renderer.ImageSize; +import ru.noties.markwon.renderer.html2.CssInlineStyleParser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class ImageSizeParserImplTest { + + private static final float DELTA = 1e-7F; + + private ImageSizeParserImpl impl; + + @Before + public void before() { + impl = new ImageSizeParserImpl(CssInlineStyleParser.create()); + } + + @Test + public void nothing() { + assertNull(impl.parse(Collections.emptyMap())); + } + + @Test + public void width_height_from_style() { + + final String style = "width: 123; height: 321"; + + assertImageSize( + new ImageSize(dimension(123, null), dimension(321, null)), + impl.parse(Collections.singletonMap("style", style)) + ); + } + + @Test + public void style_has_higher_priority_width() { + + // if property is found in styles, do not lookup raw attribute + final Map attributes = new HashMap() {{ + put("style", "width: 43"); + put("width", "991"); + }}; + + assertImageSize( + new ImageSize(dimension(43, null), null), + impl.parse(attributes) + ); + } + + @Test + public void style_has_higher_priority_height() { + + // if property is found in styles, do not lookup raw attribute + final Map attributes = new HashMap() {{ + put("style", "height: 177"); + put("height", "8"); + }}; + + assertImageSize( + new ImageSize(null, dimension(177, null)), + impl.parse(attributes) + ); + } + + @Test + public void width_style_height_attributes() { + + final Map attributes = new HashMap() {{ + put("style", "width: 99"); + put("height", "7"); + }}; + + assertImageSize( + new ImageSize(dimension(99, null), dimension(7, null)), + impl.parse(attributes) + ); + } + + @Test + public void height_style_width_attributes() { + + final Map attributes = new HashMap() {{ + put("style", "height: 15"); + put("width", "88"); + }}; + + assertImageSize( + new ImageSize(dimension(88, null), dimension(15, null)), + impl.parse(attributes) + ); + } + + @Test + public void non_empty_styles_width_height_attributes() { + + final Map attributes = new HashMap() {{ + put("style", "key1: value1; width0: 123; height0: 99"); + put("width", "40"); + put("height", "77"); + }}; + + assertImageSize( + new ImageSize(dimension(40, null), dimension(77, null)), + impl.parse(attributes) + ); + } + + @Test + public void dimension_units() { + + final Map map = new HashMap() {{ + put("100", dimension(100, null)); + put("100%", dimension(100, "%")); + put("1%", dimension(1, "%")); + put("0.2em", dimension(0.2F, "em")); + put("155px", dimension(155, "px")); + put("67blah", dimension(67, "blah")); + put("-1", dimension(-1, null)); + put("-0.01pt", dimension(-0.01F, "pt")); + }}; + + for (Map.Entry entry : map.entrySet()) { + assertDimension(entry.getKey(), entry.getValue(), impl.dimension(entry.getKey())); + } + } + + @Test + public void bad_dimension() { + + final String[] dimensions = { + "calc(5px + 10rem)", + "whataver6", + "165 165", + "!@#$%^&*(%" + }; + + for (String dimension: dimensions) { + assertNull(dimension, impl.dimension(dimension)); + } + } + + private static void assertImageSize(@Nullable ImageSize expected, @Nullable ImageSize actual) { + if (expected == null) { + assertNull(actual); + } else { + assertNotNull(actual); + assertDimension("width", expected.width, actual.width); + assertDimension("height", expected.height, actual.height); + } + } + + private static void assertDimension( + @NonNull String name, + @Nullable ImageSize.Dimension expected, + @Nullable ImageSize.Dimension actual) { + if (expected == null) { + assertNull(name, actual); + } else { + assertNotNull(name, actual); + assertEquals(name, expected.value, actual.value, DELTA); + assertEquals(name, expected.unit, actual.unit); + } + } + + @NonNull + private static ImageSize.Dimension dimension(float value, @Nullable String unit) { + return new ImageSize.Dimension(value, unit); + } +} \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java new file mode 100644 index 00000000..047a0584 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java @@ -0,0 +1,95 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; +import android.text.SpannableStringBuilder; + +import org.commonmark.node.Node; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.ParameterizedRobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.Collection; + +import ru.noties.markwon.LinkResolverDef; +import ru.noties.markwon.Markwon; +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.SpannableFactory; +import ru.noties.markwon.html.api.MarkwonHtmlParser; +import ru.noties.markwon.renderer.SpannableMarkdownVisitor; +import ru.noties.markwon.spans.SpannableTheme; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +@RunWith(ParameterizedRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class SpannableMarkdownVisitorTest { + + @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") + public static Collection parameters() { + return TestDataReader.testFiles(); + } + + private final String file; + + public SpannableMarkdownVisitorTest(@NonNull String file) { + this.file = file; + } + + @Test + public void test() { + + final TestData data = TestDataReader.readTest(file); + + final SpannableConfiguration configuration = configuration(data.config()); + final SpannableBuilder builder = new SpannableBuilder(); + final SpannableMarkdownVisitor visitor = new SpannableMarkdownVisitor(configuration, builder); + final Node node = Markwon.createParser().parse(data.input()); + node.accept(visitor); + + final SpannableStringBuilder stringBuilder = builder.spannableStringBuilder(); + + final TestValidator validator = TestValidator.create(file); + + int index = 0; + + for (TestNode testNode : data.output()) { + index = validator.validate(stringBuilder, index, testNode); + } + + // assert that the whole thing is processed + assertEquals("`" + stringBuilder + "`", stringBuilder.length(), index); + + final Object[] spans = stringBuilder.getSpans(0, stringBuilder.length(), Object.class); + final int length = spans != null + ? spans.length + : 0; + + assertEquals(Arrays.toString(spans), validator.processedSpanNodesCount(), length); + } + + @SuppressWarnings("ConstantConditions") + @NonNull + private SpannableConfiguration configuration(@NonNull TestConfig config) { + + final SpannableFactory factory = new TestFactory(config.hasOption(TestConfig.USE_PARAGRAPHS)); + final MarkwonHtmlParser htmlParser = config.hasOption(TestConfig.USE_HTML) + ? null + : MarkwonHtmlParser.noOp(); + + final boolean softBreakAddsNewLine = config.hasOption(TestConfig.SOFT_BREAK_ADDS_NEW_LINE); + final boolean htmlAllowNonClosedTags = config.hasOption(TestConfig.HTML_ALLOW_NON_CLOSED_TAGS); + + return SpannableConfiguration.builder(null) + .theme(mock(SpannableTheme.class)) + .linkResolver(mock(LinkResolverDef.class)) + .htmlParser(htmlParser) + .factory(factory) + .softBreakAddsNewLine(softBreakAddsNewLine) + .htmlAllowNonClosedTags(htmlAllowNonClosedTags) + .build(); + } +} \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestConfig.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestConfig.java new file mode 100644 index 00000000..61fc29a5 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestConfig.java @@ -0,0 +1,31 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; + +import java.util.Map; + +class TestConfig { + + static final String USE_PARAGRAPHS = "use-paragraphs"; + static final String USE_HTML = "use-html"; + static final String SOFT_BREAK_ADDS_NEW_LINE = "soft-break-adds-new-line"; + static final String HTML_ALLOW_NON_CLOSED_TAGS = "html-allow-non-closed-tags"; + + private final Map map; + + TestConfig(@NonNull Map map) { + this.map = map; + } + + boolean hasOption(@NonNull String option) { + final Boolean value = map.get(option); + return value != null && value; + } + + @Override + public String toString() { + return "TestConfig{" + + "map=" + map + + '}'; + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java new file mode 100644 index 00000000..67807202 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java @@ -0,0 +1,45 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; + +class TestData { + + private final String description; + private final String input; + private final TestConfig config; + private final List output; + + TestData( + @Nullable String description, + @NonNull String input, + @NonNull TestConfig config, + @NonNull List output) { + this.description = description; + this.input = input; + this.config = config; + this.output = output; + } + + @Nullable + public String description() { + return description; + } + + @NonNull + public String input() { + return input; + } + + @NonNull + public TestConfig config() { + return config; + } + + @NonNull + public List output() { + return output; + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java new file mode 100644 index 00000000..bfba93b1 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java @@ -0,0 +1,351 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import ix.Ix; +import ix.IxFunction; +import ix.IxPredicate; +import ru.noties.markwon.spans.TableRowSpan; + +import static ru.noties.markwon.renderer.visitor.TestSpan.BLOCK_QUOTE; +import static ru.noties.markwon.renderer.visitor.TestSpan.BULLET_LIST; +import static ru.noties.markwon.renderer.visitor.TestSpan.CODE; +import static ru.noties.markwon.renderer.visitor.TestSpan.CODE_BLOCK; +import static ru.noties.markwon.renderer.visitor.TestSpan.EMPHASIS; +import static ru.noties.markwon.renderer.visitor.TestSpan.HEADING; +import static ru.noties.markwon.renderer.visitor.TestSpan.IMAGE; +import static ru.noties.markwon.renderer.visitor.TestSpan.LINK; +import static ru.noties.markwon.renderer.visitor.TestSpan.ORDERED_LIST; +import static ru.noties.markwon.renderer.visitor.TestSpan.PARAGRAPH; +import static ru.noties.markwon.renderer.visitor.TestSpan.STRIKE_THROUGH; +import static ru.noties.markwon.renderer.visitor.TestSpan.STRONG_EMPHASIS; +import static ru.noties.markwon.renderer.visitor.TestSpan.SUB_SCRIPT; +import static ru.noties.markwon.renderer.visitor.TestSpan.SUPER_SCRIPT; +import static ru.noties.markwon.renderer.visitor.TestSpan.TABLE_ROW; +import static ru.noties.markwon.renderer.visitor.TestSpan.TASK_LIST; +import static ru.noties.markwon.renderer.visitor.TestSpan.THEMATIC_BREAK; +import static ru.noties.markwon.renderer.visitor.TestSpan.UNDERLINE; + +abstract class TestDataReader { + + private static final String FOLDER = "tests/"; + + @NonNull + static Collection testFiles() { + + final InputStream in = TestDataReader.class.getClassLoader().getResourceAsStream(FOLDER); + if (in == null) { + throw new RuntimeException("Cannot access test cases folder"); + } + + try { + //noinspection unchecked + return (Collection) Ix.from(IOUtils.readLines(in, StandardCharsets.UTF_8)) + .filter(new IxPredicate() { + @Override + public boolean test(String s) { + return s.endsWith(".yaml"); + } + }) + .map(new IxFunction() { + @Override + public String apply(String s) { + return FOLDER + s; + } + }) + .map(new IxFunction() { + @Override + public Object[] apply(String s) { + return new Object[]{ + s + }; + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @NonNull + static TestData readTest(@NonNull String file) { + return new Reader(file).read(); + } + + private TestDataReader() { + } + + static class Reader { + + private static final String TEXT = "text"; + private static final String CELLS = "cells"; + + private static final Set TAGS; + + static { + TAGS = new HashSet<>(Arrays.asList( + STRONG_EMPHASIS, + EMPHASIS, + BLOCK_QUOTE, + CODE, + CODE_BLOCK, + ORDERED_LIST, + BULLET_LIST, + THEMATIC_BREAK, + HEADING, + STRIKE_THROUGH, + TASK_LIST, + TABLE_ROW, + PARAGRAPH, + IMAGE, + LINK, + SUPER_SCRIPT, + SUB_SCRIPT, + UNDERLINE, + HEADING + "1", + HEADING + "2", + HEADING + "3", + HEADING + "4", + HEADING + "5", + HEADING + "6", + TEXT + )); + } + + private final String file; + + Reader(@NonNull String file) { + this.file = file; + } + + @NonNull + TestData read() { + return testData(jsonObject()); + } + + @NonNull + private JsonObject jsonObject() { + try { + final String input = IOUtils.resourceToString(file, StandardCharsets.UTF_8, TestDataReader.class.getClassLoader()); + final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); + final Object object = objectMapper.readValue(input, Object.class); + final ObjectMapper jsonWriter = new ObjectMapper(); + final String json = jsonWriter.writeValueAsString(object); + return new Gson().fromJson(json, JsonObject.class); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + @NonNull + private TestData testData(@NonNull JsonObject jsonObject) { + + final String description; + { + final JsonElement element = jsonObject.get("description"); + if (element != null + && element.isJsonPrimitive()) { + description = element.getAsString(); + } else { + description = null; + } + } + + final String input = jsonObject.get("input").getAsString(); + if (TextUtils.isEmpty(input)) { + throw new RuntimeException(String.format("Test case file `%s` is missing " + + "input parameter", file)); + } + + final TestConfig testConfig = testConfig(jsonObject.get("config")); + + final List testNodes = testNodes(jsonObject.get("output").getAsJsonArray()); + if (testNodes.size() == 0) { + throw new RuntimeException(String.format("Test case file `%s` has no " + + "output specified", file)); + } + + return new TestData( + description, + input, + testConfig, + testNodes + ); + } + + @NonNull + private List testNodes(@NonNull JsonArray array) { + return testNodes(null, array); + } + + @NonNull + private List testNodes(@Nullable TestNode parent, @NonNull JsonArray array) { + + // an item in array is a JsonObject + + // it can be "b": "bold" -> means Span(name="b", children=[Text(bold)] + // or b: + // - text: "bold" -> which is the same as above + + // it can additionally contain "attrs" key which is the attributes + // b: + // - text: "bold" + // href: "my-href" + + final int size = array.size(); + + final List testNodes = new ArrayList<>(size); + + for (int i = 0; i < size; i++) { + + // if element is a string (or a json primitive) let's just add a text node + // right away, this way we will not have to provide text with `text: "my-text"` + // (we still can though) + final JsonElement jsonElement = array.get(i); + if (jsonElement.isJsonPrimitive()) { + testNodes.add(new TestNode.Text(parent, jsonElement.getAsString())); + continue; + } + + final JsonObject object = jsonElement.getAsJsonObject(); + + String name = null; + Map attributes = new HashMap<>(0); + + for (String key : object.keySet()) { + if (TAGS.contains(key)) { + if (name == null) { + name = key; + } else { + throw new RuntimeException("Unexpected key in object: " + object); + } + } else { + // fill attribute map with it + final String value; + final JsonElement valueElement = object.get(key); + if (valueElement.isJsonNull()) { + value = null; + } else { + // another special case: table cell + // this is not so good + if (CELLS.equals(key)) { + final JsonArray cells = valueElement.getAsJsonArray(); + final int length = cells.size(); + final List list = new ArrayList<>(length); + for (int k = 0; k < length; k++) { + final JsonObject cell = cells.get(k).getAsJsonObject(); + list.add(new TableRowSpan.Cell( + cell.get("alignment").getAsInt(), + cell.get("text").getAsString() + )); + } + value = list.toString(); + } else { + value = valueElement.getAsString(); + } + } + attributes.put(key, value); + } + } + + if (name == null) { + throw new RuntimeException("Object is missing tag name: " + object); + } + + final JsonElement element = object.get(name); + + if (TEXT.equals(name)) { + testNodes.add(new TestNode.Text(parent, element.getAsString())); + } else { + + final List children = new ArrayList<>(1); + final TestNode.Span span = new TestNode.Span(parent, name, children, attributes); + + // if it's primitive string -> just append text node + if (element.isJsonPrimitive()) { + children.add(new TestNode.Text(span, element.getAsString())); + } else if (element.isJsonArray()) { + children.addAll(testNodes(span, element.getAsJsonArray())); + } else { + throw new RuntimeException("Unexpected element: " + object); + } + + testNodes.add(span); + } + } + + return testNodes; + } + + @NonNull + private TestConfig testConfig(@Nullable JsonElement element) { + + final JsonObject object = element != null && element.isJsonObject() + ? element.getAsJsonObject() + : null; + + final Map map; + + if (object != null) { + + map = new HashMap<>(object.size()); + + for (String key : object.keySet()) { + + final JsonElement value = object.get(key); + + if (value.isJsonPrimitive()) { + + final JsonPrimitive jsonPrimitive = value.getAsJsonPrimitive(); + + Boolean b = null; + + if (jsonPrimitive.isBoolean()) { + b = jsonPrimitive.getAsBoolean(); + } else if (jsonPrimitive.isString()) { + final String s = jsonPrimitive.getAsString(); + if ("true".equalsIgnoreCase(s)) { + b = Boolean.TRUE; + } else if ("false".equalsIgnoreCase(s)) { + b = Boolean.FALSE; + } + } + + if (b != null) { + map.put(key, b); + } + } + } + } else { + map = Collections.emptyMap(); + } + + return new TestConfig(map); + } + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java new file mode 100644 index 00000000..89a0f646 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java @@ -0,0 +1,193 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ru.noties.markwon.SpannableFactory; +import ru.noties.markwon.renderer.ImageSize; +import ru.noties.markwon.renderer.ImageSizeResolver; +import ru.noties.markwon.spans.AsyncDrawable; +import ru.noties.markwon.spans.LinkSpan; +import ru.noties.markwon.spans.SpannableTheme; +import ru.noties.markwon.spans.TableRowSpan; + +import static ru.noties.markwon.renderer.visitor.TestSpan.BLOCK_QUOTE; +import static ru.noties.markwon.renderer.visitor.TestSpan.BULLET_LIST; +import static ru.noties.markwon.renderer.visitor.TestSpan.CODE; +import static ru.noties.markwon.renderer.visitor.TestSpan.CODE_BLOCK; +import static ru.noties.markwon.renderer.visitor.TestSpan.EMPHASIS; +import static ru.noties.markwon.renderer.visitor.TestSpan.HEADING; +import static ru.noties.markwon.renderer.visitor.TestSpan.IMAGE; +import static ru.noties.markwon.renderer.visitor.TestSpan.LINK; +import static ru.noties.markwon.renderer.visitor.TestSpan.ORDERED_LIST; +import static ru.noties.markwon.renderer.visitor.TestSpan.PARAGRAPH; +import static ru.noties.markwon.renderer.visitor.TestSpan.STRIKE_THROUGH; +import static ru.noties.markwon.renderer.visitor.TestSpan.STRONG_EMPHASIS; +import static ru.noties.markwon.renderer.visitor.TestSpan.SUB_SCRIPT; +import static ru.noties.markwon.renderer.visitor.TestSpan.SUPER_SCRIPT; +import static ru.noties.markwon.renderer.visitor.TestSpan.TABLE_ROW; +import static ru.noties.markwon.renderer.visitor.TestSpan.TASK_LIST; +import static ru.noties.markwon.renderer.visitor.TestSpan.THEMATIC_BREAK; +import static ru.noties.markwon.renderer.visitor.TestSpan.UNDERLINE; + +class TestFactory implements SpannableFactory { + + private final boolean useParagraphs; + + TestFactory(boolean useParagraphs) { + this.useParagraphs = useParagraphs; + } + + @Nullable + @Override + public Object strongEmphasis() { + return new TestSpan(STRONG_EMPHASIS); + } + + @Nullable + @Override + public Object emphasis() { + return new TestSpan(EMPHASIS); + } + + @Nullable + @Override + public Object blockQuote(@NonNull SpannableTheme theme) { + return new TestSpan(BLOCK_QUOTE); + } + + @Nullable + @Override + public Object code(@NonNull SpannableTheme theme, boolean multiline) { + final String name = multiline + ? CODE_BLOCK + : CODE; + return new TestSpan(name); + } + + @Nullable + @Override + public Object orderedListItem(@NonNull SpannableTheme theme, int startNumber) { + return new TestSpan(ORDERED_LIST, map("start", startNumber)); + } + + @Nullable + @Override + public Object bulletListItem(@NonNull SpannableTheme theme, int level) { + return new TestSpan(BULLET_LIST, map("level", level)); + } + + @Nullable + @Override + public Object thematicBreak(@NonNull SpannableTheme theme) { + return new TestSpan(THEMATIC_BREAK); + } + + @Nullable + @Override + public Object heading(@NonNull SpannableTheme theme, int level) { + return new TestSpan(HEADING + level); + } + + @Nullable + @Override + public Object strikethrough() { + return new TestSpan(STRIKE_THROUGH); + } + + @Nullable + @Override + public Object taskListItem(@NonNull SpannableTheme theme, int blockIndent, boolean isDone) { + return new TestSpan(TASK_LIST, map( + Pair.of("blockIdent", blockIndent), + Pair.of("done", isDone) + )); + } + + @Nullable + @Override + public Object tableRow(@NonNull SpannableTheme theme, @NonNull List cells, boolean isHeader, boolean isOdd) { + return new TestSpan(TABLE_ROW, map( + Pair.of("cells", cells), + Pair.of("header", isHeader), + Pair.of("odd", isOdd) + )); + } + + @Nullable + @Override + public Object paragraph(boolean inTightList) { + return !useParagraphs + ? null + : new TestSpan(PARAGRAPH); + } + + @Nullable + @Override + public Object image(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) { + return new TestSpan(IMAGE, map( + Pair.of("src", destination), + Pair.of("imageSize", imageSize), + Pair.of("replacementTextIsLink", replacementTextIsLink) + )); + } + + @Nullable + @Override + public Object link(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull LinkSpan.Resolver resolver) { + return new TestSpan(LINK, map("href", destination)); + } + + @Nullable + @Override + public Object superScript(@NonNull SpannableTheme theme) { + return new TestSpan(SUPER_SCRIPT); + } + + @Nullable + @Override + public Object subScript(@NonNull SpannableTheme theme) { + return new TestSpan(SUB_SCRIPT); + } + + @Nullable + @Override + public Object underline() { + return new TestSpan(UNDERLINE); + } + + @NonNull + private static Map map(@NonNull String key, @Nullable Object value) { + return Collections.singletonMap(key, String.valueOf(value)); + } + + private static class Pair { + + static Pair of(@NonNull String key, @Nullable Object value) { + return new Pair(key, value); + } + + final String key; + final Object value; + + Pair(@NonNull String key, @Nullable Object value) { + this.key = key; + this.value = value; + } + } + + @NonNull + private static Map map(Pair... pairs) { + final int length = pairs.length; + final Map map = new HashMap<>(length); + for (Pair pair : pairs) { + map.put(pair.key, pair.value == null ? null : String.valueOf(pair.value)); + } + return map; + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java new file mode 100644 index 00000000..9124fd59 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java @@ -0,0 +1,140 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; +import java.util.Map; + +abstract class TestNode { + + private final TestNode parent; + + TestNode(@Nullable TestNode parent) { + this.parent = parent; + } + + @Nullable + public TestNode parent() { + return parent; + } + + abstract boolean isText(); + + abstract boolean isSpan(); + + @NonNull + abstract Text getAsText(); + + @NonNull + abstract Span getAsSpan(); + + + static class Text extends TestNode { + + private final String text; + + Text(@Nullable TestNode parent, @NonNull String text) { + super(parent); + this.text = text; + } + + @NonNull + public String text() { + return text; + } + + @Override + boolean isText() { + return true; + } + + @Override + boolean isSpan() { + return false; + } + + @NonNull + @Override + Text getAsText() { + return this; + } + + @NonNull + @Override + Span getAsSpan() { + throw new ClassCastException(); + } + + @Override + public String toString() { + return "Text{" + + "text='" + text + '\'' + + '}'; + } + } + + static class Span extends TestNode { + + private final String name; + private final List children; + private final Map attributes; + + Span( + @Nullable TestNode parent, + @NonNull String name, + @NonNull List children, + @NonNull Map attributes) { + super(parent); + this.name = name; + this.children = children; + this.attributes = attributes; + } + + @NonNull + public String name() { + return name; + } + + @NonNull + public List children() { + return children; + } + + @NonNull + public Map attributes() { + return attributes; + } + + @Override + boolean isText() { + return false; + } + + @Override + boolean isSpan() { + return true; + } + + @NonNull + @Override + Text getAsText() { + throw new ClassCastException(); + } + + @NonNull + @Override + Span getAsSpan() { + return this; + } + + @Override + public String toString() { + return "Span{" + + "name='" + name + '\'' + + ", children=" + children + + ", attributes=" + attributes + + '}'; + } + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java new file mode 100644 index 00000000..f4c8d6ba --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java @@ -0,0 +1,59 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; + +import java.util.Collections; +import java.util.Map; + +class TestSpan { + + static final String STRONG_EMPHASIS = "b"; + static final String EMPHASIS = "i"; + static final String BLOCK_QUOTE = "blockquote"; + static final String CODE = "code"; + static final String CODE_BLOCK = "code-block"; + static final String ORDERED_LIST = "ol"; + static final String BULLET_LIST = "ul"; + static final String THEMATIC_BREAK = "hr"; + static final String HEADING = "h"; + static final String STRIKE_THROUGH = "s"; + static final String TASK_LIST = "task-list"; + static final String TABLE_ROW = "tr"; + static final String PARAGRAPH = "p"; + static final String IMAGE = "img"; + static final String LINK = "a"; + static final String SUPER_SCRIPT = "sup"; + static final String SUB_SCRIPT = "sub"; + static final String UNDERLINE = "u"; + + + private final String name; + private final Map attributes; + + TestSpan(@NonNull String name) { + this(name, Collections.emptyMap()); + } + + TestSpan(@NonNull String name, @NonNull Map attributes) { + this.name = name; + this.attributes = attributes; + } + + @NonNull + public String name() { + return name; + } + + @NonNull + public Map attributes() { + return attributes; + } + + @Override + public String toString() { + return "TestSpan{" + + "name='" + name + '\'' + + ", attributes=" + attributes + + '}'; + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestValidator.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestValidator.java new file mode 100644 index 00000000..59c000fe --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestValidator.java @@ -0,0 +1,199 @@ +package ru.noties.markwon.renderer.visitor; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; + +import java.util.Map; + +import ix.Ix; +import ix.IxPredicate; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +abstract class TestValidator { + + abstract int validate( + @NonNull SpannableStringBuilder builder, + int index, + @NonNull TestNode node); + + abstract int processedSpanNodesCount(); + + + @NonNull + static TestValidator create(@NonNull String id) { + return new Impl(id); + } + + static class Impl extends TestValidator { + + private final String id; + + private int processedCount; + + Impl(@NonNull String id) { + this.id = id; + } + + @Override + int validate( + @NonNull final SpannableStringBuilder builder, + final int index, + @NonNull TestNode node) { + + if (node.isText()) { + + final String text; + { + final String content = node.getAsText().text(); + + // code is a special case as we wrap it around non-breakable spaces + final TestNode parent = node.parent(); + if (parent != null) { + final TestNode.Span span = parent.getAsSpan(); + if (TestSpan.CODE.equals(span.name())) { + text = "\u00a0" + content + "\u00a0"; + } else if (TestSpan.CODE_BLOCK.equals(span.name())) { + text = "\u00a0\n" + content + "\n\u00a0"; + } else { + text = content; + } + } else { + text = content; + } + } + + assertEquals( + String.format("text: %s, position: {%d-%d}", text, index, index + text.length()), + text, + builder.subSequence(index, index + text.length()).toString()); + + return index + text.length(); + } + + final TestNode.Span span = node.getAsSpan(); + processedCount += 1; + + int out = index; + + for (TestNode child : span.children()) { + out = validate(builder, out, child); + } + + final int end = out; + + // we can possibly have parent spans here, should filter them + final Object[] spans = builder.getSpans(index, out, Object.class); + + // expected span{name, attributes} at position{start-end}, with text: `%s`, spans: [] + + + assertTrue( + message(span, index, end, builder, spans), + spans != null + ); + + final TestSpan testSpan = Ix.fromArray(spans) + .filter(new IxPredicate() { + @Override + public boolean test(Object o) { + return o instanceof TestSpan; + } + }) + .cast(TestSpan.class) + .filter(new IxPredicate() { + @Override + public boolean test(TestSpan testSpan) { + + // in case of nested spans with the same name (lists) + // we also must validate attributes + // and thus we are moving most of assertions to this filter method + return span.name().equals(testSpan.name()) + && index == builder.getSpanStart(testSpan) + && end == builder.getSpanEnd(testSpan) + && mapEquals(span.attributes(), testSpan.attributes()); + } + }) + .first(null); + + assertNotNull( + message(span, index, end, builder, spans), + testSpan + ); + + return out; + } + + @Override + int processedSpanNodesCount() { + return processedCount; + } + + private static boolean mapEquals( + @NonNull Map expected, + @NonNull Map actual) { + + if (expected.size() != actual.size()) { + return false; + } + + boolean result = true; + + for (Map.Entry entry : expected.entrySet()) { + if (!actual.containsKey(entry.getKey()) + || !equals(entry.getValue(), actual.get(entry.getKey()))) { + result = false; + break; + } + } + + return result; + } + + private static boolean equals(@Nullable Object o1, @Nullable Object o2) { + return o1 != null + ? o1.equals(o2) + : o2 == null; + } + + @NonNull + private static String message( + @NonNull TestNode.Span span, + int start, + int end, + @NonNull Spanned text, + @Nullable Object[] spans) { + final String spansText; + if (spans == null + || spans.length == 0) { + spansText = "[]"; + } else { + final StringBuilder builder = new StringBuilder(); + for (Object o : spans) { + final TestSpan testSpan = (TestSpan) o; + if (builder.length() > 0) { + builder.append(", "); + } + + builder + .append("{name: '").append(testSpan.name()).append('\'') + .append(", position{").append(start).append(", ").append(end).append('}'); + + if (testSpan.attributes().size() > 0) { + builder.append(", attributes: ").append(testSpan.attributes()); + } + + builder.append('}'); + } + spansText = builder.toString(); + } + return String.format("Expected span: %s at position{%d-%d} with text `%s`, spans: %s", + span, start, end, text.subSequence(start, end), spansText + ); + } + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/test/TestUtils.java b/markwon/src/test/java/ru/noties/markwon/test/TestUtils.java new file mode 100644 index 00000000..4a8f3890 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/test/TestUtils.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.test; + +import android.support.annotation.NonNull; + +public abstract class TestUtils { + + public interface Action { + void apply(@NonNull T t); + } + + public static void with(@NonNull T t, @NonNull Action action) { + action.apply(t); + } + + private TestUtils() { + } +} diff --git a/markwon/src/test/resources/tests/bold-italic.yaml b/markwon/src/test/resources/tests/bold-italic.yaml new file mode 100644 index 00000000..d7d24682 --- /dev/null +++ b/markwon/src/test/resources/tests/bold-italic.yaml @@ -0,0 +1,5 @@ +input: "**_bold italic_**" + +output: + - b: + - i: "bold italic" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/code-blocks.yaml b/markwon/src/test/resources/tests/code-blocks.yaml new file mode 100644 index 00000000..53b9bd50 --- /dev/null +++ b/markwon/src/test/resources/tests/code-blocks.yaml @@ -0,0 +1,17 @@ +input: |- + ```java + final String s = null; + ``` + ```html + + ``` + ``` + nothing here + ``` + +output: + - code-block: "final String s = null;" + - "\n\n" + - code-block: "" + - "\n\n" + - code-block: "nothing here" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/deeply-nested.yaml b/markwon/src/test/resources/tests/deeply-nested.yaml new file mode 100644 index 00000000..2f40ddf3 --- /dev/null +++ b/markwon/src/test/resources/tests/deeply-nested.yaml @@ -0,0 +1,15 @@ +input: |- + **bold *bold italic ~~bold italic strike `bold italic strike code` bold italic strike~~ bold italic* bold** normal + +output: + - b: + - "bold " + - i: + - "bold italic " + - s: + - "bold italic strike " + - code: "bold italic strike code" + - " bold italic strike" + - " bold italic" + - " bold" + - " normal" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/first.yaml b/markwon/src/test/resources/tests/first.yaml new file mode 100644 index 00000000..d52d670d --- /dev/null +++ b/markwon/src/test/resources/tests/first.yaml @@ -0,0 +1,23 @@ +description: Defining test case format + +input: |- + Here is some [link](https://my.href) + **bold _bold italic_ bold** normal + +config: + use-paragraphs: false + use-html: false + soft-break-adds-new-line: false + html-allow-non-closed-tags: false + +output: + - "Here is some " + - a: "link" + href: "https://my.href" + - " " + - b: + - "bold " + - i: "bold italic" #equals to: `- i: - text: "bold italic"` + - " bold" + - " normal" + diff --git a/markwon/src/test/resources/tests/html-allow-non-closed-tags.yaml b/markwon/src/test/resources/tests/html-allow-non-closed-tags.yaml new file mode 100644 index 00000000..f0f219b0 --- /dev/null +++ b/markwon/src/test/resources/tests/html-allow-non-closed-tags.yaml @@ -0,0 +1,18 @@ +input: |- + italic + bold italic + underline bold italic + strike underline bold italic + +config: + use-html: true + html-allow-non-closed-tags: true + +output: + - i: + - "italic " + - b: + - "bold italic " + - u: + - "underline bold italic " + - s: "strike underline bold italic" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/html-non-closed-ignore.yaml b/markwon/src/test/resources/tests/html-non-closed-ignore.yaml new file mode 100644 index 00000000..0c97e34e --- /dev/null +++ b/markwon/src/test/resources/tests/html-non-closed-ignore.yaml @@ -0,0 +1,12 @@ +input: |- + no italic here + bold yeah + no underline + +config: + use-html: true + +output: + - "no italic here " + - b: "bold yeah" + - " no underline" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/html.yaml b/markwon/src/test/resources/tests/html.yaml new file mode 100644 index 00000000..d952ce96 --- /dev/null +++ b/markwon/src/test/resources/tests/html.yaml @@ -0,0 +1,105 @@ +input: |- +

    html

    +

    emphasis

    + iemcitedfn +

    strong-emphasis

    + bstrong +

    super-script

    + sup +

    sub-script

    + sub +

    underline

    + uins +

    strike

    + sdel +

    link

    + a +

    unordered-list

    +
    • ul1
    • ul2
    +

    ordered-list

    +
    1. ol1
    2. ol2
    +

    image

    + img +

    blockquote

    +
    blockquote
    +

    3

    +

    4

    +
    5
    +
    6
    + +config: + use-html: true + +output: + - h1: "html" + - "\n" + - h2: "emphasis" + - "\n" + - i: "i" + - i: "em" + - i: "cite" + - i: "dfn" + - "\n" + - h2: "strong-emphasis" + - "\n" + - b: "b" + - b: "strong" + - "\n" + - h2: "super-script" + - "\n" + - sup: "sup" + - "\n" + - h2: "sub-script" + - "\n" + - sub: "sub" + - "\n" + - h2: "underline" + - "\n" + - u: "u" + - u: "ins" + - "\n" + - h2: "strike" + - "\n" + - s: "s" + - s: "del" + - "\n" + - h2: "link" + - "\n" + - a: "a" + href: "a://href" + - "\n" + - h2: "unordered-list" + - "\n" + - ul: "ul1" + level: 0 + - "\n" + - ul: "ul2" + level: 0 + - "\n" + - h2: "ordered-list" + - "\n" + - ol: "ol1" + start: 1 + - "\n" + - ol: "ol2" + start: 2 + - "\n" + - h2: "image" + - "\n" + - img: "img" + src: "img://src" + imageSize: null + replacementTextIsLink: false + - "\n" + - h2: "blockquote" + - "\n" + - blockquote: "blockquote" + - "\n" + - h3: "3" + - "\n" + - h4: "4" + - "\n" + - h5: "5" + - "\n" + - h6: "6" + diff --git a/markwon/src/test/resources/tests/nested-blockquotes.yaml b/markwon/src/test/resources/tests/nested-blockquotes.yaml new file mode 100644 index 00000000..fbdb04b9 --- /dev/null +++ b/markwon/src/test/resources/tests/nested-blockquotes.yaml @@ -0,0 +1,12 @@ +input: |- + > First + > > Second + > > > Third + +output: + - blockquote: + - "First\n\n" + - blockquote: + - "Second\n\n" + - blockquote: + - "Third" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/no-paragraphs.yaml b/markwon/src/test/resources/tests/no-paragraphs.yaml new file mode 100644 index 00000000..048f3ef4 --- /dev/null +++ b/markwon/src/test/resources/tests/no-paragraphs.yaml @@ -0,0 +1,12 @@ +input: |- + This could be a paragraph + + But it is not and this one is not also + +config: + use-paragraphs: false + +output: + - text: "This could be a paragraph" + - text: "\n\n" + - text: "But it is not and this one is not also" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/ol-2-spaces.yaml b/markwon/src/test/resources/tests/ol-2-spaces.yaml new file mode 100644 index 00000000..0ffd7adb --- /dev/null +++ b/markwon/src/test/resources/tests/ol-2-spaces.yaml @@ -0,0 +1,16 @@ +description: "Will be rendered as simple flat list" + +input: |- + 1. First + 2. Second + 3. Third + +output: + - ol: "First" + start: 1 + - text: "\n" + - ol: "Second" + start: 2 + - text: "\n" + - ol: "Third" + start: 3 \ No newline at end of file diff --git a/markwon/src/test/resources/tests/ol-starts-with-5.yaml b/markwon/src/test/resources/tests/ol-starts-with-5.yaml new file mode 100644 index 00000000..eaabc7bb --- /dev/null +++ b/markwon/src/test/resources/tests/ol-starts-with-5.yaml @@ -0,0 +1,14 @@ +input: |- + 5. Five + 6. Six + 7. Seven + +output: + - ol: "Five" + start: 5 + - text: "\n" + - ol: "Six" + start: 6 + - text: "\n" + - ol: "Seven" + start: 7 \ No newline at end of file diff --git a/markwon/src/test/resources/tests/ol.yaml b/markwon/src/test/resources/tests/ol.yaml new file mode 100644 index 00000000..246b84ba --- /dev/null +++ b/markwon/src/test/resources/tests/ol.yaml @@ -0,0 +1,14 @@ +input: |- + 1. First + 1. Second + 1. Third + +output: + - ol: + - text: "First\n" + - ol: + - text: "Second\n" + - ol: "Third" + start: 1 + start: 1 + start: 1 \ No newline at end of file diff --git a/markwon/src/test/resources/tests/paragraph.yaml b/markwon/src/test/resources/tests/paragraph.yaml new file mode 100644 index 00000000..862bbc06 --- /dev/null +++ b/markwon/src/test/resources/tests/paragraph.yaml @@ -0,0 +1,12 @@ +input: |- + So, this is a paragraph + + And this one is another + +config: + use-paragraphs: true + +output: + - p: "So, this is a paragraph" + - text: "\n\n" + - p: "And this one is another" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/second.yaml b/markwon/src/test/resources/tests/second.yaml new file mode 100644 index 00000000..bd088dc2 --- /dev/null +++ b/markwon/src/test/resources/tests/second.yaml @@ -0,0 +1,32 @@ +input: |- + First **line** is *always* + ~~strike~~ down + + > Some quote here! + + # Header 1 + ## Header 2 + + and `some code` and more: + + ```java + the code in multiline + ``` + +output: + - text: "First " + - b: "line" + - text: " is " + - i: "always" + - text: " " + - s: "strike" + - text: " down\n\n" + - blockquote: "Some quote here!" + - text: "\n\n" + - h1: "Header 1" + - text: "\n\n" + - h2: "Header 2" + - text: "\n\nand " + - code: "some code" + - text: " and more:\n\n" + - code-block: "the code in multiline" diff --git a/markwon/src/test/resources/tests/single-a.yaml b/markwon/src/test/resources/tests/single-a.yaml new file mode 100644 index 00000000..82b1b134 --- /dev/null +++ b/markwon/src/test/resources/tests/single-a.yaml @@ -0,0 +1,5 @@ +input: "[link](#href)" + +output: + - a: "link" + href: "#href" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-b.yaml b/markwon/src/test/resources/tests/single-b.yaml new file mode 100644 index 00000000..a107424d --- /dev/null +++ b/markwon/src/test/resources/tests/single-b.yaml @@ -0,0 +1,4 @@ +input: "**bold**" + +output: + - b: "bold" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-blockquote.yaml b/markwon/src/test/resources/tests/single-blockquote.yaml new file mode 100644 index 00000000..3c8d818e --- /dev/null +++ b/markwon/src/test/resources/tests/single-blockquote.yaml @@ -0,0 +1,4 @@ +input: "> blockquote" + +output: + - blockquote: "blockquote" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-code-block.yaml b/markwon/src/test/resources/tests/single-code-block.yaml new file mode 100644 index 00000000..d44d9818 --- /dev/null +++ b/markwon/src/test/resources/tests/single-code-block.yaml @@ -0,0 +1,7 @@ +input: |- + ``` + code block + ``` + +output: + - code-block: "code block" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-code.yaml b/markwon/src/test/resources/tests/single-code.yaml new file mode 100644 index 00000000..e82d1123 --- /dev/null +++ b/markwon/src/test/resources/tests/single-code.yaml @@ -0,0 +1,4 @@ +input: "`code`" + +output: + - code: "code" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-h1.yaml b/markwon/src/test/resources/tests/single-h1.yaml new file mode 100644 index 00000000..613ae0c5 --- /dev/null +++ b/markwon/src/test/resources/tests/single-h1.yaml @@ -0,0 +1,4 @@ +input: "# head1" + +output: + - h1: "head1" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-h2.yaml b/markwon/src/test/resources/tests/single-h2.yaml new file mode 100644 index 00000000..09489697 --- /dev/null +++ b/markwon/src/test/resources/tests/single-h2.yaml @@ -0,0 +1,4 @@ +input: "## head2" + +output: + - h2: "head2" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-h3.yaml b/markwon/src/test/resources/tests/single-h3.yaml new file mode 100644 index 00000000..8f2d99a0 --- /dev/null +++ b/markwon/src/test/resources/tests/single-h3.yaml @@ -0,0 +1,4 @@ +input: "### head3" + +output: + - h3: "head3" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-h4.yaml b/markwon/src/test/resources/tests/single-h4.yaml new file mode 100644 index 00000000..b65a2b73 --- /dev/null +++ b/markwon/src/test/resources/tests/single-h4.yaml @@ -0,0 +1,4 @@ +input: "#### head4" + +output: + - h4: "head4" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-h5.yaml b/markwon/src/test/resources/tests/single-h5.yaml new file mode 100644 index 00000000..44a3d078 --- /dev/null +++ b/markwon/src/test/resources/tests/single-h5.yaml @@ -0,0 +1,4 @@ +input: "##### head5" + +output: + - h5: "head5" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-h6.yaml b/markwon/src/test/resources/tests/single-h6.yaml new file mode 100644 index 00000000..f040ecaf --- /dev/null +++ b/markwon/src/test/resources/tests/single-h6.yaml @@ -0,0 +1,4 @@ +input: "###### head6" + +output: + - h6: "head6" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-hr.yaml b/markwon/src/test/resources/tests/single-hr.yaml new file mode 100644 index 00000000..86bb106a --- /dev/null +++ b/markwon/src/test/resources/tests/single-hr.yaml @@ -0,0 +1,7 @@ +# it is failing as we are still removing white spaces manually +# this will be fixed when different logic for new lines will be introduced + +input: "---" + +output: + - hr: "\u00a0" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-i.yaml b/markwon/src/test/resources/tests/single-i.yaml new file mode 100644 index 00000000..334e923c --- /dev/null +++ b/markwon/src/test/resources/tests/single-i.yaml @@ -0,0 +1,4 @@ +input: "*italic*" + +output: + - i: "italic" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-img.yaml b/markwon/src/test/resources/tests/single-img.yaml new file mode 100644 index 00000000..9c55a2f6 --- /dev/null +++ b/markwon/src/test/resources/tests/single-img.yaml @@ -0,0 +1,7 @@ +input: "![image](#href)" + +output: + - img: "image" + src: "#href" + imageSize: null + replacementTextIsLink: false \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-ol.yaml b/markwon/src/test/resources/tests/single-ol.yaml new file mode 100644 index 00000000..a2046cb1 --- /dev/null +++ b/markwon/src/test/resources/tests/single-ol.yaml @@ -0,0 +1,5 @@ +input: "1. ol" + +output: + - ol: "ol" + start: 1 \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-s.yaml b/markwon/src/test/resources/tests/single-s.yaml new file mode 100644 index 00000000..3a1c12cc --- /dev/null +++ b/markwon/src/test/resources/tests/single-s.yaml @@ -0,0 +1,4 @@ +input: "~~strike~~" + +output: + - s: "strike" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-sub.yaml b/markwon/src/test/resources/tests/single-sub.yaml new file mode 100644 index 00000000..bfeb5367 --- /dev/null +++ b/markwon/src/test/resources/tests/single-sub.yaml @@ -0,0 +1,7 @@ +input: "sub" + +config: + use-html: true + +output: + - sub: "sub" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-sup.yaml b/markwon/src/test/resources/tests/single-sup.yaml new file mode 100644 index 00000000..8b21ad61 --- /dev/null +++ b/markwon/src/test/resources/tests/single-sup.yaml @@ -0,0 +1,7 @@ +input: "sup" + +config: + use-html: true + +output: + - sup: "sup" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-task-list.yaml b/markwon/src/test/resources/tests/single-task-list.yaml new file mode 100644 index 00000000..cbb15186 --- /dev/null +++ b/markwon/src/test/resources/tests/single-task-list.yaml @@ -0,0 +1,6 @@ +input: "- [ ] task-list" + +output: + - task-list: "task-list" + blockIdent: 1 + done: false \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-tr.yaml b/markwon/src/test/resources/tests/single-tr.yaml new file mode 100644 index 00000000..6ca92909 --- /dev/null +++ b/markwon/src/test/resources/tests/single-tr.yaml @@ -0,0 +1,13 @@ +input: "col1|col2|col3\n---|---|---|" + +output: + - tr: "\u00a0" + header: true + odd: false + cells: + - alignment: 0 + text: "col1" + - alignment: 0 + text: "col2" + - alignment: 0 + text: "col3" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-u.yaml b/markwon/src/test/resources/tests/single-u.yaml new file mode 100644 index 00000000..1bd135d1 --- /dev/null +++ b/markwon/src/test/resources/tests/single-u.yaml @@ -0,0 +1,7 @@ +input: "underline" + +config: + use-html: true + +output: + - u: "underline" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/single-ul.yaml b/markwon/src/test/resources/tests/single-ul.yaml new file mode 100644 index 00000000..d5fc6647 --- /dev/null +++ b/markwon/src/test/resources/tests/single-ul.yaml @@ -0,0 +1,5 @@ +input: "* ul" + +output: + - ul: "ul" + level: 0 \ No newline at end of file diff --git a/markwon/src/test/resources/tests/soft-break-adds-new-line.yaml b/markwon/src/test/resources/tests/soft-break-adds-new-line.yaml new file mode 100644 index 00000000..4fbb7d0a --- /dev/null +++ b/markwon/src/test/resources/tests/soft-break-adds-new-line.yaml @@ -0,0 +1,10 @@ +input: |- + hello there! + this one is on the next line + hard break to the full extend + +config: + soft-break-adds-new-line: true + +output: + - text: "hello there!\nthis one is on the next line\nhard break to the full extend" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/soft-break.yaml b/markwon/src/test/resources/tests/soft-break.yaml new file mode 100644 index 00000000..4406a5c7 --- /dev/null +++ b/markwon/src/test/resources/tests/soft-break.yaml @@ -0,0 +1,7 @@ +input: |- + First line + same line but with space between + this is also the first line + +output: + - text: "First line same line but with space between this is also the first line" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/table.yaml b/markwon/src/test/resources/tests/table.yaml new file mode 100644 index 00000000..96d8236f --- /dev/null +++ b/markwon/src/test/resources/tests/table.yaml @@ -0,0 +1,51 @@ +input: |- + head1|head2|head3 + ---|:---:|---: + row1-col1|row1-col2|row1-col3 + row2-col1|row2-col2|row2-col3 + row3-col1|row3-col2|row3-col3 + +output: + - tr: "\u00a0" + header: true + odd: false + cells: + - alignment: 0 + text: "head1" + - alignment: 1 + text: "head2" + - alignment: 2 + text: "head3" + - text: "\n" + - tr: "\u00a0" + header: false + odd: false + cells: + - alignment: 0 + text: "row1-col1" + - alignment: 1 + text: "row1-col2" + - alignment: 2 + text: "row1-col3" + - text: "\n" + - tr: "\u00a0" + header: false + odd: true + cells: + - alignment: 0 + text: "row2-col1" + - alignment: 1 + text: "row2-col2" + - alignment: 2 + text: "row2-col3" + - text: "\n" + - tr: "\u00a0" + header: false + odd: false + cells: + - alignment: 0 + text: "row3-col1" + - alignment: 1 + text: "row3-col2" + - alignment: 2 + text: "row3-col3" \ No newline at end of file diff --git a/markwon/src/test/resources/tests/ul-levels.yaml b/markwon/src/test/resources/tests/ul-levels.yaml new file mode 100644 index 00000000..a7a06c96 --- /dev/null +++ b/markwon/src/test/resources/tests/ul-levels.yaml @@ -0,0 +1,20 @@ +input: |- + * First + * * Second + * * * Third + +output: + - ul: "First" + level: 0 + - text: "\n" + - ul: + - ul: "Second" + level: 1 + level: 0 + - text: "\n" + - ul: + - ul: + - ul: "Third" + level: 2 + level: 1 + level: 0 \ No newline at end of file diff --git a/markwon/src/test/resources/tests/ul.yaml b/markwon/src/test/resources/tests/ul.yaml new file mode 100644 index 00000000..171145da --- /dev/null +++ b/markwon/src/test/resources/tests/ul.yaml @@ -0,0 +1,14 @@ +input: |- + * First + * Second + * Third + +output: + - ul: + - text: "First\n" + - ul: + - text: "Second\n" + - ul: "Third" + level: 2 + level: 1 + level: 0 \ No newline at end of file diff --git a/sample-custom-extension/build.gradle b/sample-custom-extension/build.gradle index baa37484..5672dfe9 100644 --- a/sample-custom-extension/build.gradle +++ b/sample-custom-extension/build.gradle @@ -2,8 +2,8 @@ apply plugin: 'com.android.application' android { - compileSdkVersion TARGET_SDK - buildToolsVersion BUILD_TOOLS + compileSdkVersion config['compile-sdk'] + buildToolsVersion config['build-tools'] defaultConfig { @@ -11,12 +11,12 @@ android { // using 21 as minimum only to be able to vector assets minSdkVersion 21 - targetSdkVersion TARGET_SDK + targetSdkVersion config['target-sdk'] versionCode 1 versionName version } } dependencies { - implementation project(':library') + implementation project(':markwon') } diff --git a/sample-latex-math/build.gradle b/sample-latex-math/build.gradle new file mode 100644 index 00000000..81732d95 --- /dev/null +++ b/sample-latex-math/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.application' + +android { + + compileSdkVersion config['compile-sdk'] + buildToolsVersion config['build-tools'] + + defaultConfig { + + applicationId "ru.noties.markwon.sample.jlatexmath" + + minSdkVersion config['min-sdk'] + targetSdkVersion config['target-sdk'] + versionCode 1 + versionName version + } +} + +dependencies { + implementation project(':markwon') + implementation project(':markwon-image-loader') + implementation 'ru.noties:jlatexmath-android:0.1.0' +} diff --git a/sample-latex-math/src/main/AndroidManifest.xml b/sample-latex-math/src/main/AndroidManifest.xml new file mode 100644 index 00000000..327ca324 --- /dev/null +++ b/sample-latex-math/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java new file mode 100644 index 00000000..3e3e4479 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlock.java @@ -0,0 +1,16 @@ +package ru.noties.markwon.sample.jlatexmath; + +import org.commonmark.node.CustomBlock; + +public class JLatexMathBlock extends CustomBlock { + + private String latex; + + public String latex() { + return latex; + } + + public void latex(String latex) { + this.latex = latex; + } +} diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java new file mode 100644 index 00000000..542b3869 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathBlockParser.java @@ -0,0 +1,79 @@ +package ru.noties.markwon.sample.jlatexmath; + +import org.commonmark.node.Block; +import org.commonmark.parser.block.AbstractBlockParser; +import org.commonmark.parser.block.AbstractBlockParserFactory; +import org.commonmark.parser.block.BlockContinue; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.MatchedBlockParser; +import org.commonmark.parser.block.ParserState; + +public class JLatexMathBlockParser extends AbstractBlockParser { + + private final JLatexMathBlock block = new JLatexMathBlock(); + + private final StringBuilder builder = new StringBuilder(); + + private boolean isClosed; + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + + if (isClosed) { + return BlockContinue.finished(); + } + + return BlockContinue.atIndex(parserState.getIndex()); + } + + @Override + public void addLine(CharSequence line) { + + if (builder.length() > 0) { + builder.append('\n'); + } + + builder.append(line); + + final int length = builder.length(); + if (length > 1) { + isClosed = '$' == builder.charAt(length - 1) + && '$' == builder.charAt(length - 2); + if (isClosed) { + builder.replace(length - 2, length, ""); + } + } + } + + @Override + public void closeBlock() { + block.latex(builder.toString()); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + + final CharSequence line = state.getLine(); + final int length = line != null + ? line.length() + : 0; + + if (length > 1) { + if ('$' == line.charAt(0) + && '$' == line.charAt(1)) { + return BlockStart.of(new JLatexMathBlockParser()) + .atIndex(state.getIndex() + 2); + } + } + + return BlockStart.none(); + } + } +} diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java new file mode 100644 index 00000000..bf0b76a5 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/JLatexMathMedia.java @@ -0,0 +1,138 @@ +package ru.noties.markwon.sample.jlatexmath; + +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Collection; +import java.util.Collections; +import java.util.Scanner; + +import ru.noties.jlatexmath.JLatexMathDrawable; +import ru.noties.markwon.il.ImageItem; +import ru.noties.markwon.il.MediaDecoder; +import ru.noties.markwon.il.SchemeHandler; + +public class JLatexMathMedia { + + public static class Config { + + protected final float textSize; + + protected Drawable background; + + @JLatexMathDrawable.Align + protected int align = JLatexMathDrawable.ALIGN_CENTER; + + protected boolean fitCanvas = true; + + protected int padding; + + public Config(float textSize) { + this.textSize = textSize; + } + } + + @NonNull + public static String makeDestination(@NonNull String latex) { + return SCHEME + "://" + latex; + } + + private static final String SCHEME = "jlatexmath"; + private static final String CONTENT_TYPE = "text/jlatexmath"; + + private final Config config; + + public JLatexMathMedia(@NonNull Config config) { + this.config = config; + } + + @NonNull + public SchemeHandler schemeHandler() { + return new SchemeHandlerImpl(); + } + + @NonNull + public MediaDecoder mediaDecoder() { + return new MediaDecoderImpl(config); + } + + static class SchemeHandlerImpl extends SchemeHandler { + + @Nullable + @Override + public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { + + ImageItem item = null; + + try { + final byte[] bytes = raw.substring(SCHEME.length()).getBytes("UTF-8"); + item = new ImageItem( + CONTENT_TYPE, + new ByteArrayInputStream(bytes), + null + ); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return item; + } + + @Override + public void cancel(@NonNull String raw) { + // no op + } + + @NonNull + @Override + public Collection schemes() { + return Collections.singleton(SCHEME); + } + } + + static class MediaDecoderImpl extends MediaDecoder { + + private final Config config; + + MediaDecoderImpl(@NonNull Config config) { + this.config = config; + } + + @Override + public boolean canDecodeByContentType(@Nullable String contentType) { + return CONTENT_TYPE.equals(contentType); + } + + @Override + public boolean canDecodeByFileName(@NonNull String fileName) { + return false; + } + + @Nullable + @Override + public Drawable decode(@NonNull InputStream inputStream) { + + final Scanner scanner = new Scanner(inputStream, "UTF-8").useDelimiter("\\A"); + final String latex = scanner.hasNext() + ? scanner.next() + : null; + + if (latex == null) { + return null; + } + + return JLatexMathDrawable.builder(latex) + .textSize(config.textSize) + .background(config.background) + .align(config.align) + .fitCanvas(config.fitCanvas) + .padding(config.padding) + .build(); + } + } +} diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java new file mode 100644 index 00000000..dee84ee4 --- /dev/null +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java @@ -0,0 +1,105 @@ +package ru.noties.markwon.sample.jlatexmath; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +import org.commonmark.node.CustomBlock; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; + +import ru.noties.jlatexmath.JLatexMathAndroid; +import ru.noties.markwon.Markwon; +import ru.noties.markwon.SpannableBuilder; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.il.AsyncDrawableLoader; +import ru.noties.markwon.renderer.ImageSize; +import ru.noties.markwon.renderer.SpannableMarkdownVisitor; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + final TextView textView = findViewById(R.id.text_view); + + String latex = "\\begin{array}{l}"; + latex += "\\forall\\varepsilon\\in\\mathbb{R}_+^*\\ \\exists\\eta>0\\ |x-x_0|\\leq\\eta\\Longrightarrow|f(x)-f(x_0)|\\leq\\varepsilon\\\\"; + latex += "\\det\\begin{bmatrix}a_{11}&a_{12}&\\cdots&a_{1n}\\\\a_{21}&\\ddots&&\\vdots\\\\\\vdots&&\\ddots&\\vdots\\\\a_{n1}&\\cdots&\\cdots&a_{nn}\\end{bmatrix}\\overset{\\mathrm{def}}{=}\\sum_{\\sigma\\in\\mathfrak{S}_n}\\varepsilon(\\sigma)\\prod_{k=1}^n a_{k\\sigma(k)}\\\\"; + latex += "\\sideset{_\\alpha^\\beta}{_\\gamma^\\delta}{\\begin{pmatrix}a&b\\\\c&d\\end{pmatrix}}\\\\"; + latex += "\\int_0^\\infty{x^{2n} e^{-a x^2}\\,dx} = \\frac{2n-1}{2a} \\int_0^\\infty{x^{2(n-1)} e^{-a x^2}\\,dx} = \\frac{(2n-1)!!}{2^{n+1}} \\sqrt{\\frac{\\pi}{a^{2n+1}}}\\\\"; + latex += "\\int_a^b{f(x)\\,dx} = (b - a) \\sum\\limits_{n = 1}^\\infty {\\sum\\limits_{m = 1}^{2^n - 1} {\\left( { - 1} \\right)^{m + 1} } } 2^{ - n} f(a + m\\left( {b - a} \\right)2^{-n} )\\\\"; + latex += "\\int_{-\\pi}^{\\pi} \\sin(\\alpha x) \\sin^n(\\beta x) dx = \\textstyle{\\left \\{ \\begin{array}{cc} (-1)^{(n+1)/2} (-1)^m \\frac{2 \\pi}{2^n} \\binom{n}{m} & n \\mbox{ odd},\\ \\alpha = \\beta (2m-n) \\\\ 0 & \\mbox{otherwise} \\\\ \\end{array} \\right .}\\\\"; + latex += "L = \\int_a^b \\sqrt{ \\left|\\sum_{i,j=1}^ng_{ij}(\\gamma(t))\\left(\\frac{d}{dt}x^i\\circ\\gamma(t)\\right)\\left(\\frac{d}{dt}x^j\\circ\\gamma(t)\\right)\\right|}\\,dt\\\\"; + latex += "\\begin{array}{rl} s &= \\int_a^b\\left\\|\\frac{d}{dt}\\vec{r}\\,(u(t),v(t))\\right\\|\\,dt \\\\ &= \\int_a^b \\sqrt{u'(t)^2\\,\\vec{r}_u\\cdot\\vec{r}_u + 2u'(t)v'(t)\\, \\vec{r}_u\\cdot\\vec{r}_v+ v'(t)^2\\,\\vec{r}_v\\cdot\\vec{r}_v}\\,\\,\\, dt. \\end{array}\\\\"; + latex += "\\end{array}"; + +// String latex = "\\text{A long division \\longdiv{12345}{13}"; +// String latex = "{a \\bangle b} {c \\brace d} {e \\brack f} {g \\choose h}"; + +// String latex = "\\begin{array}{cc}"; +// latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; +// latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; +// latex += "\\end{array}"; + + + final JLatexMathMedia.Config config = new JLatexMathMedia.Config(textView.getTextSize()) {{ +// align = JLatexMathDrawable.ALIGN_RIGHT; + }}; + final JLatexMathMedia jLatexMathMedia = new JLatexMathMedia(config); + + final AsyncDrawableLoader asyncDrawableLoader = AsyncDrawableLoader.builder() + .addSchemeHandler(jLatexMathMedia.schemeHandler()) + .mediaDecoders(jLatexMathMedia.mediaDecoder()) + .build(); + + final SpannableConfiguration configuration = SpannableConfiguration.builder(this) + .asyncDrawableLoader(asyncDrawableLoader) + .build(); + + final String markdown = "# Example of LaTeX\n\n$$" + + latex + "$$\n\n something like **this**"; + + final Parser parser = new Parser.Builder() + .customBlockParserFactory(new JLatexMathBlockParser.Factory()) + .build(); + + final Node node = parser.parse(markdown); + final SpannableBuilder builder = new SpannableBuilder(); + final SpannableMarkdownVisitor visitor = new SpannableMarkdownVisitor(SpannableConfiguration.create(this), builder) { + + @Override + public void visit(CustomBlock customBlock) { + + if (!(customBlock instanceof JLatexMathBlock)) { + super.visit(customBlock); + return; + } + + final String latex = ((JLatexMathBlock) customBlock).latex(); + + final int length = builder.length(); + builder.append(latex); + + SpannableBuilder.setSpans( + builder, + configuration.factory().image( + configuration.theme(), + JLatexMathMedia.makeDestination(latex), + configuration.asyncDrawableLoader(), + configuration.imageSizeResolver(), + new ImageSize(new ImageSize.Dimension(100, "%"), null), + false + ), + length, + builder.length() + ); + } + }; + node.accept(visitor); + + Markwon.setText(textView, builder.text()); + } +} diff --git a/sample-latex-math/src/main/res/layout/activity_main.xml b/sample-latex-math/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..23e9e463 --- /dev/null +++ b/sample-latex-math/src/main/res/layout/activity_main.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/sample-latex-math/src/main/res/values/strings.xml b/sample-latex-math/src/main/res/values/strings.xml new file mode 100644 index 00000000..58b56912 --- /dev/null +++ b/sample-latex-math/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Markwon-JLatexMath + diff --git a/sample-latex-math/src/main/res/values/styles.xml b/sample-latex-math/src/main/res/values/styles.xml new file mode 100644 index 00000000..b0e65b65 --- /dev/null +++ b/sample-latex-math/src/main/res/values/styles.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/settings.gradle b/settings.gradle index 29dc38f9..11192dbe 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ -include ':app', ':library', ':library-image-loader', ':library-view', ':sample-custom-extension', ':library-syntax' +rootProject.name = 'MarkwonProject' +include ':app', ':markwon', ':markwon-image-loader', ':markwon-view', ':sample-custom-extension', ':sample-latex-math', + ':markwon-syntax-highlight', ':markwon-html-parser-api', ':markwon-html-parser-impl'