Fuzzy-Searchable Emoji Picker
This commit is contained in:
parent
2f47a3570b
commit
0e9d549cf3
48 changed files with 1697 additions and 280 deletions
|
@ -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
|
||||
|
|
5
js/modules/data.d.ts
vendored
5
js/modules/data.d.ts
vendored
|
@ -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 }>>;
|
||||
|
|
|
@ -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
28
js/modules/emojis.js
Normal 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;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue