Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

various fixes #620

Merged
merged 4 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/afraid-lobsters-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'markdown-to-jsx': patch
---

Fix false detection of tables in some scenarios.
5 changes: 5 additions & 0 deletions .changeset/chatty-waves-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'markdown-to-jsx': patch
---

Handle `class` attribute from arbitrary HTML properly to avoid React warnings.
5 changes: 5 additions & 0 deletions .changeset/chilled-kings-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'markdown-to-jsx': patch
---

Fenced code blocks are now tolerant to a missing closing sequence; this improves use in LLM scenarios where the code block markdown is being streamed into the editor in chunks.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The most lightweight, customizable React markdown component.
- [Getting the smallest possible bundle size](#getting-the-smallest-possible-bundle-size)
- [Usage with Preact](#usage-with-preact)
- [Gotchas](#gotchas)
- [Passing props to stringified React components](#passing-props-to-stringified-react-components)
- [Significant indentation inside arbitrary HTML](#significant-indentation-inside-arbitrary-html)
- [Code blocks](#code-blocks)
- [Using The Compiler Directly](#using-the-compiler-directly)
Expand Down Expand Up @@ -621,6 +622,52 @@ Everything will work just fine! Simply [Alias `react` to `preact/compat`](https:

## Gotchas

### Passing props to stringified React components

Using the [`options.overrides`](#optionsoverrides---rendering-arbitrary-react-components) functionality to render React components, props are passed into the component in stringifed form. It is up to you to parse the string to make use of the data.

```tsx
const Table: React.FC<
JSX.IntrinsicElements['table'] & {
columns: string
dataSource: string
}
> = ({ columns, dataSource, ...props }) => {
const parsedColumns = JSON.parse(columns)
const parsedData = JSON.parse(dataSource)

return (
<div {...props}>
<h1>Columns</h1>
{parsedColumns.map(column => (
<span key={column.key}>{column.title}</span>
))}

<h2>Data</h2>
{parsedData.map(datum => (
<span key={datum.key}>{datum.Month}</span>
))}
</div>
)
}

/**
* Example HTML in markdown:
*
* <Table
* columns={[{ title: 'Month', dataIndex: 'Month', key: 'Month' }]}
* dataSource={[
* {
* Month: '2024-09-01',
* 'Forecasted Revenue': '$3,137,678.85',
* 'Forecasted Expenses': '$2,036,660.28',
* key: 0,
* },
* ]}
* />
*/
```

### Significant indentation inside arbitrary HTML

People usually write HTML like this:
Expand Down
38 changes: 34 additions & 4 deletions index.compiler.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,16 @@ describe('headings', () => {
</h1>
`)
})

it('#595 regression - handle pipe character inside header', () => {
render(compiler('# Heading | text'))

expect(root.innerHTML).toMatchInlineSnapshot(`
<h1 id="heading--text">
Heading | text
</h1>
`)
})
})

describe('images', () => {
Expand Down Expand Up @@ -1851,7 +1861,7 @@ describe('GFM tables', () => {
it('should handle a basic table', () => {
render(
compiler(theredoc`
foo|bar
|foo|bar|
---|---
1 |2
`)
Expand Down Expand Up @@ -1886,7 +1896,7 @@ describe('GFM tables', () => {
it('should handle a table with aligned columns', () => {
render(
compiler(theredoc`
foo|bar|baz
|foo|bar|baz|
--:|:---:|:--
1|2|3
`)
Expand Down Expand Up @@ -2460,10 +2470,18 @@ describe('GFM tables', () => {

describe('arbitrary HTML', () => {
it('preserves the HTML given', () => {
render(compiler('<dd>Hello</dd>'))
const ast = compiler('<dd class="foo">Hello</dd>')
expect(ast).toMatchInlineSnapshot(`
<dd
className="foo"
>
Hello
</dd>
`)

render(ast)
expect(root.innerHTML).toMatchInlineSnapshot(`
<dd>
<dd class="foo">
Hello
</dd>
`)
Expand Down Expand Up @@ -3801,6 +3819,18 @@ Yeah boi
</pre>
`)
})

it('regression 602 - should treat anything following ``` as code until the closing pair', () => {
render(compiler('```\nfoo'))

expect(root.innerHTML).toMatchInlineSnapshot(`
<pre>
<code>
foo
</code>
</pre>
`)
})
})

describe('indented code blocks', () => {
Expand Down
19 changes: 4 additions & 15 deletions index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ const ATTRIBUTE_TO_JSX_PROP_MAP = [
'cellPadding',
'cellSpacing',
'charSet',
'className',
'classId',
'colSpan',
'contentEditable',
Expand Down Expand Up @@ -126,7 +125,7 @@ const ATTRIBUTE_TO_JSX_PROP_MAP = [
obj[x.toLowerCase()] = x
return obj
},
{ for: 'htmlFor' }
{ class: 'className', for: 'htmlFor' }
)

const namedCodesToUnicode = {
Expand Down Expand Up @@ -184,7 +183,7 @@ const BLOCKQUOTE_TRIM_LEFT_MULTILINE_R = /^ *> ?/gm
const BREAK_LINE_R = /^ {2,}\n/
const BREAK_THEMATIC_R = /^(?:( *[-*_])){3,} *(?:\n *)+\n/
const CODE_BLOCK_FENCED_R =
/^\s*(`{3,}|~{3,}) *(\S+)?([^\n]*?)?\n([\s\S]+?)\s*\1 *(?:\n *)*\n?/
/^(?: {1,3})?(`{3,}|~{3,}) *(\S+)? *([^\n]*?)?\n([\s\S]*?)(?:\1\n?|$)/
const CODE_BLOCK_R = /^(?: {4}[^\n]+\n*)+(?:\n *)+\n?/
const CODE_INLINE_R = /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/
const CONSECUTIVE_NEWLINE_R = /^(?:\n *)*\n/
Expand Down Expand Up @@ -274,8 +273,7 @@ const LINK_AUTOLINK_BARE_URL_R = /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/
const LINK_AUTOLINK_MAILTO_R = /^<([^ >]+@[^ >]+)>/
const LINK_AUTOLINK_R = /^<([^ >]+:\/[^ >]+)>/
const CAPTURE_LETTER_AFTER_HYPHEN = /-([a-z])?/gi
const NP_TABLE_R =
/^(.*\|.*)\n(?: *(\|? *[-:]+ *\|[-| :]*)\n((?:.*\|.*\n)*))?\n?/
const NP_TABLE_R = /^(\|.*)\n(?: *(\|? *[-:]+ *\|[-| :]*)\n((?:.*\|.*\n)*))?\n?/
const PARAGRAPH_R = /^[^\n]+(?: \n|\n{2,})/
const REFERENCE_IMAGE_OR_LINK = /^\[([^\]]*)\]:\s+<?([^\s>]+)>?\s*("([^"]*)")?/
const REFERENCE_IMAGE_R = /^!\[([^\]]*)\] ?\[([^\]]*)\]/
Expand Down Expand Up @@ -927,20 +925,11 @@ function anyScopeRegex(regex: RegExp) {
}
}

function matchParagraph(
source: string,
state: MarkdownToJSX.State,
prevCapturedString?: string
) {
function matchParagraph(source: string, state: MarkdownToJSX.State) {
if (state.inline || state.simple) {
return null
}

if (prevCapturedString && !prevCapturedString.endsWith('\n')) {
// don't match continuation of a line
return null
}

let match = ''

source.split('\n').every(line => {
Expand Down
Loading