diff --git a/app/assets/javascripts/admin/addon/components/emoji-value-list.hbs b/app/assets/javascripts/admin/addon/components/emoji-value-list.hbs index 8562b1cd92d..a8f1f298de7 100644 --- a/app/assets/javascripts/admin/addon/components/emoji-value-list.hbs +++ b/app/assets/javascripts/admin/addon/components/emoji-value-list.hbs @@ -43,17 +43,8 @@ {{/if}}
- -
- - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/javascripts/admin/addon/components/emoji-value-list.js b/app/assets/javascripts/admin/addon/components/emoji-value-list.js index b6a8a55ce3d..37972ceb6a2 100644 --- a/app/assets/javascripts/admin/addon/components/emoji-value-list.js +++ b/app/assets/javascripts/admin/addon/components/emoji-value-list.js @@ -1,17 +1,18 @@ import Component from "@ember/component"; import { action, set, setProperties } from "@ember/object"; import { schedule } from "@ember/runloop"; +import { service } from "@ember/service"; import { classNameBindings } from "@ember-decorators/component"; +import EmojiPickerDetached from "discourse/components/emoji-picker/detached"; import { emojiUrlFor } from "discourse/lib/text"; -import discourseLater from "discourse-common/lib/later"; import discourseComputed from "discourse-common/utils/decorators"; import { i18n } from "discourse-i18n"; @classNameBindings(":value-list", ":emoji-list") export default class EmojiValueList extends Component { + @service menu; + values = null; - emojiPickerIsActive = false; - isEditorFocused = false; @discourseComputed("values") collection(values) { @@ -30,13 +31,6 @@ export default class EmojiValueList extends Component { }); } - @action - closeEmojiPicker() { - this.collection.setEach("isEditing", false); - this.set("emojiPickerIsActive", false); - this.set("isEditorFocused", false); - } - @action emojiSelected(code) { if (!this._validateInput(code)) { @@ -62,9 +56,6 @@ export default class EmojiValueList extends Component { this.collection.addObject(newCollectionValue); this._saveValues(); } - - this.set("emojiPickerIsActive", false); - this.set("isEditorFocused", false); } @discourseComputed("collection") @@ -94,8 +85,7 @@ export default class EmojiValueList extends Component { } @action - editValue(index) { - this.closeEmojiPicker(); + editValue(index, event) { schedule("afterRender", () => { if (parseInt(index, 10) >= 0) { const item = this.collection[index]; @@ -104,12 +94,18 @@ export default class EmojiValueList extends Component { } } - this.set("isEditorFocused", true); - discourseLater(() => { - if (this.element && !this.isDestroying && !this.isDestroyed) { - this.set("emojiPickerIsActive", true); - } - }, 100); + this.menu.show(event.target, { + identifier: "emoji-picker", + groupIdentifier: "emoji-picker", + component: EmojiPickerDetached, + modalForMobile: true, + data: { + context: "chat", + didSelectEmoji: (emoji) => { + this._replaceValue(index, emoji); + }, + }, + }); }); } diff --git a/app/assets/javascripts/discourse/app/components/d-editor.hbs b/app/assets/javascripts/discourse/app/components/d-editor.hbs index f509d8e73a9..27a5f94ca07 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/app/components/d-editor.hbs @@ -76,12 +76,4 @@ /> - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js index 5d228de9830..13a106d11e7 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.js +++ b/app/assets/javascripts/discourse/app/components/d-editor.js @@ -10,6 +10,7 @@ import { translations } from "pretty-text/emoji/data"; import { resolveCachedShortUrls } from "pretty-text/upload-short-url"; import { Promise } from "rsvp"; import TextareaEditor from "discourse/components/composer/textarea-editor"; +import EmojiPickerDetached from "discourse/components/emoji-picker/detached"; import InsertHyperlink from "discourse/components/modal/insert-hyperlink"; import { ajax } from "discourse/lib/ajax"; import { SKIP } from "discourse/lib/autocomplete"; @@ -27,6 +28,7 @@ import { initUserStatusHtml, renderUserStatusHtml, } from "discourse/lib/user-status-on-autocomplete"; +import virtualElementFromTextRange from "discourse/lib/virtual-element-from-text-range"; import { isTesting } from "discourse-common/config/environment"; import discourseDebounce from "discourse-common/lib/debounce"; import deprecated from "discourse-common/lib/deprecated"; @@ -53,8 +55,9 @@ export function onToolbarCreate(func) { @classNames("d-editor") export default class DEditor extends Component { - @service("emoji-store") emojiStore; + @service emojiStore; @service modal; + @service menu; editorComponent = TextareaEditor; textManipulation; @@ -62,8 +65,6 @@ export default class DEditor extends Component { ready = false; lastSel = null; showLink = true; - emojiPickerIsActive = false; - emojiFilter = ""; isEditorFocused = false; processPreview = true; morphingOptions = { @@ -347,13 +348,25 @@ export default class DEditor extends Component { transformComplete: (v) => { if (v.code) { - this.emojiStore.track(v.code); + this.emojiStore.trackEmojiForContext(v.code, "topic"); return `${v.code}:`; } else { this.textManipulation.autocomplete({ cancel: true }); - this.set("emojiPickerIsActive", true); - this.set("emojiFilter", v.term); + const menuOptions = { + identifier: "emoji-picker", + component: EmojiPickerDetached, + modalForMobile: true, + data: { + didSelectEmoji: (emoji) => { + this.textManipulation.emojiSelected(emoji); + }, + term: v.term, + }, + }; + + const virtualElement = virtualElementFromTextRange(); + this.menuInstance = this.menu.show(virtualElement, menuOptions); return ""; } }, @@ -368,9 +381,10 @@ export default class DEditor extends Component { } if (term === "") { - if (this.emojiStore.favorites.length) { + const favorites = this.emojiStore.favoritesForContext("topic"); + if (favorites.length) { return resolve( - this.emojiStore.favorites + favorites .filter((f) => !this.site.denied_emojis?.includes(f)) .slice(0, 5) ); @@ -515,13 +529,6 @@ export default class DEditor extends Component { return true; } - @action - onEmojiPickerClose() { - if (!(this.isDestroyed || this.isDestroying)) { - this.set("emojiPickerIsActive", false); - } - } - /** * Represents a toolbar event object passed to toolbar buttons. * @@ -568,15 +575,6 @@ export default class DEditor extends Component { }; } - @action - emoji() { - if (this.disabled) { - return; - } - - this.set("emojiPickerIsActive", !this.emojiPickerIsActive); - } - @action toolbarButton(button) { if (this.disabled) { diff --git a/app/assets/javascripts/discourse/app/components/emoji-group-buttons.hbs b/app/assets/javascripts/discourse/app/components/emoji-group-buttons.hbs deleted file mode 100644 index 1ba331fc4a9..00000000000 --- a/app/assets/javascripts/discourse/app/components/emoji-group-buttons.hbs +++ /dev/null @@ -1,75 +0,0 @@ -{{! DO NOT EDIT THIS FILE!!! }} -{{! Update it by running `rake javascript:update_constants` }} - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/emoji-group-buttons.js b/app/assets/javascripts/discourse/app/components/emoji-group-buttons.js deleted file mode 100644 index de1579c0b8d..00000000000 --- a/app/assets/javascripts/discourse/app/components/emoji-group-buttons.js +++ /dev/null @@ -1,3 +0,0 @@ -import Component from "@ember/component"; - -export default class EmojiGroupButtons extends Component {} diff --git a/app/assets/javascripts/discourse/app/components/emoji-group-sections.hbs b/app/assets/javascripts/discourse/app/components/emoji-group-sections.hbs deleted file mode 100644 index 0f6817f595c..00000000000 --- a/app/assets/javascripts/discourse/app/components/emoji-group-sections.hbs +++ /dev/null @@ -1,2725 +0,0 @@ -{{! DO NOT EDIT THIS FILE!!! }} -{{! Update it by running `rake javascript:update_constants` }} - -
-
- {{i18n "emoji_picker.smileys_&_emotion"}} -
-
- {{replace-emoji ":grinning:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smiley:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smile:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":grin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":laughing:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sweat_smile:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rofl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":joy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":slightly_smiling_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":upside_down_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":melting_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wink:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blush:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":innocent:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":smiling_face_with_three_hearts:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":heart_eyes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":star_struck:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kissing_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kissing:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smiling_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kissing_closed_eyes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kissing_smiling_eyes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smiling_face_with_tear:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yum:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stuck_out_tongue:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":stuck_out_tongue_winking_eye:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":crazy_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":stuck_out_tongue_closed_eyes:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":money_mouth_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hugs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":face_with_hand_over_mouth:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":face_with_open_eyes_and_hand_over_mouth:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":face_with_peeking_eye:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shushing_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":thinking:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":saluting_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zipper_mouth_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_with_raised_eyebrow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":neutral_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":expressionless:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_mouth:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dotted_line_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_in_clouds:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smirk:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":unamused:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":roll_eyes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":grimacing:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_exhaling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lying_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":relieved:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pensive:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sleepy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":drooling_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sleeping:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mask:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_with_thermometer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_with_head_bandage:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nauseated_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_vomiting:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sneezing_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hot_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cold_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":woozy_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dizzy_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_with_spiral_eyes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":exploding_head:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cowboy_hat_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":partying_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":disguised_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sunglasses:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nerd_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_with_monocle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":confused:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_with_diagonal_mouth:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":worried:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":slightly_frowning_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":frowning_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":open_mouth:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hushed:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":astonished:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flushed:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pleading_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":face_holding_back_tears:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":frowning_with_open_mouth:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":anguished:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fearful:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cold_sweat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":disappointed_relieved:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cry:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sob:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scream:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":confounded:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":persevere:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":disappointed:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sweat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":weary:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tired_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yawning_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":triumph:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rage:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":angry:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":face_with_symbols_over_mouth:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":smiling_imp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":imp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":skull:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":skull_and_crossbones:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":poop:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clown_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":japanese_ogre:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":japanese_goblin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ghost:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":alien:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":space_invader:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":robot:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smiley_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smile_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":joy_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heart_eyes_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smirk_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kissing_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scream_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crying_cat_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pouting_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":see_no_evil:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hear_no_evil:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":speak_no_evil:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kiss:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":love_letter:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cupid:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gift_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sparkling_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heartpulse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heartbeat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":revolving_hearts:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":two_hearts:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heart_decoration:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_heart_exclamation:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":broken_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heart_on_fire:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mending_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":orange_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yellow_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":green_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blue_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":purple_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":brown_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":100:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":anger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":boom:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dizzy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sweat_drops:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dash:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hole:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bomb:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":speech_balloon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eye_in_speech_bubble:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":left_speech_bubble:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":right_anger_bubble:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":thought_balloon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zzz:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.people_&_body"}} -
-
- {{replace-emoji ":wave:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":raised_back_of_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":raised_hand_with_fingers_splayed:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":raised_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":vulcan_salute:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":rightwards_hand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":leftwards_hand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":palm_down_hand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":palm_up_hand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":ok_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":pinched_fingers:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":pinching_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":v:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":crossed_fingers:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":hand_with_index_finger_and_thumb_crossed:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":love_you_gesture:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":metal:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":call_me_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":point_left:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":point_right:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":point_up_2:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":fu:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":point_down:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":point_up:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":index_pointing_at_the_viewer:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":+1:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":-1:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":fist:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":facepunch:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":fist_left:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":fist_right:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":clap:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":raised_hands:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":heart_hands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":open_hands:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":palms_up_together:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":handshake:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":pray:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":writing_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":nail_care:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":selfie:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":muscle:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":mechanical_arm:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mechanical_leg:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":leg:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":foot:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":ear:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":hear_with_hearing_aid:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":nose:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":brain:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":anatomical_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lungs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tooth:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eyes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eye:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tongue:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lips:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":biting_lip:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":baby:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":child:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":boy:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":girl:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":adult:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":blonde_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":man:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":bearded_person:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":man_beard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":woman_beard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":man_red_haired:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_curly_haired:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_white_haired:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_bald:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":woman:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":woman_red_haired:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_red_hair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_curly_haired:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_curly_hair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_white_haired:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_white_hair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_bald:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_bald:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":blonde_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_blond_hair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":older_adult:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":older_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":older_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_frowning:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":frowning_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":frowning_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_pouting:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":pouting_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":pouting_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_gesturing_no:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":no_good_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":no_good_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_gesturing_ok:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":ok_man:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":ok_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_tipping_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":tipping_hand_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":tipping_hand_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_raising_hand:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":raising_hand_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":raising_hand_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":deaf_person:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":deaf_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":deaf_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":bowing_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_bowing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":bowing_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_facepalming:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_facepalming:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_facepalming:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_shrugging:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_shrugging:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_shrugging:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":health_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_health_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_health_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":student:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_student:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_student:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":teacher:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_teacher:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_teacher:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":judge:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_judge:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_judge:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":farmer:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_farmer:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_farmer:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":cook:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_cook:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_cook:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":mechanic:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_mechanic:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_mechanic:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":factory_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_factory_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_factory_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":office_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_office_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_office_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":scientist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_scientist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_scientist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":technologist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_technologist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_technologist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":singer:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_singer:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_singer:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":artist:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_artist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_artist:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":pilot:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_pilot:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_pilot:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":astronaut:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_astronaut:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_astronaut:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":firefighter:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_firefighter:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_firefighter:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":policeman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_police_officer:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":policewoman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":male_detective:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_detective:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":female_detective:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":guardsman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_guard:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":guardswoman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":ninja:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":construction_worker_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_construction_worker:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":construction_worker_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":person_with_crown:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":prince:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":princess:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_with_turban:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_wearing_turban:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_with_turban:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_with_gua_pi_mao:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_with_headscarf:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_in_tuxedo:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_in_tuxedo:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_in_tuxedo:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":bride_with_veil:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_with_veil:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_with_veil:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":pregnant_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":pregnant_man:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pregnant_person:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":breast_feeding:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_feeding_baby:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_feeding_baby:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_feeding_baby:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":angel:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji ":santa:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":mrs_claus:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":mx_claus:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":superhero:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_superhero:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_superhero:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":supervillain:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_supervillain:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_supervillain:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":mage:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_mage:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_mage:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":fairy:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_fairy:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_fairy:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":vampire:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_vampire:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_vampire:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":merperson:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":merman:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":mermaid:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":elf:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_elf:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_elf:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":genie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":man_genie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":woman_genie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zombie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":man_zombie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":woman_zombie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":troll:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":person_getting_massage:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":massage_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":massage_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_getting_haircut:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":haircut_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":haircut_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":walking_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_walking:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":walking_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_standing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_standing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_standing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_kneeling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_kneeling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_kneeling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_with_white_cane:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_with_probing_cane:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_with_probing_cane:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_in_motorized_wheelchair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_in_motorized_wheelchair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_in_motorized_wheelchair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_in_manual_wheelchair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_in_manual_wheelchair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_in_manual_wheelchair:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":running_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_running:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":running_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":dancer:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":man_dancing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":business_suit_levitating:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":dancing_women:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dancing_men:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":women_with_bunny_ears:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":person_in_steamy_room:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_in_steamy_room:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_in_steamy_room:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_climbing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_climbing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_climbing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":person_fencing:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":horse_racing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":skier:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":snowboarder:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":golfing_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_golfing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":golfing_woman:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":surfing_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_surfing:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":surfing_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":rowing_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_rowing_boat:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":rowing_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":swimming_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_swimming:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":swimming_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":basketball_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_bouncing_ball:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":basketball_woman:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":weight_lifting_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_lifting_weights:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":weight_lifting_woman:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":biking_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_biking:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":biking_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":mountain_biking_man:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_mountain_biking:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":mountain_biking_woman:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_cartwheeling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_cartwheeling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_cartwheeling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":people_wrestling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":men_wrestling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":women_wrestling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":person_playing_water_polo:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_playing_water_polo:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_playing_water_polo:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_playing_handball:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_playing_handball:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_playing_handball:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_juggling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_juggling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_juggling:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":person_in_lotus_position:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":man_in_lotus_position:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":woman_in_lotus_position:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":bath:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":sleeping_bed:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":people_holding_hands:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji - ":two_women_holding_hands:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":couple:" (hash lazy=true class="diversity" tabIndex="0")}} - {{replace-emoji - ":two_men_holding_hands:" - (hash lazy=true class="diversity" tabIndex="0") - }} - {{replace-emoji ":couplekiss_man_woman:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kiss_woman_man:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":couplekiss_man_man:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":couplekiss_woman_woman:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":couple_with_heart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":couple_with_heart_woman_man:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":couple_with_heart_man_man:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":couple_with_heart_woman_woman:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":family:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_woman_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_woman_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":family_man_woman_girl_boy:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":family_man_woman_boy_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":family_man_woman_girl_girl:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":family_man_man_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_man_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_man_girl_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_man_boy_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_man_girl_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_woman_woman_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_woman_woman_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":family_woman_woman_girl_boy:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":family_woman_woman_boy_boy:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":family_woman_woman_girl_girl:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":family_man_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_boy_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_girl_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_man_girl_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_woman_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_woman_boy_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_woman_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_woman_girl_boy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":family_woman_girl_girl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":speaking_head:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bust_in_silhouette:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":busts_in_silhouette:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":people_hugging:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":footprints:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.animals_&_nature"}} -
-
- {{replace-emoji ":monkey_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":monkey:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gorilla:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":orangutan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dog:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dog2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guide_dog:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":service_dog:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":poodle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wolf:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fox_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":raccoon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cat2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_cat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lion:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tiger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tiger2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":leopard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":horse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":racehorse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":unicorn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zebra:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":deer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bison:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ox:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":water_buffalo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cow2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pig:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pig2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":boar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pig_nose:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ram:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sheep:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":goat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dromedary_camel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":camel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":llama:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":giraffe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":elephant:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mammoth:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rhinoceros:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hippopotamus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mouse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mouse2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hamster:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rabbit:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rabbit2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chipmunk:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beaver:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hedgehog:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bear:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":polar_bear:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":koala:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":panda_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sloth:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":otter:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":skunk:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kangaroo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":badger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":paw_prints:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":turkey:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chicken:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rooster:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hatching_chick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":baby_chick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hatched_chick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bird:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":penguin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dove:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eagle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":duck:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":swan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":owl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dodo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":feather:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flamingo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":peacock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":parrot:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":frog:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crocodile:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":turtle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lizard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":snake:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dragon_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dragon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sauropod:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":t_rex:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":whale:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":whale2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dolphin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":seal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fish:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tropical_fish:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blowfish:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shark:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":octopus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shell:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":coral:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":snail:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":butterfly:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bug:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ant:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":honeybee:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beetle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lady_beetle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cricket:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cockroach:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":spider:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":spider_web:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scorpion:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mosquito:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fly:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":worm:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":microbe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bouquet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cherry_blossom:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_flower:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lotus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rosette:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rose:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wilted_flower:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hibiscus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sunflower:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blossom:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tulip:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":seedling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":potted_plant:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":evergreen_tree:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":deciduous_tree:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":palm_tree:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cactus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ear_of_rice:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":herb:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shamrock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":four_leaf_clover:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":maple_leaf:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fallen_leaf:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":leaves:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":empty_nest:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nest_with_eggs:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.food_&_drink"}} -
-
- {{replace-emoji ":grapes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":melon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":watermelon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tangerine:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lemon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":banana:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pineapple:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mango:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":apple:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":green_apple:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pear:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":peach:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cherries:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":strawberry:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blueberries:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kiwi_fruit:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tomato:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":olive:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":coconut:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":avocado:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eggplant:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":potato:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":carrot:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":corn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hot_pepper:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bell_pepper:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cucumber:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":leafy_green:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":broccoli:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":garlic:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":onion:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mushroom:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":peanuts:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beans:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chestnut:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bread:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":croissant:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":baguette_bread:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flatbread:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pretzel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bagel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pancakes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":waffle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cheese:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":meat_on_bone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":poultry_leg:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cut_of_meat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bacon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hamburger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fries:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pizza:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hotdog:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sandwich:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":taco:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":burrito:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tamale:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stuffed_flatbread:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":falafel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":egg:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fried_egg:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shallow_pan_of_food:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stew:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fondue:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bowl_with_spoon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":green_salad:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":popcorn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":butter:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":salt:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":canned_food:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bento:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rice_cracker:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rice_ball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rice:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":curry:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ramen:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":spaghetti:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sweet_potato:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oden:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sushi:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fried_shrimp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fish_cake:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":moon_cake:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dango:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dumpling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fortune_cookie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":takeout_box:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crab:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lobster:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shrimp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":squid:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oyster:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":icecream:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shaved_ice:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ice_cream:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":doughnut:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cookie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":birthday:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cake:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cupcake:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chocolate_bar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":candy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lollipop:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":custard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":honey_pot:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":baby_bottle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":milk_glass:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":coffee:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":teapot:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tea:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sake:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":champagne:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wine_glass:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cocktail:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tropical_drink:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beers:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clinking_glasses:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tumbler_glass:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pouring_liquid:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cup_with_straw:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bubble_tea:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beverage_box:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":maté:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ice_cube:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chopsticks:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":plate_with_cutlery:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fork_and_knife:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":spoon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hocho:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":jar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":amphora:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.travel_&_places"}} -
-
- {{replace-emoji ":earth_africa:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":earth_americas:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":earth_asia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":globe_with_meridians:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":world_map:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":japan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":compass:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mountain_snow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mountain:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":volcano:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mount_fuji:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":camping:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beach_umbrella:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":desert:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":desert_island:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":national_park:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stadium:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":classical_building:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":building_construction:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":brick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wood:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hut:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":houses:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":derelict_house:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":house:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":house_with_garden:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":office:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":post_office:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":european_post_office:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hospital:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bank:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hotel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":love_hotel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":convenience_store:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":school:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":department_store:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":factory:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":japanese_castle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":european_castle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wedding:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tokyo_tower:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":statue_of_liberty:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":church:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mosque:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hindu_temple:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":synagogue:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shinto_shrine:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kaaba:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fountain:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tent:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":foggy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":night_with_stars:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cityscape:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sunrise_over_mountains:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sunrise:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":city_sunset:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":city_sunrise:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bridge_at_night:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hotsprings:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":carousel_horse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":playground_slide:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ferris_wheel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":roller_coaster:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":barber:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":circus_tent:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":steam_locomotive:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":railway_car:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bullettrain_side:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bullettrain_front:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":train2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":metro:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":light_rail:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":station:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tram:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":monorail:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mountain_railway:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":train:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oncoming_bus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":trolleybus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":minibus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ambulance:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fire_engine:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":police_car:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oncoming_police_car:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":taxi:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oncoming_taxi:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":red_car:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oncoming_automobile:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blue_car:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pickup_truck:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":truck:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":articulated_lorry:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tractor:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":racing_car:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":motorcycle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":motor_scooter:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":manual_wheelchair:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":motorized_wheelchair:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":auto_rickshaw:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bike:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kick_scooter:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":skateboard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":roller_skate:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":busstop:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":motorway:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":railway_track:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oil_drum:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fuelpump:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wheel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rotating_light:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":traffic_light:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":vertical_traffic_light:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stop_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":construction:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":anchor:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ring_buoy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sailboat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":canoe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":speedboat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":passenger_ship:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ferry:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":motor_boat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ship:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":airplane:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":small_airplane:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flight_departure:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flight_arrival:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":parachute:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":seat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":helicopter:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":suspension_railway:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mountain_cableway:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":aerial_tramway:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":artificial_satellite:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rocket:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flying_saucer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bellhop_bell:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":luggage:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hourglass:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hourglass_flowing_sand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":watch:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":alarm_clock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stopwatch:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":timer_clock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mantelpiece_clock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock12:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock1230:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock1:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock130:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock230:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock3:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock330:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock4:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock430:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock5:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock530:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock6:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock630:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock7:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock730:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock8:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock830:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock9:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock930:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock10:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock1030:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock11:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clock1130:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":new_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":waxing_crescent_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":first_quarter_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":waxing_gibbous_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":full_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":waning_gibbous_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":last_quarter_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":waning_crescent_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crescent_moon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":new_moon_with_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":first_quarter_moon_with_face:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":last_quarter_moon_with_face:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":thermometer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sunny:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":full_moon_with_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sun_with_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ringer_planet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":star:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":star2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stars:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":milky_way:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cloud:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":partly_sunny:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":cloud_with_lightning_and_rain:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":sun_behind_small_cloud:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sun_behind_large_cloud:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sun_behind_rain_cloud:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cloud_with_rain:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cloud_with_snow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cloud_with_lightning:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tornado:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fog:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wind_face:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cyclone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rainbow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":closed_umbrella:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":open_umbrella:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":umbrella:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":parasol_on_ground:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zap:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":snowflake:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":snowman_with_snow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":snowman:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":comet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fire:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":droplet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ocean:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.activities"}} -
-
- {{replace-emoji ":jack_o_lantern:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":christmas_tree:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fireworks:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sparkler:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":firecracker:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sparkles:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":balloon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tada:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":confetti_ball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tanabata_tree:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bamboo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dolls:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flags:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wind_chime:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rice_scene:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":red_gift_envelope:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ribbon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gift:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":reminder_ribbon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tickets:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ticket:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":medal_military:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":trophy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":medal_sports:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":1st_place_medal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":2nd_place_medal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":3rd_place_medal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":soccer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":baseball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":softball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":basketball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":volleyball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":football:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rugby_football:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tennis:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flying_disc:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bowling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cricket_bat_and_ball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":field_hockey:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ice_hockey:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lacrosse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ping_pong:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":badminton:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":boxing_glove:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":martial_arts_uniform:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":goal_net:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":golf:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ice_skate:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fishing_pole_and_fish:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":diving_mask:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":running_shirt_with_sash:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ski:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sled:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":curling_stone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yo-yo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kite:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":8ball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crystal_ball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":magic_wand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nazar_amulet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hamsa:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":video_game:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":joystick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":slot_machine:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":game_die:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":jigsaw:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":teddy_bear:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":piñata:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mirror_ball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nesting_dolls:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":spades:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hearts:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":diamonds:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clubs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chess_pawn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_joker:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mahjong:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flower_playing_cards:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":performing_arts:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":framed_picture:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":art:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":thread:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sewing_needle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yarn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":knot:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.objects"}} -
-
- {{replace-emoji ":eyeglasses:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dark_sunglasses:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":goggles:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lab_coat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":safety_vest:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":necktie:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tshirt:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":jeans:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scarf:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gloves:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":coat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":socks:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dress:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kimono:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sari:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":one_piece_swimsuit:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":briefs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shorts:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bikini:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":womans_clothes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":purse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":handbag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pouch:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shopping:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":school_satchel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":thong_sandal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mans_shoe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":athletic_shoe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hiking_boot:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flat_shoe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":high_heel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sandal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ballet_shoes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":boot:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crown:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":womans_hat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tophat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mortar_board:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":billed_cap:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":military_helmet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rescue_worker_helmet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":prayer_beads:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lipstick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ring:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gem:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mute:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":speaker:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sound:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":loud_sound:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":loudspeaker:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mega:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":postal_horn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bell:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_bell:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":musical_score:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":musical_note:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":notes:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":studio_microphone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":level_slider:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":control_knobs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":microphone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":headphones:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":radio:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":saxophone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":accordion:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guitar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":musical_keyboard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":trumpet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":violin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":banjo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":drum:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":long_drum:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":iphone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":calling:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":phone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":telephone_receiver:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pager:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fax:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":battery:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":low_battery:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":electric_plug:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":computer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":desktop_computer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":printer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":keyboard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":computer_mouse:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":trackball:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":minidisc:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":floppy_disk:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cd:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dvd:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":abacus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":movie_camera:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":film_strip:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":film_projector:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clapper:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tv:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":camera:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":camera_flash:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":video_camera:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":vhs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mag_right:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":candle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bulb:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":flashlight:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":izakaya_lantern:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":diya_lamp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":notebook_with_decorative_cover:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":closed_book:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":open_book:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":green_book:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blue_book:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":orange_book:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":books:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":notebook:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ledger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":page_with_curl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scroll:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":page_facing_up:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":newspaper:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":newspaper_roll:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bookmark_tabs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bookmark:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":label:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":moneybag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":coin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yen:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dollar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":euro:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pound:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":money_with_wings:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":credit_card:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":receipt:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":email:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":e-mail:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":incoming_envelope:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":envelope_with_arrow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":outbox_tray:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":inbox_tray:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":package:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mailbox:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mailbox_closed:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mailbox_with_mail:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mailbox_with_no_mail:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":postbox:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ballot_box:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pencil2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_nib:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fountain_pen:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pen:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":paintbrush:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crayon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":memo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":briefcase:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":file_folder:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":open_file_folder:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":card_index_dividers:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":date:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":calendar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":spiral_notepad:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":spiral_calendar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":card_index:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chart_with_upwards_trend:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":chart_with_downwards_trend:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":bar_chart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clipboard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pushpin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":round_pushpin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":paperclip:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":paperclips:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":straight_ruler:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":triangular_ruler:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scissors:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":card_file_box:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":file_cabinet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wastebasket:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":unlock:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lock_with_ink_pen:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":closed_lock_with_key:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":key:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":old_key:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hammer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":axe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hammer_and_pick:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hammer_and_wrench:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dagger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crossed_swords:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gun:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":boomerang:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bow_and_arrow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shield:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":carpentry_saw:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wrench:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":screwdriver:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nut_and_bolt:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gear:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clamp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":balance_scale:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":probing_cane:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":link:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chains:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hook:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":toolbox:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":magnet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ladder:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":alembic:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":test_tube:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":petri_dish:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dna:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":microscope:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":telescope:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":satellite:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":syringe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":drop_of_blood:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pill:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":adhesive_bandage:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crutch:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stethoscope:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":xray:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":door:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":elevator:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mirror:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":window:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bed:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":couch_and_lamp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chair:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":toilet:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":plunger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shower:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bathtub:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mouse_trap:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":razor:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lotion_bottle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":safety_pin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":broom:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":basket:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":roll_of_toilet_paper:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bucket:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":soap:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bubbles:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":toothbrush:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sponge:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fire_extinguisher:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":shopping_cart:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":smoking:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":coffin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":headstone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":funeral_urn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":moyai:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":placard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":identification_card:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.symbols"}} -
-
- {{replace-emoji ":atm:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":put_litter_in_its_place:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":potable_water:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wheelchair:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mens:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":womens:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":restroom:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":baby_symbol:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wc:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":passport_control:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":customs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":baggage_claim:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":left_luggage:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":warning:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":children_crossing:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_entry:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_entry_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_bicycles:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_smoking:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":do_not_litter:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":non-potable_water:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_pedestrians:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":no_mobile_phones:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":underage:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":radioactive:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":biohazard:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_up:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_upper_right:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_right:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_lower_right:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_down:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_lower_left:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_left:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_upper_left:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_up_down:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":left_right_arrow:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":leftwards_arrow_with_hook:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":arrow_right_hook:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_heading_up:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_heading_down:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrows_clockwise:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrows_counterclockwise:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":back:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":end:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":on:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":soon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":top:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":place_of_worship:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":atom_symbol:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":om:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":star_of_david:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wheel_of_dharma:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yin_yang:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":latin_cross:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":orthodox_cross:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":star_and_crescent:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":peace_symbol:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":menorah:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":six_pointed_star:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":aries:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":taurus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gemini:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cancer:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":leo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":virgo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":libra:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scorpius:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sagittarius:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":capricorn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":aquarius:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pisces:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ophiuchus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":twisted_rightwards_arrows:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":repeat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":repeat_one:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_forward:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fast_forward:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":next_track_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":play_or_pause_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_backward:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rewind:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":previous_track_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_up_small:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_double_up:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_down_small:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":arrow_double_down:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pause_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":stop_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":record_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eject_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cinema:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":low_brightness:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":high_brightness:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":signal_strength:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":vibration_mode:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mobile_phone_off:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":female_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":male_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":transgender_symbol:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_multiplication_x:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_plus_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_minus_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_division_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_equals_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":infinity:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bangbang:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":interrobang:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":question:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":grey_question:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":grey_exclamation:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":exclamation:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wavy_dash:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":currency_exchange:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_dollar_sign:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":medical_symbol:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":recycle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fleur_de_lis:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":trident:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":name_badge:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":beginner:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":o:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_check_mark:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ballot_box_with_check:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":heavy_check_mark:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":x:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":negative_squared_cross_mark:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":curly_loop:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":loop:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":part_alternation_mark:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eight_spoked_asterisk:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eight_pointed_black_star:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sparkle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":copyright:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":registered:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tm:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hash:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":asterisk:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zero:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":one:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":two:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":three:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":four:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":five:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":six:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":seven:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eight:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nine:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":keycap_ten:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":capital_abcd:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":abcd:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":1234:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":symbols:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":abc:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":a:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ab:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":b:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cl:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cool:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":free:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":information_source:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":id:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":m:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":new:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ng:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":o2:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ok:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":parking:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sos:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":up:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":vs:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":koko:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sa:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u6708:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u6709:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u6307:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ideograph_advantage:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u5272:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u7121:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u7981:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":accept:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u7533:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u5408:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u7a7a:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":congratulations:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":secret:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u55b6:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":u6e80:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":red_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":orange_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yellow_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":green_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":large_blue_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":purple_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":brown_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_circle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":red_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":orange_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yellow_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":green_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":blue_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":purple_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":brown_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_large_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_large_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_medium_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_medium_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":black_medium_small_square:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji - ":white_medium_small_square:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":black_small_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_small_square:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":large_orange_diamond:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":large_blue_diamond:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":small_orange_diamond:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":small_blue_diamond:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":small_red_triangle:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":small_red_triangle_down:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":diamond_shape_with_a_dot_inside:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":radio_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_square_button:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_square_button:" (hash lazy=true tabIndex="0")}} -
-
-
-
- {{i18n "emoji_picker.flags"}} -
-
- {{replace-emoji ":checkered_flag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":triangular_flag_on_post:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":crossed_flags:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":black_flag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":white_flag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rainbow_flag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":transgender_flag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pirate_flag:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ascension_island:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":andorra:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":united_arab_emirates:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":afghanistan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":antigua_barbuda:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":anguilla:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":albania:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":armenia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":angola:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":antarctica:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":argentina:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":american_samoa:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":austria:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":australia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":aruba:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":aland_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":azerbaijan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bosnia_herzegovina:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":barbados:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bangladesh:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":belgium:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":burkina_faso:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bulgaria:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bahrain:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":burundi:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":benin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":st_barthelemy:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bermuda:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":brunei:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bolivia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":caribbean_netherlands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":brazil:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bahamas:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bhutan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":bouvet_island:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":botswana:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":belarus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":belize:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":canada:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cocos_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":congo_kinshasa:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":central_african_republic:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":congo_brazzaville:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":switzerland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cote_divoire:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cook_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chile:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cameroon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cn:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":colombia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":clipperton_island:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":costa_rica:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cuba:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cape_verde:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":curacao:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":christmas_island:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cyprus:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":czech_republic:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":de:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":diego_garcia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":djibouti:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":denmark:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dominica:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":dominican_republic:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":algeria:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ceuta_and_melilla:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ecuador:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":estonia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":egypt:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":western_sahara:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eritrea:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":es:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ethiopia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":eu:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":finland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fiji:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":falkland_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":micronesia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":faroe_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":fr:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gabon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":uk:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":grenada:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":georgia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":french_guiana:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guernsey:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ghana:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gibraltar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":greenland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":gambia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guinea:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guadeloupe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":equatorial_guinea:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":greece:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":south_georgia_south_sandwich_islands:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":guatemala:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guam:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guinea_bissau:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":guyana:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hong_kong:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":heard_and_mc_donald_islands:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":honduras:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":croatia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":haiti:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":hungary:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":canary_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":indonesia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ireland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":israel:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":isle_of_man:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":india:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":british_indian_ocean_territory:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":iraq:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":iran:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":iceland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":it:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":jersey:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":jamaica:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":jordan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":jp:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kenya:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kyrgyzstan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cambodia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kiribati:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":comoros:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":st_kitts_nevis:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":north_korea:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kr:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kuwait:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":cayman_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kazakhstan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":laos:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lebanon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":st_lucia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":liechtenstein:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sri_lanka:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":liberia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lesotho:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":lithuania:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":luxembourg:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":latvia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":libya:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":morocco:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":monaco:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":moldova:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":montenegro:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":st_martin:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":madagascar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":marshall_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":macedonia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mali:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":myanmar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mongolia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":macau:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":northern_mariana_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":martinique:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mauritania:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":montserrat:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":malta:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mauritius:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":maldives:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":malawi:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mexico:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":malaysia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mozambique:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":namibia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":new_caledonia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":niger:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":norfolk_island:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nigeria:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nicaragua:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":netherlands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":norway:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nepal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":nauru:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":niue:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":new_zealand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":oman:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":panama:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":peru:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":french_polynesia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":papua_new_guinea:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":philippines:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pakistan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":poland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":st_pierre_miquelon:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":pitcairn_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":puerto_rico:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":palestinian_territories:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":portugal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":palau:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":paraguay:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":qatar:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":reunion:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":romania:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":serbia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ru:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":rwanda:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":saudi_arabia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":solomon_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":seychelles:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sudan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sweden:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":singapore:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":st_helena:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":slovenia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":svalbard_and_jan_mayen:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":slovakia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sierra_leone:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":san_marino:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":senegal:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":somalia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":suriname:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":south_sudan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sao_tome_principe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":el_salvador:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":sint_maarten:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":syria:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":swaziland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tristan_da_cunha:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":turks_caicos_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":chad:" (hash lazy=true tabIndex="0")}} - {{replace-emoji - ":french_southern_territories:" - (hash lazy=true tabIndex="0") - }} - {{replace-emoji ":togo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":thailand:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tajikistan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tokelau:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":timor_leste:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":turkmenistan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tunisia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tonga:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tr:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":trinidad_tobago:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tuvalu:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":taiwan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":tanzania:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":ukraine:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":uganda:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":us_outlying_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":united_nations:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":us:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":uruguay:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":uzbekistan:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":vatican_city:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":st_vincent_grenadines:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":venezuela:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":british_virgin_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":us_virgin_islands:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":vietnam:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":vanuatu:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wallis_futuna:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":samoa:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":kosovo:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":yemen:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":mayotte:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":south_africa:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zambia:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":zimbabwe:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":england:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":scotland:" (hash lazy=true tabIndex="0")}} - {{replace-emoji ":wales:" (hash lazy=true tabIndex="0")}} -
-
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/emoji-group-sections.js b/app/assets/javascripts/discourse/app/components/emoji-group-sections.js deleted file mode 100644 index 0951a47984f..00000000000 --- a/app/assets/javascripts/discourse/app/components/emoji-group-sections.js +++ /dev/null @@ -1,3 +0,0 @@ -import Component from "@ember/component"; - -export default class EmojiGroupSections extends Component {} diff --git a/app/assets/javascripts/discourse/app/components/emoji-picker.hbs b/app/assets/javascripts/discourse/app/components/emoji-picker.hbs deleted file mode 100644 index 23e275e6089..00000000000 --- a/app/assets/javascripts/discourse/app/components/emoji-picker.hbs +++ /dev/null @@ -1,154 +0,0 @@ -{{#if this.isActive}} - {{! template-lint-disable no-invalid-interactive no-pointer-down-event-binding }} -
- {{! template-lint-enable no-invalid-interactive no-pointer-down-event-binding }} -
- {{#if this.recentEmojis.length}} - - {{/if}} - - - - {{#each-in this.customEmojis as |group emojis|}} - - {{/each-in}} -
- -
-
- - - {{d-icon "magnifying-glass"}} -
- -
-
- -
- {{#if this.recentEmojis.length}} -
-
- {{i18n "emoji_picker.recent"}} - -
-
- {{#each this.recentEmojis as |emoji|}} - {{replace-emoji - (concat ":" emoji ":") - (hash lazy=true tabIndex="0" class="recent-emoji") - }} - {{/each}} -
-
- {{/if}} - - - - {{#each-in this.customEmojis as |group emojis|}} -
-
- - {{i18n - (concat "emoji_picker." group) - translatedFallback=group - }} - -
- {{#if emojis.length}} -
- {{#each emojis as |emoji|}} - - - - {{/each}} -
- {{/if}} -
- {{/each-in}} -
-
- - -
-
- - {{#if this.site.mobileView}} -
- {{/if}} -{{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/emoji-picker.js b/app/assets/javascripts/discourse/app/components/emoji-picker.js deleted file mode 100644 index 8d234b83598..00000000000 --- a/app/assets/javascripts/discourse/app/components/emoji-picker.js +++ /dev/null @@ -1,467 +0,0 @@ -import Component from "@ember/component"; -import { action, computed } from "@ember/object"; -import { schedule } from "@ember/runloop"; -import { service } from "@ember/service"; -import { underscore } from "@ember/string"; -import { htmlSafe } from "@ember/template"; -import { tagName } from "@ember-decorators/component"; -import { observes } from "@ember-decorators/object"; -import { createPopper } from "@popperjs/core"; -import { - emojiSearch, - extendedEmojiList, - isSkinTonableEmoji, -} from "pretty-text/emoji"; -import { emojiUnescape, emojiUrlFor } from "discourse/lib/text"; -import { escapeExpression } from "discourse/lib/utilities"; -import discourseLater from "discourse-common/lib/later"; -import discourseComputed, { bind } from "discourse-common/utils/decorators"; - -function customEmojis() { - const groups = []; - for (const [code, emoji] of extendedEmojiList()) { - groups[emoji.group] ||= []; - groups[emoji.group].push({ code, src: emojiUrlFor(code) }); - } - return groups; -} - -@tagName("") -export default class EmojiPicker extends Component { - @service emojiStore; - - customEmojis = customEmojis(); - recentEmojis = null; - hoveredEmoji = null; - isActive = false; - usePopper = true; - placement = "auto"; // one of popper.js' placements, see https://popper.js.org/docs/v2/constructors/#options - initialFilter = ""; - - elements = { - searchInput: ".emoji-picker-search-container input", - picker: ".emoji-picker-emoji-area", - }; - - didInsertElement() { - super.didInsertElement(...arguments); - this._sectionObserver = this._setupSectionObserver(); - this.appEvents.on("emoji-picker:close", this, "onClose"); - } - - // `readOnly` may seem like a better choice here, but the computed property - // provides caching (emojiStore.diversity is a simple getter) - @discourseComputed("emojiStore.diversity") - selectedDiversity(diversity) { - return diversity; - } - - // didReceiveAttrs would be a better choice here, but this is sadly causing - // too many unexpected reloads as it's triggered for other reasons than a mutation - // of isActive - @observes("isActive") - _setup() { - if (this.isActive) { - this.onShow(); - } else { - this.onClose(); - } - } - - willDestroyElement() { - super.willDestroyElement(...arguments); - this._sectionObserver?.disconnect(); - this.appEvents.off("emoji-picker:close", this, "onClose"); - } - - @action - onShow() { - this.set("recentEmojis", this.emojiStore.favorites); - - schedule("afterRender", () => { - this._applyFilter(this.initialFilter); - - const emojiPicker = document.querySelector(".emoji-picker"); - if (!emojiPicker) { - return; - } - - document.addEventListener("click", this.handleOutsideClick); - - const popperAnchor = this._getPopperAnchor(); - - if (!this.site.isMobileDevice && this.usePopper && popperAnchor) { - const modifiers = [ - { - name: "preventOverflow", - }, - { - name: "offset", - options: { - offset: [5, 5], - }, - }, - ]; - - if ( - this.placement === "auto" && - window.innerWidth < popperAnchor.clientWidth * 2 - ) { - modifiers.push({ - name: "computeStyles", - enabled: true, - fn({ state }) { - state.styles.popper = { - ...state.styles.popper, - position: "fixed", - left: `${(window.innerWidth - state.rects.popper.width) / 2}px`, - top: "50%", - transform: "translateY(-50%)", - }; - - return state; - }, - }); - } - - this._popper = createPopper(popperAnchor, emojiPicker, { - placement: this.placement, - }); - } - - // this is a low-tech trick to prevent appending hundreds of emojis - // of blocking the rendering of the picker - discourseLater(() => { - schedule("afterRender", () => { - if (!this.site.isMobileDevice || this.isEditorFocused) { - emojiPicker.querySelector("input.filter")?.focus(); - - if (this._sectionObserver) { - emojiPicker - .querySelectorAll(".emojis-container .section .section-header") - .forEach((p) => this._sectionObserver.observe(p)); - } - } - - if (this.selectedDiversity !== 0) { - this._applyDiversity(this.selectedDiversity); - } - }); - }, 50); - }); - } - - @action - onClose(event) { - event?.stopPropagation(); - document.removeEventListener("click", this.handleOutsideClick); - this.onEmojiPickerClose?.(event); - } - - @computed("selectedDiversity") - get diversityScales() { - return [ - "default", - "light", - "medium-light", - "medium", - "medium-dark", - "dark", - ].map((name, index) => { - return { - name, - title: `emoji_picker.${underscore(name)}_tone`, - icon: index + 1 === this.selectedDiversity ? "check" : "", - }; - }); - } - - @action - onClearRecent() { - this.emojiStore.favorites = []; - this.set("recentEmojis", []); - } - - @action - onDiversitySelection(index) { - const scale = index + 1; - this.emojiStore.diversity = scale; - - this._applyDiversity(scale); - } - - @action - onEmojiHover(event) { - const img = event.target; - if (!img.classList.contains("emoji") || img.tagName !== "IMG") { - return false; - } - - this._updateEmojiPreview(event.target.title); - } - - @action - onEmojiSelection(event) { - const img = event.target; - - if (!img.classList.contains("emoji") || img.tagName !== "IMG") { - return false; - } - - let code = event.target.title; - code = this._codeWithDiversity(code, this.selectedDiversity); - - this.emojiSelected(code); - - this._trackEmojiUsage(code, { - refresh: !img.parentNode.parentNode.classList.contains("recent"), - }); - - if (this.site.isMobileDevice) { - this.onClose(event); - } - } - - @action - onCategorySelection(sectionName, event) { - event?.preventDefault(); - document - .querySelector( - `.emoji-picker-emoji-area .section[data-section="${sectionName}"]` - ) - ?.scrollIntoView(); - } - - @action - keydown(event) { - const arrowKeys = ["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"]; - const emojis = document.querySelectorAll(".emoji-picker-emoji-area .emoji"); - - let currentEmoji; - - if ( - event.key === "ArrowDown" && - this._focusedOn(this.elements.searchInput) - ) { - this._updateEmojiPreview(emojis[0].title); - emojis[0].focus(); - event.preventDefault(); - return false; - } - - if (event.key === "Escape") { - this.onClose(event); - const path = event.path || event.composedPath?.(); - - const fromChatComposer = path.find((e) => - e?.classList?.contains("chat-composer-container") - ); - - const fromTopicComposer = path.find((e) => - e?.classList?.contains("d-editor") - ); - - if (fromTopicComposer) { - document.querySelector(".d-editor-input")?.focus(); - } else if (fromChatComposer) { - document.querySelector(".chat-composer__input")?.focus(); - } else { - document.querySelector("textarea")?.focus(); - } - - return false; - } - - if (arrowKeys.includes(event.key)) { - if (!this._focusedOn(this.elements.picker)) { - return; - } - - Array.from(emojis).find((e, index) => { - currentEmoji = index; - return e.isEqualNode(event.target); - }); - - if (event.key === "ArrowRight") { - let nextEmoji = currentEmoji + 1; - - if (nextEmoji < emojis.length) { - this._updateEmojiPreview(emojis[nextEmoji].title); - emojis[nextEmoji].focus(); - } else if (nextEmoji >= emojis.length) { - this._updateEmojiPreview(emojis[0].title); - emojis[0].focus(); - } - } - - if (event.key === "ArrowLeft") { - const previousEmoji = currentEmoji - 1; - if (currentEmoji > 0) { - this._updateEmojiPreview(emojis[previousEmoji].title); - emojis[previousEmoji].focus(); - } - } - - const active = emojis[currentEmoji]; - - if (event.key === "ArrowDown") { - // source: https://stackoverflow.com/a/49090383/349424 - // look for same element type with - // - higher offsetTop - // - same offsetLeft - const emojiBelow = [...emojis] - .filter((c) => c.offsetTop > active.offsetTop) - .find((c) => c.offsetLeft === active.offsetLeft); - if (emojiBelow) { - this._updateEmojiPreview(emojiBelow.title); - emojiBelow.focus(); - } - } - - if (event.key === "ArrowUp") { - // look for same element type with - // - lower offsetTop - // - same offsetLeft - const emojiAbove = [...emojis] - .reverse() - .filter((c) => c.offsetTop < active.offsetTop) - .find((c) => c.offsetLeft === active.offsetLeft); - - if (emojiAbove) { - this._updateEmojiPreview(emojiAbove.title); - emojiAbove.focus(); - } else { - this.set("hoveredEmoji", null); - document.querySelector(this.elements.searchInput).focus(); - } - } - - event.preventDefault(); - return false; - } - - if (event.key === "Enter") { - if (!this._focusedOn(".emoji")) { - return; - } - this.onEmojiSelection(event); - this.onClose(event); - event.preventDefault(); - return false; - } - } - - @action - onFilterChange(event) { - this._applyFilter(event.target.value); - } - - _focusedOn(item) { - // returns the item currently being focused on - return document.activeElement.closest(item) ? document.activeElement : null; - } - - _applyFilter(filter) { - const emojiPicker = document.querySelector(".emoji-picker"); - const results = document.querySelector(".emoji-picker-emoji-area .results"); - results.innerHTML = ""; - - if (filter) { - results.innerHTML = emojiSearch(filter.toLowerCase(), { - diversity: this.emojiStore.diversity, - exclude: this.site.denied_emojis, - }) - .map(this._replaceEmoji) - .join(""); - - emojiPicker.classList.add("has-filter"); - results.scrollIntoView(); - } else { - emojiPicker.classList.remove("has-filter"); - } - } - - _trackEmojiUsage(code, options = {}) { - this.emojiStore.track(code); - - if (options.refresh) { - this.set("recentEmojis", [...this.emojiStore.favorites]); - } - } - - _replaceEmoji(code) { - const escaped = emojiUnescape(`:${escapeExpression(code)}:`, { - lazy: true, - tabIndex: "0", - }); - return htmlSafe(escaped); - } - - _codeWithDiversity(code, selectedDiversity) { - if (/:t\d/.test(code)) { - return code; - } else if (selectedDiversity > 1 && isSkinTonableEmoji(code)) { - return `${code}:t${selectedDiversity}`; - } else { - return code; - } - } - - _applyDiversity(diversity) { - const emojiPickerArea = document.querySelector(".emoji-picker-emoji-area"); - emojiPickerArea?.querySelectorAll(".emoji.diversity").forEach((img) => { - img.src = emojiUrlFor(this._codeWithDiversity(img.title, diversity)); - }); - } - - _setupSectionObserver() { - return new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const sectionName = entry.target.parentNode.dataset.section; - const categoryButtons = document.querySelector( - ".emoji-picker .emoji-picker-category-buttons" - ); - - if (!categoryButtons) { - return; - } - - categoryButtons - .querySelectorAll(".category-button") - .forEach((b) => b.classList.remove("current")); - - categoryButtons - .querySelector(`.category-button[data-section="${sectionName}"]`) - ?.classList?.add("current"); - } - }); - }, - { threshold: 1 } - ); - } - - _getPopperAnchor() { - // .d-editor-textarea-wrapper is only for backward compatibility here - // in new code use .emoji-picker-anchor - return ( - document.querySelector(".emoji-picker-anchor") ?? - document.querySelector(".d-editor-textarea-wrapper") - ); - } - - _updateEmojiPreview(title) { - return this.set( - "hoveredEmoji", - this._codeWithDiversity(title, this.selectedDiversity) - ); - } - - @bind - handleOutsideClick(event) { - if (!event.target.closest(".emoji-picker")) { - this.onClose(event); - } - } -} diff --git a/app/assets/javascripts/discourse/app/components/emoji-picker/content.gjs b/app/assets/javascripts/discourse/app/components/emoji-picker/content.gjs new file mode 100644 index 00000000000..6d81cec5b49 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/emoji-picker/content.gjs @@ -0,0 +1,620 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { concat, fn, hash } from "@ember/helper"; +import { on } from "@ember/modifier"; +import { action, get } from "@ember/object"; +import didInsert from "@ember/render-modifiers/modifiers/did-insert"; +import { cancel, next, schedule } from "@ember/runloop"; +import { service } from "@ember/service"; +import { modifier as modifierFn } from "ember-modifier"; +import { eq, gt, includes, notEq } from "truth-helpers"; +import DButton from "discourse/components/d-button"; +import FilterInput from "discourse/components/filter-input"; +import concatClass from "discourse/helpers/concat-class"; +import noop from "discourse/helpers/noop"; +import replaceEmoji from "discourse/helpers/replace-emoji"; +import withEventValue from "discourse/helpers/with-event-value"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { + disableBodyScroll, + enableBodyScroll, +} from "discourse/lib/body-scroll-lock"; +import { emojiUrlFor } from "discourse/lib/text"; +import { INPUT_DELAY } from "discourse-common/config/environment"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { makeArray } from "discourse-common/lib/helpers"; +import { bind } from "discourse-common/utils/decorators"; +import { i18n } from "discourse-i18n"; +import DiversityMenu from "./diversity-menu"; + +const DEFAULT_VISIBLE_SECTIONS = ["favorites", "smileys_&_emotion"]; +const DEFAULT_LAST_SECTION = "favorites"; + +const tonableEmojiTitle = (emoji, diversity) => { + if (!emoji.tonable || diversity === 1) { + return `:${emoji.name}:`; + } + + return `:${emoji.name}:t${diversity}:`; +}; + +const tonableEmojiUrl = (emoji, scale) => { + if (!emoji.tonable || scale === 1) { + return emoji.url; + } + + return emoji.url.split(".png")[0] + `/${scale}.png`; +}; + +export default class EmojiPicker extends Component { + @service emojiStore; + @service capabilities; + @service site; + + @tracked filteredEmojis = null; + @tracked scrollObserverEnabled = true; + @tracked scrollDirection = "up"; + @tracked emojis = null; + @tracked visibleSections = DEFAULT_VISIBLE_SECTIONS; + @tracked lastVisibleSection = DEFAULT_LAST_SECTION; + @tracked term = this.args.term; + + prevYPosition = 0; + + scrollableNode; + + setupSectionsNavScroll = modifierFn((element) => { + disableBodyScroll(element); + + return () => { + enableBodyScroll(element); + }; + }); + + scrollListener = modifierFn((element) => { + this.scrollableNode = element; + disableBodyScroll(element); + element.addEventListener("scroll", this._handleScroll); + + return () => { + this.scrollableNode = null; + element.removeEventListener("scroll", this._handleScroll); + enableBodyScroll(element); + }; + }); + + addVisibleSections(sections) { + this.visibleSections = makeArray(this.visibleSections) + .concat(makeArray(sections)) + .uniq(); + } + + get sections() { + return !this.loading && this.emojiStore.list + ? Object.keys(this.emojiStore.list) + : []; + } + + get groups() { + const favorites = { + favorites: this.emojiStore + .favoritesForContext(this.args.context) + .filter((f) => !this.site.denied_emojis?.includes(f)) + .map((emoji) => { + return { + name: emoji, + group: "favorites", + url: emojiUrlFor(emoji), + }; + }), + }; + + return { + ...favorites, + ...this.emojiStore.list, + }; + } + + get flatEmojis() { + if (!this.emojiStore.list) { + return []; + } + + // eslint-disable-next-line no-unused-vars + let { favorites, ...rest } = this.emojiStore.list; + return Object.values(rest).flat(); + } + + @action + registerFilterInput(element) { + this.filterInput = element; + } + + @action + clearFavorites() { + this.emojiStore.resetContext(this.args.context); + } + + @action + trapKeyDownEvents(event) { + if (event.key === "ArrowUp") { + event.stopPropagation(); + } + + if (event.key === "ArrowDown" && event.target === this.filterInput) { + event.stopPropagation(); + event.preventDefault(); + + this.scrollableNode.querySelector(`.emoji[tabindex="0"]`)?.focus(); + } + } + + @action + didInputFilter(value) { + if (!value?.length) { + cancel(this.debouncedFilterHandler); + this.filteredEmojis = null; + return; + } + + this.debouncedFilterHandler = discourseDebounce( + this, + this.debouncedDidInputFilter, + value, + INPUT_DELAY + ); + } + + @action + focusFilter(target) { + target?.focus({ preventScroll: true }); + } + + debouncedDidInputFilter(filter = "") { + filter = filter.toLowerCase(); + + this.filteredEmojis = this.flatEmojis.filter( + (emoji) => + emoji.name.toLowerCase().includes(filter) || + emoji.search_aliases?.any((alias) => + alias.toLowerCase().includes(filter) + ) + ); + + schedule("afterRender", () => { + if (this.scrollableNode) { + this.scrollableNode.scrollTop = 0; + } + }); + } + + @action + onSectionsKeyDown(event) { + if (event.key === "Enter") { + this.didSelectEmoji(event); + } else { + this.didNavigateSection(event); + } + } + + @action + didNavigateSection(event) { + const sectionsEmojis = (section) => [...section.querySelectorAll(".emoji")]; + const focusSectionsLastEmoji = (section) => { + const emojis = sectionsEmojis(section); + return emojis[emojis.length - 1].focus(); + }; + const focusSectionsFirstEmoji = (section) => { + sectionsEmojis(section)[0].focus(); + }; + const currentSection = event.target.closest(".emoji-picker__section"); + const focusFilter = () => { + this.filterInput?.focus(); + }; + const allEmojis = () => [ + ...document.querySelectorAll( + ".emoji-picker__section:not(.hidden) .emoji" + ), + ]; + + if (event.key === "ArrowRight") { + event.preventDefault(); + const nextEmoji = event.target.nextElementSibling; + + if (nextEmoji) { + nextEmoji.focus(); + } else { + const nextSection = currentSection.nextElementSibling; + if (nextSection) { + focusSectionsFirstEmoji(nextSection); + } + } + } + + if (event.key === "ArrowLeft") { + event.preventDefault(); + const prevEmoji = event.target.previousElementSibling; + + if (prevEmoji) { + prevEmoji.focus(); + } else { + const prevSection = currentSection.previousElementSibling; + if (prevSection) { + focusSectionsLastEmoji(prevSection); + } else { + focusFilter(); + } + } + } + + if (event.key === "ArrowDown") { + event.preventDefault(); + event.stopPropagation(); + + const nextEmoji = allEmojis() + .filter((c) => c.offsetTop > event.target.offsetTop) + .findBy("offsetLeft", event.target.offsetLeft); + + if (nextEmoji) { + nextEmoji.focus(); + } else { + // for perf reason all emojis might not be loaded at this point + // but the first one will always be + const nextSection = currentSection.nextElementSibling; + if (nextSection) { + focusSectionsFirstEmoji(nextSection); + } + } + } + + if (event.key === "ArrowUp") { + event.preventDefault(); + event.stopPropagation(); + + const prevEmoji = allEmojis() + .reverse() + .filter((c) => c.offsetTop < event.target.offsetTop) + .findBy("offsetLeft", event.target.offsetLeft); + + if (prevEmoji) { + prevEmoji.focus(); + } else { + focusFilter(); + } + } + } + + @action + async didSelectEmoji(event) { + if (!event.target.classList.contains("emoji")) { + return; + } + + if (event.type === "click" || event.key === "Enter") { + event.preventDefault(); + event.stopPropagation(); + let emoji = event.target.dataset.emoji; + const tonable = event.target.dataset.tonable; + const diversity = this.emojiStore.diversity; + if (tonable && diversity > 1) { + emoji = `${emoji}:t${diversity}`; + } + + this.emojiStore.trackEmojiForContext(emoji, this.args.context); + + this.args.didSelectEmoji?.(emoji); + + await this.args.close?.(); + } + } + + @action + didRequestSection(section) { + this.term = ""; + this.didInputFilter(null); + + // we disable scroll listener during requesting section + // to avoid it from detecting another section during scroll to requested section + this.scrollObserverEnabled = false; + this.addVisibleSections(this._getSectionsUpTo(section)); + this.lastVisibleSection = section; + + // iOS hack to avoid blank div when requesting section during momentum + if (this.scrollableNode && this.capabilities.isIOS) { + this.scrollableNode.style.overflow = "hidden"; + } + + next(() => { + schedule("afterRender", () => { + const targetEmoji = document.querySelector( + `.emoji-picker__section[data-section="${section}"]` + ); + targetEmoji.scrollIntoView({ block: "start" }); + + // iOS hack to avoid blank div when requesting section during momentum + if (this.scrollableNode && this.capabilities.isIOS) { + this.scrollableNode.style.overflow = "scroll"; + } + + this.scrollObserverEnabled = true; + }); + }); + } + + @action + async loadEmojis() { + if (this.emojiStore.list) { + this.didInputFilter(this.term); + return; + } + + this.loading = true; + + try { + this.emojiStore.list = await ajax("/emojis.json"); + + // we cant filter an empty list so have to wait for it + this.didInputFilter(this.term); + } catch (error) { + popupAjaxError(error); + } finally { + this.loading = false; + } + } + + @bind + _handleScroll(event) { + if (!this.scrollObserverEnabled) { + return; + } + + this._setScrollDirection(event.target); + + const visibleSections = [ + ...document.querySelectorAll(".emoji-picker__section"), + ].filter((sectionElement) => + this._isSectionVisibleInPicker(sectionElement, event.target) + ); + + if (visibleSections?.length) { + let sectionElement; + + if (this.scrollDirection === "up" || this.prevYPosition < 50) { + sectionElement = visibleSections.firstObject; + } else { + sectionElement = visibleSections.lastObject; + } + + this.lastVisibleSection = sectionElement.dataset.section; + this.addVisibleSections(visibleSections.map((s) => s.dataset.section)); + + document + .querySelector(".emoji-picker__section-btn.active") + ?.scrollIntoView({ + block: "nearest", + inline: "start", + }); + } + } + + _setScrollDirection(target) { + if (target.scrollTop > this.prevYPosition) { + this.scrollDirection = "down"; + } else { + this.scrollDirection = "up"; + } + + this.prevYPosition = target.scrollTop; + } + + _isSectionVisibleInPicker(section, picker) { + const { bottom, height, top } = section.getBoundingClientRect(); + const containerRect = picker.getBoundingClientRect(); + + return top <= containerRect.top + ? containerRect.top - top <= height + : bottom - containerRect.bottom <= height; + } + + _getSectionsUpTo(section) { + const sections = []; + for (const sectionNode of document.querySelectorAll( + ".emoji-picker__section" + )) { + const sectionName = sectionNode.dataset.section; + sections.push(sectionNode.dataset.section); + if (sectionName === section) { + break; + } + } + return sections; + } + + +} diff --git a/app/assets/javascripts/discourse/app/components/emoji-picker/detached.gjs b/app/assets/javascripts/discourse/app/components/emoji-picker/detached.gjs new file mode 100644 index 00000000000..e2aa71308e3 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/emoji-picker/detached.gjs @@ -0,0 +1,12 @@ +import EmojiPickerContent from "discourse/components/emoji-picker/content"; + +const EmojiPickerDetached = ; + +export default EmojiPickerDetached; diff --git a/app/assets/javascripts/discourse/app/components/emoji-picker/diversity-menu.gjs b/app/assets/javascripts/discourse/app/components/emoji-picker/diversity-menu.gjs new file mode 100644 index 00000000000..44abc6257cc --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/emoji-picker/diversity-menu.gjs @@ -0,0 +1,71 @@ +import Component from "@glimmer/component"; +import { concat, fn } from "@ember/helper"; +import { action } from "@ember/object"; +import { service } from "@ember/service"; +import { eq } from "truth-helpers"; +import DButton from "discourse/components/d-button"; +import DropdownMenu from "discourse/components/dropdown-menu"; +import replaceEmoji from "discourse/helpers/replace-emoji"; +import DMenu from "float-kit/components/d-menu"; + +export const FITZPATRICK_MODIFIERS = [ + { scale: 1, modifier: null }, + { scale: 2, modifier: ":t2" }, + { scale: 3, modifier: ":t3" }, + { scale: 4, modifier: ":t4" }, + { scale: 5, modifier: ":t5" }, + { scale: 6, modifier: ":t6" }, +]; + +export default class EmojiPicker extends Component { + @service emojiStore; + + fitzpatrickModifiers = FITZPATRICK_MODIFIERS; + + @action + didRequestFitzpatrickScale(scale) { + this.emojiStore.diversity = scale; + this.api.close(); + } + + @action + registerApi(api) { + this.api = api; + } + + +} diff --git a/app/assets/javascripts/discourse/app/components/emoji-picker/index.gjs b/app/assets/javascripts/discourse/app/components/emoji-picker/index.gjs new file mode 100644 index 00000000000..f3da70e563e --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/emoji-picker/index.gjs @@ -0,0 +1,59 @@ +import Component from "@glimmer/component"; +import { concat } from "@ember/helper"; +import { action } from "@ember/object"; +import EmojiPickerContent from "discourse/components/emoji-picker/content"; +import concatClass from "discourse/helpers/concat-class"; +import replaceEmoji from "discourse/helpers/replace-emoji"; +import icon from "discourse-common/helpers/d-icon"; +import DMenu from "float-kit/components/d-menu"; + +export default class EmojiPicker extends Component { + @action + onRegisterMenu(api) { + this.menu = api; + } + + get icon() { + return this.args.icon ?? "discourse-emojis"; + } + + get context() { + return this.args.context ?? "topic"; + } + + +} diff --git a/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.gjs b/app/assets/javascripts/discourse/app/components/filter-input.gjs similarity index 91% rename from plugins/chat/assets/javascripts/discourse/components/dc-filter-input.gjs rename to app/assets/javascripts/discourse/app/components/filter-input.gjs index be0c5b23626..5a8002d0254 100644 --- a/plugins/chat/assets/javascripts/discourse/components/dc-filter-input.gjs +++ b/app/assets/javascripts/discourse/app/components/filter-input.gjs @@ -7,7 +7,7 @@ import concatClass from "discourse/helpers/concat-class"; import noop from "discourse/helpers/noop"; import icon from "discourse-common/helpers/d-icon"; -export default class DcFilterInput extends Component { +export default class FilterInput extends Component { @tracked isFocused = false; focusState = modifier((element) => { @@ -31,7 +31,7 @@ export default class DcFilterInput extends Component {
@@ -43,7 +43,7 @@ export default class DcFilterInput extends Component { {{this.focusState}} {{on "input" (if @filterAction @filterAction (noop))}} @value={{@value}} - class="dc-filter-input" + class="filter-input" ...attributes /> diff --git a/app/assets/javascripts/discourse/app/components/user-status-picker.gjs b/app/assets/javascripts/discourse/app/components/user-status-picker.gjs index 848b551c605..30eb093ad66 100644 --- a/app/assets/javascripts/discourse/app/components/user-status-picker.gjs +++ b/app/assets/javascripts/discourse/app/components/user-status-picker.gjs @@ -2,9 +2,6 @@ import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import { on } from "@ember/modifier"; import { action } from "@ember/object"; -import { scheduleOnce } from "@ember/runloop"; -import { htmlSafe } from "@ember/template"; -import DButton from "discourse/components/d-button"; import EmojiPicker from "discourse/components/emoji-picker"; import concatClass from "discourse/helpers/concat-class"; import { emojiUnescape } from "discourse/lib/text"; @@ -14,16 +11,11 @@ import { i18n } from "discourse-i18n"; export default class UserStatusPicker extends Component { @tracked isFocused = false; - @tracked emojiPickerIsActive = false; get emojiHtml() { return emojiUnescape(escapeExpression(`:${this.args.status.emoji}:`)); } - focusEmojiButton() { - document.querySelector(".user-status-picker .btn-emoji")?.focus(); - } - @action blur() { this.isFocused = false; @@ -32,9 +24,6 @@ export default class UserStatusPicker extends Component { @action emojiSelected(emoji) { this.args.status.emoji = emoji; - this.emojiPickerIsActive = false; - - scheduleOnce("afterRender", this, this.focusEmojiButton); } @action @@ -42,22 +31,12 @@ export default class UserStatusPicker extends Component { this.isFocused = true; } - @action - onEmojiPickerOutsideClick() { - this.emojiPickerIsActive = false; - } - @action updateDescription(event) { this.args.status.description = event.target.value; this.args.status.emoji ||= "speech_balloon"; } - @action - toggleEmojiPicker() { - this.emojiPickerIsActive = !this.emojiPickerIsActive; - } - } diff --git a/app/assets/javascripts/discourse/app/form-kit/components/fk/control/menu.gjs b/app/assets/javascripts/discourse/app/form-kit/components/fk/control/menu.gjs index 591ce872653..ca4d39064f6 100644 --- a/app/assets/javascripts/discourse/app/form-kit/components/fk/control/menu.gjs +++ b/app/assets/javascripts/discourse/app/form-kit/components/fk/control/menu.gjs @@ -22,7 +22,8 @@ export default class FKControlMenu extends Component { ); - assert.dom(".form-kit__control-menu").hasAttribute("disabled"); + assert.dom(".form-kit__control-menu-trigger").hasAttribute("disabled"); }); }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-status-picker-test.gjs b/app/assets/javascripts/discourse/tests/integration/components/user-status-picker-test.gjs index 1f929617182..9a4295b214e 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/user-status-picker-test.gjs +++ b/app/assets/javascripts/discourse/tests/integration/components/user-status-picker-test.gjs @@ -39,11 +39,11 @@ module("Integration | Component | user-status-picker", function (hooks) { await render(); await click(".btn-emoji"); - await fillIn(".emoji-picker-content .filter", "mega"); - await click(".results .emoji"); + await fillIn(".emoji-picker-content .filter-input", "raised"); + await click(".emoji-picker__sections .emoji"); - assert.dom(".emoji").hasAttribute("alt", "mega"); - assert.strictEqual(status.emoji, "mega"); + assert.dom(".emoji").hasAttribute("alt", "raised_hands"); + assert.strictEqual(status.emoji, "raised_hands"); }); test("it sets default emoji when user starts typing a description", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/unit/services/emoji-store-test.js b/app/assets/javascripts/discourse/tests/unit/services/emoji-store-test.js index bfd5a5199a8..621dd9f863b 100644 --- a/app/assets/javascripts/discourse/tests/unit/services/emoji-store-test.js +++ b/app/assets/javascripts/discourse/tests/unit/services/emoji-store-test.js @@ -1,40 +1,132 @@ import { getOwner } from "@ember/owner"; import { setupTest } from "ember-qunit"; import { module, test } from "qunit"; +import KeyValueStore from "discourse/lib/key-value-store"; +import { + MAX_DISPLAYED_EMOJIS, + MAX_TRACKED_EMOJIS, + SKIN_TONE_STORE_KEY, + STORE_NAMESPACE, + USER_EMOJIS_STORE_KEY, +} from "discourse/services/emoji-store"; module("Unit | Service | emoji-store", function (hooks) { setupTest(hooks); hooks.beforeEach(function () { this.emojiStore = getOwner(this).lookup("service:emoji-store"); - this.emojiStore.reset(); }); hooks.afterEach(function () { this.emojiStore.reset(); }); - test("defaults", function (assert) { - assert.deepEqual(this.emojiStore.favorites, []); - assert.strictEqual(this.emojiStore.diversity, 1); + test(".favoritesForContext", function (assert) { + assert.deepEqual(this.emojiStore.favoritesForContext("topic"), [ + "+1", + "heart", + "tada", + ]); }); - test("diversity", function (assert) { + test(".trackEmojiForContext", function (assert) { + this.emojiStore.trackEmojiForContext("grinning", "topic"); + const storedEmojis = new KeyValueStore(STORE_NAMESPACE).getObject( + `topic_${USER_EMOJIS_STORE_KEY}` + ); + + assert.deepEqual(this.emojiStore.favoritesForContext("topic"), [ + "grinning", + "+1", + "heart", + "tada", + ]); + assert.deepEqual( + storedEmojis, + ["grinning", "+1", "heart", "tada"], + "it persists the tracked emojis" + ); + }); + + test("limits the maximum number of tracked emojis", function (assert) { + let trackedEmojis; + Array.from({ length: 45 }).forEach(() => { + trackedEmojis = this.emojiStore.trackEmojiForContext("grinning", "topic"); + }); + + assert.strictEqual(trackedEmojis.length, MAX_TRACKED_EMOJIS); + }); + + test("limits the maximum number of favorites emojis", function (assert) { + Array.from({ length: 25 }).forEach((_, i) => { + this.emojiStore.trackEmojiForContext(`emoji_${i}`, "topic"); + }); + + assert.strictEqual( + this.emojiStore.favoritesForContext("topic").length, + MAX_DISPLAYED_EMOJIS + ); + }); + + test("support for multiple contexts", function (assert) { + this.emojiStore.trackEmojiForContext("grinning", "topic"); + + assert.deepEqual(this.emojiStore.favoritesForContext("topic"), [ + "grinning", + "+1", + "heart", + "tada", + ]); + + this.emojiStore.trackEmojiForContext("cat", "chat"); + + assert.deepEqual(this.emojiStore.favoritesForContext("chat"), [ + "cat", + "+1", + "heart", + "tada", + ]); + }); + + test(".resetContext", function (assert) { + this.emojiStore.trackEmojiForContext("grinning", "topic"); + + this.emojiStore.resetContext("topic"); + + assert.deepEqual(this.emojiStore.favoritesForContext("topic"), [ + "+1", + "heart", + "tada", + ]); + }); + + test(".diversity", function (assert) { + assert.deepEqual(this.emojiStore.diversity, 1); + }); + + test(".diversity=", function (assert) { this.emojiStore.diversity = 2; - assert.strictEqual(this.emojiStore.diversity, 2); + const storedDiversity = new KeyValueStore(STORE_NAMESPACE).getObject( + SKIN_TONE_STORE_KEY + ); + + assert.deepEqual(this.emojiStore.diversity, 2); + assert.deepEqual(storedDiversity, 2, "it persists the diversity value"); }); - test("favorites", function (assert) { - this.emojiStore.favorites = ["smile"]; - assert.deepEqual(this.emojiStore.favorites, ["smile"]); - }); + test("sort emojis by frequency", function (assert) { + this.emojiStore.trackEmojiForContext("cat", "topic"); + this.emojiStore.trackEmojiForContext("cat", "topic"); + this.emojiStore.trackEmojiForContext("cat", "topic"); + this.emojiStore.trackEmojiForContext("dog", "topic"); + this.emojiStore.trackEmojiForContext("dog", "topic"); - test("track", function (assert) { - this.emojiStore.track("woman:t4"); - assert.deepEqual(this.emojiStore.favorites, ["woman:t4"]); - - this.emojiStore.track("otter"); - this.emojiStore.track(":otter:"); - assert.deepEqual(this.emojiStore.favorites, ["otter", "woman:t4"]); + assert.deepEqual(this.emojiStore.favoritesForContext("topic"), [ + "cat", + "dog", + "+1", + "heart", + "tada", + ]); }); }); diff --git a/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs b/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs index bb344a4228b..6d61a80edbe 100644 --- a/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs +++ b/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs @@ -9,6 +9,7 @@ import DFloatPortal from "float-kit/components/d-float-portal"; import { getScrollParent } from "float-kit/lib/get-scroll-parent"; import FloatKitApplyFloatingUi from "float-kit/modifiers/apply-floating-ui"; import FloatKitCloseOnEscape from "float-kit/modifiers/close-on-escape"; +import and from "truth-helpers/helpers/and"; export default class DFloatBody extends Component { closeOnScroll = modifierFn(() => { @@ -25,6 +26,18 @@ export default class DFloatBody extends Component { }; }); + trapPointerDown = modifierFn((element) => { + const handler = (event) => { + event.stopPropagation(); + }; + + element.addEventListener("pointerdown", handler); + + return () => { + element.removeEventListener("pointerdown", handler); + }; + }); + get supportsCloseOnClickOutside() { return this.args.instance.expanded && this.options.closeOnClickOutside; } @@ -66,9 +79,10 @@ export default class DFloatBody extends Component { aria-expanded={{if @instance.expanded "true" "false"}} role={{@role}} {{FloatKitApplyFloatingUi this.trigger this.options @instance}} + {{this.trapPointerDown}} {{(if @trapTab (modifier TrapTab autofocus=this.options.autofocus))}} {{(if - this.supportsCloseOnClickOutside + (and @instance.expanded this.supportsCloseOnClickOutside) (modifier closeOnClickOutside (fn @instance.close (hash focusTrigger=false)) diff --git a/app/assets/javascripts/float-kit/addon/components/d-menu.gjs b/app/assets/javascripts/float-kit/addon/components/d-menu.gjs index 4cbbf092576..3534ef336e3 100644 --- a/app/assets/javascripts/float-kit/addon/components/d-menu.gjs +++ b/app/assets/javascripts/float-kit/addon/components/d-menu.gjs @@ -3,8 +3,6 @@ import { concat } from "@ember/helper"; import { on } from "@ember/modifier"; import { action } from "@ember/object"; import { getOwner } from "@ember/owner"; -import didInsert from "@ember/render-modifiers/modifiers/did-insert"; -import willDestroy from "@ember/render-modifiers/modifiers/will-destroy"; import { service } from "@ember/service"; import { modifier } from "ember-modifier"; import { and } from "truth-helpers"; @@ -35,10 +33,13 @@ export default class DMenu extends Component { }; }); - @action - registerFloatBody(element) { + registerFloatBody = modifier((element) => { this.body = element; - } + + return () => { + this.body = null; + }; + }); @action teardownFloatBody() { @@ -156,8 +157,7 @@ export default class DMenu extends Component { @innerClass="fk-d-menu__inner-content" @role="dialog" @inline={{this.options.inline}} - {{didInsert this.registerFloatBody}} - {{willDestroy this.teardownFloatBody}} + {{this.registerFloatBody}} > {{#if (has-block)}} {{yield this.componentArgs}} diff --git a/app/assets/javascripts/float-kit/addon/lib/constants.js b/app/assets/javascripts/float-kit/addon/lib/constants.js index 9f68c8d3ed3..c1d63c27607 100644 --- a/app/assets/javascripts/float-kit/addon/lib/constants.js +++ b/app/assets/javascripts/float-kit/addon/lib/constants.js @@ -71,6 +71,7 @@ export const MENU = { modalForMobile: false, inline: null, groupIdentifier: null, + parentIdentifier: null, triggerClass: null, contentClass: null, class: null, diff --git a/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js index 33653217177..949c2837e67 100644 --- a/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js +++ b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js @@ -70,6 +70,8 @@ export default class DMenuInstance extends FloatKitInstance { if (options.focusTrigger) { this.trigger?.focus?.(); } + + await this.options.onClose?.(this); } @action diff --git a/app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js b/app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js index e90433dffbd..e19aa016df7 100644 --- a/app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js +++ b/app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js @@ -1,11 +1,8 @@ import { registerDestructor } from "@ember/destroyable"; -import { service } from "@ember/service"; import Modifier from "ember-modifier"; import { bind } from "discourse-common/utils/decorators"; export default class FloatKitCloseOnEscape extends Modifier { - @service menu; - constructor(owner, args) { super(owner, args); registerDestructor(this, (instance) => instance.cleanup()); diff --git a/app/assets/javascripts/pretty-text/addon/emoji.js b/app/assets/javascripts/pretty-text/addon/emoji.js index 75f663f1298..78c29b30ec6 100644 --- a/app/assets/javascripts/pretty-text/addon/emoji.js +++ b/app/assets/javascripts/pretty-text/addon/emoji.js @@ -15,10 +15,6 @@ export function registerEmoji(code, url, group) { extendedEmojiMap.set(code, { url, group }); } -export function extendedEmojiList() { - return extendedEmojiMap; -} - const emojiMap = new Map(); // Regex from https://github.com/mathiasbynens/emoji-test-regex-pattern/blob/main/dist/latest/javascript.txt diff --git a/app/assets/stylesheets/common/base/emoji.scss b/app/assets/stylesheets/common/base/emoji.scss index 70f86f07c7e..ad204857418 100644 --- a/app/assets/stylesheets/common/base/emoji.scss +++ b/app/assets/stylesheets/common/base/emoji.scss @@ -35,230 +35,3 @@ sup img.emoji { height: 1.1em; width: 1.1em; } - -.emoji-picker { - width: 100%; - color: var(--primary); - background-color: var(--secondary); - border: 1px solid var(--primary-low); - display: flex; - box-sizing: border-box; - background-clip: padding-box; - z-index: z("modal", "content"); - flex-direction: row; - height: 320px; - max-height: 50vh; - max-width: 420px; - - img.emoji { - // custom emojis might import images of various sizes - // we don't want them to be deformed in the picker - width: 20px !important; - height: 20px !important; - } - - .emoji-picker-content { - display: flex; - flex-direction: column; - flex: 20; - } - - .emoji-picker-emoji-area { - overflow-y: scroll; - -webkit-overflow-scrolling: touch; - width: 100%; - box-sizing: border-box; - padding: 0.25em; - padding-left: 0.75em; - height: 100%; - background: var(--secondary); - - .section { - margin: 0 0 1em; - - .trash-recent { - background: none; - font-size: var(--font-down-1); - - &:hover .d-icon { - color: var(--danger-hover); - } - } - } - - .section-header { - font-weight: 900; - padding: 0.25em 0; - display: flex; - justify-content: space-between; - align-items: center; - } - - .section-group, - .results { - display: flex; - flex-wrap: wrap; - img.emoji { - padding: 0.25em 0.28em; - cursor: pointer; - margin: 0; - display: inline-flex; - - &:hover, - &:focus { - background: var(--tertiary-low); - border-radius: 3px; - } - } - } - - .results { - padding: 0.25em 0; - - &:empty { - display: none; - } - } - } - - .emoji-picker-category-buttons { - overflow-y: auto; - width: 50px; - padding-left: 0.5em; - display: flex; - flex-wrap: wrap; - border-right: 1px solid var(--primary-low); - - .category-button { - background: none; - border: none; - padding: 0.5em; - outline: none; - - .emoji { - pointer-events: none; - filter: grayscale(100%); - } - - &:hover .emoji, - &.current .emoji { - filter: grayscale(0); - } - } - } - - &.has-filter { - .emojis-container { - visibility: hidden; - height: 0px; - overflow: hidden; - } - - .emoji-picker-category-buttons { - pointer-events: none; - opacity: 0.5; - .category-button.current .emoji { - filter: grayscale(100%); - } - } - } -} - -.emoji-picker-search-container { - display: flex; - width: 100%; - position: relative; - padding: 0.75em; - border-bottom: 1px solid var(--primary-low); - box-sizing: border-box; - align-items: center; - - .filter { - flex: 1 0 auto; - margin: 0; - width: calc(100% - 50px); - margin-right: 0.5em; - } - - .d-icon { - color: var(--primary-medium); - cursor: pointer; - padding: 0.25em; - &:hover { - color: var(--tertiary); - } - } -} - -.emoji-picker-footer { - display: flex; - justify-content: space-between; - align-items: center; - border-top: 1px solid var(--primary-low); -} - -.emoji-picker-emoji-info { - display: flex; - align-items: center; - padding-left: 0.5em; - - img.emoji { - height: 32px !important; - width: 32px !important; - } -} - -.emoji-picker-diversity-picker { - border: 0; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: 0.5em; - - .diversity-scale { - display: flex; - align-items: center; - justify-content: center; - min-height: auto; - border-radius: 3px; - margin: 0.15em; - height: 24px; - width: 24px; - - .d-icon { - color: #fff; - filter: drop-shadow(0.5px 1.5px 0 rgba(0, 0, 0, 0.3)); - } - } - - .diversity-scale.default { - background: #ffcc4d; - } - .diversity-scale.light { - background: #f7dece; - } - .diversity-scale.medium-light { - background: #f3d2a2; - } - .diversity-scale.medium { - background: #d5ab88; - } - .diversity-scale.medium-dark { - background: #af7e57; - } - .diversity-scale.dark { - background: #7c533e; - } -} - -.emoji-picker-modal-overlay { - z-index: z("modal", "overlay"); - position: fixed; - left: 0; - top: 0; - width: 100%; - height: 100%; - opacity: 0.8; - background-color: var(--primary); -} diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss index d8eb9592fb6..07df54c0354 100644 --- a/app/assets/stylesheets/common/components/_index.scss +++ b/app/assets/stylesheets/common/components/_index.scss @@ -58,3 +58,6 @@ @import "widget-dropdown"; @import "welcome-header"; @import "notifications-tracking"; +@import "emoji-picker"; +@import "filter-input"; +@import "dropdown-menu"; diff --git a/app/assets/stylesheets/common/components/dropdown-menu.scss b/app/assets/stylesheets/common/components/dropdown-menu.scss new file mode 100644 index 00000000000..9a4c754825b --- /dev/null +++ b/app/assets/stylesheets/common/components/dropdown-menu.scss @@ -0,0 +1,18 @@ +.dropdown-menu { + padding: 0; + margin: 0; + + &__item { + list-style: none; + + .btn { + padding: 0.65rem 1rem; + width: 100%; + justify-content: flex-start; + } + } + + &__divider { + margin: 0rem; + } +} diff --git a/plugins/chat/assets/stylesheets/common/chat-emoji-picker.scss b/app/assets/stylesheets/common/components/emoji-picker.scss similarity index 61% rename from plugins/chat/assets/stylesheets/common/chat-emoji-picker.scss rename to app/assets/stylesheets/common/components/emoji-picker.scss index 9c51a474b45..30fdcae5897 100644 --- a/plugins/chat/assets/stylesheets/common/chat-emoji-picker.scss +++ b/app/assets/stylesheets/common/components/emoji-picker.scss @@ -1,16 +1,36 @@ -.chat-emoji-picker { - border-top: 1px solid var(--primary-low); - transition: height 125ms ease; +.fk-d-menu[data-content][data-identifier="emoji-picker"] { + z-index: z("modal", "dialog"); +} + +.emoji-picker-trigger { + .d-icon + .d-button-label { + margin-left: 0.25em; + } +} + +.emoji-picker { display: flex; flex-direction: column; height: 300px; - overflow: hidden; - background: var(--secondary); + width: 500px; + max-width: 100vw; + + .spinner-container { + height: 100%; + } + + &__diversity-menu.fk-d-menu { + z-index: z("modal", "dialog"); + + .dropdown-menu { + min-width: auto; + } + } .emoji { padding: 6px; - width: 32px; - height: 32px; + width: 24px; + height: 24px; image-rendering: -webkit-optimize-contrast; cursor: pointer; @@ -23,20 +43,19 @@ } &__filter-container { - top: 0; - position: sticky; - background: var(--secondary); + background: var(--primary-very-low); display: flex; height: 50px; } &__filter { width: 100%; - padding: 0.5rem; - margin: 0.25rem; + margin: 0.5rem; + border-radius: var(--d-border-radius); + box-sizing: border-box; input { - background: none; + background-color: transparent !important; width: 100%; } @@ -44,40 +63,51 @@ color: var(--primary-medium); } - &.dc-filter-input-container { + &.filter-input-container { border-color: transparent; - background: var(--primary-very-low); + background: var(--secondary); } } + &__content { + display: flex; + flex-direction: row; + height: 250px; + } + &__scrollable-content { - height: 100%; + width: 100%; overflow-y: scroll; - text-transform: capitalize; - @include chat-scrollbar(); - margin: 1px; + overscroll-behavior: contain; + height: 100%; } &__no-results { - padding: 1em; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + font-weight: 700; + padding: 2em; } &__sections-nav { - top: 0; - position: sticky; - background: var(--secondary); - border-bottom: 1px solid var(--primary-low); - height: 50px; + height: 100%; display: flex; - align-items: center; + flex-direction: column; + min-width: 40px; + overflow-y: auto; + overflow-x: hidden; + height: 250px; + background: var(--primary-low); + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + overscroll-behavior: contain; + } - &__indicator { - background: var(--tertiary); - height: 4px; - transition: transform 0.3s cubic-bezier(0.1, 0.82, 0.25, 1); - position: absolute; - bottom: 0; - } + &__sections-nav::-webkit-scrollbar { + display: none; } &__section-btn { @@ -94,6 +124,10 @@ background: none; } + &.active { + scale: 1.4; + } + .emoji { width: 21px; height: 21px; @@ -114,18 +148,23 @@ right: 0; } + &__section-title-container { + display: flex; + position: sticky; + top: -1px; // to avoid an ugly sub 1px gap on retina + background: rgba(var(--secondary-rgb), 0.95); + z-index: 2; + } + &__section-title { margin: 0; padding: 0.5rem; - color: var(--primary-very-high); - font-size: var(--font-up-0); + color: var(--primary-high); + font-size: var(--font-down-2); font-weight: 700; - background: rgba(var(--secondary-rgb), 0.95); - position: sticky; - top: 0; - z-index: 1; width: 100%; box-sizing: border-box; + text-transform: uppercase; } &__fitzpatrick-modifier-btn { @@ -198,18 +237,3 @@ align-items: center; } } - -.chat-channel-message-emoji-picker-connector { - position: relative; - - .chat-emoji-picker { - border: 1px solid var(--primary-low); - width: 320px; - z-index: z("header") + 1; - - .emoji { - width: 22px; - height: 22px; - } - } -} diff --git a/plugins/chat/assets/stylesheets/common/dc-filter-input.scss b/app/assets/stylesheets/common/components/filter-input.scss similarity index 86% rename from plugins/chat/assets/stylesheets/common/dc-filter-input.scss rename to app/assets/stylesheets/common/components/filter-input.scss index bf6dd2fbe4c..98ca3461f93 100644 --- a/plugins/chat/assets/stylesheets/common/dc-filter-input.scss +++ b/app/assets/stylesheets/common/components/filter-input.scss @@ -1,4 +1,4 @@ -.dc-filter-input-container { +.filter-input-container { display: flex; align-items: center; justify-content: space-between; @@ -9,8 +9,8 @@ border: 1px solid var(--tertiary); } - .dc-filter-input, - .dc-filter-input:focus { + .filter-input, + .filter-input:focus { width: 100%; margin: 0; border: none; diff --git a/app/assets/stylesheets/common/components/user-status-picker.scss b/app/assets/stylesheets/common/components/user-status-picker.scss index 05819079ae5..2ebb7670f24 100644 --- a/app/assets/stylesheets/common/components/user-status-picker.scss +++ b/app/assets/stylesheets/common/components/user-status-picker.scss @@ -8,9 +8,10 @@ width: 100%; border: 1px solid var(--primary-medium); - .btn-emoji { + .emoji-picker-trigger { margin: 3px; - width: 2.3em; + width: 38px; + height: 38px; text-align: center; } diff --git a/app/assets/stylesheets/common/form-kit/_control-menu.scss b/app/assets/stylesheets/common/form-kit/_control-menu.scss index 1ad96915607..8503e08643d 100644 --- a/app/assets/stylesheets/common/form-kit/_control-menu.scss +++ b/app/assets/stylesheets/common/form-kit/_control-menu.scss @@ -1,4 +1,15 @@ -.form-kit__control-menu { +.form-kit__control-menu-trigger { @include default-input; justify-content: space-between; } + +.form-kit__control-menu-content { + .dropdown-menu { + min-width: 200px; + } + .dropdown-menu__item { + &:hover { + background: var(--d-hover); + } + } +} diff --git a/app/assets/stylesheets/mobile/_index.scss b/app/assets/stylesheets/mobile/_index.scss index 696fa9ce9ab..d5f687dce87 100644 --- a/app/assets/stylesheets/mobile/_index.scss +++ b/app/assets/stylesheets/mobile/_index.scss @@ -12,7 +12,6 @@ @import "directory"; @import "discourse"; @import "edit-category"; -@import "emoji"; @import "header"; @import "invite-signup"; @import "lightbox"; diff --git a/app/assets/stylesheets/mobile/components/_index.scss b/app/assets/stylesheets/mobile/components/_index.scss index c6f42c21789..133fb5db2e6 100644 --- a/app/assets/stylesheets/mobile/components/_index.scss +++ b/app/assets/stylesheets/mobile/components/_index.scss @@ -4,3 +4,6 @@ @import "user-card"; @import "user-stream-item"; @import "welcome-header"; +@import "more-topics"; +@import "bookmark-menu"; +@import "emoji-picker"; diff --git a/app/assets/stylesheets/mobile/components/emoji-picker.scss b/app/assets/stylesheets/mobile/components/emoji-picker.scss new file mode 100644 index 00000000000..068e1d11f19 --- /dev/null +++ b/app/assets/stylesheets/mobile/components/emoji-picker.scss @@ -0,0 +1,57 @@ +.fk-d-menu-modal.emoji-picker-content { + .emoji-picker { + height: auto; + } + + html:not(.keyboard-visible.mobile-view) & .d-modal__container { + height: calc(var(--composer-vh, var(--1dvh)) * 100); + max-height: 100%; + + .emoji-picker__content { + height: calc( + var(--composer-vh, var(--1dvh)) * 100 - 50px - + env(safe-area-inset-bottom) + ); + } + } + + html.keyboard-visible.mobile-view & .d-modal__container { + .emoji-picker__content { + height: calc(var(--composer-vh, var(--1dvh)) * 100 - 50px); + } + } + + .d-modal__body { + padding: 0; + } + + .d-modal__container { + align-self: flex-start; + } + + .emoji-picker__sections-nav { + order: 1; + height: 50px; + width: 100%; + flex-direction: row; + display: flex; + box-sizing: border-box; + overflow-y: hidden; + overflow-x: auto; + } + + .emoji-picker__content { + flex-direction: column; + padding-top: 1em; + box-sizing: border-box; + } + + .emoji-picker__scrollable-content { + height: 100%; + } + + .emoji-picker__close-btn { + margin-left: 0.25em; + padding-left: 0.75em; + } +} diff --git a/app/assets/stylesheets/mobile/emoji.scss b/app/assets/stylesheets/mobile/emoji.scss deleted file mode 100644 index ee41bc80954..00000000000 --- a/app/assets/stylesheets/mobile/emoji.scss +++ /dev/null @@ -1,18 +0,0 @@ -.emoji-picker { - border: none; - position: fixed; - width: 100%; - max-width: 100vh; - top: 0; - left: 0; - border-bottom: 1px solid var(--primary-low); - min-height: 50vh; - .emoji-picker-emoji-area { - img.emoji { - // custom emojis might import images of various sizes - // we don't want them to be deformed in the picker - width: 28px !important; - height: 28px !important; - } - } -} diff --git a/app/assets/stylesheets/mobile/float-kit/d-menu.scss b/app/assets/stylesheets/mobile/float-kit/d-menu.scss index b315d85581a..c9ebac78145 100644 --- a/app/assets/stylesheets/mobile/float-kit/d-menu.scss +++ b/app/assets/stylesheets/mobile/float-kit/d-menu.scss @@ -10,9 +10,6 @@ max-width: 100px; border-radius: 10px; } - .d-modal__body { - padding: 1em 0; - } h3 { padding-top: 0.25em; diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb new file mode 100644 index 00000000000..0217d8d8d14 --- /dev/null +++ b/app/controllers/emojis_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class EmojisController < ApplicationController + def index + emojis = Emoji.allowed.group_by(&:group) + render json: MultiJson.dump(emojis) + end +end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 840774add95..ac316fa1cd1 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2603,6 +2603,8 @@ en: disable_mailing_list_mode: "Disallow users from enabling mailing list mode (prevents any mailing list emails from being sent.)" default_email_previous_replies: "Include previous replies in emails by default." + default_emoji_reactions: "Default favorites emoji reactions. Add up to 5 emojis for quick reaction." + default_email_in_reply_to: "Include excerpt of replied to post in emails by default." default_hide_profile: "Hide user public profile by default." @@ -2977,6 +2979,7 @@ en: default_email_mailing_list_mode_frequency: "" default_email_messages_level: "" default_email_previous_replies: "" + default_emoji_reactions: "" default_hide_presence: "" default_hide_profile: "" default_include_tl0_in_digests: "" diff --git a/config/routes.rb b/config/routes.rb index b9178f85dce..f8480e6a819 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1711,6 +1711,8 @@ Discourse::Application.routes.draw do get "/form-templates/:id" => "form_templates#show" get "/form-templates" => "form_templates#index" + get "/emojis" => "emojis#index" + if Rails.env.test? # Routes that are only used for testing get "/test_net_http_timeouts" => "test_requests#test_net_http_timeouts" diff --git a/config/site_settings.yml b/config/site_settings.yml index 881eb95d5e0..85799e5a258 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -959,6 +959,10 @@ posting: max_emojis_in_title: default: 1 area: "emojis" + default_emoji_reactions: + type: emoji_list + default: +1|heart|tada + client: true allow_uncategorized_topics: client: true default: false diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index a794bef55c5..08ae71850b4 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -189,20 +189,6 @@ task "javascript:update_constants" => :environment do write_template("pretty-text/addon/emoji/version.js", task_name, <<~JS) export const IMAGE_VERSION = "#{Emoji::EMOJI_VERSION}"; JS - - groups_json = JSON.parse(File.read("lib/emoji/groups.json")) - - emoji_buttons = groups_json.map { |group| <<~HTML } - - HTML - - emoji_sections = groups_json.map { |group| html_for_section(group) } - - components_dir = "discourse/app/components" - write_hbs_template("#{components_dir}/emoji-group-buttons.hbs", task_name, emoji_buttons.join) - write_hbs_template("#{components_dir}/emoji-group-sections.hbs", task_name, emoji_sections.join) end task "javascript:update" => "clean_up" do diff --git a/plugins/chat/admin/assets/javascripts/admin/components/chat-incoming-webhook-edit-form.gjs b/plugins/chat/admin/assets/javascripts/admin/components/chat-incoming-webhook-edit-form.gjs index 13f23a054c6..69fbad0f0cc 100644 --- a/plugins/chat/admin/assets/javascripts/admin/components/chat-incoming-webhook-edit-form.gjs +++ b/plugins/chat/admin/assets/javascripts/admin/components/chat-incoming-webhook-edit-form.gjs @@ -1,5 +1,4 @@ import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; import { fn } from "@ember/helper"; import { action } from "@ember/object"; import { service } from "@ember/service"; @@ -17,8 +16,6 @@ export default class ChatIncomingWebhookEditForm extends Component { @service toasts; @service router; - @tracked emojiPickerIsActive = false; - get formData() { return { name: this.args.webhook?.name, @@ -32,7 +29,6 @@ export default class ChatIncomingWebhookEditForm extends Component { @action emojiSelected(setData, emoji) { setData("emoji", `:${emoji}:`); - this.emojiPickerIsActive = false; } @action @@ -129,6 +125,7 @@ export default class ChatIncomingWebhookEditForm extends Component { @name="emoji" @title={{i18n "chat.incoming_webhooks.emoji"}} @description={{i18n "chat.incoming_webhooks.emoji_instructions"}} + @size="large" as |field| > @@ -140,33 +137,19 @@ export default class ChatIncomingWebhookEditForm extends Component { {{/if}} - - - {{#unless this.emojiPickerIsActive}} - - - - - - - - - {{/unless}} - + + + + + + + + diff --git a/plugins/chat/app/controllers/chat/emojis_controller.rb b/plugins/chat/app/controllers/chat/emojis_controller.rb deleted file mode 100644 index e27f8ae537e..00000000000 --- a/plugins/chat/app/controllers/chat/emojis_controller.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Chat - class EmojisController < ::Chat::BaseController - def index - emojis = Emoji.allowed.group_by(&:group) - render json: MultiJson.dump(emojis) - end - end -end diff --git a/plugins/chat/assets/javascripts/discourse/components/browse-channels.gjs b/plugins/chat/assets/javascripts/discourse/components/browse-channels.gjs index fa3014af898..d91502ad623 100644 --- a/plugins/chat/assets/javascripts/discourse/components/browse-channels.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/browse-channels.gjs @@ -8,13 +8,13 @@ import { schedule } from "@ember/runloop"; import { service } from "@ember/service"; import { eq } from "truth-helpers"; import DButton from "discourse/components/d-button"; +import FilterInput from "discourse/components/filter-input"; import { INPUT_DELAY } from "discourse-common/config/environment"; import discourseDebounce from "discourse-common/lib/debounce"; import { i18n } from "discourse-i18n"; import List from "discourse/plugins/chat/discourse/components/chat/list"; import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message"; import ChatChannelCard from "discourse/plugins/chat/discourse/components/chat-channel-card"; -import DcFilterInput from "discourse/plugins/chat/discourse/components/dc-filter-input"; const ARCHIVED = "archived"; const ALL = "all"; @@ -89,11 +89,10 @@ export default class BrowseChannels extends Component { -
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs deleted file mode 100644 index 0d83337d0a0..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs +++ /dev/null @@ -1,77 +0,0 @@ -import Component from "@glimmer/component"; -import { action } from "@ember/object"; -import { service } from "@ember/service"; -import { createPopper } from "@popperjs/core"; -import { modifier } from "ember-modifier"; -import { headerOffset } from "discourse/lib/offset-calculator"; -import ChatEmojiPicker from "discourse/plugins/chat/discourse/components/chat-emoji-picker"; - -export default class ChatChannelMessageEmojiPicker extends Component { - @service site; - @service chatEmojiPickerManager; - - context = "chat-channel-message"; - - listenToBodyScroll = modifier(() => { - const handler = () => { - this.chatEmojiPickerManager.close(); - }; - - document.addEventListener("scroll", handler); - - return () => { - document.removeEventListener("scroll", handler); - }; - }); - - @action - willDestroy() { - super.willDestroy(...arguments); - this._popper?.destroy(); - } - - @action - didSelectEmoji(emoji) { - this.chatEmojiPickerManager.picker?.didSelectEmoji(emoji); - this.chatEmojiPickerManager.close(); - } - - @action - didInsert(element) { - if (this.site.mobileView) { - element.classList.remove("hidden"); - return; - } - - this._popper = createPopper( - this.chatEmojiPickerManager.picker?.trigger, - element, - { - placement: "top", - modifiers: [ - { - name: "eventListeners", - options: { scroll: false, resize: false }, - }, - { - name: "flip", - options: { padding: { top: headerOffset() } }, - }, - ], - } - ); - - element.classList.remove("hidden"); - } - - -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs index 1cb2efe7895..cfef3ffdd4e 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel.gjs @@ -55,7 +55,6 @@ export default class ChatChannel extends Component { @service chatApi; @service chatChannelsManager; @service chatDraftsManager; - @service chatEmojiPickerManager; @service chatStateManager; @service chatChannelScrollPositions; @service("chat-channel-composer") composer; diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.gjs index e57e0e1d8c4..dc1e9cccfc7 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.gjs @@ -8,8 +8,9 @@ import DMenu from "float-kit/components/d-menu"; export default class ChatComposerDropdown extends Component { @action - onButtonClick(button, closeFn) { - closeFn({ focusTrigger: false }); + async onButtonClick(button, closeFn) { + await closeFn({ focusTrigger: false }); + button.action(); } @@ -27,6 +28,7 @@ export default class ChatComposerDropdown extends Component { @arrow={{true}} @placements={{array "top" "bottom"}} @identifier="chat-composer-dropdown__menu" + @modalForMobile={{true}} ...attributes as |menu| > @@ -39,6 +41,7 @@ export default class ChatComposerDropdown extends Component { @label={{button.label}} class={{concatClass "chat-composer-dropdown__action-btn" + "btn-transparent" button.id }} /> diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs index cffa9b679ae..23ab4d29a19 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs @@ -79,9 +79,13 @@ /> {{/each}} - {{/if}} + + {{#if this.site.desktopView}} - - \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js index 5dcc8ca5afa..5da6a1a5ec4 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js @@ -9,6 +9,7 @@ import $ from "jquery"; import { emojiSearch, isSkinTonableEmoji } from "pretty-text/emoji"; import { translations } from "pretty-text/emoji/data"; import { Promise } from "rsvp"; +import EmojiPickerDetached from "discourse/components/emoji-picker/detached"; import InsertHyperlink from "discourse/components/modal/insert-hyperlink"; import { SKIP } from "discourse/lib/autocomplete"; import { @@ -23,6 +24,8 @@ import { initUserStatusHtml, renderUserStatusHtml, } from "discourse/lib/user-status-on-autocomplete"; +import virtualElementFromTextRange from "discourse/lib/virtual-element-from-text-range"; +import { waitForClosedKeyboard } from "discourse/lib/wait-for-keyboard"; import { cloneJSON } from "discourse-common/lib/object"; import { findRawTemplate } from "discourse-common/lib/raw-templates"; import { i18n } from "discourse-i18n"; @@ -41,12 +44,12 @@ export default class ChatComposer extends Component { @service composerPresenceManager; @service chatComposerWarningsTracker; @service appEvents; - @service chatEmojiReactionStore; - @service chatEmojiPickerManager; + @service emojiStore; @service currentUser; @service chatApi; @service chatDraftsManager; @service modal; + @service menu; @tracked isFocused = false; @tracked inProgressUploadsCount = 0; @@ -375,14 +378,10 @@ export default class ChatComposer extends Component { @action onSelectEmoji(emoji) { - const code = `:${emoji}:`; - this.chatEmojiReactionStore.track(code); this.composer.textarea.emojiSelected(emoji); if (this.site.desktopView) { this.composer.focus(); - } else { - this.chatEmojiPickerManager.close(); } } @@ -490,16 +489,33 @@ export default class ChatComposer extends Component { return [matches[1]]; } }, - transformComplete: (v) => { + transformComplete: async (v) => { if (v.code) { - this.chatEmojiReactionStore.track(v.code); return `${v.code}:`; } else { $textarea.autocomplete({ cancel: true }); - this.chatEmojiPickerManager.open({ - context: this.context, - initialFilter: v.term, - }); + + const menuOptions = { + identifier: "emoji-picker", + groupIdentifier: "emoji-picker", + component: EmojiPickerDetached, + context: `channel_${this.args.channel.id}`, + modalForMobile: true, + data: { + didSelectEmoji: (emoji) => { + this.onSelectEmoji(emoji); + }, + term: v.term, + context: "chat", + }, + }; + + // Close the keyboard before showing the emoji picker + // it avoids a whole range of bugs on iOS + await waitForClosedKeyboard(this); + + const virtualElement = virtualElementFromTextRange(); + this.menuInstance = await this.menu.show(virtualElement, menuOptions); return ""; } }, @@ -529,8 +545,9 @@ export default class ChatComposer extends Component { } if (term === "") { - if (this.chatEmojiReactionStore.favorites.length) { - return resolve(this.chatEmojiReactionStore.favorites.slice(0, 5)); + const favorites = this.emojiStore.favoritesForContext("chat"); + if (favorites.length > 0) { + return resolve(favorites.slice(0, 5)); } else { return resolve([ "slight_smile", @@ -571,7 +588,7 @@ export default class ChatComposer extends Component { const options = emojiSearch(term, { maxResults: 5, - diversity: this.chatEmojiReactionStore.diversity, + diversity: this.emojiStore.diversity, exclude: emojiDenied, }); diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-emoji-picker.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-emoji-picker.hbs deleted file mode 100644 index 0c85fef2300..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-emoji-picker.hbs +++ /dev/null @@ -1,246 +0,0 @@ -{{! template-lint-disable no-invalid-interactive }} -{{! template-lint-disable no-nested-interactive }} -{{! template-lint-disable no-pointer-down-event-binding }} - -{{#if (eq this.chatEmojiPickerManager.picker.context @context)}} -
-
- - - -
- - {{#if this.chatEmojiPickerManager.sections.length}} - {{#if (eq this.filteredEmojis null)}} -
-
- - {{#each-in this.groups as |section emojis|}} - - {{#if (eq section "favorites")}} - {{replace-emoji ":star:"}} - {{else}} - - {{/if}} - - {{/each-in}} -
- {{/if}} - -
-
- {{#if (not-eq this.filteredEmojis null)}} -
- {{#each this.filteredEmojis as |emoji|}} - {{emoji.name}} - {{else}} -

- {{i18n "chat.emoji_picker.no_results"}} -

- {{/each}} -
- {{/if}} - - {{#each-in this.groups as |section emojis|}} -
-

- {{i18n - (concat "chat.emoji_picker." section) - translatedFallback=section - }} -

-
- {{! we always want the first emoji for tabbing}} - {{#let (get emojis "0") as |emoji|}} - {{emoji.name}} - {{/let}} - - {{#if - (includes this.chatEmojiPickerManager.visibleSections section) - }} - {{#each emojis as |emoji index|}} - {{! first emoji has already been rendered, we don't want to re render or would lose focus}} - {{#if (gt index 0)}} - {{emoji.name}} - {{/if}} - {{/each}} - {{/if}} -
-
- {{/each-in}} -
-
- {{else}} -
- {{/if}} -
- - {{#if - (and - this.site.mobileView - (eq this.chatEmojiPickerManager.picker.context "chat-channel-message") - ) - }} -
- {{/if}} -{{/if}} \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-emoji-picker.js b/plugins/chat/assets/javascripts/discourse/components/chat-emoji-picker.js deleted file mode 100644 index c64a3695c0c..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-emoji-picker.js +++ /dev/null @@ -1,424 +0,0 @@ -import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; -import { action } from "@ember/object"; -import { later, schedule } from "@ember/runloop"; -import { service } from "@ember/service"; -import { htmlSafe } from "@ember/template"; -import { emojiUrlFor } from "discourse/lib/text"; -import { INPUT_DELAY } from "discourse-common/config/environment"; -import discourseDebounce from "discourse-common/lib/debounce"; -import { bind } from "discourse-common/utils/decorators"; - -export const FITZPATRICK_MODIFIERS = [ - { - scale: 1, - modifier: null, - }, - { - scale: 2, - modifier: ":t2", - }, - { - scale: 3, - modifier: ":t3", - }, - { - scale: 4, - modifier: ":t4", - }, - { - scale: 5, - modifier: ":t5", - }, - { - scale: 6, - modifier: ":t6", - }, -]; - -export default class ChatEmojiPicker extends Component { - @service chatEmojiPickerManager; - @service emojiPickerScrollObserver; - @service chatEmojiReactionStore; - @service capabilities; - @service site; - - @tracked filteredEmojis = null; - @tracked isExpandedFitzpatrickScale = false; - - fitzpatrickModifiers = FITZPATRICK_MODIFIERS; - - get groups() { - const emojis = this.chatEmojiPickerManager.emojis; - const favorites = { - favorites: this.chatEmojiReactionStore.favorites - .filter((f) => !this.site.denied_emojis?.includes(f)) - .map((name) => { - return { - name, - group: "favorites", - url: emojiUrlFor(name), - }; - }), - }; - - return { - ...favorites, - ...emojis, - }; - } - - get flatEmojis() { - if (!this.chatEmojiPickerManager.emojis) { - return []; - } - - // eslint-disable-next-line no-unused-vars - let { favorites, ...rest } = this.chatEmojiPickerManager.emojis; - return Object.values(rest).flat(); - } - - get navIndicatorStyle() { - const section = this.chatEmojiPickerManager.lastVisibleSection; - const index = Object.keys(this.groups).indexOf(section); - - return htmlSafe( - `width: ${ - 100 / Object.keys(this.groups).length - }%; transform: translateX(${index * 100}%);` - ); - } - - get navBtnStyle() { - return htmlSafe(`width: ${100 / Object.keys(this.groups).length}%;`); - } - - @action - trapKeyDownEvents(event) { - if (event.key === "Escape") { - this.chatEmojiPickerManager.close(); - } - - if (event.key === "ArrowUp") { - event.stopPropagation(); - } - - if ( - event.key === "ArrowDown" && - event.target.classList.contains("dc-filter-input") - ) { - event.stopPropagation(); - event.preventDefault(); - - document - .querySelector( - `.chat-emoji-picker__scrollable-content .emoji[tabindex="0"]` - ) - ?.focus(); - } - } - - @action - didNavigateFitzpatrickScale(event) { - if (event.type !== "keyup") { - return; - } - - const scaleNodes = - event.target - .closest(".chat-emoji-picker__fitzpatrick-scale") - ?.querySelectorAll(".chat-emoji-picker__fitzpatrick-modifier-btn") || - []; - - const scales = [...scaleNodes]; - - if (event.key === "ArrowRight") { - event.preventDefault(); - - if (event.target === scales[scales.length - 1]) { - scales[0].focus(); - } else { - event.target.nextElementSibling?.focus(); - } - } - - if (event.key === "ArrowLeft") { - event.preventDefault(); - - if (event.target === scales[0]) { - scales[scales.length - 1].focus(); - } else { - event.target.previousElementSibling?.focus(); - } - } - } - - @action - didToggleFitzpatrickScale(event) { - if (event.type === "keyup") { - if (event.key === "Escape") { - event.preventDefault(); - this.isExpandedFitzpatrickScale = false; - return; - } - - if (event.key !== "Enter") { - return; - } - } - - this.isExpandedFitzpatrickScale = !this.isExpandedFitzpatrickScale; - } - - @action - didRequestFitzpatrickScale(scale, event) { - if (event.type === "keyup") { - if (event.key === "Escape") { - event.preventDefault(); - event.stopPropagation(); - this.isExpandedFitzpatrickScale = false; - this._focusCurrentFitzpatrickScale(); - return; - } - - if (event.key !== "Enter") { - return; - } - } - - event.preventDefault(); - event.stopPropagation(); - - this.isExpandedFitzpatrickScale = false; - this.chatEmojiReactionStore.diversity = scale; - this._focusCurrentFitzpatrickScale(); - } - - _focusCurrentFitzpatrickScale() { - schedule("afterRender", () => { - document - .querySelector(".chat-emoji-picker__fitzpatrick-modifier-btn.current") - ?.focus(); - }); - } - - @action - didInputFilter(value) { - if (!value?.length) { - this.filteredEmojis = null; - return; - } - - discourseDebounce(this, this.debouncedDidInputFilter, value, INPUT_DELAY); - } - - @action - focusFilter(target) { - schedule("afterRender", () => { - target?.focus({ preventScroll: true }); - }); - } - - debouncedDidInputFilter(filter = "") { - filter = filter.toLowerCase(); - - this.filteredEmojis = this.flatEmojis.filter( - (emoji) => - emoji.name.toLowerCase().includes(filter) || - emoji.search_aliases?.any((alias) => - alias.toLowerCase().includes(filter) - ) - ); - - schedule("afterRender", () => { - const scrollableContent = document.querySelector( - ".chat-emoji-picker__scrollable-content" - ); - - if (scrollableContent) { - scrollableContent.scrollTop = 0; - } - }); - } - - @action - onSectionsKeyDown(event) { - if (event.key === "Enter") { - this.didSelectEmoji(event); - } else { - this.didNavigateSection(event); - } - } - - @action - didNavigateSection(event) { - const sectionsEmojis = (section) => [...section.querySelectorAll(".emoji")]; - const focusSectionsLastEmoji = (section) => { - const emojis = sectionsEmojis(section); - return emojis[emojis.length - 1].focus(); - }; - const focusSectionsFirstEmoji = (section) => { - sectionsEmojis(section)[0].focus(); - }; - const currentSection = event.target.closest(".chat-emoji-picker__section"); - const focusFilter = () => { - document.querySelector(".dc-filter-input")?.focus(); - }; - const allEmojis = () => [ - ...document.querySelectorAll( - ".chat-emoji-picker__section:not(.hidden) .emoji" - ), - ]; - - if (event.key === "ArrowRight") { - event.preventDefault(); - const nextEmoji = event.target.nextElementSibling; - - if (nextEmoji) { - nextEmoji.focus(); - } else { - const nextSection = currentSection.nextElementSibling; - if (nextSection) { - focusSectionsFirstEmoji(nextSection); - } - } - } - - if (event.key === "ArrowLeft") { - event.preventDefault(); - const prevEmoji = event.target.previousElementSibling; - - if (prevEmoji) { - prevEmoji.focus(); - } else { - const prevSection = currentSection.previousElementSibling; - if (prevSection) { - focusSectionsLastEmoji(prevSection); - } else { - focusFilter(); - } - } - } - - if (event.key === "ArrowDown") { - event.preventDefault(); - event.stopPropagation(); - - const nextEmoji = allEmojis() - .filter((c) => c.offsetTop > event.target.offsetTop) - .findBy("offsetLeft", event.target.offsetLeft); - - if (nextEmoji) { - nextEmoji.focus(); - } else { - // for perf reason all emojis might not be loaded at this point - // but the first one will always be - const nextSection = currentSection.nextElementSibling; - if (nextSection) { - focusSectionsFirstEmoji(nextSection); - } - } - } - - if (event.key === "ArrowUp") { - event.preventDefault(); - event.stopPropagation(); - - const prevEmoji = allEmojis() - .reverse() - .filter((c) => c.offsetTop < event.target.offsetTop) - .findBy("offsetLeft", event.target.offsetLeft); - - if (prevEmoji) { - prevEmoji.focus(); - } else { - focusFilter(); - } - } - } - - @action - didSelectEmoji(event) { - if (!event.target.classList.contains("emoji")) { - return; - } - - if (event.type === "click" || event.key === "Enter") { - event.preventDefault(); - event.stopPropagation(); - let emoji = event.target.dataset.emoji; - const tonable = event.target.dataset.tonable; - const diversity = this.chatEmojiReactionStore.diversity; - if (tonable && diversity > 1) { - emoji = `${emoji}:t${diversity}`; - } - - this.args.didSelectEmoji?.(emoji); - } - } - - @action - didRequestSection(section) { - const scrollableContent = document.querySelector( - ".chat-emoji-picker__scrollable-content" - ); - - this.filteredEmojis = null; - - // we disable scroll listener during requesting section - // to avoid it from detecting another section during scroll to requested section - this.emojiPickerScrollObserver.enabled = false; - this.chatEmojiPickerManager.addVisibleSections([section]); - this.chatEmojiPickerManager.lastVisibleSection = section; - - // iOS hack to avoid blank div when requesting section during momentum - if (scrollableContent && this.capabilities.isIOS) { - document.querySelector( - ".chat-emoji-picker__scrollable-content" - ).style.overflow = "hidden"; - } - - schedule("afterRender", () => { - const firstEmoji = document.querySelector( - `.chat-emoji-picker__section[data-section="${section}"] .emoji:nth-child(1)` - ); - - const targetEmoji = - [ - ...document.querySelectorAll( - `.chat-emoji-picker__section[data-section="${section}"] .emoji` - ), - ].find((emoji) => emoji.offsetTop > firstEmoji.offsetTop) || firstEmoji; - - targetEmoji.focus(); - - later(() => { - // iOS hack to avoid blank div when requesting section during momentum - if (scrollableContent && this.capabilities.isIOS) { - document.querySelector( - ".chat-emoji-picker__scrollable-content" - ).style.overflow = "scroll"; - } - - this.emojiPickerScrollObserver.enabled = true; - }, 200); - }); - } - - @action - addClickOutsideEventListener() { - document.addEventListener("click", this.didClickOutside); - } - - @action - removeClickOutsideEventListener() { - document.removeEventListener("click", this.didClickOutside); - } - - @bind - didClickOutside(event) { - if (!event.target.closest(".chat-emoji-picker")) { - this.chatEmojiPickerManager.close(); - } - } -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.gjs index 26c4e31a6fb..8c269d5540c 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.gjs @@ -18,6 +18,7 @@ import DropdownSelectBox from "select-kit/components/dropdown-select-box"; import ChatMessageReaction from "discourse/plugins/chat/discourse/components/chat-message-reaction"; import chatMessageContainer from "discourse/plugins/chat/discourse/lib/chat-message-container"; import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor"; +import ChatMessageReactionModel from "discourse/plugins/chat/discourse/models/chat-message-reaction"; const MSG_ACTIONS_VERTICAL_PADDING = -10; const FULL = "full"; @@ -26,13 +27,24 @@ const REDUCED_WIDTH_THRESHOLD = 500; export default class ChatMessageActionsDesktop extends Component { @service chat; - @service chatEmojiPickerManager; @service site; + @service emojiStore; @tracked size = FULL; popper = null; + get favoriteReactions() { + return this.emojiStore + .favoritesForContext(`channel_${this.message.channel.id}`) + .slice(0, 3) + .map( + (emoji) => + this.message.reactions.find((reaction) => reaction.emoji === emoji) || + ChatMessageReactionModel.create({ emoji }) + ); + } + get message() { return this.chat.activeMessage.model; } @@ -53,6 +65,16 @@ export default class ChatMessageActionsDesktop extends Component { return this.size === FULL; } + get messageContainer() { + return chatMessageContainer(this.message.id, this.context); + } + + @action + openEmojiPicker(_, event) { + event.preventDefault(); + this.messageInteractor.openEmojiPicker(event.target); + } + @action onWheel() { // prevents menu to stop scroll on the list of messages @@ -64,24 +86,19 @@ export default class ChatMessageActionsDesktop extends Component { this.popper?.destroy(); schedule("afterRender", () => { - const messageContainer = chatMessageContainer( - this.message.id, - this.context - ); - - if (!messageContainer) { + if (!this.messageContainer) { return; } - const viewport = messageContainer.closest(".popper-viewport"); + const viewport = this.messageContainer.closest(".popper-viewport"); this.size = viewport.clientWidth < REDUCED_WIDTH_THRESHOLD ? REDUCED : FULL; - if (!messageContainer) { + if (!this.messageContainer) { return; } - this.popper = createPopper(messageContainer, element, { + this.popper = createPopper(this.messageContainer, element, { placement: "top-end", strategy: "fixed", modifiers: [ @@ -133,10 +150,7 @@ export default class ChatMessageActionsDesktop extends Component { }} > {{#if this.shouldRenderFavoriteReactions}} - {{#each - this.messageInteractor.emojiReactions key="emoji" - as |reaction| - }} + {{#each this.favoriteReactions as |reaction|}} {{/if}} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.gjs index 4b37aa8b26b..af5a679318c 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-mobile.gjs @@ -10,6 +10,7 @@ import { and, or } from "truth-helpers"; import BookmarkIcon from "discourse/components/bookmark-icon"; import DButton from "discourse/components/d-button"; import DModal from "discourse/components/d-modal"; +import EmojiPickerDetached from "discourse/components/emoji-picker/detached"; import concatClass from "discourse/helpers/concat-class"; import ChatMessageReaction from "discourse/plugins/chat/discourse/components/chat-message-reaction"; import ChatUserAvatar from "discourse/plugins/chat/discourse/components/chat-user-avatar"; @@ -19,6 +20,8 @@ export default class ChatMessageActionsMobile extends Component { @service chat; @service site; @service capabilities; + @service modal; + @service menu; @tracked hasExpandedReply = false; @@ -70,9 +73,19 @@ export default class ChatMessageActionsMobile extends Component { } @action - openEmojiPicker(_, event) { - this.args.closeModal(); - this.messageInteractor.openEmojiPicker(_, event); + async openEmojiPicker(_, event) { + await this.args.closeModal(); + + await this.menu.show(event.target, { + identifier: "emoji-picker", + groupIdentifier: "emoji-picker", + component: EmojiPickerDetached, + modalForMobile: true, + data: { + context: "chat", + didSelectEmoji: this.messageInteractor.selectReaction, + }, + }); }