Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit e69ba24

Browse files
authored
Fix edge case around composer handling gendered facepalm emoji (#8686)
* Write tests around composer badly handling gendered facepalm emoji * Commit export for tests to be happy * Fix edge case around composer handling gendered facepalm emoji * Fix offset calculations and make code more readable
1 parent 90dfb8d commit e69ba24

File tree

2 files changed

+51
-8
lines changed

2 files changed

+51
-8
lines changed

src/editor/parts.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ abstract class BasePart {
9393
this._text = text;
9494
}
9595

96+
// chr can also be a grapheme cluster
9697
protected acceptsInsertion(chr: string, offset: number, inputType: string): boolean {
9798
return true;
9899
}
@@ -128,14 +129,20 @@ abstract class BasePart {
128129
// append str, returns the remaining string if a character was rejected.
129130
public appendUntilRejected(str: string, inputType: string): string | undefined {
130131
const offset = this.text.length;
131-
for (let i = 0; i < str.length; ++i) {
132-
const chr = str.charAt(i);
133-
if (!this.acceptsInsertion(chr, offset + i, inputType)) {
134-
this._text = this._text + str.slice(0, i);
135-
return str.slice(i);
132+
// Take a copy as we will be taking chunks off the start of the string as we process them
133+
// To only need to grapheme split the bits of the string we're working on.
134+
let buffer = str;
135+
while (buffer) {
136+
// We use lodash's grapheme splitter to avoid breaking apart compound emojis
137+
const [char] = split(buffer, "", 2);
138+
if (!this.acceptsInsertion(char, offset + str.length - buffer.length, inputType)) {
139+
break;
136140
}
141+
buffer = buffer.slice(char.length);
137142
}
138-
this._text = this._text + str;
143+
144+
this._text += str.slice(0, str.length - buffer.length);
145+
return buffer || undefined;
139146
}
140147

141148
// inserts str at offset if all the characters in str were accepted, otherwise don't do anything
@@ -361,7 +368,7 @@ class NewlinePart extends BasePart implements IBasePart {
361368
}
362369
}
363370

364-
class EmojiPart extends BasePart implements IBasePart {
371+
export class EmojiPart extends BasePart implements IBasePart {
365372
protected acceptsInsertion(chr: string, offset: number): boolean {
366373
return EMOJIBASE_REGEX.test(chr);
367374
}
@@ -555,7 +562,8 @@ export class PartCreator {
555562
case "\n":
556563
return new NewlinePart();
557564
default:
558-
if (EMOJIBASE_REGEX.test(input[0])) {
565+
// We use lodash's grapheme splitter to avoid breaking apart compound emojis
566+
if (EMOJIBASE_REGEX.test(split(input, "", 2)[0])) {
559567
return new EmojiPart();
560568
}
561569
return new PlainPart();

test/editor/parts-test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { EmojiPart, PlainPart } from "../../src/editor/parts";
18+
19+
describe("editor/parts", () => {
20+
describe("appendUntilRejected", () => {
21+
const femaleFacepalmEmoji = "🤦‍♀️";
22+
23+
it("should not accept emoji strings into type=plain", () => {
24+
const part = new PlainPart();
25+
expect(part.appendUntilRejected(femaleFacepalmEmoji, "")).toEqual(femaleFacepalmEmoji);
26+
expect(part.text).toEqual("");
27+
});
28+
29+
it("should accept emoji strings into type=emoji", () => {
30+
const part = new EmojiPart();
31+
expect(part.appendUntilRejected(femaleFacepalmEmoji, "")).toBeUndefined();
32+
expect(part.text).toEqual(femaleFacepalmEmoji);
33+
});
34+
});
35+
});

0 commit comments

Comments
 (0)