@@ -18,9 +18,10 @@ limitations under the License.
1818*/
1919
2020import React from 'react' ;
21- import { uniq , sortBy } from 'lodash' ;
21+ import { uniq , sortBy , ListIteratee } from 'lodash' ;
2222import EMOTICON_REGEX from 'emojibase-regex/emoticon' ;
2323import { Room } from 'matrix-js-sdk/src/models/room' ;
24+ import { MatrixEvent } from 'matrix-js-sdk/src/matrix' ;
2425
2526import { _t } from '../languageHandler' ;
2627import AutocompleteProvider from './AutocompleteProvider' ;
@@ -30,6 +31,7 @@ import { ICompletion, ISelectionRange } from './Autocompleter';
3031import SettingsStore from "../settings/SettingsStore" ;
3132import { EMOJI , IEmoji } from '../emoji' ;
3233import { TimelineRenderingType } from '../contexts/RoomContext' ;
34+ import { mediaFromMxc } from '../customisations/Media' ;
3335
3436const LIMIT = 20 ;
3537
@@ -38,10 +40,16 @@ const LIMIT = 20;
3840const EMOJI_REGEX = new RegExp ( '(' + EMOTICON_REGEX . source + '|(?:^|\\s):[+-\\w]*:?)$' , 'g' ) ;
3941
4042interface ISortedEmoji {
41- emoji : IEmoji ;
43+ emoji : IEmoji | ICustomEmoji ;
4244 _orderBy : number ;
4345}
4446
47+ export interface ICustomEmoji {
48+ shortcodes : string [ ] ;
49+ emoticon ?: string ;
50+ url : string ;
51+ }
52+
4553const SORTED_EMOJI : ISortedEmoji [ ] = EMOJI . sort ( ( a , b ) => {
4654 if ( a . group === b . group ) {
4755 return a . order - b . order ;
@@ -65,6 +73,7 @@ function score(query, space) {
6573export default class EmojiProvider extends AutocompleteProvider {
6674 matcher : QueryMatcher < ISortedEmoji > ;
6775 nameMatcher : QueryMatcher < ISortedEmoji > ;
76+ customEmojiMatcher : QueryMatcher < ISortedEmoji > ;
6877
6978 constructor ( room : Room , renderingType ?: TimelineRenderingType ) {
7079 super ( { commandRegex : EMOJI_REGEX , renderingType } ) ;
@@ -74,11 +83,42 @@ export default class EmojiProvider extends AutocompleteProvider {
7483 // For matching against ascii equivalents
7584 shouldMatchWordsOnly : false ,
7685 } ) ;
77- this . nameMatcher = new QueryMatcher ( SORTED_EMOJI , {
86+ this . nameMatcher = new QueryMatcher < ISortedEmoji > ( SORTED_EMOJI , {
7887 keys : [ 'emoji.annotation' ] ,
7988 // For removing punctuation
8089 shouldMatchWordsOnly : true ,
8190 } ) ;
91+
92+ // Load this room's image sets.
93+ const loadedImages : ICustomEmoji [ ] = [ ] ;
94+ const imageSetEvents = room . currentState . getStateEvents ( 'im.ponies.room_emotes' ) ;
95+ imageSetEvents . forEach ( imageSetEvent => {
96+ this . loadImageSet ( loadedImages , imageSetEvent ) ;
97+ } ) ;
98+ const sortedCustomImages = loadedImages . map ( ( emoji , index ) => ( {
99+ emoji,
100+ // Include the index so that we can preserve the original order
101+ _orderBy : index ,
102+ } ) ) ;
103+ this . customEmojiMatcher = new QueryMatcher < ISortedEmoji > ( sortedCustomImages , {
104+ keys : [ ] ,
105+ funcs : [ o => o . emoji . shortcodes . map ( s => `:${ s } :` ) ] ,
106+ shouldMatchWordsOnly : true ,
107+ } ) ;
108+ }
109+
110+ private loadImageSet ( loadedImages : ICustomEmoji [ ] , imageSetEvent : MatrixEvent ) : void {
111+ const images = imageSetEvent . getContent ( ) . images ;
112+ if ( ! images ) {
113+ return ;
114+ }
115+ for ( const imageKey in images ) {
116+ const imageData = images [ imageKey ] ;
117+ loadedImages . push ( {
118+ shortcodes : [ imageKey ] ,
119+ url : imageData . url ,
120+ } ) ;
121+ }
82122 }
83123
84124 async getCompletions (
@@ -91,17 +131,23 @@ export default class EmojiProvider extends AutocompleteProvider {
91131 return [ ] ; // don't give any suggestions if the user doesn't want them
92132 }
93133
94- let completions = [ ] ;
134+ let completionResult : ICompletion [ ] = [ ] ;
95135 const { command, range } = this . getCurrentCommand ( query , selection ) ;
96136
97137 if ( command && command [ 0 ] . length > 2 ) {
138+ let completions : ISortedEmoji [ ] = [ ] ;
139+
140+ // find completions
98141 const matchedString = command [ 0 ] ;
99142 completions = this . matcher . match ( matchedString , limit ) ;
100143
101144 // Do second match with shouldMatchWordsOnly in order to match against 'name'
102- completions = completions . concat ( this . nameMatcher . match ( matchedString ) ) ;
145+ completions = completions . concat ( this . nameMatcher . match ( matchedString , limit ) ) ;
146+
147+ // do a match for the custom emoji
148+ completions = completions . concat ( this . customEmojiMatcher . match ( matchedString , limit ) ) ;
103149
104- const sorters = [ ] ;
150+ const sorters : ListIteratee < ISortedEmoji > [ ] = [ ] ;
105151 // make sure that emoticons come first
106152 sorters . push ( c => score ( matchedString , c . emoji . emoticon || "" ) ) ;
107153
@@ -121,17 +167,41 @@ export default class EmojiProvider extends AutocompleteProvider {
121167 sorters . push ( c => c . _orderBy ) ;
122168 completions = sortBy ( uniq ( completions ) , sorters ) ;
123169
124- completions = completions . map ( c => ( {
125- completion : c . emoji . unicode ,
126- component : (
127- < PillCompletion title = { `:${ c . emoji . shortcodes [ 0 ] } :` } aria-label = { c . emoji . unicode } >
128- < span > { c . emoji . unicode } </ span >
129- </ PillCompletion >
130- ) ,
131- range,
132- } ) ) . slice ( 0 , LIMIT ) ;
170+ completionResult = completions . map ( c => {
171+ if ( 'unicode' in c . emoji ) {
172+ return {
173+ completion : c . emoji . unicode ,
174+ component : (
175+ < PillCompletion title = { `:${ c . emoji . shortcodes [ 0 ] } :` } aria-label = { c . emoji . unicode } >
176+ < span > { c . emoji . unicode } </ span >
177+ </ PillCompletion >
178+ ) ,
179+ range,
180+ } ;
181+ } else {
182+ const mediaUrl = mediaFromMxc ( c . emoji . url ) . getThumbnailOfSourceHttp ( 24 , 24 , 'scale' ) ;
183+ return {
184+ completion : c . emoji . shortcodes [ 0 ] ,
185+ type : "customEmoji" ,
186+ completionId : c . emoji . url ,
187+ component : (
188+ < PillCompletion title = { `:${ c . emoji . shortcodes [ 0 ] } :` } >
189+ < img
190+ className = "mx_BaseAvatar_image"
191+ src = { mediaUrl }
192+ alt = { c . emoji . shortcodes [ 0 ] }
193+ style = { {
194+ width : '24px' ,
195+ height : '24px' ,
196+ } } />
197+ </ PillCompletion >
198+ ) ,
199+ range,
200+ } as const ;
201+ }
202+ } ) . slice ( 0 , LIMIT ) ;
133203 }
134- return completions ;
204+ return completionResult ;
135205 }
136206
137207 getName ( ) {
0 commit comments