Skip to content

Commit

Permalink
fix(markdown): fix rendering and parsing of tasklists in markdown. Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
whawker committed May 31, 2022
1 parent 483551e commit 6f1b591
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-zebras-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@remirror/extension-markdown': patch
---

Fix rendering and parsing of tasklists in markdown
Original file line number Diff line number Diff line change
@@ -1,5 +1,91 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`commands.insertMarkdown can insert lists 1`] = `
<div
aria-label=""
aria-multiline="true"
class="ProseMirror remirror-editor"
contenteditable="true"
role="textbox"
translate="no"
>
<p>
Bullet list
</p>
<ul>
<li>
<p>
Item 1
</p>
</li>
<li>
<p>
Item 2
</p>
</li>
</ul>
<p>
Ordered list
</p>
<ol>
<li>
<p>
Item 1
</p>
</li>
<li>
<p>
Item 2
</p>
</li>
</ol>
<p>
Task list
</p>
<ul
data-task-list=""
>
<li
class="remirror-list-item-with-custom-mark"
data-checked=""
data-task-list-item=""
>
<label
class="remirror-list-item-marker-container"
>
<input
class="remirror-list-item-checkbox"
type="checkbox"
/>
</label>
<div>
<p>
Item 1
</p>
</div>
</li>
<li
class="remirror-list-item-with-custom-mark"
data-task-list-item=""
>
<label
class="remirror-list-item-marker-container"
>
<input
class="remirror-list-item-checkbox"
type="checkbox"
/>
</label>
<div>
<p>
Item 2
</p>
</div>
</li>
</ul>
</div>
`;

