Compare commits

...

17 Commits

Author SHA1 Message Date
Evаn Summers
4ed0d62ff6
Merge 5425419b27 into 14cb5ff881 2025-09-03 20:05:44 +00:00
Claire
14cb5ff881
Add compatibility hack for GoToSocial interaction policies (#36004)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
2025-09-03 19:35:43 +00:00
diondiondion
bc952ebde9
Add new plain (text-only) button variant (#36002)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Chromatic / Run Chromatic (push) Has been cancelled
CSS Linting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
2025-09-03 12:34:29 +00:00
renovate[bot]
c1542643f5
fix(deps): update dependency sass to v1.92.0 (#36001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 12:23:00 +00:00
Evan Summers
5425419b27 Add filters to ignore_unused for i18n like other import/export 2025-08-09 10:43:39 +02:00
Evan Summers
c6d846cecd Order filters in export by title to match filters page 2025-08-09 01:50:07 +02:00
Evan Summers
6d70292278 i18n normalize 2025-08-09 01:45:48 +02:00
Evan Summers
df53fb5bde Fix tests 2025-08-08 23:17:02 +02:00
Evan Summers
9474197b43 Partially fix export_spec 2025-08-08 21:32:15 +02:00
Evan Summers
48c8b48df7 Add "whole word" column to some places it was missing 2025-08-08 21:08:06 +02:00
Evаn Summers
66f2543228
Merge branch 'main' into feat/custom-filters-import-export 2025-08-08 21:05:28 +02:00
Evan Summers
6205d42462 Add some tests 2025-08-08 20:48:25 +02:00
Evan Summers
9c9dd66129 Whole word import/export 2025-08-08 20:27:01 +02:00
Evan Summers
3e6fac42c1 Working on tests 2025-08-07 23:40:57 +02:00
Evan Summers
d8725ea554 Import seems to work! 2025-08-07 22:28:58 +02:00
Evan Summers
dbdf20b373 Working on import 2025-08-07 18:27:07 +02:00
Evan Summers
61c4aab3e4 Export seems to work! 2025-08-06 23:10:40 +02:00
22 changed files with 224 additions and 18 deletions

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Settings
module Exports
class FiltersController < BaseController
include Settings::ExportControllerConcern
def index
send_export_file
end
private
def export_data
@export.to_filters_csv
end
end
end
end

View File

@ -13,6 +13,7 @@ class Settings::ImportsController < Settings::BaseController
domain_blocking: 'blocked_domains_failures.csv', domain_blocking: 'blocked_domains_failures.csv',
bookmarks: 'bookmarks_failures.csv', bookmarks: 'bookmarks_failures.csv',
lists: 'lists_failures.csv', lists: 'lists_failures.csv',
filters: 'filters_failures.csv',
}.freeze }.freeze
TYPE_TO_HEADERS_MAP = { TYPE_TO_HEADERS_MAP = {
@ -22,6 +23,7 @@ class Settings::ImportsController < Settings::BaseController
domain_blocking: false, domain_blocking: false,
bookmarks: false, bookmarks: false,
lists: false, lists: false,
filters: false,
}.freeze }.freeze
RECENT_IMPORTS_LIMIT = 10 RECENT_IMPORTS_LIMIT = 10
@ -55,6 +57,8 @@ class Settings::ImportsController < Settings::BaseController
csv << [row.data['uri']] csv << [row.data['uri']]
when :lists when :lists
csv << [row.data['list_name'], row.data['acct']] csv << [row.data['list_name'], row.data['acct']]
when :filters
csv << [row.data['title'], row.data['context'], row.data['keywords'], row.data['whole_word'], row.data['action'], row.data['expires_at']]
end end
end end
end end

View File

@ -8,6 +8,7 @@ const meta = {
component: Button, component: Button,
args: { args: {
secondary: false, secondary: false,
plain: false,
compact: false, compact: false,
dangerous: false, dangerous: false,
disabled: false, disabled: false,
@ -57,6 +58,14 @@ export const Secondary: Story = {
play: buttonTest, play: buttonTest,
}; };
export const Plain: Story = {
args: {
plain: true,
children: 'Plain button',
},
play: buttonTest,
};
export const Compact: Story = { export const Compact: Story = {
args: { args: {
compact: true, compact: true,
@ -101,6 +110,14 @@ export const SecondaryDisabled: Story = {
play: disabledButtonTest, play: disabledButtonTest,
}; };
export const PlainDisabled: Story = {
args: {
...Plain.args,
disabled: true,
},
play: disabledButtonTest,
};
const loadingButtonTest: Story['play'] = async ({ const loadingButtonTest: Story['play'] = async ({
args, args,
canvas, canvas,

View File

@ -9,6 +9,7 @@ interface BaseProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> { extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
block?: boolean; block?: boolean;
secondary?: boolean; secondary?: boolean;
plain?: boolean;
compact?: boolean; compact?: boolean;
dangerous?: boolean; dangerous?: boolean;
loading?: boolean; loading?: boolean;
@ -35,6 +36,7 @@ export const Button: React.FC<Props> = ({
disabled, disabled,
block, block,
secondary, secondary,
plain,
compact, compact,
dangerous, dangerous,
loading, loading,
@ -62,6 +64,7 @@ export const Button: React.FC<Props> = ({
<button <button
className={classNames('button', className, { className={classNames('button', className, {
'button-secondary': secondary, 'button-secondary': secondary,
'button--plain': plain,
'button--compact': compact, 'button--compact': compact,
'button--block': block, 'button--block': block,
'button--dangerous': dangerous, 'button--dangerous': dangerous,

View File

@ -201,6 +201,41 @@
} }
} }
&.button--plain {
color: $highlight-text-color;
background: transparent;
padding: 6px;
// The button has no outline, so we use negative margin to
// visually align its label with its surroundings while maintaining
// a generous click target
margin-inline: -6px;
border: 1px solid transparent;
&:active,
&:focus,
&:hover {
border-color: transparent;
color: lighten($highlight-text-color, 4%);
background-color: transparent;
text-decoration: none;
}
&:disabled,
&.disabled {
opacity: 0.7;
border-color: transparent;
color: $ui-button-disabled-color;
&:active,
&:focus,
&:hover {
border-color: transparent;
color: $ui-button-disabled-color;
}
}
}
&.button-tertiary { &.button-tertiary {
background: transparent; background: transparent;
padding: 6px 17px; padding: 6px 17px;

View File

@ -34,6 +34,7 @@ class BulkImport < ApplicationRecord
domain_blocking: 3, domain_blocking: 3,
bookmarks: 4, bookmarks: 4,
lists: 5, lists: 5,
filters: 6,
} }
enum :state, { enum :state, {

View File

@ -55,6 +55,14 @@ class Export
end end
end end
def to_filters_csv
CSV.generate(headers: ['Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'], write_headers: true) do |csv|
account.custom_filters.reorder(:title).each do |filter|
csv << [filter.title, filter.context, filter.keywords.map(&:keyword), filter.keywords.map(&:whole_word), filter.action, filter.expires_at]
end
end
end
private private
def to_csv(accounts) def to_csv(accounts)

View File

@ -19,6 +19,7 @@ class Form::Import
domain_blocking: ['#domain'], domain_blocking: ['#domain'],
bookmarks: ['#uri'], bookmarks: ['#uri'],
lists: ['List name', 'Account address'], lists: ['List name', 'Account address'],
filters: ['Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'],
}.freeze }.freeze
KNOWN_FIRST_HEADERS = EXPECTED_HEADERS_BY_TYPE.values.map(&:first).uniq.freeze KNOWN_FIRST_HEADERS = EXPECTED_HEADERS_BY_TYPE.values.map(&:first).uniq.freeze
@ -32,6 +33,14 @@ class Form::Import
'#domain' => 'domain', '#domain' => 'domain',
'#uri' => 'uri', '#uri' => 'uri',
'List name' => 'list_name', 'List name' => 'list_name',
# Filters
'Title' => 'title',
'Context' => 'context',
'Keywords' => 'keywords',
'Whole Word' => 'whole_word',
'Action' => 'action',
'Expire after' => 'expires_at',
}.freeze }.freeze
class EmptyFileError < StandardError; end class EmptyFileError < StandardError; end
@ -55,6 +64,8 @@ class Form::Import
:bookmarks :bookmarks
elsif file_name_matches?('lists') elsif file_name_matches?('lists')
:lists :lists
elsif file_name_matches?('filters') || csv_headers_match?('Keywords')
:filters
end end
end end
@ -102,6 +113,8 @@ class Form::Import
['#uri'] ['#uri']
when :lists when :lists
['List name', 'Account address'] ['List name', 'Account address']
when :filters
['Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after']
end end
end end
@ -109,19 +122,31 @@ class Form::Import
return @csv_data if defined?(@csv_data) return @csv_data if defined?(@csv_data)
csv_converter = lambda do |field, field_info| csv_converter = lambda do |field, field_info|
case field_info.header case type.to_sym
when 'Show boosts', 'Notify on new posts', 'Hide notifications' when :filters
ActiveModel::Type::Boolean.new.cast(field&.downcase) case field_info.header
when 'Languages' when 'Context', 'Keywords', 'Whole Word'
field&.split(',')&.map(&:strip)&.presence Oj.load(field)
when 'Account address' when 'Expire after'
field.strip.gsub(/\A@/, '') field.blank? ? nil : Time.zone.parse(field)
when '#domain' else
field&.strip&.downcase field
when '#uri', 'List name' end
field.strip
else else
field case field_info.header
when 'Show boosts', 'Notify on new posts', 'Hide notifications'
ActiveModel::Type::Boolean.new.cast(field&.downcase)
when 'Languages'
field&.split(',')&.map(&:strip)&.presence
when 'Account address'
field.strip.gsub(/\A@/, '')
when '#domain'
field&.strip&.downcase
when '#uri', 'List name'
field.strip
else
field
end
end end
end end

View File

@ -10,6 +10,7 @@ class ExportSummary
:owned_lists, :owned_lists,
:media_attachments, :media_attachments,
:muting, :muting,
:custom_filters,
to: :account, to: :account,
prefix: true prefix: true
) )
@ -47,6 +48,10 @@ class ExportSummary
counts[:muting].value counts[:muting].value
end end
def total_filters
counts[:filters].value
end
def total_statuses def total_statuses
account.statuses_count account.statuses_count
end end
@ -64,6 +69,7 @@ class ExportSummary
domain_blocks: account_domain_blocks.async_count, domain_blocks: account_domain_blocks.async_count,
owned_lists: account_owned_lists.async_count, owned_lists: account_owned_lists.async_count,
muting: account_muting.async_count, muting: account_muting.async_count,
filters: account_custom_filters.async_count,
storage: account_media_attachments.async_sum(:file_file_size), storage: account_media_attachments.async_sum(:file_file_size),
} }
end end

View File

@ -232,6 +232,15 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
canQuote: { canQuote: {
automaticApproval: approved_uris, automaticApproval: approved_uris,
}, },
canReply: {
always: 'https://www.w3.org/ns/activitystreams#Public',
},
canLike: {
always: 'https://www.w3.org/ns/activitystreams#Public',
},
canAnnounce: {
always: 'https://www.w3.org/ns/activitystreams#Public',
},
} }
end end

View File

@ -42,6 +42,13 @@ class BulkImportRowService
FollowService.new.call(@account, @target_account) unless @account.id == @target_account.id FollowService.new.call(@account, @target_account) unless @account.id == @target_account.id
list.accounts << @target_account list.accounts << @target_account
when :filters
filter = @account.custom_filters.find_or_initialize_by(title: @data['title'])
filter.context = @data['context']
filter.keywords = @data['keywords'].map.with_index { |keyword, i| CustomFilterKeyword.new(keyword: keyword, whole_word: @data['whole_word'][i]) }
filter.action = @data['action'].to_sym
filter.expires_at = @data['expires_at']
filter.save!
end end
true true

View File

@ -18,6 +18,10 @@ class BulkImportService < BaseService
import_bookmarks! import_bookmarks!
when :lists when :lists
import_lists! import_lists!
when :filters
import_filters!
else
raise NotImplementedError, "Unknown import type: #{@import.type}"
end end
@import.update!(state: :finished, finished_at: Time.now.utc) if @import.processed_items == @import.total_items @import.update!(state: :finished, finished_at: Time.now.utc) if @import.processed_items == @import.total_items
@ -182,4 +186,14 @@ class BulkImportService < BaseService
[row.id] [row.id]
end end
end end
def import_filters!
rows = @import.rows.to_a
@account.custom_filters.destroy_all if @import.overwrite?
Import::RowWorker.push_bulk(rows) do |row|
[row.id]
end
end
end end

View File

@ -40,6 +40,10 @@
%th= t('exports.bookmarks') %th= t('exports.bookmarks')
%td= number_with_delimiter @export_summary.total_bookmarks %td= number_with_delimiter @export_summary.total_bookmarks
%td= table_link_to 'download', t('exports.csv'), settings_exports_bookmarks_path(format: :csv) %td= table_link_to 'download', t('exports.csv'), settings_exports_bookmarks_path(format: :csv)
%tr
%th= t('exports.filters')
%td= number_with_delimiter @export_summary.total_filters
%td= table_link_to 'download', t('exports.csv'), settings_exports_filters_path(format: :csv)
%hr.spacer/ %hr.spacer/

View File

@ -5,7 +5,7 @@
.field-group .field-group
= f.input :type, = f.input :type,
as: :grouped_select, as: :grouped_select,
collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) }, collection: { constructive: %i(following bookmarks lists), destructive: %i(filters muting blocking domain_blocking) },
group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") },
group_method: :last, group_method: :last,
hint: t('imports.preface'), hint: t('imports.preface'),

View File

@ -66,8 +66,8 @@ ignore_unused:
- 'admin_mailer.*.subject' - 'admin_mailer.*.subject'
- 'user_mailer.*.subject' - 'user_mailer.*.subject'
- 'notification_mailer.*' - 'notification_mailer.*'
- 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*' - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists,filters}_html.*'
- 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*' - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists,filters}_html.*'
- 'mail_subscriptions.unsubscribe.emails.*' - 'mail_subscriptions.unsubscribe.emails.*'
- 'preferences.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use - 'preferences.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use
- 'edit_profile.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use - 'edit_profile.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use

View File

@ -1408,6 +1408,7 @@ en:
bookmarks: Bookmarks bookmarks: Bookmarks
csv: CSV csv: CSV
domain_blocks: Domain blocks domain_blocks: Domain blocks
filters: Filters
lists: Lists lists: Lists
mutes: You mute mutes: You mute
storage: Media storage storage: Media storage
@ -1507,6 +1508,9 @@ en:
domain_blocking_html: domain_blocking_html:
one: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>. one: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>. other: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
filters_html:
one: You are about to <strong>replace your filters</strong> with up to <strong>%{count} filter</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>replace your filters</strong> with up to <strong>%{count} filters</strong> from <strong>%{filename}</strong>.
following_html: following_html:
one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>. one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>. other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
@ -1526,6 +1530,9 @@ en:
domain_blocking_html: domain_blocking_html:
one: You are about to <strong>block</strong> up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>. one: You are about to <strong>block</strong> up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>block</strong> up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>. other: You are about to <strong>block</strong> up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
filters_html:
one: You are about to <strong>add</strong> up to <strong>%{count} filter</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>add</strong> up to <strong>%{count} filters</strong> from <strong>%{filename}</strong>.
following_html: following_html:
one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>. one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>. other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
@ -1560,6 +1567,7 @@ en:
blocking: Blocking list blocking: Blocking list
bookmarks: Bookmarks bookmarks: Bookmarks
domain_blocking: Domain blocking list domain_blocking: Domain blocking list
filters: Filters
following: Following list following: Following list
lists: Lists lists: Lists
muting: Muting list muting: Muting list

View File

@ -30,6 +30,7 @@ namespace :settings do
resources :lists, only: :index resources :lists, only: :index
resources :domain_blocks, only: :index, controller: :blocked_domains resources :domain_blocks, only: :index, controller: :blocked_domains
resources :bookmarks, only: :index resources :bookmarks, only: :index
resources :filters, only: :index
end end
resources :two_factor_authentication_methods, only: [:index] do resources :two_factor_authentication_methods, only: [:index] do

View File

@ -229,6 +229,18 @@ RSpec.describe Settings::ImportsController do
it_behaves_like 'export failed rows', "Amigos,user@example.com\nFrenemies,user@org.org\n" it_behaves_like 'export failed rows', "Amigos,user@example.com\nFrenemies,user@org.org\n"
end end
context 'with filters' do
let(:import_type) { 'filters' }
let(:rows) do
[
{ 'title' => 'Invalid Filter', 'context' => ['waffles'], 'keywords' => ['mean stuff'], 'whole_word' => [true], 'action' => 'hide', 'expires_at' => nil },
]
end
it_behaves_like 'export failed rows', "Invalid Filter,\"[\"\"waffles\"\"]\",\"[\"\"mean stuff\"\"]\",[true],hide,\n"
end
end end
describe 'POST #create' do describe 'POST #create' do
@ -277,6 +289,8 @@ RSpec.describe Settings::ImportsController do
it_behaves_like 'successful import', 'domain_blocking', 'domain_blocks.csv', 'overwrite' it_behaves_like 'successful import', 'domain_blocking', 'domain_blocks.csv', 'overwrite'
it_behaves_like 'successful import', 'bookmarks', 'bookmark-imports.txt', 'merge' it_behaves_like 'successful import', 'bookmarks', 'bookmark-imports.txt', 'merge'
it_behaves_like 'successful import', 'bookmarks', 'bookmark-imports.txt', 'overwrite' it_behaves_like 'successful import', 'bookmarks', 'bookmark-imports.txt', 'overwrite'
it_behaves_like 'successful import', 'filters', 'filters.csv', 'merge'
it_behaves_like 'successful import', 'filters', 'filters.csv', 'overwrite'
it_behaves_like 'unsuccessful import', 'following', 'domain_blocks.csv', 'merge' it_behaves_like 'unsuccessful import', 'following', 'domain_blocks.csv', 'merge'
it_behaves_like 'unsuccessful import', 'following', 'domain_blocks.csv', 'overwrite' it_behaves_like 'unsuccessful import', 'following', 'domain_blocks.csv', 'overwrite'
@ -284,6 +298,8 @@ RSpec.describe Settings::ImportsController do
it_behaves_like 'unsuccessful import', 'blocking', 'domain_blocks.csv', 'overwrite' it_behaves_like 'unsuccessful import', 'blocking', 'domain_blocks.csv', 'overwrite'
it_behaves_like 'unsuccessful import', 'muting', 'domain_blocks.csv', 'merge' it_behaves_like 'unsuccessful import', 'muting', 'domain_blocks.csv', 'merge'
it_behaves_like 'unsuccessful import', 'muting', 'domain_blocks.csv', 'overwrite' it_behaves_like 'unsuccessful import', 'muting', 'domain_blocks.csv', 'overwrite'
it_behaves_like 'unsuccessful import', 'filters', 'domain_blocks.csv', 'merge'
it_behaves_like 'unsuccessful import', 'filters', 'domain_blocks.csv', 'overwrite'
it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'merge' it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'merge'
it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'overwrite' it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'overwrite'

4
spec/fixtures/files/filters.csv vendored Normal file
View File

@ -0,0 +1,4 @@
Title,Context,Keywords,Whole Word,Action,Expire after
current events,"[""home"", ""public"", ""account""]","[""minions song contest""]",[true],warn,2025-08-14 21:33:29 UTC
unwanted solicitations,"[""notifications"", ""thread""]","[""wizard school enrolment""]",[false],hide,
scary things,"[""home"", ""public""]","[""flying sharks"", ""sapient crabs""]","[true, false]",blur,
1 Title Context Keywords Whole Word Action Expire after
2 current events ["home", "public", "account"] ["minions song contest"] [true] warn 2025-08-14 21:33:29 UTC
3 unwanted solicitations ["notifications", "thread"] ["wizard school enrolment"] [false] hide
4 scary things ["home", "public"] ["flying sharks", "sapient crabs"] [true, false] blur

View File

@ -103,4 +103,23 @@ RSpec.describe Export do
) )
end end
end end
describe '#to_filters_csv' do
before do
Fabricate.times(2, :custom_filter, account: account) do
keywords { [Fabricate(:custom_filter_keyword)] }
end
end
let(:export) { CSV.parse(subject.to_filters_csv) }
it 'returns a csv of custom filters' do
expect(export)
.to contain_exactly(
contain_exactly('Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'),
include('discourse', '["home", "notifications"]', '["discourse"]', '[true]', 'warn', be_blank),
include(be_present)
)
end
end
end end

View File

@ -282,6 +282,12 @@ RSpec.describe Form::Import do
{ 'acct' => 'foo@example.com', 'list_name' => 'test' }, { 'acct' => 'foo@example.com', 'list_name' => 'test' },
] ]
it_behaves_like 'on successful import', 'filters', 'merge', 'filters.csv', [
{ 'title' => 'current events', 'context' => %w(home public account), 'keywords' => ['minions song contest'], 'whole_word' => [true], 'action' => 'warn', 'expires_at' => Time.zone.parse('2025-08-14 21:33:29 UTC') },
{ 'title' => 'unwanted solicitations', 'context' => %w(notifications thread), 'keywords' => ['wizard school enrolment'], 'whole_word' => [false], 'action' => 'hide', 'expires_at' => nil },
{ 'title' => 'scary things', 'context' => %w(home public), 'keywords' => ['flying sharks', 'sapient crabs'], 'whole_word' => [true, false], 'action' => 'blur', 'expires_at' => nil },
]
# Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users
# #
# https://github.com/mastodon/mastodon/issues/20571 # https://github.com/mastodon/mastodon/issues/20571

View File

@ -11957,8 +11957,8 @@ __metadata:
linkType: hard linkType: hard
"sass@npm:^1.62.1": "sass@npm:^1.62.1":
version: 1.91.0 version: 1.92.0
resolution: "sass@npm:1.91.0" resolution: "sass@npm:1.92.0"
dependencies: dependencies:
"@parcel/watcher": "npm:^2.4.1" "@parcel/watcher": "npm:^2.4.1"
chokidar: "npm:^4.0.0" chokidar: "npm:^4.0.0"
@ -11969,7 +11969,7 @@ __metadata:
optional: true optional: true
bin: bin:
sass: sass.js sass: sass.js
checksum: 10c0/5be1c98f7a618cb5f90b62f63d2aa0f78f9bf369c93ec7cd9880752a26b0ead19aa63cc341e8a26ce6c74d080baa5705f1685dff52fe6a3f28a7828ae50182b6 checksum: 10c0/bdff9fa6988620e2a81962efdd016e3894d19934cfadc105cf41db767f59dd47afd8ff32840e612ef700cb67e19d9e83c108f1724eb8f0bef56c4877dbe6f14d
languageName: node languageName: node
linkType: hard linkType: hard