about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/admin/custom_emojis_controller.rb78
-rw-r--r--app/controllers/custom_emojis_controller.rb97
-rw-r--r--app/models/concerns/account_associations.rb3
-rw-r--r--app/models/custom_emoji.rb8
-rw-r--r--app/models/custom_emoji_filter.rb9
-rw-r--r--app/models/form/custom_emoji_batch.rb39
-rw-r--r--app/policies/custom_emoji_policy.rb36
-rw-r--r--app/views/custom_emojis/_custom_emoji.html.haml (renamed from app/views/admin/custom_emojis/_custom_emoji.html.haml)1
-rw-r--r--app/views/custom_emojis/index.html.haml (renamed from app/views/admin/custom_emojis/index.html.haml)41
-rw-r--r--app/views/custom_emojis/new.html.haml (renamed from app/views/admin/custom_emojis/new.html.haml)2
10 files changed, 208 insertions, 106 deletions
diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb
deleted file mode 100644
index 71efb543e..000000000
--- a/app/controllers/admin/custom_emojis_controller.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
-  class CustomEmojisController < BaseController
-    def index
-      authorize :custom_emoji, :index?
-
-      @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
-      @form          = Form::CustomEmojiBatch.new
-    end
-
-    def new
-      authorize :custom_emoji, :create?
-
-      @custom_emoji = CustomEmoji.new
-    end
-
-    def create
-      authorize :custom_emoji, :create?
-
-      @custom_emoji = CustomEmoji.new(resource_params)
-
-      if @custom_emoji.save
-        log_action :create, @custom_emoji
-        redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg')
-      else
-        render :new
-      end
-    end
-
-    def batch
-      @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
-      @form.save
-    rescue ActionController::ParameterMissing
-      flash[:alert] = I18n.t('admin.accounts.no_account_selected')
-    rescue Mastodon::NotPermittedError
-      flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
-    ensure
-      redirect_to admin_custom_emojis_path(filter_params)
-    end
-
-    private
-
-    def resource_params
-      params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
-    end
-
-    def filtered_custom_emojis
-      CustomEmojiFilter.new(filter_params).results
-    end
-
-    def filter_params
-      params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS)
-    end
-
-    def action_from_button
-      if params[:update]
-        'update'
-      elsif params[:list]
-        'list'
-      elsif params[:unlist]
-        'unlist'
-      elsif params[:enable]
-        'enable'
-      elsif params[:disable]
-        'disable'
-      elsif params[:copy]
-        'copy'
-      elsif params[:delete]
-        'delete'
-      end
-    end
-
-    def form_custom_emoji_batch_params
-      params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
-    end
-  end
-end
diff --git a/app/controllers/custom_emojis_controller.rb b/app/controllers/custom_emojis_controller.rb
new file mode 100644
index 000000000..0ef8d0a50
--- /dev/null
+++ b/app/controllers/custom_emojis_controller.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+class CustomEmojisController < ApplicationController
+  include Authorization
+  include AccountableConcern
+
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_pack
+  before_action :set_body_classes
+
+  def index
+    authorize :custom_emoji, :index?
+
+    @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
+    @form          = Form::CustomEmojiBatch.new
+  end
+
+  def new
+    authorize :custom_emoji, :create?
+
+    @custom_emoji = CustomEmoji.new(account: current_account)
+  end
+
+  def create
+    authorize :custom_emoji, :create?
+
+    @custom_emoji = CustomEmoji.new(resource_params.merge(account: current_account))
+
+    if @custom_emoji.save
+      log_action :create, @custom_emoji
+      redirect_to custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg')
+    else
+      render :new
+    end
+  end
+
+  def batch
+    @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
+    @form.save
+  rescue ActionController::ParameterMissing
+    flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+  rescue Mastodon::NotPermittedError
+    flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
+  ensure
+    redirect_to custom_emojis_path(filter_params)
+  end
+
+  private
+
+  def resource_params
+    params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
+  end
+
+  def filtered_custom_emojis
+    CustomEmojiFilter.new(filter_params, current_account).results
+  end
+
+  def filter_params
+    params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS)
+  end
+
+  def action_from_button
+    if params[:update]
+      'update'
+    elsif params[:list]
+      'list'
+    elsif params[:unlist]
+      'unlist'
+    elsif params[:enable]
+      'enable'
+    elsif params[:disable]
+      'disable'
+    elsif params[:copy]
+      'copy'
+    elsif params[:delete]
+      'delete'
+    elsif params[:claim]
+      'claim'
+    elsif params[:unclaim]
+      'unclaim'
+    end
+  end
+
+  def form_custom_emoji_batch_params
+    params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
+  end
+
+  def set_pack
+    use_pack 'settings'
+  end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
+end
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index a8b024346..71947fc22 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -78,5 +78,8 @@ module AccountAssociations
 
     # Collection items
     has_many :collection_items, inverse_of: :account, dependent: :destroy
