Fuzzy-Searchable Emoji Picker

This commit is contained in:
Ken Powers 2019-05-24 16:58:27 -07:00 committed by Scott Nonnenberg
parent 2f47a3570b
commit 0e9d549cf3
48 changed files with 1697 additions and 280 deletions

View file

@ -434,6 +434,7 @@
await Promise.all([
ConversationController.load(),
Signal.Stickers.load(),
Signal.Emojis.load(),
textsecure.storage.protocol.hydrateCaches(),
]);
} catch (error) {
@ -457,6 +458,7 @@
conversations: {
conversationLookup: Signal.Util.makeLookup(conversations, 'id'),
},
emojis: Signal.Emojis.getInitialState(),
items: storage.getItemsState(),
stickers: Signal.Stickers.getInitialState(),
user: {
@ -480,6 +482,10 @@
Signal.State.Ducks.conversations.actions,
store.dispatch
);
actions.emojis = Signal.State.bindActionCreators(
Signal.State.Ducks.emojis.actions,
store.dispatch
);
actions.items = Signal.State.bindActionCreators(
Signal.State.Ducks.items.actions,
store.dispatch

View file

@ -18,3 +18,8 @@ export function getRecentStickers(): Promise<
packId: string;
}>
>;
export function updateEmojiUsage(shortName: string): Promise<void>;
export function getRecentEmojis(
limit: number
): Promise<Array<{ shortName: string; lastUsage: string }>>;

View file

@ -150,6 +150,9 @@ module.exports = {
getAllStickers,
getRecentStickers,
updateEmojiUsage,
getRecentEmojis,
removeAll,
removeAllConfiguration,
@ -934,6 +937,14 @@ async function getRecentStickers() {
return recentStickers;
}
// Emojis
async function updateEmojiUsage(shortName) {
await channels.updateEmojiUsage(shortName);
}
async function getRecentEmojis(limit = 32) {
return channels.getRecentEmojis(limit);
}
// Other
async function removeAll() {

28
js/modules/emojis.js Normal file
View file

@ -0,0 +1,28 @@
const { take } = require('lodash');
const { getRecentEmojis } = require('./data');
const { replaceColons } = require('../../ts/components/emoji/lib');
module.exports = {
getInitialState,
load,
replaceColons,
};
let initialState = null;
async function load() {
const recents = await getRecentEmojisForRedux();
initialState = {
recents: take(recents, 32),
};
}
async function getRecentEmojisForRedux() {
const recent = await getRecentEmojis();
return recent.map(e => e.shortName);
}
function getInitialState() {
return initialState;
}

View file

@ -5,6 +5,7 @@ const Backbone = require('../../ts/backbone');
const Crypto = require('./crypto');
const Data = require('./data');
const Database = require('./database');
const Emojis = require('./emojis');
const Emoji = require('../../ts/util/emoji');
const IndexedDB = require('./indexeddb');
const Notifications = require('../../ts/notifications');
@ -69,6 +70,7 @@ const {
} = require('../../ts/components/conversation/VerificationNotification');
// State
const { createEmojiButton } = require('../../ts/state/roots/createEmojiButton');
const { createLeftPane } = require('../../ts/state/roots/createLeftPane');
const {
createStickerButton,
@ -82,6 +84,7 @@ const {
const { createStore } = require('../../ts/state/createStore');
const conversationsDuck = require('../../ts/state/ducks/conversations');
const emojisDuck = require('../../ts/state/ducks/emojis');
const itemsDuck = require('../../ts/state/ducks/items');
const stickersDuck = require('../../ts/state/ducks/stickers');
const userDuck = require('../../ts/state/ducks/user');
@ -262,6 +265,7 @@ exports.setup = (options = {}) => {
};
const Roots = {
createEmojiButton,
createLeftPane,
createStickerButton,
createStickerManager,
@ -269,6 +273,7 @@ exports.setup = (options = {}) => {
};
const Ducks = {
conversations: conversationsDuck,
emojis: emojisDuck,
items: itemsDuck,
user: userDuck,
stickers: stickersDuck,
@ -308,6 +313,7 @@ exports.setup = (options = {}) => {
Crypto,
Data,
Database,
Emojis,
Emoji,
IndexedDB,
LinkPreviews,

View file

@ -2,8 +2,6 @@
$,
_,
ConversationController
emojiData,
EmojiPanel,
extension,
i18n,
Signal,
@ -278,23 +276,21 @@
this.$('.send-message').focus(this.focusBottomBar.bind(this));
this.$('.send-message').blur(this.unfocusBottomBar.bind(this));
this.$emojiPanelContainer = this.$('.emoji-panel-container');
this.setupEmojiPickerButton();
this.setupStickerPickerButton();
},
events: {
keydown: 'onKeyDown',
'submit .send': 'clickSend',
'input .send-message': 'updateMessageFieldSize',
'keydown .send-message': 'updateMessageFieldSize',
'keyup .send-message': 'onKeyUp',
click: 'onClick',
'click .sticker-button-placeholder': 'onClickStickerButtonPlaceholder',
'click .emoji-button-placeholder': 'onClickPlaceholder',
'click .sticker-button-placeholder': 'onClickPlaceholder',
'click .bottom-bar': 'focusMessageField',
'click .capture-audio .microphone': 'captureAudio',
'click .module-scroll-down': 'scrollToBottom',
'click button.emoji': 'toggleEmojiPanel',
'focus .send-message': 'focusBottomBar',
'change .file-input': 'toggleMicrophone',
'blur .send-message': 'unfocusBottomBar',
@ -314,6 +310,20 @@
paste: 'onPaste',
},
setupEmojiPickerButton() {
const props = {
onPickEmoji: e => this.insertEmoji(e),
};
this.emojiButtonView = new Whisper.ReactWrapperView({
className: 'emoji-button-wrapper',
JSX: Signal.State.Roots.createEmojiButton(window.reduxStore, props),
});
// Finally, add it to the DOM
this.$('.emoji-button-placeholder').append(this.emojiButtonView.el);
},
setupStickerPickerButton() {
if (!window.ENABLE_STICKER_SEND) {
return;
@ -334,9 +344,9 @@
this.$('.sticker-button-placeholder').append(this.stickerButtonView.el);
},
// We need this, or clicking the sticker button will submit the form and send any
// We need this, or clicking the reactified buttons will submit the form and send any
// mid-composition message content.
onClickStickerButtonPlaceholder(e) {
onClickPlaceholder(e) {
e.preventDefault();
},
@ -1684,40 +1694,10 @@
return null;
},
toggleEmojiPanel(e) {
e.preventDefault();
if (!this.emojiPanel) {
this.openEmojiPanel();
} else {
this.closeEmojiPanel();
}
},
onKeyDown(event) {
if (event.key !== 'Escape') {
return;
}
this.closeEmojiPanel();
},
openEmojiPanel() {
this.$emojiPanelContainer.outerHeight(200);
this.emojiPanel = new EmojiPanel(this.$emojiPanelContainer[0], {
onClick: this.insertEmoji.bind(this),
});
this.view.resetScrollPosition();
this.updateMessageFieldSize({});
},
closeEmojiPanel() {
if (this.emojiPanel === null) {
return;
}
this.$emojiPanelContainer.empty().outerHeight(0);
this.emojiPanel = null;
this.view.resetScrollPosition();
this.updateMessageFieldSize({});
},
insertEmoji(e) {
const colons = `:${emojiData[e.index].short_name}:`;
insertEmoji({ shortName, skinTone }) {
const colons = `:${shortName}:${
skinTone ? `:skin-tone-${skinTone}:` : ''
}`;
const textarea = this.$messageField[0];
if (textarea.selectionStart || textarea.selectionStart === 0) {
@ -1806,11 +1786,10 @@
async sendMessage(e) {
this.removeLastSeenIndicator();
this.closeEmojiPanel();
this.model.clearTypingTimers();
const input = this.$messageField;
const message = window.Signal.Emoji.replaceColons(input.val()).trim();
const message = window.Signal.Emojis.replaceColons(input.val()).trim();
let toast;
if (extension.expired()) {
@ -2283,7 +2262,6 @@
const height =
this.$messageField.outerHeight() +
$attachmentPreviews.outerHeight() +
this.$emojiPanelContainer.outerHeight() +
quoteHeight +
parseInt($bottomBar.css('min-height'), 10);