diff --git a/Gemfile.lock b/Gemfile.lock
index 865686a5bd..86cfaa3132 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -395,7 +395,7 @@ GEM
rexml
link_header (0.0.8)
lint_roller (1.1.0)
- linzer (0.6.4)
+ linzer (0.6.5)
openssl (~> 3.0, >= 3.0.0)
rack (>= 2.2, < 4.0)
starry (~> 0.2)
diff --git a/app/javascript/mastodon/components/account.tsx b/app/javascript/mastodon/components/account.tsx
index 55f1e6fb91..f6241504f6 100644
--- a/app/javascript/mastodon/components/account.tsx
+++ b/app/javascript/mastodon/components/account.tsx
@@ -1,4 +1,4 @@
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
@@ -12,6 +12,7 @@ import {
muteAccount,
unmuteAccount,
} from 'mastodon/actions/accounts';
+import { openModal } from 'mastodon/actions/modal';
import { initMuteModal } from 'mastodon/actions/mutes';
import { Avatar } from 'mastodon/components/avatar';
import { Button } from 'mastodon/components/button';
@@ -23,7 +24,7 @@ import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
import { VerifiedBadge } from 'mastodon/components/verified_badge';
-import { me } from 'mastodon/initial_state';
+import type { MenuItem } from 'mastodon/models/dropdown_menu';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
const messages = defineMessages({
@@ -46,6 +47,14 @@ const messages = defineMessages({
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
block: { id: 'account.block_short', defaultMessage: 'Block' },
more: { id: 'status.more', defaultMessage: 'More' },
+ addToLists: {
+ id: 'account.add_or_remove_from_list',
+ defaultMessage: 'Add or Remove from lists',
+ },
+ openOriginalPage: {
+ id: 'account.open_original_page',
+ defaultMessage: 'Open original page',
+ },
});
export const Account: React.FC<{
@@ -60,6 +69,7 @@ export const Account: React.FC<{
const account = useAppSelector((state) => state.accounts.get(id));
const relationship = useAppSelector((state) => state.relationships.get(id));
const dispatch = useAppDispatch();
+ const accountUrl = account?.url;
const handleBlock = useCallback(() => {
if (relationship?.blocking) {
@@ -77,13 +87,62 @@ export const Account: React.FC<{
}
}, [dispatch, id, account, relationship]);
- const handleMuteNotifications = useCallback(() => {
- dispatch(muteAccount(id, true));
- }, [dispatch, id]);
+ const menu = useMemo(() => {
+ let arr: MenuItem[] = [];
- const handleUnmuteNotifications = useCallback(() => {
- dispatch(muteAccount(id, false));
- }, [dispatch, id]);
+ if (defaultAction === 'mute') {
+ const handleMuteNotifications = () => {
+ dispatch(muteAccount(id, true));
+ };
+
+ const handleUnmuteNotifications = () => {
+ dispatch(muteAccount(id, false));
+ };
+
+ arr = [
+ {
+ text: intl.formatMessage(
+ relationship?.muting_notifications
+ ? messages.unmute_notifications
+ : messages.mute_notifications,
+ ),
+ action: relationship?.muting_notifications
+ ? handleUnmuteNotifications
+ : handleMuteNotifications,
+ },
+ ];
+ } else if (defaultAction !== 'block') {
+ const handleAddToLists = () => {
+ dispatch(
+ openModal({
+ modalType: 'LIST_ADDER',
+ modalProps: {
+ accountId: id,
+ },
+ }),
+ );
+ };
+
+ arr = [
+ {
+ text: intl.formatMessage(messages.addToLists),
+ action: handleAddToLists,
+ },
+ ];
+
+ if (accountUrl) {
+ arr.unshift(
+ {
+ text: intl.formatMessage(messages.openOriginalPage),
+ href: accountUrl,
+ },
+ null,
+ );
+ }
+ }
+
+ return arr;
+ }, [dispatch, intl, id, accountUrl, relationship, defaultAction]);
if (hidden) {
return (
@@ -94,68 +153,42 @@ export const Account: React.FC<{
);
}
- let buttons;
+ let button: React.ReactNode, dropdown: React.ReactNode;
- if (account && account.id !== me && relationship) {
- const { requested, blocking, muting } = relationship;
-
- if (requested) {
- buttons = ;
- } else if (blocking) {
- buttons = (
-
- );
- } else if (muting) {
- const menu = [
- {
- text: intl.formatMessage(
- relationship.muting_notifications
- ? messages.unmute_notifications
- : messages.mute_notifications,
- ),
- action: relationship.muting_notifications
- ? handleUnmuteNotifications
- : handleMuteNotifications,
- },
- ];
-
- buttons = (
- <>
-
-
-
- >
- );
- } else if (defaultAction === 'mute') {
- buttons = (
-
- );
- } else if (defaultAction === 'block') {
- buttons = (
-
- );
- } else {
- buttons = ;
- }
- } else {
- buttons = ;
+ if (menu.length > 0) {
+ dropdown = (
+
+ );
}
- let muteTimeRemaining;
+ if (defaultAction === 'block') {
+ button = (
+
+ );
+ } else if (defaultAction === 'mute') {
+ button = (
+
+ );
+ } else {
+ button = ;
+ }
+
+ let muteTimeRemaining: React.ReactNode;
if (account?.mute_expires_at) {
muteTimeRemaining = (
@@ -165,7 +198,7 @@ export const Account: React.FC<{
);
}
- let verification;
+ let verification: React.ReactNode;
const firstVerifiedField = account?.fields.find((item) => !!item.verified_at);
@@ -211,7 +244,12 @@ export const Account: React.FC<{
- {!minimal &&
{buttons}
}
+ {!minimal && (
+
+ {dropdown}
+ {button}
+
+ )}
{account &&
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index f4758e5afb..27f0bf4e9a 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -65,6 +65,7 @@
"account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
"account.unblock": "رفع مسدودیت @{name}",
"account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}",
+ "account.unblock_domain_short": "آنبلاک",
"account.unblock_short": "رفع مسدودیت",
"account.unendorse": "معرّفی نکردن در نمایه",
"account.unfollow": "پینگرفتن",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 3ac1946503..5279159d79 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -65,6 +65,7 @@
"account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}",
"account.unblock": "Desbloquear @{name}",
"account.unblock_domain": "Desbloquear domínio {domain}",
+ "account.unblock_domain_short": "Desbloquear",
"account.unblock_short": "Desbloquear",
"account.unendorse": "Remover",
"account.unfollow": "Deixar de seguir",
diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
index f1b6dba040..0d3fd8db33 100644
--- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb
+++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
@@ -45,7 +45,6 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension
PreviewCard.sum(:image_file_size),
Account.sum(Arel.sql('COALESCE(avatar_file_size, 0) + COALESCE(header_file_size, 0)')),
Backup.sum(:dump_file_size),
- Import.sum(:data_file_size),
SiteUpload.sum(:file_file_size),
].sum
diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb
index 34f6199ec0..676e9e62a0 100644
--- a/app/lib/status_cache_hydrator.rb
+++ b/app/lib/status_cache_hydrator.rb
@@ -26,12 +26,7 @@ class StatusCacheHydrator
def hydrate_non_reblog_payload(empty_payload, account_id)
empty_payload.tap do |payload|
- payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.id)
- payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.id)
- payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.conversation_id)
- payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.id)
- payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.id) if @status.account_id == account_id
- payload[:filtered] = mapped_applied_custom_filter(account_id, @status)
+ fill_status_payload(payload, @status, account_id)
if payload[:poll]
payload[:poll][:voted] = @status.account_id == account_id
@@ -45,18 +40,12 @@ class StatusCacheHydrator
payload[:muted] = false
payload[:bookmarked] = false
payload[:pinned] = false if @status.account_id == account_id
- payload[:filtered] = mapped_applied_custom_filter(account_id, @status.reblog)
# If the reblogged status is being delivered to the author who disabled the display of the application
# used to create the status, we need to hydrate it here too
payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id
- payload[:reblog][:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.reblog_of_id)
- payload[:reblog][:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.reblog_of_id)
- payload[:reblog][:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.reblog.conversation_id)
- payload[:reblog][:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.reblog_of_id)
- payload[:reblog][:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.reblog_of_id) if @status.reblog.account_id == account_id
- payload[:reblog][:filtered] = payload[:filtered]
+ fill_status_payload(payload[:reblog], @status.reblog, account_id)
if payload[:reblog][:poll]
if @status.reblog.account_id == account_id
@@ -69,11 +58,21 @@ class StatusCacheHydrator
end
end
+ payload[:filtered] = payload[:reblog][:filtered]
payload[:favourited] = payload[:reblog][:favourited]
payload[:reblogged] = payload[:reblog][:reblogged]
end
end
+ def fill_status_payload(payload, status, account_id)
+ payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: status.id)
+ payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: status.id)
+ payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: status.conversation_id)
+ payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: status.id)
+ payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: status.id) if status.account_id == account_id
+ payload[:filtered] = mapped_applied_custom_filter(account_id, status)
+ end
+
def mapped_applied_custom_filter(account_id, status)
CustomFilter
.apply_cached_filters(CustomFilter.cached_filters_for(account_id), status)
diff --git a/app/models/import.rb b/app/models/import.rb
deleted file mode 100644
index 4bdb392014..0000000000
--- a/app/models/import.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# == Schema Information
-#
-# Table name: imports
-#
-# id :bigint(8) not null, primary key
-# type :integer not null
-# approved :boolean default(FALSE), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# data_file_name :string
-# data_content_type :string
-# data_file_size :integer
-# data_updated_at :datetime
-# account_id :bigint(8) not null
-# overwrite :boolean default(FALSE), not null
-#
-
-# NOTE: This is a deprecated model, only kept to not break ongoing imports
-# on upgrade. See `BulkImport` and `Form::Import` for its replacements.
-
-class Import < ApplicationRecord
- FILE_TYPES = %w(text/plain text/csv application/csv).freeze
- MODES = %i(merge overwrite).freeze
-
- self.inheritance_column = false
-
- belongs_to :account
-
- enum :type, { following: 0, blocking: 1, muting: 2, domain_blocking: 3, bookmarks: 4 }
-
- validates :type, presence: true
-
- has_attached_file :data
- validates_attachment_content_type :data, content_type: FILE_TYPES
- validates_attachment_presence :data
-
- def mode
- overwrite? ? :overwrite : :merge
- end
-
- def mode=(str)
- self.overwrite = str.to_sym == :overwrite
- end
-end
diff --git a/app/services/import_service.rb b/app/services/import_service.rb
deleted file mode 100644
index a695df2fc9..0000000000
--- a/app/services/import_service.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-# frozen_string_literal: true
-
-require 'csv'
-
-# NOTE: This is a deprecated service, only kept to not break ongoing imports
-# on upgrade. See `BulkImportService` for its replacement.
-
-class ImportService < BaseService
- ROWS_PROCESSING_LIMIT = 20_000
-
- def call(import)
- @import = import
- @account = @import.account
-
- case @import.type
- when 'following'
- import_follows!
- when 'blocking'
- import_blocks!
- when 'muting'
- import_mutes!
- when 'domain_blocking'
- import_domain_blocks!
- when 'bookmarks'
- import_bookmarks!
- end
- end
-
- private
-
- def import_follows!
- parse_import_data!(['Account address'])
- import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true }, notify: { header: 'Notify on new posts', default: false }, languages: { header: 'Languages', default: nil })
- end
-
- def import_blocks!
- parse_import_data!(['Account address'])
- import_relationships!('block', 'unblock', @account.blocking, ROWS_PROCESSING_LIMIT)
- end
-
- def import_mutes!
- parse_import_data!(['Account address'])
- import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT, notifications: { header: 'Hide notifications', default: true })
- end
-
- def import_domain_blocks!
- parse_import_data!(['#domain'])
- items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#domain'].strip }
-
- if @import.overwrite?
- presence_hash = items.index_with(true)
-
- @account.domain_blocks.find_each do |domain_block|
- if presence_hash[domain_block.domain]
- items.delete(domain_block.domain)
- else
- @account.unblock_domain!(domain_block.domain)
- end
- end
- end
-
- items.each do |domain|
- @account.block_domain!(domain)
- end
-
- AfterAccountDomainBlockWorker.push_bulk(items) do |domain|
- [@account.id, domain]
- end
- end
-
- def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {})
- local_domain_suffix = "@#{Rails.configuration.x.local_domain}"
- items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), extra_fields.to_h { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }] }.reject { |(id, _)| id.blank? }
-
- if @import.overwrite?
- presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] }
-
- overwrite_scope.reorder(nil).find_each do |target_account|
- if presence_hash[target_account.acct]
- items.delete(target_account.acct)
- extra = presence_hash[target_account.acct][1]
- Import::RelationshipWorker.perform_async(@account.id, target_account.acct, action, extra.stringify_keys)
- else
- Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action)
- end
- end
- end
-
- head_items = items.uniq { |acct, _| acct.split('@')[1] }
- tail_items = items - head_items
-
- Import::RelationshipWorker.push_bulk(head_items + tail_items) do |acct, extra|
- [@account.id, acct, action, extra.stringify_keys]
- end
- end
-
- def import_bookmarks!
- parse_import_data!(['#uri'])
- items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#uri'].strip }
-
- if @import.overwrite?
- presence_hash = items.index_with(true)
-
- @account.bookmarks.find_each do |bookmark|
- if presence_hash[bookmark.status.uri]
- items.delete(bookmark.status.uri)
- else
- bookmark.destroy!
- end
- end
- end
-
- statuses = items.filter_map do |uri|
- status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
- next if status.nil? && ActivityPub::TagManager.instance.local_uri?(uri)
-
- status || ActivityPub::FetchRemoteStatusService.new.call(uri)
- rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError
- nil
- rescue => e
- Rails.logger.warn "Unexpected error when importing bookmark: #{e}"
- nil
- end
-
- account_ids = statuses.map(&:account_id)
- preloaded_relations = @account.relations_map(account_ids, skip_blocking_and_muting: true)
-
- statuses.keep_if { |status| StatusPolicy.new(@account, status, preloaded_relations).show? }
-
- statuses.each do |status|
- @account.bookmarks.find_or_create_by!(account: @account, status: status)
- end
- end
-
- def parse_import_data!(default_headers)
- data = CSV.parse(import_data, headers: true)
- data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(' ')
- @data = data.compact_blank
- end
-
- def import_data
- Paperclip.io_adapters.for(@import.data).read.force_encoding(Encoding::UTF_8)
- end
-end
diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb
deleted file mode 100644
index 2298b095a7..0000000000
--- a/app/workers/import/relationship_worker.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-# NOTE: This is a deprecated worker, only kept to not break ongoing imports
-# on upgrade. See `Import::RowWorker` for its replacement.
-
-class Import::RelationshipWorker
- include Sidekiq::Worker
-
- sidekiq_options queue: 'pull', retry: 8, dead: false
-
- def perform(account_id, target_account_uri, relationship, options)
- from_account = Account.find(account_id)
- target_domain = domain(target_account_uri)
- target_account = stoplight_wrapper(target_domain).run { ResolveAccountService.new.call(target_account_uri, { check_delivery_availability: true }) }
- options.symbolize_keys!
-
- return if target_account.nil?
-
- case relationship
- when 'follow'
- begin
- FollowService.new.call(from_account, target_account, **options)
- rescue ActiveRecord::RecordInvalid
- raise if FollowLimitValidator.limit_for_account(from_account) < from_account.following_count
- end
- when 'unfollow'
- UnfollowService.new.call(from_account, target_account)
- when 'block'
- BlockService.new.call(from_account, target_account)
- when 'unblock'
- UnblockService.new.call(from_account, target_account)
- when 'mute'
- MuteService.new.call(from_account, target_account, **options)
- when 'unmute'
- UnmuteService.new.call(from_account, target_account)
- end
- rescue ActiveRecord::RecordNotFound
- true
- end
-
- def domain(uri)
- domain = uri.is_a?(Account) ? uri.domain : uri.split('@')[1]
- TagManager.instance.local_domain?(domain) ? nil : TagManager.instance.normalize_domain(domain)
- end
-
- def stoplight_wrapper(domain)
- if domain.present?
- Stoplight("source:#{domain}")
- .with_fallback { nil }
- .with_threshold(1)
- .with_cool_off_time(5.minutes.seconds)
- .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) }
- else
- Stoplight('domain-blank')
- end
- end
-end
diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb
deleted file mode 100644
index b6afb972a9..0000000000
--- a/app/workers/import_worker.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-# NOTE: This is a deprecated worker, only kept to not break ongoing imports
-# on upgrade. See `ImportWorker` for its replacement.
-
-class ImportWorker
- include Sidekiq::Worker
-
- sidekiq_options queue: 'pull', retry: false
-
- def perform(import_id)
- import = Import.find(import_id)
- ImportService.new.call(import)
- ensure
- import&.destroy
- end
-end
diff --git a/app/workers/unfilter_notifications_worker.rb b/app/workers/unfilter_notifications_worker.rb
index 53a35ce12c..cb8a46b8f4 100644
--- a/app/workers/unfilter_notifications_worker.rb
+++ b/app/workers/unfilter_notifications_worker.rb
@@ -4,25 +4,14 @@ class UnfilterNotificationsWorker
include Sidekiq::Worker
include Redisable
- # Earlier versions of the feature passed a `notification_request` ID
- # If `to_account_id` is passed, the first argument is an account ID
- # TODO for after 4.3.0: drop the single-argument case
- def perform(notification_request_or_account_id, from_account_id = nil)
- if from_account_id.present?
- @notification_request = nil
- @from_account = Account.find_by(id: from_account_id)
- @recipient = Account.find_by(id: notification_request_or_account_id)
- else
- @notification_request = NotificationRequest.find_by(id: notification_request_or_account_id)
- @from_account = @notification_request&.from_account
- @recipient = @notification_request&.account
- end
+ def perform(account_id, from_account_id)
+ @from_account = Account.find_by(id: from_account_id)
+ @recipient = Account.find_by(id: account_id)
return if @from_account.nil? || @recipient.nil?
push_to_conversations!
unfilter_notifications!
- remove_request!
decrement_worker_count!
end
@@ -36,10 +25,6 @@ class UnfilterNotificationsWorker
filtered_notifications.in_batches.update_all(filtered: false)
end
- def remove_request!
- @notification_request&.destroy!
- end
-
def filtered_notifications
Notification.where(account: @recipient, from_account: @from_account, filtered: true)
end
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index ed16d50a76..6d908fa477 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -169,7 +169,7 @@ else
end
Rails.application.reloader.to_prepare do
- Paperclip.options[:content_type_mappings] = { csv: Import::FILE_TYPES }
+ Paperclip.options[:content_type_mappings] = { csv: %w(text/plain text/csv application/csv) }
end
# In some places in the code, we rescue this exception, but we don't always
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index b5dce6dabf..f1c74829c9 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -479,6 +479,22 @@ fa:
new:
title: درونریزی انسدادهای دامنه
no_file: هیچ پروندهای گزیده نشده
+ fasp:
+ debug:
+ callbacks:
+ delete: حذف
+ providers:
+ active: فعال
+ delete: حذف
+ finish_registration: تکمیل ثبتنام
+ name: نام
+ providers: ارائه دهندگان
+ registrations:
+ confirm: تایید
+ reject: رد کردن
+ save: ذخیره
+ sign_in: ورود
+ status: وضعیت
follow_recommendations:
description_html: "پیشنهادات پیگیری به کاربران جدید کک میکند تا سریعتر محتوای جالب را پیدا کنند. زمانی که کاربری هنوز به اندازه کافی با دیگران تعامل نداشته است تا پیشنهادات پیگیری شخصیسازیشده دریافت کند، این حسابها را به جای آن فهرست مشاهده خواهد کرد. این حسابها به صورت روزانه و در ترکیب با بیشتری تعاملات و بالاترین دنبالکنندگان محلی برای یک زبان مشخص بازمحاسبه میشوند."
language: برای زبان
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 151cc6cf6c..fad8a2609e 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -259,6 +259,7 @@ lv:
create_user_role_html: "%{name} nomainīja %{target} lomu"
demote_user_html: "%{name} pazemināja lietotāju %{target}"
destroy_announcement_html: "%{name} izdzēsa paziņojumu %{target}"
+ destroy_canonical_email_block_html: "%{name} atcēla e-pasta adreses liegumu ar jaucējvērtību %{target}"
destroy_custom_emoji_html: "%{name} izdzēsa emocijzīmi %{target}"
destroy_domain_allow_html: "%{name} neatļāva federāciju ar domēnu %{target}"
destroy_domain_block_html: "%{name} atbloķēja domēnu %{target}"
@@ -409,7 +410,7 @@ lv:
permanent_action: Apturēšanas atsaukšana neatjaunos nekādus datus vai attiecības.
preamble_html: Tu gatavojies apturēt domēna %{domain} un tā apakšdomēnu darbību.
remove_all_data: Tādējādi no tava servera tiks noņemts viss šī domēna kontu saturs, multivide un profila dati.
- stop_communication: Jūsu serveris pārtrauks sazināties ar šiem serveriem.
+ stop_communication: Tavs serveris pārtrauks sazināties ar šiem serveriem.
title: Apstiprināt domēna %{domain} bloķēšanu
undo_relationships: Tādējādi tiks atsauktas jebkuras sekošanas attiecības starp šo un tavu serveru kontiem.
created_msg: Domēna bloķēšana tagad tiek apstrādāta
@@ -949,11 +950,13 @@ lv:
message_html: "Tava objektu krātuve ir nepareizi konfigurēta. Tavu lietotāju privātums ir apdraudēts."
tags:
moderation:
+ not_trendable: Nav izplatīts
not_usable: Nav izmantojams
pending_review: Gaida pārskatīšanu
review_requested: Pieprasīta pārskatīšana
reviewed: Pārskatīts
title: Stāvoklis
+ trendable: Izplatīts
unreviewed: Nepārskatīts
usable: Izmantojams
name: Nosaukums
@@ -1312,7 +1315,7 @@ lv:
appeal_approved: Šis brīdinājums tika sekmīgi pārsūdzēts un vairs nav spēkā
appeal_rejected: Apelācija ir noraidīta
appeal_submitted_at: Apelācija iesniegta
- appealed_msg: Jūsu apelācija ir iesniegta. Ja tā tiks apstiprināta, jums tiks paziņots.
+ appealed_msg: Tava pārsūdzība ir iesniegta. Ja tā tiks apstiprināta, Tev tiks paziņots.
appeals:
submit: Iesniegt apelāciju
approve_appeal: Apstiprināt apelāciju
@@ -1332,9 +1335,9 @@ lv:
sensitive: Konta atzīmēšana kā jūtīgu
silence: Konta ierobežošana
suspend: Konta apturēšana
- your_appeal_approved: Jūsu apelācija ir apstiprināta
+ your_appeal_approved: Tava pārsūdzība tika apstiprināta
your_appeal_pending: Jūs esat iesniedzis apelāciju
- your_appeal_rejected: Jūsu apelācija ir noraidīta
+ your_appeal_rejected: Tava pārsūdzība tika noraidīta
edit_profile:
basic_information: Pamata informācija
hint_html: "Pielāgo, ko cilvēki redz Tavā publiskajā profilā un blakus Taviem ierakstiem. Ir lielāka iespējamība, ka citi clivēki sekos Tev un mijiedarbosies ar Tevi, ja Tev ir aizpildīts profils un profila attēls."
@@ -1929,7 +1932,7 @@ lv:
'7889238': 3 mēneši
min_age_label: Vecuma slieksnis
min_favs: Saglabāt ziņas izlsasē vismaz
- min_favs_hint: Nedzēš nevienu jūsu ziņu, kas ir saņēmusi vismaz tik daudz izcēlumu. Atstājiet tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita
+ min_favs_hint: Neizdzēš nevienu no Taviem ierakstiem, kas ir pievienoti šādā daudzumā izlašu. Atstāt tukšu, lai izdzēstu ierakstus neatkarīgi no tā, cik izlasēs tie ir pievienoti
min_reblogs: Saglabāt ziņas izceltas vismaz
min_reblogs_hint: Neizdzēš nevienu no tavām ziņām, kas ir izceltas vismaz tik reižu. Atstāj tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita
stream_entries:
@@ -1978,12 +1981,13 @@ lv:
title: "%{domain} pakalpojuma paziņojums"
appeal_approved:
action: Konta iestatījumi
- explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir apstiprināta. Jūsu konts atkal ir labā stāvoklī.
- subject: Jūsu %{date} apelācija ir apstiprināta
+ explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, ir apstiprināta. Tavs konts atkal ir labā stāvoklī.
+ subject: Tava %{date} iesniegtā pārsūdzība tika apstiprināta
title: Apelācija apstiprināta
appeal_rejected:
- explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir noraidīta.
- subject: Jūsu %{date} apelācija ir noraidīta
+ explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, tika noraidīta.
+ subject: Tava %{date} iesniegta pārsūdzība tika noraidīta
+ subtitle: Tava pārsūdzība tika noraidīta.
title: Apelācija noraidīta
backup_ready:
explanation: Tu pieprasīji pilnu sava Mastodon konta rezerves kopiju.
@@ -1993,6 +1997,9 @@ lv:
failed_2fa:
details: 'Šeit ir informācija par pieteikšanās mēģinājumu:'
explanation: Kāds mēģināja pieteikties Tavā kontā, bet norādīja nederīgu otro autentificēšanās soli.
+ further_actions_html: Ja tas nebiji Tu, mēs iesakām nekavējoties %{action}, jo var būt noticis drošības pārkāpums.
+ subject: Otrās pakāpes autentificēšanās atteice
+ title: Neizdevās otrās pakāpes autentificēšanās
suspicious_sign_in:
change_password: mainīt paroli
details: 'Šeit ir pieteikšanās izvērsums:'
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 6a8fdda024..a2e142ab55 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -320,6 +320,7 @@ pt-BR:
title: Novo anúncio
preview:
explanation_html: 'Esse e-mail será enviado a %{display_count} usuários. O texto a seguir será incluído ao e-mail:'
+ title: Visualizar anúncio
publish: Publicar
published_msg: Anúncio publicado!
scheduled_for: Agendado para %{time}
@@ -484,19 +485,30 @@ pt-BR:
created_at: Criado em
delete: Apagar
ip: Endereço de IP
+ request_body: Corpo da solicitação
+ title: Depurar Callbacks
providers:
+ active: Ativo
base_url: URL Base
+ callback: Callback
delete: Apagar
+ edit: Editar provedor
finish_registration: Finalizar o cadastro
name: Nome
+ providers: Provedores
public_key_fingerprint: Impressão digital de chave pública
registration_requested: Cadastro solicitado
registrations:
confirm: Confirmar
+ description: Você recebeu um registro de um FASP. Rejeite se você não tiver iniciado isso. Se você iniciou isso, compare cuidadosamente o nome e a impressão digital da chave antes de confirmar o registro.
reject: Rejeitar
+ title: Confirmar o registro FASP
save: Salvar
+ select_capabilities: Selecionar recursos
sign_in: Entrar
status: Estado
+ title: Provedores de serviços auxiliares do Fediverso
+ title: FASP
follow_recommendations:
description_html: "A recomendação de contas ajuda os novos usuários a encontrar rapidamente conteúdo interessante. Quando um usuário ainda não tiver interagido o suficiente para gerar recomendações de contas, essas contas serão recomendadas. Essas recomendações são recalculadas diariamente a partir de uma lista de contas com alto engajamento e maior número de seguidores locais em uma dada língua."
language: Na língua
@@ -1927,6 +1939,10 @@ pt-BR:
recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. Mantenha os códigos de recuperação em um local seguro. Por exemplo, você pode imprimi-los e guardá-los junto a outros documentos importantes.
webauthn: Chaves de segurança
user_mailer:
+ announcement_published:
+ description: 'Os administradores do %{domain} estão fazendo um anúncio:'
+ subject: Anúncio de serviço
+ title: Anúncio de serviço de %{domain}
appeal_approved:
action: Configurações da conta
explanation: A revisão da punição na sua conta em %{strike_date} que você enviou em %{appeal_date} foi aprovada. Sua conta está novamente em situação regular.
diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml
index 520b684847..9f46cdec7d 100644
--- a/config/locales/simple_form.fa.yml
+++ b/config/locales/simple_form.fa.yml
@@ -349,6 +349,9 @@ fa:
jurisdiction: صلاحیت قانونی
min_age: کمینهٔ زمان
user:
+ date_of_birth_1i: روز
+ date_of_birth_2i: ماه
+ date_of_birth_3i: سال
role: نقش
time_zone: منطقهٔ زمانی
user_role:
diff --git a/db/migrate/20250410144908_drop_imports.rb b/db/migrate/20250410144908_drop_imports.rb
new file mode 100644
index 0000000000..7be9daf750
--- /dev/null
+++ b/db/migrate/20250410144908_drop_imports.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DropImports < ActiveRecord::Migration[7.1]
+ def up
+ drop_table :imports
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 30b0e8184a..cf79ec35ab 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do
+ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@@ -555,19 +555,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do
t.index ["user_id"], name: "index_identities_on_user_id"
end
- create_table "imports", force: :cascade do |t|
- t.integer "type", null: false
- t.boolean "approved", default: false, null: false
- t.datetime "created_at", precision: nil, null: false
- t.datetime "updated_at", precision: nil, null: false
- t.string "data_file_name"
- t.string "data_content_type"
- t.integer "data_file_size"
- t.datetime "data_updated_at", precision: nil
- t.bigint "account_id", null: false
- t.boolean "overwrite", default: false, null: false
- end
-
create_table "invites", force: :cascade do |t|
t.bigint "user_id", null: false
t.string "code", default: "", null: false
@@ -1332,7 +1319,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do
add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
add_foreign_key "generated_annual_reports", "accounts"
add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
- add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
add_foreign_key "invites", "users", on_delete: :cascade
add_foreign_key "list_accounts", "accounts", on_delete: :cascade
add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade
diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb
index 84ec13eaab..1059eb6066 100644
--- a/lib/mastodon/cli/media.rb
+++ b/lib/mastodon/cli/media.rb
@@ -293,7 +293,6 @@ module Mastodon::CLI
Account
Backup
CustomEmoji
- Import
MediaAttachment
PreviewCard
SiteUpload
@@ -309,7 +308,6 @@ module Mastodon::CLI
[:headers, Account.sum(:header_file_size), Account.local.sum(:header_file_size)],
[:preview_cards, PreviewCard.sum(:image_file_size), nil],
[:backups, Backup.sum(:dump_file_size), nil],
- [:imports, Import.sum(:data_file_size), nil],
[:settings, SiteUpload.sum(:file_file_size), nil],
].map { |label, total, local| [label.to_s.titleize, number_to_human_size(total), local.present? ? number_to_human_size(local) : nil] }
end
diff --git a/spec/fabricators/import_fabricator.rb b/spec/fabricators/import_fabricator.rb
deleted file mode 100644
index 4951bb9a4d..0000000000
--- a/spec/fabricators/import_fabricator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-Fabricator(:import) do
- account
- type :following
- data { attachment_fixture('imports.txt') }
-end
diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb
deleted file mode 100644
index 587e0a9d26..0000000000
--- a/spec/models/import_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Import do
- describe 'Validations' do
- it { is_expected.to validate_presence_of(:type) }
- it { is_expected.to validate_presence_of(:data) }
- end
-end
diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb
deleted file mode 100644
index 2e1358c635..0000000000
--- a/spec/services/import_service_spec.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ImportService, :inline_jobs do
- include RoutingHelper
-
- let!(:account) { Fabricate(:account, locked: false) }
- let!(:bob) { Fabricate(:account, username: 'bob', locked: false) }
- let!(:eve) { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') }
-
- before do
- stub_request(:post, 'https://example.com/inbox').to_return(status: 200)
- end
-
- context 'when importing old-style list of muted users' do
- subject { described_class.new }
-
- let(:csv) { attachment_fixture('mute-imports.txt') }
-
- describe 'when no accounts are muted' do
- let(:import) { Import.create(account: account, type: 'muting', data: csv) }
-
- it 'mutes the listed accounts, including notifications' do
- subject.call(import)
- expect(account.muting.count).to eq 2
- expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
- end
- end
-
- describe 'when some accounts are muted and overwrite is not set' do
- let(:import) { Import.create(account: account, type: 'muting', data: csv) }
-
- it 'mutes the listed accounts, including notifications' do
- account.mute!(bob, notifications: false)
- subject.call(import)
- expect(account.muting.count).to eq 2
- expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
- end
- end
-
- describe 'when some accounts are muted and overwrite is set' do
- let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }
-
- it 'mutes the listed accounts, including notifications' do
- account.mute!(bob, notifications: false)
- subject.call(import)
- expect(account.muting.count).to eq 2
- expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
- end
- end
- end
-
- context 'when importing new-style list of muted users' do
- subject { described_class.new }
-
- let(:csv) { attachment_fixture('new-mute-imports.txt') }
-
- describe 'when no accounts are muted' do
- let(:import) { Import.create(account: account, type: 'muting', data: csv) }
-
- it 'mutes the listed accounts, respecting notifications' do
- subject.call(import)
- expect(account.muting.count).to eq 2
- expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
- expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
- end
- end
-
- describe 'when some accounts are muted and overwrite is not set' do
- let(:import) { Import.create(account: account, type: 'muting', data: csv) }
-
- it 'mutes the listed accounts, respecting notifications' do
- account.mute!(bob, notifications: true)
- subject.call(import)
- expect(account.muting.count).to eq 2
- expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
- expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
- end
- end
-
- describe 'when some accounts are muted and overwrite is set' do
- let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }
-
- it 'mutes the listed accounts, respecting notifications' do
- account.mute!(bob, notifications: true)
- subject.call(import)
- expect(account.muting.count).to eq 2
- expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
- expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
- end
- end
- end
-
- context 'when importing old-style list of followed users' do
- subject { described_class.new }
-
- let(:csv) { attachment_fixture('mute-imports.txt') }
-
- describe 'when no accounts are followed' do
- let(:import) { Import.create(account: account, type: 'following', data: csv) }
-
- it 'follows the listed accounts, including boosts' do
- subject.call(import)
-
- expect(account.following.count).to eq 1
- expect(account.follow_requests.count).to eq 1
- expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
- end
- end
-
- describe 'when some accounts are already followed and overwrite is not set' do
- let(:import) { Import.create(account: account, type: 'following', data: csv) }
-
- it 'follows the listed accounts, including notifications' do
- account.follow!(bob, reblogs: false)
- subject.call(import)
- expect(account.following.count).to eq 1
- expect(account.follow_requests.count).to eq 1
- expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
- end
- end
-
- describe 'when some accounts are already followed and overwrite is set' do
- let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
-
- it 'mutes the listed accounts, including notifications' do
- account.follow!(bob, reblogs: false)
- subject.call(import)
- expect(account.following.count).to eq 1
- expect(account.follow_requests.count).to eq 1
- expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
- end
- end
- end
-
- context 'when importing new-style list of followed users' do
- subject { described_class.new }
-
- let(:csv) { attachment_fixture('new-following-imports.txt') }
-
- describe 'when no accounts are followed' do
- let(:import) { Import.create(account: account, type: 'following', data: csv) }
-
- it 'follows the listed accounts, respecting boosts' do
- subject.call(import)
- expect(account.following.count).to eq 1
- expect(account.follow_requests.count).to eq 1
- expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
- expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
- end
- end
-
- describe 'when some accounts are already followed and overwrite is not set' do
- let(:import) { Import.create(account: account, type: 'following', data: csv) }
-
- it 'mutes the listed accounts, respecting notifications' do
- account.follow!(bob, reblogs: true)
- subject.call(import)
- expect(account.following.count).to eq 1
- expect(account.follow_requests.count).to eq 1
- expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
- expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
- end
- end
-
- describe 'when some accounts are already followed and overwrite is set' do
- let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
-
- it 'mutes the listed accounts, respecting notifications' do
- account.follow!(bob, reblogs: true)
- subject.call(import)
- expect(account.following.count).to eq 1
- expect(account.follow_requests.count).to eq 1
- expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
- expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
- end
- end
- end
-
- # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users
- #
- # https://github.com/mastodon/mastodon/issues/20571
- context 'with a utf-8 encoded domains' do
- subject { described_class.new }
-
- let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') }
- let(:csv) { attachment_fixture('utf8-followers.txt') }
- let(:import) { Import.create(account: account, type: 'following', data: csv) }
-
- # Make sure to not actually go to the remote server
- before do
- stub_request(:post, nare.inbox_url).to_return(status: 200)
- end
-
- it 'follows the listed account' do
- expect(account.follow_requests.count).to eq 0
- subject.call(import)
- expect(account.follow_requests.count).to eq 1
- end
- end
-
- context 'when importing bookmarks' do
- subject { described_class.new }
-
- let(:csv) { attachment_fixture('bookmark-imports.txt') }
- let(:local_account) { Fabricate(:account, username: 'foo', domain: nil) }
- let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') }
- let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) }
-
- around do |example|
- local_before = Rails.configuration.x.local_domain
- web_before = Rails.configuration.x.web_domain
- Rails.configuration.x.local_domain = 'local.com'
- Rails.configuration.x.web_domain = 'local.com'
- example.run
- Rails.configuration.x.web_domain = web_before
- Rails.configuration.x.local_domain = local_before
- end
-
- before do
- service = instance_double(ActivityPub::FetchRemoteStatusService)
- allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service)
- allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do
- Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1')
- end
- end
-
- describe 'when no bookmarks are set' do
- let(:import) { Import.create(account: account, type: 'bookmarks', data: csv) }
-
- it 'adds the toots the user has access to to bookmarks' do
- local_status = Fabricate(:status, account: local_account, uri: 'https://local.com/users/foo/statuses/42', id: 42, local: true)
- subject.call(import)
- expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(local_status.id)
- expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(remote_status.id)
- expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to_not include(direct_status.id)
- expect(account.bookmarks.count).to eq 3
- end
- end
- end
-end
diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb
index ffbba177b3..44bbc64a59 100644
--- a/spec/support/system_helpers.rb
+++ b/spec/support/system_helpers.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module SystemHelpers
+ FRONTEND_TRANSLATIONS = JSON.parse Rails.root.join('app', 'javascript', 'mastodon', 'locales', 'en.json').read
+
def submit_button
I18n.t('generic.save_changes')
end
@@ -16,4 +18,8 @@ module SystemHelpers
def css_id(record)
"##{dom_id(record)}"
end
+
+ def frontend_translations(key)
+ FRONTEND_TRANSLATIONS[key]
+ end
end
diff --git a/spec/system/account_notes_spec.rb b/spec/system/account_notes_spec.rb
index c4054f204e..1d125e1984 100644
--- a/spec/system/account_notes_spec.rb
+++ b/spec/system/account_notes_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe 'Account notes', :inline_jobs, :js, :streaming do
visit_profile(other_account)
note_text = 'This is a personal note'
- fill_in 'Click to add note', with: note_text
+ fill_in frontend_translations('account_note.placeholder'), with: note_text
# This is a bit awkward since there is no button to save the change
# The easiest way is to send ctrl+enter ourselves
diff --git a/spec/system/log_out_spec.rb b/spec/system/log_out_spec.rb
index 2e52254ca0..ebbf5a5772 100644
--- a/spec/system/log_out_spec.rb
+++ b/spec/system/log_out_spec.rb
@@ -17,8 +17,9 @@ RSpec.describe 'Log out' do
click_on 'Logout'
end
- expect(page).to have_title(I18n.t('auth.login'))
- expect(page).to have_current_path('/auth/sign_in')
+ expect(page)
+ .to have_title(I18n.t('auth.login'))
+ .and have_current_path('/auth/sign_in')
end
end
@@ -28,6 +29,8 @@ RSpec.describe 'Log out' do
ignore_js_error(/Failed to load resource: the server responded with a status of 422/)
visit root_path
+ expect(page)
+ .to have_css('body', class: 'app-body')
within '.navigation-bar' do
click_on 'Menu'
@@ -39,8 +42,9 @@ RSpec.describe 'Log out' do
click_on 'Log out'
- expect(page).to have_title(I18n.t('auth.login'))
- expect(page).to have_current_path('/auth/sign_in')
+ expect(page)
+ .to have_title(I18n.t('auth.login'))
+ .and have_current_path('/auth/sign_in')
end
end
end
diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb
index 480c77cf87..d14bcc0058 100644
--- a/spec/system/new_statuses_spec.rb
+++ b/spec/system/new_statuses_spec.rb
@@ -17,20 +17,7 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
status_text = 'This is a new status!'
within('.compose-form') do
- fill_in "What's on your mind?", with: status_text
- click_on 'Post'
- end
-
- expect(page)
- .to have_css('.status__content__text', text: status_text)
- end
-
- it 'can be posted again' do
- visit_homepage
- status_text = 'This is a second status!'
-
- within('.compose-form') do
- fill_in "What's on your mind?", with: status_text
+ fill_in frontend_translations('compose_form.placeholder'), with: status_text
click_on 'Post'
end
diff --git a/spec/system/share_entrypoint_spec.rb b/spec/system/share_entrypoint_spec.rb
index 7ccfee599a..b55ea31657 100644
--- a/spec/system/share_entrypoint_spec.rb
+++ b/spec/system/share_entrypoint_spec.rb
@@ -23,24 +23,14 @@ RSpec.describe 'Share page', :js, :streaming do
fill_in_form
expect(page)
- .to have_css('.notification-bar-message', text: translations['compose.published.body'])
+ .to have_css('.notification-bar-message', text: frontend_translations('compose.published.body'))
end
def fill_in_form
within('.compose-form') do
- fill_in translations['compose_form.placeholder'],
+ fill_in frontend_translations('compose_form.placeholder'),
with: 'This is a new status!'
- click_on translations['compose_form.publish']
+ click_on frontend_translations('compose_form.publish')
end
end
-
- def translations
- # TODO: Extract to system spec helper for re-use?
- JSON.parse(
- Rails
- .root
- .join('app', 'javascript', 'mastodon', 'locales', 'en.json')
- .read
- )
- end
end
diff --git a/spec/workers/import_worker_spec.rb b/spec/workers/import_worker_spec.rb
deleted file mode 100644
index 1d34aafe86..0000000000
--- a/spec/workers/import_worker_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ImportWorker do
- let(:worker) { described_class.new }
- let(:service) { instance_double(ImportService, call: true) }
-
- describe '#perform' do
- before do
- allow(ImportService).to receive(:new).and_return(service)
- end
-
- let(:import) { Fabricate(:import) }
-
- it 'sends the import to the service' do
- worker.perform(import.id)
-
- expect(service).to have_received(:call).with(import)
- expect { import.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
- end
-end
diff --git a/spec/workers/unfilter_notifications_worker_spec.rb b/spec/workers/unfilter_notifications_worker_spec.rb
index 464a4520ff..2fd130301f 100644
--- a/spec/workers/unfilter_notifications_worker_spec.rb
+++ b/spec/workers/unfilter_notifications_worker_spec.rb
@@ -5,6 +5,7 @@ require 'rails_helper'
RSpec.describe UnfilterNotificationsWorker do
let(:recipient) { Fabricate(:account) }
let(:sender) { Fabricate(:account) }
+ let(:worker) { described_class.new }
before do
# Populate multiple kinds of filtered notifications
@@ -67,23 +68,22 @@ RSpec.describe UnfilterNotificationsWorker do
end
describe '#perform' do
- context 'with single argument (prerelease behavior)' do
- subject { described_class.new.perform(notification_request.id) }
-
- let(:notification_request) { Fabricate(:notification_request, from_account: sender, account: recipient) }
+ context 'with recipient and sender' do
+ subject { worker.perform(recipient.id, sender.id) }
it_behaves_like 'shared behavior'
-
- it 'destroys the notification request' do
- expect { subject }
- .to change { NotificationRequest.exists?(notification_request.id) }.to(false)
- end
end
- context 'with two arguments' do
- subject { described_class.new.perform(recipient.id, sender.id) }
+ context 'with missing records' do
+ it 'runs without error for missing sender' do
+ expect { worker.perform(recipient.id, nil) }
+ .to_not raise_error
+ end
- it_behaves_like 'shared behavior'
+ it 'runs without error for missing recipient' do
+ expect { worker.perform(nil, sender.id) }
+ .to_not raise_error
+ end
end
end
end