- Use unicode when selecting emoji through picker - Convert shortcodes to unicode when storing text input server-side - Do not convert shortcodes in JS anymore
This commit is contained in:
parent
c42092ba7a
commit
e2685ccc81
15 changed files with 69 additions and 71 deletions
19
app/helpers/emoji_helper.rb
Normal file
19
app/helpers/emoji_helper.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module EmojiHelper
|
||||
EMOJI_PATTERN = /(?<=[^[:alnum:]:]|\n|^):([\w+-]+):(?=[^[:alnum:]:]|$)/x
|
||||
|
||||
def emojify(text)
|
||||
return text if text.blank?
|
||||
|
||||
text.gsub(EMOJI_PATTERN) do |match|
|
||||
emoji = Emoji.find_by_alias($1) # rubocop:disable Rails/DynamicFindBy,Style/PerlBackrefs
|
||||
|
||||
if emoji
|
||||
emoji.raw
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,8 +2,6 @@ import api from '../api';
|
|||
|
||||
import { updateTimeline } from './timelines';
|
||||
|
||||
import * as emojione from 'emojione';
|
||||
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||
|
@ -73,11 +71,14 @@ export function mentionCompose(account, router) {
|
|||
|
||||
export function submitCompose() {
|
||||
return function (dispatch, getState) {
|
||||
const status = emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], ''));
|
||||
const status = getState().getIn(['compose', 'text'], '');
|
||||
|
||||
if (!status || !status.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(submitComposeRequest());
|
||||
|
||||
api(getState).post('/api/v1/statuses', {
|
||||
status,
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
|
|
|
@ -6,36 +6,18 @@ const trie = new Trie(Object.keys(emojione.jsEscapeMap));
|
|||
|
||||
function emojify(str) {
|
||||
// This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
|
||||
// and replacing valid shortnames like :smile: and :wink: as well as unicode strings
|
||||
// and replacing valid unicode strings
|
||||
// that _aren't_ within tags with an <img> version.
|
||||
// The goal is to be the same as an emojione.regShortNames/regUnicode replacement, but faster.
|
||||
// The goal is to be the same as an emojione.regUnicode replacement, but faster.
|
||||
let i = -1;
|
||||
let insideTag = false;
|
||||
let insideShortname = false;
|
||||
let shortnameStartIndex = -1;
|
||||
let match;
|
||||
while (++i < str.length) {
|
||||
const char = str.charAt(i);
|
||||
if (insideShortname && char === ':') {
|
||||
const shortname = str.substring(shortnameStartIndex, i + 1);
|
||||
if (shortname in emojione.emojioneList) {
|
||||
const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1];
|
||||
const alt = emojione.convert(unicode.toUpperCase());
|
||||
const replacement = `<img draggable="false" class="emojione" alt="${alt}" title="${shortname}" src="/emoji/${unicode}.svg" />`;
|
||||
str = str.substring(0, shortnameStartIndex) + replacement + str.substring(i + 1);
|
||||
i += (replacement.length - shortname.length - 1); // jump ahead the length we've added to the string
|
||||
} else {
|
||||
i--; // stray colon, try again
|
||||
}
|
||||
insideShortname = false;
|
||||
} else if (insideTag && char === '>') {
|
||||
if (insideTag && char === '>') {
|
||||
insideTag = false;
|
||||
} else if (char === '<') {
|
||||
insideTag = true;
|
||||
insideShortname = false;
|
||||
} else if (!insideTag && char === ':') {
|
||||
insideShortname = true;
|
||||
shortnameStartIndex = i;
|
||||
} else if (!insideTag && (match = trie.search(str.substring(i)))) {
|
||||
const unicodeStr = match;
|
||||
if (unicodeStr in emojione.jsEscapeMap) {
|
||||
|
|
|
@ -136,7 +136,8 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
handleEmojiPick = (data) => {
|
||||
const position = this.autosuggestTextarea.textarea.selectionStart;
|
||||
this._restoreCaret = position + data.shortname.length + 1;
|
||||
const emojiChar = String.fromCodePoint(parseInt(data.unicode, 16));
|
||||
this._restoreCaret = position + emojiChar.length + 1;
|
||||
this.props.onPickEmoji(position, data);
|
||||
}
|
||||
|
||||
|
|
|
@ -109,11 +109,12 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
|||
<Dropdown ref={this.setRef} className='emoji-picker__dropdown' onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
|
||||
<DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)}>
|
||||
<img
|
||||
draggable='false'
|
||||
className={`emojione ${active && loading ? 'pulse-loading' : ''}`}
|
||||
alt='🙂' src='/emoji/1f602.svg'
|
||||
alt='🙂'
|
||||
src='/emoji/1f602.svg'
|
||||
/>
|
||||
</DropdownTrigger>
|
||||
|
||||
<DropdownContent className='dropdown__left'>
|
||||
{
|
||||
this.state.active && !this.state.loading &&
|
||||
|
|
|
@ -118,7 +118,7 @@ const insertSuggestion = (state, position, token, completion) => {
|
|||
};
|
||||
|
||||
const insertEmoji = (state, position, emojiData) => {
|
||||
const emoji = emojiData.shortname;
|
||||
const emoji = String.fromCodePoint(parseInt(emojiData.unicode, 16));
|
||||
|
||||
return state.withMutations(map => {
|
||||
map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
|
||||
|
|
|
@ -2708,6 +2708,7 @@ button.icon-button.active i.fa-retweet {
|
|||
margin-left: 2px;
|
||||
width: 24px;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
|
|
|
@ -47,6 +47,7 @@ class Account < ApplicationRecord
|
|||
include AccountInteractions
|
||||
include Attachmentable
|
||||
include Remotable
|
||||
include EmojiHelper
|
||||
|
||||
# Local users
|
||||
has_one :user, inverse_of: :account
|
||||
|
@ -240,9 +241,18 @@ class Account < ApplicationRecord
|
|||
|
||||
before_create :generate_keys
|
||||
before_validation :normalize_domain
|
||||
before_validation :prepare_contents, if: :local?
|
||||
|
||||
private
|
||||
|
||||
def prepare_contents
|
||||
display_name&.strip!
|
||||
note&.strip!
|
||||
|
||||
self.display_name = emojify(display_name)
|
||||
self.note = emojify(note)
|
||||
end
|
||||
|
||||
def generate_keys
|
||||
return unless local?
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ class Status < ApplicationRecord
|
|||
include Streamable
|
||||
include Cacheable
|
||||
include StatusThreadingConcern
|
||||
include EmojiHelper
|
||||
|
||||
enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility
|
||||
|
||||
|
@ -120,7 +121,7 @@ class Status < ApplicationRecord
|
|||
!sensitive? && media_attachments.any?
|
||||
end
|
||||
|
||||
before_validation :prepare_contents
|
||||
before_validation :prepare_contents, if: :local?
|
||||
before_validation :set_reblog
|
||||
before_validation :set_visibility
|
||||
before_validation :set_conversation
|
||||
|
@ -241,6 +242,9 @@ class Status < ApplicationRecord
|
|||
def prepare_contents
|
||||
text&.strip!
|
||||
spoiler_text&.strip!
|
||||
|
||||
self.text = emojify(text)
|
||||
self.spoiler_text = emojify(spoiler_text)
|
||||
end
|
||||
|
||||
def set_reblog
|
||||
|
|
|
@ -21,6 +21,7 @@ class PostStatusService < BaseService
|
|||
|
||||
media = validate_media!(options[:media_ids])
|
||||
status = nil
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
status = account.statuses.create!(text: text,
|
||||
thread: in_reply_to,
|
||||
|
@ -31,6 +32,7 @@ class PostStatusService < BaseService
|
|||
application: options[:application])
|
||||
attach_media(status, media)
|
||||
end
|
||||
|
||||
process_mentions_service.call(status)
|
||||
process_hashtags_service.call(status)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue