diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dca982a2..d61b96152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- [#711](https://github.com/plotly/dash-core-components/pull/711) Added support for `dcc.Link` (dccLink) and nested `dcc.Markdown` (dccMarkdown) react components inside of `dcc.Markdown` - [#706](https://github.com/plotly/dash-core-components/pull/706) - Added new `responsive` property that overrides the underlying Plotly.js graph responsiveness from Dash-land - Added responsiveness on graph parent element resize (previously only worked on window.resize) diff --git a/MANIFEST.in b/MANIFEST.in index ade1fc8d1..31a1634fd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,9 @@ include dash_core_components/dash_core_components.min.js include dash_core_components/dash_core_components.min.js.map +include dash_core_components/dash_core_components-shared.js +include dash_core_components/dash_core_components-shared.js.map include dash_core_components/async~*.js include dash_core_components/async~*.js.map -include dash_core_components/highlight.pack.js include dash_core_components/metadata.json include dash_core_components/package-info.json include dash_core_components/plotly.min.js diff --git a/babel.config.js b/babel.config.js index 02368ad3a..b544f8e30 100644 --- a/babel.config.js +++ b/babel.config.js @@ -4,9 +4,7 @@ const presets = [ ]; const plugins = [ - '@babel/plugin-syntax-dynamic-import', - '@babel/plugin-transform-async-to-generator', - '@babel/plugin-transform-runtime' + '@babel/plugin-syntax-dynamic-import' ]; // eslint-disable-next-line no-process-env diff --git a/dash_core_components_base/__init__.py b/dash_core_components_base/__init__.py index 53c9ed4aa..e0c84be35 100644 --- a/dash_core_components_base/__init__.py +++ b/dash_core_components_base/__init__.py @@ -88,6 +88,23 @@ 'namespace': 'dash_core_components', 'dynamic': True }, + { + 'relative_package_path': '{}-shared.js'.format(__name__), + 'external_url': ( + 'https://unpkg.com/dash-core-components@{}' + '/dash_core_components/dash_core_components-shared.js' + ).format(__version__), + 'namespace': 'dash_core_components' + }, + { + 'relative_package_path': '{}-shared.js.map'.format(__name__), + 'external_url': ( + 'https://unpkg.com/dash-core-components@{}' + '/dash_core_components/dash_core_components-shared.js.map' + ).format(__version__), + 'namespace': 'dash_core_components', + 'dynamic': True + }, { 'relative_package_path': 'plotly.min.js', 'external_url': ( diff --git a/package-lock.json b/package-lock.json index 684772234..483b81d33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1311,52 +1311,6 @@ "regenerator-transform": "^0.13.4" } }, - "@babel/plugin-transform-runtime": { - "version": "7.7.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.6.tgz", - "integrity": "sha512-tajQY+YmXR7JjTwRvwL4HePqoL3DYxpYXIHKVvrOIvJmeHe2y1w4tz5qz9ObUDC9m76rCzIMPyn4eERuwA4a4A==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "resolve": "^1.8.1", - "semver": "^5.5.1" - }, - "dependencies": { - "@babel/helper-module-imports": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz", - "integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4" - } - }, - "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "@babel/plugin-transform-shorthand-properties": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", @@ -11982,8 +11936,7 @@ "neo-async": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", - "dev": true + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==" }, "next-tick": { "version": "1.0.0", @@ -12579,7 +12532,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" @@ -12588,14 +12540,12 @@ "minimist": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" } } }, @@ -13894,6 +13844,30 @@ "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==", "dev": true }, + "react-jsx-parser": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-1.21.0.tgz", + "integrity": "sha512-3ZYS0+Fiz1hx1owMDvA6skoYnISdsdthKEMLmcoqQ2CF+LzBKnYK/GtMJ5lKKz0AfZiNpsBq8qK7Av2ZE6vEVQ==", + "dev": true, + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + } + } + }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -16907,7 +16881,6 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.0.tgz", "integrity": "sha512-kY2sHdru6BcIDg+i1SCZ1bpPhZJ6yiE0bEKLEJDC4M/lDSMhr/zVJeuEzvHJGjAXJCizSzUVh9atREf2jnR7yQ==", - "dev": true, "optional": true, "requires": { "commander": "~2.19.0", @@ -16918,7 +16891,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "optional": true } } diff --git a/package.json b/package.json index 53260487a..4b0f996a5 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,6 @@ "@babel/core": "^7.4.0", "@babel/plugin-proposal-object-rest-spread": "^7.4.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.7.4", - "@babel/plugin-transform-runtime": "^7.7.6", "@babel/preset-env": "^7.4.1", "@babel/preset-react": "^7.0.0", "@plotly/dash-component-plugins": "^1.0.2", @@ -85,6 +83,8 @@ "prettier": "^1.14.2", "react": "^16.8.6", "react-dom": "^16.8.6", + "react-jsx-parser": "^1.21.0", + "react-resize-detector": "^4.2.1", "style-loader": "^0.23.1", "styled-jsx": "^3.1.1", "terser-webpack-plugin": "^2.3.0", diff --git a/src/fragments/Graph.react.js b/src/fragments/Graph.react.js index 7b99ed606..03c0bf3d7 100644 --- a/src/fragments/Graph.react.js +++ b/src/fragments/Graph.react.js @@ -270,7 +270,7 @@ class PlotlyGraph extends Component { ); } - async graphResize(force = false) { + graphResize(force = false) { if (!force && !this.isResponsive(this.props)) { return; } diff --git a/src/fragments/Markdown.react.js b/src/fragments/Markdown.react.js index ade03d63a..84854e7a7 100644 --- a/src/fragments/Markdown.react.js +++ b/src/fragments/Markdown.react.js @@ -1,10 +1,13 @@ import React, {Component} from 'react'; -import {type} from 'ramda'; +import {mergeDeepRight, pick, type} from 'ramda'; +import JsxParser from 'react-jsx-parser'; import Markdown from 'react-markdown'; import MarkdownHighlighter from '../utils/MarkdownHighlighter'; import {propTypes, defaultProps} from '../components/Markdown.react'; +import DccLink from './../components/Link.react'; + export default class DashMarkdown extends Component { constructor(props) { super(props); @@ -91,6 +94,18 @@ export default class DashMarkdown extends Component { const displayText = dedent && textProp ? this.dedent(textProp) : textProp; + const componentTransforms = { + dccLink: props => , + dccMarkdown: props => ( + + ), + }; + return (
+ props.escapeHtml ? ( + props.value + ) : ( + + ), + }} />
); diff --git a/src/utils/MarkdownHighlighter.js b/src/utils/MarkdownHighlighter.js index abfa9496e..ab446ddbe 100644 --- a/src/utils/MarkdownHighlighter.js +++ b/src/utils/MarkdownHighlighter.js @@ -1,10 +1,12 @@ import lazyhljs from './LazyLoader/hljs'; const MarkdownHighlighter = { - loadhljs: async function() { - this.hljs = await lazyhljs(); - this.hljsResolve(); - this.isReady = true; + loadhljs: function() { + return lazyhljs().then(hljs => { + this.hljs = hljs; + this.hljsResolve(); + this.isReady = true; + }); }, }; diff --git a/tests/assets/image.png b/tests/assets/image.png new file mode 100644 index 000000000..560370a6f Binary files /dev/null and b/tests/assets/image.png differ diff --git a/tests/integration/markdown/test_markdown.py b/tests/integration/markdown/test_markdown.py new file mode 100644 index 000000000..84757ca68 --- /dev/null +++ b/tests/integration/markdown/test_markdown.py @@ -0,0 +1,98 @@ +import os +import pytest +from selenium.common.exceptions import WebDriverException + +import dash +from dash.dependencies import Input, Output +import dash_core_components as dcc +import dash_html_components as html + + +def test_mkdw001_img(dash_dcc): + app = dash.Dash(__name__, eager_loading=True) + + app.layout = html.Div( + [ + html.Div('Markdown img'), + dcc.Markdown( + [''], dangerously_allow_html=True + ), + html.Div('Markdown img - requires dangerously_allow_html'), + dcc.Markdown(['']), + ] + ) + + dash_dcc.start_server(app) + dash_dcc.percy_snapshot("mkdw001 - image display") + + +def test_mkdw002_dcclink(dash_dcc): + app = dash.Dash(__name__, eager_loading=True) + + app.layout = html.Div( + [ + html.Div(['Markdown link']), + dcc.Markdown(['[Title](title_crumb)']), + html.Div(['Markdown dccLink']), + dcc.Markdown( + [''], + dangerously_allow_html=True, + ), + html.Div(['Markdown dccLink - explicit children']), + dcc.Markdown( + [ + ''' + + Title + + ''' + ], + dangerously_allow_html=True, + ), + html.Div('Markdown dccLink = inlined'), + dcc.Markdown( + [ + 'This is an inlined with text on both sides' + ], + dangerously_allow_html=True, + ), + html.Div('Markdown dccLink - nested image'), + dcc.Markdown( + [ + ''' + + + + ''' + ], + dangerously_allow_html=True, + ), + html.Div('Markdown dccLink - nested markdown'), + dcc.Markdown( + [ + ''' + + + + ''' + ], + dangerously_allow_html=True, + ), + html.Div('Markdown dccLink - nested markdown image'), + dcc.Markdown( + [ + ''' + + + + ''' + ], + dangerously_allow_html=True, + ), + html.Div('Markdown dccLink - requires dangerously_allow_html'), + dcc.Markdown(['']), + ] + ) + + dash_dcc.start_server(app) + dash_dcc.percy_snapshot("mkdw002 - markdowns display") diff --git a/tests/integration/misc/test_markdown_highlight.py b/tests/integration/misc/test_markdown_highlight.py index 634909641..ac0556ecf 100644 --- a/tests/integration/misc/test_markdown_highlight.py +++ b/tests/integration/misc/test_markdown_highlight.py @@ -17,6 +17,8 @@ def test_msmh001_no_window_variable(dash_dcc): app.layout = html.Div(dcc.Markdown(md_text)) dash_dcc.start_server(app) + dash_dcc.wait_for_element('code') + window_hljs = dash_dcc.driver.execute_script('return window.hljs') assert window_hljs is None diff --git a/webpack.config.js b/webpack.config.js index 48f5ecb62..166cc0450 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -59,10 +59,25 @@ module.exports = (env, argv) => { { test: /\.jsx?$/, exclude: /node_modules/, + use: { + loader: 'babel-loader' + } + }, + { + test: /\.jsx?$/, + include: /node_modules\/(react-jsx-parser\/)/, use: { loader: 'babel-loader', - }, + options: { + babelrc: false, + configFile: false, + presets: [ + '@babel/preset-env' + ] + } + } }, + { test: /\.css$/, use: [ @@ -105,6 +120,12 @@ module.exports = (env, argv) => { name(module, chunks, cacheGroupKey) { return `${cacheGroupKey}~${chunks[0].name}`; } + }, + shared: { + chunks: 'all', + minSize: 0, + minChunks: 2, + name: 'dash_core_components-shared' } } }