+
+    # Custom emojis
+    has_many :custom_emojis, inverse_of: :account, dependent: :nullify
   end
 end
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index 7cb03b819..c819288ba 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -18,6 +18,7 @@
 #  visible_in_picker            :boolean          default(TRUE), not null
 #  category_id                  :bigint(8)
 #  image_storage_schema_version :integer
+#  account_id                   :bigint(8)
 #
 
 class CustomEmoji < ApplicationRecord
@@ -32,6 +33,7 @@ class CustomEmoji < ApplicationRecord
   IMAGE_MIME_TYPES = %w(image/png image/gif).freeze
 
   belongs_to :category, class_name: 'CustomEmojiCategory', optional: true
+  belongs_to :account, inverse_of: :custom_emojis, 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' } }
@@ -46,6 +48,7 @@ class CustomEmoji < ApplicationRecord
   scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) }
   scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }
   scope :listed, -> { local.where(disabled: false).where(visible_in_picker: true) }
+  scope :owned_by, ->(account) { where(account: account) }
 
   remotable_attachment :image, LIMIT
 
@@ -61,8 +64,11 @@ class CustomEmoji < ApplicationRecord
     :emoji
   end
 
-  def copy!
+  def copy!(current_account = nil)
     copy = self.class.find_or_initialize_by(domain: nil, shortcode: shortcode)
+    return copy if copy.account_id.present? && copy.account_id != current_account&.id
+
+    copy.account = current_account
     copy.image = image
     copy.tap(&:save!)
   end
diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb
index 414e1fcdd..58c888518 100644
--- a/app/models/custom_emoji_filter.rb
+++ b/app/models/custom_emoji_filter.rb
@@ -5,13 +5,16 @@ class CustomEmojiFilter
     local
     remote
     by_domain
+    claimed
+    unclaimed
     shortcode
   ).freeze
 
   attr_reader :params
 
-  def initialize(params)
+  def initialize(params, account)
     @params = params
+    @account = account
   end
 
   def results
@@ -36,6 +39,10 @@ class CustomEmojiFilter
       CustomEmoji.remote
     when 'by_domain'
       CustomEmoji.where(domain: value.strip.downcase)
+    when 'claimed'
+      CustomEmoji.where(account: @account)
+    when 'unclaimed'
+      CustomEmoji.where(account: nil)
     when 'shortcode'
       CustomEmoji.search(value.strip)
     else
diff --git a/app/models/form/custom_emoji_batch.rb b/app/models/form/custom_emoji_batch.rb
index f4fa84c10..54a15dc18 100644
--- a/app/models/form/custom_emoji_batch.rb
+++ b/app/models/form/custom_emoji_batch.rb
@@ -24,13 +24,17 @@ class Form::CustomEmojiBatch
       copy!
     when 'delete'
       delete!
+    when 'claim'
+      claim!
+    when 'unclaim'
+      unclaim!
     end
   end
 
   private
 
-  def custom_emojis
-    @custom_emojis ||= CustomEmoji.where(id: custom_emoji_ids)
+  def custom_emojis(include_all = false)
+    @custom_emojis ||= (include_all || current_account&.user&.staff? ? CustomEmoji.where(id: custom_emoji_ids) : CustomEmoji.local.where(id: custom_emoji_ids, account: current_account))
   end
 
   def update!
@@ -40,10 +44,12 @@ class Form::CustomEmojiBatch
       if category_id.present?
         CustomEmojiCategory.find(category_id)
       elsif category_name.present?
-        CustomEmojiCategory.find_or_create_by!(name: category_name)
+        CustomEmojiCategory.find_or_create_by!(name: current_account&.user&.staff? ? category_name.strip : "(@#{current_account.username}) #{category_name}".rstrip)
       end
     end
 
+    return if category.name.start_with?('(@') && !category.name.start_with?("(@#{current_account.username}) ")
+
     custom_emojis.each do |custom_emoji|
       custom_emoji.update(category_id: category&.id)
       log_action :update, custom_emoji
