@azure/communication-react 1.5.1-alpha-202305160013 → 1.5.1-alpha-202305180013

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.
Files changed (23) hide show
  1. package/dist/dist-cjs/communication-react/index.js +595 -373
  2. package/dist/dist-cjs/communication-react/index.js.map +1 -1
  3. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js +1 -1
  4. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js.map +1 -1
  5. package/dist/dist-esm/react-components/src/components/CameraButton.js +4 -2
  6. package/dist/dist-esm/react-components/src/components/CameraButton.js.map +1 -1
  7. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js +559 -363
  8. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js.map +1 -1
  9. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoEffectsItem.js +1 -1
  10. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoEffectsItem.js.map +1 -1
  11. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/CallArrangement.js +12 -2
  12. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/CallArrangement.js.map +1 -1
  13. package/dist/dist-esm/react-composites/src/composites/CallComposite/pages/ConfigurationPage.js +1 -1
  14. package/dist/dist-esm/react-composites/src/composites/CallComposite/pages/ConfigurationPage.js.map +1 -1
  15. package/dist/dist-esm/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.js +12 -1
  16. package/dist/dist-esm/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.js.map +1 -1
  17. package/dist/dist-esm/react-composites/src/composites/ChatComposite/adapter/AzureCommunicationChatAdapter.js +4 -0
  18. package/dist/dist-esm/react-composites/src/composites/ChatComposite/adapter/AzureCommunicationChatAdapter.js.map +1 -1
  19. package/dist/dist-esm/react-composites/src/composites/common/ControlBar/CommonCallControlBar.js +3 -1
  20. package/dist/dist-esm/react-composites/src/composites/common/ControlBar/CommonCallControlBar.js.map +1 -1
  21. package/dist/dist-esm/react-composites/src/composites/common/Drawer/MoreDrawer.js +1 -1
  22. package/dist/dist-esm/react-composites/src/composites/common/Drawer/MoreDrawer.js.map +1 -1
  23. package/package.json +8 -8
@@ -25,14 +25,16 @@ import { _MentionPopover } from './MentionPopover';
25
25
  /* @conditional-compile-remove(mention) */
26
26
  import { useDebouncedCallback } from 'use-debounce';
27
27
  /* @conditional-compile-remove(mention) */
28
- const defaultMentionTrigger = '@';
28
+ const DEFAULT_MENTION_TRIGGER = '@';
29
+ /* @conditional-compile-remove(mention) */
30
+ const MSFT_MENTION_TAG = 'msft-mention';
29
31
  /**
30
32
  * @private
31
33
  */
32
34
  export const InputBoxComponent = (props) => {
33
- const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled, children,
35
+ const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled,
34
36
  /* @conditional-compile-remove(mention) */
35
- mentionLookupOptions } = props;
37
+ mentionLookupOptions, children } = props;
36
38
  const inputBoxRef = useRef(null);
37
39
  /* @conditional-compile-remove(mention) */
38
40
  // Current suggestion list, provided by the callback
