diff options
Diffstat (limited to 'app')
47 files changed, 463 insertions, 155 deletions
diff --git a/app/controllers/api/v1/preferences_controller.rb b/app/controllers/api/v1/preferences_controller.rb new file mode 100644 index 000000000..077d39f5d --- /dev/null +++ b/app/controllers/api/v1/preferences_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Api::V1::PreferencesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:accounts' } + before_action :require_user! + + respond_to :json + + def index + render json: current_account, serializer: REST::PreferencesSerializer + end +end diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index 04847a6b7..ed4f55100 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -9,7 +9,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController respond_to :json def create - @status = ReblogService.new.call(current_user.account, status_for_reblog) + @status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params) render json: @status, serializer: REST::StatusSerializer end @@ -32,4 +32,8 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController def status_for_destroy current_user.account.statuses.where(reblog_of_id: params[:status_id]).first! end + + def reblog_params + params.permit(:visibility) + end end diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb new file mode 100644 index 000000000..e6dd65e44 --- /dev/null +++ b/app/controllers/relationships_controller.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +class RelationshipsController < ApplicationController + layout 'admin' + + before_action :authenticate_user! + before_action :set_accounts, only: :show + before_action :set_body_classes + + helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship? + + def show + @form = Form::AccountBatch.new + end + + def update + @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button)) + @form.save + rescue ActionController::ParameterMissing + # Do nothing + ensure + redirect_to relationships_path(current_params) + end + + private + + def set_accounts + @accounts = relationships_scope.page(params[:page]).per(40) + end + + def relationships_scope + scope = begin + if following_relationship? + current_account.following.includes(:account_stat) + else + current_account.followers.includes(:account_stat) + end + end + + scope.merge!(Follow.recent) + scope.merge!(mutual_relationship_scope) if mutual_relationship? + scope.merge!(abandoned_account_scope) if params[:status] == 'abandoned' + scope.merge!(active_account_scope) if params[:status] == 'active' + scope.merge!(by_domain_scope) if params[:by_domain].present? + + scope + end + + def mutual_relationship_scope + Account.where(id: current_account.following) + end + + def abandoned_account_scope + Account.where.not(moved_to_account_id: nil) + end + + def active_account_scope + Account.where(moved_to_account_id: nil) + end + + def by_domain_scope + Account.where(domain: params[:by_domain]) + end + + def form_account_batch_params + params.require(:form_account_batch).permit(:action, account_ids: []) + end + + def following_relationship? + params[:relationship].blank? || params[:relationship] == 'following' + end + + def mutual_relationship? + params[:relationship] == 'mutual' + end + + def followed_by_relationship? + params[:relationship] == 'followed_by' + end + + def current_params + params.slice(:page, :status, :relationship, :by_domain).permit(:page, :status, :relationship, :by_domain) + end + + def action_from_button + if params[:unfollow] + 'unfollow' + elsif params[:remove_from_followers] + 'remove_from_followers' + elsif params[:block_domains] + 'block_domains' + end + end + + def set_body_classes + @body_classes = 'admin' + end +end diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb deleted file mode 100644 index 8aae379aa..000000000 --- a/app/controllers/settings/follower_domains_controller.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -class Settings::FollowerDomainsController < Settings::BaseController - def show - @account = current_account - @domains = current_account.followers.reorder(Arel.sql('MIN(follows.id) DESC')).group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10) - end - - def update - domains = bulk_params[:select] || [] - - AfterAccountDomainBlockWorker.push_bulk(domains) do |domain| - [current_account.id, domain] - end - - redirect_to settings_follower_domains_path, notice: I18n.t('followers.success', count: domains.size) - end - - private - - def bulk_params - params.permit(select: []) - end -end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 359d60b60..e5fbb1500 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -9,42 +9,6 @@ module Admin::ActionLogsHelper end end - def linkable_log_target(record) - case record.class.name - when 'Account' - link_to record.acct, admin_account_path(record.id) - when 'User' - link_to record.account.acct, admin_account_path(record.account_id) - when 'CustomEmoji' - record.shortcode - when 'Report' - link_to "##{record.id}", admin_report_path(record) - when 'DomainBlock', 'EmailDomainBlock' - link_to record.domain, "https://#{record.domain}" - when 'Status' - link_to record.account.acct, TagManager.instance.url_for(record) - when 'AccountWarning' - link_to record.target_account.acct, admin_account_path(record.target_account_id) - end - end - - def log_target_from_history(type, attributes) - case type - when 'CustomEmoji' - attributes['shortcode'] - when 'DomainBlock', 'EmailDomainBlock' - link_to attributes['domain'], "https://#{attributes['domain']}" - when 'Status' - tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count')) - - if tmp_status.account - link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id) - else - I18n.t('admin.action_logs.deleted_status') - end - end - end - def relevant_log_changes(log) if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action) log.recorded_changes.slice('domain') @@ -111,4 +75,40 @@ module Admin::ActionLogsHelper def opposite_verbs?(log) %w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type) end + + def linkable_log_target(record) + case record.class.name + when 'Account' + link_to record.acct, admin_account_path(record.id) + when 'User' + link_to record.account.acct, admin_account_path(record.account_id) + when 'CustomEmoji' + record.shortcode + when 'Report' + link_to "##{record.id}", admin_report_path(record) + when 'DomainBlock', 'EmailDomainBlock' + link_to record.domain, "https://#{record.domain}" + when 'Status' + link_to record.account.acct, TagManager.instance.url_for(record) + when 'AccountWarning' + link_to record.target_account.acct, admin_account_path(record.target_account_id) + end + end + + def log_target_from_history(type, attributes) + case type + when 'CustomEmoji' + attributes['shortcode'] + when 'DomainBlock', 'EmailDomainBlock' + link_to attributes['domain'], "https://#{attributes['domain']}" + when 'Status' + tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count')) + + if tmp_status.account + link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id) + else + I18n.t('admin.action_logs.deleted_status') + end + end + end end diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index 8f78bf5f8..09a356296 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -7,8 +7,9 @@ module Admin::FilterHelper CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze TAGS_FILTERS = %i(hidden).freeze INSTANCES_FILTERS = %i(limited by_domain).freeze + FOLLOWERS_FILTERS = %i(relationship status by_domain).freeze - FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FOLLOWERS_FILTERS def filter_link_to(text, link_to_params, link_class_params = link_to_params) new_url = filtered_url_for(link_to_params) diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js new file mode 100644 index 000000000..d1ca5bf75 --- /dev/null +++ b/app/javascript/mastodon/components/error_boundary.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import illustration from '../../images/elephant_ui_disappointed.svg'; + +export default class ErrorBoundary extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + }; + + state = { + hasError: false, + stackTrace: undefined, + componentStack: undefined, + } + + componentDidCatch(error, info) { + this.setState({ + hasError: true, + stackTrace: error.stack, + componentStack: info && info.componentStack, + }); + } + + render() { + const { hasError } = this.state; + + if (!hasError) { + return this.props.children; + } + + return ( + <div> + <img src={illustration} alt='' /> + </div> + ); + } + +} diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 2912540a0..542b68282 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -13,6 +13,7 @@ import { connectUserStream } from '../actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import initialState from '../initial_state'; +import ErrorBoundary from '../components/error_boundary'; const { localeData, messages } = getLocale(); addLocaleData(localeData); @@ -75,7 +76,9 @@ export default class Mastodon extends React.PureComponent { return ( <IntlProvider locale={locale} messages={messages}> <Provider store={store}> - <MastodonMount /> + <ErrorBoundary> + <MastodonMount /> + </ErrorBoundary> </Provider> </IntlProvider> ); diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js index e7d935251..a60ace35b 100644 --- a/app/javascript/mastodon/features/ui/components/bundle.js +++ b/app/javascript/mastodon/features/ui/components/bundle.js @@ -53,6 +53,11 @@ class Bundle extends React.PureComponent { const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props; const cachedMod = Bundle.cache.get(fetchComponent); + if (fetchComponent === undefined) { + this.setState({ mod: null }); + return Promise.resolve(); + } + onFetch(); if (cachedMod) { diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index a9442d803..9396d7b5d 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -73,6 +73,10 @@ "compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledující.", "compose_form.lock_disclaimer.lock": "uzamčen", "compose_form.placeholder": "Co se vám honí hlavou?", + "compose_form.poll.add_option": "Přidat volbu", + "compose_form.poll.duration": "Délka ankety", + "compose_form.poll.option_placeholder": "Volba {number}", + "compose_form.poll.remove_option": "Odstranit tuto volbu", "compose_form.publish": "Tootnout", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "Mediální obsah je označen jako citlivý", @@ -151,6 +155,9 @@ "home.column_settings.basic": "Základní", "home.column_settings.show_reblogs": "Zobrazit boosty", "home.column_settings.show_replies": "Zobrazit odpovědi", + "intervals.full.days": "{number, plural, one {# den} few {# dny} many {# dne} other {# dní}}", + "intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodiny} other {# hodin}}", + "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minuty} other {# minut}}", "introduction.federation.action": "Další", "introduction.federation.federated.headline": "Federovaná", "introduction.federation.federated.text": "Veřejné příspěvky z jiných serverů na fediverse se zobrazí na federované časové ose.", @@ -240,6 +247,7 @@ "notification.favourite": "{name} si oblíbil/a váš toot", "notification.follow": "{name} vás začal/a sledovat", "notification.mention": "{name} vás zmínil/a", + "notification.poll": "Anketa, ve které jste hlasoval/a, skončila", "notification.reblog": "{name} boostnul/a váš toot", "notifications.clear": "Vymazat oznámení", "notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?", @@ -264,6 +272,8 @@ "poll.refresh": "Refresh", "poll.total_votes": "{count, plural, one {# vote} other {# votes}}", "poll.vote": "Vote", + "poll_button.add_poll": "Přidat anketu", + "poll_button.remove_poll": "Odstranit anketu", "privacy.change": "Změnit soukromí tootu", "privacy.direct.long": "Odeslat pouze zmíněným uživatelům", "privacy.direct.short": "Přímý", @@ -356,6 +366,7 @@ "upload_area.title": "Přetažením nahrajete", "upload_button.label": "Přidat média (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_error.limit": "Byl překročen limit nahraných souborů.", + "upload_error.poll": "Nahrávání souborů není povoleno u anket.", "upload_form.description": "Popis pro zrakově postižené", "upload_form.focus": "Změnit náhled", "upload_form.undo": "Smazat", diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index 9e8785679..d3a0ea03d 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -140,6 +140,15 @@ a.table-action-link { input { margin-top: 8px; } + + &--aligned { + display: flex; + align-items: center; + + input { + margin-top: 0; + } + } } &__actions, @@ -183,6 +192,10 @@ a.table-action-link { &__content { padding-top: 12px; padding-bottom: 16px; + + &--unpadded { + padding: 0; + } } } @@ -197,4 +210,10 @@ a.table-action-link { font-weight: 700; } } + + .nothing-here { + border: 1px solid darken($ui-base-color, 8%); + border-top: 0; + box-shadow: none; + } } diff --git a/app/lib/language_detector.rb b/app/lib/language_detector.rb index 58c8e2069..70a9084d1 100644 --- a/app/lib/language_detector.rb +++ b/app/lib/language_detector.rb @@ -3,7 +3,8 @@ class LanguageDetector include Singleton - CHARACTER_THRESHOLD = 140 + CHARACTER_THRESHOLD = 140 + RELIABLE_CHARACTERS_RE = /[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}\p{Han}\p{Katakana}\p{Hiragana}\p{Hangul}]+/m def initialize @identifier = CLD3::NNetLanguageIdentifier.new(1, 2048) @@ -11,15 +12,14 @@ class LanguageDetector def detect(text, account) input_text = prepare_text(text) + return if input_text.blank? detect_language_code(input_text) || default_locale(account) end def language_names - @language_names = - CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym } - .uniq + @language_names = CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym }.uniq end private @@ -29,12 +29,29 @@ class LanguageDetector end def unreliable_input?(text) - text.size < CHARACTER_THRESHOLD + !reliable_input?(text) + end + + def reliable_input?(text) + sufficient_text_length?(text) || language_specific_character_set?(text) + end + + def sufficient_text_length?(text) + text.size >= CHARACTER_THRESHOLD + end + + def language_specific_character_set?(text) + words = text.scan(RELIABLE_CHARACTERS_RE) + + if words.present? + words.reduce(0) { |acc, elem| acc + elem.size }.to_f / text.size.to_f > 0.3 + else + false + end end def detect_language_code(text) return if unreliable_input?(text) - result = @identifier.find_language(text) iso6391(result.language.to_s).to_sym if result.reliable? end @@ -77,6 +94,6 @@ class LanguageDetector end def default_locale(account) - return account.user_locale&.to_sym || I18n.default_locale if account.local? + account.user_locale&.to_sym || I18n.default_locale if account.local? end end diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb new file mode 100644 index 000000000..60eaaf0e2 --- /dev/null +++ b/app/models/form/account_batch.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class Form::AccountBatch + include ActiveModel::Model + + attr_accessor :account_ids, :action, :current_account + + def save + case action + when 'unfollow' + unfollow! + when 'remove_from_followers' + remove_from_followers! + when 'block_domains' + block_domains! + end + end + + private + + def unfollow! + accounts.find_each do |target_account| + UnfollowService.new.call(current_account, target_account) + end + end + + def remove_from_followers! + current_account.passive_relationships.where(account_id: account_ids).find_each do |follow| + reject_follow!(follow) + end + end + + def block_domains! + AfterAccountDomainBlockWorker.push_bulk(account_domains) do |domain| + [current_account.id, domain] + end + end + + def account_domains + accounts.pluck(Arel.sql('distinct domain')).compact + end + + def accounts + Account.where(id: account_ids) + end + + def reject_follow!(follow) + follow.destroy + + return unless follow.account.activitypub? + + json = ActiveModelSerializers::SerializableResource.new( + follow, + serializer: ActivityPub::RejectFollowSerializer, + adapter: ActivityPub::Adapter + ).to_json + + ActivityPub::DeliveryWorker.perform_async(json, current_account.id, follow.account.inbox_url) + end +end diff --git a/app/models/status.rb b/app/models/status.rb index f576489b4..440d7cc63 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -73,7 +73,9 @@ class Status < ApplicationRecord validates_with StatusLengthValidator validates_with DisallowedHashtagsValidator validates :reblog, uniqueness: { scope: :account }, if: :reblog? - validates_associated :owned_poll + validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog? + + accepts_nested_attributes_for :owned_poll default_scope { recent } diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index 97fed63d1..98c53c84a 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -32,7 +32,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer end def thumbnail - instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('preview.jpg') + instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.jpg') end def max_toot_chars diff --git a/app/serializers/rest/preferences_serializer.rb b/app/serializers/rest/preferences_serializer.rb new file mode 100644 index 000000000..119f0e06d --- /dev/null +++ b/app/serializers/rest/preferences_serializer.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class REST::PreferencesSerializer < ActiveModel::Serializer + attribute :posting_default_privacy, key: 'posting:default:visibility' + attribute :posting_default_sensitive, key: 'posting:default:sensitive' + attribute :posting_default_language, key: 'posting:default:language' + + attribute :reading_default_sensitive_media, key: 'reading:expand:media' + attribute :reading_default_sensitive_text, key: 'reading:expand:spoilers' + + def posting_default_privacy + object.user.setting_default_privacy + end + + def posting_default_sensitive + object.user.setting_default_sensitive + end + + def posting_default_language + object.user.setting_default_language.presence + end + + def reading_default_sensitive_media + object.user.setting_display_media + end + + def reading_default_sensitive_text + object.user.setting_expand_spoilers + end +end diff --git a/app/serializers/rss/account_serializer.rb b/app/serializers/rss/account_serializer.rb index 712b1347a..88eca79ed 100644 --- a/app/serializers/rss/account_serializer.rb +++ b/app/serializers/rss/account_serializer.rb @@ -11,7 +11,7 @@ class RSS::AccountSerializer builder.title("#{display_name(account)} (@#{account.local_username_and_domain})") .description(account_description(account)) .link(TagManager.instance.url_for(account)) - .logo(full_asset_url(asset_pack_path('logo.svg'))) + .logo(full_pack_url('media/images/logo.svg')) .accent_color('2b90d9') builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar? diff --git a/app/serializers/rss/tag_serializer.rb b/app/serializers/rss/tag_serializer.rb index 7680a8da5..644380149 100644 --- a/app/serializers/rss/tag_serializer.rb +++ b/app/serializers/rss/tag_serializer.rb @@ -12,7 +12,7 @@ class RSS::TagSerializer builder.title("##{tag.name}") .description(strip_tags(I18n.t('about.about_hashtag_html', hashtag: tag.name))) .link(tag_url(tag)) - .logo(full_asset_url(asset_pack_path('logo.svg'))) + .logo(full_pack_url('media/images/logo.svg')) .accent_color('2b90d9') statuses.each do |status| diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index b9952369d..820c553c9 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -29,7 +29,6 @@ class PostStatusService < BaseService return idempotency_duplicate if idempotency_given? && idempotency_duplicate? validate_media! - validate_poll! preprocess_attributes! if scheduled? @@ -74,6 +73,7 @@ class PostStatusService < BaseService def schedule_status! status_for_validation = @account.statuses.build(status_attributes) + if status_for_validation.valid? status_for_validation.destroy @@ -110,12 +110,6 @@ class PostStatusService < BaseService raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?) end - def validate_poll! - return if @options[:poll].blank? - - @poll = @account.polls.new(@options[:poll]) - end - def language_from_option(str) ISO_639.find(str)&.alpha2 end @@ -168,13 +162,13 @@ class PostStatusService < BaseService text: @text, media_attachments: @media || [], thread: @in_reply_to, - owned_poll: @poll, + owned_poll_attributes: poll_attributes, sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?, spoiler_text: @options[:spoiler_text] || '', visibility: @visibility, language: language_from_option(@options[:language]) || @account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(@text, @account), application: @options[:application], - } + }.compact end def scheduled_status_attributes @@ -185,6 +179,12 @@ class PostStatusService < BaseService } end + def poll_attributes + return if @options[:poll].blank? + + @options[:poll].merge(account: @account) + end + def scheduled_options @options.tap do |options_hash| options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 03db27406..deaa0549e 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -7,8 +7,9 @@ class ReblogService < BaseService # Reblog a status and notify its remote author # @param [Account] account Account to reblog from # @param [Status] reblogged_status Status to be reblogged + # @param [Hash] options # @return [Status] - def call(account, reblogged_status) + def call(account, reblogged_status, options = {}) reblogged_status = reblogged_status.reblog if reblogged_status.reblog? authorize_with account, reblogged_status, :reblog? @@ -17,7 +18,7 @@ class ReblogService < BaseService return reblog unless reblog.nil? - reblog = account.statuses.create!(reblog: reblogged_status, text: '') + reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: options[:visibility] || account.user&.setting_default_privacy) DistributionWorker.perform_async(reblog.id) @@ -38,7 +39,7 @@ class ReblogService < BaseService reblogged_status = reblog.reblog if reblogged_status.account.local? - NotifyService.new.call(reblogged_status.account, reblog) + LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name) elsif reblogged_status.account.ostatus? NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), reblog.account_id, reblogged_status.account_id) elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account) diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 87f1071d9..f02a7906a 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -8,7 +8,7 @@ .column-0 .public-account-header.public-account-header--no-bar .public-account-header__image - = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax' + = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax' .column-1 .landing-page__call-to-action{ dir: 'ltr' } @@ -24,7 +24,7 @@ %span= t 'about.status_count_after', count: @instance_presenter.status_count .row__mascot .landing-page__mascot - = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('elephant_ui_plane.svg'), alt: '' + = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: '' .column-2 .landing-page__information.contact-widget diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index 15d0af64e..21dcf226d 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -8,7 +8,7 @@ .landing .landing__brand = link_to root_url, class: 'brand' do - = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + = image_pack_tag 'logo_full.svg', alt: 'Mastodon' %span.brand__tagline=t 'about.tagline' .landing__grid @@ -48,7 +48,7 @@ .hero-widget .hero-widget__img - = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title + = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title - if @instance_presenter.site_short_description.present? .hero-widget__text diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml index ee0eacaf5..e6ad9de34 100644 --- a/app/views/admin/invites/_invite.html.haml +++ b/app/views/admin/invites/_invite.html.haml @@ -10,10 +10,7 @@ = image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar' %span.username= invite.user.account.username - - if invite.expired? - %td{ colspan: 2 } - = t('invites.expired') - - else + - if invite.valid_for_use? %td = fa_icon 'user fw' = invite.uses @@ -24,6 +21,10 @@ - else %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) } = l invite.expires_at + - else + %td{ colspan: 2 } + = t('invites.expired') + %td - - if !invite.expired? && policy(invite).destroy? + - if invite.valid_for_use? && policy(invite).destroy? = table_link_to 'times', t('invites.delete'), admin_invite_path(invite), method: :delete diff --git a/app/views/application/_sidebar.html.haml b/app/views/application/_sidebar.html.haml index 2ff14b252..b5ce5845e 100644 --- a/app/views/application/_sidebar.html.haml +++ b/app/views/application/_sidebar.html.haml @@ -1,6 +1,6 @@ .hero-widget .hero-widget__img - = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title + = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title .hero-widget__text %p= @instance_presenter.site_short_description.html_safe.presence || @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 5f32635e5..6c5268b61 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -9,7 +9,7 @@ .app-holder#mastodon{ data: { props: Oj.dump(default_props) } } %noscript - = image_tag asset_pack_path('logo.svg'), alt: 'Mastodon' + = image_pack_tag 'logo.svg', alt: 'Mastodon' %div = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml index 4240aa3e7..62799ca5b 100644 --- a/app/views/invites/_invite.html.haml +++ b/app/views/invites/_invite.html.haml @@ -5,10 +5,7 @@ %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) } %button{ type: :button }= t('generic.copy') - - if invite.expired? - %td{ colspan: 2 } - = t('invites.expired') - - else + - if invite.valid_for_use? %td = fa_icon 'user fw' = invite.uses @@ -19,7 +16,10 @@ - else %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) } = l invite.expires_at + - else + %td{ colspan: 2 } + = t('invites.expired') %td - - if !invite.expired? && policy(invite).destroy? + - if invite.valid_for_use? && policy(invite).destroy? = table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 0e52702dc..a0cb7c4fe 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -3,7 +3,7 @@ .sidebar-wrapper .sidebar = link_to root_path do - = image_tag asset_pack_path('logo.svg'), class: 'logo', alt: 'Mastodon' + = image_pack_tag 'logo.svg', class: 'logo', alt: 'Mastodon' = render_navigation .content-wrapper diff --git a/app/views/layouts/auth.html.haml b/app/views/layouts/auth.html.haml index ca9c13945..fcbd29fe9 100644 --- a/app/views/layouts/auth.html.haml +++ b/app/views/layouts/auth.html.haml @@ -3,7 +3,7 @@ .logo-container %h1 = link_to root_path do - = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + = image_pack_tag 'logo_full.svg', alt: 'Mastodon' .form-container = render 'flashes' diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml index 6321fec61..26fb697bb 100644 --- a/app/views/layouts/mailer.html.haml +++ b/app/views/layouts/mailer.html.haml @@ -24,7 +24,7 @@ %tr %td.column-cell = link_to root_url do - = image_tag full_pack_url('logo_full.png'), alt: 'Mastodon', height: 34, class: 'logo' + = image_tag full_pack_url('media/images/mailer/logo_full.png'), alt: 'Mastodon', height: 34, class: 'logo' = yield @@ -49,4 +49,4 @@ %p= link_to t('application_mailer.notification_preferences'), settings_notifications_url %td.column-cell.text-right = link_to root_url do - = image_tag full_pack_url('logo_transparent.png'), alt: 'Mastodon', height: 24 + = image_tag full_pack_url('media/images/mailer/logo_transparent.png'), alt: 'Mastodon', height: 24 diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index 1d3519b8a..b4a21caf1 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -5,7 +5,7 @@ %nav.header .nav-left = link_to root_url, class: 'brand' do - = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + = image_pack_tag 'logo_full.svg', alt: 'Mastodon' = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory = link_to t('about.about_this'), about_more_path, class: 'nav-link optional' diff --git a/app/views/notification_mailer/favourite.html.haml b/app/views/notification_mailer/favourite.html.haml index 7d1b494d0..a715d615c 100644 --- a/app/views/notification_mailer/favourite.html.haml +++ b/app/views/notification_mailer/favourite.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_grade.png'), alt:'' + = image_tag full_pack_url('media/images/mailer/icon_grade.png'), alt:'' %h1= t 'notification_mailer.favourite.title' %p.lead= t('notification_mailer.favourite.body', name: @account.acct) diff --git a/app/views/notification_mailer/follow.html.haml b/app/views/notification_mailer/follow.html.haml index 31a2b7445..cd84f7858 100644 --- a/app/views/notification_mailer/follow.html.haml +++ b/app/views/notification_mailer/follow.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_person_add.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: '' %h1= t 'notification_mailer.follow.title' %p.lead= t('notification_mailer.follow.body', name: @account.acct) diff --git a/app/views/notification_mailer/follow_request.html.haml b/app/views/notification_mailer/follow_request.html.haml index 44f1911c4..a63e27a90 100644 --- a/app/views/notification_mailer/follow_request.html.haml +++ b/app/views/notification_mailer/follow_request.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_person_add.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: '' %h1= t 'notification_mailer.follow_request.title' %p.lead= t('notification_mailer.follow_request.body', name: @account.acct) diff --git a/app/views/notification_mailer/mention.html.haml b/app/views/notification_mailer/mention.html.haml index 479fed41c..619873cfa 100644 --- a/app/views/notification_mailer/mention.html.haml +++ b/app/views/notification_mailer/mention.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_reply.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_reply.png'), alt: '' %h1= t 'notification_mailer.mention.title' %p.lead= t('notification_mailer.mention.body', name: @status.account.acct) diff --git a/app/views/notification_mailer/reblog.html.haml b/app/views/notification_mailer/reblog.html.haml index 85b202cf9..a2811be23 100644 --- a/app/views/notification_mailer/reblog.html.haml +++ b/app/views/notification_mailer/reblog.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_cached.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_cached.png'), alt: '' %h1= t 'notification_mailer.reblog.title' %p.lead= t('notification_mailer.reblog.body', name: @account.acct) diff --git a/app/views/relationships/_account.html.haml b/app/views/relationships/_account.html.haml new file mode 100644 index 000000000..6c22deb51 --- /dev/null +++ b/app/views/relationships/_account.html.haml @@ -0,0 +1,20 @@ +.batch-table__row + %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox + = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id + .batch-table__row__content.batch-table__row__content--unpadded + %table.accounts-table + %tbody + %tr + %td= account_link_to account + %td.accounts-table__count.optional + = number_to_human account.statuses_count, strip_insignificant_zeros: true + %small= t('accounts.posts', count: account.statuses_count).downcase + %td.accounts-table__count.optional + = number_to_human account.followers_count, strip_insignificant_zeros: true + %small= t('accounts.followers', count: account.followers_count).downcase + %td.accounts-table__count + - if account.last_status_at.present? + %time.time-ago{ datetime: account.last_status_at.iso8601, title: l(account.last_status_at) }= l account.last_status_at + - else + \- + %small= t('accounts.last_active') diff --git a/app/views/relationships/show.html.haml b/app/views/relationships/show.html.haml new file mode 100644 index 000000000..33a43f1a8 --- /dev/null +++ b/app/views/relationships/show.html.haml @@ -0,0 +1,43 @@ +- content_for :page_title do + = t('settings.relationships') + +- content_for :header_tags do + = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous' + +.filters + .filter-subset + %strong= t 'relationships.relationship' + %ul + %li= filter_link_to t('accounts.following', count: current_account.following_count), relationship: nil + %li= filter_link_to t('accounts.followers', count: current_account.followers_count), relationship: 'followed_by' + %li= filter_link_to t('relationships.mutual'), relationship: 'mutual' + + .filter-subset + %strong= t 'relationships.status' + %ul + %li= filter_link_to t('generic.all'), status: nil + %li= filter_link_to t('relationships.active'), status: 'active' + %li= filter_link_to t('relationships.abandoned'), status: 'abandoned' + += form_for(@form, url: relationships_path, method: :patch) do |f| + = hidden_field_tag :page, params[:page] || 1 + = hidden_field_tag :relationship, params[:relationship] + = hidden_field_tag :status, params[:status] + + .batch-table + .batch-table__toolbar + %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('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless followed_by_relationship? + + = f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless following_relationship? + + = f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :block_domains, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship? + .batch-table__body + - if @accounts.empty? + = nothing_here 'nothing-here--under-tabs' + - else + = render partial: 'account', collection: @accounts, locals: { f: f } + += paginate @accounts diff --git a/app/views/settings/follower_domains/show.html.haml b/app/views/settings/follower_domains/show.html.haml deleted file mode 100644 index f1687d4d2..000000000 --- a/app/views/settings/follower_domains/show.html.haml +++ /dev/null @@ -1,34 +0,0 @@ -- content_for :page_title do - = t('settings.followers') - -= form_tag settings_follower_domains_path, method: :patch, class: 'table-form' do - - unless @account.locked? - .warning - %strong - = fa_icon('warning') - = t('followers.unlocked_warning_title') - = t('followers.unlocked_warning_html', lock_link: link_to(t('followers.lock_link'), settings_profile_url)) - - %p= t('followers.explanation_html') - %p= t('followers.true_privacy_html') - - .table-wrapper - %table.table - %thead - %tr - %th - %th= t('followers.domain') - %th= t('followers.followers_count') - %tbody - - @domains.each do |domain| - %tr - %td - = check_box_tag 'select[]', domain.domain, false, disabled: !@account.locked? unless domain.domain.nil? - %td - %samp= domain.domain.presence || Rails.configuration.x.local_domain - %td= number_with_delimiter domain.accounts_from_domain - - .action-pagination - .actions - = button_tag t('followers.purge'), type: :submit, class: 'button', disabled: !@account.locked? - = paginate @domains diff --git a/app/views/shared/_og.html.haml b/app/views/shared/_og.html.haml index 802d8c41d..67238fc8b 100644 --- a/app/views/shared/_og.html.haml +++ b/app/views/shared/_og.html.haml @@ -8,7 +8,7 @@ = opengraph 'og:type', 'website' = opengraph 'og:title', @instance_presenter.site_title = opengraph 'og:description', description -= opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('preview.jpg', protocol: :request)) += opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg', protocol: :request)) = opengraph 'og:image:width', thumbnail ? thumbnail.meta['width'] : '1200' = opengraph 'og:image:height', thumbnail ? thumbnail.meta['height'] : '630' = opengraph 'twitter:card', 'summary_large_image' diff --git a/app/views/user_mailer/backup_ready.html.haml b/app/views/user_mailer/backup_ready.html.haml index d5a4b8b48..85140b08b 100644 --- a/app/views/user_mailer/backup_ready.html.haml +++ b/app/views/user_mailer/backup_ready.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_file_download.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_file_download.png'), alt: '' %h1= t 'user_mailer.backup_ready.title' diff --git a/app/views/user_mailer/confirmation_instructions.html.haml b/app/views/user_mailer/confirmation_instructions.html.haml index 70d0f5a24..39a83faff 100644 --- a/app/views/user_mailer/confirmation_instructions.html.haml +++ b/app/views/user_mailer/confirmation_instructions.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_email.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: '' %h1= t 'devise.mailer.confirmation_instructions.title' diff --git a/app/views/user_mailer/email_changed.html.haml b/app/views/user_mailer/email_changed.html.haml index 0802aaf96..7e91e87ad 100644 --- a/app/views/user_mailer/email_changed.html.haml +++ b/app/views/user_mailer/email_changed.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_email.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: '' %h1= t 'devise.mailer.email_changed.title' %p.lead= t 'devise.mailer.email_changed.explanation' diff --git a/app/views/user_mailer/password_change.html.haml b/app/views/user_mailer/password_change.html.haml index 26314a217..559abf027 100644 --- a/app/views/user_mailer/password_change.html.haml +++ b/app/views/user_mailer/password_change.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_lock_open.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: '' %h1= t 'devise.mailer.password_change.title' %p.lead= t 'devise.mailer.password_change.explanation' diff --git a/app/views/user_mailer/reconfirmation_instructions.html.haml b/app/views/user_mailer/reconfirmation_instructions.html.haml index e3be8e295..7f10ba94f 100644 --- a/app/views/user_mailer/reconfirmation_instructions.html.haml +++ b/app/views/user_mailer/reconfirmation_instructions.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_email.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: '' %h1= t 'devise.mailer.reconfirmation_instructions.title' %p.lead= t 'devise.mailer.reconfirmation_instructions.explanation' diff --git a/app/views/user_mailer/reset_password_instructions.html.haml b/app/views/user_mailer/reset_password_instructions.html.haml index 5d9ce6a75..eeed38c9e 100644 --- a/app/views/user_mailer/reset_password_instructions.html.haml +++ b/app/views/user_mailer/reset_password_instructions.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_lock_open.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: '' %h1= t 'devise.mailer.reset_password_instructions.title' %p.lead= t 'devise.mailer.reset_password_instructions.explanation' diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml index c5e1f5a28..72ea5e5d2 100644 --- a/app/views/user_mailer/warning.html.haml +++ b/app/views/user_mailer/warning.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_warning.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_warning.png'), alt: '' %h1= t "user_mailer.warning.title.#{@warning.action}" diff --git a/app/views/user_mailer/welcome.html.haml b/app/views/user_mailer/welcome.html.haml index 4a5788bf6..1f75ff48a 100644 --- a/app/views/user_mailer/welcome.html.haml +++ b/app/views/user_mailer/welcome.html.haml @@ -17,7 +17,7 @@ %tbody %tr %td - = image_tag full_pack_url('icon_done.png'), alt: '' + = image_tag full_pack_url('media/images/mailer/icon_done.png'), alt: '' %h1= t 'user_mailer.welcome.title', name: @resource.account.username %p.lead= t 'user_mailer.welcome.explanation' |