@@ -87,10 +93,10 @@ class Form::CustomEmojiBatch
   end
 
   def copy!
-    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :copy?) }
+    custom_emojis(true).each { |custom_emoji| authorize(custom_emoji, :copy?) }
 
     custom_emojis.each do |custom_emoji|
-      copied_custom_emoji = custom_emoji.copy!
+      copied_custom_emoji = custom_emoji.copy!(current_account)
       log_action :create, copied_custom_emoji
     end
   end
@@ -103,4 +109,27 @@ class Form::CustomEmojiBatch
       log_action :destroy, custom_emoji
     end
   end
+
+  def claim!
+    custom_emojis(true).each { |custom_emoji| authorize(custom_emoji, :claim?) }
+
+    custom_emojis.each do |custom_emoji|
+      if custom_emoji.local?
+        custom_emoji.update(account: current_account)
+        log_action :update, custom_emoji
+      else
+        copied_custom_emoji = custom_emoji.copy!(current_account)
+        log_action :create, copied_custom_emoji
+      end
+    end
+  end
+
+  def unclaim!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :unclaim?) }
+
+    custom_emojis.each do |custom_emoji|
+      custom_emoji.update(account: nil)
+      log_action :update, custom_emoji
+    end
+  end
 end
diff --git a/app/policies/custom_emoji_policy.rb b/app/policies/custom_emoji_policy.rb
index a8c3cbc73..7e585a3d6 100644
--- a/app/policies/custom_emoji_policy.rb
+++ b/app/policies/custom_emoji_policy.rb
@@ -2,30 +2,52 @@
 
 class CustomEmojiPolicy < ApplicationPolicy
   def index?
-    staff?
+    user_signed_in?
   end
 
   def create?
-    admin?
+    user_signed_in?
   end
 
   def update?
-    admin?
+    user_signed_in? && owned?
   end
 
   def copy?
-    admin?
+    staff? || (user_signed_in? && new_or_owned?)
   end
 
   def enable?
-    staff?
+    user_signed_in? && owned?
   end
 
   def disable?
-    staff?
+    user_signed_in? && owned?
   end
 
   def destroy?
-    admin?
+    user_signed_in? && owned?
+  end
+
+  def claim?
+    staff? || claimable?
+  end
+
+  def unclaim?
+    user_signed_in? && owned?
+  end
+
+  private
+
+  def owned?
+    staff? || (current_account.present? && record.account_id == current_account.id)
+  end
+
+  def new_or_owned?
+    !CustomEmoji.where(domain: nil, shortcode: record.shortcode).where('account_id IS NULL OR account_id != ?', current_account.id).exists?
+  end
+
+  def claimable?
+    record.local? ? record.account_id.blank? || record.account_id == current_account.id : new_or_owned?
   end
 end
diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/custom_emojis/_custom_emoji.html.haml
index 526c844e9..e124373c6 100644
--- a/app/views/admin/custom_emojis/_custom_emoji.html.haml
+++ b/app/views/custom_emojis/_custom_emoji.html.haml
@@ -7,6 +7,7 @@
 
     .batch-table__row__content__text
       %samp= ":#{custom_emoji.shortcode}:"
+      %p.hint.muted-hint{ title: t('admin.custom_emojis.owner') }= custom_emoji.account_id.present? ? "@#{custom_emoji.account.username}" : t('admin.custom_emojis.unclaimed') if custom_emoji.local?
 
       - if custom_emoji.local?
         %span.account-role.bot= custom_emoji.category&.name || t('admin.custom_emojis.uncategorized')
diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/custom_emojis/index.html.haml
index b6cf7ba64..f81d91d53 100644
--- a/app/views/admin/custom_emojis/index.html.haml
+++ b/app/views/custom_emojis/index.html.haml
@@ -3,7 +3,9 @@
 
 - if can?(:create, :custom_emoji)
   - content_for :heading_actions do
-    = link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button'
+    = link_to t('admin.custom_emojis.upload'), new_custom_emoji_path, class: 'button'
+
+%p= t('admin.custom_emojis.ownership_warning')
 
 .filters
   .filter-subset
@@ -11,17 +13,27 @@
     %ul
       %li= filter_link_to t('admin.accounts.location.all'), local: nil, remote: nil
       %li
