diff --git a/index.d.ts b/index.d.ts
index ff96264e..55a678d5 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,9 +1,9 @@
// TypeScript Version: 3.4
-import {ElementType, ReactNode, ReactElement} from 'react'
+import {ElementType, ReactElement} from 'react'
import {PluggableList} from 'unified'
import * as unist from 'unist'
-import * as mdast from 'mdast'
+import * as hast from 'hast'
type Not Here is some JavaScript code:
- The lift coefficient (
- This Markdown contains HTML, and will require
- the Some emphasis and strong! `)
-* `emphasis` — Emphasis (``)
-* `strong` — Strong (``)
-* `thematicBreak` — Horizontal rule (`
This is bold text
This is bold text
This is italic text
This is italic text
Blockquotes can also be nested...
...by using additional greater-than signs right next to each other...
...or with spaces between arrows.
Unordered
Ordered
Or:
Start numbering with offset:
Loose lists?
foo
bar
Inline
Indented code
Block code "fences"
Syntax highlighting
Left/right aligned columns
Like links, Images also have a footnote style syntax
With a reference later in the document defining the URL location:
Yeah, hard breaks
Some characters, like æ, & and similar should be handled properly.
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
-
-
Cool, eh?
This is bold text
This is bold text
This is italic text
This is italic text
Blockquotes can also be nested...
...by using additional greater-than signs right next to each other...
...or with spaces between arrows.
Unordered
Ordered
Or:
Start numbering with offset:
Loose lists?
foo
bar
Inline
Indented code
Block code "fences"
Syntax highlighting
Left/right aligned columns
Like links, Images also have a footnote style syntax
With a reference later in the document defining the URL location:
Yeah, hard breaks
Some characters, like æ, & and similar should be handled properly.
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
-
Cool, eh?
Lift(L) can be determined by Lift Coefficient (CL) like the following equation.
And
for instance
Regular
allowed
Should allow
links tho
Also,
should be allowed
- I am having
-
- so
-
- much fun
-
Link
-
-
- Just call
-
- I am having
- ",
- }
- }
- />
- so
- ",
- }
- }
- />
- much fun
-
- I am having
-
- so
-
- much fun
-
- I am having
-
- so
-
- much fun
-
- I am having
-
-
- so
-
-
- much fun
-
- I am having
-
- I am having
-
- I am having
-
- I am having
-
- I am having
-
- so much
-
- fun
+ Just call
+
This is a regular paragraph.
This is another regular paragraph.
This is a regular paragraph.
This is another regular paragraph.
Moo
Tools
FTW
Foo
Bar
- This is a regular paragraph.
-
- This is another regular paragraph.
-
- This is a regular paragraph.
-
- This is another regular paragraph.
-
This is
@@ -1627,25 +1906,6 @@ exports[`should handle images without title attribute 1`] = `
- I am having
- <strong>
- so
- </strong>
- much fun
-
- I am having
-
This is
@@ -1697,31 +1957,59 @@ exports[`should handle links without title attribute 1`] = `
exports[`should handle loose, unordered lists 1`] = `
foo
bar
foo
}
}
@@ -262,7 +269,7 @@ console.log('It works!')
~~~
`
-render(
+
>
```
html-parser
AST plugin to be loaded, in addition to setting the allowDangerousHtml
property to false.
-`
+const input = `Show equivalent JSX
```jsx
-html-parser
AST plugin to be loaded, in addition to setting the{' '}
- allowDangerousHtml
property to false.
-
`)
-* `paragraph` — Paragraph (`
`)
-* `blockquote` — Block quote (``)
-* `link` — Link (``)
-* `image` — Image (``)
-* `linkReference` — Link through a reference (``)
-* `imageReference` — Image through a reference (``)
-* `list` — List (`
` or `
`)
-* `listItem` — List item (`
` through `
`)
-* `inlineCode` — Inline code (`
`)
-* `code` — Block of code (`
`)
-* `html` — HTML node (Best-effort rendering)
-* `virtualHtml` — If `allowDangerousHtml` is not on and `skipHtml` is off, a
- naive HTML parser is used to support basic HTML
-* `parsedHtml` — If `allowDangerousHtml` is on, `skipHtml` is off, and
- `html-parser` is used, more advanced HTML is supported
-
-With [`remark-gfm`][gfm], the following are also available:
-
-* `delete` — Delete text (`
`)
-* `table` — Table (``)
-* `tableHead` — Table head (``)
-* `tableBody` — Table body (``)
-* `tableRow` — Table row (`
",
- }
- }
- />,
- `)
-* `tableCell` — Table cell (` ` or ` `)
+The keys in components are HTML equivalents for the things you write with
+markdown (such as `h1` for `# heading`)**†**
+
+**†** Normally, in markdown, those are: `a`, `blockquote`, `code`, `em`, `h1`,
+`h2`, `h3`, `h4`, `h5`, `h6`, `hr`, `img`, `li`, `ol`, `p`, `pre`, `strong`, and
+`ul`.
+With [`remark-gfm`][gfm], you can also use: `del`, `input`, `table`, `tbody`,
+`td`, `th`, `thead`, and `tr`.
+Other remark or rehype plugins that add support for new constructs will also
+work with `react-markdown`.
+
+The props that are passed are what you probably would expect: an `a` (link) will
+get `href` (and `title`) props, and `img` (image) an `src` (and `title`), etc.
+There are some extra props passed.
+
+* `code`
+ * `inline` (`boolean?`)
+ — set to `true` for inline code
+ * `className` (`string?`)
+ — set to `language-js` or so when using ` ```js `
+* `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
+ * `level` (`number` beween 1 and 6)
+ — heading rank
+* `input` (when using [`remark-gfm`][gfm])
+ * `checked` (`boolean`)
+ — whether the item is checked
+ * `disabled` (`true`)
+ * `type` (`'checkbox'`)
+* `li`
+ * `index` (`number`)
+ — number of preceding items (so first gets `0`, etc.)
+ * `ordered` (`boolean`)
+ — whether the parent is an `ol` or not
+ * `checked` (`boolean?`)
+ — `null` normally, `boolean` when using [`remark-gfm`][gfm]’s tasklists
+ * `className` (`string?`)
+ — set to `task-list-item` when using [`remark-gfm`][gfm] and the
+ item1 is a tasklist
+* `ol`, `ul`
+ * `depth` (`number`)
+ — number of ancestral lists (so first gets `0`, etc.)
+ * `ordered` (`boolean`)
+ — whether it’s an `ol` or not
+ * `className` (`string?`)
+ — set to `contains-task-list` when using [`remark-gfm`][gfm] and the
+ list contains one or more tasklists
+* `td`, `th` (when using [`remark-gfm`][gfm])
+ * `style` (`Object?`)
+ — something like `{textAlign: 'left'}` depending on how the cell is
+ aligned
+ * `isHeader` (`boolean`)
+ — whether it’s a `th` or not
+* `tr` (when using [`remark-gfm`][gfm])
+ * `isHeader` (`boolean`)
+ — whether it’s in the `thead` or not
+
+Every component will receive a `node` (`Object`).
+This is the original [hast](https://github.com/syntax-tree/hast) element being
+turned into a React element.
+
+Every element will receive a `key` (`string`).
+See [React’s docs](https://reactjs.org/docs/lists-and-keys.html#keys) for more
+info.
+
+Optionally, components will also receive:
+
+* `data-sourcepos` (`string`)
+ — see `sourcePos` option
+* `sourcePosition` (`Object`)
+ — see `rawSourcePos` option
+* `index` and `siblingCount` (`number`)
+ — see `includeElementIndex` option
+* `target` on `a` (`string`)
+ — see `linkTarget` option
## Security
Use of `react-markdown` is secure by default.
-Overwriting `transformLinkUri` or `transformImageUri` to something insecure or
-turning `allowDangerousHtml` on, will open you up to XSS vectors.
-Furthermore, the `plugins` you use and `renderers` you write may be insecure.
+Overwriting `transformLinkUri` or `transformImageUri` to something insecure will
+open you up to XSS vectors.
+Furthermore, the `remarkPlugins` and `rehypePlugins` you use and `components`
+you write may be insecure.
+
+To make sure the content is completely safe, even after what plugins do,
+use [`rehype-sanitize`][sanitize].
+That plugin lets you define your own schema of what is and isn’t allowed.
## Related
@@ -480,20 +543,26 @@ abide by its terms.
[position]: https://github.com/syntax-tree/unist#position
-[index]: https://github.com/syntax-tree/unist#index
-
[gfm]: https://github.com/remarkjs/remark-gfm
[math]: https://github.com/remarkjs/remark-math
+[katex]: https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex
+
+[raw]: https://github.com/rehypejs/rehype-raw
+
+[sanitize]: https://github.com/rehypejs/rehype-sanitize
+
[remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins
+[rehype-plugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins
+
+[cm-html]: https://spec.commonmark.org/0.29/#html-blocks
+
[uri]: https://github.com/remarkjs/react-markdown/blob/main/src/uri-transformer.js
[security]: #security
-[react-katex]: https://github.com/MatejBransky/react-katex
-
[react-syntax-highlighter]: https://github.com/react-syntax-highlighter/react-syntax-highlighter
[conor]: https://github.com/conorhastings
diff --git a/src/ast-to-react.js b/src/ast-to-react.js
index 5288db3d..cb00d256 100644
--- a/src/ast-to-react.js
+++ b/src/ast-to-react.js
@@ -2,257 +2,250 @@
const React = require('react')
const ReactIs = require('react-is')
+const convert = require('unist-util-is/convert')
+const svg = require('property-information/svg')
+const find = require('property-information/find')
+const hastToReact = require('property-information/hast-to-react.json')
+const spaces = require('space-separated-tokens')
+const commas = require('comma-separated-tokens')
+const style = require('style-to-object')
+
+exports.hastToReact = toReact
+exports.hastChildrenToReact = childrenToReact
+
+const element = convert('element')
+const text = convert('text')
+
+const own = {}.hasOwnProperty
+
+function childrenToReact(context, node) {
+ const children = []
+ let childIndex = -1
+ let child
+
+ while (++childIndex < node.children.length) {
+ child = node.children[childIndex]
+
+ if (element(child)) {
+ children.push(toReact(context, child, childIndex, node))
+ } else if (text(child)) {
+ children.push(child.value)
+ } else if (child.type === 'raw' && !context.options.skipHtml) {
+ // Default behavior is to show (encoded) HTML.
+ children.push(child.value)
+ }
+ }
-function astToReact(node, options, parent = {}, index = 0) {
- const renderer = options.renderers[node.type]
+ return children
+}
- // Nodes created by plugins do not have positional info, in which case we set
- // an object that matches the positon interface.
- if (!node.position) {
- node.position = {
- start: {line: null, column: null, offset: null},
- end: {line: null, column: null, offset: null}
+// eslint-disable-next-line complexity, max-statements
+function toReact(context, node, index, parent) {
+ const options = context.options
+ const parentSchema = context.schema
+ const name = node.tagName
+ const properties = {}
+ let schema = parentSchema
+ let property
+
+ if (parentSchema.space === 'html' && name === 'svg') {
+ schema = svg
+ context.schema = schema
+ }
+
+ for (property in node.properties) {
+ /* istanbul ignore else - prototype polution. */
+ if (own.call(node.properties, property)) {
+ addProperty(properties, property, node.properties[property], context, name)
}
}
- const pos = node.position.start
- const key = [node.type, pos.line, pos.column, index].join('-')
+ if (name === 'ol' || name === 'ul') {
+ context.listDepth++
+ }
+
+ const children = childrenToReact(context, node)
- if (!ReactIs.isValidElementType(renderer)) {
- throw new Error(`Renderer for type \`${node.type}\` not defined or is not renderable`)
+ if (name === 'ol' || name === 'ul') {
+ context.listDepth--
}
- const nodeProps = getNodeProps(node, key, options, renderer, parent, index)
-
- return React.createElement(
- renderer,
- nodeProps,
- nodeProps.children || resolveChildren() || undefined
- )
-
- function resolveChildren() {
- return (
- node.children &&
- node.children.map((childNode, i) =>
- astToReact(childNode, options, {node, props: nodeProps}, i)
- )
- )
+ // Restore parent schema.
+ context.schema = parentSchema
+
+ // Nodes created by plugins do not have positional info, in which case we use
+ // an object that matches the positon interface.
+ const position = node.position || {
+ start: {line: null, column: null, offset: null},
+ end: {line: null, column: null, offset: null}
}
-}
+ const component =
+ options.components && own.call(options.components, name) ? options.components[name] : name
+ const basic = typeof component === 'string' || component === React.Fragment
-// eslint-disable-next-line max-params, complexity
-function getNodeProps(node, key, opts, renderer, parent, index) {
- const props = {key}
+ if (!ReactIs.isValidElementType(component)) {
+ throw new TypeError(`Component for name \`${name}\` not defined or is not renderable`)
+ }
- const isSimpleRenderer = typeof renderer === 'string' || renderer === React.Fragment
+ properties.key = [name, position.start.line, position.start.column, index].join('-')
- // `sourcePos` is true if the user wants source information (line/column info from markdown source)
- if (opts.sourcePos && node.position) {
- props['data-sourcepos'] = flattenPosition(node.position)
+ if (name === 'a' && options.linkTarget) {
+ properties.target =
+ typeof options.linkTarget === 'function'
+ ? options.linkTarget(properties.href, node.children, properties.title)
+ : options.linkTarget
}
- if (opts.rawSourcePos && !isSimpleRenderer) {
- props.sourcePosition = node.position
+ if (name === 'a' && options.transformLinkUri) {
+ properties.href = options.transformLinkUri(properties.href, node.children, properties.title)
}
- // If `includeNodeIndex` is true, pass node index info to all non-tag renderers
- if (opts.includeNodeIndex && parent.node && parent.node.children && !isSimpleRenderer) {
- props.index = parent.node.children.indexOf(node)
- props.parentChildCount = parent.node.children.length
+ if (!basic && name === 'code' && parent.tagName !== 'pre') {
+ properties.inline = true
}
- const ref =
- node.identifier !== null && node.identifier !== undefined
- ? /* istanbul ignore next - plugins could inject an undefined reference. */
- opts.definitions[node.identifier.toUpperCase()] || {}
- : null
-
- switch (node.type) {
- case 'root':
- assignDefined(props, {className: opts.className})
- break
- case 'text':
- props.nodeKey = key
- props.children = node.value
- break
- case 'heading':
- props.level = node.depth
- break
- case 'list':
- props.start = node.start
- props.ordered = node.ordered
- props.spread = node.spread
- props.depth = node.depth
- break
- case 'listItem':
- props.checked = node.checked
- props.spread = node.spread
- props.ordered = node.ordered
- props.index = node.index
- props.children = getListItemChildren(node, parent).map((childNode, i) => {
- return astToReact(childNode, opts, {node: node, props: props}, i)
- })
- break
- case 'definition':
- assignDefined(props, {identifier: node.identifier, title: node.title, url: node.url})
- break
- case 'code':
- assignDefined(props, {language: node.lang && node.lang.split(/\s/, 1)[0]})
- break
- case 'inlineCode':
- props.children = node.value
- props.inline = true
- break
- case 'link':
- assignDefined(props, {
- title: node.title || undefined,
- target:
- typeof opts.linkTarget === 'function'
- ? opts.linkTarget(node.url, node.children, node.title)
- : opts.linkTarget,
- href: opts.transformLinkUri
- ? opts.transformLinkUri(node.url, node.children, node.title)
- : node.url
- })
- break
- case 'image':
- assignDefined(props, {
- src: opts.transformImageUri
- ? opts.transformImageUri(node.url, node.children, node.title, node.alt)
- : node.url,
- alt: node.alt || '',
- title: node.title || undefined
- })
- break
- case 'linkReference':
- assignDefined(
- props,
- Object.assign({}, ref, {
- href: opts.transformLinkUri ? opts.transformLinkUri(ref.href) : ref.href
- })
- )
- break
- case 'imageReference':
- assignDefined(props, {
- src:
- opts.transformImageUri && ref.href
- ? opts.transformImageUri(ref.href, node.children, ref.title, node.alt)
- : ref.href,
- alt: node.alt || '',
- title: ref.title || undefined
- })
- break
- case 'table':
- case 'tableHead':
- case 'tableBody':
- props.columnAlignment = node.align
- break
- case 'tableRow':
- props.isHeader = parent.node.type === 'tableHead'
- props.columnAlignment = parent.props.columnAlignment
- break
- case 'tableCell':
- assignDefined(props, {
- isHeader: parent.props.isHeader,
- align: parent.props.columnAlignment[index]
- })
- break
- case 'virtualHtml':
- props.tag = node.tag
- break
- case 'html':
- // @todo find a better way than this
- props.isBlock = node.position.start.line !== node.position.end.line
- props.allowDangerousHtml = opts.allowDangerousHtml
- props.skipHtml = opts.skipHtml
- break
- case 'parsedHtml': {
- let parsedChildren
- if (node.children) {
- parsedChildren = node.children.map((child, i) => astToReact(child, opts, {node, props}, i))
- }
- props.allowDangerousHtml = opts.allowDangerousHtml
- props.skipHtml = opts.skipHtml
- props.element = node.element ? mergeNodeChildren(node, parsedChildren) : null
- break
- }
- default:
- assignDefined(
- props,
- Object.assign({}, node, {
- type: undefined,
- position: undefined,
- children: undefined
- })
- )
+ if (
+ !basic &&
+ (name === 'h1' ||
+ name === 'h2' ||
+ name === 'h3' ||
+ name === 'h4' ||
+ name === 'h5' ||
+ name === 'h6')
+ ) {
+ properties.level = parseInt(name.charAt(1), 10)
}
- if (!isSimpleRenderer && node.value) {
- props.value = node.value
+ if (name === 'img' && options.transformImageUri) {
+ properties.src = options.transformImageUri(properties.src, node.alt, properties.title)
}
- if (!isSimpleRenderer) {
- props.node = node
+ if (!basic && name === 'li') {
+ const input = getInputElement(node)
+ properties.checked = input ? Boolean(input.properties.checked) : null
+ properties.index = getElementsBeforeCount(parent, node)
+ properties.ordered = parent.tagName === 'ol'
}
- return props
-}
+ if (!basic && (name === 'ol' || name === 'ul')) {
+ properties.ordered = name === 'ol'
+ properties.depth = context.listDepth
+ }
+
+ if (name === 'td' || name === 'th') {
+ if (properties.align) {
+ if (!properties.style) properties.style = {}
+ properties.style.textAlign = properties.align
+ delete properties.align
+ }
-function assignDefined(target, attrs) {
- for (const key in attrs) {
- if (typeof attrs[key] !== 'undefined') {
- target[key] = attrs[key]
+ if (!basic) {
+ properties.isHeader = name === 'th'
}
}
-}
-function mergeNodeChildren(node, parsedChildren) {
- const el = node.element
- if (Array.isArray(el)) {
- return React.createElement(React.Fragment, null, el)
+ if (!basic && name === 'tr') {
+ properties.isHeader = Boolean(parent.tagName === 'thead')
}
- if (el.props.children || parsedChildren) {
- const children = React.Children.toArray(el.props.children).concat(parsedChildren)
- return React.cloneElement(el, null, children)
+ // If `sourcePos` is given, pass source information (line/column info from markdown source).
+ if (options.sourcePos) {
+ properties['data-sourcepos'] = flattenPosition(position)
}
- return React.cloneElement(el, null)
+
+ if (!basic && options.rawSourcePos) {
+ properties.sourcePosition = node.position
+ }
+
+ // If `includeElementIndex` is given, pass node index info to components.
+ if (!basic && options.includeElementIndex) {
+ properties.index = getElementsBeforeCount(parent, node)
+ properties.siblingCount = getElementsBeforeCount(parent)
+ }
+
+ if (!basic) {
+ properties.node = node
+ }
+
+ // Ensure no React warnings are emitted for void elements w/ children.
+ return children.length
+ ? React.createElement(component, properties, children)
+ : React.createElement(component, properties)
}
-function flattenPosition(pos) {
- return [pos.start.line, ':', pos.start.column, '-', pos.end.line, ':', pos.end.column]
- .map(String)
- .join('')
+function getInputElement(node) {
+ let index = -1
+
+ while (++index < node.children.length) {
+ if (element(node.children[index]) && node.children[index].tagName === 'input')
+ return node.children[index]
+ }
+
+ return null
}
-function getListItemChildren(node, parent) {
- /* istanbul ignore next - list items are always in a list, but best to be sure. */
- const loose = parent && parent.node ? listLoose(parent.node) : listItemLoose(node)
- return loose ? node.children : unwrapParagraphs(node)
+function getElementsBeforeCount(parent, node) {
+ let index = -1
+ let count = 0
+
+ while (++index < parent.children.length) {
+ if (parent.children[index] === node) break
+ if (element(parent.children[index])) count++
+ }
+
+ return count
}
-function unwrapParagraphs(node) {
- return node.children.reduce((array, child) => {
- return array.concat(child.type === 'paragraph' ? child.children : [child])
- }, [])
+function addProperty(props, prop, value, ctx, name) {
+ const info = find(ctx.schema, prop)
+ let result = value
+
+ // Ignore nullish and `NaN` values.
+ // eslint-disable-next-line no-self-compare
+ if (result === null || result === undefined || result !== result) {
+ return
+ }
+
+ // Accept `array`.
+ // Most props are space-separated.
+ if (result && typeof result === 'object' && 'length' in result) {
+ result = (info.commaSeparated ? commas : spaces).stringify(result)
+ }
+
+ if (info.property === 'style' && typeof result === 'string') {
+ result = parseStyle(result, name)
+ }
+
+ if (info.space) {
+ props[hastToReact[info.property] || info.property] = result
+ } else {
+ props[info.attribute] = result
+ }
}
-function listLoose(node) {
- const children = node.children
- let loose = node.spread
- let index = -1
+function parseStyle(value) {
+ const result = {}
- while (!loose && ++index < children.length) {
- loose = listItemLoose(children[index])
+ try {
+ style(value, iterator)
+ } catch (error) {
+ // Silent.
}
- return loose
+ return result
+
+ function iterator(name, v) {
+ const k = name.slice(0, 4) === '-ms-' ? `ms-${name.slice(4)}` : name
+ result[k.replace(/-([a-z])/g, styleReplacer)] = v
+ }
}
-function listItemLoose(node) {
- const spread = node.spread
- /* istanbul ignore next - spread is present from remark-parse, but maybe plugins don’t set it. */
- return spread === undefined || spread === null ? node.children.length > 1 : spread
+function styleReplacer(_, $1) {
+ return $1.toUpperCase()
}
-module.exports = astToReact
+function flattenPosition(pos) {
+ return [pos.start.line, ':', pos.start.column, '-', pos.end.line, ':', pos.end.column]
+ .map(String)
+ .join('')
+}
diff --git a/src/get-definitions.js b/src/get-definitions.js
deleted file mode 100644
index f1eae57e..00000000
--- a/src/get-definitions.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict'
-
-const visit = require('unist-util-visit')
-
-module.exports = function getDefinitions(tree, definitions = {}) {
- visit(tree, 'definition', (node) => {
- const identifier = node.identifier.toUpperCase()
- if (identifier in definitions) return
- definitions[identifier] = {href: node.url, title: node.title}
- })
- return definitions
-}
diff --git a/src/plugins/html-parser.js b/src/plugins/html-parser.js
deleted file mode 100644
index c7ca56ea..00000000
--- a/src/plugins/html-parser.js
+++ /dev/null
@@ -1,173 +0,0 @@
-/**
- * Full blown HTML parsing based on htmlparser2.
- * Pulls in a heavy set of dependencies and thus WILL bloat your bundle size.
- * You have been warned.
- **/
-const React = require('react')
-const visit = require('unist-util-visit')
-const HtmlToReact = require('html-to-react')
-
-const type = 'parsedHtml'
-const selfClosingRe = /^<(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)\s*\/?>$/i
-const startTagRe = /^<([a-z]+)\b/i
-const closingTagRe = /^<\/([a-z]+)\s*>$/
-
-const parser = new HtmlToReact.Parser()
-const processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React)
-
-const defaultConfig = {
- isValidNode: (node) => node.type !== 'script',
- processingInstructions: [
- {
- shouldProcessNode: () => true,
- processNode: processNodeDefinitions.processDefaultNode
- }
- ]
-}
-
-function findAndPull(open, matchingTag) {
- let i = open.length
- while (i--) {
- if (open[i].tag === matchingTag) {
- return open.splice(i, 1)[0]
- }
- }
-
- return false
-}
-
-function parseNode(node, config) {
- const match = node.value.trim().match(closingTagRe)
- if (match) {
- return {tag: match[1], opening: false, node}
- }
-
- const el = parser.parseWithInstructions(
- node.value,
- config.isValidNode,
- config.processingInstructions
- )
-
- /* istanbul ignore if - seems to never happen. Hiding it because we plan on
- * moving to rehype. */
- if (!el) {
- return false
- }
-
- const isMultiple = React.Children.count(el) > 1
- const isSelfClosing = !isMultiple && selfClosingRe.test(`<${el.type}>`)
- if (isMultiple || isSelfClosing) {
- return {
- type,
- position: node.position,
- node: el
- }
- }
-
- const startTagMatch = node.value.trim().match(startTagRe)
- /* istanbul ignore next - seems it’s always a start tag, hiding it because we
- * plan on moving to rehype. */
- const tag = startTagMatch ? startTagMatch[1] : el.type
- return {tag, opening: true, node, element: el}
-}
-
-function getSelfClosingTagName(node) {
- const match = node.value.match(selfClosingRe)
- return match ? match[1] : false
-}
-
-function parsedHtml(fromNode, toNode, parent) {
- const fromIndex = parent.children.indexOf(fromNode.node)
- const toIndex = parent.children.indexOf(toNode.node)
-
- const extracted = parent.children.splice(fromIndex, toIndex - fromIndex + 1)
- const children = extracted.slice(1, -1)
- return {
- type,
- children,
- tag: fromNode.tag,
- element: fromNode.element,
- value: fromNode.node.value,
- position: {
- start: fromNode.node.position.start,
- end: toNode.node.position.end,
- indent: []
- }
- }
-}
-
-module.exports = function getHtmlParserPlugin(options, _2) {
- if (_2 && typeof options.children !== 'undefined') {
- throw new Error(
- 'react-markdown: `html-parser` must be called before use - see https://github.com/remarkjs/react-markdown#parsing-html'
- )
- }
-
- const config = Object.assign({}, defaultConfig, options || {})
-
- return parseHtml
-
- function parseHtml(tree) {
- let open
- let currentParent
- visit(
- tree,
- 'html',
- (node, index, parent) => {
- if (currentParent !== parent) {
- open = []
- currentParent = parent
- }
-
- const selfClosing = getSelfClosingTagName(node)
- if (selfClosing) {
- parent.children[index] = {
- type: 'virtualHtml',
- tag: selfClosing,
- position: node.position
- }
- return
- }
-
- const current = parseNode(node, config)
- if (!current || current.type === type) {
- return
- }
-
- const matching = findAndPull(open, current.tag)
-
- if (matching) {
- parent.children.splice(index, 0, parsedHtml(current, matching, parent))
- } else if (!current.opening) {
- open.push(current)
- }
- },
- true // Iterate in reverse
- )
-
- // Find any leftover HTML elements and blindly replace them with a parsed version
- visit(tree, 'html', (node, index, parent) => {
- const element = parser.parseWithInstructions(
- node.value,
- config.isValidNode,
- config.processingInstructions
- )
-
- if (!element) {
- parent.children.splice(index, 1)
- return index
- }
-
- parent.children[index] = {
- type,
- element,
- value: node.value,
- position: node.position
- }
-
- return undefined
- })
-
- return tree
- }
-}
diff --git a/src/react-markdown.js b/src/react-markdown.js
index aafd2809..6f98fd9b 100644
--- a/src/react-markdown.js
+++ b/src/react-markdown.js
@@ -1,78 +1,75 @@
'use strict'
+const React = require('react')
const unified = require('unified')
const parse = require('remark-parse')
+const remarkRehype = require('remark-rehype')
const PropTypes = require('prop-types')
-const addListMetadata = require('mdast-add-list-metadata')
-const astToReact = require('./ast-to-react')
-const wrapTableRows = require('./remark-wrap-table-rows')
-const filterNodes = require('./remark-filter-nodes')
-const parseHtml = require('./remark-parse-html')
-const getDefinitions = require('./get-definitions')
+const convert = require('unist-util-is/convert')
+const html = require('property-information/html')
+const filter = require('./rehype-filter')
const uriTransformer = require('./uri-transformer')
-const defaultRenderers = require('./renderers')
+const childrenToReact = require('./ast-to-react.js').hastChildrenToReact
-let warningIssuedSource
-let warningIssuedEscapeHtml
+module.exports = ReactMarkdown
-function ReactMarkdown(props) {
- if ('source' in props && !warningIssuedSource) {
- console.warn('[react-markdown] Warning: please use `children` instead of `source`')
- warningIssuedSource = true
- }
+const root = convert('root')
+
+let warningIssued
- if ('escapeHtml' in props && !warningIssuedEscapeHtml) {
- console.warn(
- '[react-markdown] Warning: please use `allowDangerousHtml` instead of `escapeHtml`'
- )
- warningIssuedEscapeHtml = true
+function ReactMarkdown(options) {
+ if ('source' in options && !warningIssued) {
+ console.warn('[react-markdown] Warning: please use `children` instead of `source`')
+ warningIssued = true
}
const processor = unified()
.use(parse)
- .use(props.plugins || [])
- .use(wrapTableRows)
- .use(addListMetadata)
- .use(filterNodes, props)
- .use(parseHtml, props)
+ // TODO: deprecate `plugins` in v7.0.0.
+ .use(options.remarkPlugins || options.plugins || [])
+ .use(remarkRehype, {allowDangerousHtml: true})
+ .use(options.rehypePlugins || [])
+ .use(filter, options)
// eslint-disable-next-line no-sync
- const tree = processor.runSync(processor.parse(props.children || ''))
+ const hastNode = processor.runSync(processor.parse(options.children || ''))
+
+ if (!root(hastNode)) {
+ throw new TypeError('Expected a `root` node')
+ }
- return astToReact(
- tree,
- Object.assign({}, props, {
- renderers: Object.assign({}, defaultRenderers, props.renderers),
- definitions: getDefinitions(tree)
- })
+ let result = React.createElement(
+ React.Fragment,
+ {},
+ childrenToReact({options: options, schema: html, listDepth: 0}, hastNode)
)
-}
-ReactMarkdown.defaultProps = {
- transformLinkUri: uriTransformer
+ if (options.className) {
+ result = React.createElement('div', {className: options.className}, result)
+ }
+
+ return result
}
+ReactMarkdown.defaultProps = {transformLinkUri: uriTransformer}
+
ReactMarkdown.propTypes = {
className: PropTypes.string,
children: PropTypes.string,
sourcePos: PropTypes.bool,
rawSourcePos: PropTypes.bool,
- allowDangerousHtml: PropTypes.bool,
skipHtml: PropTypes.bool,
- allowNode: PropTypes.func,
- allowedTypes: PropTypes.arrayOf(PropTypes.string),
- disallowedTypes: PropTypes.arrayOf(PropTypes.string),
+ allowElement: PropTypes.func,
+ allowedElements: PropTypes.arrayOf(PropTypes.string),
+ disallowedElements: PropTypes.arrayOf(PropTypes.string),
transformLinkUri: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
linkTarget: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
transformImageUri: PropTypes.func,
htmlParser: PropTypes.func,
unwrapDisallowed: PropTypes.bool,
- renderers: PropTypes.object,
- plugins: PropTypes.array
+ components: PropTypes.object,
+ remarkPlugins: PropTypes.array,
+ rehypePlugins: PropTypes.array
}
-ReactMarkdown.types = Object.keys(defaultRenderers)
-ReactMarkdown.renderers = defaultRenderers
ReactMarkdown.uriTransformer = uriTransformer
-
-module.exports = ReactMarkdown
diff --git a/src/rehype-filter.js b/src/rehype-filter.js
new file mode 100644
index 00000000..679f0b83
--- /dev/null
+++ b/src/rehype-filter.js
@@ -0,0 +1,46 @@
+const visit = require('unist-util-visit')
+
+const splice = [].splice
+
+module.exports = rehypeFilter
+
+function rehypeFilter(options) {
+ if (options.allowedElements && options.disallowedElements) {
+ throw new TypeError('Only one of `allowedElements` and `disallowedElements` should be defined')
+ }
+
+ return options.allowedElements || options.disallowedElements || options.allowElement
+ ? transform
+ : undefined
+
+ function transform(tree) {
+ visit(tree, 'element', onelement)
+ }
+
+ function onelement(node, index, parent) {
+ let remove
+
+ if (options.allowedElements) {
+ remove = !options.allowedElements.includes(node.tagName)
+ } else if (options.disallowedElements) {
+ remove = options.disallowedElements.includes(node.tagName)
+ }
+
+ if (!remove && options.allowElement) {
+ remove = !options.allowElement(node, index, parent)
+ }
+
+ if (remove) {
+ let parameters = [index, 1]
+
+ if (options.unwrapDisallowed && node.children) {
+ parameters = parameters.concat(node.children)
+ }
+
+ splice.apply(parent.children, parameters)
+ return index
+ }
+
+ return undefined
+ }
+}
diff --git a/src/remark-filter-nodes.js b/src/remark-filter-nodes.js
deleted file mode 100644
index 47fd9d88..00000000
--- a/src/remark-filter-nodes.js
+++ /dev/null
@@ -1,48 +0,0 @@
-const visit = require('unist-util-visit')
-
-const splice = [].splice
-
-module.exports = remarkFilterNodes
-
-function remarkFilterNodes(options) {
- if (options.allowedTypes && options.disallowedTypes) {
- throw new Error('Only one of `allowedTypes` and `disallowedTypes` should be defined')
- }
-
- return options.allowedTypes || options.disallowedTypes || options.allowNode
- ? transform
- : undefined
-
- function transform(tree) {
- visit(tree, onnode)
- }
-
- function onnode(node, index, parent) {
- let remove
-
- if (parent) {
- if (options.allowedTypes) {
- remove = !options.allowedTypes.includes(node.type)
- } else if (options.disallowedTypes) {
- remove = options.disallowedTypes.includes(node.type)
- }
-
- if (!remove && options.allowNode) {
- remove = !options.allowNode(node, index, parent)
- }
- }
-
- if (remove) {
- let parameters = [index, 1]
-
- if (options.unwrapDisallowed && node.children) {
- parameters = parameters.concat(node.children)
- }
-
- splice.apply(parent.children, parameters)
- return index
- }
-
- return undefined
- }
-}
diff --git a/src/remark-parse-html.js b/src/remark-parse-html.js
deleted file mode 100644
index 297bd74a..00000000
--- a/src/remark-parse-html.js
+++ /dev/null
@@ -1,102 +0,0 @@
-'use strict'
-
-const visit = require('unist-util-visit')
-
-const selfClosingRe = /^<(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)\s*\/?>$/i
-const simpleTagRe = /^<(\/?)([a-z]+)\s*>$/
-
-module.exports = remarkParseHtml
-
-function remarkParseHtml(options) {
- if (options.skipHtml || !options.allowDangerousHtml) {
- return undefined
- }
-
- return options.htmlParser || naiveHtml
-}
-
-/**
- * Naive, simple plugin to match inline nodes without attributes
- * This allows say foo, but not foo
- * For proper HTML support, you'll want a different plugin
- **/
-function naiveHtml(tree) {
- let open
- let currentParent
-
- visit(
- tree,
- 'html',
- (node, index, parent) => {
- if (currentParent !== parent) {
- open = []
- currentParent = parent
- }
-
- const selfClosing = getSelfClosing(node)
- if (selfClosing) {
- parent.children[index] = {
- type: 'virtualHtml',
- tag: selfClosing,
- position: node.position
- }
- return
- }
-
- const current = getSimpleTag(node, parent)
- if (!current) {
- return
- }
-
- const matching = findAndPull(open, current.tag)
-
- if (matching) {
- parent.children.splice(index, 0, virtual(current, matching, parent))
- } else if (!current.opening) {
- open.push(current)
- }
- },
- true // Iterate in reverse
- )
-
- return tree
-}
-
-function findAndPull(open, matchingTag) {
- let i = open.length
- while (i--) {
- if (open[i].tag === matchingTag) {
- return open.splice(i, 1)[0]
- }
- }
-
- return false
-}
-
-function getSimpleTag(node, parent) {
- const match = node.value.match(simpleTagRe)
- return match ? {tag: match[2], opening: !match[1], node} : false
-}
-
-function getSelfClosing(node) {
- const match = node.value.match(selfClosingRe)
- return match ? match[1] : false
-}
-
-function virtual(fromNode, toNode, parent) {
- const fromIndex = parent.children.indexOf(fromNode.node)
- const toIndex = parent.children.indexOf(toNode.node)
-
- const extracted = parent.children.splice(fromIndex, toIndex - fromIndex + 1)
- const children = extracted.slice(1, -1)
- return {
- type: 'virtualHtml',
- children,
- tag: fromNode.tag,
- position: {
- start: fromNode.node.position.start,
- end: toNode.node.position.end,
- indent: []
- }
- }
-}
diff --git a/src/remark-wrap-table-rows.js b/src/remark-wrap-table-rows.js
deleted file mode 100644
index b67314c7..00000000
--- a/src/remark-wrap-table-rows.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict'
-
-const visit = require('unist-util-visit')
-
-module.exports = remarkWrapTableRows
-
-function remarkWrapTableRows() {
- return transform
-}
-
-function transform(tree) {
- visit(tree, 'table', ontable)
-}
-
-function ontable(table) {
- const children = table.children
- table.children = [
- {
- type: 'tableHead',
- align: table.align,
- children: [children[0]],
- position: children[0].position
- }
- ]
- if (children.length > 1) {
- table.children.push({
- type: 'tableBody',
- align: table.align,
- children: children.slice(1),
- position: {
- start: children[1].position.start,
- end: children[children.length - 1].position.end
- }
- })
- }
-}
diff --git a/src/renderers.js b/src/renderers.js
deleted file mode 100644
index 878c71be..00000000
--- a/src/renderers.js
+++ /dev/null
@@ -1,131 +0,0 @@
-/* eslint-disable react/prop-types, react/no-multi-comp */
-'use strict'
-
-const React = require('react')
-
-module.exports = {
- break: 'br',
- paragraph: 'p',
- emphasis: 'em',
- strong: 'strong',
- thematicBreak: 'hr',
- blockquote: 'blockquote',
- delete: 'del',
- link: 'a',
- image: 'img',
- linkReference: 'a',
- imageReference: 'img',
- table: SimpleRenderer.bind(null, 'table'),
- tableHead: SimpleRenderer.bind(null, 'thead'),
- tableBody: SimpleRenderer.bind(null, 'tbody'),
- tableRow: SimpleRenderer.bind(null, 'tr'),
- tableCell: TableCell,
-
- root: Root,
- text: TextRenderer,
- list: List,
- listItem: ListItem,
- definition: NullRenderer,
- heading: Heading,
- inlineCode: InlineCode,
- code: CodeBlock,
- html: Html,
- virtualHtml: VirtualHtml,
- parsedHtml: ParsedHtml
-}
-
-function TextRenderer(props) {
- /* istanbul ignore next - a text node w/o a value could be injected by plugins */
- return props.children || ''
-}
-
-function Root(props) {
- const {className} = props
- return className
- ? React.createElement('div', {className}, props.children)
- : React.createElement(React.Fragment, {}, props.children)
-}
-
-function SimpleRenderer(tag, props) {
- return React.createElement(tag, getCoreProps(props), props.children)
-}
-
-function TableCell(props) {
- const style = props.align ? {textAlign: props.align} : undefined
- const coreProps = getCoreProps(props)
- return React.createElement(
- props.isHeader ? 'th' : 'td',
- Object.assign({style}, coreProps),
- props.children
- )
-}
-
-function Heading(props) {
- return React.createElement(`h${props.level}`, getCoreProps(props), props.children)
-}
-
-function List(props) {
- const attrs = getCoreProps(props)
- if (props.start !== null && props.start !== 1 && props.start !== undefined) {
- attrs.start = props.start.toString()
- }
-
- return React.createElement(props.ordered ? 'ol' : 'ul', attrs, props.children)
-}
-
-function ListItem(props) {
- let checkbox = null
- if (props.checked !== null && props.checked !== undefined) {
- const checked = props.checked
- checkbox = React.createElement('input', {type: 'checkbox', checked, readOnly: true})
- }
-
- return React.createElement('li', getCoreProps(props), checkbox, props.children)
-}
-
-function CodeBlock(props) {
- const className = props.language && `language-${props.language}`
- const code = React.createElement('code', className ? {className: className} : null, props.value)
- return React.createElement('pre', getCoreProps(props), code)
-}
-
-function InlineCode(props) {
- return React.createElement('code', getCoreProps(props), props.children)
-}
-
-function Html(props) {
- if (props.skipHtml) {
- return null
- }
-
- if (!props.allowDangerousHtml) {
- return props.value
- }
-
- // Otherwise, if there still is an `html` node, that means the naive HTML
- // implementation was used, but it couldn’t handle this one.
- return React.createElement(props.isBlock ? 'div' : 'span', {
- dangerouslySetInnerHTML: {__html: props.value}
- })
-}
-
-function ParsedHtml(props) {
- /* To do: `React.cloneElement` is slow, is it really needed? */
- return props['data-sourcepos']
- ? React.cloneElement(props.element, {'data-sourcepos': props['data-sourcepos']})
- : props.element
-}
-
-function VirtualHtml(props) {
- return React.createElement(props.tag, getCoreProps(props), props.children)
-}
-
-function NullRenderer() {
- return null
-}
-
-function getCoreProps(props) {
- const source = props['data-sourcepos']
- /* istanbul ignore next - nodes from plugins w/o position */
- return source ? {'data-sourcepos': source} : {}
-}
diff --git a/src/with-html.js b/src/with-html.js
deleted file mode 100644
index 63295bc3..00000000
--- a/src/with-html.js
+++ /dev/null
@@ -1,16 +0,0 @@
-'use strict'
-
-const ReactMarkdown = require('./react-markdown')
-const htmlParser = require('./plugins/html-parser')
-
-function ReactMarkdownWithHtml(props) {
- return ReactMarkdown(Object.assign({}, props, {htmlParser: htmlParser()}))
-}
-
-ReactMarkdownWithHtml.defaultProps = ReactMarkdown.defaultProps
-ReactMarkdownWithHtml.propTypes = ReactMarkdown.propTypes
-ReactMarkdownWithHtml.types = ReactMarkdown.types
-ReactMarkdownWithHtml.renderers = ReactMarkdown.renderers
-ReactMarkdownWithHtml.uriTransformer = ReactMarkdown.uriTransformer
-
-module.exports = ReactMarkdownWithHtml
diff --git a/test/__snapshots__/react-markdown.test.js.snap b/test/__snapshots__/react-markdown.test.js.snap
index 912860b9..25098d99 100644
--- a/test/__snapshots__/react-markdown.test.js.snap
+++ b/test/__snapshots__/react-markdown.test.js.snap
@@ -23,80 +23,138 @@ Array [
h1 Heading
,
+ "
+",
h2 Heading
,
+ "
+",
h3 Heading
,
+ "
+",
h4 Heading
,
+ "
+",
h5 Heading
,
+ "
+",
h6 Heading
,
+ "
+",
Horizontal Rules
,
+ "
+",
,
+ "
+",
,
+ "
+",
,
+ "
+",
Emphasis
,
+ "
+",
Strikethrough
Blockquotes
,
+ "
+",
+
+
,
+ "
+",
+
+
+
+
+
+
+
+
Lists
,
+ "
+",
+
+
,
+ "
+",
@@ -111,120 +169,220 @@ Array [
*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
,
+ "
+",
+
+
,
+ "
+",
1.
+
+
,
+ "
+",
+
+
,
+ "
+",
Code
,
+ "
+",
code
,
+ "
+",
// Some comments
line 1 of code
line 2 of code
line 3 of code
+
,
+ "
+",
Sample text here...
+
,
+ "
+",
Tables
,
+ "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+",
,
+ "
+",
@@ -277,9 +461,34 @@ console.log(foo(5));
,
+ "
+",
@@ -366,9 +575,13 @@ console.log(foo(5));
Links
,
+ "
+",
,
+ "
+",
,
+ "
+",
,
+ "
+",
,
+ "
+",
Images
,
+ "
+",
Hard breaks
,
+ "
+",
- can be useful too.
+
+can be useful too.
HTML entities
,
+ "
+",
HTML
,
+ "
+",
- 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
+ width={640}
+ >
+
+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
-
- html-parser
-
- plugin, it can now properly handle HTML! Which is pretty sweet.
-
+ rehype-raw
+
,
+ " plugin, it can now properly handle HTML! Which is pretty sweet.",
+ ,
+ "
+",
,
,
+ "
+",
h1 Heading
,
+ "
+",
h2 Heading
,
+ "
+",
h3 Heading
,
+ "
+",
h4 Heading
,
+ "
+",
h5 Heading
,
+ "
+",
h6 Heading
,
+ "
+",
Horizontal Rules
,
+ "
+",
,
+ "
+",
,
+ "
+",
,
+ "
+",
Emphasis
,
+ "
+",
Strikethrough
Blockquotes
,
+ "
+",
+
+
,
+ "
+",
+
+
+
+
+
+
+
+
Lists
,
+ "
+",
+
+
,
+ "
+",
@@ -585,120 +889,220 @@ Array [
*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
,
+ "
+",
+
+
,
+ "
+",
1.
+
+
,
+ "
+",
+
+
,
+ "
+",
Code
,
+ "
+",
code
,
+ "
+",
// Some comments
line 1 of code
line 2 of code
line 3 of code
+
,
+ "
+",
Sample text here...
+
,
+ "
+",
Tables
,
+ "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+",
,
+ "
+",
@@ -751,9 +1181,34 @@ console.log(foo(5));
,
+ "
+",
@@ -840,9 +1295,13 @@ console.log(foo(5));
Links
,
+ "
+",
,
+ "
+",
,
+ "
+",
,
+ "
+",
,
+ "
+",
Images
,
+ "
+",
Hard breaks
,
+ "
+",
- can be useful too.
+
+can be useful too.
HTML entities
,
+ "
+",
HTML
,
+ "
+",
- , 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
- ",
- }
- }
- />
- html-parser
-
- plugin, it can now properly handle HTML! Which is pretty sweet.
-
",
- }
- }
- />,
+ className="attrs"
+ >
+ 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.",
+ ,
+ "
+",
+
,
+
,
+ "
+",
@@ -1018,6 +1477,8 @@ Array [
Much fun
,
+ "
+",
,
+ "
+",
+
+
,
+ "
+",
can be tricky, too
,
+ "
+",
-
-
-`;
-
-exports[`should be able to render a table with multiple children with HTML parser plugin 1`] = `
-
-
-
-
- I am having so much fun
-
-
-
-
-`;
-
-exports[`should be able to render basic inline html without containers 1`] = `
-
-
-
-
-
- Title
-
-
-
-
-
- I am having so much fun
-
-
- renderToStaticMarkup()
-
- , already
-
+ renderToStaticMarkup()
+
+ , already
",
+ "
+",
Foo
",
+ "
+",
Foo
+
+
`;
@@ -1475,7 +1795,8 @@ exports[`should handle bold/strong text 1`] = `
exports[`should handle code blocks by indentation 1`] = `
"
"
+</footer>
+"
`;
exports[`should handle code tags with language specification 1`] = `
@@ -1485,6 +1806,7 @@ exports[`should handle code tags with language specification 1`] = `
>
var foo = require('bar');
foo();
+
`;
@@ -1494,6 +1816,7 @@ exports[`should handle code tags without any language specification 1`] = `
<footer class="footer">
© 2014 Foo Bar
-</footer>
var foo = require('bar');
foo();
+
`;
@@ -1516,61 +1839,17 @@ Array [
,
+ "
+",
-
-Foo
-
-
-
-
,
-
-
-
-
-
-
-
- Foo
-
-
-
-
+
+
`;
exports[`should handle loose, unordered lists with sublists 1`] = `
+
+
`;
@@ -1730,6 +2018,8 @@ exports[`should handle multiline paragraphs properly (softbreak, paragraphs) 1`]
React is awesome
And so is markdown
+
+
+
+
+
+
Combining = epic
Combining = epic
, @@ -1748,84 +2040,146 @@ And so is markdown exports[`should handle nested blockquotes 1`] = `+ +`; exports[`should handle ordered lists 1`] = `+ ++ +Lots of ex-Mootoolers on the React team
+ +Totally didn't know that.
+ ++ ++ +There's a reason why it turned out so awesome
+ +Haha I guess you're right!
+ +
foo
+ +root
+ +Foo
, + " +",Bar
, + " +",Baz
, ] `; -exports[`should pass on raw source position to non-tag renderers if rawSourcePos option is enabled 1`] = ` +exports[`should pass on raw source position to non-tag components if rawSourcePos option is enabled 1`] = ` Object { "end": Object { "column": 6, @@ -1999,7 +2305,7 @@ Object { } `; -exports[`should pass on raw source position to non-tag renderers if rawSourcePos option is enabled 2`] = ` +exports[`should pass on raw source position to non-tag components if rawSourcePos option is enabled 2`] = ` Array [, + " +",
Bar
@@ -2023,18 +2333,36 @@ exports[`should render image references 1`] = `" Checkout out this ninja: Stuff were changed in 1.1.4. Check out the changelog for reference. User is writing a table by hand User is writing a table by hand Languages are fun, right? Languages are fun, right?
"`;
+exports[`should render partial tables 1`] = `
+"Test Test
+
+
"
+`;
exports[`should render table of contents plugin 1`] = `
Array [
+
+
+Test
+Test
+
Header
,
+ "
+",
Table of Contents
,
+ "
+",
,
+ "
+",
First Section
,
+ "
+",
Second Section
,
+ "
+",
Subsection
,
+ "
+",
Third Section
,
]
`;
-exports[`should render tables 1`] = `"
"`;
-
-exports[`should sanititize language strings in code blocks 1`] = `
-ID English Norwegian Italian 1 one en uno 2 two to due 3 three tre tre
-
+exports[`should render tables 1`] = `
+"
- woop
-
-
+
+
"
`;
exports[`should set source position attributes if sourcePos option is enabled 1`] = `
@@ -2105,9 +2485,13 @@ Array [
>
Foo
+
+
+
+ID
+English
+Norwegian
+Italian
+
+
+1
+one
+en
+uno
+
+
+2
+two
+to
+due
+
+
+
+3
+three
+tre
+tre
+
@@ -2116,22 +2500,15 @@ Array [ ] `; -exports[`should skip html blocks if skipHtml prop is set (with HTML parser plugin) 1`] = ` -Array [ -
- This is a regular paragraph. -
, -- This is another regular paragraph. -
, -] -`; - exports[`should skip html blocks if skipHtml prop is set 1`] = ` Array [This is a regular paragraph.
, + " +", + " +",This is another regular paragraph.
, @@ -2151,13 +2528,28 @@ Array [Paragraph
, + " +",Foo
, @@ -2166,20 +2558,55 @@ Array [ exports[`should skip nodes that are not defined as allowed 1`] = ` Array [ + " +",Paragraph
, + " +", + " +",
+
+ woop
+
+
+
+`;
+
+exports[`should support math 1`] = `
+"Lift(L) can be determined by Lift Coefficient (CL) like the following equation.
+Espen @@ -2207,32 +2634,56 @@ exports[`should use target attribute for links if specified 1`] = ` exports[`supports checkbox lists 1`] = ` Array [ -
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 html-parser
plugin, it can now properly handle HTML! Which is pretty sweet.
+tags, however. And with the rehype-raw
plugin, it can now properly handle HTML! Which is pretty sweet.
b
') - expect(console.warn).toHaveBeenCalledWith( - '[react-markdown] Warning: please use `allowDangerousHtml` instead of `escapeHtml`' - ) - console.warn = warn -}) - test('uses passed classname for root component', () => { const component = renderer.create(a\n
\nb\n
\nc
a | \n
---|
b | \n
c | \n
I am having so much fun |
a | \n
---|
b | \n
c | \n
Title |
---|
I am having so much fun |
so much
fun'
- const config = {
- isValidNode: () => true,
- processingInstructions: [
- {
- shouldProcessNode: ({name}) => name === 'code',
- // eslint-disable-next-line react/display-name
- processNode: (node, children) => {children}
- }
- ]
+test('should pass `level: number` to `h1`, `h2`, ...', () => {
+ const input = '#\n##\n###'
+ function heading({node, level, ...props}) {
+ return React.createElement(`h${level}`, props)
}
- const component = renderer.create(
- Foo | \n