hero-editor 1.9.4 → 1.9.7
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 +1 -1
- 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/components/Icon.js +3 -3
- package/src/lib.js +10 -11
- package/src/plugins/__tests__/link.test.js +119 -8
- package/src/plugins/link.js +67 -33
package/package.json
CHANGED
package/src/components/Icon.js
CHANGED
|
@@ -16,7 +16,7 @@ const svgIcons = {
|
|
|
16
16
|
looks_two:
|
|
17
17
|
'<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 8c0 1.11-.9 2-2 2h-2v2h4v2H9v-4c0-1.11.9-2 2-2h2V9H9V7h4c1.1 0 2 .89 2 2v2z"/></svg>',
|
|
18
18
|
link:
|
|
19
|
-
'<svg xmlns="http://www.w3.org/2000/svg"
|
|
19
|
+
'<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M13.723 18.654l-3.61 3.609c-2.316 2.315-6.063 2.315-8.378 0-1.12-1.118-1.735-2.606-1.735-4.188 0-1.582.615-3.07 1.734-4.189l4.866-4.865c2.355-2.355 6.114-2.262 8.377 0 .453.453.81.973 1.089 1.527l-1.593 1.592c-.18-.613-.5-1.189-.964-1.652-1.448-1.448-3.93-1.51-5.439-.001l-.001.002-4.867 4.865c-1.5 1.499-1.5 3.941 0 5.44 1.517 1.517 3.958 1.488 5.442 0l2.425-2.424c.993.284 1.791.335 2.654.284zm.161-16.918l-3.574 3.576c.847-.05 1.655 0 2.653.283l2.393-2.389c1.498-1.502 3.94-1.5 5.44-.001 1.517 1.518 1.486 3.959 0 5.442l-4.831 4.831-.003.002c-1.438 1.437-3.886 1.552-5.439-.002-.473-.474-.785-1.042-.956-1.643l-.084.068-1.517 1.515c.28.556.635 1.075 1.088 1.528 2.245 2.245 6.004 2.374 8.378 0l4.832-4.831c2.314-2.316 2.316-6.062-.001-8.377-2.317-2.321-6.067-2.313-8.379-.002z"/></svg>',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const styles = {
|
|
@@ -25,9 +25,9 @@ const styles = {
|
|
|
25
25
|
},
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const Icon = ({ children }) => (
|
|
28
|
+
const Icon = ({ children, height = 20 }) => (
|
|
29
29
|
<img
|
|
30
|
-
height={
|
|
30
|
+
height={height}
|
|
31
31
|
src={`data:image/svg+xml;utf8,${svgIcons[children]}`}
|
|
32
32
|
style={styles.icon}
|
|
33
33
|
/>
|
package/src/lib.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useState, useMemo, useEffect, useRef } from 'react';
|
|
2
2
|
import { createEditor } from 'slate';
|
|
3
3
|
import { Slate, Editable, withReact } from 'slate-react';
|
|
4
4
|
import {
|
|
@@ -62,16 +62,15 @@ const HeroEditor = ({
|
|
|
62
62
|
const wrapper = useRef(null);
|
|
63
63
|
const wrapperLayout = useRef({ width: 0, height: 0 });
|
|
64
64
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
65
|
+
const editorRef = useRef();
|
|
66
|
+
if (!editorRef.current)
|
|
67
|
+
editorRef.current = flow(
|
|
68
|
+
createEditor,
|
|
69
|
+
withReact,
|
|
70
|
+
withId(id),
|
|
71
|
+
...map(get('enhanceEditor'))(plugins),
|
|
72
|
+
)();
|
|
73
|
+
const editor = editorRef.current;
|
|
75
74
|
|
|
76
75
|
const renderLeaf = useMemo(() => composeRenderLeaf(plugins), [plugins]);
|
|
77
76
|
|
|
@@ -8,6 +8,33 @@ import {
|
|
|
8
8
|
withId,
|
|
9
9
|
} from '../../helpers';
|
|
10
10
|
|
|
11
|
+
import { EMPTY_VALUE } from '../../constants';
|
|
12
|
+
|
|
13
|
+
const SAMPLE_PARAGRAPH_WITH_URL = {
|
|
14
|
+
type: 'paragraph',
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
text: 'this is an ',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'link',
|
|
21
|
+
children: [
|
|
22
|
+
{
|
|
23
|
+
children: [
|
|
24
|
+
{
|
|
25
|
+
text: 'url',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
type: 'paragraph',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
data: {
|
|
32
|
+
url: 'https://www.example.com',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
11
38
|
describe('link plugin', () => {
|
|
12
39
|
it('is a plugin object', () => {
|
|
13
40
|
for (const prop in createPlugin()) {
|
|
@@ -37,14 +64,7 @@ describe('link plugin', () => {
|
|
|
37
64
|
const editor = withId('link-sample')(createEditor());
|
|
38
65
|
link().handleMessage(editor);
|
|
39
66
|
|
|
40
|
-
editor.insertNode(
|
|
41
|
-
type: 'paragraph',
|
|
42
|
-
children: [
|
|
43
|
-
{
|
|
44
|
-
text: '',
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
});
|
|
67
|
+
editor.insertNode(EMPTY_VALUE);
|
|
48
68
|
|
|
49
69
|
postMessage(
|
|
50
70
|
'link',
|
|
@@ -136,5 +156,96 @@ describe('link plugin', () => {
|
|
|
136
156
|
]);
|
|
137
157
|
});
|
|
138
158
|
});
|
|
159
|
+
|
|
160
|
+
it('update url of existing hyperlink', async () => {
|
|
161
|
+
const editor = withId('link-sample')(createEditor());
|
|
162
|
+
link().handleMessage(editor);
|
|
163
|
+
|
|
164
|
+
editor.insertNode(SAMPLE_PARAGRAPH_WITH_URL);
|
|
165
|
+
|
|
166
|
+
Transforms.select(editor, {
|
|
167
|
+
anchor: {
|
|
168
|
+
path: [0, 0],
|
|
169
|
+
offset: 11,
|
|
170
|
+
},
|
|
171
|
+
focus: {
|
|
172
|
+
path: [0, 0],
|
|
173
|
+
offset: 11,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
postMessage(
|
|
178
|
+
'link',
|
|
179
|
+
{ text: 'url', url: 'https://www.google.com' },
|
|
180
|
+
editor,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(editor.children).toEqual([
|
|
185
|
+
{
|
|
186
|
+
type: 'paragraph',
|
|
187
|
+
children: [{ text: 'this is an ' }],
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: 'link',
|
|
191
|
+
children: [
|
|
192
|
+
{
|
|
193
|
+
text: 'url',
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
data: {
|
|
197
|
+
url: 'https://www.google.com',
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('update text of existing hyperlink', async () => {
|
|
205
|
+
const editor = withId('link-sample')(createEditor());
|
|
206
|
+
link().handleMessage(editor);
|
|
207
|
+
|
|
208
|
+
editor.insertNode(SAMPLE_PARAGRAPH_WITH_URL);
|
|
209
|
+
|
|
210
|
+
Transforms.select(editor, {
|
|
211
|
+
anchor: {
|
|
212
|
+
path: [0, 0],
|
|
213
|
+
offset: 11,
|
|
214
|
+
},
|
|
215
|
+
focus: {
|
|
216
|
+
path: [0, 0],
|
|
217
|
+
offset: 11,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
postMessage(
|
|
222
|
+
'link',
|
|
223
|
+
{
|
|
224
|
+
text: 'updated example url',
|
|
225
|
+
url: 'https://www.example.com',
|
|
226
|
+
},
|
|
227
|
+
editor,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
await waitFor(() => {
|
|
231
|
+
expect(editor.children).toEqual([
|
|
232
|
+
{
|
|
233
|
+
type: 'paragraph',
|
|
234
|
+
children: [{ text: 'this is an ' }],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
type: 'link',
|
|
238
|
+
children: [
|
|
239
|
+
{
|
|
240
|
+
text: 'updated example url',
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
data: {
|
|
244
|
+
url: 'https://www.example.com',
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
]);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
139
250
|
});
|
|
140
251
|
});
|
package/src/plugins/link.js
CHANGED
|
@@ -15,15 +15,31 @@ import { LINK, ADD_LINK } from '../constants';
|
|
|
15
15
|
import Toolbar from '../components/Toolbar';
|
|
16
16
|
import Icon from '../components/Icon';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
const getTextFromNode = (element) =>
|
|
19
|
+
element.children.reduce((text, node) => text + node.text, '');
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return getUrlFromNode(node);
|
|
24
|
-
}
|
|
21
|
+
const getLinkNodeAtSelection = (editor) => {
|
|
22
|
+
const { selection } = editor;
|
|
25
23
|
|
|
26
|
-
return
|
|
24
|
+
return selection
|
|
25
|
+
? Editor.above(editor, {
|
|
26
|
+
at: selection,
|
|
27
|
+
match: isLink,
|
|
28
|
+
})
|
|
29
|
+
: undefined;
|
|
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]) : '';
|
|
27
43
|
};
|
|
28
44
|
|
|
29
45
|
const LinkElement = ({ attributes, children, element }) => (
|
|
@@ -39,22 +55,30 @@ const handleMessage = addMessageListener(
|
|
|
39
55
|
({ editor, data: { text, url } }) => {
|
|
40
56
|
if (!text || !url || !isUrl(url)) return;
|
|
41
57
|
|
|
42
|
-
if (isBlockActive(editor, LINK)) {
|
|
43
|
-
Transforms.unwrapNodes(editor, {
|
|
44
|
-
match: isLink,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
58
|
const { selection } = editor;
|
|
49
59
|
const isCollapsed = selection && Range.isCollapsed(selection);
|
|
60
|
+
const isTextChanged = selection && Editor.string(editor, selection) != text;
|
|
61
|
+
const insertingText = isCollapsed || isTextChanged;
|
|
50
62
|
|
|
51
63
|
const linkNode = {
|
|
52
64
|
type: LINK,
|
|
53
65
|
data: { url },
|
|
54
|
-
children:
|
|
66
|
+
children: insertingText ? [{ text: text ?? url }] : [],
|
|
55
67
|
};
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
const linkNodeAtSelection = getLinkNodeAtSelection(editor);
|
|
70
|
+
if (linkNodeAtSelection) {
|
|
71
|
+
const [, path] = linkNodeAtSelection;
|
|
72
|
+
Transforms.select(editor, path);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isBlockActive(editor, LINK)) {
|
|
76
|
+
Transforms.unwrapNodes(editor, {
|
|
77
|
+
match: isLink,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (insertingText) {
|
|
58
82
|
Transforms.insertNodes(editor, linkNode);
|
|
59
83
|
} else {
|
|
60
84
|
Transforms.wrapNodes(editor, linkNode, { split: true });
|
|
@@ -74,9 +98,16 @@ const enhanceEditor = (editor) => {
|
|
|
74
98
|
|
|
75
99
|
editor.insertData = (data) => {
|
|
76
100
|
const text = data.getData('text/plain');
|
|
77
|
-
|
|
101
|
+
const { selection } = editor;
|
|
78
102
|
if (text && isUrl(text)) {
|
|
79
|
-
postMessage(
|
|
103
|
+
postMessage(
|
|
104
|
+
LINK,
|
|
105
|
+
{
|
|
106
|
+
text: Editor.string(editor, selection) || text,
|
|
107
|
+
url: text,
|
|
108
|
+
},
|
|
109
|
+
editor,
|
|
110
|
+
);
|
|
80
111
|
} else {
|
|
81
112
|
insertData(data);
|
|
82
113
|
}
|
|
@@ -85,36 +116,34 @@ const enhanceEditor = (editor) => {
|
|
|
85
116
|
return editor;
|
|
86
117
|
};
|
|
87
118
|
|
|
88
|
-
const
|
|
89
|
-
const [
|
|
119
|
+
const LinkCustomWrapper = ({ renderLinkCustom }) => {
|
|
120
|
+
const [showLinkCustom, setShowLinkCustom] = useState(false);
|
|
90
121
|
const editor = useSlate();
|
|
91
122
|
|
|
92
|
-
const { selection } = editor || {};
|
|
93
|
-
|
|
94
123
|
const handleAddLink = useCallback((data) => {
|
|
95
124
|
postMessage(LINK, data, editor);
|
|
96
125
|
}, []);
|
|
97
126
|
|
|
98
127
|
const getSelectedData = (editor) => ({
|
|
99
|
-
text:
|
|
100
|
-
url:
|
|
128
|
+
text: getTextFromSelection(editor),
|
|
129
|
+
url: getUrlFromSelection(editor),
|
|
101
130
|
});
|
|
102
131
|
|
|
103
132
|
useEffect(() => {
|
|
104
|
-
const
|
|
105
|
-
|
|
133
|
+
const removeLinkCustomListener = addMessageListener(ADD_LINK, () => {
|
|
134
|
+
setShowLinkCustom(true);
|
|
106
135
|
})(editor);
|
|
107
136
|
|
|
108
|
-
return () =>
|
|
137
|
+
return () => removeLinkCustomListener();
|
|
109
138
|
});
|
|
110
139
|
|
|
111
140
|
return (
|
|
112
141
|
<>
|
|
113
|
-
{
|
|
114
|
-
|
|
142
|
+
{showLinkCustom &&
|
|
143
|
+
renderLinkCustom?.({
|
|
115
144
|
handleAddLink,
|
|
116
145
|
getSelectedData: () => getSelectedData(editor),
|
|
117
|
-
|
|
146
|
+
hideLinkCustom: () => setShowLinkCustom(false),
|
|
118
147
|
})}
|
|
119
148
|
</>
|
|
120
149
|
);
|
|
@@ -129,23 +158,28 @@ const ToolbarButton = ({ showToolbarButton }) => {
|
|
|
129
158
|
|
|
130
159
|
if (!showToolbarButton) return null;
|
|
131
160
|
|
|
161
|
+
const isLinkNodeAtSelection = !!getLinkNodeAtSelection(editor);
|
|
162
|
+
|
|
132
163
|
return (
|
|
133
164
|
<>
|
|
134
|
-
<Toolbar.Button
|
|
135
|
-
|
|
165
|
+
<Toolbar.Button
|
|
166
|
+
active={isLinkNodeAtSelection}
|
|
167
|
+
onMouseDown={handleMouseDown}
|
|
168
|
+
>
|
|
169
|
+
<Icon height={18}>link</Icon>
|
|
136
170
|
</Toolbar.Button>
|
|
137
171
|
<Toolbar.Separator />
|
|
138
172
|
</>
|
|
139
173
|
);
|
|
140
174
|
};
|
|
141
175
|
|
|
142
|
-
export default ({
|
|
176
|
+
export default ({ renderLinkCustom, showToolbarButton } = {}) =>
|
|
143
177
|
createPlugin({
|
|
144
178
|
name: LINK,
|
|
145
179
|
renderElement,
|
|
146
180
|
handleMessage,
|
|
147
181
|
renderCustom: () => (
|
|
148
|
-
<
|
|
182
|
+
<LinkCustomWrapper key={LINK} renderLinkCustom={renderLinkCustom} />
|
|
149
183
|
),
|
|
150
184
|
ToolbarButton: () => (
|
|
151
185
|
<ToolbarButton showToolbarButton={showToolbarButton} />
|