This repository has been archived by the owner on Feb 6, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
CompositeDraftDecorator.js
141 lines (124 loc) · 4.13 KB
/
CompositeDraftDecorator.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
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall draft_js
*/
'use strict';
import type {BlockNodeRecord} from 'BlockNodeRecord';
import type ContentState from 'ContentState';
import type {DraftDecorator} from 'DraftDecorator';
const Immutable = require('immutable');
const {List} = Immutable;
const DELIMITER = '.';
/**
* A CompositeDraftDecorator traverses through a list of DraftDecorator
* instances to identify sections of a ContentBlock that should be rendered
* in a "decorated" manner. For example, hashtags, mentions, and links may
* be intended to stand out visually, be rendered as anchors, etc.
*
* The list of decorators supplied to the constructor will be used in the
* order they are provided. This allows the caller to specify a priority for
* string matching, in case of match collisions among decorators.
*
* For instance, I may have a link with a `#` in its text. Though this section
* of text may match our hashtag decorator, it should not be treated as a
* hashtag. I should therefore list my link DraftDecorator
* before my hashtag DraftDecorator when constructing this composite
* decorator instance.
*
* Thus, when a collision like this is encountered, the earlier match is
* preserved and the new match is discarded.
*/
class CompositeDraftDecorator {
_decorators: $ReadOnlyArray<DraftDecorator>;
constructor(decorators: $ReadOnlyArray<DraftDecorator>) {
// Copy the decorator array, since we use this array order to determine
// precedence of decoration matching. If the array is mutated externally,
// we don't want to be affected here.
this._decorators = decorators.slice();
}
/**
* Returns true if this CompositeDraftDecorator has the same decorators as
* the given array. This does a reference check, so the decorators themselves
* have to be the same objects.
*/
isCompositionOfDecorators(arr: $ReadOnlyArray<DraftDecorator>): boolean {
if (this._decorators.length !== arr.length) {
return false;
}
for (let ii = 0; ii < arr.length; ii++) {
if (this._decorators[ii] !== arr[ii]) {
return false;
}
}
return true;
}
getDecorators(): $ReadOnlyArray<DraftDecorator> {
return this._decorators;
}
getDecorations(
block: BlockNodeRecord,
contentState: ContentState,
): List<?string> {
const decorations = Array(block.getText().length).fill(null);
this._decorators.forEach((decorator: DraftDecorator, ii: number) => {
let counter = 0;
const strategy = decorator.strategy;
function getDecorationsChecker(start: number, end: number) {
// Find out if any of our matching range is already occupied
// by another decorator. If so, discard the match. Otherwise, store
// the component key for rendering.
if (canOccupySlice(decorations, start, end)) {
occupySlice(decorations, start, end, ii + DELIMITER + counter);
counter++;
}
}
strategy(block, getDecorationsChecker, contentState);
});
return List(decorations);
}
getComponentForKey(key: string): Function {
const componentKey = parseInt(key.split(DELIMITER)[0], 10);
return this._decorators[componentKey].component;
}
getPropsForKey(key: string): ?Object {
const componentKey = parseInt(key.split(DELIMITER)[0], 10);
return this._decorators[componentKey].props;
}
}
/**
* Determine whether we can occupy the specified slice of the decorations
* array.
*/
function canOccupySlice(
decorations: Array<?string>,
start: number,
end: number,
): boolean {
for (let ii = start; ii < end; ii++) {
if (decorations[ii] != null) {
return false;
}
}
return true;
}
/**
* Splice the specified component into our decoration array at the desired
* range.
*/
function occupySlice(
targetArr: Array<?string>,
start: number,
end: number,
componentKey: string,
): void {
for (let ii = start; ii < end; ii++) {
targetArr[ii] = componentKey;
}
}
module.exports = CompositeDraftDecorator;