/
setSelection.ts
201 lines (173 loc) · 6.23 KB
/
setSelection.ts
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import { isGeneralSegment } from '../typeCheck/isGeneralSegment';
import type {
ContentModelBlock,
ContentModelBlockGroup,
ContentModelSegment,
ContentModelTable,
Selectable,
TableCellCoordinate,
} from 'roosterjs-content-model-types';
/**
* Set selection into Content Model. If the Content Model already has selection, existing selection will be overwritten by the new one.
* @param group The root level group of Content Model
* @param start The start selected element. If not passed, existing selection of content model will be cleared
* @param end The end selected element. If not passed, only the start element will be selected. If passed, all elements between start and end elements will be selected
*/
export function setSelection(group: ContentModelBlockGroup, start?: Selectable, end?: Selectable) {
setSelectionToBlockGroup(group, false /*isInSelection*/, start || null, end || null);
}
function setSelectionToBlockGroup(
group: ContentModelBlockGroup,
isInSelection: boolean,
start: Selectable | null,
end: Selectable | null
): boolean {
return handleSelection(isInSelection, group, start, end, isInSelection => {
if (isGeneralSegment(group)) {
setIsSelected(group, isInSelection);
}
group.blocks.forEach(block => {
isInSelection = setSelectionToBlock(block, isInSelection, start, end);
});
return isInSelection;
});
}
function setSelectionToBlock(
block: ContentModelBlock,
isInSelection: boolean,
start: Selectable | null,
end: Selectable | null
) {
switch (block.blockType) {
case 'BlockGroup':
return setSelectionToBlockGroup(block, isInSelection, start, end);
case 'Table':
return setSelectionToTable(block, isInSelection, start, end);
case 'Divider':
case 'Entity':
return handleSelection(isInSelection, block, start, end, isInSelection => {
if (isInSelection) {
block.isSelected = true;
} else {
delete block.isSelected;
}
return isInSelection;
});
case 'Paragraph':
const segmentsToDelete: number[] = [];
block.segments.forEach((segment, i) => {
isInSelection = handleSelection(
isInSelection,
segment,
start,
end,
isInSelection => {
return setSelectionToSegment(
segment,
isInSelection,
segmentsToDelete,
start,
end,
i
);
}
);
});
while (segmentsToDelete.length > 0) {
const index = segmentsToDelete.pop()!;
if (index >= 0) {
block.segments.splice(index, 1);
}
}
return isInSelection;
default:
return isInSelection;
}
}
function setSelectionToTable(
table: ContentModelTable,
isInSelection: boolean,
start: Selectable | null,
end: Selectable | null
): boolean {
const first = findCell(table, start);
const last = end ? findCell(table, end) : first;
if (!isInSelection) {
for (let row = 0; row < table.rows.length; row++) {
const currentRow = table.rows[row];
for (let col = 0; col < currentRow.cells.length; col++) {
const currentCell = table.rows[row].cells[col];
const isSelected =
row >= first.row && row <= last.row && col >= first.col && col <= last.col;
setIsSelected(currentCell, isSelected);
if (!isSelected) {
setSelectionToBlockGroup(currentCell, false /*isInSelection*/, start, end);
}
}
}
} else {
table.rows.forEach(row =>
row.cells.forEach(cell => {
isInSelection = setSelectionToBlockGroup(cell, isInSelection, start, end);
})
);
}
return isInSelection;
}
function findCell(table: ContentModelTable, cell: Selectable | null): TableCellCoordinate {
let col = -1;
const row = cell
? table.rows.findIndex(row => (col = (row.cells as Selectable[]).indexOf(cell)) >= 0)
: -1;
return { row, col };
}
function setSelectionToSegment(
segment: ContentModelSegment,
isInSelection: boolean,
segmentsToDelete: number[],
start: Selectable | null,
end: Selectable | null,
i: number
) {
switch (segment.segmentType) {
case 'SelectionMarker':
if (!isInSelection || (segment != start && segment != end)) {
// Delete the selection marker when
// 1. It is not in selection any more. Or
// 2. It is in middle of selection, so no need to have it
segmentsToDelete.push(i);
}
return isInSelection;
case 'General':
setIsSelected(segment, isInSelection);
return segment != start && segment != end
? setSelectionToBlockGroup(segment, isInSelection, start, end)
: isInSelection;
case 'Image':
setIsSelected(segment, isInSelection);
segment.isSelectedAsImageSelection = start == segment && (!end || end == segment);
return isInSelection;
default:
setIsSelected(segment, isInSelection);
return isInSelection;
}
}
function setIsSelected(selectable: Selectable, value: boolean) {
if (value) {
selectable.isSelected = true;
} else {
delete selectable.isSelected;
}
return value;
}
function handleSelection(
isInSelection: boolean,
model: ContentModelBlockGroup | ContentModelBlock | ContentModelSegment,
start: Selectable | null,
end: Selectable | null,
callback: (isInSelection: boolean) => boolean
) {
isInSelection = isInSelection || model == start;
isInSelection = callback(isInSelection);
return isInSelection && !!end && model != end;
}