@@ -48,10 +50,13 @@ export const InputBoxComponent = (props) => {
48
50
  /* @conditional-compile-remove(mention) */
49
51
  const [tagsValue, setTagsValue] = useState([]);
50
52
  /* @conditional-compile-remove(mention) */
53
+ // Index of the previous selection start in the text field
51
54
  const [selectionStartValue, setSelectionStartValue] = useState(null);
52
55
  /* @conditional-compile-remove(mention) */
56
+ // Index of the previous selection end in the text field
53
57
  const [selectionEndValue, setSelectionEndValue] = useState(null);
54
58
  /* @conditional-compile-remove(mention) */
59
+ // Boolean value to check if onMouseDown event should be handled during select as selection range for onMouseDown event is not updated yet and the selection range for mouse click/taps will be updated in onSelect event if needed.
55
60
  const [shouldHandleOnMouseDownDuringSelect, setShouldHandleOnMouseDownDuringSelect] = useState(true);
56
61
  /* @conditional-compile-remove(mention) */
57
62
  // Caret position in the text field
@@ -62,24 +67,38 @@ export const InputBoxComponent = (props) => {
62
67
  /* @conditional-compile-remove(mention) */
63
68
  const localeStrings = useLocale().strings;
64
69
  /* @conditional-compile-remove(mention) */
70
+ // Set mention suggestions
65
71
  const updateMentionSuggestions = useCallback((suggestions) => {
66
- var _a;
67
72
  setMentionSuggestions(suggestions);
68
- if (caretIndex !== undefined) {
69
- (_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionEnd(caretIndex);
70
- }
71
- }, [textFieldRef, caretIndex]);
73
+ }, [setMentionSuggestions]);
72
74
  /* @conditional-compile-remove(mention) */
73
75
  // Parse the text and get the plain text version to display in the input box
74
76
  useEffect(() => {
75
- const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || defaultMentionTrigger;
76
- const [tags, plainText] = textToTagParser(textValue, trigger);
77
- setInputTextValue(plainText);
78
- setTagsValue(tags);
77
+ const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || DEFAULT_MENTION_TRIGGER;
78
+ const parsedHTMLData = textToTagParser(textValue, trigger);
79
+ setInputTextValue(parsedHTMLData.plainText);
80
+ setTagsValue(parsedHTMLData.tags);
79
81
  updateMentionSuggestions([]);
80
82
  }, [textValue, mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger, updateMentionSuggestions]);
81
83
  const mergedRootStyle = mergeStyles(inputBoxWrapperStyle, styles === null || styles === void 0 ? void 0 : styles.root);
82
84
  const mergedTextFiledStyle = mergeStyles(inputBoxStyle, inputClassName, props.inlineChildren ? {} : inputBoxNewLineSpaceAffordance);
85
+ /* @conditional-compile-remove(mention) */
86
+ useEffect(() => {
87
+ var _a;
88
+ // effect for caret index update
89
+ if (caretIndex === undefined || textFieldRef === undefined || (textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === undefined) {
90
+ return;
91
+ }
92
+ // get validated caret index between 0 and inputTextValue.length otherwise caret will be set to incorrect index
93
+ const updatedCaretIndex = getValidatedIndexInRange({
94
+ min: 0,
95
+ max: inputTextValue.length,
96
+ currentValue: caretIndex
97
+ });
98
+ (_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(updatedCaretIndex, updatedCaretIndex);
99
+ setSelectionStartValue(updatedCaretIndex);
100
+ setSelectionEndValue(updatedCaretIndex);
101
+ }, [caretIndex, inputTextValue.length, textFieldRef, setSelectionStartValue, setSelectionEndValue]);
83
102
  const mergedTextContainerStyle = mergeStyles(textContainerStyle, styles === null || styles === void 0 ? void 0 : styles.textFieldContainer);
84
103
  const mergedTextFieldStyle = concatStyleSets(textFieldStyle, {
85
104
  fieldGroup: styles === null || styles === void 0 ? void 0 : styles.textField,
@@ -94,7 +113,7 @@ export const InputBoxComponent = (props) => {
94
113
  const mergedChildrenStyle = mergeStyles(props.inlineChildren ? {} : newLineButtonsContainerStyle);
95
114
  /* @conditional-compile-remove(mention) */
96
115
  const onSuggestionSelected = useCallback((suggestion) => {
97
- var _a, _b;
116
+ var _a, _b, _c;
98
117
  let selectionEnd = ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.selectionEnd) || -1;
99
118
  if (selectionEnd < 0) {
100
119
  selectionEnd = 0;
@@ -106,16 +125,30 @@ export const InputBoxComponent = (props) => {
106
125
  const mention = htmlStringForMentionSuggestion(suggestion, localeStrings);
107
126
  // update plain text with the mention html text
108
127
  const newPlainText = inputTextValue.substring(0, currentTriggerStartIndex) + mention + inputTextValue.substring(selectionEnd);
109
- const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : defaultMentionTrigger;
128
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
110
129
  // update html text with updated plain text
111
- const [updatedHTML] = updateHTML(textValue, oldPlainText, newPlainText, tagsValue, currentTriggerStartIndex, selectionEnd, mention, triggerText);
130
+ const updatedContent = updateHTML({
131
+ htmlText: textValue,
132
+ oldPlainText,
133
+ newPlainText,
134
+ tags: tagsValue,
135
+ startIndex: currentTriggerStartIndex,
136
+ oldPlainTextEndIndex: selectionEnd,
137
+ change: mention,
138
+ mentionTrigger: triggerText
139
+ });
112
140
  const displayName = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
113
- // Move the caret in the text field to the end of the mention plain text
114
- setCaretIndex(selectionEnd + displayName.length);
141
+ const newCaretIndex = currentTriggerStartIndex + displayName.length + triggerText.length;
142
+ // move the caret in the text field to the end of the mention plain text
143
+ setCaretIndex(newCaretIndex);
144
+ setSelectionEndValue(newCaretIndex);
145
+ setSelectionStartValue(newCaretIndex);
115
146
  setCurrentTriggerStartIndex(-1);
116
147
  updateMentionSuggestions([]);
148
+ // set focus back to text field
149
+ (_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.focus();
117
150
  setActiveSuggestionIndex(undefined);
118
- onChange && onChange(undefined, updatedHTML);
151
+ onChange && onChange(undefined, updatedContent.updatedHTML);
119
152
  }, [
120
153
  textFieldRef,
121
154
  inputTextValue,
@@ -130,6 +163,15 @@ export const InputBoxComponent = (props) => {
130
163
  localeStrings
131
164
  ]);
132
165
  const onTextFieldKeyDown = useCallback((ev) => {
166
+ /* @conditional-compile-remove(mention) */
167
+ // caretIndex should be set to undefined when the user is typing
168
+ setCaretIndex(undefined);
169
+ // shouldHandleOnMouseDownDuringSelect should be set to false after the last mouse down event.
170
+ // it shouldn't be updated in onMouseUp
171
+ // as onMouseUp can be triggered before or after onSelect event
172
+ // because its order depends on mouse events not selection.
173
+ /* @conditional-compile-remove(mention) */
174
+ setShouldHandleOnMouseDownDuringSelect(false);
133
175
  // Uses KeyCode 229 and which code 229 to determine if the press of the enter key is from a composition session or not (Safari only)
134
176
  if (ev.nativeEvent.isComposing || ev.nativeEvent.keyCode === 229 || ev.nativeEvent.which === 229) {
135
177
  return;
@@ -201,24 +243,149 @@ export const InputBoxComponent = (props) => {
201
243
  };
202
244
  }, [debouncedQueryUpdate]);
203
245
  /* @conditional-compile-remove(mention) */
204
- const handleOnChange = useCallback((event, updatedValue) => __awaiter(void 0, void 0, void 0, function* () {
205
- var _b, _c;
246
+ // Update selections index in mention to navigate by words
247
+ const updateSelectionIndexesWithMentionIfNeeded = useCallback((event, inputTextValue, selectionStartValue, selectionEndValue, tagsValue) => {
248
+ var _a, _b, _c;
249
+ let updatedStartIndex = event.currentTarget.selectionStart;
250
+ let updatedEndIndex = event.currentTarget.selectionEnd;
251
+ if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
252
+ event.currentTarget.selectionStart !== null &&
253
+ event.currentTarget.selectionStart !== -1) {
254
+ // just a caret movement/usual typing or deleting
255
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
256
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
257
+ if (mentionTag !== undefined &&
258
+ mentionTag.plainTextBeginIndex !== undefined &&
259
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
260
+ event.currentTarget.selectionStart < ((_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex)) {
261
+ // get updated selection index
262
+ const newSelectionIndex = findNewSelectionIndexForMention({
263
+ tag: mentionTag,
264
+ textValue: inputTextValue,
265
+ currentSelectionIndex: event.currentTarget.selectionStart,
266
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
267
+ });
268
+ updatedStartIndex = newSelectionIndex;
269
+ updatedEndIndex = newSelectionIndex;
270
+ }
271
+ }
272
+ else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
273
+ // Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
274
+ if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
275
+ // the selection start is changed
276
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
277
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
278
+ if (mentionTag !== undefined &&
279
+ mentionTag.plainTextBeginIndex !== undefined &&
280
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
281
+ event.currentTarget.selectionStart < ((_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex)) {
282
+ updatedStartIndex = findNewSelectionIndexForMention({
283
+ tag: mentionTag,
284
+ textValue: inputTextValue,
285
+ currentSelectionIndex: event.currentTarget.selectionStart,
286
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
287
+ });
288
+ }
289
+ }
290
+ if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
291
+ // the selection end is changed
292
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
293
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
294
+ if (mentionTag !== undefined &&
295
+ mentionTag.plainTextBeginIndex !== undefined &&
296
+ event.currentTarget.selectionEnd > mentionTag.plainTextBeginIndex &&
297
+ event.currentTarget.selectionEnd < ((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex)) {
298
+ updatedEndIndex = findNewSelectionIndexForMention({
299
+ tag: mentionTag,
300
+ textValue: inputTextValue,
301
+ currentSelectionIndex: event.currentTarget.selectionEnd,
302
+ previousSelectionIndex: selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : inputTextValue.length
303
+ });
304
+ }
305
+ }
306
+ }
307
+ // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
308
+ if (event.currentTarget.selectionDirection === null) {
309
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
310
+ }
311
+ else {
312
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
313
+ }
314
+ setSelectionStartValue(updatedStartIndex);
315
+ setSelectionEndValue(updatedEndIndex);
316
+ }, [setSelectionStartValue, setSelectionEndValue]);
317
+ /* @conditional-compile-remove(mention) */
318
+ const handleOnSelect = useCallback((event, inputTextValue, tags, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue) => {
319
+ var _a, _b, _c;
320
+ /* @conditional-compile-remove(mention) */
321
+ if (shouldHandleOnMouseDownDuringSelect && event.currentTarget.selectionStart !== null) {
322
+ // on select was triggered by mouse down
323
+ const mentionTag = findMentionTagForSelection(tags, event.currentTarget.selectionStart);
324
+ if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
325
+ // handle mention click
326
+ if (event.currentTarget.selectionDirection === null) {
327
+ event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex);
328
+ }
329
+ else {
330
+ event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex, event.currentTarget.selectionDirection);
331
+ }
332
+ setSelectionStartValue(mentionTag.plainTextBeginIndex);
333
+ setSelectionEndValue((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex);
334
+ }
335
+ else {
336
+ setSelectionStartValue(event.currentTarget.selectionStart);
337
+ setSelectionEndValue(event.currentTarget.selectionEnd);
338
+ }
339
+ }
340
+ else {
341
+ // selection was changed by keyboard
342
+ updateSelectionIndexesWithMentionIfNeeded(event, inputTextValue, selectionStartValue, selectionEndValue, tags);
343
+ }
344
+ // don't set setShouldHandleOnMouseDownDuringSelect(false) here as setSelectionRange could trigger additional calls of onSelect event and they may not be handled correctly (because of setSelectionRange calls or rerender)
345
+ }, [updateSelectionIndexesWithMentionIfNeeded]);
346
+ /* @conditional-compile-remove(mention) */
347
+ const handleOnChange = useCallback((event, tagsValue, htmlTextValue, inputTextValue, currentTriggerStartIndex, previousSelectionStart, previousSelectionEnd, currentSelectionStart, currentSelectionEnd, updatedValue) => __awaiter(void 0, void 0, void 0, function* () {
348
+ var _b;
349
+ if (event.currentTarget === null) {
350
+ return;
351
+ }
352
+ // handle backspace change
353
+ // onSelect is not called for backspace as selection is not changed and local caret index is outdated
354
+ setCaretIndex(undefined);
206
355
  const newValue = updatedValue !== null && updatedValue !== void 0 ? updatedValue : '';
207
- const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : defaultMentionTrigger;
356
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
208
357
  const newTextLength = newValue.length;
209
- let selectionEnd = ((_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.selectionEnd) || -1;
210
- selectionEnd = Math.max(0, selectionEnd);
211
- selectionEnd = Math.min(selectionEnd, newTextLength - 1);
358
+ // updating indexes to set between 0 and text length, otherwise selectionRange won't be set correctly
359
+ const currentSelectionEndValue = getValidatedIndexInRange({
360
+ min: 0,
361
+ max: newTextLength,
362
+ currentValue: currentSelectionEnd
363
+ });
364
+ const currentSelectionStartValue = getValidatedIndexInRange({
365
+ min: 0,
366
+ max: newTextLength,
367
+ currentValue: currentSelectionStart
368
+ });
369
+ const previousSelectionStartValue = getValidatedIndexInRange({
370
+ min: 0,
371
+ max: inputTextValue.length,
372
+ currentValue: previousSelectionStart
373
+ });
374
+ const previousSelectionEndValue = getValidatedIndexInRange({
375
+ min: 0,
376
+ max: inputTextValue.length,
377
+ currentValue: previousSelectionEnd
378
+ });
212
379
  // If we are enabled for lookups,
213
380
  if (mentionLookupOptions !== undefined) {
214
381
  // Look at the range of the change for a trigger character
215
- const triggerPriorIndex = newValue.lastIndexOf(triggerText, selectionEnd - 1);
382
+ const triggerPriorIndex = newValue.lastIndexOf(triggerText, currentSelectionEndValue - 1);
216
383
  // Update the caret position, if not doing a lookup
217
384
  setCaretPosition(Caret.getRelativePosition(event.currentTarget));
218
385
  if (triggerPriorIndex !== undefined) {
219
386
  // trigger is found
220
387
  const isSpaceBeforeTrigger = newValue.substring(triggerPriorIndex - 1, triggerPriorIndex) === ' ';
221
- const wordAtSelection = newValue.substring(triggerPriorIndex, selectionEnd);
388
+ const wordAtSelection = newValue.substring(triggerPriorIndex, currentSelectionEndValue);
222
389
  let tagIndex = currentTriggerStartIndex;
223
390
  if (!isSpaceBeforeTrigger && triggerPriorIndex !== 0) {
224
391
  //no space before the trigger <- continuation of the previous word
@@ -227,7 +394,7 @@ export const InputBoxComponent = (props) => {
227
394
  }
228
395
  else if (wordAtSelection === triggerText) {
229
396
  // start of the mention
230
- tagIndex = selectionEnd - triggerText.length;
397
+ tagIndex = currentSelectionEndValue - triggerText.length;
231
398
  if (tagIndex < 0) {
232
399
  tagIndex = 0;
233
400
  }
@@ -249,88 +416,42 @@ export const InputBoxComponent = (props) => {
249
416
  }
250
417
  let result = '';
251
418
  if (tagsValue.length === 0) {
252
- // no tags in the string, textValue is a sting
419
+ // no tags in the string and newValue should be used as a result string
253
420
  result = newValue;
254
421
  }
255
422
  else {
256
- // there are tags in the text value, textValue is html string
257
- const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes(inputTextValue, newValue, selectionEnd);
258
- // get updated html string
423
+ // there are tags in the text value and htmlTextValue is html string
424
+ // find diff between old and new text
425
+ const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes({
426
+ oldText: inputTextValue,
427
+ newText: newValue,
428
+ previousSelectionStart: previousSelectionStartValue,
429
+ previousSelectionEnd: previousSelectionEndValue,
430
+ currentSelectionStart: currentSelectionStartValue,
431
+ currentSelectionEnd: currentSelectionEndValue
432
+ });
259
433
  const change = newValue.substring(changeStart, newChangeEnd);
260
- const [updatedHTML, updatedChangeNewEndIndex] = updateHTML(textValue, inputTextValue, newValue, tagsValue, changeStart, oldChangeEnd, change, triggerText);
261
- result = updatedHTML;
262
- if (updatedChangeNewEndIndex !== null) {
263
- if ((change.length === 1 && event.currentTarget.selectionStart === event.currentTarget.selectionEnd) || // simple input
264
- (change.length === 0 && newChangeEnd === changeStart) //delete
265
- ) {
266
- setCaretIndex(updatedChangeNewEndIndex);
267
- }
434
+ // get updated html string
435
+ const updatedContent = updateHTML({
436
+ htmlText: htmlTextValue,
437
+ oldPlainText: inputTextValue,
438
+ newPlainText: newValue,
439
+ tags: tagsValue,
440
+ startIndex: changeStart,
441
+ oldPlainTextEndIndex: oldChangeEnd,
442
+ change,
443
+ mentionTrigger: triggerText
444
+ });
445
+ result = updatedContent.updatedHTML;
446
+ // update caret index if needed
447
+ if (updatedContent.updatedSelectionIndex !== null) {
448
+ setCaretIndex(updatedContent.updatedSelectionIndex);
449
+ setSelectionEndValue(updatedContent.updatedSelectionIndex);
450
+ setSelectionStartValue(updatedContent.updatedSelectionIndex);
268
451
  }
269
452
  }
270
453
  onChange && onChange(event, result);
271
- }), [
272
- onChange,
273
- mentionLookupOptions,
274
- tagsValue,
275
- textValue,
276
- inputTextValue,
277
- currentTriggerStartIndex,
278
- setCaretIndex,
279
- setCaretPosition,
280
- updateMentionSuggestions,
281
- debouncedQueryUpdate,
282
- textFieldRef
283
- ]);
284
- /* @conditional-compile-remove(mention) */
285
- const updateSelectionIndexesWithMentionIfNeeded = (event) => {
286
- var _a;
287
- let updatedStartIndex = event.currentTarget.selectionStart;
288
- let updatedEndIndex = event.currentTarget.selectionEnd;
289
- if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
290
- event.currentTarget.selectionStart !== null &&
291
- event.currentTarget.selectionStart !== -1) {
292
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
293
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
294
- if (selectionStartValue === null) {
295
- updatedStartIndex = mentionTag.plainTextBeginIndex;
296
- updatedEndIndex = (_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex;
297
- }
298
- else {
299
- const newSelectionIndex = findNewSelectionIndexForMention(mentionTag, inputTextValue, event.currentTarget.selectionStart, selectionStartValue);
300
- updatedStartIndex = newSelectionIndex;
301
- updatedEndIndex = newSelectionIndex;
302
- }
303
- }
304
- }
305
- else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
306
- // Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
307
- if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
308
- // the selection start is changed
309
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
310
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
311
- //TODO: here it takes -1 when it shouldn't, update selectionstart and end with mouse move and or touch move
312
- updatedStartIndex = findNewSelectionIndexForMention(mentionTag, inputTextValue, event.currentTarget.selectionStart, selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : -1);
313
- }
314
- }
315
- if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
316
- // the selection end is changed
317
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
318
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
319
- //TODO: here it takes -1 when it shouldn't, update selectionstart and end with mouse move and or touch move
320
- updatedEndIndex = findNewSelectionIndexForMention(mentionTag, inputTextValue, event.currentTarget.selectionEnd, selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : -1);
321
- }
322
- }
323
- }
324
- // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
325
- if (event.currentTarget.selectionDirection === null) {
326
- event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
327
- }
328
- else {
329
- event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
330
- }
331
- setSelectionStartValue(updatedStartIndex);
332
- setSelectionEndValue(updatedEndIndex);
333
- };
454
+ }), [onChange, mentionLookupOptions, setCaretIndex, setCaretPosition, updateMentionSuggestions, debouncedQueryUpdate]);
334
455
  const getInputFieldTextValue = () => {
335
456
  /* @conditional-compile-remove(mention) */
336
457
  return inputTextValue;
@@ -342,61 +463,52 @@ export const InputBoxComponent = (props) => {
342
463
  updateMentionSuggestions([]);
343
464
  } })),
344
465
  React.createElement(TextField, { autoFocus: props.autoFocus === 'sendBoxTextField', "data-ui-id": dataUiId, multiline: true, autoAdjustHeight: true, multiple: false, resizable: false, componentRef: textFieldRef, id: id, inputClassName: mergedTextFiledStyle, placeholder: placeholderText, value: getInputFieldTextValue(), onChange: (e, newValue) => {
466
+ // Remove when switching to react 17+, currently needed because of https://legacy.reactjs.org/docs/legacy-event-pooling.html
467
+ /* @conditional-compile-remove(mention) */
468
+ // Prevents React from resetting event's properties
469
+ e.persist();
345
470
  /* @conditional-compile-remove(mention) */
346
471
  setInputTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
347
472
  /* @conditional-compile-remove(mention) */
348
- handleOnChange(e, newValue);
473
+ handleOnChange(e, tagsValue, textValue, inputTextValue, currentTriggerStartIndex, selectionStartValue === null ? undefined : selectionStartValue, selectionEndValue === null ? undefined : selectionEndValue, e.currentTarget.selectionStart === null ? undefined : e.currentTarget.selectionStart, e.currentTarget.selectionEnd === null ? undefined : e.currentTarget.selectionEnd, newValue);
349
474
  /* @conditional-compile-remove(mention) */
350
475
  return;
351
476
  onChange(e, newValue);
352
477
  },
353
478
  /* @conditional-compile-remove(mention) */
354
479
  onSelect: (e) => {
355
- var _a, _b, _c;
480
+ // update selection if needed
356
481
  if (caretIndex !== undefined) {
357
- e.currentTarget.setSelectionRange(caretIndex, caretIndex);
358
482
  setCaretIndex(undefined);
359
- return;
360
- }
361
- //TODO: need to check to navigate before/after space correctly in tag + when selecting by mouse
362
- /* @conditional-compile-remove(mention) */
363
- if (shouldHandleOnMouseDownDuringSelect &&
364
- e.currentTarget.selectionStart !== null &&
365
- e.currentTarget.selectionStart === e.currentTarget.selectionEnd) {
366
- // handle mention click
367
- const mentionTag = findMentionTagForSelection(tagsValue, e.currentTarget.selectionStart);
368
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
369
- if (e.currentTarget.selectionDirection === null) {
370
- e.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex);
371
- }
372
- else {
373
- e.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex, e.currentTarget.selectionDirection);
374
- }
375
- setSelectionStartValue(mentionTag.plainTextBeginIndex);
376
- setSelectionEndValue((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex);
377
- }
378
- else {
379
- setSelectionStartValue(e.currentTarget.selectionStart);
380
- setSelectionEndValue(e.currentTarget.selectionEnd);
483
+ // sometimes setting selectionRage in effect for updating caretIndex doesn't work as expected and onSelect should handle this case
484
+ if (caretIndex !== e.currentTarget.selectionStart || caretIndex !== e.currentTarget.selectionEnd) {
485
+ e.currentTarget.setSelectionRange(caretIndex, caretIndex);
381
486
  }
487
+ return;
382
488
  }
383
- else {
384
- updateSelectionIndexesWithMentionIfNeeded(e);
385
- }
386
- /* @conditional-compile-remove(mention) */
387
- setShouldHandleOnMouseDownDuringSelect(false);
388
- }, onMouseDown: () => {
489
+ handleOnSelect(e, inputTextValue, tagsValue, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue);
490
+ },
491
+ /* @conditional-compile-remove(mention) */
492
+ onMouseDown: () => {
389
493
  // as events order is onMouseDown -> onSelect -> onClick
390
494
  // onClick and onMouseDown can't handle clicking on mention event because
391
- // onClick has wrong range as it's called after onSelect
392
- // onMouseDown doesn't have correct selectionRange yet
495
+ // onMouseDown doesn't have correct selectionRange yet and
496
+ // onClick already has wrong range as it's called after onSelect that updates the selection range
393
497
  // so we need to handle onMouseDown to prevent onSelect default behavior
394
- /* @conditional-compile-remove(mention) */
395
498
  setShouldHandleOnMouseDownDuringSelect(true);
396
- }, onTouchStart: () => {
499
+ },
500
+ /* @conditional-compile-remove(mention) */
501
+ onTouchStart: () => {
397
502
  // see onMouseDown for more details
398
- /* @conditional-compile-remove(mention) */
399
503
  setShouldHandleOnMouseDownDuringSelect(true);
504
+ },
505
+ /* @conditional-compile-remove(mention) */
506
+ onBlur: () => {
507
+ // setup all flags to default values when text field loses focus
508
+ setShouldHandleOnMouseDownDuringSelect(false);
509
+ setCaretIndex(undefined);
510
+ setSelectionStartValue(null);
511
+ setSelectionEndValue(null);
400
512
  }, autoComplete: "off", onKeyDown: onTextFieldKeyDown, styles: mergedTextFieldStyle, disabled: disabled, errorMessage: errorMessage, onRenderSuffix: onRenderChildren, elementRef: inputBoxRef }))));
401
513
  };