-        - if selected? local: '1', remote: nil
-          = filter_link_to t('admin.accounts.location.local'), {local: nil, remote: nil}, {local: '1', remote: nil}
+        - if selected? local: '1', remote: nil, claimed: nil, unclaimed: nil
+          = filter_link_to t('admin.accounts.location.local'), {local: nil, remote: nil, claimed: nil, unclaimed: nil}, {local: '1', remote: nil, claimed: nil, unclaimed: nil}
+        - else
+          = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil, claimed: nil, unclaimed: nil
+      %li
+        - if selected? remote: '1', local: nil, claimed: nil, unclaimed: nil
+          = filter_link_to t('admin.accounts.location.remote'), {remote: nil, local: nil, claimed: nil, unclaimed: nil}, {remote: '1', local: nil, claimed: nil, unclaimed: nil}
         - else
-          = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil
+          = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil, claimed: nil, unclained: nil
       %li
-        - if selected? remote: '1', local: nil
-          = filter_link_to t('admin.accounts.location.remote'), {remote: nil, local: nil}, {remote: '1', local: nil}
+        - if selected? local: '1', remote: nil, claimed: '1', unclaimed: nil
+          = filter_link_to t('admin.accounts.location.claimed'), {local: '1', remote: nil, claimed: nil, unclaimed: nil}, {local: '1', remote: nil, claimed: '1', unclaimed: nil}
         - else
-          = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil
+          = filter_link_to t('admin.accounts.location.claimed'), local: '1', remote: nil, claimed: '1', unclaimed: nil
+      %li
+        - if selected? local: '1', remote: nil, claimed: nil, unclaimed: '1'
+          = filter_link_to t('admin.accounts.location.unclaimed'), {local: '1', remote: nil, claimed: nil, unclaimed: nil}, {local: '1', remote: nil, claimed: nil, unclaimed: '1'}
+        - else
+          = filter_link_to t('admin.accounts.location.unclaimed'), local: '1', remote: nil, claimed: nil, unclaimed: '1'
 
-= form_tag admin_custom_emojis_url, method: 'GET', class: 'simple_form' do
+= form_tag custom_emojis_url, method: 'GET', class: 'simple_form' do
   .fields-group
     - CustomEmojiFilter::KEYS.each do |key|
       = hidden_field_tag key, params[key] if params[key].present?
@@ -32,9 +44,9 @@
 
     .actions
       %button.button= t('admin.accounts.search')
-      = link_to t('admin.accounts.reset'), admin_custom_emojis_path, class: 'button negative'
+      = link_to t('admin.accounts.reset'), custom_emojis_path, class: 'button negative'
 
-= form_for(@form, url: batch_admin_custom_emojis_path) do |f|
+= form_for(@form, url: batch_custom_emojis_path) do |f|
   = hidden_field_tag :page, params[:page] || 1
 
   - CustomEmojiFilter::KEYS.each do |key|
@@ -45,6 +57,10 @@
       %label.batch-table__toolbar__select.batch-checkbox-all
         = check_box_tag :batch_checkbox_all, nil, false
       .batch-table__toolbar__actions
+        = f.button safe_join([fa_icon('lock'), t('admin.custom_emojis.claim')]), name: :claim, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+        = f.button safe_join([fa_icon('unlock'), t('admin.custom_emojis.unclaim')]), name: :unclaim, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
         - if params[:local] == '1'
           = f.button safe_join([fa_icon('save'), t('generic.save_changes')]), name: :update, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
@@ -56,10 +72,9 @@
 
         = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.disable')]), name: :disable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
-        - if can?(:destroy, :custom_emoji)
-          = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+        = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
-        - if can?(:copy, :custom_emoji) && params[:local] != '1'
+        - if params[:local] != '1'
           = f.button safe_join([fa_icon('copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
     - if params[:local] == '1'
diff --git a/app/views/admin/custom_emojis/new.html.haml b/app/views/custom_emojis/new.html.haml
index e15a07cb8..fe9d8fc64 100644
--- a/app/views/admin/custom_emojis/new.html.haml
+++ b/app/views/custom_emojis/new.html.haml
@@ -1,7 +1,7 @@
 - content_for :page_title do
   = t('.title')
 
-= simple_form_for @custom_emoji, url: admin_custom_emojis_path do |f|
+= simple_form_for @custom_emoji, url: custom_emojis_path do |f|
   = render 'shared/error_messages', object: @custom_emoji
 
   .fields-group