From 4adb28917960d163f81b73aa6c4ccd828c36ed3f Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Tue, 25 Aug 2026 17:15:43 -0600 Subject: [PATCH 1/2] rewrite dcc.Markdown using typescript and updated markdown libs --- .../dash-core-components/package-lock.json | 1613 ++++++++++++----- components/dash-core-components/package.json | 12 +- .../src/components/Graph.react.js | 2 +- .../src/components/Link.react.js | 149 -- .../src/components/Link.tsx | 74 + .../src/components/Markdown.react.js | 108 -- .../src/components/Markdown.tsx | 35 + .../src/fragments/Graph.react.js | 2 +- .../src/fragments/Markdown.react.js | 189 -- .../src/fragments/Markdown.tsx | 95 + .../src/fragments/Math.react.js | 54 - components/dash-core-components/src/index.ts | 4 +- components/dash-core-components/src/types.ts | 127 ++ .../src/utils/LazyLoader/hljs.js | 4 - .../src/utils/LazyLoader/markdown.js | 2 - .../src/utils/LazyLoader/markdown.ts | 2 + .../src/utils/LazyLoader/mathjax.js | 5 - .../LazyLoader/third-party/highlight.ts} | 0 .../src/utils/LazyLoader/third-party/hljs.ts | 17 + .../utils/LazyLoader/third-party/mathjax.ts | 20 + .../utils/LazyLoader/third-party/tex-svg.ts | 5 + .../src/utils/MarkdownHighlighter.js | 19 - .../src/utils/markdown/components/Code.tsx | 46 + .../src/utils/markdown/components/DccLink.tsx | 25 + .../utils/markdown/components/DccMarkdown.tsx | 13 + .../src/utils/markdown/components/MathJax.tsx | 26 + .../src/utils/markdown/components/index.ts | 13 + .../markdown/components/jsx-renderers.d.ts | 14 + .../src/utils/markdown/components/types.ts | 16 + .../src/utils/markdown/context.ts | 18 + .../src/utils/markdown/plugins/index.ts | 23 + .../utils/markdown/plugins/rehypeDashMath.ts | 69 + .../markdown/plugins/rehypeDccComponents.ts | 33 + .../utils/markdown/plugins/rehypeStripTags.ts | 23 + .../src/utils/markdown/text.ts | 64 + .../dash-core-components/src/utils/mathjax.js | 3 - .../tests/integration/link/test_link_props.py | 68 + .../integration/markdown/test_markdown.py | 419 +++-- .../tests/unit/markdown/text.test.ts | 100 + .../dash-core-components/webpack.config.js | 2 +- dash/dash-renderer/src/types/index.ts | 5 + 41 files changed, 2430 insertions(+), 1088 deletions(-) delete mode 100644 components/dash-core-components/src/components/Link.react.js create mode 100644 components/dash-core-components/src/components/Link.tsx delete mode 100644 components/dash-core-components/src/components/Markdown.react.js create mode 100644 components/dash-core-components/src/components/Markdown.tsx delete mode 100644 components/dash-core-components/src/fragments/Markdown.react.js create mode 100644 components/dash-core-components/src/fragments/Markdown.tsx delete mode 100644 components/dash-core-components/src/fragments/Math.react.js delete mode 100644 components/dash-core-components/src/utils/LazyLoader/hljs.js delete mode 100644 components/dash-core-components/src/utils/LazyLoader/markdown.js create mode 100644 components/dash-core-components/src/utils/LazyLoader/markdown.ts delete mode 100644 components/dash-core-components/src/utils/LazyLoader/mathjax.js rename components/dash-core-components/src/{third-party/highlight.js => utils/LazyLoader/third-party/highlight.ts} (100%) create mode 100644 components/dash-core-components/src/utils/LazyLoader/third-party/hljs.ts create mode 100644 components/dash-core-components/src/utils/LazyLoader/third-party/mathjax.ts create mode 100644 components/dash-core-components/src/utils/LazyLoader/third-party/tex-svg.ts delete mode 100644 components/dash-core-components/src/utils/MarkdownHighlighter.js create mode 100644 components/dash-core-components/src/utils/markdown/components/Code.tsx create mode 100644 components/dash-core-components/src/utils/markdown/components/DccLink.tsx create mode 100644 components/dash-core-components/src/utils/markdown/components/DccMarkdown.tsx create mode 100644 components/dash-core-components/src/utils/markdown/components/MathJax.tsx create mode 100644 components/dash-core-components/src/utils/markdown/components/index.ts create mode 100644 components/dash-core-components/src/utils/markdown/components/jsx-renderers.d.ts create mode 100644 components/dash-core-components/src/utils/markdown/components/types.ts create mode 100644 components/dash-core-components/src/utils/markdown/context.ts create mode 100644 components/dash-core-components/src/utils/markdown/plugins/index.ts create mode 100644 components/dash-core-components/src/utils/markdown/plugins/rehypeDashMath.ts create mode 100644 components/dash-core-components/src/utils/markdown/plugins/rehypeDccComponents.ts create mode 100644 components/dash-core-components/src/utils/markdown/plugins/rehypeStripTags.ts create mode 100644 components/dash-core-components/src/utils/markdown/text.ts delete mode 100644 components/dash-core-components/src/utils/mathjax.js create mode 100644 components/dash-core-components/tests/integration/link/test_link_props.py create mode 100644 components/dash-core-components/tests/unit/markdown/text.test.ts diff --git a/components/dash-core-components/package-lock.json b/components/dash-core-components/package-lock.json index 9c177b39ac..a5ea958a71 100644 --- a/components/dash-core-components/package-lock.json +++ b/components/dash-core-components/package-lock.json @@ -34,10 +34,12 @@ "react-dropzone": "^4.1.2", "react-fast-compare": "^3.2.2", "react-input-autosize": "^3.0.0", - "react-markdown": "^4.3.1", + "react-markdown": "^8.0.7", "react-window": "^1.8.11", - "remark-math": "^3.0.1", - "uniqid": "^5.4.0" + "rehype-raw": "^6.1.1", + "remark-math": "^5.1.1", + "uniqid": "^5.4.0", + "unist-util-visit": "^4.1.2" }, "devDependencies": { "@babel/cli": "^7.28.6", @@ -54,6 +56,7 @@ "@testing-library/react": "^16.3.2", "@types/d3-format": "^3.0.4", "@types/fast-isnumeric": "^1.1.2", + "@types/hast": "^2.3.10", "@types/jest": "^29.5.14", "@types/js-search": "^1.4.4", "@types/ramda": "^0.31.0", @@ -79,7 +82,6 @@ "prettier": "^2.8.8", "react": "^18", "react-dom": "^18", - "react-jsx-parser": "2.4.1", "rimraf": "^6.1.3", "style-loader": "^3.3.3", "styled-jsx": "^5.1.7", @@ -4524,6 +4526,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -4548,6 +4559,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4614,6 +4634,27 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.13.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", @@ -4623,11 +4664,16 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, "license": "MIT" }, "node_modules/@types/ramda": { @@ -4707,6 +4753,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -5822,9 +5874,9 @@ } }, "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", "funding": { "type": "github", @@ -6285,29 +6337,9 @@ } }, "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", "funding": { "type": "github", @@ -6428,16 +6460,6 @@ "node": ">= 0.12.0" } }, - "node_modules/collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -6460,6 +6482,16 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -6905,6 +6937,19 @@ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dedent": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", @@ -6963,7 +7008,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -6994,6 +7038,15 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -7062,32 +7115,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/domain-browser": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz", @@ -7100,47 +7127,6 @@ "url": "https://bevry.me/fund" } }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -8678,6 +8664,112 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/highlight.js": { "version": "11.11.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", @@ -8720,49 +8812,14 @@ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, - "node_modules/html-to-react": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.7.0.tgz", - "integrity": "sha512-b5HTNaTGyOj5GGIMiWVr1k57egAZ/vGy0GGefnCQ1VW5hu9+eku8AXHtf2/DeD95cj/FKBKYa1J7SWBOX41yUQ==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0", - "htmlparser2": "^9.0", - "lodash.camelcase": "^4.3.0" - }, - "peerDependencies": { - "react": "^0.13.0 || ^0.14.0 || >=15" - } - }, - "node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/http-proxy-agent": { @@ -9005,6 +9062,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -9029,30 +9092,6 @@ "node": ">=10.13.0" } }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -9157,10 +9196,27 @@ } }, "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } }, "node_modules/is-callable": { "version": "1.2.7", @@ -9222,16 +9278,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-document.all": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", @@ -9315,16 +9361,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -9402,12 +9438,15 @@ } }, "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-object": { @@ -9581,30 +9620,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/isexe": { "version": "2.0.0", @@ -12091,6 +12110,31 @@ "node": ">=4.0" } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -12202,12 +12246,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -12229,6 +12267,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -12298,16 +12346,6 @@ "tmpl": "1.0.5" } }, - "node_modules/markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -12334,13 +12372,105 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/mdast-add-list-metadata": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz", - "integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==", + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-2.0.2.tgz", + "integrity": "sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", "license": "MIT", "dependencies": { - "unist-util-visit-parents": "1.1.2" + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/memoize-one": { @@ -12358,19 +12488,480 @@ "node": ">= 0.10.0" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-math": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.1.2.tgz", + "integrity": "sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "katex": "^0.16.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" + "node_modules/micromark/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, "node_modules/micromatch": { @@ -12487,6 +13078,15 @@ "node": ">=10" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -13058,20 +13658,6 @@ "node": ">= 0.10" } }, - "node_modules/parse-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", - "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", - "license": "MIT", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -13458,6 +14044,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -13688,45 +14284,42 @@ "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", "dev": true }, - "node_modules/react-jsx-parser": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-2.4.1.tgz", - "integrity": "sha512-gE25DiHseuKmSbb5O1K1IkyQ/bwth3zl4z3EdW8XMhwrb2agUpuh8GvqHFvGwh/wdZjDDuadGrSR2lEgjcOvqQ==", - "dev": true, - "dependencies": { - "acorn": "^8.12.1", - "acorn-jsx": "^5.3.2" - }, - "engines": { - "bun": "^1.1.27" + "node_modules/react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" }, - "optionalDependencies": { - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" }, "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "@types/react": ">=16", + "react": ">=16" } }, - "node_modules/react-markdown": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz", - "integrity": "sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==", - "license": "MIT", - "dependencies": { - "html-to-react": "^1.3.4", - "mdast-add-list-metadata": "1.0.1", - "prop-types": "^15.7.2", - "react-is": "^16.8.6", - "remark-parse": "^5.0.0", - "unified": "^6.1.5", - "unist-util-visit": "^1.3.0", - "xtend": "^4.0.1" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0" - } + "node_modules/react-markdown/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/react-remove-scroll": { "version": "2.7.2", @@ -13984,55 +14577,123 @@ "regjsparser": "bin/parser" } }, + "node_modules/rehype-raw": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", + "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-raw": "^7.2.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-math": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-3.0.1.tgz", - "integrity": "sha512-epT77R/HK0x7NqrWHdSV75uNLwn8g9qTyMqCRCDujL0vj/6T6+yhdrR7mjELWtkse+Fw02kijAaBuVcHBor1+Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-5.1.1.tgz", + "integrity": "sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==", "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-math": "^2.0.0", + "micromark-extension-math": "^2.0.0", + "unified": "^10.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/remark-parse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", - "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", "license": "MIT", "dependencies": { - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^1.1.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^1.0.0", - "vfile-location": "^2.0.0", - "xtend": "^4.0.1" + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", "license": "MIT", - "engines": { - "node": ">=0.10" + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA==", + "node_modules/remark-rehype/node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", "license": "MIT", - "engines": { - "node": ">= 0.10" + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-rehype/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, "node_modules/require-directory": { @@ -14297,6 +14958,18 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-array-concat": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", @@ -14699,6 +15372,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -14761,16 +15444,6 @@ "node": ">=8" } }, - "node_modules/state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -15051,6 +15724,15 @@ "webpack": "^5.0.0" } }, + "node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, "node_modules/styled-jsx": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.7.tgz", @@ -15360,16 +16042,10 @@ "node": ">=18" } }, - "node_modules/trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", - "deprecated": "Use String.prototype.trim() instead" - }, - "node_modules/trim-trailing-lines": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", - "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { "type": "github", @@ -15377,9 +16053,9 @@ } }, "node_modules/trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "license": "MIT", "funding": { "type": "github", @@ -15695,20 +16371,6 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true }, - "node_modules/unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -15754,17 +16416,22 @@ } }, "node_modules/unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", "license": "MIT", "dependencies": { - "bail": "^1.0.0", + "@types/unist": "^2.0.0", + "bail": "^2.0.0", "extend": "^3.0.0", - "is-plain-obj": "^1.1.0", - "trough": "^1.0.0", - "vfile": "^2.0.0", - "x-is-string": "^0.1.0" + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/uniqid": { @@ -15772,19 +16439,36 @@ "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-5.4.0.tgz", "integrity": "sha512-38JRbJ4Fj94VmnC7G/J/5n5SC7Ab46OM5iNtSstB/ko3l1b5g7ALt4qzHFgGciFkyiRNtDXtLNb+VsxtMSE77A==" }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", - "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", - "license": "MIT" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/unist-util-remove-position": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", - "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", "license": "MIT", "dependencies": { - "unist-util-visit": "^1.1.0" + "@types/unist": "^2.0.0" }, "funding": { "type": "opencollective", @@ -15792,33 +16476,45 @@ } }, "node_modules/unist-util-stringify-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", - "license": "MIT" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, "node_modules/unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", "license": "MIT", "dependencies": { - "unist-util-visit-parents": "^2.0.0" + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-visit-parents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz", - "integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==", - "license": "MIT" - }, - "node_modules/unist-util-visit/node_modules/unist-util-visit-parents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", - "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", "license": "MIT", "dependencies": { - "unist-util-is": "^3.0.0" + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/untildify": { @@ -15949,6 +16645,33 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uvu/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", @@ -15973,34 +16696,47 @@ } }, "node_modules/vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", "license": "MIT", "dependencies": { - "is-buffer": "^1.1.4", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-message": "^1.0.0" + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/vfile-location": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", - "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/vfile-message": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", - "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", "license": "MIT", "dependencies": { - "unist-util-stringify-position": "^1.1.1" + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/vm-browserify": { @@ -16045,6 +16781,16 @@ "node": ">=10.13.0" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -16419,11 +17165,6 @@ } } }, - "node_modules/x-is-string": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==" - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -16496,6 +17237,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/components/dash-core-components/package.json b/components/dash-core-components/package.json index 59887f8274..85ad16a6a5 100644 --- a/components/dash-core-components/package.json +++ b/components/dash-core-components/package.json @@ -27,7 +27,7 @@ "build:js": "webpack --mode production", "build:backends": "dash-generate-components ./src/components dash_core_components -p package-info.json && cp dash_core_components_base/** dash_core_components/ && dash-generate-components ./src/components dash_core_components -p package-info.json -k RangeSlider,Slider,Dropdown,RadioItems,Checklist,DatePickerSingle,DatePickerRange,Input,Link --r-prefix 'dcc' --r-suggests 'dash,dashHtmlComponents,jsonlite,plotly' --jl-prefix 'dcc' && black dash_core_components", "build": "run-s prepublishOnly build:js build:backends", - "postbuild": "es-check es2017 dash_core_components/*.js", + "postbuild": "es-check es2018 dash_core_components/*.js", "build:watch": "watch 'npm run build' src", "format": "run-s private::format.*", "lint": "run-s private::lint.*" @@ -61,10 +61,12 @@ "react-dropzone": "^4.1.2", "react-fast-compare": "^3.2.2", "react-input-autosize": "^3.0.0", - "react-markdown": "^4.3.1", + "react-markdown": "^8.0.7", "react-window": "^1.8.11", - "remark-math": "^3.0.1", - "uniqid": "^5.4.0" + "rehype-raw": "^6.1.1", + "remark-math": "^5.1.1", + "uniqid": "^5.4.0", + "unist-util-visit": "^4.1.2" }, "devDependencies": { "@babel/cli": "^7.28.6", @@ -81,6 +83,7 @@ "@testing-library/react": "^16.3.2", "@types/d3-format": "^3.0.4", "@types/fast-isnumeric": "^1.1.2", + "@types/hast": "^2.3.10", "@types/jest": "^29.5.14", "@types/js-search": "^1.4.4", "@types/ramda": "^0.31.0", @@ -106,7 +109,6 @@ "prettier": "^2.8.8", "react": "^18", "react-dom": "^18", - "react-jsx-parser": "2.4.1", "rimraf": "^6.1.3", "style-loader": "^3.3.3", "styled-jsx": "^5.1.7", diff --git a/components/dash-core-components/src/components/Graph.react.js b/components/dash-core-components/src/components/Graph.react.js index 94dab4559c..6dcdaba921 100644 --- a/components/dash-core-components/src/components/Graph.react.js +++ b/components/dash-core-components/src/components/Graph.react.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import {asyncDecorator} from '@plotly/dash-component-plugins'; import graph from '../utils/LazyLoader/graph'; import plotly from '../utils/LazyLoader/plotly'; -import lazyLoadMathJax from '../utils/LazyLoader/mathjax'; +import lazyLoadMathJax from '../utils/LazyLoader/third-party/mathjax'; import { privatePropTypes, privateDefaultProps, diff --git a/components/dash-core-components/src/components/Link.react.js b/components/dash-core-components/src/components/Link.react.js deleted file mode 100644 index b6d99b5737..0000000000 --- a/components/dash-core-components/src/components/Link.react.js +++ /dev/null @@ -1,149 +0,0 @@ -import PropTypes from 'prop-types'; - -import React, {useEffect, useMemo} from 'react'; -import {isNil} from 'ramda'; - -/* - * event polyfill for IE - * https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent - */ -function CustomEvent(event, params) { - // eslint-disable-next-line no-param-reassign - params = params || { - bubbles: false, - cancelable: false, - // eslint-disable-next-line no-undefined - detail: undefined, - }; - const evt = document.createEvent('CustomEvent'); - evt.initCustomEvent( - event, - params.bubbles, - params.cancelable, - params.detail - ); - return evt; -} -CustomEvent.prototype = window.Event.prototype; - -/** - * Link allows you to create a clickable link within a multi-page app. - * - * For links with destinations outside the current app, `html.A` is a better - * component to use. - */ -const Link = ({refresh = false, ...props}) => { - const {className, style, id, href, children, title, target, setProps} = - props; - const cleanUrl = window.dash_clientside.clean_url; - const sanitizedUrl = useMemo(() => { - return href ? cleanUrl(href) : undefined; - }, [href]); - - const ctx = window.dash_component_api.useDashContext(); - const loading = ctx.useLoading(); - - const updateLocation = e => { - const hasModifiers = e.metaKey || e.shiftKey || e.altKey || e.ctrlKey; - - if (hasModifiers) { - return; - } - if (target !== '_self' && !isNil(target)) { - return; - } - // prevent anchor from updating location - e.preventDefault(); - if (refresh) { - window.location = sanitizedUrl; - } else { - window.history.pushState({}, '', sanitizedUrl); - window.dispatchEvent(new CustomEvent('_dashprivate_pushstate')); - } - // scroll back to top - window.scrollTo(0, 0); - }; - - useEffect(() => { - if (sanitizedUrl && sanitizedUrl !== href) { - setProps({ - _dash_error: new Error(`Dangerous link detected:: ${href}`), - }); - } - }, [href, sanitizedUrl]); - - return ( - - {isNil(children) ? sanitizedUrl : children} - - ); -}; - -Link.propTypes = { - /** - * The children of this component - */ - children: PropTypes.node, - /** - * The URL of a linked resource. - */ - href: PropTypes.string.isRequired, - /** - * Specifies where to open the link reference. - */ - target: PropTypes.string, - /** - * Controls whether or not the page will refresh when the link is clicked - */ - refresh: PropTypes.bool, - - /** - * Adds the title attribute to your link, which can contain supplementary - * information. - */ - title: PropTypes.string, - - /** - * Often used with CSS to style elements with common properties. - */ - className: PropTypes.string, - /** - * Defines CSS styles which will override styles previously set. - */ - style: PropTypes.object, - /** - * The ID of this component, used to identify dash components - * in callbacks. The ID needs to be unique across all of the - * components in an app. - */ - id: PropTypes.string, - /** - * Object that holds the loading state object coming from dash-renderer - */ - loading_state: PropTypes.shape({ - /** - * Determines if the component is loading or not - */ - is_loading: PropTypes.bool, - /** - * Holds which property is loading - */ - prop_name: PropTypes.string, - /** - * Holds the name of the component that is loading - */ - component_name: PropTypes.string, - }), - setProps: PropTypes.func, -}; - -export default Link; diff --git a/components/dash-core-components/src/components/Link.tsx b/components/dash-core-components/src/components/Link.tsx new file mode 100644 index 0000000000..af727399be --- /dev/null +++ b/components/dash-core-components/src/components/Link.tsx @@ -0,0 +1,74 @@ +import React, {useEffect, useMemo} from 'react'; +import {isNil} from 'ramda'; + +import LoadingElement from '../utils/_LoadingElement'; +import {LinkProps} from '../types'; + +type LinkComponentProps = LinkProps & { + setProps?: (props: Record) => void; +}; + +/** + * Link allows you to create a clickable link within a multi-page app. + * + * For links with destinations outside the current app, `html.A` is a better + * component to use. + */ +const Link = ({refresh = false, ...props}: LinkComponentProps) => { + const {className, style, id, href, children, title, target, setProps} = + props; + const cleanUrl = window.dash_clientside.clean_url; + const sanitizedUrl = useMemo(() => { + return href ? cleanUrl(href) : undefined; + }, [href]); + + const updateLocation = (e: React.MouseEvent) => { + const hasModifiers = e.metaKey || e.shiftKey || e.altKey || e.ctrlKey; + + if (hasModifiers) { + return; + } + if (target !== '_self' && !isNil(target)) { + return; + } + // prevent anchor from updating location + e.preventDefault(); + if (refresh) { + window.location.href = sanitizedUrl as string; + } else { + window.history.pushState({}, '', sanitizedUrl); + window.dispatchEvent(new CustomEvent('_dashprivate_pushstate')); + } + // scroll back to top + window.scrollTo(0, 0); + }; + + useEffect(() => { + if (sanitizedUrl && sanitizedUrl !== href) { + setProps?.({ + _dash_error: new Error(`Dangerous link detected:: ${href}`), + }); + } + }, [href, sanitizedUrl]); + + return ( + + {loadingProps => ( + + {isNil(children) ? sanitizedUrl : children} + + )} + + ); +}; + +export default Link; diff --git a/components/dash-core-components/src/components/Markdown.react.js b/components/dash-core-components/src/components/Markdown.react.js deleted file mode 100644 index a2daa0538d..0000000000 --- a/components/dash-core-components/src/components/Markdown.react.js +++ /dev/null @@ -1,108 +0,0 @@ -import {asyncDecorator} from '@plotly/dash-component-plugins'; -import PropTypes from 'prop-types'; -import React, {Component, Suspense} from 'react'; -import markdown from '../utils/LazyLoader/markdown'; -import lazyLoadMathJax from '../utils/LazyLoader/mathjax'; - -// eslint-disable-next-line valid-jsdoc -/** - * A component that renders Markdown text as specified by the - * GitHub Markdown spec. These component uses - * [react-markdown](https://rexxars.github.io/react-markdown/) under the hood. - */ -export default class DashMarkdown extends Component { - constructor(props) { - super(props); - - if (props.mathjax) { - DashMarkdown._loadMathjax = true; - } - } - - render() { - return ( - - - - ); - } -} - -DashMarkdown.propTypes = { - /** - * The ID of this component, used to identify dash components - * in callbacks. The ID needs to be unique across all of the - * components in an app. - */ - id: PropTypes.string, - /** - * Class name of the container element - */ - className: PropTypes.string, - - /** - * If true, loads mathjax v3 (tex-svg) into the page and use it in the markdown - */ - mathjax: PropTypes.bool, - - /** - * A boolean to control raw HTML escaping. - * Setting HTML from code is risky because it's easy to - * inadvertently expose your users to a cross-site scripting (XSS) - * (https://en.wikipedia.org/wiki/Cross-site_scripting) attack. - */ - dangerously_allow_html: PropTypes.bool, - - /** - * A string for the target attribute to use on links (such as "_blank") - */ - link_target: PropTypes.string, - - /** - * A markdown string (or array of strings) that adheres to the CommonMark spec - */ - children: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - - /** - * Remove matching leading whitespace from all lines. - * Lines that are empty, or contain *only* whitespace, are ignored. - * Both spaces and tab characters are removed, but only if they match; - * we will not convert tabs to spaces or vice versa. - */ - dedent: PropTypes.bool, - - /** - * Config options for syntax highlighting. - */ - highlight_config: PropTypes.exact({ - /** - * Color scheme; default 'light' - */ - theme: PropTypes.oneOf(['dark', 'light']), - }), - - /** - * User-defined inline styles for the rendered Markdown - */ - style: PropTypes.object, -}; - -DashMarkdown.defaultProps = { - mathjax: false, - dangerously_allow_html: false, - highlight_config: {}, - dedent: true, -}; - -const RealDashMarkdown = asyncDecorator(DashMarkdown, () => - Promise.all([ - markdown(), - DashMarkdown._loadMathjax ? lazyLoadMathJax() : undefined, - ]).then(([md]) => md) -); - -export const propTypes = DashMarkdown.propTypes; -export const defaultProps = DashMarkdown.defaultProps; diff --git a/components/dash-core-components/src/components/Markdown.tsx b/components/dash-core-components/src/components/Markdown.tsx new file mode 100644 index 0000000000..a37f422fac --- /dev/null +++ b/components/dash-core-components/src/components/Markdown.tsx @@ -0,0 +1,35 @@ +import React, {lazy, Suspense, useEffect} from 'react'; +import {MarkdownProps} from '../types'; +import markdown from '../utils/LazyLoader/markdown'; +import lazyLoadMathJax from '../utils/LazyLoader/third-party/mathjax'; + +const RealMarkdown = lazy(markdown); + +/** + * A component that renders Markdown text as specified by the + * GitHub Markdown spec. These component uses + * [react-markdown](https://github.com/remarkjs/react-markdown) under the hood. + */ +export default function Markdown({ + mathjax = false, + dangerously_allow_html = false, + dedent = true, + ...props +}: MarkdownProps) { + useEffect(() => { + if (mathjax) { + lazyLoadMathJax(); + } + }, [mathjax]); + + return ( + + + + ); +} diff --git a/components/dash-core-components/src/fragments/Graph.react.js b/components/dash-core-components/src/fragments/Graph.react.js index 8fae4f7189..0edf2187c9 100644 --- a/components/dash-core-components/src/fragments/Graph.react.js +++ b/components/dash-core-components/src/fragments/Graph.react.js @@ -1,4 +1,4 @@ -import lazyLoadMathJax from '../utils/LazyLoader/mathjax'; +import lazyLoadMathJax from '../utils/LazyLoader/third-party/mathjax'; import React, {Component} from 'react'; import { equals, diff --git a/components/dash-core-components/src/fragments/Markdown.react.js b/components/dash-core-components/src/fragments/Markdown.react.js deleted file mode 100644 index 557ed863fd..0000000000 --- a/components/dash-core-components/src/fragments/Markdown.react.js +++ /dev/null @@ -1,189 +0,0 @@ -import React, {Component} from 'react'; -import {mergeDeepRight, pick, type} from 'ramda'; -import JsxParser from 'react-jsx-parser'; -import Markdown from 'react-markdown'; -import RemarkMath from 'remark-math'; - -import Math from './Math.react'; -import MarkdownHighlighter from '../utils/MarkdownHighlighter'; -import {propTypes} from '../components/Markdown.react'; - -import DccLink from './../components/Link.react'; -import LoadingElement from '../utils/LoadingElement'; - -export default class DashMarkdown extends Component { - constructor(props) { - super(props); - - if (MarkdownHighlighter.isReady !== true) { - MarkdownHighlighter.isReady.then(() => { - this.setState({}); - }); - } - this.highlightCode = this.highlightCode.bind(this); - this.dedent = this.dedent.bind(this); - } - - componentDidMount() { - this.highlightCode(); - } - - componentDidUpdate() { - this.highlightCode(); - } - - highlightCode() { - const isHighlighted = node => { - // a highlighted node will have children which are what color the text - return node.children.length > 0; - }; - - if (this.mdContainer) { - const nodes = this.mdContainer.querySelectorAll('pre code'); - - if (MarkdownHighlighter.hljs) { - for (let i = 0; i < nodes.length; i++) { - if (!isHighlighted(nodes[i])) { - nodes[i].removeAttribute('data-highlighted'); - MarkdownHighlighter.hljs.highlightElement(nodes[i]); - } - } - } else { - MarkdownHighlighter.loadhljs(); - } - } - } - - dedent(text) { - const lines = text.split(/\r\n|\r|\n/); - let commonPrefix = null; - for (const line of lines) { - const preMatch = line && line.match(/^\s*(?=\S)/); - if (preMatch) { - const prefix = preMatch[0]; - if (commonPrefix !== null) { - for (let i = 0; i < commonPrefix.length; i++) { - // Like Python's textwrap.dedent, we'll remove both - // space and tab characters, but only if they match - if (prefix[i] !== commonPrefix[i]) { - commonPrefix = commonPrefix.substr(0, i); - break; - } - } - } else { - commonPrefix = prefix; - } - - if (!commonPrefix) { - break; - } - } - } - - const commonLen = commonPrefix ? commonPrefix.length : 0; - return lines - .map(line => { - return line.match(/\S/) ? line.substr(commonLen) : ''; - }) - .join('\n'); - } - - render() { - const { - id, - style, - className, - highlight_config, - dangerously_allow_html, - link_target, - mathjax, - children, - dedent, - } = this.props; - - const textProp = - type(children) === 'Array' ? children.join('\n') : children; - const displayText = - dedent && textProp ? this.dedent(textProp) : textProp; - - const componentTransforms = { - dccLink: props => , - dccMarkdown: props => ( - - ), - dashMathjax: props => ( - - ), - }; - - const regexMath = value => { - const newValue = value.replace( - /(\${1,2})((?:\\.|[^$])+)\1/g, - function (m, tag, src) { - const inline = tag.length === 1 || src.indexOf('\n') === -1; - return ``; - } - ); - return newValue; - }; - - return ( - { - this.mdContainer = node; - }} - style={style} - className={ - ((highlight_config && highlight_config.theme) || - className) && - `${className ? className : ''} ${ - highlight_config && - highlight_config.theme && - highlight_config.theme === 'dark' - ? 'hljs-dark' - : '' - }` - } - > - ( - - ), - - inlineMath: props => ( - - ), - - html: props => - props.escapeHtml ? ( - props.value - ) : ( - - ), - }} - /> - - ); - } -} - -DashMarkdown.propTypes = propTypes; diff --git a/components/dash-core-components/src/fragments/Markdown.tsx b/components/dash-core-components/src/fragments/Markdown.tsx new file mode 100644 index 0000000000..3aa4305531 --- /dev/null +++ b/components/dash-core-components/src/fragments/Markdown.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import ReactMarkdown from 'react-markdown'; +import type {PluggableList} from 'unified'; + +import LoadingElement from '../utils/_LoadingElement'; +import {MarkdownContentProps, MarkdownProps} from '../types'; +import {components} from '../utils/markdown/components'; +import {MarkdownContext} from '../utils/markdown/context'; +import { + rawHtmlPlugins, + rehypeMathPlugins, + remarkMathPlugins, +} from '../utils/markdown/plugins'; +import {dedentText} from '../utils/markdown/text'; + +const MarkdownContent = React.memo(function MarkdownContent({ + children, + dedent, + dangerously_allow_html, + mathjax, + link_target, +}: MarkdownContentProps & {children: string}) { + const displayText = dedent && children ? dedentText(children) : children; + + const remarkPlugins: PluggableList = []; + const rehypePlugins: PluggableList = []; + if (mathjax) { + remarkPlugins.push(...remarkMathPlugins); + rehypePlugins.push(...rehypeMathPlugins); + } + if (dangerously_allow_html) { + rehypePlugins.push(...rawHtmlPlugins); + } + + return ( + + {displayText} + + ); +}); + +function MarkdownContainer({ + id, + style, + className, + highlight_config, + dangerously_allow_html, + link_target, + mathjax, + children, + dedent, +}: MarkdownProps) { + const textProp = Array.isArray(children) ? children.join('\n') : children; + + const classNames = [ + className, + highlight_config?.theme === 'dark' ? 'hljs-dark' : '', + ].filter(Boolean); + + // provides a recursive method to render nested markdown elements + const renderMarkdown = (text: string) => ( + + {text} + + ); + + return ( + + {loadingProps => ( +
+ + {renderMarkdown(textProp ?? '')} + +
+ )} +
+ ); +} + +export default MarkdownContainer; diff --git a/components/dash-core-components/src/fragments/Math.react.js b/components/dash-core-components/src/fragments/Math.react.js deleted file mode 100644 index 989cb78e12..0000000000 --- a/components/dash-core-components/src/fragments/Math.react.js +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {Component} from 'react'; - -import lazyLoadMathJax from '../utils/LazyLoader/mathjax'; - -export default class DashMath extends Component { - constructor(props) { - super(props); - this.span_element = React.createRef(); - } - - componentDidMount() { - this.renderMath(); - } - - componentDidUpdate(prevProps) { - if ( - prevProps.tex !== this.props.tex || - prevProps.inline !== this.props.inline - ) { - this.renderMath(); - } - } - - renderMath() { - const current = this.span_element.current; - lazyLoadMathJax().then(function () { - window.MathJax.typeset([current]); - }); - } - - render() { - return ( - - {this.props.inline ? '\\(' : '\\['} - {this.props.tex} - {this.props.inline ? '\\)' : '\\]'} - - ); - } -} - -DashMath.propTypes = { - tex: PropTypes.string, - inline: PropTypes.bool, -}; - -DashMath.defaultProps = { - tex: '', - inline: true, -}; - -export const propTypes = DashMath.propTypes; -export const defaultProps = DashMath.defaultProps; diff --git a/components/dash-core-components/src/index.ts b/components/dash-core-components/src/index.ts index a2555149d4..f759a412df 100644 --- a/components/dash-core-components/src/index.ts +++ b/components/dash-core-components/src/index.ts @@ -12,10 +12,10 @@ import Geolocation from './components/Geolocation.react'; import Graph from './components/Graph.react'; import Input from './components/Input'; import Interval from './components/Interval.react'; -import Link from './components/Link.react'; +import Link from './components/Link'; import Loading from './components/Loading'; import Location from './components/Location.react'; -import Markdown from './components/Markdown.react'; +import Markdown from './components/Markdown'; import RadioItems from './components/RadioItems'; import RangeSlider from './components/RangeSlider'; import Slider from './components/Slider'; diff --git a/components/dash-core-components/src/types.ts b/components/dash-core-components/src/types.ts index f4bc430141..da6d6def97 100644 --- a/components/dash-core-components/src/types.ts +++ b/components/dash-core-components/src/types.ts @@ -1492,3 +1492,130 @@ export interface DatePickerRangeProps setProps: (props: Partial) => void; } + +export interface LinkProps { + /** + * The children of this component + */ + children?: React.ReactNode; + + /** + * The URL of a linked resource. + */ + href: string; + + /** + * Specifies where to open the link reference. + */ + target?: string; + + /** + * Controls whether or not the page will refresh when the link is clicked + */ + refresh?: boolean; + + /** + * Adds the title attribute to your link, which can contain supplementary + * information. + */ + title?: string; + + /** + * Often used with CSS to style elements with common properties. + */ + className?: string; + + /** + * Defines CSS styles which will override styles previously set. + */ + style?: React.CSSProperties; + + /** + * The ID of this component, used to identify dash components + * in callbacks. The ID needs to be unique across all of the + * components in an app. + */ + id?: string; + + /** + * Object that holds the loading state object coming from dash-renderer + */ + loading_state?: { + /** + * Determines if the component is loading or not + */ + is_loading?: boolean; + /** + * Holds which property is loading + */ + prop_name?: string; + /** + * Holds the name of the component that is loading + */ + component_name?: string; + }; +} + +export interface MarkdownContentProps { + /** + * If true, loads mathjax v3 (tex-svg) into the page and use it in the markdown + */ + mathjax?: boolean; + + /** + * A boolean to control raw HTML escaping. + * Setting HTML from code is risky because it's easy to + * inadvertently expose your users to a cross-site scripting (XSS) + * (https://en.wikipedia.org/wiki/Cross-site_scripting) attack. + */ + dangerously_allow_html?: boolean; + + /** + * A string for the target attribute to use on links (such as "_blank") + */ + link_target?: string; + + /** + * Remove matching leading whitespace from all lines. + * Lines that are empty, or contain *only* whitespace, are ignored. + * Both spaces and tab characters are removed, but only if they match; + * we will not convert tabs to spaces or vice versa. + */ + dedent?: boolean; +} + +// Markdown is a display-only component: it has no persistence and does not +// call `setProps`, so it deliberately does not extend `BaseDccProps`. +export interface MarkdownProps extends MarkdownContentProps { + /** + * The ID of this component, used to identify dash components + * in callbacks. The ID needs to be unique across all of the + * components in an app. + */ + id?: string; + + /** + * Class name of the container element + */ + className?: string; + + /** + * A markdown string (or array of strings) that adheres to the CommonMark spec + */ + children?: string | string[]; + + /** + * Config options for syntax highlighting. + */ + highlight_config?: { + /** + * Color scheme; default 'light' + */ + theme?: 'dark' | 'light'; + }; + + /** + * User-defined inline styles for the rendered Markdown + */ + style?: React.CSSProperties; +} diff --git a/components/dash-core-components/src/utils/LazyLoader/hljs.js b/components/dash-core-components/src/utils/LazyLoader/hljs.js deleted file mode 100644 index d7371d4bab..0000000000 --- a/components/dash-core-components/src/utils/LazyLoader/hljs.js +++ /dev/null @@ -1,4 +0,0 @@ -export default () => Promise.resolve(window.hljs || - import(/* webpackChunkName: "highlight" */ '../../third-party/highlight.js').then( - result => result.default - )); diff --git a/components/dash-core-components/src/utils/LazyLoader/markdown.js b/components/dash-core-components/src/utils/LazyLoader/markdown.js deleted file mode 100644 index 45b0456ed1..0000000000 --- a/components/dash-core-components/src/utils/LazyLoader/markdown.js +++ /dev/null @@ -1,2 +0,0 @@ -export default () => import(/* webpackChunkName: "markdown" */ '../../fragments/Markdown.react'); - diff --git a/components/dash-core-components/src/utils/LazyLoader/markdown.ts b/components/dash-core-components/src/utils/LazyLoader/markdown.ts new file mode 100644 index 0000000000..e8d95fc5a9 --- /dev/null +++ b/components/dash-core-components/src/utils/LazyLoader/markdown.ts @@ -0,0 +1,2 @@ +export default () => + import(/* webpackChunkName: "markdown" */ '../../fragments/Markdown'); diff --git a/components/dash-core-components/src/utils/LazyLoader/mathjax.js b/components/dash-core-components/src/utils/LazyLoader/mathjax.js deleted file mode 100644 index f0160df51a..0000000000 --- a/components/dash-core-components/src/utils/LazyLoader/mathjax.js +++ /dev/null @@ -1,5 +0,0 @@ -export default (mathjax) => Promise.resolve(window.MathJax || ( - mathjax === false ? - undefined : - import(/* webpackChunkName: "mathjax" */ '../mathjax').then(() => window.MathJax) -)); diff --git a/components/dash-core-components/src/third-party/highlight.js b/components/dash-core-components/src/utils/LazyLoader/third-party/highlight.ts similarity index 100% rename from components/dash-core-components/src/third-party/highlight.js rename to components/dash-core-components/src/utils/LazyLoader/third-party/highlight.ts diff --git a/components/dash-core-components/src/utils/LazyLoader/third-party/hljs.ts b/components/dash-core-components/src/utils/LazyLoader/third-party/hljs.ts new file mode 100644 index 0000000000..1232da6113 --- /dev/null +++ b/components/dash-core-components/src/utils/LazyLoader/third-party/hljs.ts @@ -0,0 +1,17 @@ +import type {HLJSApi} from 'highlight.js'; + +declare global { + interface Window { + hljs?: HLJSApi; + } +} + +// Reuse a highlight.js instance already on `window` (e.g. supplied by the host +// page), otherwise lazily load the bundled third-party build. +export default (): Promise => + Promise.resolve( + window.hljs ?? + import(/* webpackChunkName: "highlight" */ './highlight').then( + mod => mod.default as HLJSApi + ) + ); diff --git a/components/dash-core-components/src/utils/LazyLoader/third-party/mathjax.ts b/components/dash-core-components/src/utils/LazyLoader/third-party/mathjax.ts new file mode 100644 index 0000000000..a939b85330 --- /dev/null +++ b/components/dash-core-components/src/utils/LazyLoader/third-party/mathjax.ts @@ -0,0 +1,20 @@ +interface MathJaxObject { + typeset: (elements?: (HTMLElement | null)[]) => void; + config: {startup: {typeset: boolean}}; +} + +declare global { + interface Window { + MathJax?: MathJaxObject; + } +} + +export default (mathjax?: boolean): Promise => + Promise.resolve( + window.MathJax || + (mathjax === false + ? undefined + : import(/* webpackChunkName: "mathjax" */ './tex-svg').then( + () => window.MathJax + )) + ); diff --git a/components/dash-core-components/src/utils/LazyLoader/third-party/tex-svg.ts b/components/dash-core-components/src/utils/LazyLoader/third-party/tex-svg.ts new file mode 100644 index 0000000000..5f6f39a02a --- /dev/null +++ b/components/dash-core-components/src/utils/LazyLoader/third-party/tex-svg.ts @@ -0,0 +1,5 @@ +import 'mathjax/es5/tex-svg'; + +if (window.MathJax) { + window.MathJax.config.startup.typeset = false; +} diff --git a/components/dash-core-components/src/utils/MarkdownHighlighter.js b/components/dash-core-components/src/utils/MarkdownHighlighter.js deleted file mode 100644 index 8185a2c3ff..0000000000 --- a/components/dash-core-components/src/utils/MarkdownHighlighter.js +++ /dev/null @@ -1,19 +0,0 @@ -import lazyhljs from './LazyLoader/hljs'; - -const MarkdownHighlighter = { - loadhljs: function () { - return lazyhljs().then(hljs => { - this.hljs = hljs; - this.hljsResolve(); - this.isReady = true; - }); - }, -}; - -const isReady = new Promise(resolve => { - MarkdownHighlighter.hljsResolve = resolve; -}); - -MarkdownHighlighter.isReady = isReady; - -export default MarkdownHighlighter; diff --git a/components/dash-core-components/src/utils/markdown/components/Code.tsx b/components/dash-core-components/src/utils/markdown/components/Code.tsx new file mode 100644 index 0000000000..484ca3123b --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/components/Code.tsx @@ -0,0 +1,46 @@ +import React, {useEffect, useRef} from 'react'; + +import lazyLoadHljs from '../../LazyLoader/third-party/hljs'; +import {reactNodeToText} from '../text'; +import type {ReactMarkdownCodeProps} from './types'; + +/* + * highlight.js mutates the DOM in place (replacing the code text with coloured + * s), which conflicts with React's ownership of that subtree and left + * updated code blocks stale under react-markdown v8. This component sidesteps + * the conflict: React only owns the empty element, while the source text + * and highlighting are applied imperatively and re-run whenever the code or the + * language changes - so content updates always re-highlight correctly. + */ +function HighlightedCode({className, children}: ReactMarkdownCodeProps) { + const ref = useRef(null); + const code = reactNodeToText(children); + + useEffect(() => { + const node = ref.current; + if (!node) { + return; + } + // Reset to the raw source first so any stale highlight spans from a + // previous render are discarded before re-highlighting. + node.textContent = code; + lazyLoadHljs().then(hljs => { + node.removeAttribute('data-highlighted'); + hljs.highlightElement(node); + }); + }, [code, className]); + + return ; +} + +export default function CodeRenderer({ + inline, + className, + children, +}: ReactMarkdownCodeProps) { + return inline ? ( + {children} + ) : ( + {children} + ); +} diff --git a/components/dash-core-components/src/utils/markdown/components/DccLink.tsx b/components/dash-core-components/src/utils/markdown/components/DccLink.tsx new file mode 100644 index 0000000000..19b304b473 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/components/DccLink.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import type {Properties} from 'hast'; + +import DccLink from '../../../components/Link'; +import type {ReactMarkdownGenericProps} from './types'; + +export default function DccLinkRenderer({ + node, + children, +}: ReactMarkdownGenericProps) { + const properties: Properties = node?.properties ?? {}; + const content = + node && node.children.length + ? children + : (properties.children as React.ReactNode); + return ( + + {content} + + ); +} diff --git a/components/dash-core-components/src/utils/markdown/components/DccMarkdown.tsx b/components/dash-core-components/src/utils/markdown/components/DccMarkdown.tsx new file mode 100644 index 0000000000..573021b9fe --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/components/DccMarkdown.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import {useMarkdownContext} from '../context'; +import {collectText} from '../text'; +import type {ReactMarkdownGenericProps} from './types'; + +export default function DccMarkdownRenderer({node}: ReactMarkdownGenericProps) { + const {renderMarkdown} = useMarkdownContext(); + const content = String( + node?.properties?.children ?? collectText(node?.children) + ); + return <>{renderMarkdown(content)}; +} diff --git a/components/dash-core-components/src/utils/markdown/components/MathJax.tsx b/components/dash-core-components/src/utils/markdown/components/MathJax.tsx new file mode 100644 index 0000000000..039bfcdcb5 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/components/MathJax.tsx @@ -0,0 +1,26 @@ +import React, {useEffect, useRef} from 'react'; + +import lazyLoadMathJax from '../../LazyLoader/third-party/mathjax'; +import {collectText} from '../text'; +import type {ReactMarkdownGenericProps} from './types'; + +export default function MathJaxRenderer({node}: ReactMarkdownGenericProps) { + const tex = collectText(node?.children); + const inline = node?.properties?.inline !== 'false'; + const spanRef = useRef(null); + + useEffect(() => { + const current = spanRef.current; + lazyLoadMathJax().then(mathjax => { + mathjax?.typeset([current]); + }); + }, [tex, inline]); + + return ( + + {inline ? '\\(' : '\\['} + {tex} + {inline ? '\\)' : '\\]'} + + ); +} diff --git a/components/dash-core-components/src/utils/markdown/components/index.ts b/components/dash-core-components/src/utils/markdown/components/index.ts new file mode 100644 index 0000000000..ea119a3007 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/components/index.ts @@ -0,0 +1,13 @@ +import type {Components} from 'react-markdown'; + +import CodeRenderer from './Code'; +import DccLinkRenderer from './DccLink'; +import DccMarkdownRenderer from './DccMarkdown'; +import MathJaxRenderer from './MathJax'; + +export const components: Components = { + dcclink: DccLinkRenderer, + dccmarkdown: DccMarkdownRenderer, + dashmathjax: MathJaxRenderer, + code: CodeRenderer, +}; diff --git a/components/dash-core-components/src/utils/markdown/components/jsx-renderers.d.ts b/components/dash-core-components/src/utils/markdown/components/jsx-renderers.d.ts new file mode 100644 index 0000000000..6818f95b1b --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/components/jsx-renderers.d.ts @@ -0,0 +1,14 @@ +// Custom HTML tags need to be added to a global namespace for type safety +// Registering them here is the approach react-markdown's authors endorse +// (https://github.com/remarkjs/react-markdown/issues/622) +import 'react'; + +declare global { + namespace JSX { + interface IntrinsicElements { + dcclink: React.HTMLAttributes; + dccmarkdown: React.HTMLAttributes; + dashmathjax: React.HTMLAttributes; + } + } +} diff --git a/components/dash-core-components/src/utils/markdown/components/types.ts b/components/dash-core-components/src/utils/markdown/components/types.ts new file mode 100644 index 0000000000..c6ee044f17 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/components/types.ts @@ -0,0 +1,16 @@ +import type {ReactNode} from 'react'; +import type {Element} from 'hast'; + +// react-markdown only publicly exports `Components`/`Options`, not the per-tag +// prop types it hands each renderer. Rather than deep-import its internals, we +// re-declare (loosened) the shapes here. For the props each tag receives, see +// https://github.com/remarkjs/react-markdown/tree/8.0.7#appendix-b-components +export type ReactMarkdownGenericProps = { + node?: Element; + children?: ReactNode; +}; + +export type ReactMarkdownCodeProps = ReactMarkdownGenericProps & { + inline?: boolean; + className?: string; +}; diff --git a/components/dash-core-components/src/utils/markdown/context.ts b/components/dash-core-components/src/utils/markdown/context.ts new file mode 100644 index 0000000000..7190d50e98 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/context.ts @@ -0,0 +1,18 @@ +import {createContext, useContext} from 'react'; +import type {ReactNode} from 'react'; + +export type MarkdownContextValue = { + renderMarkdown: (text: string) => ReactNode; +}; + +export const MarkdownContext = createContext( + undefined +); + +export function useMarkdownContext(): MarkdownContextValue { + const value = useContext(MarkdownContext); + if (!value) { + throw new Error('MarkdownContext used outside of its provider'); + } + return value; +} diff --git a/components/dash-core-components/src/utils/markdown/plugins/index.ts b/components/dash-core-components/src/utils/markdown/plugins/index.ts new file mode 100644 index 0000000000..1cda2af881 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/plugins/index.ts @@ -0,0 +1,23 @@ +import type {PluggableList} from 'unified'; +import remarkMath from 'remark-math'; +import rehypeRaw from 'rehype-raw'; + +import rehypeDashMath from './rehypeDashMath'; +import rehypeDccComponents from './rehypeDccComponents'; +import rehypeStripTags from './rehypeStripTags'; + +// remark plugin for the `mathjax` prop: remark-math parses `$...$`/`$$...$$` in +// Markdown at the token level, so it leaves `\$` and `$` as literal dollars. +export const remarkMathPlugins: PluggableList = [remarkMath]; + +// rehype plugin for the `mathjax` prop: routes both Markdown and raw-HTML math +// into . Must run before rehype-raw. +export const rehypeMathPlugins: PluggableList = [rehypeDashMath]; + +// rehype pipeline that powers raw-HTML support for `dangerously_allow_html`. +// Order matters: rehype-raw must come first. +export const rawHtmlPlugins: PluggableList = [ + rehypeRaw, + rehypeStripTags, + rehypeDccComponents, +]; diff --git a/components/dash-core-components/src/utils/markdown/plugins/rehypeDashMath.ts b/components/dash-core-components/src/utils/markdown/plugins/rehypeDashMath.ts new file mode 100644 index 0000000000..d2e3b97b36 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/plugins/rehypeDashMath.ts @@ -0,0 +1,69 @@ +import type {Plugin} from 'unified'; +import type {Element, Root} from 'hast'; +import {visit} from 'unist-util-visit'; + +// Inline `$...$` or display `$$...$$` math. +const MATH_RE = /(\${1,2})((?:\\.|[^$])+)\1/g; + +function escapeHtml(value: string): string { + return value + .replace(/&/g, '&') + .replace(//g, '>'); +} + +// Rewrite `$...$` runs inside a raw-HTML string into `` tags. This +// runs on the raw string *before* rehype-raw decodes entities, so an escaped +// `$` is left alone and renders as a literal `$`. +function rewriteRawMath(value: string): string { + return value.replace(MATH_RE, (_full, delimiter, source) => { + const inline = delimiter.length === 1 || source.indexOf('\n') === -1; + return `${escapeHtml( + source + )}`; + }); +} + +// remark-math emits `` / `
`; report which (if either) an element is. +function mathKind(node: Element): 'inline' | 'display' | null { + const className = node.properties?.className; + if (Array.isArray(className)) { + if (className.includes('math-inline')) { + return 'inline'; + } + if (className.includes('math-display')) { + return 'display'; + } + } + return null; +} + +/** + * Routes both math sources into the Math component as `` elements: + * - Markdown `$...$` parsed by remark-math (as `.math-inline`/`.math-display`). + * - raw-HTML `$...$`, rewritten in place before rehype-raw runs. + * + * in markdown: Euler: $e=mc^2$ + * transformed: Euler: e=mc^2 + */ +const rehypeDashMath: Plugin<[], Root> = () => tree => { + visit(tree, node => { + if (node.type === 'raw') { + const raw = node as unknown as {value: string}; + raw.value = rewriteRawMath(raw.value); + return; + } + if (node.type === 'element') { + const kind = mathKind(node); + if (kind) { + node.tagName = 'dashmathjax'; + node.properties = { + inline: kind === 'inline' ? 'true' : 'false', + }; + } + } + }); +}; + +export default rehypeDashMath; diff --git a/components/dash-core-components/src/utils/markdown/plugins/rehypeDccComponents.ts b/components/dash-core-components/src/utils/markdown/plugins/rehypeDccComponents.ts new file mode 100644 index 0000000000..b4aa4f3d54 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/plugins/rehypeDccComponents.ts @@ -0,0 +1,33 @@ +import type {Plugin} from 'unified'; +import type {Root} from 'hast'; +import {visit} from 'unist-util-visit'; + +const DCC_ALLOWED_TAGS_LOWERCASE = ['dcclink', 'dccmarkdown']; + +/** + * Renders supported dash components according to the allowlist. + * Slightly obscure because HTML on its own ignores the self-closing `/>` form + * of custom tags, so we correct for that here: + * + * in markdown: Click for info + * received here (wrong): Click for info + * corrected here: Click for info + */ +const rehypeDccComponents: Plugin<[], Root> = () => tree => { + visit(tree, 'element', (node, index, parent) => { + if (!parent || typeof index !== 'number') { + return; + } + + const isDccTag = DCC_ALLOWED_TAGS_LOWERCASE.includes(node.tagName); + const hasChildrenAttribute = node.properties?.children !== undefined; + if (!isDccTag || !hasChildrenAttribute) { + return; + } + + parent.children.splice(index + 1, 0, ...node.children); + node.children = []; + }); +}; + +export default rehypeDccComponents; diff --git a/components/dash-core-components/src/utils/markdown/plugins/rehypeStripTags.ts b/components/dash-core-components/src/utils/markdown/plugins/rehypeStripTags.ts new file mode 100644 index 0000000000..13ace40fc5 --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/plugins/rehypeStripTags.ts @@ -0,0 +1,23 @@ +import type {Plugin} from 'unified'; +import type {Root} from 'hast'; +import {visit, SKIP} from 'unist-util-visit'; + +// Tags removed from raw HTML even when `dangerously_allow_html` is True. +// New additions here may require a major version bump +const STRIPPED_TAGS = ['script', 'style']; + +const rehypeStripTags: Plugin<[], Root> = () => tree => { + visit(tree, 'element', (node, index, parent) => { + if ( + STRIPPED_TAGS.includes(node.tagName) && + parent && + typeof index === 'number' + ) { + parent.children.splice(index, 1); + return [SKIP, index]; + } + return undefined; + }); +}; + +export default rehypeStripTags; diff --git a/components/dash-core-components/src/utils/markdown/text.ts b/components/dash-core-components/src/utils/markdown/text.ts new file mode 100644 index 0000000000..4d47cad3ae --- /dev/null +++ b/components/dash-core-components/src/utils/markdown/text.ts @@ -0,0 +1,64 @@ +import type {ReactNode} from 'react'; +import type {Content} from 'hast'; + +// Concatenate the text content of hast nodes, descending into element children. +export function collectText(nodes: Content[] | undefined): string { + if (!nodes) { + return ''; + } + return nodes + .map(node => { + if (node.type === 'text') { + return node.value; + } + if (node.type === 'element') { + return collectText(node.children); + } + return ''; + }) + .join(''); +} + +export function reactNodeToText(node: ReactNode): string { + if (node === null || node === undefined || typeof node === 'boolean') { + return ''; + } + if (typeof node === 'string' || typeof node === 'number') { + return String(node); + } + if (Array.isArray(node)) { + return node.map(reactNodeToText).join(''); + } + const element = node as {props?: {children?: ReactNode}}; + return element.props ? reactNodeToText(element.props.children) : ''; +} + +export function dedentText(text: string): string { + const lines = text.split(/\r\n|\r|\n/); + let commonPrefix: string | null = null; + for (const line of lines) { + const preMatch = line && line.match(/^\s*(?=\S)/); + if (preMatch) { + const prefix = preMatch[0]; + if (commonPrefix !== null) { + for (let i = 0; i < commonPrefix.length; i++) { + // Like Python's textwrap.dedent, we'll remove both space + // and tab characters, but only if they match + if (prefix[i] !== commonPrefix[i]) { + commonPrefix = commonPrefix.substr(0, i); + break; + } + } + } else { + commonPrefix = prefix; + } + if (!commonPrefix) { + break; + } + } + } + const commonLen = commonPrefix ? commonPrefix.length : 0; + return lines + .map(line => (line.match(/\S/) ? line.substr(commonLen) : '')) + .join('\n'); +} diff --git a/components/dash-core-components/src/utils/mathjax.js b/components/dash-core-components/src/utils/mathjax.js deleted file mode 100644 index 347a34513c..0000000000 --- a/components/dash-core-components/src/utils/mathjax.js +++ /dev/null @@ -1,3 +0,0 @@ -import 'mathjax/es5/tex-svg'; - -window.MathJax.config.startup.typeset = false; diff --git a/components/dash-core-components/tests/integration/link/test_link_props.py b/components/dash-core-components/tests/integration/link/test_link_props.py new file mode 100644 index 0000000000..aac9049ac6 --- /dev/null +++ b/components/dash-core-components/tests/integration/link/test_link_props.py @@ -0,0 +1,68 @@ +from multiprocessing import Lock + +from dash import Dash, Input, Output, dcc, html + + +def test_lipr001_target(dash_dcc): + # The `target` attribute is rendered on the anchor; links with a target + # (other than _self) opt out of client-side navigation. + app = Dash(__name__) + app.layout = html.Div( + [ + dcc.Link("external", id="link1", href="/page-1", target="_blank"), + ] + ) + dash_dcc.start_server(app) + + link = dash_dcc.wait_for_element("#link1") + assert link.get_attribute("target") == "_blank" + + assert dash_dcc.get_logs() == [] + + +def test_lipr002_sanitizes_dangerous_href(dash_dcc): + # A dangerous href is passed through clean_url, which rewrites disallowed + # protocols to about:blank so the link can't execute script. + app = Dash(__name__) + app.layout = html.Div( + [ + dcc.Link("click me", id="link1", href="javascript:alert(1)"), + ] + ) + dash_dcc.start_server(app) + + link = dash_dcc.wait_for_element("#link1") + assert link.get_attribute("href") == "about:blank" + + +def test_lipr003_loading_state(dash_dcc): + # While a callback targeting the Link is in flight, the anchor carries + # `data-dash-is-loading` (applied via LoadingElement). + lock = Lock() + + app = Dash(__name__) + app.layout = html.Div( + [ + html.Button(id="btn"), + dcc.Link("Page 1", id="link1", href="/page-1"), + ] + ) + + @app.callback(Output("link1", "children"), Input("btn", "n_clicks")) + def update_children(n_clicks): + with lock: + return "Page 1" + + with lock: + dash_dcc.start_server(app) + dash_dcc.wait_for_element('#link1[data-dash-is-loading="true"]') + + dash_dcc.wait_for_element('#link1:not([data-dash-is-loading="true"])') + + with lock: + dash_dcc.wait_for_element("#btn").click() + dash_dcc.wait_for_element('#link1[data-dash-is-loading="true"]') + + dash_dcc.wait_for_element('#link1:not([data-dash-is-loading="true"])') + + assert dash_dcc.get_logs() == [] diff --git a/components/dash-core-components/tests/integration/markdown/test_markdown.py b/components/dash-core-components/tests/integration/markdown/test_markdown.py index 786347cd5f..9c3f4e33e6 100644 --- a/components/dash-core-components/tests/integration/markdown/test_markdown.py +++ b/components/dash-core-components/tests/integration/markdown/test_markdown.py @@ -3,6 +3,19 @@ from dash.testing.wait import until +GRAVITY = "$F=\\frac{Gm_1m_2}{r^2}$" + +BLOCK_MATH = """ + ## h2 tag with MathJax block: + $$ + \\frac{1}{(\\sqrt{\\phi \\sqrt{5}}-\\phi) e^{\\frac25 \\pi}} = + 1+\\frac{e^{-2\\pi}} {1+\\frac{e^{-4\\pi}} {1+\\frac{e^{-6\\pi}} + {1+\\frac{e^{-8\\pi}} {1+\\ldots} } } } + $$ + ## Next line. +""" + + def test_mkdw001_img(dash_dcc): app = Dash(__name__, eager_loading=True, assets_folder="../../assets") @@ -10,14 +23,24 @@ def test_mkdw001_img(dash_dcc): [ html.Div("Markdown img"), dcc.Markdown( - [''], dangerously_allow_html=True + [''], + dangerously_allow_html=True, + id="img_html", ), html.Div("Markdown img - requires dangerously_allow_html"), - dcc.Markdown(['']), + dcc.Markdown([''], id="img_no_html"), ] ) dash_dcc.start_server(app) + + # With dangerously_allow_html the raw renders as a real image. + img = dash_dcc.wait_for_element("#img_html img") + assert img.get_attribute("src").endswith("assets/image.png") + + # Without it, the raw HTML is inert - no element is created. + dash_dcc.wait_for_no_elements("#img_no_html img") + dash_dcc.percy_snapshot("mkdw001 - image display") assert dash_dcc.get_logs() == [] @@ -29,11 +52,12 @@ def test_mkdw002_dcclink(dash_dcc): app.layout = html.Div( [ html.Div(["Markdown link"]), - dcc.Markdown(["[Title](title_crumb)"]), + dcc.Markdown(["[Title](title_crumb)"], id="md_link"), html.Div(["Markdown dccLink"]), dcc.Markdown( [''], dangerously_allow_html=True, + id="dcclink_attr", ), html.Div(["Markdown dccLink - explicit children"]), dcc.Markdown( @@ -45,6 +69,7 @@ def test_mkdw002_dcclink(dash_dcc): """ ], dangerously_allow_html=True, + id="dcclink_explicit", ), html.Div("Markdown dccLink = inlined"), dcc.Markdown( @@ -52,6 +77,7 @@ def test_mkdw002_dcclink(dash_dcc): 'This is an inlined with text on both sides' ], dangerously_allow_html=True, + id="dcclink_inline", ), html.Div("Markdown dccLink - nested image"), dcc.Markdown( @@ -63,6 +89,7 @@ def test_mkdw002_dcclink(dash_dcc): """ ], dangerously_allow_html=True, + id="dcclink_nested_img", ), html.Div("Markdown dccLink - nested markdown"), dcc.Markdown( @@ -74,6 +101,7 @@ def test_mkdw002_dcclink(dash_dcc): """ ], dangerously_allow_html=True, + id="dcclink_nested_md", ), html.Div("Markdown dccLink - nested markdown image"), dcc.Markdown( @@ -85,162 +113,156 @@ def test_mkdw002_dcclink(dash_dcc): """ ], dangerously_allow_html=True, + id="dcclink_nested_md_img", ), html.Div("Markdown dccLink - requires dangerously_allow_html"), - dcc.Markdown(['']), + dcc.Markdown( + [''], + id="dcclink_no_html", + ), ] ) dash_dcc.start_server(app) - assert dash_dcc.get_logs() == [] - -@pytest.mark.parametrize("is_eager", [True, False]) -def test_mkdw003_without_mathjax(dash_dcc, is_eager): - app = Dash(__name__, eager_loading=is_eager) + # Baseline: a plain Markdown link renders an anchor with the link text. + dash_dcc.wait_for_text_to_equal("#md_link a", "Title") + + # The regression from https://github.com/plotly/dash/issues/3951: + # a self-closing dccLink whose text is supplied via the `children` + # attribute must render "Title", NOT the href. When react-jsx-parser + # clobbered the attribute, dcc.Link fell back to rendering the href. + dash_dcc.wait_for_text_to_equal("#dcclink_attr a", "Title") + assert ( + dash_dcc.find_element("#dcclink_attr a") + .get_attribute("href") + .endswith("title_crumb") + ) - app.layout = html.Div( - [ - dcc.Markdown("# No MathJax: Apple: $2, Orange: $3"), - ] + # The nested-content form renders identically. + dash_dcc.wait_for_text_to_equal("#dcclink_explicit a", "Title") + assert ( + dash_dcc.find_element("#dcclink_explicit a") + .get_attribute("href") + .endswith("title_crumb") ) - dash_dcc.start_server(app) - dash_dcc.wait_for_text_to_equal("h1", "No MathJax: Apple: $2, Orange: $3") - assert not dash_dcc.driver.execute_script("return !!window.MathJax") + # An inlined dccLink carries the children text on the anchor itself, + # with the surrounding prose on the parent element. + dash_dcc.wait_for_text_to_equal("#dcclink_inline a", "Title") + assert "with text on both sides" in dash_dcc.find_element("#dcclink_inline").text + + # A raw nested inside the link renders an , not href text. + dash_dcc.wait_for_element("#dcclink_nested_img a img") + + # A nested dccMarkdown renders its markdown (an

) inside the link. + dash_dcc.wait_for_text_to_equal("#dcclink_nested_md a h2", "Title") + + # A nested dccMarkdown image renders an inside the link. + dash_dcc.wait_for_element("#dcclink_nested_md_img a img") + + # Without dangerously_allow_html the tag is inert: no anchor is rendered. + dash_dcc.wait_for_no_elements("#dcclink_no_html a") + assert dash_dcc.get_logs() == [] -@pytest.mark.parametrize("is_eager", [True, False]) -def test_mkdw004_inline_mathjax(dash_dcc, is_eager): - app = Dash(__name__, eager_loading=is_eager, assets_folder="../../assets") +def test_mkdw003_without_mathjax(dash_dcc): + app = Dash(__name__) app.layout = html.Div( [ - dcc.Markdown("# h1 tag with inline MathJax: $E=mc^2$", mathjax=True), + dcc.Markdown("# No MathJax: Apple: $2, Orange: $3"), ] ) dash_dcc.start_server(app) - dash_dcc.wait_for_element("h1 svg") + dash_dcc.wait_for_text_to_equal("h1", "No MathJax: Apple: $2, Orange: $3") + assert not dash_dcc.driver.execute_script("return !!window.MathJax") assert dash_dcc.get_logs() == [] -@pytest.mark.parametrize("is_eager", [True, False]) -def test_mkdw005_block_mathjax(dash_dcc, is_eager): - app = Dash(__name__, eager_loading=is_eager, assets_folder="../../assets") +@pytest.mark.parametrize( + "markdown", + [ + "# h1 tag with inline MathJax: $E=mc^2$", + BLOCK_MATH, + ], + ids=["inline", "block"], +) +def test_mkdw004_mathjax_renders(dash_dcc, markdown): + # Both inline ($...$) and block ($$...$$) math render to an . + app = Dash(__name__, assets_folder="../../assets") - app.layout = html.Div( - [ - dcc.Markdown( - """ - ## h2 tag with MathJax block: - $$ - \\frac{1}{(\\sqrt{\\phi \\sqrt{5}}-\\phi) e^{\\frac25 \\pi}} = - 1+\\frac{e^{-2\\pi}} {1+\\frac{e^{-4\\pi}} {1+\\frac{e^{-6\\pi}} - {1+\\frac{e^{-8\\pi}} {1+\\ldots} } } } - $$ - ## Next line. - """, - mathjax=True, - id="md", - ), - ] - ) + app.layout = html.Div([dcc.Markdown(markdown, mathjax=True, id="md")]) dash_dcc.start_server(app) dash_dcc.wait_for_element("#md svg") assert dash_dcc.get_logs() == [] -@pytest.mark.parametrize("is_eager", [True, False]) -def test_mkdw006_toggle_mathjax(dash_dcc, is_eager): - app = Dash(__name__, eager_loading=is_eager) - - gravity = "$F=\\frac{Gm_1m_2}{r^2}$" - +def _toggle_mathjax_setup(app): + # Flip an existing Markdown's `mathjax` prop on via callback. app.layout = html.Div( [ html.Button("Toggle MathJax", id="btn"), - dcc.Markdown( - f""" - # Test MathJax Toggling {gravity} - """, - id="md", - ), + dcc.Markdown(f"# Test MathJax {GRAVITY}", id="md"), ] ) @app.callback( - Output("md", "mathjax"), Input("btn", "n_clicks"), prevent_initial_call=True + Output("md", "mathjax"), + Input("btn", "n_clicks"), + prevent_initial_call=True, ) def toggle(n): return (n or 0) % 2 != 0 - dash_dcc.start_server(app) - - # Initial state: no MathJax loaded or rendered, unformatted text is shown - dash_dcc.wait_for_contains_text("#md", gravity) - dash_dcc.wait_for_no_elements("#md svg") - assert not dash_dcc.driver.execute_script("return !!window.MathJax") - - btn = dash_dcc.find_element("#btn") - btn.click() - - # One click: MathJax is rendered, unformatted text is gone - - dash_dcc.wait_for_element("#md svg") - assert gravity not in dash_dcc._get_element("#md").text - assert dash_dcc.driver.execute_script("return !!window.MathJax") - - btn.click() - - # Second click: Back to initial state except that MathJax library is still loaded - dash_dcc.wait_for_contains_text("#md", gravity) - dash_dcc.wait_for_no_elements("#md svg") - assert dash_dcc.driver.execute_script("return !!window.MathJax") + return "#md" -@pytest.mark.parametrize("is_eager", [True, False]) -def test_mkdw007_load_mathjax(dash_dcc, is_eager): - app = Dash(__name__, eager_loading=is_eager) - - gravity = "$F=\\frac{Gm_1m_2}{r^2}$" - +def _add_mathjax_setup(app): + # Inject a brand-new mathjax=True Markdown via callback. app.layout = html.Div( [ - html.Button("Add Second MathJax", id="btn"), - dcc.Markdown( - f""" - # No Math Rendering Here! {gravity} - """, - id="md", - mathjax=False, - ), - html.Div("initial", id="out"), + html.Button("Add MathJax", id="btn"), + dcc.Markdown(f"# No Math Here! {GRAVITY}", id="md", mathjax=False), + html.Div(id="out"), ] ) @app.callback( - Output("out", "children"), Input("btn", "n_clicks"), prevent_initial_call=True + Output("out", "children"), + Input("btn", "n_clicks"), + prevent_initial_call=True, ) - def add_math(n): - return dcc.Markdown(f"# Math!\n{gravity}", id="md2", mathjax=True) + def add(n): + return dcc.Markdown(f"# Math!\n{GRAVITY}", id="md2", mathjax=True) + + return "#md2" + + +@pytest.mark.parametrize("is_eager", [True, False]) +@pytest.mark.parametrize("setup", [_toggle_mathjax_setup, _add_mathjax_setup]) +def test_mkdw006_mathjax_loads_on_demand(dash_dcc, is_eager, setup): + # MathJax isn't loaded until a mathjax=True Markdown renders - whether that + # happens by toggling the prop or by injecting a new component. + app = Dash(__name__, eager_loading=is_eager) + + rendered = setup(app) dash_dcc.start_server(app) - # Initial state: no MathJax loaded or rendered, unformatted text is shown - dash_dcc.wait_for_contains_text("#md", gravity) + # Initial state: no MathJax loaded or rendered, raw delimiters are shown. + dash_dcc.wait_for_contains_text("#md", GRAVITY) dash_dcc.wait_for_no_elements("#md svg") assert not dash_dcc.driver.execute_script("return !!window.MathJax") - btn = dash_dcc.find_element("#btn") - btn.click() + dash_dcc.find_element("#btn").click() - # One click: MathJax is loaded and rendered on the second, unformatted text is gone - - dash_dcc.wait_for_element("#md2 svg") - assert gravity not in dash_dcc._get_element("#md2").text + # After the click: MathJax is loaded and the math renders as . + dash_dcc.wait_for_element(f"{rendered} svg") + assert GRAVITY not in dash_dcc._get_element(rendered).text assert dash_dcc.driver.execute_script("return !!window.MathJax") @@ -349,18 +371,7 @@ def test_mkdw008_mathjax_visual(dash_dcc): app.layout = html.Div( children=[ dcc.Markdown("# h1 tag with inline MathJax: $E=mc^2$", mathjax=True), - dcc.Markdown( - """ - ## h2 tag with MathJax block: - $$ - \\frac{1}{(\\sqrt{\\phi \\sqrt{5}}-\\phi) e^{\\frac25 \\pi}} = - 1+\\frac{e^{-2\\pi}} {1+\\frac{e^{-4\\pi}} {1+\\frac{e^{-6\\pi}} - {1+\\frac{e^{-8\\pi}} {1+\\ldots} } } } - $$ - ## Next line. - """, - mathjax=True, - ), + dcc.Markdown(BLOCK_MATH, mathjax=True), dcc.Graph(mathjax=True, id="graph-with-math", figure=fig), dcc.Markdown("### No MathJax: Apple: $2, Orange: $3"), dcc.Graph(id="graph-without-math", figure=fig), @@ -428,3 +439,183 @@ def test_mkdw010_mathjax_with_html(dash_dcc): dash_dcc.wait_for_element(".MathJax") assert len(dash_dcc.find_elements((".MathJax"))) == len(CONTENT) + + +def test_mkdw011_dedent(dash_dcc): + app = Dash(__name__) + + # Every line shares a 4-space indent. With dedent it becomes a heading; + # without dedent the indent makes it an indented code block instead. + indented = " # Indented heading\n with body text\n" + + app.layout = html.Div( + [ + dcc.Markdown(indented, dedent=True, id="dedent_on"), + dcc.Markdown(indented, dedent=False, id="dedent_off"), + ] + ) + + dash_dcc.start_server(app) + + # dedent=True strips the common indent, so the line renders as a heading. + dash_dcc.wait_for_text_to_equal("#dedent_on h1", "Indented heading") + + # dedent=False keeps the indent, so CommonMark renders a code block and + # there is no heading. + dash_dcc.wait_for_element("#dedent_off pre code") + dash_dcc.wait_for_no_elements("#dedent_off h1") + + assert dash_dcc.get_logs() == [] + + +def test_mkdw012_highlight_config_theme(dash_dcc): + app = Dash(__name__) + + code = "```python\nprint('hello, world!')\n```" + + app.layout = html.Div( + [ + dcc.Markdown(code, highlight_config={"theme": "dark"}, id="theme_dark"), + dcc.Markdown(code, highlight_config={"theme": "light"}, id="theme_light"), + ] + ) + + dash_dcc.start_server(app) + + # The dark theme adds the `hljs-dark` class to the container; light does not. + dash_dcc.wait_for_element("#theme_dark pre code") + assert "hljs-dark" in dash_dcc.find_element("#theme_dark").get_attribute("class") + assert "hljs-dark" not in ( + dash_dcc.find_element("#theme_light").get_attribute("class") or "" + ) + + assert dash_dcc.get_logs() == [] + + +def test_mkdw013_classname_and_style(dash_dcc): + app = Dash(__name__) + + app.layout = dcc.Markdown( + "Styled container", + id="styled", + className="my-markdown", + style={"color": "rgb(1, 2, 3)"}, + ) + + dash_dcc.start_server(app) + + container = dash_dcc.wait_for_element("#styled") + assert "my-markdown" in container.get_attribute("class") + # The inline style prop is applied to the container (the browser may + # report the colour as rgb or rgba). + assert "1, 2, 3" in container.value_of_css_property("color") + + # A plain string must render as a paragraph. + dash_dcc.wait_for_text_to_equal("#styled p", "Styled container") + dash_dcc.wait_for_no_elements("#styled pre") + + assert dash_dcc.get_logs() == [] + + +def test_mkdw014_reuses_existing_mathjax(dash_dcc): + # dcc shares a single global `window.MathJax`: whichever of dcc.Graph, + # dcc.Markdown, or a user script loads it first wins, and everything else + # (including plotly.js) reuses it. This locks in that dcc.Markdown respects + # a pre-existing `window.MathJax` instead of loading/overwriting its own. + app = Dash(__name__) + + app.layout = html.Div( + [ + html.Button("Add markdown", id="btn"), + html.Div(id="out"), + ] + ) + + @app.callback( + Output("out", "children"), Input("btn", "n_clicks"), prevent_initial_call=True + ) + def add(n): + return dcc.Markdown("Inline $E=mc^2$ math", mathjax=True, id="md") + + dash_dcc.start_server(app) + + # Simulate MathJax already present on the page (as dcc.Graph, plotly, or a + # user script would leave it) before any Markdown renders. + dash_dcc.driver.execute_script( + """ + window.MathJax = { + _sentinel: 'preloaded', + startup: {}, + config: {startup: {}}, + typeset: function (els) { + (els || []).forEach(function (el) { + el.setAttribute('data-typeset', 'preloaded'); + }); + } + }; + """ + ) + + dash_dcc.find_element("#btn").click() + + # The math was typeset by the pre-existing MathJax (our sentinel)... + dash_dcc.wait_for_element("#md [data-typeset='preloaded']") + # ...and dcc did not replace the global with its own bundled copy. + assert ( + dash_dcc.driver.execute_script( + "return window.MathJax && window.MathJax._sentinel" + ) + == "preloaded" + ) + + assert dash_dcc.get_logs() == [] + + +@pytest.mark.parametrize( + "tag, markup", + [ + # s), which conflicts with React's ownership of that subtree and left - * updated code blocks stale under react-markdown v8. This component sidesteps - * the conflict: React only owns the empty element, while the source text - * and highlighting are applied imperatively and re-run whenever the code or the - * language changes - so content updates always re-highlight correctly. - */ function HighlightedCode({className, children}: ReactMarkdownCodeProps) { const ref = useRef(null); const code = reactNodeToText(children); @@ -38,9 +30,8 @@ export default function CodeRenderer({ className, children, }: ReactMarkdownCodeProps) { - return inline ? ( - {children} - ) : ( - {children} - ); + if (inline) { + return {children}; + } + return {children}; } diff --git a/components/dash-core-components/src/utils/markdown/components/types.ts b/components/dash-core-components/src/utils/markdown/components/types.ts index c6ee044f17..2c5f84f2f8 100644 --- a/components/dash-core-components/src/utils/markdown/components/types.ts +++ b/components/dash-core-components/src/utils/markdown/components/types.ts @@ -1,9 +1,8 @@ import type {ReactNode} from 'react'; import type {Element} from 'hast'; -// react-markdown only publicly exports `Components`/`Options`, not the per-tag -// prop types it hands each renderer. Rather than deep-import its internals, we -// re-declare (loosened) the shapes here. For the props each tag receives, see +// Here we declare react-markdown's internal types that it will pass to our +// components. See: // https://github.com/remarkjs/react-markdown/tree/8.0.7#appendix-b-components export type ReactMarkdownGenericProps = { node?: Element;