402
514
  /**
@@ -425,77 +537,93 @@ export const InputBoxButton = (props) => {
425
537
  };
426
538
  /* @conditional-compile-remove(mention) */
427
539
  /**
428
- * Find mention tag if selection is inside of it
540
+ * Get validated value for index between min and max values. If currentValue is not defined, -1 will be used instead.
429
541
  *
430
542
  * @private
543
+ * @param props - Props for finding a valid index in range.
544
+ * @returns Valid index in the range.
545
+ */
546
+ const getValidatedIndexInRange = (props) => {
547
+ const { min, max, currentValue } = props;
548
+ let updatedValue = currentValue !== null && currentValue !== void 0 ? currentValue : -1;
549
+ updatedValue = Math.max(min, updatedValue);
550
+ updatedValue = Math.min(updatedValue, max);
551
+ return updatedValue;
552
+ };
553
+ /* @conditional-compile-remove(mention) */
554
+ /**
555
+ * Find mention tag for selection if exists.
556
+ *
557
+ * @private
558
+ * @param tags - Existing list of tags.
559
+ * @param selection - Selection index.
560
+ * @returns Mention tag if exists, otherwise undefined.
431
561
  */
432
562
  const findMentionTagForSelection = (tags, selection) => {
433
563
  let mentionTag = undefined;
434
564
  for (let i = 0; i < tags.length; i++) {
435
565
  const tag = tags[i];
436
- let plainTextEndIndex = 0;
437
- if (tag.plainTextEndIndex !== undefined && tag.closeTagIdx !== undefined) {
438
- // close tag exists
439
- plainTextEndIndex = tag.plainTextEndIndex;
440
- }
441
- else if (tag.plainTextBeginIndex !== undefined) {
442
- //no close tag
443
- plainTextEndIndex = tag.plainTextBeginIndex;
566
+ const closingTagInfo = getTagClosingTagInfo(tag);
567
+ if (tag.plainTextBeginIndex !== undefined && tag.plainTextBeginIndex > selection) {
568
+ // no need to check further as the selection is before the tag
569
+ break;
444
570
  }
445
- if (tag.subTags !== undefined && tag.subTags.length !== 0) {
446
- const selectedTag = findMentionTagForSelection(tag.subTags, selection);
447
- if (selectedTag !== undefined) {
448
- mentionTag = selectedTag;
571
+ else if (tag.plainTextBeginIndex !== undefined &&
572
+ tag.plainTextBeginIndex <= selection &&
573
+ selection <= closingTagInfo.plainTextEndIndex) {
574
+ // no need to check if tag doesn't contain selection
575
+ if (tag.subTags !== undefined && tag.subTags.length !== 0) {
576
+ const selectedTag = findMentionTagForSelection(tag.subTags, selection);
577
+ if (selectedTag !== undefined) {
578
+ mentionTag = selectedTag;
579
+ break;
580
+ }
581
+ }
582
+ else if (tag.tagType === MSFT_MENTION_TAG) {
583
+ mentionTag = tag;
449
584
  break;
450
585
  }
451
586
  }
452
- else if (tag.tagType === 'msft-mention' &&
453
- tag.plainTextBeginIndex !== undefined &&
454
- tag.plainTextBeginIndex < selection &&
455
- selection < plainTextEndIndex) {
456
- mentionTag = tag;
457
- break;
458
- }
459
587
  }
460
588
  return mentionTag;
461
589
  };
462
590
  /* @conditional-compile-remove(mention) */
463
591
  /**
464
- * Go through tags and find a new the selection index if it is inside of a mention tag
592
+ * Find a new the selection index.
465
593
  *
466
594
  * @private
595
+ * @param props - Props for finding new selection index for mention.
596
+ * @returns New selection index if it is inside of a mention tag, otherwise the current selection.
467
597
  */
468
- const findNewSelectionIndexForMention = (tag, textValue, selection, previousSelection) => {
598
+ const findNewSelectionIndexForMention = (props) => {
469
599
  var _a;
470
- if (tag.plainTextBeginIndex === undefined ||
471
- tag.tagType !== 'msft-mention' ||
472
- selection === previousSelection ||
600
+ const { tag, textValue, currentSelectionIndex, previousSelectionIndex } = props;
601
+ // check if this is a mention tag and selection should be updated
602
+ if (tag.tagType !== MSFT_MENTION_TAG ||
603
+ tag.plainTextBeginIndex === undefined ||
604
+ currentSelectionIndex === previousSelectionIndex ||
473
605
  tag.plainTextEndIndex === undefined) {
474
- return selection;
606
+ return currentSelectionIndex;
475
607
  }
476
608
  let spaceIndex = 0;
477
- if (selection <= previousSelection) {
478
- // the cursor is moved to the left
479
- spaceIndex = textValue.lastIndexOf(' ', selection !== null && selection !== void 0 ? selection : 0);
609
+ if (currentSelectionIndex <= previousSelectionIndex) {
610
+ // the cursor is moved to the left, find the last index before the cursor
611
+ spaceIndex = textValue.lastIndexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
480
612
  if (spaceIndex === -1) {
481
- // no space before the selection
613
+ // no space before the selection, use the beginning of the tag
482
614
  spaceIndex = tag.plainTextBeginIndex;
483
615
  }
484
616
  }
485
617
  else {
486
- // the cursor is moved to the right
487
- spaceIndex = textValue.indexOf(' ', selection !== null && selection !== void 0 ? selection : 0);
618
+ // the cursor is moved to the right, find the fist index after the cursor
619
+ spaceIndex = textValue.indexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
488
620
  if (spaceIndex === -1) {
489
- // no space after the selection
621
+ // no space after the selection, use the end of the tag
490
622
  spaceIndex = (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : tag.plainTextBeginIndex;
491
623
  }
492
624
  }
493
- if (spaceIndex < tag.plainTextBeginIndex) {
494
- spaceIndex = tag.plainTextBeginIndex;
495
- }
496
- else if (spaceIndex > tag.plainTextEndIndex) {
497
- spaceIndex = tag.plainTextEndIndex;
498
- }
625
+ spaceIndex = Math.max(tag.plainTextBeginIndex, spaceIndex);
626
+ spaceIndex = Math.min(tag.plainTextEndIndex, spaceIndex);
499
627
  return spaceIndex;
500
628
  };
501
629
  /* @conditional-compile-remove(mention) */
@@ -503,10 +631,21 @@ const findNewSelectionIndexForMention = (tag, textValue, selection, previousSele
503
631
  * Handle mention tag edit and by word deleting
504
632
  *
505
633
  * @private
634
+ * @param props - Props for mention update HTML function.
635
+ * @returns Updated texts and indexes.
506
636
  */
507
- const handleMentionTagUpdate = (htmlText, oldPlainText, lastProcessedHTMLIndex, processedChange, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength) => {
508
- if (tag.tagType !== 'msft-mention' || tag.plainTextBeginIndex === undefined) {
509
- return ['', processedChange, lastProcessedHTMLIndex, null];
637
+ const handleMentionTagUpdate = (props) => {
638
+ const { htmlText, oldPlainText, change, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength } = props;
639
+ let processedChange = props.processedChange;
640
+ let lastProcessedHTMLIndex = props.lastProcessedHTMLIndex;
641
+ if (tag.tagType !== MSFT_MENTION_TAG || tag.plainTextBeginIndex === undefined) {
642
+ // not a mention tag
643
+ return {
644
+ result: '',
645
+ updatedChange: processedChange,
646
+ htmlIndex: lastProcessedHTMLIndex,
647
+ plainTextSelectionEndIndex: null
648
+ };
510
649
  }
511
650
  let result = '';
512
651
  let plainTextSelectionEndIndex = null;
@@ -529,9 +668,11 @@ const handleMentionTagUpdate = (htmlText, oldPlainText, lastProcessedHTMLIndex,
529
668
  }
530
669
  isSpaceLengthHandled = true;
531
670
  if (rangeStart === -1 || rangeStart === undefined || rangeStart < tag.plainTextBeginIndex) {
671
+ // rangeStart should be at least equal to tag.plainTextBeginIndex
532
672
  rangeStart = tag.plainTextBeginIndex;
533
673
  }
534
674
  if (rangeEnd > plainTextEndIndex) {
675
+ // rangeEnd should be at most equal to plainTextEndIndex
535
676
  rangeEnd = plainTextEndIndex;
536
677
  }
537
678
  if (rangeStart === tag.plainTextBeginIndex && rangeEnd === plainTextEndIndex) {
@@ -551,28 +692,61 @@ const handleMentionTagUpdate = (htmlText, oldPlainText, lastProcessedHTMLIndex,
551
692
  }
552
693
  endChangeDiff = rangeEnd - tag.plainTextBeginIndex - mentionTagLength;
553
694
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff);
554
- // plainTextSelectionEndIndex = rangeStart + processedChange.length;
695
+ if (startIndex < tag.plainTextBeginIndex) {
696
+ // if the change is before the tag, the selection should start from startIndex (rangeStart will be equal to tag.plainTextBeginIndex)
697
+ plainTextSelectionEndIndex = startIndex + change.length;
698
+ }
699
+ else {
700
+ // if the change is inside the tag, the selection should start with rangeStart
701
+ plainTextSelectionEndIndex = rangeStart + processedChange.length;
702
+ }
555
703
  lastProcessedHTMLIndex = tag.openTagIdx + tag.openTagBody.length + endChangeDiff;
556
704
  // processed change should not be changed as it should be added after the tag
557
705
  }
558
- return [result, processedChange, lastProcessedHTMLIndex, plainTextSelectionEndIndex];
706
+ return { result, updatedChange: processedChange, htmlIndex: lastProcessedHTMLIndex, plainTextSelectionEndIndex };
559
707
  };
560
708
  /* @conditional-compile-remove(mention) */
561
709
  /**
562
- * Go through the text and update it with the changed text
710
+ * Get closing tag information if exists otherwise return information as for self closing tag
563
711
  *
564
712
  * @private
713
+ * @param tag - Tag data.
714
+ * @returns Closing tag information for the provided tag.
565
715
  */
566
- const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldPlainTextEndIndex, change, mentionTrigger) => {
567
- let result = '';
568
- if (tags.length === 0) {
569
- // no tags added yet
570
- return [newPlainText, null];
716
+ const getTagClosingTagInfo = (tag) => {
717
+ let plainTextEndIndex = 0;
718
+ let closeTagIdx = 0;
719
+ let closeTagLength = 0;
720
+ if (tag.plainTextEndIndex !== undefined && tag.closeTagIdx !== undefined) {
721
+ // close tag exists
722
+ plainTextEndIndex = tag.plainTextEndIndex;
723
+ closeTagIdx = tag.closeTagIdx;
724
+ // tag.tagType.length + </>
725
+ closeTagLength = tag.tagType.length + 3;
726
+ }
727
+ else if (tag.plainTextBeginIndex !== undefined) {
728
+ // no close tag
729
+ plainTextEndIndex = tag.plainTextBeginIndex;
730
+ closeTagIdx = tag.openTagIdx + tag.openTagBody.length;
731
+ closeTagLength = 0;
571
732
  }
572
- if (startIndex === 0 && oldPlainTextEndIndex === oldPlainText.length) {
573
- // the whole text is changed
574
- return [newPlainText, null];
733
+ return { plainTextEndIndex, closeTagIdx, closeTagLength };
734
+ };
735
+ /* @conditional-compile-remove(mention) */
736
+ /**
737
+ * Go through the text and update it with the changed text
738
+ *
739
+ * @private
740
+ * @param props - Props for update HTML function.
741
+ * @returns Updated HTML and selection index if the selection index should be set.
742
+ */
743
+ const updateHTML = (props) => {
744
+ const { htmlText, oldPlainText, newPlainText, tags, startIndex, oldPlainTextEndIndex, change, mentionTrigger } = props;
745
+ if (tags.length === 0 || (startIndex === 0 && oldPlainTextEndIndex === oldPlainText.length - 1)) {
746
+ // no tags added yet or the whole text is changed
747
+ return { updatedHTML: newPlainText, updatedSelectionIndex: null };
575
748
  }
749
+ let result = '';
576
750
  let lastProcessedHTMLIndex = 0;
577
751
  // the value can be updated with empty string when the change covers more than 1 place (tag + before or after the tag)
578
752
  // in this case change won't be added as part of the tag
@@ -583,7 +757,7 @@ const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldP
583
757
  let processedChange = change;
584
758
  // end tag plain text index of the last processed tag
585
759
  let lastProcessedPlainTextTagEndIndex = 0;
586
- // as some tags/text can be removed fully, selectionEnd should be updated correctly
760
+ // as some tags/text can be removed fully, selection should be updated correctly
587
761
  let changeNewEndIndex = null;
588
762
  for (let i = 0; i < tags.length; i++) {
589
763
  const tag = tags[i];
@@ -592,165 +766,225 @@ const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldP
592
766
  }
593
767
  // all plain text indexes includes trigger length for the mention that shouldn't be included in
594
768
  // htmlText.substring because html strings don't include the trigger
595
- // mentionTagLength will be set only for 'msft-mention', otherwise should be 0
769
+ // mentionTagLength will be set only for mention tag, otherwise should be 0
596
770
  let mentionTagLength = 0;
597
771
  let isMentionTag = false;
598
- if (tag.tagType === 'msft-mention') {
772
+ if (tag.tagType === MSFT_MENTION_TAG) {
599
773
  mentionTagLength = mentionTrigger.length;
600
774
  isMentionTag = true;
601
775
  }
602
- //change start is before the open tag
603
776
  if (startIndex <= tag.plainTextBeginIndex) {
777
+ // change start is before the open tag
604
778
  // Math.max(lastProcessedPlainTextTagEndIndex, startIndex) is used as startIndex may not be in [[previous tag].plainTextEndIndex - tag.plainTextBeginIndex] range
605
779
  const startChangeDiff = tag.plainTextBeginIndex - Math.max(lastProcessedPlainTextTagEndIndex, startIndex);
606
780
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx - startChangeDiff) + processedChange;
781
+ processedChange = '';
607
782
  if (oldPlainTextEndIndex <= tag.plainTextBeginIndex) {
608
783
  // the whole change is before tag start
609
- // oldPlainTextEndIndex already includes mentionTag length
784
+ // mentionTag length can be ignored here as the change is before the tag
610
785
  const endChangeDiff = tag.plainTextBeginIndex - oldPlainTextEndIndex;
611
786
  lastProcessedHTMLIndex = tag.openTagIdx - endChangeDiff;
612
- processedChange = '';
613
787
  // the change is handled; exit
614
788
  break;
615
789
  }
616
790
  else {
617
791
  // change continues in the tag
618
792
  lastProcessedHTMLIndex = tag.openTagIdx;
619
- processedChange = '';
620
793
  // proceed to the next check
621
794
  }
622
795
  }
623
- let plainTextEndIndex = 0;
624
- let closeTagIdx = 0;
625
- let closeTagLength = 0;
626
- if (tag.plainTextEndIndex !== undefined && tag.closeTagIdx !== undefined) {
627
- // close tag exists
628
- plainTextEndIndex = tag.plainTextEndIndex;
629
- closeTagIdx = tag.closeTagIdx;
630
- // tag.tagType.length + </>
631
- closeTagLength = tag.tagType.length + 3;
632
- }
633
- else {
634
- // no close tag
635
- plainTextEndIndex = tag.plainTextBeginIndex;
636
- closeTagIdx = tag.openTagIdx + tag.openTagBody.length;
637
- closeTagLength = 0;
638
- }
639
- if (startIndex < plainTextEndIndex) {
796
+ const closingTagInfo = getTagClosingTagInfo(tag);
797
+ if (startIndex <= closingTagInfo.plainTextEndIndex) {
640
798
  // change started before the end tag
641
- if (startIndex <= tag.plainTextBeginIndex && oldPlainTextEndIndex === plainTextEndIndex) {
799
+ if (startIndex <= tag.plainTextBeginIndex && oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
642
800
  // the change is a tag or starts before the tag
643
801
  // tag should be removed, no matter if there are subtags
644
802
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
645
803
  processedChange = '';
646
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
804
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
647
805
  // the change is handled; exit
648
806
  break;
649
807
  }
650
- else if (startIndex >= tag.plainTextBeginIndex && oldPlainTextEndIndex < plainTextEndIndex) {
651
- // edge case: the change is between tag
808
+ else if (startIndex >= tag.plainTextBeginIndex && oldPlainTextEndIndex <= closingTagInfo.plainTextEndIndex) {
809
+ // the change is between the tag
652
810
  if (isMentionTag) {
653
811
  if (change !== '') {
654
- // mention tag should be deleted when user tries to edit it
655
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
656
- changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
812
+ if (startIndex !== tag.plainTextBeginIndex && startIndex !== closingTagInfo.plainTextEndIndex) {
813
+ // mention tag should be deleted when user tries to edit it in the middle
814
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
815
+ changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
816
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
817
+ }
818
+ else if (startIndex === tag.plainTextBeginIndex) {
819
+ // non empty change at the beginning of the mention tag to be added before the mention tag
820
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
821
+ changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
822
+ lastProcessedHTMLIndex = tag.openTagIdx;
823
+ }
824
+ else if (startIndex === closingTagInfo.plainTextEndIndex) {
825
+ // non empty change at the end of the mention tag to be added after the mention tag
826
+ result +=
827
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
828
+ processedChange;
829
+ changeNewEndIndex = closingTagInfo.plainTextEndIndex + processedChange.length;
830
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
831
+ }
657
832
  processedChange = '';
658
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
659
833
  }
660
834
  else {
661
- const [resultValue, updatedChange, htmlIndex, plainTextSelectionEndIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, processedChange, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
662
- result += resultValue;
663
- changeNewEndIndex = plainTextSelectionEndIndex;
664
- processedChange = updatedChange;
665
- lastProcessedHTMLIndex = htmlIndex;
835
+ const updateMentionTagResult = handleMentionTagUpdate({
836
+ htmlText,
837
+ oldPlainText,
838
+ lastProcessedHTMLIndex,
839
+ processedChange,
840
+ change,
841
+ tag,
842
+ closeTagIdx: closingTagInfo.closeTagIdx,
843
+ closeTagLength: closingTagInfo.closeTagLength,
844
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
845
+ startIndex,
846
+ oldPlainTextEndIndex,
847
+ mentionTagLength
848
+ });
849
+ result += updateMentionTagResult.result;
850
+ changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
851
+ processedChange = updateMentionTagResult.updatedChange;
852
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
666
853
  }
667
- // the change is handled; exit
668
- break;
669
854
  }
670
- else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content) {
855
+ else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
671
856
  // with subtags
672
- // before the tag content
673
857
  const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
674
- lastProcessedHTMLIndex = closeTagIdx;
675
- const [content, updatedChangeNewEndIndex] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, processedChange, mentionTrigger);
676
- result += stringBefore + content;
677
- changeNewEndIndex = updatedChangeNewEndIndex;
678
- break;
858
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
859
+ const updatedContent = updateHTML({
860
+ htmlText: tag.content,
861
+ oldPlainText,
862
+ newPlainText,
863
+ tags: tag.subTags,
864
+ startIndex,
865
+ oldPlainTextEndIndex,
866
+ change: processedChange,
867
+ mentionTrigger
868
+ });
869
+ result += stringBefore + updatedContent.updatedHTML;
870
+ changeNewEndIndex = updatedContent.updatedSelectionIndex;
679
871
  }
680
872
  else {
681
873
  // no subtags
682
- const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
683
- const endChangeDiff = oldPlainTextEndIndex - tag.plainTextBeginIndex - mentionTagLength;
874
+ const startChangeDiff = startIndex - tag.plainTextBeginIndex;
684
875
  result +=
685
876
  htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff) +
686
877
  processedChange;
687
878
  processedChange = '';
688
- lastProcessedHTMLIndex = tag.openTagIdx + tag.openTagBody.length + endChangeDiff;
689
- // the change is handled; exit
690
- break;
879
+ if (oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
880
+ const endChangeDiff = oldPlainTextEndIndex - tag.plainTextBeginIndex;
881
+ lastProcessedHTMLIndex = tag.openTagIdx + tag.openTagBody.length + endChangeDiff;
882
+ }
883
+ else if (oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
884
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
885
+ }
691
886
  }
887
+ // the change is handled; exit
888
+ break;
692
889
  }
693
- else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex > plainTextEndIndex) {
694
- //the change started in the tag but finishes somewhere further
890
+ else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
891
+ // the change started in the tag but finishes somewhere further
695
892
  const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
696
893
  if (isMentionTag) {
697
- const [resultValue, updatedChange, htmlIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, oldPlainText.substring(startIndex, startIndex + 1) !== ' ' && change === '' ? ' ' : '', // if substring !== ' ' && change is empty -> the change should be " " and not empty string but " " wasn't included in change; otherwise the part of mention should be just deleted without processedChange update
698
- tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
699
- result += resultValue;
700
- lastProcessedHTMLIndex = htmlIndex;
701
- processedChange = updatedChange;
894
+ const updateMentionTagResult = handleMentionTagUpdate({
895
+ htmlText,
896
+ oldPlainText,
897
+ lastProcessedHTMLIndex,
898
+ processedChange: '',
899
+ change,
900
+ tag,
901
+ closeTagIdx: closingTagInfo.closeTagIdx,
902
+ closeTagLength: closingTagInfo.closeTagLength,
903
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
904
+ startIndex,
905
+ oldPlainTextEndIndex,
906
+ mentionTagLength
907
+ });
908
+ result += updateMentionTagResult.result;
909
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
702
910
  // no need to handle plainTextSelectionEndIndex as the change will be added later
703
- // proceed with the next calculations
704
911
  }
705
912
  else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
706
913
  // with subtags
707
- // before the tag content
708
914
  const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
709
- lastProcessedHTMLIndex = closeTagIdx;
710
- const [content] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, '', // the part of the tag should be just deleted without processedChange update and change will be added after this tag
711
- mentionTrigger);
712
- result += stringBefore + content;
713
- // proceed with the next calculations
915
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
916
+ const updatedContent = updateHTML({
917
+ htmlText: tag.content,
918
+ oldPlainText,
919
+ newPlainText,
920
+ tags: tag.subTags,
921
+ startIndex,
922
+ oldPlainTextEndIndex,
923
+ change: '',
924
+ mentionTrigger
925
+ });
926
+ result += stringBefore + updatedContent.updatedHTML;
714
927
  }
715
928
  else {
716
929
  // no subtags
717
930
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff);
718
- lastProcessedHTMLIndex = closeTagIdx;
719
- // proceed with the next calculations
931
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
720
932
  }
933
+ // proceed with the next calculations
721
934
  }
722
- else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex > plainTextEndIndex) {
935
+ else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
723
936
  // the change starts before the tag and finishes after it
724
937
  // tag should be removed, no matter if there are subtags
725
938
  // no need to save anything between lastProcessedHTMLIndex and closeTagIdx + closeTagLength
726
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
939
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
727
940
  // proceed with the next calculations
728
941
  }
729
- else if (startIndex === tag.plainTextBeginIndex && oldPlainTextEndIndex > plainTextEndIndex) {
942
+ else if (startIndex === tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
730
943
  // the change starts in the tag and finishes after it
731
944
  // tag should be removed, no matter if there are subtags
732
945
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx);
733
946
  // processedChange shouldn't be updated as it will be added after the tag
734
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
947
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
735
948
  // proceed with the next calculations
736
949
  }
737
- else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex < plainTextEndIndex) {
950
+ else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
738
951
  // the change starts before the tag and ends in a tag
739
952
  if (isMentionTag) {
740
- const [resultValue, , htmlIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, '', // the part of mention should be just deleted without processedChange update
741
- tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
742
- changeNewEndIndex = tag.plainTextBeginIndex;
743
- result += resultValue;
744
- lastProcessedHTMLIndex = htmlIndex;
953
+ // mention tag
954
+ const updateMentionTagResult = handleMentionTagUpdate({
955
+ htmlText,
956
+ oldPlainText,
957
+ lastProcessedHTMLIndex,
958
+ processedChange: '',
959
+ change,
960
+ tag,
961
+ closeTagIdx: closingTagInfo.closeTagIdx,
962
+ closeTagLength: closingTagInfo.closeTagLength,
963
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
964
+ startIndex,
965
+ oldPlainTextEndIndex,
966
+ mentionTagLength
967
+ });
968
+ changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
969
+ result += updateMentionTagResult.result;
970
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
745
971
  }
746
972
  else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
747
973
  // with subtags
748
- // before the tag content
749
974
  const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
750
- lastProcessedHTMLIndex = closeTagIdx;
751
- const [content] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, processedChange, // processedChange should equal '' and the part of the tag should be deleted as the change was handled before this tag
752
- mentionTrigger);
753
- result += stringBefore + content;
975
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
976
+ const updatedContent = updateHTML({
977
+ htmlText: tag.content,
978
+ oldPlainText,
979
+ newPlainText,
980
+ tags: tag.subTags,
981
+ startIndex,
982
+ oldPlainTextEndIndex,
983
+ change: processedChange,
984
+ mentionTrigger
985
+ });
986
+ processedChange = '';
987
+ result += stringBefore + updatedContent.updatedHTML;
754
988
  }
755
989
  else {
756
990
  // no subtags
@@ -758,123 +992,67 @@ const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldP
758
992
  htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length) + processedChange;
759
993
  processedChange = '';
760
994
  // oldPlainTextEndIndex already includes mentionTag length
761
- const endChangeDiff = plainTextEndIndex - oldPlainTextEndIndex;
995
+ const endChangeDiff = closingTagInfo.plainTextEndIndex - oldPlainTextEndIndex;
762
996
  // as change may be before the end of the tag, we need to add the rest of the tag
763
- lastProcessedHTMLIndex = closeTagIdx - endChangeDiff;
997
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx - endChangeDiff;
764
998
  }
765
999
  // the change is handled; exit
766
1000
  break;
767
1001
  }
768
- else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex === plainTextEndIndex) {
769
- // the change starts in the tag and ends at the end of a tag
770
- if (isMentionTag) {
771
- if (change !== '' && startIndex === plainTextEndIndex) {
772
- // non empty change at the end of the mention tag to be added after the mention tag
773
- result += htmlText.substring(lastProcessedHTMLIndex, closeTagIdx + closeTagLength) + processedChange;
774
- changeNewEndIndex = plainTextEndIndex + processedChange.length;
775
- processedChange = '';
776
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
777
- }
778
- else if (change !== '') {
779
- // mention tag should be deleted when user tries to edit it
780
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
781
- changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
782
- processedChange = '';
783
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
784
- }
785
- else {
786
- const [resultValue, updatedChange, htmlIndex, plainTextSelectionEndIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, processedChange, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
787
- result += resultValue;
788
- processedChange = updatedChange;
789
- lastProcessedHTMLIndex = htmlIndex;
790
- changeNewEndIndex = plainTextSelectionEndIndex;
791
- }
792
- // the change is handled; exit
793
- break;
794
- }
795
- else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
796
- // with subtags
797
- // before the tag content
798
- const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
799
- lastProcessedHTMLIndex = closeTagIdx;
800
- const [content, updatedChangeNewEndIndex] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, processedChange, mentionTrigger);
801
- result += stringBefore + content;
802
- changeNewEndIndex = updatedChangeNewEndIndex;
803
- break;
804
- }
805
- else {
806
- // no subtags
807
- const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
808
- result +=
809
- htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff) +
810
- processedChange;
811
- processedChange = '';
812
- lastProcessedHTMLIndex = closeTagIdx;
813
- // the change is handled; exit
814
- break;
815
- }
816
- }
817
- lastProcessedPlainTextTagEndIndex = plainTextEndIndex;
1002
+ lastProcessedPlainTextTagEndIndex = closingTagInfo.plainTextEndIndex;
818
1003
  }
819
- if (i === tags.length - 1 && oldPlainTextEndIndex >= plainTextEndIndex) {
1004
+ if (i === tags.length - 1 && oldPlainTextEndIndex >= closingTagInfo.plainTextEndIndex) {
820
1005
  // the last tag should handle the end of the change if needed
821
1006
  // oldPlainTextEndIndex already includes mentionTag length
822
- const endChangeDiff = oldPlainTextEndIndex - plainTextEndIndex;
823
- if (startIndex >= plainTextEndIndex) {
824
- const startChangeDiff = startIndex - plainTextEndIndex;
1007
+ const endChangeDiff = oldPlainTextEndIndex - closingTagInfo.plainTextEndIndex;
1008
+ if (startIndex >= closingTagInfo.plainTextEndIndex) {
1009
+ const startChangeDiff = startIndex - closingTagInfo.plainTextEndIndex;
825
1010
  result +=
826
- htmlText.substring(lastProcessedHTMLIndex, closeTagIdx + closeTagLength + startChangeDiff) + processedChange;
1011
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + startChangeDiff) + processedChange;
827
1012
  }
828
1013
  else {
829
- result += htmlText.substring(lastProcessedHTMLIndex, closeTagIdx + closeTagLength) + processedChange;
1014
+ result +=
1015
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
1016
+ processedChange;
830
1017
  }
831
1018
  processedChange = '';
832
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength + endChangeDiff;
1019
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + endChangeDiff;
833
1020
  // the change is handled; exit
834
1021
  // break is not required here as this is the last element but added for consistency
835
1022
  break;
836
1023
  }
837
1024
  }
838
1025
  if (lastProcessedHTMLIndex < htmlText.length) {
1026
+ // add the rest of the html string
839
1027
  result += htmlText.substring(lastProcessedHTMLIndex);
840
1028
  }
841
- return [result, changeNewEndIndex];
1029
+ return { updatedHTML: result, updatedSelectionIndex: changeNewEndIndex };
842
1030
  };
843
1031
  /* @conditional-compile-remove(mention) */
844
1032
  /**
845
1033
  * Given the oldText and newText, find the start index, old end index and new end index for the changes
846
1034
  *
847
- * @param oldText - the old text
848
- * @param newText - the new text
849
- * @param selectionEnd - the end of the selection
850
- * @returns change start index, old end index and new end index. The old and new end indexes are exclusive.
851
1035
  * @private
1036
+ * @param props - Props for finding stings diff indexes function.
1037
+ * @returns Indexes for change start and ends in new and old texts. The old and new end indexes are exclusive.
852
1038
  */
853
- const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a valid position in the input field
854
- ) => {
1039
+ const findStringsDiffIndexes = (props) => {
1040
+ const { oldText, newText, previousSelectionStart, previousSelectionEnd, currentSelectionStart, currentSelectionEnd } = props;
855
1041
  const newTextLength = newText.length;
856
1042
  const oldTextLength = oldText.length;
857
- let changeStart = 0;
1043
+ // let changeStart = 0;
858
1044
  let newChangeEnd = newTextLength;
859
1045
  let oldChangeEnd = oldTextLength;
860
- const length = Math.min(newTextLength, oldTextLength, selectionEnd);
861
- for (let i = 0; i < length; i++) {
862
- if (newText[i] !== oldText[i]) {
863
- // the symbol with changeStart index is updated
864
- changeStart = i;
865
- break;
866
- }
867
- else if (i === length - 1 && newText[i] === oldText[i]) {
868
- // the symbol is added at the end of inputTextValue
869
- changeStart = length;
870
- break;
871
- }
872
- }
1046
+ const previousSelectionStartValue = previousSelectionStart > -1 ? previousSelectionStart : oldTextLength;
1047
+ const previousSelectionEndValue = previousSelectionEnd > -1 ? previousSelectionEnd : oldTextLength;
1048
+ const currentSelectionStartValue = currentSelectionStart > -1 ? currentSelectionStart : newTextLength;
1049
+ const currentSelectionEndValue = currentSelectionEnd > -1 ? currentSelectionEnd : newTextLength;
1050
+ const changeStart = Math.min(previousSelectionStartValue, previousSelectionEndValue, currentSelectionStartValue, currentSelectionEndValue, newTextLength, oldTextLength);
873
1051
  if (oldTextLength < newTextLength) {
874
1052
  //insert or replacement
875
1053
  if (oldTextLength === changeStart) {
876
1054
  // when change was at the end of string
877
- // Change is found
1055
+ // change is found
878
1056
  newChangeEnd = newTextLength;
879
1057
  oldChangeEnd = oldTextLength;
880
1058
  }
@@ -883,7 +1061,7 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
883
1061
  newChangeEnd = newTextLength - i - 1;
884
1062
  oldChangeEnd = oldTextLength - i - 1;
885
1063
  if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
886
- // Change is found
1064
+ // change is found
887
1065
  break;
888
1066
  }
889
1067
  }
@@ -896,7 +1074,7 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
896
1074
  //deletion or replacement
897
1075
  if (newTextLength === changeStart) {
898
1076
  // when change was at the end of string
899
- // Change is found
1077
+ // change is found
900
1078
  newChangeEnd = newTextLength;
901
1079
  oldChangeEnd = oldTextLength;
902
1080
  }
@@ -905,7 +1083,7 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
905
1083
  newChangeEnd = newTextLength - i - 1;
906
1084
  oldChangeEnd = oldTextLength - i - 1;
907
1085
  if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
908
- // Change is found
1086
+ // change is found
909
1087
  break;
910
1088
  }
911
1089
  }
