~cytrogen/masto-fe

e64e6a03dd1e0978fee48f0596dcfbc7fd29958f — Eugen Rochko 6 years ago 072158e
Add categories for custom emojis (#11196)

Fix #7940
M app/controllers/api/v1/custom_emojis_controller.rb => app/controllers/api/v1/custom_emojis_controller.rb +1 -1
@@ 7,7 7,7 @@ class Api::V1::CustomEmojisController < Api::BaseController

  def index
    render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
      ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
      ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false).includes(:category), each_serializer: REST::CustomEmojiSerializer)
    end
  end
end

M app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js => app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +16 -14
@@ 6,7 6,7 @@ import Overlay from 'react-overlays/lib/Overlay';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import detectPassiveEvents from 'detect-passive-events';
import { buildCustomEmojis } from '../../emoji/emoji';
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';

const messages = defineMessages({
  emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },


@@ 31,19 31,6 @@ let EmojiPicker, Emoji; // load asynchronously
const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;

const categoriesSort = [
  'recent',
  'custom',
  'people',
  'nature',
  'foods',
  'activity',
  'places',
  'objects',
  'symbols',
  'flags',
];

class ModifierPickerMenu extends React.PureComponent {

  static propTypes = {


@@ 241,8 228,23 @@ class EmojiPickerMenu extends React.PureComponent {
    }

    const title = intl.formatMessage(messages.emoji);

    const { modifierOpen } = this.state;

    const categoriesSort = [
      'recent',
      'people',
      'nature',
      'foods',
      'activity',
      'places',
      'objects',
      'symbols',
      'flags',
    ];

    categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort());

    return (
      <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
        <EmojiPicker

M app/javascript/mastodon/features/emoji/emoji.js => app/javascript/mastodon/features/emoji/emoji.js +3 -0
@@ 92,8 92,11 @@ export const buildCustomEmojis = (customEmojis) => {
      keywords: [name],
      imageUrl: url,
      custom: true,
      customCategory: emoji.get('category'),
    });
  });

  return emojis;
};

export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set());

M app/models/custom_emoji.rb => app/models/custom_emoji.rb +2 -0
@@ 16,6 16,7 @@
#  uri                :string
#  image_remote_url   :string
#  visible_in_picker  :boolean          default(TRUE), not null
#  category_id        :bigint(8)
#

class CustomEmoji < ApplicationRecord


@@ 27,6 28,7 @@ class CustomEmoji < ApplicationRecord
    :(#{SHORTCODE_RE_FRAGMENT}):
    (?=[^[:alnum:]:]|$)/x

  belongs_to :category, class_name: 'CustomEmojiCategory', optional: true
  has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode

  has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce -strip' } }

A app/models/custom_emoji_category.rb => app/models/custom_emoji_category.rb +15 -0
@@ 0,0 1,15 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: custom_emoji_categories
#
#  id         :bigint(8)        not null, primary key
#  name       :string
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class CustomEmojiCategory < ApplicationRecord
  has_many :emojis, class_name: 'CustomEmoji', foreign_key: 'category_id', inverse_of: :category
end

M app/serializers/rest/custom_emoji_serializer.rb => app/serializers/rest/custom_emoji_serializer.rb +10 -0
@@ 5,6 5,8 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer

  attributes :shortcode, :url, :static_url, :visible_in_picker

  attribute :category, if: :category_loaded?

  def url
    full_asset_url(object.image.url)
  end


@@ 12,4 14,12 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer
  def static_url
    full_asset_url(object.image.url(:static))
  end

  def category
    object.category.name
  end

  def category_loaded?
    object.association(:category).loaded? && object.category.present?
  end
end

A db/migrate/20190627222225_create_custom_emoji_categories.rb => db/migrate/20190627222225_create_custom_emoji_categories.rb +9 -0
@@ 0,0 1,9 @@
class CreateCustomEmojiCategories < ActiveRecord::Migration[5.2]
  def change
    create_table :custom_emoji_categories do |t|
      t.string :name, index: { unique: true }

      t.timestamps
    end
  end
end

A db/migrate/20190627222826_add_category_id_to_custom_emojis.rb => db/migrate/20190627222826_add_category_id_to_custom_emojis.rb +5 -0
@@ 0,0 1,5 @@
class AddCategoryIdToCustomEmojis < ActiveRecord::Migration[5.2]
  def change
    add_column :custom_emojis, :category_id, :bigint
  end
end

M db/schema.rb => db/schema.rb +9 -1
@@ 10,7 10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_05_29_143559) do
ActiveRecord::Schema.define(version: 2019_06_27_222826) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"


