mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
feat(emoji): add Unicode 17 emoji and fix mixed skin tones (#2883)
This commit is contained in:
1 parent
7601bf98ee
commit
70e1ce682a
22 files changed
+238
-118
No files matched your search
@@ -5,7 +5,7 @@ import {type EmojiProvider, setEmojiParserConfig} from '@app/features/messaging/
|
||||
import {SKIN_TONE_SURROGATES} from '@fluxer/constants/src/EmojiConstants';
|
||||
|
||||
const emojiProvider: EmojiProvider = {
|
||||
getSurrogateName: UnicodeEmojis.getSurrogateName,
|
||||
matchEmojiSurrogates: UnicodeEmojis.matchEmojiSurrogates,
|
||||
findEmojiByName: UnicodeEmojis.findEmojiByShortcodeName,
|
||||
findEmojiWithSkinTone: UnicodeEmojis.findEmojiWithSkinTone,
|
||||
};
|
||||
@@ -13,9 +13,6 @@ const emojiProvider: EmojiProvider = {
|
||||
export function initializeEmojiParser(): void {
|
||||
setEmojiParserConfig({
|
||||
emojiProvider,
|
||||
get emojiRegex() {
|
||||
return UnicodeEmojis.EMOJI_SURROGATE_RE;
|
||||
},
|
||||
skinToneSurrogates: SKIN_TONE_SURROGATES,
|
||||
});
|
||||
}
|
||||
@@ -4,17 +4,21 @@ export interface UnicodeEmoji {
|
||||
surrogates: string;
|
||||
}
|
||||
|
||||
export interface EmojiSurrogateMatch {
|
||||
start: number;
|
||||
end: number;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface EmojiProvider {
|
||||
getSurrogateName(surrogate: string): string | null;
|
||||
matchEmojiSurrogates(text: string): Iterable<EmojiSurrogateMatch>;
|
||||
findEmojiByName(name: string): UnicodeEmoji | null;
|
||||
findEmojiWithSkinTone(baseName: string, skinToneSurrogate: string): UnicodeEmoji | null;
|
||||
}
|
||||
|
||||
export interface EmojiParserConfig {
|
||||
emojiProvider?: EmojiProvider;
|
||||
emojiRegex?: RegExp;
|
||||
skinToneSurrogates?: ReadonlyArray<string>;
|
||||
convertToCodePoints?: (emoji: string) => string;
|
||||
}
|
||||
|
||||
let globalEmojiConfig: EmojiParserConfig | null = null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {convertToCodePoints} from '@app/features/expressions/utils/EmojiCodepointUtils';
|
||||
import {flattenAST} from '@app/features/messaging/utils/markdown/parser/AstUtils';
|
||||
import {getEmojiParserConfig} from '@app/features/messaging/utils/markdown/parser/EmojiParsers';
|
||||
import {MARKDOWN_PARSER_WASM_BASE64} from '@app/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes';
|
||||
@@ -202,14 +203,6 @@ class Utf8OffsetTracker {
|
||||
}
|
||||
}
|
||||
|
||||
function defaultCodepoints(emoji: string): string {
|
||||
const containsZwJ = emoji.includes('');
|
||||
const processed = containsZwJ ? emoji : emoji.replace(/️/g, '');
|
||||
return Array.from(processed)
|
||||
.map((char) => char.codePointAt(0)?.toString(16).replace(/^0+/, '') || '')
|
||||
.join('-');
|
||||
}
|
||||
|
||||
const PLAINTEXT_SYMBOLS = new Set(['™', '™️', '©', '©️', '®', '®️']);
|
||||
const SPECIAL_SHORTCODES: Record<string, string> = {
|
||||
tm: '™',
|
||||
@@ -226,29 +219,23 @@ function buildEmojiContext(input: string): string {
|
||||
const provider = config?.emojiProvider;
|
||||
let context = '';
|
||||
if (!provider) return context;
|
||||
const convertToCodePoints = config.convertToCodePoints || defaultCodepoints;
|
||||
const emojiRegex = config.emojiRegex;
|
||||
if (emojiRegex) {
|
||||
const offsetTracker = new Utf8OffsetTracker();
|
||||
emojiRegex.lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = emojiRegex.exec(input)) !== null) {
|
||||
const candidate = match[0];
|
||||
if (!candidate || PLAINTEXT_SYMBOLS.has(candidate)) continue;
|
||||
const name = provider.getSurrogateName(candidate);
|
||||
if (!name) continue;
|
||||
const candidateBytes = textEncoder.encode(candidate).byteLength;
|
||||
const byteOffset = offsetTracker.offsetFor(input, match.index);
|
||||
context += appendContextLine([
|
||||
'S',
|
||||
String(byteOffset),
|
||||
String(candidateBytes),
|
||||
candidate,
|
||||
name,
|
||||
convertToCodePoints(candidate),
|
||||
]);
|
||||
offsetTracker.advance(candidate.length, candidateBytes);
|
||||
}
|
||||
const offsetTracker = new Utf8OffsetTracker();
|
||||
for (const match of provider.matchEmojiSurrogates(input)) {
|
||||
const candidate = input.slice(match.start, match.end);
|
||||
if (PLAINTEXT_SYMBOLS.has(candidate)) continue;
|
||||
const name = match.name;
|
||||
if (!name) continue;
|
||||
const candidateBytes = textEncoder.encode(candidate).byteLength;
|
||||
const byteOffset = offsetTracker.offsetFor(input, match.start);
|
||||
context += appendContextLine([
|
||||
'S',
|
||||
String(byteOffset),
|
||||
String(candidateBytes),
|
||||
candidate,
|
||||
name,
|
||||
convertToCodePoints(candidate),
|
||||
]);
|
||||
offsetTracker.advance(candidate.length, candidateBytes);
|
||||
}
|
||||
const shortcodeRegex = /:([\p{L}\p{N}_-]+):/gu;
|
||||
const seen = new Set<string>();
|
||||
|
||||
Reference in new issue
Block a user