@@ -915,12 +1093,12 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
915
1093
  }
916
1094
  }
917
1095
  else {
918
- //replacement
1096
+ // replacement
919
1097
  for (let i = 1; i < oldTextLength && oldTextLength - i >= changeStart; i++) {
920
1098
  newChangeEnd = newTextLength - i - 1;
921
1099
  oldChangeEnd = oldTextLength - i - 1;
922
1100
  if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
923
- // Change is found
1101
+ // change is found
924
1102
  break;
925
1103
  }
926
1104
  }
@@ -935,13 +1113,30 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
935
1113
  return { changeStart, oldChangeEnd, newChangeEnd };
936
1114
  };
937
1115
  /* @conditional-compile-remove(mention) */
1116
+ /**
1117
+ * Get the html string for the mention suggestion.
1118
+ *
1119
+ * @private
1120
+ * @param suggestion - The mention suggestion.
1121
+ * @param localeStrings - The locale strings.
1122
+ * @returns The html string for the mention suggestion.
1123
+ */
938
1124
  const htmlStringForMentionSuggestion = (suggestion, localeStrings) => {
939
1125
  const idHTML = ' id ="' + suggestion.id + '"';
940
1126
  const displayTextHTML = ' displayText ="' + suggestion.displayText + '"';
941
1127
  const displayText = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
942
- return '<msft-mention' + idHTML + displayTextHTML + '>' + displayText + '</msft-mention>';
1128
+ return '<' + MSFT_MENTION_TAG + idHTML + displayTextHTML + '>' + displayText + '</' + MSFT_MENTION_TAG + '>';
943
1129
  };
