-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
inline.js
158 lines (141 loc) · 4.15 KB
/
inline.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/**
* WordPress dependencies
*/
import { useState, useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { withSpokenMessages, Popover } from '@wordpress/components';
import { prependHTTP } from '@wordpress/url';
import {
create,
insert,
isCollapsed,
applyFormat,
useAnchorRef,
removeFormat,
} from '@wordpress/rich-text';
import { __experimentalLinkControl as LinkControl } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import { createLinkFormat, isValidHref } from './utils';
import { link as settings } from './index';
function InlineLinkUI( {
isActive,
activeAttributes,
addingLink,
value,
onChange,
speak,
stopAddingLink,
contentRef,
} ) {
/**
* Pending settings to be applied to the next link. When inserting a new
* link, toggle values cannot be applied immediately, because there is not
* yet a link for them to apply to. Thus, they are maintained in a state
* value until the time that the link can be inserted or edited.
*
* @type {[Object|undefined,Function]}
*/
const [ nextLinkValue, setNextLinkValue ] = useState();
const linkValue = {
url: activeAttributes.url,
type: activeAttributes.type,
id: activeAttributes.id,
opensInNewTab: activeAttributes.target === '_blank',
...nextLinkValue,
};
function removeLink() {
const newValue = removeFormat( value, 'core/link' );
onChange( newValue );
stopAddingLink();
speak( __( 'Link removed.' ), 'assertive' );
}
function onChangeLink( nextValue ) {
// Merge with values from state, both for the purpose of assigning the
// next state value, and for use in constructing the new link format if
// the link is ready to be applied.
nextValue = {
...nextLinkValue,
...nextValue,
};
// LinkControl calls `onChange` immediately upon the toggling a setting.
const didToggleSetting =
linkValue.opensInNewTab !== nextValue.opensInNewTab &&
linkValue.url === nextValue.url;
// If change handler was called as a result of a settings change during
// link insertion, it must be held in state until the link is ready to
// be applied.
const didToggleSettingForNewLink =
didToggleSetting && nextValue.url === undefined;
// If link will be assigned, the state value can be considered flushed.
// Otherwise, persist the pending changes.
setNextLinkValue( didToggleSettingForNewLink ? nextValue : undefined );
if ( didToggleSettingForNewLink ) {
return;
}
const newUrl = prependHTTP( nextValue.url );
const format = createLinkFormat( {
url: newUrl,
type: nextValue.type,
id:
nextValue.id !== undefined && nextValue.id !== null
? String( nextValue.id )
: undefined,
opensInNewWindow: nextValue.opensInNewTab,
} );
if ( isCollapsed( value ) && ! isActive ) {
const newText = nextValue.title || newUrl;
const toInsert = applyFormat(
create( { text: newText } ),
format,
0,
newText.length
);
onChange( insert( value, toInsert ) );
} else {
const newValue = applyFormat( value, format );
newValue.start = newValue.end;
newValue.activeFormats = [];
onChange( newValue );
}
// Focus should only be shifted back to the formatted segment when the
// URL is submitted.
if ( ! didToggleSetting ) {
stopAddingLink();
}
if ( ! isValidHref( newUrl ) ) {
speak(
__(
'Warning: the link has been inserted but may have errors. Please test it.'
),
'assertive'
);
} else if ( isActive ) {
speak( __( 'Link edited.' ), 'assertive' );
} else {
speak( __( 'Link inserted.' ), 'assertive' );
}
}
const anchorRef = useAnchorRef( { ref: contentRef, value, settings } );
// The focusOnMount prop shouldn't evolve during render of a Popover
// otherwise it causes a render of the content.
const focusOnMount = useRef( addingLink ? 'firstElement' : false );
return (
<Popover
anchorRef={ anchorRef }
focusOnMount={ focusOnMount.current }
onClose={ stopAddingLink }
position="bottom center"
>
<LinkControl
value={ linkValue }
onChange={ onChangeLink }
onRemove={ removeLink }
forceIsEditingLink={ addingLink }
hasRichPreviews
/>
</Popover>
);
}
export default withSpokenMessages( InlineLinkUI );