@@ -207,6 +207,71 @@ export function matchEmoticonAt(
207207 return null
208208}
209209
210+ const SHORTCODE_AT_START_RE = / ^ : ( [ a - z A - Z 0 - 9 _ + - ] + ) : /
211+ const graphemeSegmenter = new Intl . Segmenter ( undefined , {
212+ granularity : 'grapheme' ,
213+ } )
214+
215+ function isEmojiGrapheme ( grapheme : string ) : boolean {
216+ if ( / \p{ Extended_Pictographic} / u. test ( grapheme ) ) return true
217+ if ( / ^ \p{ Regional_Indicator} { 2 } $ / u. test ( grapheme ) ) return true
218+ if ( / ^ [ # * 0 - 9 ] \uFE0F ? \u20E3 $ / u. test ( grapheme ) ) return true
219+ return false
220+ }
221+
222+ function firstGrapheme ( text : string ) : string {
223+ const first = graphemeSegmenter . segment ( text ) [ Symbol . iterator ] ( ) . next ( ) . value
224+ return first ?. segment ?? text [ 0 ] ?? ''
225+ }
226+
227+ /** True when the body is only emoji, shortcodes, emoticons, and whitespace. */
228+ export function isEmojiOnlyMessage (
229+ body : string ,
230+ options ?: { customByName ?: Map < string , CustomEmoji > } ,
231+ ) : boolean {
232+ const text = body . trim ( )
233+ if ( ! text ) return false
234+
235+ let index = 0
236+ let found = false
237+ while ( index < text . length ) {
238+ const ch = text [ index ]
239+ if ( ch !== undefined && / \s / . test ( ch ) ) {
240+ index += 1
241+ continue
242+ }
243+
244+ const shortcode = SHORTCODE_AT_START_RE . exec ( text . slice ( index ) )
245+ if ( shortcode ?. [ 1 ] && resolveEmoji ( shortcode [ 1 ] , options ) ) {
246+ found = true
247+ index += shortcode [ 0 ] . length
248+ const skin = SHORTCODE_AT_START_RE . exec ( text . slice ( index ) )
249+ if ( skin ?. [ 1 ] && isSkinToneShortcode ( skin [ 1 ] ) !== null ) {
250+ index += skin [ 0 ] . length
251+ }
252+ continue
253+ }
254+
255+ const emoticon = matchEmoticonAt ( text , index )
256+ if ( emoticon ) {
257+ found = true
258+ index += emoticon . token . length
259+ continue
260+ }
261+
262+ const grapheme = firstGrapheme ( text . slice ( index ) )
263+ if ( grapheme && isEmojiGrapheme ( grapheme ) ) {
264+ found = true
265+ index += grapheme . length
266+ continue
267+ }
268+
269+ return false
270+ }
271+
272+ return found
273+ }
274+
210275export function getEmojiCatalog ( ) : EmojiCategory [ ] {
211276 if ( catalogCache ) return catalogCache
212277
0 commit comments