diff --git a/.editorconfig b/.editorconfig
index c6c8b362..0f178672 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,9 +1,9 @@
root = true
[*]
-indent_style = space
-indent_size = 2
-end_of_line = lf
charset = utf-8
-trim_trailing_whitespace = true
+end_of_line = lf
+indent_size = 2
+indent_style = space
insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 8b5bc234..00000000
--- a/.eslintignore
+++ /dev/null
@@ -1,2 +0,0 @@
-coverage/
-react-markdown.min.js
diff --git a/.github/workflows/bb.yml b/.github/workflows/bb.yml
index 0198fc3f..3dbfce5b 100644
--- a/.github/workflows/bb.yml
+++ b/.github/workflows/bb.yml
@@ -1,9 +1,3 @@
-name: bb
-on:
- issues:
- types: [opened, reopened, edited, closed, labeled, unlabeled]
- pull_request_target:
- types: [opened, reopened, edited, closed, labeled, unlabeled]
jobs:
main:
runs-on: ubuntu-latest
@@ -11,3 +5,9 @@ jobs:
- uses: unifiedjs/beep-boop-beta@main
with:
repo-token: ${{secrets.GITHUB_TOKEN}}
+name: bb
+on:
+ issues:
+ types: [closed, edited, labeled, opened, reopened, unlabeled]
+ pull_request_target:
+ types: [closed, edited, labeled, opened, reopened, unlabeled]
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index fe284ad1..ade39213 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,21 +1,21 @@
-name: main
-on:
- - pull_request
- - push
jobs:
main:
name: ${{matrix.node}}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: dcodeIO/setup-node-nvm@master
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
node-version: ${{matrix.node}}
- run: npm install
- run: npm test
- - uses: codecov/codecov-action@v1
+ - uses: codecov/codecov-action@v5
strategy:
matrix:
node:
- - lts/erbium
+ - lts/hydrogen
- node
+name: main
+on:
+ - pull_request
+ - push
diff --git a/.gitignore b/.gitignore
index fdbc06a8..fceff11a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
-coverage/
-node_modules/
*.d.ts
*.log
+*.map
+*.tsbuildinfo
.DS_Store
+coverage/
+node_modules/
react-markdown.min.js
yarn.lock
diff --git a/.npmrc b/.npmrc
index 43c97e71..3757b304 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
+ignore-scripts=true
package-lock=false
diff --git a/.prettierignore b/.prettierignore
index 95769e01..cebe81f8 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1,2 @@
coverage/
-*.html
*.md
-react-markdown.min.js
diff --git a/changelog.md b/changelog.md
index 06335805..54cd5698 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,13 +1,314 @@
-# Change Log
+
+
+# Changelog
All notable changes will be documented in this file.
+## 10.0.0 - 2025-02-20
+
+* [`aaaa40b`](https://github.com/remarkjs/react-markdown/commit/aaaa40b)
+ Remove support for `className` prop
+ **migrate**: see “Remove `className`” below
+
+### Remove `className`
+
+The `className` prop was removed.
+If you want to add classes to some element that wraps the markdown
+you can explicitly write that element and add the class to it.
+You can then choose yourself which tag name to use and whether to add other
+props.
+
+Before:
+
+```js
+
- Just a link: https://reactjs.com. + Just a link: www.nasa.gov.
``` @@ -161,75 +168,214 @@ ReactDom.render( ## API -This package exports the following identifier: -[`uriTransformer`][uri-transformer]. -The default export is `ReactMarkdown`. - -### `props` - -* `children` (`string`, default: `''`)\ - markdown to parse -* `components` (`RecordA paragraph with emphasis and strong importance. @@ -266,21 +411,21 @@ ReactDom.render( https://reactjs.org.
-A table:
| a | -b | +a | +b |
|---|
This ~is not~ strikethrough, but this is!
+
{children}
)
}
}}
- />,
- document.body
+ />
)
```
Show equivalent JSX
-```jsx
+```js
<>
Here is some JavaScript code:
@@ -386,43 +535,40 @@ ReactDom.render(
### Use remark and rehype plugins (math)
-This example shows how a syntax extension (through [`remark-math`][math])
+This example shows how a syntax extension
+(through [`remark-math`][github-remark-math])
is used to support math in markdown, and a transform plugin
-([`rehype-katex`][katex]) to render that math.
+([`rehype-katex`][github-rehype-katex]) to render that math.
-```jsx
+```js
import React from 'react'
-import ReactDom from 'react-dom'
-import ReactMarkdown from 'react-markdown'
-import remarkMath from 'remark-math'
+import {createRoot} from 'react-dom/client'
+import Markdown from 'react-markdown'
import rehypeKatex from 'rehype-katex'
-
+import remarkMath from 'remark-math'
import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
-ReactDom.render(
- ,
- document.body
+const markdown = `The lift coefficient ($C_L$) is a dimensionless coefficient.`
+
+createRoot(document.body).render(
+
+ {markdown}
+
)
```
Show equivalent JSX
-```jsx
+```js
The lift coefficient (
-
-
-
-
-
-
+
+
+
+
+
) is a dimensionless coefficient.
@@ -433,17 +579,21 @@ ReactDom.render(
## Plugins
-We use [unified][], specifically [remark][] for markdown and [rehype][] for
-HTML, which are tools to transform content with plugins.
+We use [unified][github-unified],
+specifically [remark][github-remark] for markdown and
+[rehype][github-rehype] for HTML,
+which are tools to transform content with plugins.
Here are three good ways to find plugins:
-* [`awesome-remark`][awesome-remark] and [`awesome-rehype`][awesome-rehype]
- — selection of the most awesome projects
-* [List of remark plugins][remark-plugins] and
- [list of rehype plugins][rehype-plugins]
- — list of all plugins
-* [`remark-plugin`][remark-plugin] and [`rehype-plugin`][rehype-plugin] topics
- — any tagged repo on GitHub
+* [`awesome-remark`][github-awesome-remark] and
+ [`awesome-rehype`][github-awesome-rehype]
+ — selection of the most awesome projects
+* [List of remark plugins][github-remark-plugins] and
+ [list of rehype plugins][github-rehype-plugins]
+ — list of all plugins
+* [`remark-plugin`][github-topic-remark-plugin] and
+ [`rehype-plugin`][github-topic-rehype-plugin] topics
+ — any tagged repo on GitHub
## Syntax
@@ -451,22 +601,20 @@ Here are three good ways to find plugins:
markdown implementations, by default.
Some syntax extensions are supported through plugins.
-We use [`micromark`][micromark] under the hood for our parsing.
+We use [`micromark`][github-micromark] under the hood for our parsing.
See its documentation for more information on markdown, CommonMark, and
extensions.
-## Types
-
-This package is fully typed with [TypeScript][].
-It exports `Options` and `Components` types, which specify the interface of the
-accepted props and components.
-
## Compatibility
-Projects maintained by the unified collective are compatible with all maintained
+Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
-As of now, that is Node.js 12.20+, 14.14+, and 16.0+.
-Our projects sometimes work with older versions, but this is not guaranteed.
+
+When we cut a new major release, we drop support for unmaintained versions of
+Node.
+This means we try to keep the current release line, `react-markdown@10`,
+compatible with Node.js 16.
+
They work in all modern browsers (essentially: everything not IE 11).
You can use a bundler (such as esbuild, webpack, or Rollup) to use this package
in your project, and use its options (or plugins) to add support for legacy
@@ -487,18 +635,19 @@ browsers.
To understand what this project does, it’s important to first understand what
-unified does: please read through the [`unifiedjs/unified`][unified] readme (the
-part until you hit the API section is required reading).
+unified does: please read through the [`unifiedjs/unified`][github-unified]
+readme
+(the part until you hit the API section is required reading).
`react-markdown` is a unified pipeline — wrapped so that most folks don’t need
to directly interact with unified.
The processor goes through these steps:
-* parse markdown to mdast (markdown syntax tree)
-* transform through remark (markdown ecosystem)
-* transform mdast to hast (HTML syntax tree)
-* transform through rehype (HTML ecosystem)
-* render hast to React with components
+* parse markdown to mdast (markdown syntax tree)
+* transform through remark (markdown ecosystem)
+* transform mdast to hast (HTML syntax tree)
+* transform through rehype (HTML ecosystem)
+* render hast to React with components
## Appendix A: HTML in markdown
@@ -507,39 +656,40 @@ because it is dangerous and defeats the purpose of this library.
However, if you are in a trusted environment (you trust the markdown), and
can spare the bundle size (±60kb minzipped), then you can use
-[`rehype-raw`][raw]:
+[`rehype-raw`][github-rehype-raw]:
-```jsx
+```js
import React from 'react'
-import ReactDom from 'react-dom'
-import ReactMarkdown from 'react-markdown'
+import {createRoot} from 'react-dom/client'
+import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
-const input = `Some emphasis and strong!
+```js ++ Some emphasis and strong! +
a
') + }) + + await t.test('should throw w/ `source`', function () { + assert.throws(function () { + // @ts-expect-error: check how the runtime handles untyped `source`. + renderToStaticMarkup(\n' + ) + }) + + await t.test('should support a break', function () { + assert.equal( + renderToStaticMarkup(a
\n
a
\nb
a\n'
+ )
+ })
+
+ await t.test('should support a code (block, flow; fenced)', function () {
+ assert.equal(
+ renderToStaticMarkup(a\n'
+ )
+ })
+
+ await t.test('should support a delete (GFM)', function () {
+ assert.equal(
+ renderToStaticMarkup(
+ a
a
' + ) + }) + + await t.test('should support a footnote (GFM)', function () { + assert.equal( + renderToStaticMarkup( +a1
\ny ↩
\n<i>a</i>
' + ) + }) + + await t.test('should support an html (w/ `rehype-raw`)', function () { + assert.equal( + renderToStaticMarkup( +a
' + ) + }) + + await t.test('should support an image', function () { + assert.equal( + renderToStaticMarkup(a
a
') + }) + + await t.test('should support a strong', function () { + assert.equal( + renderToStaticMarkup(a
' + ) + }) + + await t.test('should support a table (GFM)', function () { + assert.equal( + renderToStaticMarkup( +| a |
|---|
| b |
| a | b | c | d |
|---|
abc
') + }) + + await t.test( + 'should support `allowedElements` (drop unlisted nodes)', + function () { + assert.equal( + renderToStaticMarkup( +b
' + ) + }) + await t.test('should support `disallowedElements`', function () { + assert.equal( + renderToStaticMarkup( +a ↩
\na\n\nb\n\nc
| a |
|---|
| b |
| a |
|---|
| b |
a
' + ) + + assert.equal(calls, 1) + }) + + await t.test('should support plugins (`remark-gfm`)', function () { + assert.equal( + renderToStaticMarkup( +a b c
c
' + ) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({ + type: 'element', + tagName: 'input', + properties: {id: 'a', ariaDescribedBy: 'b', required: true}, + children: [] + }) + } + } + }) + + await t.test('should support data properties', function () { + assert.equal( + renderToStaticMarkup(b
' + ) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({ + type: 'element', + tagName: 'i', + properties: {dataWhatever: 'a', dataIgnoreThis: undefined}, + children: [] + }) + } + } + }) + + await t.test('should support comma separated properties', function () { + assert.equal( + renderToStaticMarkup(c
' + ) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({ + type: 'element', + tagName: 'i', + properties: {accept: ['a', 'b']}, + children: [] + }) + } + } + }) + + await t.test('should support `style` properties', function () { + assert.equal( + renderToStaticMarkup(a
' + ) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({ + type: 'element', + tagName: 'i', + properties: {style: 'color: red; font-weight: bold'}, + children: [] + }) + } + } + }) + + await t.test( + 'should support `style` properties w/ vendor prefixes', + function () { + assert.equal( + renderToStaticMarkup( +a
' + ) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({ + type: 'element', + tagName: 'i', + properties: {style: '-ms-b: 1; -webkit-c: 2'}, + children: [] + }) + } + } + } + ) + + await t.test('should support broken `style` properties', function () { + assert.equal( + renderToStaticMarkup(a
' + ) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({ + type: 'element', + tagName: 'i', + properties: {style: 'broken'}, + children: [] + }) + } + } + }) + + await t.test('should support SVG elements', function () { + assert.equal( + renderToStaticMarkup(a
' + ) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({ + type: 'element', + tagName: 'svg', + properties: { + viewBox: '0 0 500 500', + xmlns: 'http://www.w3.org/2000/svg' + }, + children: [ + { + type: 'element', + tagName: 'title', + properties: {}, + children: [{type: 'text', value: 'SVG `a
' + assert.equal(actual, expected) + + function plugin() { + /** + * @param {Root} tree + * @returns {undefined} + */ + return function (tree) { + tree.children.unshift({type: 'comment', value: 'things!'}) + } + } + }) + + await t.test('should support table cells w/ style', function () { + assert.equal( + renderToStaticMarkup( +| a |
|---|
a
') + resolve() + }) + ) + .on('error', reject) + }) + }) + + await t.test( + 'should support async plugins w/ `MarkdownAsync` (`rehype-starry-night`)', + async function () { + return new Promise(function (resolve) { + renderToPipeableStream( +console.log(3.14)\n'
+ )
+ resolve()
+ })
+ )
+ })
+ }
+ )
+})
+
+// Note: hooks are not supported on the “server”.
+test('MarkdownHooks', async function (t) {
+ await t.test('should support `MarkdownHooks`', async function () {
+ const plugin = deferPlugin()
+ const result = render(
+ a
') + }) + + await t.test( + 'should support async plugins w/ `MarkdownHooks` (`rehype-starry-night`)', + async function () { + const plugin = deferPlugin() + const result = render( +console.log(3.14)\n'
+ )
+ }
+ )
+
+ await t.test('should support `fallback`', async function () {
+ const plugin = deferPlugin()
+ const result = render(
+ a
') + }) + + await t.test('should support plugins that error', async function () { + const plugin = deferPlugin() + const result = render( +b
') + }) +}) + +/** + * Create an async unified plugin that waits until a promise is resolved or + * rejected from the outside. + * + * @returns {DeferredPlugin} + * Deferred plugin object. + */ +function deferPlugin() { + /** @type {(error: Error) => void} */ + let hoistedReject + /** @type {() => void} */ + let hoistedResolve + /** @type {PromiseThis is bold text
-This is bold text
-This is italic text
-This is italic text
-Strikethrough
--Blockquotes can also be nested...
---...by using additional greater-than signs right next to each other...
---...or with spaces between arrows.
-
Unordered
-+, -, or *Ordered
-Or:
-1.Start numbering with offset:
-Loose lists?
-foo
-bar
-Inline code
Indented code
-// Some comments
-line 1 of code
-line 2 of code
-line 3 of code
-
-Block code "fences"
-Sample text here...
-
-Syntax highlighting
-var foo = function (bar) {
- return bar++;
-};
-
-console.log(foo(5));
-
-| Tag | Use |
|---|---|
| p | Paragraph |
| table | Table |
| em | Emphasis |
Left/right aligned columns
- - - - - - - - - - - - - - - - - - - - - -| Project | Stars |
|---|---|
| React | 80 759 |
| Vue.js | 73 322 |
| sse-channel | 50 |
Autoconverted link https://github.com/remarkjs/react-markdown
- -
-
Like links, Images also have a footnote style syntax
-
With a reference later in the document defining the URL location:
-Yeah, hard breaks
-can be useful too.
Some characters, like æ, & and similar should be handled properly.
-Does anyone actually like the fact that you can embed HTML in markdown?
-, as long as it doesn't contain any attributes. If you -have weird ordering on your tags, it won't work either. It does support nested -tags, however. And with therehype-raw plugin, it can now properly handle HTML! Which is pretty sweet.
-Cool, eh?
\ No newline at end of file diff --git a/test/fixtures/runthrough.md b/test/fixtures/runthrough.md deleted file mode 100644 index c643ab4c..00000000 --- a/test/fixtures/runthrough.md +++ /dev/null @@ -1,171 +0,0 @@ -# h1 Heading -## h2 Heading -### h3 Heading -#### h4 Heading -##### h5 Heading -###### h6 Heading - - -## Horizontal Rules - -___ - ---- - -*** - - -## Emphasis - -**This is bold text** - -__This is bold text__ - -*This is italic text* - -_This is italic text_ - -~~Strikethrough~~ - - -## Blockquotes - - -> Blockquotes can also be nested... ->> ...by using additional greater-than signs right next to each other... -> > > ...or with spaces between arrows. - - -## Lists - -Unordered - -+ Create a list by starting a line with `+`, `-`, or `*` -+ Sub-lists are made by indenting 2 spaces: - - Marker character change forces new list start: - * Ac tristique libero volutpat at - + Facilisis in pretium nisl aliquet - - Nulla volutpat aliquam velit -+ Very easy! - -Ordered - -1. Lorem ipsum dolor sit amet -2. Consectetur adipiscing elit -3. Integer molestie lorem at massa - -Or: - -1. You can use sequential numbers... -1. ...or keep all the numbers as `1.` - -Start numbering with offset: - -57. foo -1. bar - -Loose lists? - -- foo - -- bar - - -## Code - -Inline `code` - -Indented code - - // Some comments - line 1 of code - line 2 of code - line 3 of code - - -Block code "fences" - -``` -Sample text here... -``` - -Syntax highlighting - -``` js -var foo = function (bar) { - return bar++; -}; - -console.log(foo(5)); -``` - -## Tables - -| Tag | Use | -| ------ | ----------- | -| p | Paragraph | -| table | Table | -| em | Emphasis | - -Left/right aligned columns - -| Project | Stars | -| :------ | -----------:| -| React | 80 759 | -| Vue.js | 73 322 | -| sse-channel | 50 | - - -## Links - -[Espen.Codes](https://espen.codes/) - -[Sanity](https://www.sanity.io/ "Sanity, the headless CMS and PaaS") - -Autoconverted link https://github.com/remarkjs/react-markdown - -[Link references][React] - -[React]: https://reactjs.org "React, A JavaScript library for building user interfaces" - - -## Images - - - - -Like links, Images also have a footnote style syntax - -![Alt text][someref] - -With a reference later in the document defining the URL location: - -[someref]: https://public.sanity.io/modell_@2x.png "Headless CMS" - -## Hard breaks - -Yeah, hard breaks\ -can be useful too. - -## HTML entities - -Some characters, like æ, & and similar should be handled properly. - -## HTML - -Does anyone actually like the fact that you can embed HTML in markdown? - - - -We used to have a known bug where inline HTML wasn't handled well. You can do basic tags like -code, as long as it doesn't contain any attributes. If you
-have weird ordering on your tags, it won't work either. It does support nested
-tags, however. And with the rehype-raw plugin, it can now properly handle HTML! Which is pretty sweet.
-
-Test
') -}) - -test('should warn when passed `source`', () => { - const warn = console.warn - /** @type {unknown} */ - let message - - console.warn = (/** @type {unknown} */ d) => { - message = d - } - - // @ts-expect-error runtime - assert.equal(asHtml(b
') - assert.equal( - message, - '[react-markdown] Warning: please use `children` instead of `source` (seea
') - assert.equal( - message, - '[react-markdown] Warning: please remove `allowDangerousHtml` (seeTest
React is awesome\nAnd so is markdown
\nCombining = epic
' - ) -}) - -test('should handle multiline paragraphs properly (softbreak, paragraphs)', () => { - const input = 'React is awesome\nAnd so is markdown \nCombining = epic' - const actual = asHtml(React is awesome\nAnd so is markdown
\nCombining = epic
React is totally awesome
') -}) - -test('should handle bold/strong text', () => { - const input = 'React is __totally__ **awesome**' - const actual = asHtml(React is totally awesome
' - ) -}) - -test('should handle links without title attribute', () => { - const input = 'This is [a link](https://espen.codes/) to Espen.Codes.' - const actual = asHtml(This is a link to Espen.Codes.
' - ) -}) - -test('should handle links with title attribute', () => { - const input = - 'This is [a link](https://espen.codes/ "some title") to Espen.Codes.' - const actual = asHtml(This is a link to Espen.Codes.
' - ) -}) - -test('should handle links with uppercase protocol', () => { - const input = 'This is [a link](HTTPS://ESPEN.CODES/) to Espen.Codes.' - const actual = asHtml(This is a link to Espen.Codes.
' - ) -}) - -test('should handle links with custom uri transformer', () => { - const input = 'This is [a link](https://espen.codes/) to Espen.Codes.' - const actual = asHtml( -This is a link to Espen.Codes.
' - ) -}) - -test('should handle empty links with custom uri transformer', () => { - const input = 'Empty: []()' - - const actual = asHtml( -This is a link to Espen.Codes.
' - ) -}) - -test('should call function to get target attribute for links if specified', () => { - const input = 'This is [a link](https://espen.codes/) to Espen.Codes.' - const actual = asHtml( -This is a link to Espen.Codes.
' - ) -}) - -test('should handle links with custom target transformer', () => { - const input = 'Empty: []()' - - const actual = asHtml( -This is
.
This is
.
This is
.
Empty:
Empty:
This is
.
This is
.
Just call renderToStaticMarkup(), already
var foo = require('bar');\nfoo();\n'
- )
-})
-
-test('should handle code tags with language specification', () => {
- const input = "```js\nvar foo = require('bar');\nfoo();\n```"
- const actual = asHtml(var foo = require('bar');\nfoo();\n'
- )
-})
-
-test('should only use first language definition on code blocks', () => {
- const input = "```js foo bar\nvar foo = require('bar');\nfoo();\n```"
- const actual = asHtml(var foo = require('bar');\nfoo();\n'
- )
-})
-
-test('should support character references in code blocks', () => {
- const input = `~~~js
ololo
i
can
haz
class
names
!@#$%^&*()_
- woop
- ~~~`
- const actual = asHtml( woop\n'
- )
-})
-
-test('should handle code blocks by indentation', () => {
- const input = [
- '',
- ''
- ].join(' ')
- assert.equal(
- asHtml(<footer class="footer">\n © 2014 Foo Bar\n</footer>\n'
- )
-})
-
-test('should handle blockquotes', () => {
- const input = '> Moo\n> Tools\n> FTW\n'
- const actual = asHtml(\n') -}) - -test('should handle nested blockquotes', () => { - const input = [ - '> > Lots of ex-Mootoolers on the React team\n>\n', - "> Totally didn't know that.\n>\n", - "> > There's a reason why it turned out so awesome\n>\n", - "> Haha I guess you're right!" - ].join('') - - const actual = asHtml(Moo\nTools\nFTW
\n
\n' - ) -}) - -test('should handle tight, unordered lists', () => { - const input = '* Unordered\n* Lists\n* Are cool\n' - const actual = asHtml(\n\nLots of ex-Mootoolers on the React team
\nTotally didn't know that.
\n\n\nThere's a reason why it turned out so awesome
\nHaha I guess you're right!
\n
foo
\nbar
\nfoo
\nfoo
\nroot
\na\n\nb\n\nc
| a |
|---|
| b |
| c |
| a |
|---|
| b |
| c |
I am having so much fun
') -}) - -test('should escape html blocks by default', () => { - const input = [ - 'This is a regular paragraph.\n\n| Foo | \n
This is a regular paragraph.
\n<table>\n <tr>\n <td>Foo</td>\n </tr>\n</table>\nThis is another regular paragraph.
' - ) -}) - -test('should skip html blocks if skipHtml prop is set', () => { - const input = [ - 'This is a regular paragraph.\n\n| Foo | \n
This is a regular paragraph.
\n\nThis is another regular paragraph.
' - ) -}) - -test('should escape html blocks by default (with HTML parser plugin)', () => { - const input = [ - 'This is a regular paragraph.\n\n| Foo | \n
This is a regular paragraph.
\n<table>\n <tr>\n <td>Foo</td>\n </tr>\n</table>\nThis is another regular paragraph.
' - ) -}) - -test('should handle horizontal rules', () => { - const input = 'Foo\n\n------------\n\nBar' - const actual = asHtml(Foo
\nBar
') -}) - -test('should set source position attributes if sourcePos option is enabled', () => { - const input = 'Foo\n\n------------\n\nBar' - const actual = asHtml(Foo
\nBar
' - ) -}) - -test('should pass on raw source position to non-tag components if rawSourcePos option is enabled', () => { - const input = '*Foo*\n\n------------\n\n__Bar__' - const actual = asHtml( -Foo
\nBar
' - ) -}) - -test('should pass on raw source position to non-tag components if rawSourcePos option is enabled and `rehype-raw` is used', () => { - const input = '*Foo*' - asHtml( -Paragraph
\n\nParagraph
\nFoo
' - ) -}) - -test('should unwrap child nodes from disallowed nodes, if unwrapDisallowed option is enabled', () => { - const input = - 'Espen *~~initiated~~ had the initial commit*, but has had several **contributors**' - const actual = asHtml( -Espen initiated had the initial commit, but has had several contributors
Languages are fun, right?
\n| ID | English | Norwegian | Italian |
|---|---|---|---|
| 1 | one | en | uno |
| 2 | two | to | due |
| 3 | three | tre | tre |
User is writing a table by hand
\n| Test | Test |
|---|
Stuff were changed in 1.1.4. Check out the changelog for reference.
' - ) -}) - -test('should render empty link references', () => { - const input = - 'Stuff were changed in [][]. Check out the changelog for reference.' - - assert.equal( - asHtml(Stuff were changed in [][]. Check out the changelog for reference.
' - ) -}) - -test('should render image references', () => { - const input = [ - 'Checkout out this ninja: ![The Waffle Ninja][ninja]. Pretty neat, eh?', - '', - '[ninja]: /assets/ninja.png' - ].join('\n') - - assert.equal( - asHtml(Checkout out this ninja:
. Pretty neat, eh?
'
- },
- blockquote: {
- input: '> Moo\n> Tools\n> FTW\n',
- shouldNotContain: '} */
- const inputs = []
- /** @type {keyof samples} */
- let key
-
- for (key in samples) {
- if (own.call(samples, key)) {
- inputs.push(samples[key].input)
- }
- }
-
- const fullInput = inputs.join('\n')
-
- for (key in samples) {
- if (own.call(samples, key)) {
- const sample = samples[key]
-
- // Just to make sure, let ensure that the opposite is true
- assert.equal(
- asHtml( ).includes(
- sample.shouldNotContain
- ),
- true,
- 'fixture should contain `' +
- sample.shouldNotContain +
- '` (`' +
- key +
- '`)'
- )
-
- assert.equal(
- asHtml(
-
- ).includes(sample.shouldNotContain),
- false,
- '`' +
- key +
- '` should not contain `' +
- sample.shouldNotContain +
- '` when disallowed'
- )
- }
- }
-})
-
-test('should throw if both allowed and disallowed types is specified', () => {
- assert.throws(() => {
- asHtml(
-
- )
- }, /only one of/i)
-})
-
-test('should be able to use a custom function to determine if the node should be allowed', () => {
- const input = [
- '# Header',
- '[react-markdown](https://github.com/remarkjs/react-markdown/) is a nice helper',
- 'Also check out [my website](https://espen.codes/)'
- ].join('\n\n')
-
- assert.equal(
- asHtml(
-
- element.tagName !== 'a' ||
- (element.properties &&
- typeof element.properties.href === 'string' &&
- element.properties.href.indexOf('https://github.com/') === 0)
- }
- />
- ),
- [
- 'Header
',
- 'react-markdown is a nice helper
',
- 'Also check out
'
- ].join('\n')
- )
-})
-
-test('should be able to override components', () => {
- const input =
- '# Header\n\nParagraph\n## New header\n1. List item\n2. List item 2\n\nFoo'
- /**
- * @param {number} level
- */
- const heading = (level) => {
- /**
- * @param {object} props
- * @param {Array} props.children
- */
- const component = (props) => (
- {props.children}
- )
- return component
- }
-
- const actual = asHtml(
-
- )
-
- assert.equal(
- actual,
- 'Header\nParagraph
\nNew header\n\n- List item
\n- List item 2
\n
\nFoo
'
- )
-})
-
-test('should throw on invalid component', () => {
- const input =
- '# Header\n\nParagraph\n## New header\n1. List item\n2. List item 2\n\nFoo'
- const components = {h1: 123}
- assert.throws(
- () =>
- // @ts-expect-error runtime
- asHtml( ),
- /Component for name `h1`/
- )
-})
-
-test('can render the whole spectrum of markdown within a single run', () => {
- const input = String(
- fs.readFileSync(path.join('test', 'fixtures', 'runthrough.md'))
- )
- const expected = String(
- fs.readFileSync(path.join('test', 'fixtures', 'runthrough.html'))
- )
-
- const actual = asHtml(
-
- )
-
- assert.equal(actual, expected)
-})
-
-test('sanitizes certain dangerous urls for links by default', () => {
- const error = console.error
-
- console.error = () => {}
-
- const input = [
- '# [Much fun](javascript:alert("foo"))',
- "Can be had with [XSS links](vbscript:foobar('test'))",
- '> And [other](VBSCRIPT:bap) nonsense... [files](file:///etc/passwd) for instance',
- '## [Entities]( javascript:alert("bazinga")) can be tricky, too',
- 'Regular [links](https://foo.bar) must [be]() allowed',
- '[Some ref][xss]',
- '[xss]: javascript:alert("foo") "Dangerous stuff"',
- 'Should allow [mailto](mailto:ex@ample.com) and [tel](tel:13133) links tho',
- 'Also, [protocol-agnostic](//google.com) should be allowed',
- 'local [paths](/foo/bar) should be [allowed](foo)',
- 'allow [weird](?javascript:foo) query strings and [hashes](foo#vbscript:orders)'
- ].join('\n\n')
-
- const actual = asHtml( )
- assert.equal(
- actual,
- 'Much fun
\nCan be had with XSS links
\n\n\n
\nEntities can be tricky, too
\n\n\nShould allow mailto and tel links tho
\nAlso, protocol-agnostic should be allowed
\n\nallow weird query strings and hashes
'
- )
-
- console.error = error
-})
-
-test('allows specifying a custom URI-transformer', () => {
- const input =
- 'Received a great [pull request](https://github.com/remarkjs/react-markdown/pull/15) today'
- const actual = asHtml(
- uri.replace(/^https?:\/\/github\.com\//i, '/')}
- />
- )
- assert.equal(
- actual,
- 'Received a great pull request today
'
- )
-})
-
-test('should support turning off the default URI transform', () => {
- const input = '[a](data:text/html,)'
- const actual = asHtml( )
- const expected =
- ''
- assert.equal(actual, expected)
-})
-
-test('can use parser plugins', () => {
- const input = 'a ~b~ c'
- const actual = asHtml( )
- assert.equal(actual, 'a b c
')
-})
-
-test('supports checkbox lists', () => {
- const input = '- [ ] Foo\n- [x] Bar\n\n---\n\n- Foo\n- Bar'
- const actual = asHtml( )
- assert.equal(
- actual,
- '\n- Foo
\n- Bar
\n
\n
\n\n- Foo
\n- Bar
\n
'
- )
-})
-
-test('should pass index of a node under its parent to components if `includeElementIndex` option is enabled', () => {
- const input = 'Foo\n\nBar\n\nBaz'
- const actual = asHtml(
- {otherProps.children}
- }
- }}
- />
- )
- assert.equal(actual, 'Foo
\nBar
\nBaz
')
-})
-
-test('should be able to render components with forwardRef in HOC', () => {
- /**
- * @typedef {import('react').Ref} Ref
- * @typedef {JSX.IntrinsicElements['a'] & import('../lib/ast-to-react').ReactMarkdownProps} Props
- */
-
- /**
- * @param {(params: Props) => JSX.Element} Component
- */
- const wrapper = (Component) =>
- React.forwardRef(
- /**
- * @param {Props} props
- * @param {Ref} ref
- */
- (props, ref) =>
- )
-
- /**
- * @param {Props} props
- */
- // eslint-disable-next-line react/jsx-no-target-blank
- const wrapped = (props) =>
-
- const actual = asHtml(
-
- [Link](https://example.com/)
-
- )
- assert.equal(
- actual,
- ''
- )
-})
-
-test('should render table of contents plugin', () => {
- const input = [
- '# Header',
- '## Table of Contents',
- '## First Section',
- '## Second Section',
- '### Subsection',
- '## Third Section'
- ].join('\n')
-
- const actual = asHtml( )
- assert.equal(
- actual,
- 'Header
\nTable of Contents
\n\n- \n\n
\n- \n\n
\n- Subsection
\n
\n \n- \n\n
\n
\nFirst Section
\nSecond Section
\nSubsection
\nThird Section
'
- )
-})
-
-test('should pass `node` as prop to all non-tag/non-fragment components', () => {
- const input = "# So, *headers... they're _cool_*\n\n"
- const actual = asHtml(
- {
- text += child.value
- })
- return text
- }
- }}
- />
- )
- assert.equal(actual, 'So, headers... they're cool')
-})
-
-test('should support formatting at the start of a GFM tasklist (GH-494)', () => {
- const input = '- [ ] *a*'
- const actual = asHtml( )
- const expected =
- '\n- a
\n
'
- assert.equal(actual, expected)
-})
-
-test('should support aria properties', () => {
- const input = 'c'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({
- type: 'element',
- tagName: 'input',
- properties: {id: 'a', ariaDescribedBy: 'b', required: true},
- children: []
- })
- }
-
- const actual = asHtml( )
- const expected = 'c
'
- assert.equal(actual, expected)
-})
-
-test('should support data properties', () => {
- const input = 'b'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({
- type: 'element',
- tagName: 'i',
- properties: {dataWhatever: 'a'},
- children: []
- })
- }
-
- const actual = asHtml( )
- const expected = 'b
'
- assert.equal(actual, expected)
-})
-
-test('should support comma separated properties', () => {
- const input = 'c'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({
- type: 'element',
- tagName: 'i',
- properties: {accept: ['a', 'b']},
- children: []
- })
- }
-
- const actual = asHtml( )
- const expected = 'c
'
- assert.equal(actual, expected)
-})
-
-test('should support `style` properties', () => {
- const input = 'a'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({
- type: 'element',
- tagName: 'i',
- properties: {style: 'color: red; font-weight: bold'},
- children: []
- })
- }
-
- const actual = asHtml( )
- const expected = 'a
'
- assert.equal(actual, expected)
-})
-
-test('should support `style` properties w/ vendor prefixes', () => {
- const input = 'a'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({
- type: 'element',
- tagName: 'i',
- properties: {style: '-ms-b: 1; -webkit-c: 2'},
- children: []
- })
- }
-
- const actual = asHtml( )
- const expected = 'a
'
- assert.equal(actual, expected)
-})
-
-test('should support broken `style` properties', () => {
- const input = 'a'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({
- type: 'element',
- tagName: 'i',
- properties: {style: 'broken'},
- children: []
- })
- }
-
- const actual = asHtml( )
- const expected = 'a
'
- assert.equal(actual, expected)
-})
-
-test('should support SVG elements', () => {
- const input = 'a'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({
- type: 'element',
- tagName: 'svg',
- properties: {xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 500 500'},
- children: [
- {
- type: 'element',
- tagName: 'title',
- properties: {},
- children: [{type: 'text', value: 'SVG `` element'}]
- },
- {
- type: 'element',
- tagName: 'circle',
- properties: {cx: 120, cy: 120, r: 100},
- children: []
- },
- // `strokeMiterLimit` in hast, `strokeMiterlimit` in React.
- {
- type: 'element',
- tagName: 'path',
- properties: {strokeMiterLimit: -1},
- children: []
- }
- ]
- })
- }
-
- const actual = asHtml( )
- const expected =
- 'a
'
- assert.equal(actual, expected)
-})
-
-test('should support (ignore) comments', () => {
- const input = 'a'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- root.children.unshift({type: 'comment', value: 'things!'})
- }
-
- const actual = asHtml( )
- const expected = 'a
'
- assert.equal(actual, expected)
-})
-
-test('should support table cells w/ style', () => {
- const input = '| a |\n| :- |'
-
- /** @type {import('unified').Plugin, Root>} */
- const plugin = () => (root) => {
- visit(root, {type: 'element', tagName: 'th'}, (node) => {
- node.properties = {...node.properties, style: 'color: red'}
- })
- }
-
- const actual = asHtml(
-
- )
- const expected =
- 'a
'
-
- assert.equal(actual, expected)
-})
-
-test('should crash on a plugin replacing `root`', () => {
- const input = 'a'
- /** @type {import('unified').Plugin, Root>} */
- // @ts-expect-error: runtime.
- const plugin = () => () => ({type: 'comment', value: 'things!'})
- assert.throws(() => {
- asHtml( )
- }, /Expected a `root` node/)
-})
-
-test.run()
diff --git a/tsconfig.json b/tsconfig.json
index 70285598..95ae95ae 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,20 +1,17 @@
{
- "include": ["lib/**/*.js", "test/**/*.jsx", "test/**/*.js", "index.js"],
- "exclude": ["**/*.min.js"],
"compilerOptions": {
- "target": "ES2020",
- "lib": ["ES2020", "DOM"],
- "module": "ES2020",
- "moduleResolution": "node",
- "jsx": "react",
- "allowJs": true,
"checkJs": true,
+ "customConditions": ["development"],
+ "declarationMap": true,
"declaration": true,
"emitDeclarationOnly": true,
- "allowSyntheticDefaultImports": true,
- "skipLibCheck": true,
- "noImplicitAny": false,
- "noImplicitThis": true,
- "strict": true
- }
+ "exactOptionalPropertyTypes": true,
+ "jsx": "preserve",
+ "lib": ["dom", "es2022"],
+ "module": "node16",
+ "strict": true,
+ "target": "es2022"
+ },
+ "exclude": ["coverage/", "node_modules/"],
+ "include": ["**/*.js", "**/*.jsx"]
}