-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
Copy pathapply-format.js
113 lines (99 loc) · 2.79 KB
/
apply-format.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
/**
* Internal dependencies
*/
import { normaliseFormats } from './normalise-formats';
/** @typedef {import('./types').RichTextValue} RichTextValue */
/** @typedef {import('./types').RichTextFormat} RichTextFormat */
function replace( array, index, value ) {
array = array.slice();
array[ index ] = value;
return array;
}
/**
* Apply a format object to a Rich Text value from the given `startIndex` to the
* given `endIndex`. Indices are retrieved from the selection if none are
* provided.
*
* @param {RichTextValue} value Value to modify.
* @param {RichTextFormat} format Format to apply.
* @param {number} [startIndex] Start index.
* @param {number} [endIndex] End index.
*
* @return {RichTextValue} A new value with the format applied.
*/
export function applyFormat(
value,
format,
startIndex = value.start,
endIndex = value.end
) {
const { formats, activeFormats } = value;
const newFormats = formats.slice();
// The selection is collapsed.
if ( startIndex === endIndex ) {
const startFormat = newFormats[ startIndex ]?.find(
( { type } ) => type === format.type
);
// If the caret is at a format of the same type, expand start and end to
// the edges of the format. This is useful to apply new attributes.
if ( startFormat ) {
const index = newFormats[ startIndex ].indexOf( startFormat );
while (
newFormats[ startIndex ] &&
newFormats[ startIndex ][ index ] === startFormat
) {
newFormats[ startIndex ] = replace(
newFormats[ startIndex ],
index,
format
);
startIndex--;
}
endIndex++;
while (
newFormats[ endIndex ] &&
newFormats[ endIndex ][ index ] === startFormat
) {
newFormats[ endIndex ] = replace(
newFormats[ endIndex ],
index,
format
);
endIndex++;
}
}
} else {
// Determine the highest position the new format can be inserted at.
let position = +Infinity;
for ( let index = startIndex; index < endIndex; index++ ) {
if ( newFormats[ index ] ) {
newFormats[ index ] = newFormats[ index ].filter(
( { type } ) => type !== format.type
);
const length = newFormats[ index ].length;
if ( length < position ) {
position = length;
}
} else {
newFormats[ index ] = [];
position = 0;
}
}
for ( let index = startIndex; index < endIndex; index++ ) {
newFormats[ index ].splice( position, 0, format );
}
}
return normaliseFormats( {
...value,
formats: newFormats,
// Always revise active formats. This serves as a placeholder for new
// inputs with the format so new input appears with the format applied,
// and ensures a format of the same type uses the latest values.
activeFormats: [
...( activeFormats?.filter(
( { type } ) => type !== format.type
) || [] ),
format,
],
} );
}