@@ 208,6 208,13 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
    t.index ["uri"], name: "index_conversations_on_uri", unique: true
  end

  create_table "custom_emoji_categories", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["name"], name: "index_custom_emoji_categories_on_name", unique: true
  end

  create_table "custom_emojis", force: :cascade do |t|
    t.string "shortcode", default: "", null: false
    t.string "domain"


@@ 221,6 228,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
    t.string "uri"
    t.string "image_remote_url"
    t.boolean "visible_in_picker", default: true, null: false
    t.bigint "category_id"
    t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
  end


M lib/mastodon/emoji_cli.rb => lib/mastodon/emoji_cli.rb +6 -0
@@ 15,6 15,7 @@ module Mastodon
    option :suffix
    option :overwrite, type: :boolean
    option :unlisted, type: :boolean
    option :category
    desc 'import PATH', 'Import emoji from a TAR GZIP archive at PATH'
    long_desc <<-LONG_DESC
      Imports custom emoji from a TAR GZIP archive specified by PATH.


@@ 22,6 23,9 @@ module Mastodon
      Existing emoji will be skipped unless the --overwrite option
      is provided, in which case they will be overwritten.

      You can specifiy a --category under which the emojis will be
      grouped together.

      With the --prefix option, a prefix can be added to all
      generated shortcodes. Likewise, the --suffix option controls
      the suffix of all shortcodes.


@@ 33,6 37,7 @@ module Mastodon
      imported = 0
      skipped  = 0
      failed   = 0
      category = options[:category] ? CustomEmojiCategory.find_or_create_by(name: options[:category]) : nil

      Gem::Package::TarReader.new(Zlib::GzipReader.open(path)) do |tar|
        tar.each do |entry|


@@ 50,6 55,7 @@ module Mastodon
          custom_emoji.image = StringIO.new(entry.read)
          custom_emoji.image_file_name = File.basename(entry.full_name)
          custom_emoji.visible_in_picker = !options[:unlisted]
          custom_emoji.category = category

          if custom_emoji.save
            imported += 1

A spec/fabricators/custom_emoji_category_fabricator.rb => spec/fabricators/custom_emoji_category_fabricator.rb +3 -0
@@ 0,0 1,3 @@
Fabricator(:custom_emoji_category) do
  name "MyString"
end

A spec/models/custom_emoji_category_spec.rb => spec/models/custom_emoji_category_spec.rb +5 -0
@@ 0,0 1,5 @@
require 'rails_helper'

RSpec.describe CustomEmojiCategory, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end

M yarn.lock => yarn.lock +2 -2
@@ 3384,8 3384,8 @@ elliptic@^6.0.0:
    minimalistic-crypto-utils "^1.0.0"

emoji-mart@Gargron/emoji-mart#build:
  version "2.6.2"
  resolved "https://codeload.github.com/Gargron/emoji-mart/tar.gz/ff00dc470b5b2d9f145a6d6e977a54de5df2b4c9"
  version "2.6.3"
  resolved "https://codeload.github.com/Gargron/emoji-mart/tar.gz/934f314fd8322276765066e8a2a6be5bac61b1cf"

emoji-regex@^7.0.1, emoji-regex@^7.0.2:
  version "7.0.3"