hero-editor 1.9.2 → 1.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +6 -6
- package/dist/app.js.map +1 -1
- package/dist/lib.js +2 -2
- package/dist/lib.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/helpers/__tests__/helpers.test.js +21 -0
- package/src/helpers/getUrlFromNode.js +6 -0
- package/src/helpers/index.js +1 -0
- package/src/lib.js +1 -1
- package/src/plugins/__tests__/__snapshots__/link.test.js.snap +1 -1
- package/src/plugins/link.js +94 -30
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
composeRenderLeaf,
|
|
8
8
|
makeRenderLeaf,
|
|
9
9
|
getUrl,
|
|
10
|
+
getUrlFromNode,
|
|
10
11
|
isEmptyContent,
|
|
11
12
|
} from '../index';
|
|
12
13
|
|
|
@@ -47,6 +48,26 @@ describe('getUrl', () => {
|
|
|
47
48
|
});
|
|
48
49
|
});
|
|
49
50
|
|
|
51
|
+
describe('getUrlFromNode', () => {
|
|
52
|
+
it.each`
|
|
53
|
+
node | expected
|
|
54
|
+
${{ type: 'link', data: { url: 'https://google.com' }, children: [{ text: 'link' }] }} | ${'https://google.com'}
|
|
55
|
+
${{ type: 'link', data: { url: 'https://google.com' }, children: [{ text: 'https:/' }, { text: 'google.com', bold: true }] }} | ${'https://google.com'}
|
|
56
|
+
`('gets URL from link node', ({ node, expected }) => {
|
|
57
|
+
expect(getUrlFromNode(node)).toEqual(expected);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it.each`
|
|
61
|
+
node | expected
|
|
62
|
+
${{ type: 'paragraph', children: [{ text: 'text' }] }} | ${null}
|
|
63
|
+
${null} | ${null}
|
|
64
|
+
${undefined} | ${null}
|
|
65
|
+
${{}} | ${null}
|
|
66
|
+
`('returns null when link node is not provided', ({ node, expected }) => {
|
|
67
|
+
expect(getUrlFromNode(node)).toEqual(expected);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
50
71
|
describe('compose', () => {
|
|
51
72
|
it('composes functions', () => {
|
|
52
73
|
expect(
|
package/src/helpers/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { default as apply } from './apply';
|
|
|
9
9
|
export { default as createPlugin } from './createPlugin';
|
|
10
10
|
export { default as withId } from './withId';
|
|
11
11
|
export { default as getUrl } from './getUrl';
|
|
12
|
+
export { default as getUrlFromNode } from './getUrlFromNode';
|
|
12
13
|
export { default as isEmptyContent } from './isEmptyContent';
|
|
13
14
|
|
|
14
15
|
export { default as useForceUpdate } from './useForceUpdate';
|
package/src/lib.js
CHANGED
|
@@ -151,6 +151,6 @@ export { default as Icon } from './components/Icon';
|
|
|
151
151
|
export { default as plainSerializer } from './serializers/plain';
|
|
152
152
|
export { default as makeReactTransformer } from './transformers/react';
|
|
153
153
|
export { defaultReactTransformer } from './transformers/react';
|
|
154
|
-
export { isEmptyContent, getUrl, isUrl } from './helpers';
|
|
154
|
+
export { isEmptyContent, getUrl, isUrl, getUrlFromNode } from './helpers';
|
|
155
155
|
export { EMPTY_VALUE } from './constants';
|
|
156
156
|
export default HeroEditor;
|
package/src/plugins/link.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
2
|
import { useSlate } from 'slate-react';
|
|
3
3
|
import isUrl from 'is-url';
|
|
4
4
|
import { Transforms, Editor, Range } from 'slate';
|
|
@@ -9,29 +9,41 @@ import {
|
|
|
9
9
|
postMessage,
|
|
10
10
|
isBlockActive,
|
|
11
11
|
isLink,
|
|
12
|
+
getUrlFromNode,
|
|
12
13
|
} from '../helpers';
|
|
13
|
-
import { LINK } from '../constants';
|
|
14
|
+
import { LINK, ADD_LINK } from '../constants';
|
|
15
|
+
import Toolbar from '../components/Toolbar';
|
|
16
|
+
import Icon from '../components/Icon';
|
|
14
17
|
|
|
15
|
-
const
|
|
16
|
-
element.children.reduce((
|
|
18
|
+
const getTextFromNode = (element) =>
|
|
19
|
+
element.children.reduce((text, node) => text + node.text, '');
|
|
17
20
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
21
|
+
const getLinkNodeAtSelection = (editor) => {
|
|
22
|
+
const { selection } = editor;
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
return selection
|
|
25
|
+
? Editor.above(editor, {
|
|
26
|
+
at: selection,
|
|
27
|
+
match: isLink,
|
|
28
|
+
})
|
|
29
|
+
: undefined;
|
|
26
30
|
};
|
|
27
31
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const getTextFromSelection = (editor) => {
|
|
33
|
+
const linkNode = getLinkNodeAtSelection(editor);
|
|
34
|
+
if (linkNode) return getTextFromNode(linkNode[0]);
|
|
35
|
+
|
|
36
|
+
const { selection } = editor;
|
|
37
|
+
return selection ? Editor.string(editor, selection) : '';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getUrlFromSelection = (editor) => {
|
|
41
|
+
const linkNode = getLinkNodeAtSelection(editor);
|
|
42
|
+
return linkNode ? getUrlFromNode(linkNode[0]) : '';
|
|
43
|
+
};
|
|
32
44
|
|
|
33
45
|
const LinkElement = ({ attributes, children, element }) => (
|
|
34
|
-
<a {...attributes} href={
|
|
46
|
+
<a {...attributes} href={getUrlFromNode(element)}>
|
|
35
47
|
{children}
|
|
36
48
|
</a>
|
|
37
49
|
);
|
|
@@ -51,14 +63,16 @@ const handleMessage = addMessageListener(
|
|
|
51
63
|
|
|
52
64
|
const { selection } = editor;
|
|
53
65
|
const isCollapsed = selection && Range.isCollapsed(selection);
|
|
66
|
+
const isTextChanged = selection && Editor.string(editor, selection) != text;
|
|
67
|
+
const insertingText = isCollapsed || isTextChanged;
|
|
54
68
|
|
|
55
69
|
const linkNode = {
|
|
56
70
|
type: LINK,
|
|
57
71
|
data: { url },
|
|
58
|
-
children:
|
|
72
|
+
children: insertingText ? [{ text: text ?? url }] : [],
|
|
59
73
|
};
|
|
60
74
|
|
|
61
|
-
if (
|
|
75
|
+
if (insertingText) {
|
|
62
76
|
Transforms.insertNodes(editor, linkNode);
|
|
63
77
|
} else {
|
|
64
78
|
Transforms.wrapNodes(editor, linkNode, { split: true });
|
|
@@ -78,9 +92,16 @@ const enhanceEditor = (editor) => {
|
|
|
78
92
|
|
|
79
93
|
editor.insertData = (data) => {
|
|
80
94
|
const text = data.getData('text/plain');
|
|
81
|
-
|
|
95
|
+
const { selection } = editor;
|
|
82
96
|
if (text && isUrl(text)) {
|
|
83
|
-
postMessage(
|
|
97
|
+
postMessage(
|
|
98
|
+
LINK,
|
|
99
|
+
{
|
|
100
|
+
text: Editor.string(editor, selection) || text,
|
|
101
|
+
url: text,
|
|
102
|
+
},
|
|
103
|
+
editor,
|
|
104
|
+
);
|
|
84
105
|
} else {
|
|
85
106
|
insertData(data);
|
|
86
107
|
}
|
|
@@ -89,30 +110,73 @@ const enhanceEditor = (editor) => {
|
|
|
89
110
|
return editor;
|
|
90
111
|
};
|
|
91
112
|
|
|
92
|
-
const
|
|
113
|
+
const LinkCustomWrapper = ({ renderLinkCustom }) => {
|
|
114
|
+
const [showLinkCustom, setShowLinkCustom] = useState(false);
|
|
93
115
|
const editor = useSlate();
|
|
94
116
|
|
|
95
|
-
const handleAddLink = (
|
|
96
|
-
postMessage(LINK,
|
|
97
|
-
};
|
|
117
|
+
const handleAddLink = useCallback((data) => {
|
|
118
|
+
postMessage(LINK, data, editor);
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
const getSelectedData = (editor) => ({
|
|
122
|
+
text: getTextFromSelection(editor),
|
|
123
|
+
url: getUrlFromSelection(editor),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const removeLinkCustomListener = addMessageListener(ADD_LINK, () => {
|
|
128
|
+
setShowLinkCustom(true);
|
|
129
|
+
})(editor);
|
|
130
|
+
|
|
131
|
+
return () => removeLinkCustomListener();
|
|
132
|
+
});
|
|
98
133
|
|
|
99
134
|
return (
|
|
100
135
|
<>
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
{showLinkCustom &&
|
|
137
|
+
renderLinkCustom?.({
|
|
138
|
+
handleAddLink,
|
|
139
|
+
getSelectedData: () => getSelectedData(editor),
|
|
140
|
+
hideLinkCustom: () => setShowLinkCustom(false),
|
|
141
|
+
})}
|
|
105
142
|
</>
|
|
106
143
|
);
|
|
107
144
|
};
|
|
108
145
|
|
|
109
|
-
|
|
146
|
+
const ToolbarButton = ({ showToolbarButton }) => {
|
|
147
|
+
const editor = useSlate();
|
|
148
|
+
|
|
149
|
+
const handleMouseDown = useCallback(() => {
|
|
150
|
+
postMessage(ADD_LINK, {}, editor);
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
153
|
+
if (!showToolbarButton) return null;
|
|
154
|
+
|
|
155
|
+
const isLinkNodeAtSelection = !!getLinkNodeAtSelection(editor);
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<>
|
|
159
|
+
<Toolbar.Button
|
|
160
|
+
active={isLinkNodeAtSelection}
|
|
161
|
+
onMouseDown={handleMouseDown}
|
|
162
|
+
>
|
|
163
|
+
<Icon>link</Icon>
|
|
164
|
+
</Toolbar.Button>
|
|
165
|
+
<Toolbar.Separator />
|
|
166
|
+
</>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default ({ renderLinkCustom, showToolbarButton } = {}) =>
|
|
110
171
|
createPlugin({
|
|
111
172
|
name: LINK,
|
|
112
173
|
renderElement,
|
|
113
174
|
handleMessage,
|
|
175
|
+
renderCustom: () => (
|
|
176
|
+
<LinkCustomWrapper renderLinkCustom={renderLinkCustom} />
|
|
177
|
+
),
|
|
114
178
|
ToolbarButton: () => (
|
|
115
|
-
<ToolbarButton
|
|
179
|
+
<ToolbarButton showToolbarButton={showToolbarButton} />
|
|
116
180
|
),
|
|
117
181
|
enhanceEditor,
|
|
118
182
|
});
|