Maintain last-known cursor position for inserting emojis

This commit is contained in:
Ken Powers 2019-05-30 19:35:15 -04:00 committed by Scott Nonnenberg
parent 93335f8759
commit a934759e66
5 changed files with 81 additions and 19 deletions

View file

@ -1,11 +1,15 @@
const { take } = require('lodash'); const { take } = require('lodash');
const { getRecentEmojis } = require('./data'); const { getRecentEmojis } = require('./data');
const { replaceColons } = require('../../ts/components/emoji/lib'); const {
replaceColons,
hasVariation,
} = require('../../ts/components/emoji/lib');
module.exports = { module.exports = {
getInitialState, getInitialState,
load, load,
replaceColons, replaceColons,
hasVariation,
}; };
let initialState = null; let initialState = null;

View file

@ -278,6 +278,12 @@
this.setupEmojiPickerButton(); this.setupEmojiPickerButton();
this.setupStickerPickerButton(); this.setupStickerPickerButton();
this.lastSelectionStart = 0;
document.addEventListener(
'selectionchange',
this.updateLastSelectionStart.bind(this, undefined)
);
}, },
events: { events: {
@ -313,7 +319,17 @@
setupEmojiPickerButton() { setupEmojiPickerButton() {
const props = { const props = {
onPickEmoji: e => this.insertEmoji(e), 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({ this.emojiButtonView = new Whisper.ReactWrapperView({
@ -447,6 +463,10 @@
this.window.removeEventListener('resize', this.onResize); this.window.removeEventListener('resize', this.onResize);
this.window.removeEventListener('focus', this.onFocus); this.window.removeEventListener('focus', this.onFocus);
document.removeEventListener(
'selectionchange',
this.updateLastSelectionStart
);
window.autosize.destroy(this.$messageField); window.autosize.destroy(this.$messageField);
@ -1052,6 +1072,7 @@
focusMessageFieldAndClearDisabled() { focusMessageFieldAndClearDisabled() {
this.$messageField.removeAttr('disabled'); this.$messageField.removeAttr('disabled');
this.$messageField.focus(); this.$messageField.focus();
this.updateLastSelectionStart();
}, },
async loadMoreMessages() { async loadMoreMessages() {
@ -1697,24 +1718,31 @@
}, },
insertEmoji({ shortName, skinTone }) { insertEmoji({ shortName, skinTone }) {
const colons = `:${shortName}:${ const skinReplacement = window.Signal.Emojis.hasVariation(
skinTone ? `:skin-tone-${skinTone}:` : '' shortName,
}`; skinTone
)
? `:skin-tone-${skinTone}:`
: '';
const colons = `:${shortName}:${skinReplacement}`;
const textarea = this.$messageField[0]; const textarea = this.$messageField[0];
if (textarea.selectionStart || textarea.selectionStart === 0) { const hasFocus = document.activeElement === textarea;
const startPos = textarea.selectionStart; const startPos = hasFocus
const endPos = textarea.selectionEnd; ? textarea.selectionStart
: this.lastSelectionStart;
const endPos = hasFocus ? textarea.selectionEnd : this.lastSelectionStart;
textarea.value = textarea.value =
textarea.value.substring(0, startPos) + textarea.value.substring(0, startPos) +
colons + colons +
textarea.value.substring(endPos, textarea.value.length); textarea.value.substring(endPos, textarea.value.length);
textarea.selectionStart = startPos + colons.length; const newPos = startPos + colons.length;
textarea.selectionEnd = startPos + colons.length; textarea.selectionStart = newPos;
} else { textarea.selectionEnd = newPos;
textarea.value += colons; this.forceUpdateLastSelectionStart(newPos);
} this.forceUpdateMessageFieldSize({});
}, },
async setQuoteMessage(messageId) { async setQuoteMessage(messageId) {
@ -1853,6 +1881,18 @@
this.debouncedMaybeGrabLinkPreview(); this.debouncedMaybeGrabLinkPreview();
}, },
updateLastSelectionStart(newPos) {
if (document.activeElement === this.$messageField[0]) {
this.forceUpdateLastSelectionStart(newPos);
}
},
forceUpdateLastSelectionStart(
newPos = this.$messageField[0].selectionStart
) {
this.lastSelectionStart = newPos;
},
maybeGrabLinkPreview() { maybeGrabLinkPreview() {
// Don't generate link previews if user has turned them off // Don't generate link previews if user has turned them off
if (!storage.get('linkPreviews', false)) { if (!storage.get('linkPreviews', false)) {

View file

@ -292,7 +292,6 @@
color: $color-light-90; color: $color-light-90;
border: 1px solid rgba(0, 0, 0, 0.2); border: 1px solid rgba(0, 0, 0, 0.2);
outline: 0; outline: 0;
z-index: 5;
resize: none; resize: none;
font-size: 1em; font-size: 1em;
font-family: inherit; font-family: inherit;

View file

@ -158,6 +158,25 @@ export function unifiedToEmoji(unified: string) {
.join(''); .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) { export function convertShortName(shortName: string, skinTone: number = 0) {
const base = dataByShortName[shortName]; const base = dataByShortName[shortName];

View file

@ -231,7 +231,7 @@
"rule": "jQuery-load(", "rule": "jQuery-load(",
"path": "js/modules/emojis.js", "path": "js/modules/emojis.js",
"line": "async function load() {", "line": "async function load() {",
"lineNumber": 13, "lineNumber": 17,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2019-05-23T22:27:53.554Z" "updated": "2019-05-23T22:27:53.554Z"
}, },