about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/javascript/mastodon/emoji.js40
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js16
-rw-r--r--app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js8
-rw-r--r--app/javascript/mastodon/reducers/custom_emojis.js13
-rw-r--r--app/javascript/mastodon/reducers/index.js2
-rw-r--r--app/javascript/styles/components.scss6
-rw-r--r--app/serializers/initial_state_serializer.rb6
8 files changed, 91 insertions, 2 deletions
diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js
index 407b21b4b..39123768a 100644
--- a/app/javascript/mastodon/emoji.js
+++ b/app/javascript/mastodon/emoji.js
@@ -47,3 +47,43 @@ const emojify = (str, customEmojis = {}) => {
 };
 
 export default emojify;
+
+export const toCodePoint = (unicodeSurrogates, sep = '-') => {
+  let r = [], c = 0, p = 0, i = 0;
+
+  while (i < unicodeSurrogates.length) {
+    c = unicodeSurrogates.charCodeAt(i++);
+
+    if (p) {
+      r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
+      p = 0;
+    } else if (0xD800 <= c && c <= 0xDBFF) {
+      p = c;
+    } else {
+      r.push(c.toString(16));
+    }
+  }
+
+  return r.join(sep);
+};
+
+export const buildCustomEmojis = customEmojis => {
+  const emojis = [];
+
+  customEmojis.forEach(emoji => {
+    const shortcode = emoji.get('shortcode');
+    const url       = emoji.get('url');
+    const name      = shortcode.replace(':', '');
+
+    emojis.push({
+      name,
+      short_names: [name],
+      text: '',
+      emoticons: [],
+      keywords: [name],
+      imageUrl: url,
+    });
+  });
+
+  return emojis;
+};
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index bb747b611..d31041061 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -12,7 +12,7 @@ import Collapsable from '../../../components/collapsable';
 import SpoilerButtonContainer from '../containers/spoiler_button_container';
 import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
 import SensitiveButtonContainer from '../containers/sensitive_button_container';
-import EmojiPickerDropdown from './emoji_picker_dropdown';
+import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
 import UploadFormContainer from '../containers/upload_form_container';
 import WarningContainer from '../containers/warning_container';
 import { isMobile } from '../../../is_mobile';
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index 43e175be5..f55d59e03 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -4,6 +4,8 @@ import { defineMessages, injectIntl } from 'react-intl';
 import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
 import { Overlay } from 'react-overlays';
 import classNames from 'classnames';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { buildCustomEmojis } from '../../../emoji';
 
 const messages = defineMessages({
   emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
@@ -130,6 +132,7 @@ class ModifierPicker extends React.PureComponent {
 class EmojiPickerMenu extends React.PureComponent {
 
   static propTypes = {
+    custom_emojis: ImmutablePropTypes.list,
     loading: PropTypes.bool,
     onClose: PropTypes.func.isRequired,
     onPick: PropTypes.func.isRequired,
@@ -194,6 +197,10 @@ class EmojiPickerMenu extends React.PureComponent {
   }
 
   handleClick = emoji => {
+    if (!emoji.native) {
+      emoji.native = emoji.colons;
+    }
+
     this.props.onClose();
     this.props.onPick(emoji);
   }
@@ -225,6 +232,7 @@ class EmojiPickerMenu extends React.PureComponent {
     return (
       <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
         <EmojiPicker
+          custom={buildCustomEmojis(this.props.custom_emojis)}
           perLine={8}
           emojiSize={22}
           sheetSize={32}
@@ -255,6 +263,7 @@ class EmojiPickerMenu extends React.PureComponent {
 export default class EmojiPickerDropdown extends React.PureComponent {
 
   static propTypes = {
+    custom_emojis: ImmutablePropTypes.list,
     intl: PropTypes.object.isRequired,
     onPickEmoji: PropTypes.func.isRequired,
   };
@@ -328,7 +337,12 @@ export default class EmojiPickerDropdown extends React.PureComponent {
         </div>
 
         <Overlay show={active} placement='bottom' target={this.findTarget}>
-          <EmojiPickerMenu loading={loading} onClose={this.onHideDropdown} onPick={onPickEmoji} />
+          <EmojiPickerMenu
+            custom_emojis={this.props.custom_emojis}
+            loading={loading}
+            onClose={this.onHideDropdown}
+            onPick={onPickEmoji}
+          />
         </Overlay>
       </div>
     );
diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
new file mode 100644
index 000000000..7a8026bbc
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
@@ -0,0 +1,8 @@
+import { connect } from 'react-redux';
+import EmojiPickerDropdown from '../components/emoji_picker_dropdown';
+
+const mapStateToProps = state => ({
+  custom_emojis: state.get('custom_emojis'),
+});
+
+export default connect(mapStateToProps)(EmojiPickerDropdown);
diff --git a/app/javascript/mastodon/reducers/custom_emojis.js b/app/javascript/mastodon/reducers/custom_emojis.js
new file mode 100644
index 000000000..15bba7bcc
--- /dev/null
+++ b/app/javascript/mastodon/reducers/custom_emojis.js
@@ -0,0 +1,13 @@
+import { List as ImmutableList } from 'immutable';
+import { STORE_HYDRATE } from '../actions/store';
+
+const initialState = ImmutableList();
+
+export default function statuses(state = initialState, action) {
+  switch(action.type) {
+  case STORE_HYDRATE:
+    return action.state.get('custom_emojis');
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index 0a8c3ce6c..e65144871 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -20,6 +20,7 @@ import search from './search';
 import media_attachments from './media_attachments';
 import notifications from './notifications';
 import height_cache from './height_cache';
+import custom_emojis from './custom_emojis';
 
 const reducers = {
   timelines,
@@ -43,6 +44,7 @@ const reducers = {
   media_attachments,
   notifications,
   height_cache,
+  custom_emojis,
 };
 
 export default combineReducers(reducers);
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
index abf5cfd5b..595ab3658 100644
--- a/app/javascript/styles/components.scss
+++ b/app/javascript/styles/components.scss
@@ -2629,6 +2629,12 @@ button.icon-button.active i.fa-retweet {
   }
 }
 
+.emoji-mart-emoji {
+  span {
+    background-repeat: no-repeat;
+  }
+}
+
 .upload-area {
   align-items: center;
   background: rgba($base-overlay-background, 0.8);
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 88bbc0dff..e2f15a601 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -4,6 +4,12 @@ class InitialStateSerializer < ActiveModel::Serializer
   attributes :meta, :compose, :accounts,
              :media_attachments, :settings, :push_subscription
 
+  has_many :custom_emojis, serializer: REST::CustomEmojiSerializer
+
+  def custom_emojis
+    CustomEmoji.local
+  end
+
   def meta
     store = {
       streaming_api_base_url: Rails.configuration.x.streaming_api_base_url,