Skip to content

Commit f6959e2

Browse files
hip3rdemshy
andauthored
fix(markdown): copying html into markdown (#7290)
* fix(markdown): copying html into markdown //issues/7233 * refactor: sync markdown html parsing script with original * fix: lint --------- Co-authored-by: Anze Demsar <anze.demsar@p-m.si>
1 parent d9655ea commit f6959e2

File tree

4 files changed

+105
-4
lines changed

4 files changed

+105
-4
lines changed

packages/decap-cms-widget-markdown/src/MarkdownControl/VisualEditor.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { markdownToSlate, slateToMarkdown } from '../serializers';
2626
import withShortcodes from './plugins/shortcodes/withShortcodes';
2727
import insertShortcode from './plugins/shortcodes/insertShortcode';
2828
import defaultEmptyBlock from './plugins/blocks/defaultEmptyBlock';
29+
import withHtml from './plugins/html/withHtml';
2930

3031
function visualEditorStyles({ minimal }) {
3132
return `
@@ -97,7 +98,9 @@ function Editor(props) {
9798

9899
const editor = useMemo(
99100
() =>
100-
withReact(withHistory(withShortcodes(withBlocks(withLists(withInlines(createEditor())))))),
101+
withHtml(
102+
withReact(withHistory(withShortcodes(withBlocks(withLists(withInlines(createEditor())))))),
103+
),
101104
[],
102105
);
103106

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// source: https://github.com/ianstormtaylor/slate/blob/main/site/examples/ts/paste-html.tsx
2+
import { jsx } from 'slate-hyperscript';
3+
import { Transforms } from 'slate';
4+
5+
const ELEMENT_TAGS = {
6+
A: el => ({ type: 'link', url: el.getAttribute('href') }),
7+
BLOCKQUOTE: () => ({ type: 'quote' }),
8+
H1: () => ({ type: 'heading-one' }),
9+
H2: () => ({ type: 'heading-two' }),
10+
H3: () => ({ type: 'heading-three' }),
11+
H4: () => ({ type: 'heading-four' }),
12+
H5: () => ({ type: 'heading-five' }),
13+
H6: () => ({ type: 'heading-six' }),
14+
IMG: el => ({ type: 'image', url: el.getAttribute('src') }),
15+
LI: () => ({ type: 'list-item' }),
16+
OL: () => ({ type: 'numbered-list' }),
17+
P: () => ({ type: 'paragraph' }),
18+
PRE: () => ({ type: 'code' }),
19+
UL: () => ({ type: 'bulleted-list' }),
20+
};
21+
22+
// COMPAT: `B` is omitted here because Google Docs uses `<b>` in weird ways.
23+
const TEXT_TAGS = {
24+
CODE: () => ({ code: true }),
25+
DEL: () => ({ strikethrough: true }),
26+
EM: () => ({ italic: true }),
27+
I: () => ({ italic: true }),
28+
S: () => ({ strikethrough: true }),
29+
STRONG: () => ({ bold: true }),
30+
U: () => ({ underline: true }),
31+
};
32+
33+
function deserialize(el) {
34+
if (el.nodeType === 3) {
35+
return el.textContent;
36+
} else if (el.nodeType !== 1) {
37+
return null;
38+
} else if (el.nodeName === 'BR') {
39+
return '\n';
40+
}
41+
42+
const { nodeName } = el;
43+
let parent = el;
44+
45+
if (nodeName === 'PRE' && el.childNodes[0] && el.childNodes[0].nodeName === 'CODE') {
46+
parent = el.childNodes[0];
47+
}
48+
let children = Array.from(parent.childNodes).map(deserialize).flat();
49+
50+
if (children.length === 0) {
51+
children = [{ text: '' }];
52+
}
53+
54+
if (el.nodeName === 'BODY') {
55+
return jsx('fragment', {}, children);
56+
}
57+
58+
if (ELEMENT_TAGS[nodeName]) {
59+
const attrs = ELEMENT_TAGS[nodeName](el);
60+
return jsx('element', attrs, children);
61+
}
62+
63+
if (TEXT_TAGS[nodeName]) {
64+
const attrs = TEXT_TAGS[nodeName](el);
65+
return children.map(child => jsx('text', attrs, child));
66+
}
67+
68+
return children;
69+
}
70+
71+
function withHtml(editor) {
72+
const { insertData, isInline, isVoid } = editor;
73+
74+
editor.isInline = element => {
75+
return element.type === 'link' ? true : isInline(element);
76+
};
77+
78+
editor.isVoid = element => {
79+
return element.type === 'image' ? true : isVoid(element);
80+
};
81+
82+
editor.insertData = data => {
83+
const html = data.getData('text/html');
84+
85+
if (html) {
86+
const parsed = new DOMParser().parseFromString(html, 'text/html');
87+
const fragment = deserialize(parsed.body);
88+
Transforms.insertFragment(editor, fragment);
89+
return;
90+
}
91+
92+
insertData(data);
93+
};
94+
95+
return editor;
96+
}
97+
98+
export default withHtml;

packages/decap-cms-widget-markdown/src/MarkdownControl/renderers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ function NumberedList(props) {
226226
}
227227

228228
function Link(props) {
229-
const url = props.url;
230-
const title = props.title || url;
229+
const url = props.element.url;
230+
const title = props.element.title || url;
231231

232232
return (
233233
<StyledA href={url} title={title} {...props.attributes}>

packages/decap-cms-widget-markdown/src/serializers/slateRemark.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ export default function slateToRemark(value, { voidCodeBlock }) {
450450
*/
451451
case 'link': {
452452
const { title, data } = node;
453-
return u(typeMap[node.type], { url: data?.url, title, ...data }, children);
453+
return u(typeMap[node.type], { url: node.url, title, ...data }, children);
454454
}
455455

456456
/**

0 commit comments

Comments
 (0)