diff --git a/js/modules/emojis.js b/js/modules/emojis.js index 47730c2f335..c4220f1cbb3 100644 --- a/js/modules/emojis.js +++ b/js/modules/emojis.js @@ -1,11 +1,15 @@ const { take } = require('lodash'); const { getRecentEmojis } = require('./data'); -const { replaceColons } = require('../../ts/components/emoji/lib'); +const { + replaceColons, + hasVariation, +} = require('../../ts/components/emoji/lib'); module.exports = { getInitialState, load, replaceColons, + hasVariation, }; let initialState = null; diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 1234f95d8c2..53d2831cb0c 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -278,6 +278,12 @@ this.setupEmojiPickerButton(); this.setupStickerPickerButton(); + + this.lastSelectionStart = 0; + document.addEventListener( + 'selectionchange', + this.updateLastSelectionStart.bind(this, undefined) + ); }, events: { @@ -313,7 +319,17 @@ setupEmojiPickerButton() { const props = { onPickEmoji: e => this.insertEmoji(e), - onClose: () => this.focusMessageField(), + onClose: () => { + const textarea = this.$messageField[0]; + + textarea.focus(); + + const newPos = textarea.value.length; + textarea.selectionStart = newPos; + textarea.selectionEnd = newPos; + + this.forceUpdateLastSelectionStart(newPos); + }, }; this.emojiButtonView = new Whisper.ReactWrapperView({ @@ -447,6 +463,10 @@ this.window.removeEventListener('resize', this.onResize); this.window.removeEventListener('focus', this.onFocus); + document.removeEventListener( + 'selectionchange', + this.updateLastSelectionStart + ); window.autosize.destroy(this.$messageField); @@ -1052,6 +1072,7 @@ focusMessageFieldAndClearDisabled() { this.$messageField.removeAttr('disabled'); this.$messageField.focus(); + this.updateLastSelectionStart(); }, async loadMoreMessages() { @@ -1697,24 +1718,31 @@ }, insertEmoji({ shortName, skinTone }) { - const colons = `:${shortName}:${ - skinTone ? `:skin-tone-${skinTone}:` : '' - }`; + const skinReplacement = window.Signal.Emojis.hasVariation( + shortName, + skinTone + ) + ? `:skin-tone-${skinTone}:` + : ''; + + const colons = `:${shortName}:${skinReplacement}`; const textarea = this.$messageField[0]; - if (textarea.selectionStart || textarea.selectionStart === 0) { - const startPos = textarea.selectionStart; - const endPos = textarea.selectionEnd; + const hasFocus = document.activeElement === textarea; + const startPos = hasFocus + ? textarea.selectionStart + : this.lastSelectionStart; + const endPos = hasFocus ? textarea.selectionEnd : this.lastSelectionStart; - textarea.value = - textarea.value.substring(0, startPos) + - colons + - textarea.value.substring(endPos, textarea.value.length); - textarea.selectionStart = startPos + colons.length; - textarea.selectionEnd = startPos + colons.length; - } else { - textarea.value += colons; - } + textarea.value = + textarea.value.substring(0, startPos) + + colons + + textarea.value.substring(endPos, textarea.value.length); + const newPos = startPos + colons.length; + textarea.selectionStart = newPos; + textarea.selectionEnd = newPos; + this.forceUpdateLastSelectionStart(newPos); + this.forceUpdateMessageFieldSize({}); }, async setQuoteMessage(messageId) { @@ -1853,6 +1881,18 @@ this.debouncedMaybeGrabLinkPreview(); }, + updateLastSelectionStart(newPos) { + if (document.activeElement === this.$messageField[0]) { + this.forceUpdateLastSelectionStart(newPos); + } + }, + + forceUpdateLastSelectionStart( + newPos = this.$messageField[0].selectionStart + ) { + this.lastSelectionStart = newPos; + }, + maybeGrabLinkPreview() { // Don't generate link previews if user has turned them off if (!storage.get('linkPreviews', false)) { diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 965080574d3..912d16d7ba2 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -292,7 +292,6 @@ color: $color-light-90; border: 1px solid rgba(0, 0, 0, 0.2); outline: 0; - z-index: 5; resize: none; font-size: 1em; font-family: inherit; diff --git a/ts/components/emoji/lib.ts b/ts/components/emoji/lib.ts index bf869c971cd..c16d841fbbd 100644 --- a/ts/components/emoji/lib.ts +++ b/ts/components/emoji/lib.ts @@ -158,6 +158,25 @@ export function unifiedToEmoji(unified: string) { .join(''); } +export function hasVariation(shortName: string, skinTone: number = 0) { + if (skinTone === 0) { + return false; + } + + const base = dataByShortName[shortName]; + if (!base) { + return false; + } + + if (skinTone > 0 && base.skin_variations) { + const toneKey = skinTones[skinTone - 1]; + + return Boolean(base.skin_variations[toneKey]); + } + + return false; +} + export function convertShortName(shortName: string, skinTone: number = 0) { const base = dataByShortName[shortName]; diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 3f2bb1a82f1..95be87139af 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -231,7 +231,7 @@ "rule": "jQuery-load(", "path": "js/modules/emojis.js", "line": "async function load() {", - "lineNumber": 13, + "lineNumber": 17, "reasonCategory": "falseMatch", "updated": "2019-05-23T22:27:53.554Z" },