diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index 866a9902c..f0e3df944 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -4,6 +4,74 @@ module StatusesHelper EMBEDDED_CONTROLLER = 'statuses' EMBEDDED_ACTION = 'embed' + def account_action_button(account) + if user_signed_in? + if account.id == current_user.account_id + link_to settings_profile_url, class: 'button logo-button' do + safe_join([svg_logo, t('settings.edit_profile')]) + end + elsif current_account.following?(account) || current_account.requested?(account) + link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do + safe_join([svg_logo, t('accounts.unfollow')]) + end + elsif !(account.memorial? || account.moved?) + link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do + safe_join([svg_logo, t('accounts.follow')]) + end + end + elsif !(account.memorial? || account.moved?) + link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do + safe_join([svg_logo, t('accounts.follow')]) + end + end + end + + def minimal_account_action_button(account) + if user_signed_in? + return if account.id == current_user.account_id + + if current_account.following?(account) || current_account.requested?(account) + link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do + fa_icon('user-times fw') + end + elsif !(account.memorial? || account.moved?) + link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do + fa_icon('user-plus fw') + end + end + elsif !(account.memorial? || account.moved?) + link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do + fa_icon('user-plus fw') + end + end + end + + def svg_logo + content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976') + end + + def svg_logo_full + content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo-full'), 'viewBox' => '0 0 713.35878 175.8678') + end + + def account_badge(account, all: false) + if account.bot? + content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles') + elsif account.group? + content_tag(:div, content_tag(:div, t('accounts.roles.group'), class: 'account-role group'), class: 'roles') + elsif (Setting.show_staff_badge && account.user_staff?) || all + content_tag(:div, class: 'roles') do + if all && !account.user_staff? + content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role') + elsif account.user_admin? + content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin') + elsif account.user_moderator? + content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator') + end + end + end + end + def link_to_more(url) link_to t('statuses.show_more'), url, class: 'load-more load-gap' end diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index dbb567e85..8bd7f2db5 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -238,9 +238,18 @@ class Header extends ImmutablePureComponent { const content = { __html: account.get('note_emojified') }; const displayNameHtml = { __html: account.get('display_name_html') }; const fields = account.get('fields'); - const badge = account.get('bot') ? (
) : null; const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + let badge; + + if (account.get('bot')) { + badge = (
); + } else if (account.get('group')) { + badge = (
); + } else { + badge = null; + } + return (
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index cdd406043..0ca6b92a4 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -89,7 +89,7 @@ class ActivityPub::Activity def distribute(status) crawl_links(status) - notify_about_reblog(status) if reblog_of_local_account?(status) + notify_about_reblog(status) if reblog_of_local_account?(status) && !reblog_by_following_group_account?(status) notify_about_mentions(status) # Only continue if the status is supposed to have arrived in real-time. @@ -105,6 +105,10 @@ class ActivityPub::Activity status.reblog? && status.reblog.account.local? end + def reblog_by_following_group_account?(status) + status.reblog? && status.account.group? && status.reblog.account.following?(status.account) + end + def notify_about_reblog(status) NotifyService.new.call(status.reblog.account, status) end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 512272dbe..ed680d762 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -68,10 +68,19 @@ class ActivityPub::TagManager if status.account.silenced? # Only notify followers if the account is locally silenced account_ids = status.active_mentions.pluck(:account_id) - to = status.account.followers.where(id: account_ids).map { |account| uri_for(account) } - to.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).map { |request| uri_for(request.account) }) + to = status.account.followers.where(id: account_ids).each_with_object([]) do |account, result| + result << uri_for(account) + result << account.followers_url if account.group? + end + to.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result| + result << uri_for(request.account) + result << request.account.followers_url if request.account.group? + end) else - status.active_mentions.map { |mention| uri_for(mention.account) } + status.active_mentions.each_with_object([]) do |mention, result| + result << uri_for(mention.account) + result << mention.account.followers_url if mention.account.group? + end end end end @@ -97,10 +106,19 @@ class ActivityPub::TagManager if status.account.silenced? # Only notify followers if the account is locally silenced account_ids = status.active_mentions.pluck(:account_id) - cc.concat(status.account.followers.where(id: account_ids).map { |account| uri_for(account) }) - cc.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).map { |request| uri_for(request.account) }) + cc.concat(status.account.followers.where(id: account_ids).each_with_object([]) do |account, result| + result << uri_for(account) + result << account.followers_url if account.group? + end) + cc.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result| + result << uri_for(request.account) + result << request.account.followers_url if request.account.group? + end) else - cc.concat(status.active_mentions.map { |mention| uri_for(mention.account) }) + cc.concat(status.active_mentions.each_with_object([]) do |mention, result| + result << uri_for(mention.account) + result << mention.account.followers_url if mention.account.group? + end) end end diff --git a/app/models/account.rb b/app/models/account.rb index d17782f78..884332e5a 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -93,6 +93,7 @@ class Account < ApplicationRecord scope :without_silenced, -> { where(silenced_at: nil) } scope :recent, -> { reorder(id: :desc) } scope :bots, -> { where(actor_type: %w(Application Service)) } + scope :groups, -> { where(actor_type: 'Group') } scope :alphabetic, -> { order(domain: :asc, username: :asc) } scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') } scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) } @@ -153,6 +154,12 @@ class Account < ApplicationRecord self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person' end + def group? + actor_type == 'Group' + end + + alias group group? + def acct local? ? username : "#{username}@#{domain}" end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 17df85de3..aa64936a7 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -49,6 +49,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer 'Application' elsif object.bot? 'Service' + elsif object.group? + 'Group' else 'Person' end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 7e3041ae3..5fec75673 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -3,7 +3,7 @@ class REST::AccountSerializer < ActiveModel::Serializer include RoutingHelper - attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :created_at, + attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at, :note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, :following_count, :statuses_count, :last_status_at diff --git a/config/locales/en.yml b/config/locales/en.yml index d498f6ce3..f6a14ad1a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -78,6 +78,7 @@ en: roles: admin: Admin bot: Bot + group: Group moderator: Mod unavailable: Profile unavailable unfollow: Unfollow