Repository: draft-js-plugins/draft-js-plugins (ts)

Issue:
Multi-character triggers no longer work
## Environment

- @draft-js-plugins/editor version: v4.1.0
- plugin name and version: @draft-js-plugins/mention v4.4.1

## Description

Multi-character triggers appear to be broken for `@draft-js-plugins/mention`.

With [this PR](https://github.com/draft-js-plugins/draft-js-plugins/pull/1807/files#diff-f5be53e8ae08de0a2489882b1824da7087229abf7a92b29c8d5f3a9522e43690R45), support for multi-character triggers appears to have gone away.  I've verified that a change along the following lines to `@draft-js-plugins/mention` v4.4.1 restores the support for multi-character triggers, although I'm not sure whether there are other unwanted side effects, e.g., related to the new group capture:

```patch
diff --git a/packages/mention/src/mentionSuggestionsStrategy.ts b/packages/mention/src/mentionSuggestionsStrategy.ts
index 3107214a..f7d726a8 100644
--- a/packages/mention/src/mentionSuggestionsStrategy.ts
+++ b/packages/mention/src/mentionSuggestionsStrategy.ts
@@ -67,9 +67,9 @@ export default (
   regExp: string
 ): ((contentBlock: ContentBlock, callback: FindWithRegexCb) => void) => {
   //eslint-disable-line
-  const triggerPattern = `[${triggers
+  const triggerPattern = `(${triggers
     .map((trigger) => escapeRegExp(trigger))
-    .join('')}]`;
+    .join('|')})`;
   const MENTION_REGEX = supportWhiteSpace
     ? new RegExp(`${triggerPattern}(${regExp}|\\s)*`, 'g')
     : new RegExp(`(\\s|^)${triggerPattern}${regExp}*`, 'g');
```

Note that the earlier code in the diff is constructing a regular expression along the lines of `/[trigger](...)/`; i.e., it's collapsing the multi-character trigger into a character class search.

In version v3.1.5 of `draft-js-mention-plugin`, a trigger of "in:" worked for me:

![2021-04-28_13-22](https://user-images.githubusercontent.com/760949/116460888-cefe6680-a824-11eb-8a37-3a1fd6367589.png)


Pull request description:
[Mention Plugin] Fixing multi-character trigger
## Checklist

- [x] Fix any eslint errors that occur
- [x] Update change-log for every plugin you touch
- [x] Add/Update tests if you add/change new functionality
- [x] Add/Update docs if you add/change functionality
- [x] Enable "Allow edits from maintainers" for this PR

## Use-case/Problem

closes #2017 


Patch:
diff --git a/packages/mention/CHANGELOG.md b/packages/mention/CHANGELOG.md
index 2b2f062d..038d1e74 100644
--- a/packages/mention/CHANGELOG.md
+++ b/packages/mention/CHANGELOG.md
@@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## To Be Released
 
+## 4.5.0
+
+- fixing multi-character trigger [#2017](https://github.com/draft-js-plugins/draft-js-plugins/issues/2017)
 - change MentionSuggestions to popper.js with option `popperOptions`, `popoverContainer` and deprecate `popoverComponent` and `positionSuggestions` [#1933](https://github.com/draft-js-plugins/draft-js-plugins/issues/1933)
 - Fix @-mentions to work when the trigger character appears within the search string, for instance an email address.
 
diff --git a/packages/mention/package.json b/packages/mention/package.json
index 94f92580..83d908bd 100644
--- a/packages/mention/package.json
+++ b/packages/mention/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@draft-js-plugins/mention",
-  "version": "4.4.1",
+  "version": "4.5.0",
   "sideEffects": false,
   "description": "Mention Plugin for DraftJS",
   "author": {
diff --git a/packages/mention/src/mentionSuggestionsStrategy.ts b/packages/mention/src/mentionSuggestionsStrategy.ts
index 3107214a..0e02e164 100644
--- a/packages/mention/src/mentionSuggestionsStrategy.ts
+++ b/packages/mention/src/mentionSuggestionsStrategy.ts
@@ -14,6 +14,63 @@ function checkForWhiteSpaceBeforeTrigger(text: string, index: number): boolean {
   return whitespaceRegEx.test(text[index - 1]);
 }
 
+function findInContentBlock(
+  regex: RegExp,
+  text: string,
+  nonEntityStart: number,
+  callback: FindWithRegexCb
+): void {
+  let matchArr;
+  let start;
+  let prevLastIndex = regex.lastIndex;
+
+  // Go through all matches in the text and return the indices to the callback
+  // Break the loop if lastIndex is not changed
+  while ((matchArr = regex.exec(text)) !== null) {
+    if (regex.lastIndex === prevLastIndex) {
+      break;
+    }
+
+    prevLastIndex = regex.lastIndex;
+    start = nonEntityStart + matchArr.index;
+    const end = start + matchArr[0].length;
+
+    if (whitespaceRegEx.test(text[start])) {
+      //trim the result so that we have no whitespaces
+      start += 1;
+    }
+
+    callback(start, end);
+  }
+}
+
+function findInContentBlockWithWhitespace(
+  regex: RegExp,
+  text: string,
+  nonEntityStart: number,
+  callback: FindWithRegexCb
+): void {
+  let matchArr;
+  let start;
+  let prevLastIndex = regex.lastIndex;
+
+  // Go through all matches in the text and return the indices to the callback
+  // Break the loop if lastIndex is not changed
+  while ((matchArr = regex.exec(text)) !== null) {
+    if (regex.lastIndex === prevLastIndex) {
+      break;
+    }
+    prevLastIndex = regex.lastIndex;
+    start = nonEntityStart + matchArr.index;
+    const end = start + matchArr[0].length;
+
+    //check if whitespace support is active that the char before the trigger is a white space #1844
+    if (checkForWhiteSpaceBeforeTrigger(text, matchArr.index)) {
+      callback(start, end);
+    }
+  }
+}
+
 const findWithRegex = (
   regex: RegExp,
   contentBlock: ContentBlock,
@@ -27,35 +84,10 @@ const findWithRegex = (
     (character) => !character.getEntity(),
     (nonEntityStart, nonEntityEnd) => {
       const text = contentBlockText.slice(nonEntityStart, nonEntityEnd);
-      let matchArr;
-      let start;
-      let prevLastIndex = regex.lastIndex;
-
-      // Go through all matches in the text and return the indices to the callback
-      // Break the loop if lastIndex is not changed
-      while ((matchArr = regex.exec(text)) !== null) {
-        // eslint-disable-line
-        if (regex.lastIndex === prevLastIndex) {
-          break;
-        }
-        //console.log(matchArr, text[matchArr.index - 1], text);
-        prevLastIndex = regex.lastIndex;
-        start = nonEntityStart + matchArr.index;
-        const end = start + matchArr[0].length;
-
-        if (!supportWhiteSpace && whitespaceRegEx.test(text[start])) {
-          //trim the result so that we have no whitespaces
-          start += 1;
-        }
-
-        //check if whitespace support is active that the char before the trigger is a white space #1844
-        if (
-          (supportWhiteSpace &&
-            checkForWhiteSpaceBeforeTrigger(text, matchArr.index)) ||
-          !supportWhiteSpace
-        ) {
-          callback(start, end);
-        }
+      if (supportWhiteSpace) {
+        findInContentBlockWithWhitespace(regex, text, nonEntityStart, callback);
+      } else {
+        findInContentBlock(regex, text, nonEntityStart, callback);
       }
     }
   );
@@ -66,13 +98,13 @@ export default (
   supportWhiteSpace: boolean,
   regExp: string
 ): ((contentBlock: ContentBlock, callback: FindWithRegexCb) => void) => {
-  //eslint-disable-line
-  const triggerPattern = `[${triggers
+  const triggerPattern = `(${triggers
     .map((trigger) => escapeRegExp(trigger))
-    .join('')}]`;
+    .join('|')})`;
   const MENTION_REGEX = supportWhiteSpace
     ? new RegExp(`${triggerPattern}(${regExp}|\\s)*`, 'g')
     : new RegExp(`(\\s|^)${triggerPattern}${regExp}*`, 'g');
+
   return (contentBlock: ContentBlock, callback: FindWithRegexCb) => {
     findWithRegex(MENTION_REGEX, contentBlock, supportWhiteSpace, callback);
   };


Test patch:
diff --git a/packages/mention/src/__test__/mentionSuggestionsStrategy.test.ts b/packages/mention/src/__test__/mentionSuggestionsStrategy.test.ts
index 3661c2d6..c85440d2 100644
--- a/packages/mention/src/__test__/mentionSuggestionsStrategy.test.ts
+++ b/packages/mention/src/__test__/mentionSuggestionsStrategy.test.ts
@@ -53,6 +53,24 @@ describe('test strategy with whitespace support disabled', () => {
         [14, 19],
       ],
     ],
+    ['multi char trigger - empty string', '', [':in:'], []],
+    ['multi char trigger - match single word', ':in:the ', [':in:'], [[0, 7]]],
+    [
+      'multi char trigger - match within text',
+      'a lof :in:of text',
+      [':in:'],
+      [[6, 12]],
+    ],
+    [
+      'multi char trigger - should match multiple mentions',
+      ':in:the :in:walking :in:dead',
+      [':in:'],
+      [
+        [0, 7],
+        [8, 19],
+        [20, 28],
+      ],
+    ],
   ])('%s', (_description, text, trigger, result) => {
     const callback = jest.fn();
     mentionSuggestionsStrategy(
@@ -99,6 +117,18 @@ describe('test strategy with whitespace support enabled', () => {
         [15, 27],
       ],
     ],
+
+    ['multi char trigger - empty string', '', [':in:'], []],
+    ['multi char trigger - match single word', ':in:the ', [':in:'], [[0, 8]]],
+    [
+      'multi char trigger - should match multiple mentions with spaces',
+      ':in:the walking dead tv :in:the white house',
+      [':in:'],
+      [
+        [0, 24],
+        [24, 43],
+      ],
+    ],
   ])('%s', (_description, text, trigger, result) => {
     const callback = jest.fn();
     mentionSuggestionsStrategy(