944
1130
  /* @conditional-compile-remove(mention) */
1131
+ /**
1132
+ * Get display name for the mention suggestion.
1133
+ *
1134
+ * @private
1135
+ *
1136
+ * @param suggestion - The mention suggestion.
1137
+ * @param localeStrings - The locale strings.
1138
+ * @returns The display name for the mention suggestion or display name placeholder if display name is empty.
1139
+ */
945
1140
  const getDisplayNameForMentionSuggestion = (suggestion, localeStrings) => {
946
1141
  const displayNamePlaceholder = localeStrings.participantItem.displayNamePlaceholder;
947
1142
  return suggestion.displayText !== '' ? suggestion.displayText : displayNamePlaceholder !== null && displayNamePlaceholder !== void 0 ? displayNamePlaceholder : '';
@@ -949,8 +1144,9 @@ const getDisplayNameForMentionSuggestion = (suggestion, localeStrings) => {
949
1144
  /* @conditional-compile-remove(mention) */
950
1145
  /**
951
1146
  * Parse the text and return the tags and the plain text in one go
1147
+ * @private
952
1148
  * @param text - The text to parse for HTML tags
953
- * @param trigger The trigger to show for the msft-mention tag in plain text
1149
+ * @param trigger The trigger to show for the mention tag in plain text
954
1150
  *
955
1151
  * @returns An array of tags and the plain text representation
956
1152
  */
@@ -994,7 +1190,7 @@ const textToTagParser = (text, trigger) => {
994
1190
  // Tag startIdx is absolute to the text. This is updated later to be relative to the parent tag
995
1191
  currentOpenTag.content = text.substring(currentOpenTag.openTagIdx + currentOpenTag.openTagBody.length, foundHtmlTag.startIdx);
996
1192
  // Insert the plain text pieces for the sub tags
997
- if (currentOpenTag.tagType === 'msft-mention') {
1193
+ if (currentOpenTag.tagType === MSFT_MENTION_TAG) {
998
1194
  plainTextRepresentation =
999
1195
  plainTextRepresentation.slice(0, currentOpenTag.plainTextBeginIndex) +
1000
1196
  trigger +
@@ -1024,7 +1220,7 @@ const textToTagParser = (text, trigger) => {
1024
1220
  // Update parsing index; move past the end of the close tag
1025
1221
  parseIndex = foundHtmlTag.startIdx + foundHtmlTag.content.length;
1026
1222
  } // While parseIndex < text.length loop
1027
- return [tags, plainTextRepresentation];
1223
+ return { tags, plainText: plainTextRepresentation };
1028
1224
  };
1029
1225
  /* @conditional-compile-remove(mention) */
1030
1226
  const parseOpenTag = (tag, startIdx) => {