@azure/communication-react 1.5.1-alpha-202306060014 → 1.5.1-alpha-202306070014
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/dist/dist-cjs/communication-react/index.js +1457 -1484
- package/dist/dist-cjs/communication-react/index.js.map +1 -1
- package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js +1 -1
- package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js.map +1 -1
- package/dist/dist-esm/react-components/src/components/InputBoxComponent.js +38 -1375
- package/dist/dist-esm/react-components/src/components/InputBoxComponent.js.map +1 -1
- package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.d.ts +41 -0
- package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js +554 -0
- package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js.map +1 -0
- package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.d.ts +167 -0
- package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.js +780 -0
- package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.js.map +1 -0
- package/package.json +8 -8
@@ -1,117 +1,20 @@
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
2
2
|
// Licensed under the MIT license.
|
3
|
-
|
4
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
5
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
6
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
7
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
8
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
9
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
10
|
-
});
|
11
|
-
};
|
12
|
-
import React, { useState, useCallback, useRef } from 'react';
|
13
|
-
/* @conditional-compile-remove(mention) */
|
14
|
-
import { useEffect, useMemo } from 'react';
|
15
|
-
/* @conditional-compile-remove(mention) */
|
16
|
-
import { useLocale } from '../localization';
|
17
|
-
/* @conditional-compile-remove(mention) */
|
18
|
-
import { Announcer } from './Announcer';
|
3
|
+
import React, { useState, useCallback } from 'react';
|
19
4
|
import { Stack, TextField, mergeStyles, concatStyleSets, IconButton, TooltipHost } from '@fluentui/react';
|
20
5
|
import { isEnterKeyEventFromCompositionSession } from './utils';
|
21
|
-
/* @conditional-compile-remove(mention) */
|
22
|
-
import { nullToUndefined, undefinedToNull } from './utils';
|
23
6
|
import { inputBoxStyle, inputBoxWrapperStyle, inputButtonStyle, textFieldStyle, textContainerStyle, newLineButtonsContainerStyle, inputBoxNewLineSpaceAffordance, inputButtonTooltipStyle, iconWrapperStyle } from './styles/InputBoxComponent.style';
|
24
|
-
/* @conditional-compile-remove(mention) */
|
25
|
-
import { Caret } from 'textarea-caret-ts';
|
26
7
|
import { isDarkThemed } from '../theming/themeUtils';
|
27
8
|
import { useTheme } from '../theming';
|
28
9
|
/* @conditional-compile-remove(mention) */
|
29
|
-
import {
|
30
|
-
/* @conditional-compile-remove(mention) */
|
31
|
-
import { useDebouncedCallback } from 'use-debounce';
|
32
|
-
/* @conditional-compile-remove(mention) */
|
33
|
-
const DEFAULT_MENTION_TRIGGER = '@';
|
34
|
-
/* @conditional-compile-remove(mention) */
|
35
|
-
const MSFT_MENTION_TAG = 'msft-mention';
|
10
|
+
import { TextFieldWithMention } from './TextFieldWithMention/TextFieldWithMention';
|
36
11
|
/**
|
37
12
|
* @private
|
38
13
|
*/
|
39
14
|
export const InputBoxComponent = (props) => {
|
40
|
-
const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled,
|
41
|
-
/* @conditional-compile-remove(mention) */
|
42
|
-
mentionLookupOptions, children } = props;
|
43
|
-
const inputBoxRef = useRef(null);
|
44
|
-
/* @conditional-compile-remove(mention) */
|
45
|
-
// Current suggestion list, provided by the callback
|
46
|
-
const [mentionSuggestions, setMentionSuggestions] = useState([]);
|
47
|
-
/* @conditional-compile-remove(mention) */
|
48
|
-
// Current suggestion list, provided by the callback
|
49
|
-
const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(undefined);
|
50
|
-
/* @conditional-compile-remove(mention) */
|
51
|
-
// Index of the current trigger character in the text field
|
52
|
-
const [currentTriggerStartIndex, setCurrentTriggerStartIndex] = useState(-1);
|
53
|
-
/* @conditional-compile-remove(mention) */
|
54
|
-
const [inputTextValue, setInputTextValue] = useState('');
|
55
|
-
/* @conditional-compile-remove(mention) */
|
56
|
-
const [tagsValue, setTagsValue] = useState([]);
|
57
|
-
/* @conditional-compile-remove(mention) */
|
58
|
-
// Index of the previous selection start in the text field
|
59
|
-
const [selectionStartValue, setSelectionStartValue] = useState();
|
60
|
-
/* @conditional-compile-remove(mention) */
|
61
|
-
// Index of the previous selection end in the text field
|
62
|
-
const [selectionEndValue, setSelectionEndValue] = useState();
|
63
|
-
/* @conditional-compile-remove(mention) */
|
64
|
-
// Boolean value to check if onMouseDown event should be handled during select as selection range
|
65
|
-
// for onMouseDown event is not updated yet and the selection range for mouse click/taps will be
|
66
|
-
// updated in onSelect event if needed.
|
67
|
-
const [shouldHandleOnMouseDownDuringSelect, setShouldHandleOnMouseDownDuringSelect] = useState(true);
|
68
|
-
/* @conditional-compile-remove(mention) */
|
69
|
-
// Point of start of touch/mouse selection
|
70
|
-
const [interactionStartPoint, setInteractionStartPoint] = useState();
|
71
|
-
/* @conditional-compile-remove(mention) */
|
72
|
-
// Target selection from mouse movement
|
73
|
-
const [targetSelection, setTargetSelection] = useState();
|
74
|
-
/* @conditional-compile-remove(mention) */
|
75
|
-
// Caret position in the text field
|
76
|
-
const [caretPosition, setCaretPosition] = useState(undefined);
|
77
|
-
/* @conditional-compile-remove(mention) */
|
78
|
-
// Index of where the caret is in the text field
|
79
|
-
const [caretIndex, setCaretIndex] = useState(undefined);
|
80
|
-
/* @conditional-compile-remove(mention) */
|
81
|
-
const localeStrings = useLocale().strings;
|
82
|
-
/* @conditional-compile-remove(mention) */
|
83
|
-
// Set mention suggestions
|
84
|
-
const updateMentionSuggestions = useCallback((suggestions) => {
|
85
|
-
setMentionSuggestions(suggestions);
|
86
|
-
}, [setMentionSuggestions]);
|
87
|
-
/* @conditional-compile-remove(mention) */
|
88
|
-
// Parse the text and get the plain text version to display in the input box
|
89
|
-
useEffect(() => {
|
90
|
-
const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || DEFAULT_MENTION_TRIGGER;
|
91
|
-
const parsedHTMLData = textToTagParser(textValue, trigger);
|
92
|
-
setInputTextValue(parsedHTMLData.plainText);
|
93
|
-
setTagsValue(parsedHTMLData.tags);
|
94
|
-
updateMentionSuggestions([]);
|
95
|
-
}, [textValue, mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger, updateMentionSuggestions]);
|
15
|
+
const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled, children } = props;
|
96
16
|
const mergedRootStyle = mergeStyles(inputBoxWrapperStyle, styles === null || styles === void 0 ? void 0 : styles.root);
|
97
17
|
const mergedInputFieldStyle = mergeStyles(inputBoxStyle, inputClassName, props.inlineChildren ? {} : inputBoxNewLineSpaceAffordance);
|
98
|
-
/* @conditional-compile-remove(mention) */
|
99
|
-
useEffect(() => {
|
100
|
-
var _a;
|
101
|
-
// effect for caret index update
|
102
|
-
if (caretIndex === undefined || textFieldRef === undefined || (textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === undefined) {
|
103
|
-
return;
|
104
|
-
}
|
105
|
-
// get validated caret index between 0 and inputTextValue.length otherwise caret will be set to incorrect index
|
106
|
-
const updatedCaretIndex = getValidatedIndexInRange({
|
107
|
-
min: 0,
|
108
|
-
max: inputTextValue.length,
|
109
|
-
currentValue: caretIndex
|
110
|
-
});
|
111
|
-
(_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(updatedCaretIndex, updatedCaretIndex);
|
112
|
-
setSelectionStartValue(updatedCaretIndex);
|
113
|
-
setSelectionEndValue(updatedCaretIndex);
|
114
|
-
}, [caretIndex, inputTextValue.length, textFieldRef, setSelectionStartValue, setSelectionEndValue]);
|
115
18
|
const mergedTextContainerStyle = mergeStyles(textContainerStyle, styles === null || styles === void 0 ? void 0 : styles.textFieldContainer);
|
116
19
|
const mergedTextFieldStyle = concatStyleSets(textFieldStyle, {
|
117
20
|
fieldGroup: styles === null || styles === void 0 ? void 0 : styles.textField,
|
@@ -124,511 +27,56 @@ export const InputBoxComponent = (props) => {
|
|
124
27
|
}
|
125
28
|
});
|
126
29
|
const mergedChildrenStyle = mergeStyles(props.inlineChildren ? {} : newLineButtonsContainerStyle);
|
127
|
-
/* @conditional-compile-remove(mention) */
|
128
|
-
const onSuggestionSelected = useCallback((suggestion) => {
|
129
|
-
var _a, _b, _c;
|
130
|
-
let selectionEnd = ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.selectionEnd) || -1;
|
131
|
-
if (selectionEnd < 0) {
|
132
|
-
selectionEnd = 0;
|
133
|
-
}
|
134
|
-
else if (selectionEnd > inputTextValue.length) {
|
135
|
-
selectionEnd = inputTextValue.length;
|
136
|
-
}
|
137
|
-
const oldPlainText = inputTextValue;
|
138
|
-
const mention = htmlStringForMentionSuggestion(suggestion, localeStrings);
|
139
|
-
// update plain text with the mention html text
|
140
|
-
const newPlainText = inputTextValue.substring(0, currentTriggerStartIndex) + mention + inputTextValue.substring(selectionEnd);
|
141
|
-
const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
|
142
|
-
// update html text with updated plain text
|
143
|
-
const updatedContent = updateHTML({
|
144
|
-
htmlText: textValue,
|
145
|
-
oldPlainText,
|
146
|
-
newPlainText,
|
147
|
-
tags: tagsValue,
|
148
|
-
startIndex: currentTriggerStartIndex,
|
149
|
-
oldPlainTextEndIndex: selectionEnd,
|
150
|
-
change: mention,
|
151
|
-
mentionTrigger: triggerText
|
152
|
-
});
|
153
|
-
const displayName = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
|
154
|
-
const newCaretIndex = currentTriggerStartIndex + displayName.length + triggerText.length;
|
155
|
-
// move the caret in the text field to the end of the mention plain text
|
156
|
-
setCaretIndex(newCaretIndex);
|
157
|
-
setSelectionEndValue(newCaretIndex);
|
158
|
-
setSelectionStartValue(newCaretIndex);
|
159
|
-
setCurrentTriggerStartIndex(-1);
|
160
|
-
updateMentionSuggestions([]);
|
161
|
-
// set focus back to text field
|
162
|
-
(_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.focus();
|
163
|
-
setActiveSuggestionIndex(undefined);
|
164
|
-
onChange && onChange(undefined, updatedContent.updatedHTML);
|
165
|
-
}, [
|
166
|
-
textFieldRef,
|
167
|
-
inputTextValue,
|
168
|
-
currentTriggerStartIndex,
|
169
|
-
mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger,
|
170
|
-
onChange,
|
171
|
-
textValue,
|
172
|
-
tagsValue,
|
173
|
-
/* @conditional-compile-remove(mention) */
|
174
|
-
updateMentionSuggestions,
|
175
|
-
/* @conditional-compile-remove(mention) */
|
176
|
-
localeStrings
|
177
|
-
]);
|
178
30
|
const onTextFieldKeyDown = useCallback((ev) => {
|
179
|
-
/* @conditional-compile-remove(mention) */
|
180
|
-
// caretIndex should be set to undefined when the user is typing
|
181
|
-
setCaretIndex(undefined);
|
182
|
-
// shouldHandleOnMouseDownDuringSelect should be set to false after the last mouse down event.
|
183
|
-
// it shouldn't be updated in onMouseUp
|
184
|
-
// as onMouseUp can be triggered before or after onSelect event
|
185
|
-
// because its order depends on mouse events not selection.
|
186
|
-
/* @conditional-compile-remove(mention) */
|
187
|
-
setShouldHandleOnMouseDownDuringSelect(false);
|
188
31
|
if (isEnterKeyEventFromCompositionSession(ev)) {
|
189
32
|
return;
|
190
33
|
}
|
191
|
-
/* @conditional-compile-remove(mention) */
|
192
|
-
if (mentionSuggestions.length > 0) {
|
193
|
-
if (ev.key === 'ArrowUp') {
|
194
|
-
ev.preventDefault();
|
195
|
-
const newActiveIndex = activeSuggestionIndex === undefined
|
196
|
-
? mentionSuggestions.length - 1
|
197
|
-
: Math.max(activeSuggestionIndex - 1, 0);
|
198
|
-
setActiveSuggestionIndex(newActiveIndex);
|
199
|
-
}
|
200
|
-
else if (ev.key === 'ArrowDown') {
|
201
|
-
ev.preventDefault();
|
202
|
-
const newActiveIndex = activeSuggestionIndex === undefined
|
203
|
-
? 0
|
204
|
-
: Math.min(activeSuggestionIndex + 1, mentionSuggestions.length - 1);
|
205
|
-
setActiveSuggestionIndex(newActiveIndex);
|
206
|
-
}
|
207
|
-
else if (ev.key === 'Escape') {
|
208
|
-
updateMentionSuggestions([]);
|
209
|
-
}
|
210
|
-
}
|
211
34
|
if (ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline)) {
|
212
35
|
ev.preventDefault();
|
213
|
-
// If we are looking up a mention, select the focused suggestion
|
214
|
-
/* @conditional-compile-remove(mention) */
|
215
|
-
if (mentionSuggestions.length > 0 && activeSuggestionIndex !== undefined) {
|
216
|
-
const selectedMention = mentionSuggestions[activeSuggestionIndex];
|
217
|
-
if (selectedMention) {
|
218
|
-
onSuggestionSelected(selectedMention);
|
219
|
-
return;
|
220
|
-
}
|
221
|
-
}
|
222
36
|
onEnterKeyDown && onEnterKeyDown();
|
223
37
|
}
|
224
38
|
onKeyDown && onKeyDown(ev);
|
225
|
-
}, [
|
226
|
-
onEnterKeyDown,
|
227
|
-
onKeyDown,
|
228
|
-
supportNewline,
|
229
|
-
/* @conditional-compile-remove(mention) */
|
230
|
-
mentionSuggestions,
|
231
|
-
/* @conditional-compile-remove(mention) */
|
232
|
-
activeSuggestionIndex,
|
233
|
-
/* @conditional-compile-remove(mention) */
|
234
|
-
onSuggestionSelected,
|
235
|
-
/* @conditional-compile-remove(mention) */
|
236
|
-
updateMentionSuggestions
|
237
|
-
]);
|
39
|
+
}, [onEnterKeyDown, onKeyDown, supportNewline]);
|
238
40
|
const onRenderChildren = () => {
|
239
41
|
return (React.createElement(Stack, { horizontal: true, className: mergedChildrenStyle }, children));
|
240
42
|
};
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
let updatedStartIndex = event.currentTarget.selectionStart;
|
259
|
-
let updatedEndIndex = event.currentTarget.selectionEnd;
|
260
|
-
if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
|
261
|
-
event.currentTarget.selectionStart !== null &&
|
262
|
-
event.currentTarget.selectionStart !== -1) {
|
263
|
-
// just a caret movement/usual typing or deleting
|
264
|
-
const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
|
265
|
-
// don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
|
266
|
-
if (mentionTag !== undefined &&
|
267
|
-
mentionTag.plainTextBeginIndex !== undefined &&
|
268
|
-
event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
|
269
|
-
event.currentTarget.selectionStart < ((_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex)) {
|
270
|
-
// get updated selection index
|
271
|
-
const newSelectionIndex = findNewSelectionIndexForMention({
|
272
|
-
tag: mentionTag,
|
273
|
-
textValue: inputTextValue,
|
274
|
-
currentSelectionIndex: event.currentTarget.selectionStart,
|
275
|
-
previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
|
276
|
-
});
|
277
|
-
updatedStartIndex = newSelectionIndex;
|
278
|
-
updatedEndIndex = newSelectionIndex;
|
279
|
-
}
|
280
|
-
}
|
281
|
-
else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
|
282
|
-
// Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
|
283
|
-
if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
|
284
|
-
// the selection start is changed
|
285
|
-
const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
|
286
|
-
// don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
|
287
|
-
if (mentionTag !== undefined &&
|
288
|
-
mentionTag.plainTextBeginIndex !== undefined &&
|
289
|
-
event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
|
290
|
-
event.currentTarget.selectionStart < ((_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex)) {
|
291
|
-
updatedStartIndex = findNewSelectionIndexForMention({
|
292
|
-
tag: mentionTag,
|
293
|
-
textValue: inputTextValue,
|
294
|
-
currentSelectionIndex: event.currentTarget.selectionStart,
|
295
|
-
previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
|
296
|
-
});
|
297
|
-
}
|
298
|
-
}
|
299
|
-
if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
|
300
|
-
// the selection end is changed
|
301
|
-
const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
|
302
|
-
// don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
|
303
|
-
if (mentionTag !== undefined &&
|
304
|
-
mentionTag.plainTextBeginIndex !== undefined &&
|
305
|
-
event.currentTarget.selectionEnd > mentionTag.plainTextBeginIndex &&
|
306
|
-
event.currentTarget.selectionEnd < ((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex)) {
|
307
|
-
updatedEndIndex = findNewSelectionIndexForMention({
|
308
|
-
tag: mentionTag,
|
309
|
-
textValue: inputTextValue,
|
310
|
-
currentSelectionIndex: event.currentTarget.selectionEnd,
|
311
|
-
previousSelectionIndex: selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : inputTextValue.length
|
312
|
-
});
|
313
|
-
}
|
314
|
-
}
|
315
|
-
}
|
316
|
-
// e.currentTarget.selectionDirection should be set to handle shift + arrow keys
|
317
|
-
if (event.currentTarget.selectionDirection === null) {
|
318
|
-
event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
|
319
|
-
}
|
320
|
-
else {
|
321
|
-
event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
|
322
|
-
}
|
323
|
-
setSelectionStartValue(nullToUndefined(updatedStartIndex));
|
324
|
-
setSelectionEndValue(nullToUndefined(updatedEndIndex));
|
325
|
-
}, [setSelectionStartValue, setSelectionEndValue]);
|
326
|
-
/* @conditional-compile-remove(mention) */
|
327
|
-
const handleOnSelect = useCallback(({ event, inputTextValue, tags, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue }) => {
|
328
|
-
if (shouldHandleOnMouseDownDuringSelect) {
|
329
|
-
if (targetSelection !== undefined) {
|
330
|
-
setSelectionStartValue(targetSelection.start);
|
331
|
-
setSelectionEndValue(targetSelection.end);
|
332
|
-
event.currentTarget.setSelectionRange(targetSelection.start, undefinedToNull(targetSelection.end));
|
333
|
-
setTargetSelection(undefined);
|
334
|
-
}
|
335
|
-
else if (event.currentTarget.selectionStart !== null) {
|
336
|
-
// on select was triggered by mouse down/up with no movement
|
337
|
-
const mentionTag = findMentionTagForSelection(tags, event.currentTarget.selectionStart);
|
338
|
-
if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
|
339
|
-
// handle mention click
|
340
|
-
// Get range of word that was clicked on
|
341
|
-
const selectionRange = rangeOfWordInSelection({
|
342
|
-
textInput: inputTextValue,
|
343
|
-
selectionStart: event.currentTarget.selectionStart,
|
344
|
-
selectionEnd: nullToUndefined(event.currentTarget.selectionEnd),
|
345
|
-
tag: mentionTag
|
346
|
-
});
|
347
|
-
if (event.currentTarget.selectionDirection === null) {
|
348
|
-
event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end);
|
349
|
-
}
|
350
|
-
else {
|
351
|
-
event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end, event.currentTarget.selectionDirection);
|
352
|
-
}
|
353
|
-
setSelectionStartValue(selectionRange.start);
|
354
|
-
setSelectionEndValue(selectionRange.end);
|
355
|
-
}
|
356
|
-
else {
|
357
|
-
setSelectionStartValue(event.currentTarget.selectionStart);
|
358
|
-
setSelectionEndValue(nullToUndefined(event.currentTarget.selectionEnd));
|
359
|
-
}
|
360
|
-
}
|
361
|
-
}
|
362
|
-
else {
|
363
|
-
// selection was changed by keyboard
|
364
|
-
updateSelectionIndexesWithMentionIfNeeded({
|
365
|
-
event,
|
366
|
-
inputTextValue,
|
367
|
-
selectionStartValue,
|
368
|
-
selectionEndValue,
|
369
|
-
tagsValue: tags
|
370
|
-
});
|
371
|
-
}
|
372
|
-
// don't set setShouldHandleOnMouseDownDuringSelect(false) here as setSelectionRange
|
373
|
-
// could trigger additional calls of onSelect event and they may not be handled correctly
|
374
|
-
// (because of setSelectionRange calls or rerender)
|
375
|
-
}, [
|
376
|
-
updateSelectionIndexesWithMentionIfNeeded,
|
377
|
-
targetSelection,
|
378
|
-
setTargetSelection,
|
379
|
-
setSelectionStartValue,
|
380
|
-
setSelectionEndValue
|
381
|
-
]);
|
382
|
-
/* @conditional-compile-remove(mention) */
|
383
|
-
const handleOnChange = useCallback(({ currentSelectionEnd, currentSelectionStart, currentTriggerStartIndex, event, htmlTextValue, inputTextValue, previousSelectionEnd, previousSelectionStart, tagsValue, updatedValue }) => __awaiter(void 0, void 0, void 0, function* () {
|
384
|
-
var _b;
|
385
|
-
debouncedQueryUpdate.cancel();
|
386
|
-
if (event.currentTarget === null) {
|
387
|
-
return;
|
388
|
-
}
|
389
|
-
// handle backspace change
|
390
|
-
// onSelect is not called for backspace as selection is not changed and local caret index is outdated
|
391
|
-
setCaretIndex(undefined);
|
392
|
-
const newValue = updatedValue !== null && updatedValue !== void 0 ? updatedValue : '';
|
393
|
-
const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
|
394
|
-
const newTextLength = newValue.length;
|
395
|
-
// updating indexes to set between 0 and text length, otherwise selectionRange won't be set correctly
|
396
|
-
const currentSelectionEndValue = getValidatedIndexInRange({
|
397
|
-
min: 0,
|
398
|
-
max: newTextLength,
|
399
|
-
currentValue: currentSelectionEnd
|
400
|
-
});
|
401
|
-
const currentSelectionStartValue = getValidatedIndexInRange({
|
402
|
-
min: 0,
|
403
|
-
max: newTextLength,
|
404
|
-
currentValue: currentSelectionStart
|
405
|
-
});
|
406
|
-
const previousSelectionStartValue = getValidatedIndexInRange({
|
407
|
-
min: 0,
|
408
|
-
max: inputTextValue.length,
|
409
|
-
currentValue: previousSelectionStart
|
410
|
-
});
|
411
|
-
const previousSelectionEndValue = getValidatedIndexInRange({
|
412
|
-
min: 0,
|
413
|
-
max: inputTextValue.length,
|
414
|
-
currentValue: previousSelectionEnd
|
415
|
-
});
|
416
|
-
// If we are enabled for lookups,
|
417
|
-
if (mentionLookupOptions !== undefined) {
|
418
|
-
// Look at the range of the change for a trigger character
|
419
|
-
const triggerPriorIndex = newValue.lastIndexOf(triggerText, currentSelectionEndValue - 1);
|
420
|
-
// Update the caret position, if not doing a lookup
|
421
|
-
setCaretPosition(Caret.getRelativePosition(event.currentTarget));
|
422
|
-
if (triggerPriorIndex !== undefined) {
|
423
|
-
// trigger is found
|
424
|
-
const isSpaceBeforeTrigger = newValue.substring(triggerPriorIndex - 1, triggerPriorIndex) === ' ';
|
425
|
-
const wordAtSelection = newValue.substring(triggerPriorIndex, currentSelectionEndValue);
|
426
|
-
let tagIndex = currentTriggerStartIndex;
|
427
|
-
if (!isSpaceBeforeTrigger && triggerPriorIndex !== 0) {
|
428
|
-
//no space before the trigger <- continuation of the previous word
|
429
|
-
tagIndex = -1;
|
430
|
-
setCurrentTriggerStartIndex(tagIndex);
|
431
|
-
}
|
432
|
-
else if (wordAtSelection === triggerText) {
|
433
|
-
// start of the mention
|
434
|
-
tagIndex = currentSelectionEndValue - triggerText.length;
|
435
|
-
if (tagIndex < 0) {
|
436
|
-
tagIndex = 0;
|
437
|
-
}
|
438
|
-
setCurrentTriggerStartIndex(tagIndex);
|
439
|
-
}
|
440
|
-
if (tagIndex === -1) {
|
441
|
-
updateMentionSuggestions([]);
|
442
|
-
}
|
443
|
-
else {
|
444
|
-
// In the middle of a @mention lookup
|
445
|
-
if (tagIndex > -1) {
|
446
|
-
const query = wordAtSelection.substring(triggerText.length, wordAtSelection.length);
|
447
|
-
if (query !== undefined) {
|
448
|
-
yield debouncedQueryUpdate(query);
|
449
|
-
}
|
450
|
-
}
|
451
|
-
}
|
452
|
-
}
|
453
|
-
}
|
454
|
-
let result = '';
|
455
|
-
if (tagsValue.length === 0) {
|
456
|
-
// no tags in the string and newValue should be used as a result string
|
457
|
-
result = newValue;
|
458
|
-
}
|
459
|
-
else {
|
460
|
-
// there are tags in the text value and htmlTextValue is html string
|
461
|
-
// find diff between old and new text
|
462
|
-
const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes({
|
463
|
-
oldText: inputTextValue,
|
464
|
-
newText: newValue,
|
465
|
-
previousSelectionStart: previousSelectionStartValue,
|
466
|
-
previousSelectionEnd: previousSelectionEndValue,
|
467
|
-
currentSelectionStart: currentSelectionStartValue,
|
468
|
-
currentSelectionEnd: currentSelectionEndValue
|
469
|
-
});
|
470
|
-
const change = newValue.substring(changeStart, newChangeEnd);
|
471
|
-
// get updated html string
|
472
|
-
const updatedContent = updateHTML({
|
473
|
-
htmlText: htmlTextValue,
|
474
|
-
oldPlainText: inputTextValue,
|
475
|
-
newPlainText: newValue,
|
476
|
-
tags: tagsValue,
|
477
|
-
startIndex: changeStart,
|
478
|
-
oldPlainTextEndIndex: oldChangeEnd,
|
479
|
-
change,
|
480
|
-
mentionTrigger: triggerText
|
481
|
-
});
|
482
|
-
result = updatedContent.updatedHTML;
|
483
|
-
// update caret index if needed
|
484
|
-
if (updatedContent.updatedSelectionIndex !== undefined) {
|
485
|
-
setCaretIndex(updatedContent.updatedSelectionIndex);
|
486
|
-
setSelectionEndValue(updatedContent.updatedSelectionIndex);
|
487
|
-
setSelectionStartValue(updatedContent.updatedSelectionIndex);
|
488
|
-
}
|
489
|
-
}
|
490
|
-
onChange && onChange(event, result);
|
491
|
-
}), [onChange, mentionLookupOptions, setCaretIndex, setCaretPosition, updateMentionSuggestions, debouncedQueryUpdate]);
|
492
|
-
const getInputFieldTextValue = () => {
|
43
|
+
const renderTextField = () => {
|
44
|
+
const textFieldProps = {
|
45
|
+
autoFocus: props.autoFocus === 'sendBoxTextField',
|
46
|
+
multiline: true,
|
47
|
+
autoAdjustHeight: true,
|
48
|
+
multiple: false,
|
49
|
+
resizable: false,
|
50
|
+
componentRef: textFieldRef,
|
51
|
+
id,
|
52
|
+
inputClassName: mergedInputFieldStyle,
|
53
|
+
placeholder: placeholderText,
|
54
|
+
autoComplete: 'off',
|
55
|
+
styles: mergedTextFieldStyle,
|
56
|
+
disabled,
|
57
|
+
errorMessage,
|
58
|
+
onRenderSuffix: onRenderChildren
|
59
|
+
};
|
493
60
|
/* @conditional-compile-remove(mention) */
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
const direction = event.clientX > interactionStartPoint.x ? 'forward' : 'backward';
|
509
|
-
const mentionTag = findMentionTagForSelection(tagsValue, direction === 'backward'
|
510
|
-
? event.currentTarget.selectionStart
|
511
|
-
: (_a = event.currentTarget.selectionEnd) !== null && _a !== void 0 ? _a : event.currentTarget.selectionStart);
|
512
|
-
let updateCurrentTarget = false;
|
513
|
-
if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
|
514
|
-
targetStart = Math.min(mentionTag.plainTextBeginIndex, targetStart);
|
515
|
-
if (mentionTag.plainTextEndIndex !== undefined && targetEnd !== null) {
|
516
|
-
targetEnd = Math.max(mentionTag.plainTextEndIndex, targetEnd);
|
517
|
-
}
|
518
|
-
updateCurrentTarget = true;
|
519
|
-
setShouldHandleOnMouseDownDuringSelect(false);
|
520
|
-
}
|
521
|
-
// Update selection range
|
522
|
-
setTargetSelection({ start: targetStart, end: targetEnd });
|
523
|
-
if (updateCurrentTarget) {
|
524
|
-
// Only set the control, if the values are updated
|
525
|
-
event.currentTarget.setSelectionRange(targetStart, targetEnd, direction);
|
526
|
-
}
|
527
|
-
}
|
528
|
-
}, [setTargetSelection, targetSelection, setShouldHandleOnMouseDownDuringSelect, interactionStartPoint, tagsValue]);
|
529
|
-
/* @conditional-compile-remove(mention) */
|
530
|
-
const announcerText = useMemo(() => {
|
531
|
-
if (activeSuggestionIndex === undefined) {
|
532
|
-
return undefined;
|
61
|
+
const textFieldWithMentionProps = {
|
62
|
+
textFieldProps: textFieldProps,
|
63
|
+
dataUiId: dataUiId,
|
64
|
+
textValue: textValue,
|
65
|
+
onChange: onChange,
|
66
|
+
onKeyDown: onKeyDown,
|
67
|
+
onEnterKeyDown: onEnterKeyDown,
|
68
|
+
textFieldRef: textFieldRef,
|
69
|
+
supportNewline: supportNewline,
|
70
|
+
mentionLookupOptions: props.mentionLookupOptions
|
71
|
+
};
|
72
|
+
/* @conditional-compile-remove(mention) */
|
73
|
+
if (props.mentionLookupOptions) {
|
74
|
+
return React.createElement(TextFieldWithMention, Object.assign({}, textFieldWithMentionProps));
|
533
75
|
}
|
534
|
-
|
535
|
-
|
536
|
-
? currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText
|
537
|
-
: localeStrings.participantItem.displayNamePlaceholder;
|
538
|
-
}, [activeSuggestionIndex, mentionSuggestions, localeStrings.participantItem.displayNamePlaceholder]);
|
76
|
+
return (React.createElement(TextField, Object.assign({}, textFieldProps, { "data-ui-id": dataUiId, value: textValue, onChange: onChange, onKeyDown: onTextFieldKeyDown })));
|
77
|
+
};
|
539
78
|
return (React.createElement(Stack, { className: mergedRootStyle },
|
540
|
-
React.createElement("div", { className: mergedTextContainerStyle },
|
541
|
-
/* @conditional-compile-remove(mention) */ mentionSuggestions.length > 0 && (React.createElement(_MentionPopover, { suggestions: mentionSuggestions, activeSuggestionIndex: activeSuggestionIndex, target: inputBoxRef, targetPositionOffset: caretPosition, onRenderSuggestionItem: mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onRenderSuggestionItem, onSuggestionSelected: onSuggestionSelected, onDismiss: () => {
|
542
|
-
updateMentionSuggestions([]);
|
543
|
-
} })),
|
544
|
-
/* @conditional-compile-remove(mention) */ announcerText !== undefined && (React.createElement(Announcer, { announcementString: announcerText, ariaLive: 'polite' })),
|
545
|
-
React.createElement(TextField, { autoFocus: props.autoFocus === 'sendBoxTextField', "data-ui-id": dataUiId, multiline: true, autoAdjustHeight: true, multiple: false, resizable: false, componentRef: textFieldRef, id: id, inputClassName: mergedInputFieldStyle, placeholder: placeholderText, value: getInputFieldTextValue(), onChange: (e, newValue) => {
|
546
|
-
// Remove when switching to react 17+, currently needed because of https://legacy.reactjs.org/docs/legacy-event-pooling.html
|
547
|
-
/* @conditional-compile-remove(mention) */
|
548
|
-
// Prevents React from resetting event's properties
|
549
|
-
e.persist();
|
550
|
-
/* @conditional-compile-remove(mention) */
|
551
|
-
setInputTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
|
552
|
-
/* @conditional-compile-remove(mention) */
|
553
|
-
handleOnChange({
|
554
|
-
event: e,
|
555
|
-
tagsValue,
|
556
|
-
htmlTextValue: textValue,
|
557
|
-
inputTextValue,
|
558
|
-
currentTriggerStartIndex,
|
559
|
-
previousSelectionStart: nullToUndefined(selectionStartValue),
|
560
|
-
previousSelectionEnd: nullToUndefined(selectionEndValue),
|
561
|
-
currentSelectionStart: nullToUndefined(e.currentTarget.selectionStart),
|
562
|
-
currentSelectionEnd: nullToUndefined(e.currentTarget.selectionEnd),
|
563
|
-
updatedValue: newValue
|
564
|
-
});
|
565
|
-
/* @conditional-compile-remove(mention) */
|
566
|
-
return;
|
567
|
-
onChange(e, newValue);
|
568
|
-
},
|
569
|
-
/* @conditional-compile-remove(mention) */
|
570
|
-
onSelect: (e) => {
|
571
|
-
// update selection if needed
|
572
|
-
if (caretIndex !== undefined) {
|
573
|
-
setCaretIndex(undefined);
|
574
|
-
// sometimes setting selectionRage in effect for updating caretIndex doesn't work as expected and onSelect should handle this case
|
575
|
-
if (caretIndex !== e.currentTarget.selectionStart || caretIndex !== e.currentTarget.selectionEnd) {
|
576
|
-
e.currentTarget.setSelectionRange(caretIndex, caretIndex);
|
577
|
-
}
|
578
|
-
return;
|
579
|
-
}
|
580
|
-
handleOnSelect({
|
581
|
-
event: e,
|
582
|
-
inputTextValue,
|
583
|
-
shouldHandleOnMouseDownDuringSelect,
|
584
|
-
selectionEndValue,
|
585
|
-
selectionStartValue,
|
586
|
-
tags: tagsValue
|
587
|
-
});
|
588
|
-
},
|
589
|
-
/* @conditional-compile-remove(mention) */
|
590
|
-
onMouseDown: (e) => {
|
591
|
-
setInteractionStartPoint({ x: e.clientX, y: e.clientY });
|
592
|
-
// as events order is onMouseDown -> onMouseMove -> onMouseUp -> onSelect -> onClick
|
593
|
-
// onClick and onMouseDown can't handle clicking on mention event because
|
594
|
-
// onMouseDown doesn't have correct selectionRange yet and
|
595
|
-
// onClick already has wrong range as it's called after onSelect that updates the selection range
|
596
|
-
// so we need to handle onMouseDown to prevent onSelect default behavior
|
597
|
-
setShouldHandleOnMouseDownDuringSelect(true);
|
598
|
-
},
|
599
|
-
/* @conditional-compile-remove(mention) */
|
600
|
-
onMouseMove: handleOnMove,
|
601
|
-
/* @conditional-compile-remove(mention) */
|
602
|
-
onMouseUp: () => {
|
603
|
-
setInteractionStartPoint(undefined);
|
604
|
-
},
|
605
|
-
/* @conditional-compile-remove(mention) */
|
606
|
-
onTouchStart: (e) => {
|
607
|
-
setInteractionStartPoint({
|
608
|
-
x: e.targetTouches.item(0).clientX,
|
609
|
-
y: e.targetTouches.item(0).clientY
|
610
|
-
});
|
611
|
-
// see onMouseDown for more details
|
612
|
-
setShouldHandleOnMouseDownDuringSelect(true);
|
613
|
-
},
|
614
|
-
/* @conditional-compile-remove(mention) */
|
615
|
-
onTouchMove: handleOnMove,
|
616
|
-
/* @conditional-compile-remove(mention) */
|
617
|
-
onTouchEnd: () => {
|
618
|
-
setInteractionStartPoint(undefined);
|
619
|
-
},
|
620
|
-
/* @conditional-compile-remove(mention) */
|
621
|
-
onBlur: () => {
|
622
|
-
// setup all flags to default values when text field loses focus
|
623
|
-
setShouldHandleOnMouseDownDuringSelect(false);
|
624
|
-
setCaretIndex(undefined);
|
625
|
-
setSelectionStartValue(undefined);
|
626
|
-
setSelectionEndValue(undefined);
|
627
|
-
// Dismiss the suggestions on blur, after enough time to select by mouse if needed
|
628
|
-
setTimeout(() => {
|
629
|
-
setMentionSuggestions([]);
|
630
|
-
}, 200);
|
631
|
-
}, autoComplete: "off", onKeyDown: onTextFieldKeyDown, styles: mergedTextFieldStyle, disabled: disabled, errorMessage: errorMessage, onRenderSuffix: onRenderChildren, elementRef: inputBoxRef }))));
|
79
|
+
React.createElement("div", { className: mergedTextContainerStyle }, renderTextField())));
|
632
80
|
};
|
633
81
|
/**
|
634
82
|
* @private
|
@@ -654,789 +102,4 @@ export const InputBoxButton = (props) => {
|
|
654
102
|
// VoiceOver fix: Avoid icon from stealing focus when IconButton is double-tapped to send message by wrapping with Stack with pointerEvents style to none
|
655
103
|
onRenderIcon: () => React.createElement(Stack, { className: iconWrapperStyle }, onRenderIcon(isHover)) })));
|
656
104
|
};
|
657
|
-
/* @conditional-compile-remove(mention) */
|
658
|
-
/**
|
659
|
-
* Get validated value for index between min and max values. If currentValue is not defined, -1 will be used instead.
|
660
|
-
*
|
661
|
-
* @private
|
662
|
-
* @param props - Props for finding a valid index in range.
|
663
|
-
* @returns Valid index in the range.
|
664
|
-
*/
|
665
|
-
const getValidatedIndexInRange = (props) => {
|
666
|
-
const { min, max, currentValue } = props;
|
667
|
-
let updatedValue = currentValue !== null && currentValue !== void 0 ? currentValue : -1;
|
668
|
-
updatedValue = Math.max(min, updatedValue);
|
669
|
-
updatedValue = Math.min(updatedValue, max);
|
670
|
-
return updatedValue;
|
671
|
-
};
|
672
|
-
/* @conditional-compile-remove(mention) */
|
673
|
-
/**
|
674
|
-
* Find mention tag for selection if exists.
|
675
|
-
*
|
676
|
-
* @private
|
677
|
-
* @param tags - Existing list of tags.
|
678
|
-
* @param selection - Selection index.
|
679
|
-
* @returns Mention tag if exists, otherwise undefined.
|
680
|
-
*/
|
681
|
-
const findMentionTagForSelection = (tags, selection) => {
|
682
|
-
let mentionTag = undefined;
|
683
|
-
tags.every((tag) => {
|
684
|
-
const closingTagInfo = getTagClosingTagInfo(tag);
|
685
|
-
if (tag.plainTextBeginIndex !== undefined && tag.plainTextBeginIndex > selection) {
|
686
|
-
// no need to check further as the selection is before the tag
|
687
|
-
return false;
|
688
|
-
}
|
689
|
-
else if (tag.plainTextBeginIndex !== undefined &&
|
690
|
-
tag.plainTextBeginIndex <= selection &&
|
691
|
-
selection <= closingTagInfo.plainTextEndIndex) {
|
692
|
-
// no need to check if tag doesn't contain selection
|
693
|
-
if (tag.subTags !== undefined && tag.subTags.length !== 0) {
|
694
|
-
const selectedTag = findMentionTagForSelection(tag.subTags, selection);
|
695
|
-
if (selectedTag !== undefined) {
|
696
|
-
mentionTag = selectedTag;
|
697
|
-
return false;
|
698
|
-
}
|
699
|
-
}
|
700
|
-
else if (tag.tagType === MSFT_MENTION_TAG) {
|
701
|
-
mentionTag = tag;
|
702
|
-
return false;
|
703
|
-
}
|
704
|
-
}
|
705
|
-
return true;
|
706
|
-
});
|
707
|
-
return mentionTag;
|
708
|
-
};
|
709
|
-
/* @conditional-compile-remove(mention) */
|
710
|
-
const rangeOfWordInSelection = ({ textInput, selectionStart, selectionEnd, tag }) => {
|
711
|
-
var _a;
|
712
|
-
if (tag.plainTextBeginIndex === undefined) {
|
713
|
-
return { start: selectionStart, end: selectionEnd === undefined ? selectionStart : selectionEnd };
|
714
|
-
}
|
715
|
-
// Look at start word index and optionally end word index.
|
716
|
-
// Select combination of the two and return the range.
|
717
|
-
let start = selectionStart;
|
718
|
-
let end = selectionEnd === undefined ? selectionStart : selectionEnd;
|
719
|
-
const firstWordStartIndex = textInput.lastIndexOf(' ', selectionStart);
|
720
|
-
if (firstWordStartIndex === tag.plainTextBeginIndex) {
|
721
|
-
start = firstWordStartIndex;
|
722
|
-
}
|
723
|
-
else {
|
724
|
-
start = Math.max(firstWordStartIndex + 1, tag.plainTextBeginIndex);
|
725
|
-
}
|
726
|
-
const firstWordEndIndex = textInput.indexOf(' ', selectionStart);
|
727
|
-
end = Math.max(firstWordEndIndex + 1, (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : firstWordEndIndex + 1);
|
728
|
-
if (selectionEnd !== undefined && tag.plainTextEndIndex !== undefined) {
|
729
|
-
const lastWordEndIndex = textInput.indexOf(' ', selectionEnd);
|
730
|
-
end = Math.max(lastWordEndIndex > -1 ? lastWordEndIndex : tag.plainTextEndIndex, selectionEnd);
|
731
|
-
}
|
732
|
-
return { start, end };
|
733
|
-
};
|
734
|
-
/* @conditional-compile-remove(mention) */
|
735
|
-
/**
|
736
|
-
* Find a new the selection index.
|
737
|
-
*
|
738
|
-
* @private
|
739
|
-
* @param props - Props for finding new selection index for mention.
|
740
|
-
* @returns New selection index if it is inside of a mention tag, otherwise the current selection.
|
741
|
-
*/
|
742
|
-
const findNewSelectionIndexForMention = (props) => {
|
743
|
-
var _a;
|
744
|
-
const { tag, textValue, currentSelectionIndex, previousSelectionIndex } = props;
|
745
|
-
// check if this is a mention tag and selection should be updated
|
746
|
-
if (tag.tagType !== MSFT_MENTION_TAG ||
|
747
|
-
tag.plainTextBeginIndex === undefined ||
|
748
|
-
currentSelectionIndex === previousSelectionIndex ||
|
749
|
-
tag.plainTextEndIndex === undefined) {
|
750
|
-
return currentSelectionIndex;
|
751
|
-
}
|
752
|
-
let spaceIndex = 0;
|
753
|
-
if (currentSelectionIndex <= previousSelectionIndex) {
|
754
|
-
// the cursor is moved to the left, find the last index before the cursor
|
755
|
-
spaceIndex = textValue.lastIndexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
|
756
|
-
if (spaceIndex === -1) {
|
757
|
-
// no space before the selection, use the beginning of the tag
|
758
|
-
spaceIndex = tag.plainTextBeginIndex;
|
759
|
-
}
|
760
|
-
}
|
761
|
-
else {
|
762
|
-
// the cursor is moved to the right, find the fist index after the cursor
|
763
|
-
spaceIndex = textValue.indexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
|
764
|
-
if (spaceIndex === -1) {
|
765
|
-
// no space after the selection, use the end of the tag
|
766
|
-
spaceIndex = (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : tag.plainTextBeginIndex;
|
767
|
-
}
|
768
|
-
}
|
769
|
-
spaceIndex = Math.max(tag.plainTextBeginIndex, spaceIndex);
|
770
|
-
spaceIndex = Math.min(tag.plainTextEndIndex, spaceIndex);
|
771
|
-
return spaceIndex;
|
772
|
-
};
|
773
|
-
/* @conditional-compile-remove(mention) */
|
774
|
-
/**
|
775
|
-
* Handle mention tag edit and by word deleting
|
776
|
-
*
|
777
|
-
* @private
|
778
|
-
* @param props - Props for mention update HTML function.
|
779
|
-
* @returns Updated texts and indexes.
|
780
|
-
*/
|
781
|
-
const handleMentionTagUpdate = (props) => {
|
782
|
-
const { htmlText, oldPlainText, change, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength } = props;
|
783
|
-
let processedChange = props.processedChange;
|
784
|
-
let lastProcessedHTMLIndex = props.lastProcessedHTMLIndex;
|
785
|
-
if (tag.tagType !== MSFT_MENTION_TAG || tag.plainTextBeginIndex === undefined) {
|
786
|
-
// not a mention tag
|
787
|
-
return {
|
788
|
-
result: '',
|
789
|
-
updatedChange: processedChange,
|
790
|
-
htmlIndex: lastProcessedHTMLIndex,
|
791
|
-
plainTextSelectionEndIndex: undefined
|
792
|
-
};
|
793
|
-
}
|
794
|
-
let result = '';
|
795
|
-
let plainTextSelectionEndIndex;
|
796
|
-
let rangeStart;
|
797
|
-
let rangeEnd;
|
798
|
-
// check if space symbol is handled in case if string looks like '<1 2 3>'
|
799
|
-
let isSpaceLengthHandled = false;
|
800
|
-
rangeStart = oldPlainText.lastIndexOf(' ', startIndex);
|
801
|
-
if (rangeStart !== -1 && rangeStart !== undefined && rangeStart > tag.plainTextBeginIndex) {
|
802
|
-
isSpaceLengthHandled = true;
|
803
|
-
}
|
804
|
-
rangeEnd = oldPlainText.indexOf(' ', oldPlainTextEndIndex);
|
805
|
-
if (rangeEnd === -1 || rangeEnd === undefined) {
|
806
|
-
// check if space symbol is not found
|
807
|
-
rangeEnd = plainTextEndIndex;
|
808
|
-
}
|
809
|
-
else if (!isSpaceLengthHandled) {
|
810
|
-
// +1 to include the space symbol
|
811
|
-
rangeEnd += 1;
|
812
|
-
}
|
813
|
-
isSpaceLengthHandled = true;
|
814
|
-
if (rangeStart === -1 || rangeStart === undefined || rangeStart < tag.plainTextBeginIndex) {
|
815
|
-
// rangeStart should be at least equal to tag.plainTextBeginIndex
|
816
|
-
rangeStart = tag.plainTextBeginIndex;
|
817
|
-
}
|
818
|
-
if (rangeEnd > plainTextEndIndex) {
|
819
|
-
// rangeEnd should be at most equal to plainTextEndIndex
|
820
|
-
rangeEnd = plainTextEndIndex;
|
821
|
-
}
|
822
|
-
if (rangeStart === tag.plainTextBeginIndex && rangeEnd === plainTextEndIndex) {
|
823
|
-
// the whole tag should be removed
|
824
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
|
825
|
-
plainTextSelectionEndIndex = tag.plainTextBeginIndex + processedChange.length;
|
826
|
-
processedChange = '';
|
827
|
-
lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
|
828
|
-
}
|
829
|
-
else {
|
830
|
-
// only part of the tag should be removed
|
831
|
-
let startChangeDiff = 0;
|
832
|
-
let endChangeDiff = 0;
|
833
|
-
// need to check only rangeStart > tag.plainTextBeginIndex as when rangeStart === tag.plainTextBeginIndex startChangeDiff = 0 and mentionTagLength shouldn't be subtracted
|
834
|
-
if (rangeStart > tag.plainTextBeginIndex) {
|
835
|
-
startChangeDiff = rangeStart - tag.plainTextBeginIndex - mentionTagLength;
|
836
|
-
}
|
837
|
-
endChangeDiff = rangeEnd - tag.plainTextBeginIndex - mentionTagLength;
|
838
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff);
|
839
|
-
if (startIndex < tag.plainTextBeginIndex) {
|
840
|
-
// if the change is before the tag, the selection should start from startIndex (rangeStart will be equal to tag.plainTextBeginIndex)
|
841
|
-
plainTextSelectionEndIndex = startIndex + change.length;
|
842
|
-
}
|
843
|
-
else {
|
844
|
-
// if the change is inside the tag, the selection should start with rangeStart
|
845
|
-
plainTextSelectionEndIndex = rangeStart + processedChange.length;
|
846
|
-
}
|
847
|
-
lastProcessedHTMLIndex = tag.openTagIndex + tag.openTagBody.length + endChangeDiff;
|
848
|
-
// processed change should not be changed as it should be added after the tag
|
849
|
-
}
|
850
|
-
return { result, updatedChange: processedChange, htmlIndex: lastProcessedHTMLIndex, plainTextSelectionEndIndex };
|
851
|
-
};
|
852
|
-
/* @conditional-compile-remove(mention) */
|
853
|
-
/**
|
854
|
-
* Get closing tag information if exists otherwise return information as for self closing tag
|
855
|
-
*
|
856
|
-
* @private
|
857
|
-
* @param tag - Tag data.
|
858
|
-
* @returns Closing tag information for the provided tag.
|
859
|
-
*/
|
860
|
-
const getTagClosingTagInfo = (tag) => {
|
861
|
-
let plainTextEndIndex = 0;
|
862
|
-
let closeTagIdx = 0;
|
863
|
-
let closeTagLength = 0;
|
864
|
-
if (tag.plainTextEndIndex !== undefined && tag.closingTagIndex !== undefined) {
|
865
|
-
// close tag exists
|
866
|
-
plainTextEndIndex = tag.plainTextEndIndex;
|
867
|
-
closeTagIdx = tag.closingTagIndex;
|
868
|
-
// tag.tagType.length + </>
|
869
|
-
closeTagLength = tag.tagType.length + 3;
|
870
|
-
}
|
871
|
-
else if (tag.plainTextBeginIndex !== undefined) {
|
872
|
-
// no close tag
|
873
|
-
plainTextEndIndex = tag.plainTextBeginIndex;
|
874
|
-
closeTagIdx = tag.openTagIndex + tag.openTagBody.length;
|
875
|
-
closeTagLength = 0;
|
876
|
-
}
|
877
|
-
return { plainTextEndIndex, closeTagIdx, closeTagLength };
|
878
|
-
};
|
879
|
-
/* @conditional-compile-remove(mention) */
|
880
|
-
/**
|
881
|
-
* Go through the text and update it with the changed text
|
882
|
-
*
|
883
|
-
* @private
|
884
|
-
* @param props - Props for update HTML function.
|
885
|
-
* @returns Updated HTML and selection index if the selection index should be set.
|
886
|
-
*/
|
887
|
-
const updateHTML = (props) => {
|
888
|
-
const { htmlText, oldPlainText, newPlainText, tags, startIndex, oldPlainTextEndIndex, change, mentionTrigger } = props;
|
889
|
-
if (tags.length === 0 || (startIndex === 0 && oldPlainTextEndIndex === oldPlainText.length - 1)) {
|
890
|
-
// no tags added yet or the whole text is changed
|
891
|
-
return { updatedHTML: newPlainText, updatedSelectionIndex: undefined };
|
892
|
-
}
|
893
|
-
let result = '';
|
894
|
-
let lastProcessedHTMLIndex = 0;
|
895
|
-
// the value can be updated with empty string when the change covers more than 1 place (tag + before or after the tag)
|
896
|
-
// in this case change won't be added as part of the tag
|
897
|
-
// e.g.: change is before and partially in tag => change will be added before the tag and outdated text in the tag will be removed
|
898
|
-
// e.g.: change is after and partially in tag => change will be added after the tag and outdated text in the tag will be removed
|
899
|
-
// e.g.: change is on the beginning of the tag => change will be added before the tag
|
900
|
-
// e.g.: change is on the end of the tag => change will be added to the tag if it's not mention and after the tag if it's mention
|
901
|
-
let processedChange = change;
|
902
|
-
// end tag plain text index of the last processed tag
|
903
|
-
let lastProcessedPlainTextTagEndIndex = 0;
|
904
|
-
// as some tags/text can be removed fully, selection should be updated correctly
|
905
|
-
let changeNewEndIndex;
|
906
|
-
for (let i = 0; i < tags.length; i++) {
|
907
|
-
const tag = tags[i];
|
908
|
-
if (tag.plainTextBeginIndex === undefined) {
|
909
|
-
continue;
|
910
|
-
}
|
911
|
-
// all plain text indexes includes trigger length for the mention that shouldn't be included in
|
912
|
-
// htmlText.substring because html strings don't include the trigger
|
913
|
-
// mentionTagLength will be set only for mention tag, otherwise should be 0
|
914
|
-
let mentionTagLength = 0;
|
915
|
-
let isMentionTag = false;
|
916
|
-
if (tag.tagType === MSFT_MENTION_TAG) {
|
917
|
-
mentionTagLength = mentionTrigger.length;
|
918
|
-
isMentionTag = true;
|
919
|
-
}
|
920
|
-
if (startIndex <= tag.plainTextBeginIndex) {
|
921
|
-
// change start is before the open tag
|
922
|
-
// Math.max(lastProcessedPlainTextTagEndIndex, startIndex) is used as startIndex may not be in [[previous tag].plainTextEndIndex - tag.plainTextBeginIndex] range
|
923
|
-
const startChangeDiff = tag.plainTextBeginIndex - Math.max(lastProcessedPlainTextTagEndIndex, startIndex);
|
924
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex - startChangeDiff) + processedChange;
|
925
|
-
processedChange = '';
|
926
|
-
if (oldPlainTextEndIndex <= tag.plainTextBeginIndex) {
|
927
|
-
// the whole change is before tag start
|
928
|
-
// mentionTag length can be ignored here as the change is before the tag
|
929
|
-
const endChangeDiff = tag.plainTextBeginIndex - oldPlainTextEndIndex;
|
930
|
-
lastProcessedHTMLIndex = tag.openTagIndex - endChangeDiff;
|
931
|
-
// the change is handled; exit
|
932
|
-
break;
|
933
|
-
}
|
934
|
-
else {
|
935
|
-
// change continues in the tag
|
936
|
-
lastProcessedHTMLIndex = tag.openTagIndex;
|
937
|
-
// proceed to the next check
|
938
|
-
}
|
939
|
-
}
|
940
|
-
const closingTagInfo = getTagClosingTagInfo(tag);
|
941
|
-
if (startIndex <= closingTagInfo.plainTextEndIndex) {
|
942
|
-
// change started before the end tag
|
943
|
-
if (startIndex <= tag.plainTextBeginIndex && oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
|
944
|
-
// the change is a tag or starts before the tag
|
945
|
-
// tag should be removed, no matter if there are sub-tags
|
946
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
|
947
|
-
processedChange = '';
|
948
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
|
949
|
-
// the change is handled; exit
|
950
|
-
break;
|
951
|
-
}
|
952
|
-
else if (startIndex >= tag.plainTextBeginIndex && oldPlainTextEndIndex <= closingTagInfo.plainTextEndIndex) {
|
953
|
-
// the change is between the tag
|
954
|
-
if (isMentionTag) {
|
955
|
-
if (change !== '') {
|
956
|
-
if (startIndex !== tag.plainTextBeginIndex && startIndex !== closingTagInfo.plainTextEndIndex) {
|
957
|
-
// mention tag should be deleted when user tries to edit it in the middle
|
958
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
|
959
|
-
changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
|
960
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
|
961
|
-
}
|
962
|
-
else if (startIndex === tag.plainTextBeginIndex) {
|
963
|
-
// non empty change at the beginning of the mention tag to be added before the mention tag
|
964
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
|
965
|
-
changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
|
966
|
-
lastProcessedHTMLIndex = tag.openTagIndex;
|
967
|
-
}
|
968
|
-
else if (startIndex === closingTagInfo.plainTextEndIndex) {
|
969
|
-
// non empty change at the end of the mention tag to be added after the mention tag
|
970
|
-
result +=
|
971
|
-
htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
|
972
|
-
processedChange;
|
973
|
-
changeNewEndIndex = closingTagInfo.plainTextEndIndex + processedChange.length;
|
974
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
|
975
|
-
}
|
976
|
-
processedChange = '';
|
977
|
-
}
|
978
|
-
else {
|
979
|
-
const updateMentionTagResult = handleMentionTagUpdate({
|
980
|
-
htmlText,
|
981
|
-
oldPlainText,
|
982
|
-
lastProcessedHTMLIndex,
|
983
|
-
processedChange,
|
984
|
-
change,
|
985
|
-
tag,
|
986
|
-
closeTagIdx: closingTagInfo.closeTagIdx,
|
987
|
-
closeTagLength: closingTagInfo.closeTagLength,
|
988
|
-
plainTextEndIndex: closingTagInfo.plainTextEndIndex,
|
989
|
-
startIndex,
|
990
|
-
oldPlainTextEndIndex,
|
991
|
-
mentionTagLength
|
992
|
-
});
|
993
|
-
result += updateMentionTagResult.result;
|
994
|
-
changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
|
995
|
-
processedChange = updateMentionTagResult.updatedChange;
|
996
|
-
lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
|
997
|
-
}
|
998
|
-
}
|
999
|
-
else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
|
1000
|
-
// with subtags
|
1001
|
-
const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
|
1002
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
|
1003
|
-
const updatedContent = updateHTML({
|
1004
|
-
htmlText: tag.content,
|
1005
|
-
oldPlainText,
|
1006
|
-
newPlainText,
|
1007
|
-
tags: tag.subTags,
|
1008
|
-
startIndex,
|
1009
|
-
oldPlainTextEndIndex,
|
1010
|
-
change: processedChange,
|
1011
|
-
mentionTrigger
|
1012
|
-
});
|
1013
|
-
result += stringBefore + updatedContent.updatedHTML;
|
1014
|
-
changeNewEndIndex = updatedContent.updatedSelectionIndex;
|
1015
|
-
}
|
1016
|
-
else {
|
1017
|
-
// no subtags
|
1018
|
-
const startChangeDiff = startIndex - tag.plainTextBeginIndex;
|
1019
|
-
result +=
|
1020
|
-
htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff) +
|
1021
|
-
processedChange;
|
1022
|
-
processedChange = '';
|
1023
|
-
if (oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
|
1024
|
-
const endChangeDiff = oldPlainTextEndIndex - tag.plainTextBeginIndex;
|
1025
|
-
lastProcessedHTMLIndex = tag.openTagIndex + tag.openTagBody.length + endChangeDiff;
|
1026
|
-
}
|
1027
|
-
else if (oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
|
1028
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
|
1029
|
-
}
|
1030
|
-
}
|
1031
|
-
// the change is handled; exit
|
1032
|
-
break;
|
1033
|
-
}
|
1034
|
-
else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
|
1035
|
-
// the change started in the tag but finishes somewhere further
|
1036
|
-
const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
|
1037
|
-
if (isMentionTag) {
|
1038
|
-
const updateMentionTagResult = handleMentionTagUpdate({
|
1039
|
-
htmlText,
|
1040
|
-
oldPlainText,
|
1041
|
-
lastProcessedHTMLIndex,
|
1042
|
-
processedChange: '',
|
1043
|
-
change,
|
1044
|
-
tag,
|
1045
|
-
closeTagIdx: closingTagInfo.closeTagIdx,
|
1046
|
-
closeTagLength: closingTagInfo.closeTagLength,
|
1047
|
-
plainTextEndIndex: closingTagInfo.plainTextEndIndex,
|
1048
|
-
startIndex,
|
1049
|
-
oldPlainTextEndIndex,
|
1050
|
-
mentionTagLength
|
1051
|
-
});
|
1052
|
-
result += updateMentionTagResult.result;
|
1053
|
-
lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
|
1054
|
-
// no need to handle plainTextSelectionEndIndex as the change will be added later
|
1055
|
-
}
|
1056
|
-
else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
|
1057
|
-
// with subtags
|
1058
|
-
const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
|
1059
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
|
1060
|
-
const updatedContent = updateHTML({
|
1061
|
-
htmlText: tag.content,
|
1062
|
-
oldPlainText,
|
1063
|
-
newPlainText,
|
1064
|
-
tags: tag.subTags,
|
1065
|
-
startIndex,
|
1066
|
-
oldPlainTextEndIndex,
|
1067
|
-
change: '',
|
1068
|
-
mentionTrigger
|
1069
|
-
});
|
1070
|
-
result += stringBefore + updatedContent.updatedHTML;
|
1071
|
-
}
|
1072
|
-
else {
|
1073
|
-
// no subtags
|
1074
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff);
|
1075
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
|
1076
|
-
}
|
1077
|
-
// proceed with the next calculations
|
1078
|
-
}
|
1079
|
-
else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
|
1080
|
-
// the change starts before the tag and finishes after it
|
1081
|
-
// tag should be removed, no matter if there are subtags
|
1082
|
-
// no need to save anything between lastProcessedHTMLIndex and closeTagIdx + closeTagLength
|
1083
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
|
1084
|
-
// proceed with the next calculations
|
1085
|
-
}
|
1086
|
-
else if (startIndex === tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
|
1087
|
-
// the change starts in the tag and finishes after it
|
1088
|
-
// tag should be removed, no matter if there are subtags
|
1089
|
-
result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex);
|
1090
|
-
// processedChange shouldn't be updated as it will be added after the tag
|
1091
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
|
1092
|
-
// proceed with the next calculations
|
1093
|
-
}
|
1094
|
-
else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
|
1095
|
-
// the change starts before the tag and ends in a tag
|
1096
|
-
if (isMentionTag) {
|
1097
|
-
// mention tag
|
1098
|
-
const updateMentionTagResult = handleMentionTagUpdate({
|
1099
|
-
htmlText,
|
1100
|
-
oldPlainText,
|
1101
|
-
lastProcessedHTMLIndex,
|
1102
|
-
processedChange: '',
|
1103
|
-
change,
|
1104
|
-
tag,
|
1105
|
-
closeTagIdx: closingTagInfo.closeTagIdx,
|
1106
|
-
closeTagLength: closingTagInfo.closeTagLength,
|
1107
|
-
plainTextEndIndex: closingTagInfo.plainTextEndIndex,
|
1108
|
-
startIndex,
|
1109
|
-
oldPlainTextEndIndex,
|
1110
|
-
mentionTagLength
|
1111
|
-
});
|
1112
|
-
changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
|
1113
|
-
result += updateMentionTagResult.result;
|
1114
|
-
lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
|
1115
|
-
}
|
1116
|
-
else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
|
1117
|
-
// with subtags
|
1118
|
-
const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
|
1119
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
|
1120
|
-
const updatedContent = updateHTML({
|
1121
|
-
htmlText: tag.content,
|
1122
|
-
oldPlainText,
|
1123
|
-
newPlainText,
|
1124
|
-
tags: tag.subTags,
|
1125
|
-
startIndex,
|
1126
|
-
oldPlainTextEndIndex,
|
1127
|
-
change: processedChange,
|
1128
|
-
mentionTrigger
|
1129
|
-
});
|
1130
|
-
processedChange = '';
|
1131
|
-
result += stringBefore + updatedContent.updatedHTML;
|
1132
|
-
}
|
1133
|
-
else {
|
1134
|
-
// no subtags
|
1135
|
-
result +=
|
1136
|
-
htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length) + processedChange;
|
1137
|
-
processedChange = '';
|
1138
|
-
// oldPlainTextEndIndex already includes mentionTag length
|
1139
|
-
const endChangeDiff = closingTagInfo.plainTextEndIndex - oldPlainTextEndIndex;
|
1140
|
-
// as change may be before the end of the tag, we need to add the rest of the tag
|
1141
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx - endChangeDiff;
|
1142
|
-
}
|
1143
|
-
// the change is handled; exit
|
1144
|
-
break;
|
1145
|
-
}
|
1146
|
-
lastProcessedPlainTextTagEndIndex = closingTagInfo.plainTextEndIndex;
|
1147
|
-
}
|
1148
|
-
if (i === tags.length - 1 && oldPlainTextEndIndex >= closingTagInfo.plainTextEndIndex) {
|
1149
|
-
// the last tag should handle the end of the change if needed
|
1150
|
-
// oldPlainTextEndIndex already includes mentionTag length
|
1151
|
-
const endChangeDiff = oldPlainTextEndIndex - closingTagInfo.plainTextEndIndex;
|
1152
|
-
if (startIndex >= closingTagInfo.plainTextEndIndex) {
|
1153
|
-
const startChangeDiff = startIndex - closingTagInfo.plainTextEndIndex;
|
1154
|
-
result +=
|
1155
|
-
htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + startChangeDiff) + processedChange;
|
1156
|
-
}
|
1157
|
-
else {
|
1158
|
-
result +=
|
1159
|
-
htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
|
1160
|
-
processedChange;
|
1161
|
-
}
|
1162
|
-
processedChange = '';
|
1163
|
-
lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + endChangeDiff;
|
1164
|
-
// the change is handled; exit
|
1165
|
-
// break is not required here as this is the last element but added for consistency
|
1166
|
-
break;
|
1167
|
-
}
|
1168
|
-
}
|
1169
|
-
if (lastProcessedHTMLIndex < htmlText.length) {
|
1170
|
-
// add the rest of the html string
|
1171
|
-
result += htmlText.substring(lastProcessedHTMLIndex);
|
1172
|
-
}
|
1173
|
-
return { updatedHTML: result, updatedSelectionIndex: changeNewEndIndex };
|
1174
|
-
};
|
1175
|
-
/* @conditional-compile-remove(mention) */
|
1176
|
-
/**
|
1177
|
-
* Given the oldText and newText, find the start index, old end index and new end index for the changes
|
1178
|
-
*
|
1179
|
-
* @private
|
1180
|
-
* @param props - Props for finding stings diff indexes function.
|
1181
|
-
* @returns Indexes for change start and ends in new and old texts. The old and new end indexes are exclusive.
|
1182
|
-
*/
|
1183
|
-
const findStringsDiffIndexes = (props) => {
|
1184
|
-
const { oldText, newText, previousSelectionStart, previousSelectionEnd, currentSelectionStart, currentSelectionEnd } = props;
|
1185
|
-
const newTextLength = newText.length;
|
1186
|
-
const oldTextLength = oldText.length;
|
1187
|
-
// let changeStart = 0;
|
1188
|
-
let newChangeEnd = newTextLength;
|
1189
|
-
let oldChangeEnd = oldTextLength;
|
1190
|
-
const previousSelectionStartValue = previousSelectionStart > -1 ? previousSelectionStart : oldTextLength;
|
1191
|
-
const previousSelectionEndValue = previousSelectionEnd > -1 ? previousSelectionEnd : oldTextLength;
|
1192
|
-
const currentSelectionStartValue = currentSelectionStart > -1 ? currentSelectionStart : newTextLength;
|
1193
|
-
const currentSelectionEndValue = currentSelectionEnd > -1 ? currentSelectionEnd : newTextLength;
|
1194
|
-
const changeStart = Math.min(previousSelectionStartValue, previousSelectionEndValue, currentSelectionStartValue, currentSelectionEndValue, newTextLength, oldTextLength);
|
1195
|
-
if (oldTextLength < newTextLength) {
|
1196
|
-
//insert or replacement
|
1197
|
-
if (oldTextLength === changeStart) {
|
1198
|
-
// when change was at the end of string
|
1199
|
-
// change is found
|
1200
|
-
newChangeEnd = newTextLength;
|
1201
|
-
oldChangeEnd = oldTextLength;
|
1202
|
-
}
|
1203
|
-
else {
|
1204
|
-
for (let i = 1; i < newTextLength && oldTextLength - i >= changeStart; i++) {
|
1205
|
-
newChangeEnd = newTextLength - i - 1;
|
1206
|
-
oldChangeEnd = oldTextLength - i - 1;
|
1207
|
-
if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
|
1208
|
-
// change is found
|
1209
|
-
break;
|
1210
|
-
}
|
1211
|
-
}
|
1212
|
-
// make indexes exclusive
|
1213
|
-
newChangeEnd += 1;
|
1214
|
-
oldChangeEnd += 1;
|
1215
|
-
}
|
1216
|
-
}
|
1217
|
-
else if (oldTextLength > newTextLength) {
|
1218
|
-
//deletion or replacement
|
1219
|
-
if (newTextLength === changeStart) {
|
1220
|
-
// when change was at the end of string
|
1221
|
-
// change is found
|
1222
|
-
newChangeEnd = newTextLength;
|
1223
|
-
oldChangeEnd = oldTextLength;
|
1224
|
-
}
|
1225
|
-
else {
|
1226
|
-
for (let i = 1; i < oldTextLength && newTextLength - i >= changeStart; i++) {
|
1227
|
-
newChangeEnd = newTextLength - i - 1;
|
1228
|
-
oldChangeEnd = oldTextLength - i - 1;
|
1229
|
-
if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
|
1230
|
-
// change is found
|
1231
|
-
break;
|
1232
|
-
}
|
1233
|
-
}
|
1234
|
-
// make indexes exclusive
|
1235
|
-
newChangeEnd += 1;
|
1236
|
-
oldChangeEnd += 1;
|
1237
|
-
}
|
1238
|
-
}
|
1239
|
-
else {
|
1240
|
-
// replacement
|
1241
|
-
for (let i = 1; i < oldTextLength && oldTextLength - i >= changeStart; i++) {
|
1242
|
-
newChangeEnd = newTextLength - i - 1;
|
1243
|
-
oldChangeEnd = oldTextLength - i - 1;
|
1244
|
-
if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
|
1245
|
-
// change is found
|
1246
|
-
break;
|
1247
|
-
}
|
1248
|
-
}
|
1249
|
-
// make indexes exclusive if they aren't equal to the length of the string
|
1250
|
-
if (newChangeEnd !== newText.length) {
|
1251
|
-
newChangeEnd += 1;
|
1252
|
-
}
|
1253
|
-
if (oldChangeEnd !== oldText.length) {
|
1254
|
-
oldChangeEnd += 1;
|
1255
|
-
}
|
1256
|
-
}
|
1257
|
-
return { changeStart, oldChangeEnd, newChangeEnd };
|
1258
|
-
};
|
1259
|
-
/* @conditional-compile-remove(mention) */
|
1260
|
-
/**
|
1261
|
-
* Get the html string for the mention suggestion.
|
1262
|
-
*
|
1263
|
-
* @private
|
1264
|
-
* @param suggestion - The mention suggestion.
|
1265
|
-
* @param localeStrings - The locale strings.
|
1266
|
-
* @returns The html string for the mention suggestion.
|
1267
|
-
*/
|
1268
|
-
const htmlStringForMentionSuggestion = (suggestion, localeStrings) => {
|
1269
|
-
const idHTML = ' id ="' + suggestion.id + '"';
|
1270
|
-
const displayTextHTML = ' displayText ="' + suggestion.displayText + '"';
|
1271
|
-
const displayText = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
|
1272
|
-
return '<' + MSFT_MENTION_TAG + idHTML + displayTextHTML + '>' + displayText + '</' + MSFT_MENTION_TAG + '>';
|
1273
|
-
};
|
1274
|
-
/* @conditional-compile-remove(mention) */
|
1275
|
-
/**
|
1276
|
-
* Get display name for the mention suggestion.
|
1277
|
-
*
|
1278
|
-
* @private
|
1279
|
-
*
|
1280
|
-
* @param suggestion - The mention suggestion.
|
1281
|
-
* @param localeStrings - The locale strings.
|
1282
|
-
* @returns The display name for the mention suggestion or display name placeholder if display name is empty.
|
1283
|
-
*/
|
1284
|
-
const getDisplayNameForMentionSuggestion = (suggestion, localeStrings) => {
|
1285
|
-
const displayNamePlaceholder = localeStrings.participantItem.displayNamePlaceholder;
|
1286
|
-
return suggestion.displayText !== '' ? suggestion.displayText : displayNamePlaceholder !== null && displayNamePlaceholder !== void 0 ? displayNamePlaceholder : '';
|
1287
|
-
};
|
1288
|
-
/* @conditional-compile-remove(mention) */
|
1289
|
-
/**
|
1290
|
-
* Parse the text and return the tags and the plain text in one go
|
1291
|
-
* @private
|
1292
|
-
* @param text - The text to parse for HTML tags
|
1293
|
-
* @param trigger The trigger to show for the mention tag in plain text
|
1294
|
-
*
|
1295
|
-
* @returns An array of tags and the plain text representation
|
1296
|
-
*/
|
1297
|
-
const textToTagParser = (text, trigger) => {
|
1298
|
-
var _a, _b;
|
1299
|
-
const tags = []; // Tags passed back to the caller
|
1300
|
-
const tagParseStack = []; // Local stack to use while parsing
|
1301
|
-
let plainTextRepresentation = '';
|
1302
|
-
let parseIndex = 0;
|
1303
|
-
while (parseIndex < text.length) {
|
1304
|
-
const foundHtmlTag = findNextHtmlTag(text, parseIndex);
|
1305
|
-
if (!foundHtmlTag) {
|
1306
|
-
if (parseIndex !== 0) {
|
1307
|
-
// Add the remaining text to the plain text representation
|
1308
|
-
plainTextRepresentation += text.substring(parseIndex);
|
1309
|
-
}
|
1310
|
-
else {
|
1311
|
-
plainTextRepresentation = text;
|
1312
|
-
}
|
1313
|
-
break;
|
1314
|
-
}
|
1315
|
-
if (foundHtmlTag.type === 'open' || foundHtmlTag.type === 'self-closing') {
|
1316
|
-
const nextTag = parseOpenTag(foundHtmlTag.content, foundHtmlTag.startIdx);
|
1317
|
-
// Add the plain text between the last tag and this one found
|
1318
|
-
plainTextRepresentation += text.substring(parseIndex, foundHtmlTag.startIdx);
|
1319
|
-
nextTag.plainTextBeginIndex = plainTextRepresentation.length;
|
1320
|
-
if (foundHtmlTag.type === 'open') {
|
1321
|
-
tagParseStack.push(nextTag);
|
1322
|
-
}
|
1323
|
-
else {
|
1324
|
-
nextTag.content = '';
|
1325
|
-
nextTag.plainTextBeginIndex = plainTextRepresentation.length;
|
1326
|
-
nextTag.plainTextEndIndex = plainTextRepresentation.length;
|
1327
|
-
addTag(nextTag, tagParseStack, tags);
|
1328
|
-
}
|
1329
|
-
}
|
1330
|
-
if (foundHtmlTag.type === 'close') {
|
1331
|
-
const currentOpenTag = tagParseStack.pop();
|
1332
|
-
const closeTagType = foundHtmlTag.content.substring(2, foundHtmlTag.content.length - 1).toLowerCase();
|
1333
|
-
if (currentOpenTag && currentOpenTag.tagType === closeTagType) {
|
1334
|
-
// Tag startIdx is absolute to the text. This is updated later to be relative to the parent tag
|
1335
|
-
currentOpenTag.content = text.substring(currentOpenTag.openTagIndex + currentOpenTag.openTagBody.length, foundHtmlTag.startIdx);
|
1336
|
-
// Insert the plain text pieces for the sub tags
|
1337
|
-
if (currentOpenTag.tagType === MSFT_MENTION_TAG) {
|
1338
|
-
plainTextRepresentation =
|
1339
|
-
plainTextRepresentation.slice(0, currentOpenTag.plainTextBeginIndex) +
|
1340
|
-
trigger +
|
1341
|
-
plainTextRepresentation.slice(currentOpenTag.plainTextBeginIndex);
|
1342
|
-
}
|
1343
|
-
if (!currentOpenTag.subTags) {
|
1344
|
-
plainTextRepresentation += currentOpenTag.content;
|
1345
|
-
}
|
1346
|
-
else if (currentOpenTag.subTags.length > 0) {
|
1347
|
-
// Add text after the last tag
|
1348
|
-
const lastSubTag = currentOpenTag.subTags[currentOpenTag.subTags.length - 1];
|
1349
|
-
const startOfRemainingText = ((_a = lastSubTag.closingTagIndex) !== null && _a !== void 0 ? _a : lastSubTag.openTagIndex) + lastSubTag.tagType.length + 3;
|
1350
|
-
const trailingText = currentOpenTag.content.substring(startOfRemainingText);
|
1351
|
-
plainTextRepresentation += trailingText;
|
1352
|
-
}
|
1353
|
-
currentOpenTag.plainTextEndIndex = plainTextRepresentation.length;
|
1354
|
-
addTag(currentOpenTag, tagParseStack, tags);
|
1355
|
-
}
|
1356
|
-
else {
|
1357
|
-
throw new Error('Unexpected close tag found. Got "' +
|
1358
|
-
closeTagType +
|
1359
|
-
'" but expected "' +
|
1360
|
-
((_b = tagParseStack[tagParseStack.length - 1]) === null || _b === void 0 ? void 0 : _b.tagType) +
|
1361
|
-
'"');
|
1362
|
-
}
|
1363
|
-
}
|
1364
|
-
// Update parsing index; move past the end of the close tag
|
1365
|
-
parseIndex = foundHtmlTag.startIdx + foundHtmlTag.content.length;
|
1366
|
-
} // While parseIndex < text.length loop
|
1367
|
-
return { tags, plainText: plainTextRepresentation };
|
1368
|
-
};
|
1369
|
-
/* @conditional-compile-remove(mention) */
|
1370
|
-
const parseOpenTag = (tag, startIdx) => {
|
1371
|
-
const tagType = tag
|
1372
|
-
.substring(1, tag.length - 1)
|
1373
|
-
.split(' ')[0]
|
1374
|
-
.toLowerCase()
|
1375
|
-
.replace('/', '');
|
1376
|
-
return {
|
1377
|
-
tagType,
|
1378
|
-
openTagIndex: startIdx,
|
1379
|
-
openTagBody: tag
|
1380
|
-
};
|
1381
|
-
};
|
1382
|
-
/* @conditional-compile-remove(mention) */
|
1383
|
-
const findNextHtmlTag = (text, startIndex) => {
|
1384
|
-
const tagStartIndex = text.indexOf('<', startIndex);
|
1385
|
-
if (tagStartIndex === -1) {
|
1386
|
-
// No more tags
|
1387
|
-
return undefined;
|
1388
|
-
}
|
1389
|
-
const tagEndIndex = text.indexOf('>', tagStartIndex);
|
1390
|
-
if (tagEndIndex === -1) {
|
1391
|
-
// No close tag
|
1392
|
-
return undefined;
|
1393
|
-
}
|
1394
|
-
const tag = text.substring(tagStartIndex, tagEndIndex + 1);
|
1395
|
-
let type = 'open';
|
1396
|
-
if (tag[1] === '/') {
|
1397
|
-
type = 'close';
|
1398
|
-
}
|
1399
|
-
else if (tag[tag.length - 2] === '/') {
|
1400
|
-
type = 'self-closing';
|
1401
|
-
}
|
1402
|
-
return {
|
1403
|
-
content: tag,
|
1404
|
-
startIdx: tagStartIndex,
|
1405
|
-
type
|
1406
|
-
};
|
1407
|
-
};
|
1408
|
-
/* @conditional-compile-remove(mention) */
|
1409
|
-
const addTag = (tag, parseStack, tags) => {
|
1410
|
-
var _a;
|
1411
|
-
// Add as sub-tag to the parent stack tag, if there is one
|
1412
|
-
const parentTag = parseStack[parseStack.length - 1];
|
1413
|
-
if (parentTag) {
|
1414
|
-
// Adjust the open tag index to be relative to the parent tag
|
1415
|
-
const parentContentStartIdx = parentTag.openTagIndex + parentTag.openTagBody.length;
|
1416
|
-
const relativeIdx = tag.openTagIndex - parentContentStartIdx;
|
1417
|
-
tag.openTagIndex = relativeIdx;
|
1418
|
-
}
|
1419
|
-
if (!tag.closingTagIndex) {
|
1420
|
-
// If the tag is self-closing, the close tag is the same as the open tag
|
1421
|
-
if (tag.openTagBody[tag.openTagBody.length - 2] === '/') {
|
1422
|
-
tag.closingTagIndex = tag.openTagIndex;
|
1423
|
-
}
|
1424
|
-
else {
|
1425
|
-
// Otherwise, the close tag index is the open tag index + the open tag body + the content length
|
1426
|
-
tag.closingTagIndex = tag.openTagIndex + tag.openTagBody.length + ((_a = tag.content) !== null && _a !== void 0 ? _a : []).length;
|
1427
|
-
}
|
1428
|
-
}
|
1429
|
-
// Put the tag where it belongs
|
1430
|
-
if (!parentTag) {
|
1431
|
-
tags.push(tag);
|
1432
|
-
}
|
1433
|
-
else {
|
1434
|
-
if (!parentTag.subTags) {
|
1435
|
-
parentTag.subTags = [tag];
|
1436
|
-
}
|
1437
|
-
else {
|
1438
|
-
parentTag.subTags.push(tag);
|
1439
|
-
}
|
1440
|
-
}
|
1441
|
-
};
|
1442
105
|
//# sourceMappingURL=InputBoxComponent.js.map
|