@wordpress/format-library 3.0.2 → 3.0.6
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/README.md +1 -1
- package/build/bold/index.js +5 -2
- package/build/bold/index.js.map +1 -1
- package/build/code/index.js +4 -2
- package/build/code/index.js.map +1 -1
- package/build/italic/index.js +5 -2
- package/build/italic/index.js.map +1 -1
- package/build/keyboard/index.js +4 -2
- package/build/keyboard/index.js.map +1 -1
- package/build/link/inline.js +110 -6
- package/build/link/inline.js.map +1 -1
- package/build/link/use-link-instance-key.js +36 -0
- package/build/link/use-link-instance-key.js.map +1 -0
- package/build/link/utils.js +112 -0
- package/build/link/utils.js.map +1 -1
- package/build/strikethrough/index.js +4 -2
- package/build/strikethrough/index.js.map +1 -1
- package/build/subscript/index.js +4 -2
- package/build/subscript/index.js.map +1 -1
- package/build/superscript/index.js +4 -2
- package/build/superscript/index.js.map +1 -1
- package/build/text-color/index.js +60 -21
- package/build/text-color/index.js.map +1 -1
- package/build/text-color/inline.js +105 -37
- package/build/text-color/inline.js.map +1 -1
- package/build/underline/index.js +4 -2
- package/build/underline/index.js.map +1 -1
- package/build-module/bold/index.js +5 -2
- package/build-module/bold/index.js.map +1 -1
- package/build-module/code/index.js +4 -2
- package/build-module/code/index.js.map +1 -1
- package/build-module/italic/index.js +5 -2
- package/build-module/italic/index.js.map +1 -1
- package/build-module/keyboard/index.js +4 -2
- package/build-module/keyboard/index.js.map +1 -1
- package/build-module/link/inline.js +111 -11
- package/build-module/link/inline.js.map +1 -1
- package/build-module/link/use-link-instance-key.js +29 -0
- package/build-module/link/use-link-instance-key.js.map +1 -0
- package/build-module/link/utils.js +110 -1
- package/build-module/link/utils.js.map +1 -1
- package/build-module/strikethrough/index.js +4 -2
- package/build-module/strikethrough/index.js.map +1 -1
- package/build-module/subscript/index.js +4 -2
- package/build-module/subscript/index.js.map +1 -1
- package/build-module/superscript/index.js +4 -2
- package/build-module/superscript/index.js.map +1 -1
- package/build-module/text-color/index.js +61 -22
- package/build-module/text-color/index.js.map +1 -1
- package/build-module/text-color/inline.js +104 -37
- package/build-module/text-color/inline.js.map +1 -1
- package/build-module/underline/index.js +6 -2
- package/build-module/underline/index.js.map +1 -1
- package/build-style/style-rtl.css +2 -25
- package/build-style/style.css +2 -25
- package/package.json +15 -15
- package/src/bold/index.js +2 -2
- package/src/code/index.js +2 -1
- package/src/italic/index.js +2 -2
- package/src/keyboard/index.js +2 -1
- package/src/link/inline.js +121 -8
- package/src/link/test/utils.js +362 -1
- package/src/link/use-link-instance-key.js +31 -0
- package/src/link/utils.js +132 -1
- package/src/strikethrough/index.js +2 -1
- package/src/subscript/index.js +2 -1
- package/src/superscript/index.js +2 -1
- package/src/text-color/index.js +66 -23
- package/src/text-color/inline.js +125 -49
- package/src/text-color/style.scss +2 -24
- package/src/underline/index.js +3 -1
package/src/link/inline.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { useState, useRef } from '@wordpress/element';
|
|
5
|
-
import { __ } from '@wordpress/i18n';
|
|
4
|
+
import { useState, useRef, createInterpolateElement } from '@wordpress/element';
|
|
5
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
6
6
|
import { withSpokenMessages, Popover } from '@wordpress/components';
|
|
7
7
|
import { prependHTTP } from '@wordpress/url';
|
|
8
8
|
import {
|
|
@@ -12,14 +12,21 @@ import {
|
|
|
12
12
|
applyFormat,
|
|
13
13
|
useAnchorRef,
|
|
14
14
|
removeFormat,
|
|
15
|
+
slice,
|
|
16
|
+
replace,
|
|
15
17
|
} from '@wordpress/rich-text';
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
__experimentalLinkControl as LinkControl,
|
|
20
|
+
store as blockEditorStore,
|
|
21
|
+
} from '@wordpress/block-editor';
|
|
22
|
+
import { useSelect } from '@wordpress/data';
|
|
17
23
|
|
|
18
24
|
/**
|
|
19
25
|
* Internal dependencies
|
|
20
26
|
*/
|
|
21
|
-
import { createLinkFormat, isValidHref } from './utils';
|
|
27
|
+
import { createLinkFormat, isValidHref, getFormatBoundary } from './utils';
|
|
22
28
|
import { link as settings } from './index';
|
|
29
|
+
import useLinkInstanceKey from './use-link-instance-key';
|
|
23
30
|
|
|
24
31
|
function InlineLinkUI( {
|
|
25
32
|
isActive,
|
|
@@ -31,6 +38,11 @@ function InlineLinkUI( {
|
|
|
31
38
|
stopAddingLink,
|
|
32
39
|
contentRef,
|
|
33
40
|
} ) {
|
|
41
|
+
const richLinkTextValue = getRichTextValueFromSelection( value, isActive );
|
|
42
|
+
|
|
43
|
+
// Get the text content minus any HTML tags.
|
|
44
|
+
const richTextText = richLinkTextValue.text;
|
|
45
|
+
|
|
34
46
|
/**
|
|
35
47
|
* Pending settings to be applied to the next link. When inserting a new
|
|
36
48
|
* link, toggle values cannot be applied immediately, because there is not
|
|
@@ -41,11 +53,22 @@ function InlineLinkUI( {
|
|
|
41
53
|
*/
|
|
42
54
|
const [ nextLinkValue, setNextLinkValue ] = useState();
|
|
43
55
|
|
|
56
|
+
const { createPageEntity, userCanCreatePages } = useSelect( ( select ) => {
|
|
57
|
+
const { getSettings } = select( blockEditorStore );
|
|
58
|
+
const _settings = getSettings();
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
createPageEntity: _settings.__experimentalCreatePageEntity,
|
|
62
|
+
userCanCreatePages: _settings.__experimentalUserCanCreatePages,
|
|
63
|
+
};
|
|
64
|
+
}, [] );
|
|
65
|
+
|
|
44
66
|
const linkValue = {
|
|
45
67
|
url: activeAttributes.url,
|
|
46
68
|
type: activeAttributes.type,
|
|
47
69
|
id: activeAttributes.id,
|
|
48
70
|
opensInNewTab: activeAttributes.target === '_blank',
|
|
71
|
+
title: richTextText,
|
|
49
72
|
...nextLinkValue,
|
|
50
73
|
};
|
|
51
74
|
|
|
@@ -85,7 +108,7 @@ function InlineLinkUI( {
|
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
const newUrl = prependHTTP( nextValue.url );
|
|
88
|
-
const
|
|
111
|
+
const linkFormat = createLinkFormat( {
|
|
89
112
|
url: newUrl,
|
|
90
113
|
type: nextValue.type,
|
|
91
114
|
id:
|
|
@@ -95,17 +118,46 @@ function InlineLinkUI( {
|
|
|
95
118
|
opensInNewWindow: nextValue.opensInNewTab,
|
|
96
119
|
} );
|
|
97
120
|
|
|
121
|
+
const newText = nextValue.title || newUrl;
|
|
98
122
|
if ( isCollapsed( value ) && ! isActive ) {
|
|
99
|
-
|
|
123
|
+
// Scenario: we don't have any actively selected text or formats.
|
|
100
124
|
const toInsert = applyFormat(
|
|
101
125
|
create( { text: newText } ),
|
|
102
|
-
|
|
126
|
+
linkFormat,
|
|
103
127
|
0,
|
|
104
128
|
newText.length
|
|
105
129
|
);
|
|
106
130
|
onChange( insert( value, toInsert ) );
|
|
107
131
|
} else {
|
|
108
|
-
|
|
132
|
+
// Scenario: we have any active text selection or an active format
|
|
133
|
+
let newValue;
|
|
134
|
+
|
|
135
|
+
if ( newText === richTextText ) {
|
|
136
|
+
// If we're not updating the text then ignore
|
|
137
|
+
newValue = applyFormat( value, linkFormat );
|
|
138
|
+
} else {
|
|
139
|
+
// Create new RichText value for the new text in order that we
|
|
140
|
+
// can apply formats to it.
|
|
141
|
+
newValue = create( { text: newText } );
|
|
142
|
+
|
|
143
|
+
// Apply the new Link format to this new text value.
|
|
144
|
+
newValue = applyFormat(
|
|
145
|
+
newValue,
|
|
146
|
+
linkFormat,
|
|
147
|
+
0,
|
|
148
|
+
newText.length
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Update the original (full) RichTextValue replacing the
|
|
152
|
+
// target text with the *new* RichTextValue containing:
|
|
153
|
+
// 1. The new text content.
|
|
154
|
+
// 2. The new link format.
|
|
155
|
+
// Note original formats will be lost when applying this change.
|
|
156
|
+
// That is expected behaviour.
|
|
157
|
+
// See: https://github.com/WordPress/gutenberg/pull/33849#issuecomment-936134179.
|
|
158
|
+
newValue = replace( value, richTextText, newValue );
|
|
159
|
+
}
|
|
160
|
+
|
|
109
161
|
newValue.start = newValue.end;
|
|
110
162
|
newValue.activeFormats = [];
|
|
111
163
|
onChange( newValue );
|
|
@@ -133,10 +185,42 @@ function InlineLinkUI( {
|
|
|
133
185
|
|
|
134
186
|
const anchorRef = useAnchorRef( { ref: contentRef, value, settings } );
|
|
135
187
|
|
|
188
|
+
// Generate a string based key that is unique to this anchor reference.
|
|
189
|
+
// This is used to force re-mount the LinkControl component to avoid
|
|
190
|
+
// potential stale state bugs caused by the component not being remounted
|
|
191
|
+
// See https://github.com/WordPress/gutenberg/pull/34742.
|
|
192
|
+
const forceRemountKey = useLinkInstanceKey( anchorRef );
|
|
193
|
+
|
|
136
194
|
// The focusOnMount prop shouldn't evolve during render of a Popover
|
|
137
195
|
// otherwise it causes a render of the content.
|
|
138
196
|
const focusOnMount = useRef( addingLink ? 'firstElement' : false );
|
|
139
197
|
|
|
198
|
+
async function handleCreate( pageTitle ) {
|
|
199
|
+
const page = await createPageEntity( {
|
|
200
|
+
title: pageTitle,
|
|
201
|
+
status: 'draft',
|
|
202
|
+
} );
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
id: page.id,
|
|
206
|
+
type: page.type,
|
|
207
|
+
title: page.title.rendered,
|
|
208
|
+
url: page.link,
|
|
209
|
+
kind: 'post-type',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function createButtonText( searchTerm ) {
|
|
214
|
+
return createInterpolateElement(
|
|
215
|
+
sprintf(
|
|
216
|
+
/* translators: %s: search term. */
|
|
217
|
+
__( 'Create Page: <mark>%s</mark>' ),
|
|
218
|
+
searchTerm
|
|
219
|
+
),
|
|
220
|
+
{ mark: <mark /> }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
140
224
|
return (
|
|
141
225
|
<Popover
|
|
142
226
|
anchorRef={ anchorRef }
|
|
@@ -145,14 +229,43 @@ function InlineLinkUI( {
|
|
|
145
229
|
position="bottom center"
|
|
146
230
|
>
|
|
147
231
|
<LinkControl
|
|
232
|
+
key={ forceRemountKey }
|
|
148
233
|
value={ linkValue }
|
|
149
234
|
onChange={ onChangeLink }
|
|
150
235
|
onRemove={ removeLink }
|
|
151
236
|
forceIsEditingLink={ addingLink }
|
|
152
237
|
hasRichPreviews
|
|
238
|
+
createSuggestion={ createPageEntity && handleCreate }
|
|
239
|
+
withCreateSuggestion={ userCanCreatePages }
|
|
240
|
+
createSuggestionButtonText={ createButtonText }
|
|
241
|
+
hasTextControl
|
|
153
242
|
/>
|
|
154
243
|
</Popover>
|
|
155
244
|
);
|
|
156
245
|
}
|
|
157
246
|
|
|
247
|
+
function getRichTextValueFromSelection( value, isActive ) {
|
|
248
|
+
// Default to the selection ranges on the RichTextValue object.
|
|
249
|
+
let textStart = value.start;
|
|
250
|
+
let textEnd = value.end;
|
|
251
|
+
|
|
252
|
+
// If the format is currently active then the rich text value
|
|
253
|
+
// should always be taken from the bounds of the active format
|
|
254
|
+
// and not the selected text.
|
|
255
|
+
if ( isActive ) {
|
|
256
|
+
const boundary = getFormatBoundary( value, {
|
|
257
|
+
type: 'core/link',
|
|
258
|
+
} );
|
|
259
|
+
|
|
260
|
+
textStart = boundary.start;
|
|
261
|
+
|
|
262
|
+
// Text *selection* always extends +1 beyond the edge of the format.
|
|
263
|
+
// We account for that here.
|
|
264
|
+
textEnd = boundary.end + 1;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Get a RichTextValue containing the selected text content.
|
|
268
|
+
return slice( value, textStart, textEnd );
|
|
269
|
+
}
|
|
270
|
+
|
|
158
271
|
export default withSpokenMessages( InlineLinkUI );
|
package/src/link/test/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { isValidHref } from '../utils';
|
|
4
|
+
import { isValidHref, getFormatBoundary } from '../utils';
|
|
5
5
|
|
|
6
6
|
describe( 'isValidHref', () => {
|
|
7
7
|
it( 'returns true if the href cannot be recognised as a url or an anchor link', () => {
|
|
@@ -96,3 +96,364 @@ describe( 'isValidHref', () => {
|
|
|
96
96
|
} );
|
|
97
97
|
} );
|
|
98
98
|
} );
|
|
99
|
+
|
|
100
|
+
describe( 'getFormatBoundary', () => {
|
|
101
|
+
const boldFormat = {
|
|
102
|
+
type: 'core/bold',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const italicFormat = {
|
|
106
|
+
type: 'core/italic',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const linkFormat = {
|
|
110
|
+
type: 'core/link',
|
|
111
|
+
attributes: {
|
|
112
|
+
url: 'http://www.wordpress.org',
|
|
113
|
+
type: 'URL',
|
|
114
|
+
id: 'www.wordpress.org',
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
describe( 'Invalid values', () => {
|
|
119
|
+
it( 'should return empty bounds if value contains no formats', () => {
|
|
120
|
+
const record = {
|
|
121
|
+
formats: [], // no formats here!
|
|
122
|
+
text: 'Lorem ipsum dolor.',
|
|
123
|
+
start: 8,
|
|
124
|
+
end: 8,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
expect(
|
|
128
|
+
getFormatBoundary( record, { type: 'core/link' } )
|
|
129
|
+
).toEqual( {
|
|
130
|
+
start: null,
|
|
131
|
+
end: null,
|
|
132
|
+
} );
|
|
133
|
+
} );
|
|
134
|
+
|
|
135
|
+
it.each( [
|
|
136
|
+
[ 5, 12 ],
|
|
137
|
+
[ 11, 12 ],
|
|
138
|
+
] )(
|
|
139
|
+
'should return empty bounds if both start and end indexes do not contain the target format',
|
|
140
|
+
() => {
|
|
141
|
+
const record = {
|
|
142
|
+
formats: [
|
|
143
|
+
null,
|
|
144
|
+
[ italicFormat ],
|
|
145
|
+
[ italicFormat ],
|
|
146
|
+
[ italicFormat ],
|
|
147
|
+
null,
|
|
148
|
+
null, // 5
|
|
149
|
+
[ linkFormat, italicFormat ], // 6
|
|
150
|
+
[ linkFormat ],
|
|
151
|
+
[ linkFormat, boldFormat ],
|
|
152
|
+
[ linkFormat, boldFormat ],
|
|
153
|
+
[ linkFormat, italicFormat ], // 10
|
|
154
|
+
null, // 11
|
|
155
|
+
[ boldFormat ], // 12
|
|
156
|
+
[ boldFormat ],
|
|
157
|
+
[ boldFormat ],
|
|
158
|
+
[ boldFormat ],
|
|
159
|
+
null,
|
|
160
|
+
null,
|
|
161
|
+
],
|
|
162
|
+
|
|
163
|
+
text: 'Lorem ipsum dolor.',
|
|
164
|
+
start: 5, // no format here
|
|
165
|
+
end: 12, // format here but it doesn't match the target format
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
expect(
|
|
169
|
+
getFormatBoundary( record, { type: 'core/link' } )
|
|
170
|
+
).toEqual( {
|
|
171
|
+
start: null,
|
|
172
|
+
end: null,
|
|
173
|
+
} );
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
it.each( [
|
|
178
|
+
[
|
|
179
|
+
"start and end are beyond the length of the provided value's formats",
|
|
180
|
+
[ 19, 22 ],
|
|
181
|
+
],
|
|
182
|
+
[ 'start and end are less than 0', [ -1, -2 ] ],
|
|
183
|
+
] )(
|
|
184
|
+
'should return empty bounds if %s',
|
|
185
|
+
( _ignored, [ start, end ] ) => {
|
|
186
|
+
const record = {
|
|
187
|
+
formats: [
|
|
188
|
+
null,
|
|
189
|
+
[ italicFormat ],
|
|
190
|
+
[ italicFormat ],
|
|
191
|
+
[ italicFormat ],
|
|
192
|
+
null,
|
|
193
|
+
null, // 5
|
|
194
|
+
[ linkFormat, italicFormat ], // 6
|
|
195
|
+
[ linkFormat ],
|
|
196
|
+
[ linkFormat, boldFormat ],
|
|
197
|
+
[ linkFormat, boldFormat ],
|
|
198
|
+
[ linkFormat, italicFormat ], // 10
|
|
199
|
+
null, // 11
|
|
200
|
+
[ boldFormat ], // 12
|
|
201
|
+
[ boldFormat ],
|
|
202
|
+
[ boldFormat ],
|
|
203
|
+
[ boldFormat ],
|
|
204
|
+
null,
|
|
205
|
+
null, // 17
|
|
206
|
+
],
|
|
207
|
+
|
|
208
|
+
text: 'Lorem ipsum dolor.',
|
|
209
|
+
start,
|
|
210
|
+
end,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
expect(
|
|
214
|
+
getFormatBoundary( record, { type: 'core/link' } )
|
|
215
|
+
).toEqual( {
|
|
216
|
+
start: null,
|
|
217
|
+
end: null,
|
|
218
|
+
} );
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
} );
|
|
222
|
+
|
|
223
|
+
describe( 'Collapsed values', () => {
|
|
224
|
+
it.each( [
|
|
225
|
+
[ 'inside', [ 8, 8 ] ],
|
|
226
|
+
[ 'start', [ 6, 6 ] ],
|
|
227
|
+
[ 'end', [ 10, 10 ] ],
|
|
228
|
+
[ 'just inside the start', [ 7, 7 ] ],
|
|
229
|
+
[ 'just inside the end', [ 9, 9 ] ],
|
|
230
|
+
] )(
|
|
231
|
+
'should find bounds of a format from %s of a collapsed RichTextValue',
|
|
232
|
+
( _ignored, [ start, end ] ) => {
|
|
233
|
+
const record = {
|
|
234
|
+
formats: [
|
|
235
|
+
null,
|
|
236
|
+
[ italicFormat ],
|
|
237
|
+
[ italicFormat ],
|
|
238
|
+
[ italicFormat ],
|
|
239
|
+
null,
|
|
240
|
+
null,
|
|
241
|
+
[ linkFormat, italicFormat ], // 6
|
|
242
|
+
[ linkFormat ],
|
|
243
|
+
[ linkFormat, boldFormat ],
|
|
244
|
+
[ linkFormat, boldFormat ],
|
|
245
|
+
[ linkFormat, italicFormat ], // 10
|
|
246
|
+
],
|
|
247
|
+
|
|
248
|
+
text: 'Lorem ipsum dolor.',
|
|
249
|
+
start,
|
|
250
|
+
end,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
expect(
|
|
254
|
+
getFormatBoundary( record, { type: 'core/link' } )
|
|
255
|
+
).toEqual( {
|
|
256
|
+
start: 6,
|
|
257
|
+
end: 10,
|
|
258
|
+
} );
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
it( 'should find bounds of a format from just outside the end of a collapsed RichTextValue', () => {
|
|
263
|
+
// This is an edge case which will occur if you create a format, then place
|
|
264
|
+
// the caret just before the format and hit the back ARROW key. The resulting
|
|
265
|
+
// value object will have start and end +1 beyond the edge of the format boundary.
|
|
266
|
+
// The code under test here has to cope with that by searching for formats -1 back
|
|
267
|
+
// from the end value.
|
|
268
|
+
const record = {
|
|
269
|
+
formats: [
|
|
270
|
+
null,
|
|
271
|
+
[ italicFormat ],
|
|
272
|
+
[ italicFormat ],
|
|
273
|
+
[ italicFormat ],
|
|
274
|
+
null,
|
|
275
|
+
null,
|
|
276
|
+
[ linkFormat, italicFormat ], // 6
|
|
277
|
+
[ linkFormat ],
|
|
278
|
+
[ linkFormat, boldFormat ],
|
|
279
|
+
[ linkFormat, boldFormat ],
|
|
280
|
+
[ linkFormat, italicFormat ], // 10
|
|
281
|
+
],
|
|
282
|
+
|
|
283
|
+
text: 'Lorem ipsum dolor.',
|
|
284
|
+
start: 11, // +1 beyond the end of the formats
|
|
285
|
+
end: 11, // +1 beyond the end of the formats
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
expect(
|
|
289
|
+
getFormatBoundary( record, { type: 'core/link' } )
|
|
290
|
+
).toEqual( {
|
|
291
|
+
start: 6,
|
|
292
|
+
end: 10,
|
|
293
|
+
} );
|
|
294
|
+
} );
|
|
295
|
+
} );
|
|
296
|
+
|
|
297
|
+
describe( 'Non-collapsed values', () => {
|
|
298
|
+
it.each( [
|
|
299
|
+
[ 'at the bounds', [ 6, 10 ] ],
|
|
300
|
+
[ 'inside the bounds', [ 7, 8 ] ],
|
|
301
|
+
] )(
|
|
302
|
+
'should find bounds of a format beginning %s of a non-collapsed RichTextValue',
|
|
303
|
+
( _ignored, [ start, end ] ) => {
|
|
304
|
+
const record = {
|
|
305
|
+
formats: [
|
|
306
|
+
null,
|
|
307
|
+
[ italicFormat ],
|
|
308
|
+
[ italicFormat ],
|
|
309
|
+
[ italicFormat ],
|
|
310
|
+
null,
|
|
311
|
+
null,
|
|
312
|
+
[ linkFormat, italicFormat ], // 6
|
|
313
|
+
[ linkFormat ],
|
|
314
|
+
[ linkFormat, boldFormat ],
|
|
315
|
+
[ linkFormat, boldFormat ],
|
|
316
|
+
[ linkFormat, italicFormat ], // 10
|
|
317
|
+
null,
|
|
318
|
+
null,
|
|
319
|
+
[ boldFormat ],
|
|
320
|
+
[ boldFormat ],
|
|
321
|
+
[ boldFormat ],
|
|
322
|
+
null,
|
|
323
|
+
null,
|
|
324
|
+
],
|
|
325
|
+
|
|
326
|
+
text: 'Lorem ipsum dolor.',
|
|
327
|
+
start,
|
|
328
|
+
end,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
expect(
|
|
332
|
+
getFormatBoundary( record, { type: 'core/link' } )
|
|
333
|
+
).toEqual( {
|
|
334
|
+
start: 6,
|
|
335
|
+
end: 10,
|
|
336
|
+
} );
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
} );
|
|
340
|
+
|
|
341
|
+
it( 'should find bounds of a format which starts at zeroth index where start/end pointers are at 0', () => {
|
|
342
|
+
const record = {
|
|
343
|
+
formats: [
|
|
344
|
+
[ linkFormat ], // 0 (th zeroth index)
|
|
345
|
+
[ linkFormat ],
|
|
346
|
+
[ linkFormat ],
|
|
347
|
+
[ linkFormat ],
|
|
348
|
+
[ linkFormat ],
|
|
349
|
+
[ linkFormat ], // 5
|
|
350
|
+
[ boldFormat ],
|
|
351
|
+
[ boldFormat ],
|
|
352
|
+
null,
|
|
353
|
+
null,
|
|
354
|
+
null,
|
|
355
|
+
null,
|
|
356
|
+
null,
|
|
357
|
+
null,
|
|
358
|
+
null,
|
|
359
|
+
[ boldFormat ],
|
|
360
|
+
[ boldFormat ],
|
|
361
|
+
[ boldFormat ],
|
|
362
|
+
null,
|
|
363
|
+
null,
|
|
364
|
+
null,
|
|
365
|
+
null,
|
|
366
|
+
null,
|
|
367
|
+
null,
|
|
368
|
+
null,
|
|
369
|
+
[ italicFormat ],
|
|
370
|
+
[ italicFormat ],
|
|
371
|
+
[ italicFormat ],
|
|
372
|
+
[ italicFormat ],
|
|
373
|
+
[ italicFormat ],
|
|
374
|
+
null,
|
|
375
|
+
],
|
|
376
|
+
|
|
377
|
+
text: "Lorem ok let's try again dolor.",
|
|
378
|
+
start: 0,
|
|
379
|
+
end: 0,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
expect( getFormatBoundary( record, { type: 'core/link' } ) ).toEqual( {
|
|
383
|
+
start: 0,
|
|
384
|
+
end: 5,
|
|
385
|
+
} );
|
|
386
|
+
} );
|
|
387
|
+
|
|
388
|
+
describe( 'Single value out of bounds', () => {
|
|
389
|
+
it.each( [
|
|
390
|
+
[ 'start is outside the boundary but end is inside', [ 1, 4 ] ],
|
|
391
|
+
[ 'end is outside the boundary but start is inside', [ 4, 10 ] ],
|
|
392
|
+
[
|
|
393
|
+
'end is withins bounds but is less than start (inverted)',
|
|
394
|
+
[ 4, 1 ],
|
|
395
|
+
],
|
|
396
|
+
[ 'start is negative but end is within bounds', [ -1000, 4 ] ],
|
|
397
|
+
[ 'end is negative but start is within bounds', [ 4, -1000 ] ],
|
|
398
|
+
] )( 'should return bounds when %s', ( _ignored, [ start, end ] ) => {
|
|
399
|
+
const record = {
|
|
400
|
+
formats: [
|
|
401
|
+
[],
|
|
402
|
+
[],
|
|
403
|
+
[],
|
|
404
|
+
[],
|
|
405
|
+
[ boldFormat ], // 4
|
|
406
|
+
[ boldFormat ],
|
|
407
|
+
[ boldFormat ], // 6
|
|
408
|
+
[],
|
|
409
|
+
[],
|
|
410
|
+
],
|
|
411
|
+
text: 'Lorem ipsum dolor.',
|
|
412
|
+
start,
|
|
413
|
+
end,
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
expect(
|
|
417
|
+
getFormatBoundary( record, { type: 'core/bold' } )
|
|
418
|
+
).toEqual( {
|
|
419
|
+
start: 4,
|
|
420
|
+
end: 6,
|
|
421
|
+
} );
|
|
422
|
+
} );
|
|
423
|
+
} );
|
|
424
|
+
|
|
425
|
+
it( 'should respect startIndex and endIndex arguments when seeking boundary', () => {
|
|
426
|
+
const startIndex = 1;
|
|
427
|
+
const endIndex = 3;
|
|
428
|
+
|
|
429
|
+
const record = {
|
|
430
|
+
formats: [
|
|
431
|
+
[ boldFormat ],
|
|
432
|
+
[ boldFormat ],
|
|
433
|
+
[ boldFormat ],
|
|
434
|
+
[ boldFormat ],
|
|
435
|
+
[ boldFormat ],
|
|
436
|
+
[],
|
|
437
|
+
[],
|
|
438
|
+
[],
|
|
439
|
+
[],
|
|
440
|
+
[],
|
|
441
|
+
],
|
|
442
|
+
text: 'Lorem ipsum dolor.',
|
|
443
|
+
start: 10, // the value start is outside the format
|
|
444
|
+
end: 10, // the value end is outside the format
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
expect(
|
|
448
|
+
getFormatBoundary(
|
|
449
|
+
record,
|
|
450
|
+
{ type: 'core/bold' },
|
|
451
|
+
startIndex,
|
|
452
|
+
endIndex
|
|
453
|
+
)
|
|
454
|
+
).toEqual( {
|
|
455
|
+
start: 0,
|
|
456
|
+
end: 4,
|
|
457
|
+
} );
|
|
458
|
+
} );
|
|
459
|
+
} );
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Weakly referenced map allows unused ids to be garbage collected.
|
|
2
|
+
const weakMap = new WeakMap();
|
|
3
|
+
|
|
4
|
+
// Incrementing zero-based ID value
|
|
5
|
+
let id = -1;
|
|
6
|
+
|
|
7
|
+
const prefix = 'link-control-instance';
|
|
8
|
+
|
|
9
|
+
function getKey( _id ) {
|
|
10
|
+
return `${ prefix }-${ _id }`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds a unique link control key for the given object reference.
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} instance an unique object reference specific to this link control instance.
|
|
17
|
+
* @return {string} the unique key to use for this link control.
|
|
18
|
+
*/
|
|
19
|
+
function useLinkInstanceKey( instance ) {
|
|
20
|
+
if ( weakMap.has( instance ) ) {
|
|
21
|
+
return getKey( weakMap.get( instance ) );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
id += 1;
|
|
25
|
+
|
|
26
|
+
weakMap.set( instance, id );
|
|
27
|
+
|
|
28
|
+
return getKey( id );
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default useLinkInstanceKey;
|