exports[`commands.insertMarkdown can insert marks 1`] = `
<div
aria-label=""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ const casesArray = [
['strike', '<strike>Lorem ipsum</strike>', '~Lorem ipsum~'],
['s', '<s>Lorem ipsum</s>', '~Lorem ipsum~'],
['del', '<del>Lorem ipsum</del>', '~Lorem ipsum~'],
['unchecked inputs', '<ul><li><input type="checkbox">Check Me!</li></ul>', '* [ ] Check Me!'],
[
'unchecked inputs',
'<ul data-task-list><li data-task-list-item><p>Check Me!</p></li></ul>',
'- [ ] Check Me!',
],
[
'checked inputs',
'<ul><li><input type="checkbox" checked="">Checked!</li></ul>',
'* [x] Checked!',
'<ul data-task-list><li data-task-list-item data-checked><p>Checked!</p></li></ul>',
'- [x] Checked!',
],
[
'basic table',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { extensionValidityTest, renderEditor } from 'jest-remirror';
import { BoldExtension, HeadingExtension, ItalicExtension } from 'remirror/extensions';
import {
BoldExtension,
BulletListExtension,
HeadingExtension,
ItalicExtension,
OrderedListExtension,
TaskListExtension,
} from 'remirror/extensions';

import { MarkdownExtension } from '../';

Expand Down Expand Up @@ -69,6 +76,67 @@ describe('commands.insertMarkdown', () => {
expect(editor.dom).toMatchSnapshot();
});

it('can insert lists', () => {
const editor = renderEditor([
new BulletListExtension(),
new OrderedListExtension(),
new TaskListExtension(),
new MarkdownExtension(),
]);
const { doc, p, bulletList: ul, orderedList: ol, taskList, listItem: li } = editor.nodes;
const { taskListItem } = editor.attributeNodes;

editor.add(doc(p('<cursor>')));

// Intentionally the same as the output from getMarkdown helper
const tabSpace = ` `;
const markdown = `Bullet list
* Item 1
${tabSpace}
* Item 2
${tabSpace}
Ordered list
1. Item 1
${tabSpace}
2. Item 2
${tabSpace}
Task list
- [x] Item 1
- [ ] Item 2`;

editor.chain.insertMarkdown(markdown).selectText('end').run();
const expected = doc(
//
p('Bullet list'),
ul(
//
li(p('Item 1')),
li(p('Item 2')),
),
p('Ordered list'),
ol(
//
li(p('Item 1')),
li(p('Item 2')),
),
p('Task list'),
taskList(
//
taskListItem({ checked: true })(p('Item 1')),
taskListItem({ checked: false })(p('Item 2')),
),
);

expect(editor.state.doc).toEqualProsemirrorNode(expected);
expect(editor.dom).toMatchSnapshot();
});

it('does not replace marks', () => {
const editor = renderEditor([new BoldExtension(), new MarkdownExtension()]);
const { doc, p } = editor.nodes;
Expand Down Expand Up @@ -96,3 +164,63 @@ describe('commands.insertMarkdown', () => {
expect(editor.dom).toMatchSnapshot();
});
});

describe('helpers.getMarkdown', () => {
it('returns the expected markdown content for lists', () => {
const editor = renderEditor([
new BulletListExtension(),
new OrderedListExtension(),
new TaskListExtension(),
new MarkdownExtension(),
]);
const { doc, p, bulletList: ul, orderedList: ol, taskList, listItem: li } = editor.nodes;
const { taskListItem } = editor.attributeNodes;

editor.add(
doc(
//
p('Bullet list'),
ul(
//
li(p('Item 1')),
li(p('Item 2')),
),
p('Ordered list'),
ol(
//
li(p('Item 1')),
li(p('Item 2')),
),
p('Task list'),
taskList(
//
taskListItem({ checked: true })(p('Item 1')),
taskListItem({ checked: false })(p('Item 2')),
),
),
);

const tabSpace = ` `;
const expectedMarkdown = `Bullet list
* Item 1
${tabSpace}
* Item 2
${tabSpace}
Ordered list
1. Item 1
${tabSpace}
2. Item 2
${tabSpace}
Task list
- [x] Item 1
- [ ] Item 2`;

expect(editor.helpers.getMarkdown()).toEqual(expectedMarkdown);
});
});
7 changes: 4 additions & 3 deletions packages/remirror__extension-markdown/src/html-to-markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ function cell(content: string, node: Node) {
const turndownService = new TurndownService({ codeBlockStyle: 'fenced', headingStyle: 'atx' })
.addRule('taskListItems', {
filter: (node) => {
return (node as HTMLInputElement).type === 'checkbox' && node.parentNode?.nodeName === 'LI';
return node.nodeName === 'LI' && node.hasAttribute('data-task-list-item');
},
replacement: (_, node) => {
return `${(node as HTMLInputElement).checked ? '[x]' : '[ ]'} `;
replacement: (content, node) => {
const isChecked = (node as HTMLElement).hasAttribute('data-checked');
return `- ${isChecked ? '[x]' : '[ ]'} ${content.trimStart()}`;
},
})
.addRule('tableCell', {
Expand Down
22 changes: 22 additions & 0 deletions packages/remirror__extension-markdown/src/markdown-to-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@

import { marked } from 'marked';

marked.use({
renderer: {
list(body: string, isOrdered: boolean, start: number): string {
if (isOrdered) {
const startAttr = start !== 1 ? `start="${start}"` : '';
return `<ol ${startAttr}>\n${body}</ol>\n`;
}

const taskListAttr = body.startsWith('<li data-task-list-item ') ? 'data-task-list' : '';
return `<ul ${taskListAttr}>\n${body}</ul>\n`;
},
listitem(text: string, isTask: boolean, isChecked: boolean): string {
if (!isTask) {
return `<li>${text}</li>\n`;
}

const checkedAttr = isChecked ? 'data-checked' : '';
return `<li data-task-list-item ${checkedAttr}>${text}</li>\n`;
},
},
});

/**
* Converts the provided markdown to HTML.
*/
Expand Down

1 comment on commit 6f1b591

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Published on https://remirror.io as production
🚀 Deployed on https://629621d1bb68e755724b7d09--remirror.netlify.app

Please sign in to comment.