aboutsummaryrefslogtreecommitdiff
path: root/src/components/emoji-input/suggestor.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/emoji-input/suggestor.js')
-rw-r--r--src/components/emoji-input/suggestor.js94
1 files changed, 94 insertions, 0 deletions
diff --git a/src/components/emoji-input/suggestor.js b/src/components/emoji-input/suggestor.js
new file mode 100644
index 00000000..aec5c39d
--- /dev/null
+++ b/src/components/emoji-input/suggestor.js
@@ -0,0 +1,94 @@
+import { debounce } from 'lodash'
+/**
+ * suggest - generates a suggestor function to be used by emoji-input
+ * data: object providing source information for specific types of suggestions:
+ * data.emoji - optional, an array of all emoji available i.e.
+ * (state.instance.emoji + state.instance.customEmoji)
+ * data.users - optional, an array of all known users
+ * updateUsersList - optional, a function to search and append to users
+ *
+ * Depending on data present one or both (or none) can be present, so if field
+ * doesn't support user linking you can just provide only emoji.
+ */
+
+const debounceUserSearch = debounce((data, input) => {
+ data.updateUsersList(input)
+}, 500, { leading: true, trailing: false })
+
+export default data => input => {
+ const firstChar = input[0]
+ if (firstChar === ':' && data.emoji) {
+ return suggestEmoji(data.emoji)(input)
+ }
+ if (firstChar === '@' && data.users) {
+ return suggestUsers(data)(input)
+ }
+ return []
+}
+
+export const suggestEmoji = emojis => input => {
+ const noPrefix = input.toLowerCase().substr(1)
+ return emojis
+ .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix))
+ .sort((a, b) => {
+ let aScore = 0
+ let bScore = 0
+
+ // Make custom emojis a priority
+ aScore += a.imageUrl ? 10 : 0
+ bScore += b.imageUrl ? 10 : 0
+
+ // Sort alphabetically
+ const alphabetically = a.displayText > b.displayText ? 1 : -1
+
+ return bScore - aScore + alphabetically
+ })
+}
+
+export const suggestUsers = data => input => {
+ const noPrefix = input.toLowerCase().substr(1)
+ const users = data.users
+
+ const newUsers = users.filter(
+ user =>
+ user.screen_name.toLowerCase().startsWith(noPrefix) ||
+ user.name.toLowerCase().startsWith(noPrefix)
+
+ /* taking only 20 results so that sorting is a bit cheaper, we display
+ * only 5 anyway. could be inaccurate, but we ideally we should query
+ * backend anyway
+ */
+ ).slice(0, 20).sort((a, b) => {
+ let aScore = 0
+ let bScore = 0
+
+ // Matches on screen name (i.e. user@instance) makes a priority
+ aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
+ bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
+
+ // Matches on name takes second priority
+ aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
+ bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
+
+ const diff = (bScore - aScore) * 10
+
+ // Then sort alphabetically
+ const nameAlphabetically = a.name > b.name ? 1 : -1
+ const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
+
+ return diff + nameAlphabetically + screenNameAlphabetically
+ /* eslint-disable camelcase */
+ }).map(({ screen_name, name, profile_image_url_original }) => ({
+ displayText: screen_name,
+ detailText: name,
+ imageUrl: profile_image_url_original,
+ replacement: '@' + screen_name + ' '
+ }))
+
+ // BE search users if there are no matches
+ if (newUsers.length === 0 && data.updateUsersList) {
+ debounceUserSearch(data, noPrefix)
+ }
+ return newUsers
+ /* eslint-enable camelcase */
+}