Add ltl mode support
This commit is contained in:
parent
7656687d43
commit
d1ae53e688
12 changed files with 188 additions and 27 deletions
|
@ -42,6 +42,6 @@ class Api::V1::AntennasController < Api::BaseController
|
|||
end
|
||||
|
||||
def antenna_params
|
||||
params.permit(:title, :list_id, :insert_feeds, :stl, :with_media_only, :ignore_reblog)
|
||||
params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -236,10 +236,10 @@ export const createAntennaFail = error => ({
|
|||
error,
|
||||
});
|
||||
|
||||
export const updateAntenna = (id, title, shouldReset, list_id, stl, with_media_only, ignore_reblog, insert_feeds) => (dispatch, getState) => {
|
||||
export const updateAntenna = (id, title, shouldReset, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds) => (dispatch, getState) => {
|
||||
dispatch(updateAntennaRequest(id));
|
||||
|
||||
api(getState).put(`/api/v1/antennas/${id}`, { title, list_id, stl, with_media_only, ignore_reblog, insert_feeds }).then(({ data }) => {
|
||||
api(getState).put(`/api/v1/antennas/${id}`, { title, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds }).then(({ data }) => {
|
||||
dispatch(updateAntennaSuccess(data));
|
||||
|
||||
if (shouldReset) {
|
||||
|
|
|
@ -198,31 +198,37 @@ class AntennaSetting extends PureComponent {
|
|||
onStlToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, target.checked, undefined, undefined, undefined));
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, target.checked, undefined, undefined, undefined, undefined));
|
||||
};
|
||||
|
||||
onLtlToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, target.checked, undefined, undefined, undefined));
|
||||
};
|
||||
|
||||
onMediaOnlyToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, target.checked, undefined, undefined));
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, target.checked, undefined, undefined));
|
||||
};
|
||||
|
||||
onIgnoreReblogToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, target.checked, undefined));
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, target.checked, undefined));
|
||||
};
|
||||
|
||||
onNoInsertFeedsToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, target.checked));
|
||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, undefined, target.checked));
|
||||
};
|
||||
|
||||
onSelect = value => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateAntenna(id, undefined, false, value.value, undefined, undefined, undefined, undefined));
|
||||
dispatch(updateAntenna(id, undefined, false, value.value, undefined, undefined, undefined, undefined, undefined));
|
||||
};
|
||||
|
||||
onHomeSelect = () => this.onSelect({ value: '0' });
|
||||
|
@ -293,6 +299,7 @@ class AntennaSetting extends PureComponent {
|
|||
const pinned = !!columnId;
|
||||
const title = antenna ? antenna.get('title') : id;
|
||||
const isStl = antenna ? antenna.get('stl') : undefined;
|
||||
const isLtl = antenna ? antenna.get('ltl') : undefined;
|
||||
const isMediaOnly = antenna ? antenna.get('with_media_only') : undefined;
|
||||
const isIgnoreReblog = antenna ? antenna.get('ignore_reblog') : undefined;
|
||||
const isInsertFeeds = antenna ? antenna.get('insert_feeds') : undefined;
|
||||
|
@ -312,7 +319,7 @@ class AntennaSetting extends PureComponent {
|
|||
}
|
||||
|
||||
let columnSettings;
|
||||
if (!isStl) {
|
||||
if (!isStl && !isLtl) {
|
||||
columnSettings = (
|
||||
<>
|
||||
<div className='setting-toggle'>
|
||||
|
@ -339,6 +346,12 @@ class AntennaSetting extends PureComponent {
|
|||
<p><FormattedMessage id='antennas.in_stl_mode' defaultMessage='This antenna is in STL mode.' /></p>
|
||||
</div>
|
||||
);
|
||||
} else if (isLtl) {
|
||||
stlAlert = (
|
||||
<div className='antenna-setting'>
|
||||
<p><FormattedMessage id='antennas.in_ltl_mode' defaultMessage='This antenna is in LTL mode.' /></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const rangeRadioValues = ImmutableList([
|
||||
|
@ -384,12 +397,23 @@ class AntennaSetting extends PureComponent {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{!isLtl && (
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id={`antenna-${id}-stl`} defaultChecked={isStl} onChange={this.onStlToggle} />
|
||||
<label htmlFor={`antenna-${id}-stl`} className='setting-toggle__label'>
|
||||
<FormattedMessage id='antennas.stl' defaultMessage='STL mode' />
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isStl && (
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id={`antenna-${id}-ltl`} defaultChecked={isLtl} onChange={this.onLtlToggle} />
|
||||
<label htmlFor={`antenna-${id}-ltl`} className='setting-toggle__label'>
|
||||
<FormattedMessage id='antennas.ltl' defaultMessage='LTL mode' />
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id={`antenna-${id}-noinsertfeeds`} defaultChecked={isInsertFeeds} onChange={this.onNoInsertFeedsToggle} />
|
||||
|
@ -429,7 +453,7 @@ class AntennaSetting extends PureComponent {
|
|||
</>
|
||||
)}
|
||||
|
||||
{!isStl && (
|
||||
{!isStl && !isLtl && (
|
||||
<>
|
||||
<h2><FormattedMessage id='antennas.filter' defaultMessage='Filter' /></h2>
|
||||
<RadioPanel values={rangeRadioValues} value={rangeRadioValue} onChange={this.onRangeRadioChanged} />
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
# stl :boolean default(FALSE), not null
|
||||
# ignore_reblog :boolean default(FALSE), not null
|
||||
# insert_feeds :boolean default(FALSE), not null
|
||||
# ltl :boolean default(FALSE), not null
|
||||
#
|
||||
class Antenna < ApplicationRecord
|
||||
include Expireable
|
||||
|
@ -45,16 +46,19 @@ class Antenna < ApplicationRecord
|
|||
belongs_to :list, optional: true
|
||||
|
||||
scope :stls, -> { where(stl: true) }
|
||||
scope :ltls, -> { where(ltl: true) }
|
||||
scope :all_keywords, -> { where(any_keywords: true) }
|
||||
scope :all_domains, -> { where(any_domains: true) }
|
||||
scope :all_accounts, -> { where(any_accounts: true) }
|
||||
scope :all_tags, -> { where(any_tags: true) }
|
||||
scope :availables, -> { where(available: true).where(Arel.sql('any_keywords = FALSE OR any_domains = FALSE OR any_accounts = FALSE OR any_tags = FALSE')) }
|
||||
scope :available_stls, -> { where(available: true, stl: true) }
|
||||
scope :available_ltls, -> { where(available: true, stl: false, ltl: true) }
|
||||
|
||||
validate :list_owner
|
||||
validate :validate_limit
|
||||
validate :validate_stl_limit
|
||||
validate :validate_ltl_limit
|
||||
|
||||
def list_owner
|
||||
raise Mastodon::ValidationError, I18n.t('antennas.errors.invalid_list_owner') if !list_id.zero? && list.present? && list.account != account
|
||||
|
@ -235,6 +239,22 @@ class Antenna < ApplicationRecord
|
|||
|
||||
stls = account.antennas.where(stl: true).where.not(id: id)
|
||||
|
||||
errors.add(:base, I18n.t('antennas.errors.over_stl_limit', limit: 1)) if list_id.zero? ? stls.any? { |tl| tl.list_id.zero? } : stls.any? { |tl| tl.list_id != 0 }
|
||||
errors.add(:base, I18n.t('antennas.errors.over_stl_limit', limit: 1)) if if insert_feeds
|
||||
list_id.zero? ? stls.any? { |tl| tl.list_id.zero? } : stls.any? { |tl| tl.list_id != 0 }
|
||||
else
|
||||
stls.any? { |tl| !tl.insert_feeds }
|
||||
end
|
||||
end
|
||||
|
||||
def validate_ltl_limit
|
||||
return unless ltl
|
||||
|
||||
ltls = account.antennas.where(ltl: true).where.not(id: id)
|
||||
|
||||
errors.add(:base, I18n.t('antennas.errors.over_ltl_limit', limit: 1)) if if insert_feeds
|
||||
list_id.zero? ? ltls.any? { |tl| tl.list_id.zero? } : ltls.any? { |tl| tl.list_id != 0 }
|
||||
else
|
||||
ltls.any? { |tl| !tl.insert_feeds }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::AntennaSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title, :stl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count
|
||||
attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count
|
||||
|
||||
class ListSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title
|
||||
|
|
|
@ -3,15 +3,19 @@
|
|||
class DeliveryAntennaService
|
||||
include FormattingHelper
|
||||
|
||||
def call(status, update, stl_home)
|
||||
def call(status, update, **options)
|
||||
@status = status
|
||||
@account = @status.account
|
||||
@update = update
|
||||
|
||||
if stl_home
|
||||
delivery_stl!
|
||||
else
|
||||
mode = options[:mode] || :home
|
||||
case mode
|
||||
when :home
|
||||
delivery!
|
||||
when :stl
|
||||
delivery_stl!
|
||||
when :ltl
|
||||
delivery_ltl!
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -44,7 +48,7 @@ class DeliveryAntennaService
|
|||
antennas = antennas.where(account: @status.mentioned_accounts) if @status.visibility.to_sym == :limited
|
||||
antennas = antennas.where(with_media_only: false) unless @status.with_media?
|
||||
antennas = antennas.where(ignore_reblog: false) if @status.reblog?
|
||||
antennas = antennas.where(stl: false)
|
||||
antennas = antennas.where(stl: false, ltl: false)
|
||||
|
||||
collection = AntennaCollection.new(@status, @update, false)
|
||||
content = extract_status_plain_text_with_spoiler_text(@status)
|
||||
|
@ -72,7 +76,7 @@ class DeliveryAntennaService
|
|||
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
||||
|
||||
home_post = !@account.domain.nil? || @status.reblog? || [:public, :public_unlisted, :login].exclude?(@status.visibility.to_sym)
|
||||
antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where.not(list_id: 0) if home_post
|
||||
antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where('insert_feeds IS FALSE OR list_id > 0') if home_post
|
||||
|
||||
collection = AntennaCollection.new(@status, @update, home_post)
|
||||
|
||||
|
@ -87,6 +91,26 @@ class DeliveryAntennaService
|
|||
collection.deliver!
|
||||
end
|
||||
|
||||
def delivery_ltl!
|
||||
return if %i(public public_unlisted login).exclude?(@status.visibility.to_sym)
|
||||
return unless @account.local?
|
||||
|
||||
antennas = Antenna.available_ltls
|
||||
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
||||
|
||||
collection = AntennaCollection.new(@status, @update, false)
|
||||
|
||||
antennas.in_batches do |ans|
|
||||
ans.each do |antenna|
|
||||
next if antenna.expired?
|
||||
|
||||
collection.push(antenna)
|
||||
end
|
||||
end
|
||||
|
||||
collection.deliver!
|
||||
end
|
||||
|
||||
class AntennaCollection
|
||||
def initialize(status, update, stl_home = false) # rubocop:disable Style/OptionalBooleanParameter
|
||||
@status = status
|
||||
|
|
|
@ -53,6 +53,7 @@ class FanOutOnWriteService < BaseService
|
|||
deliver_to_lists!
|
||||
deliver_to_antennas! if !@account.dissubscribable || (@status.dtl? && @account.user&.setting_dtl_force_subscribable && @status.tags.exists?(name: 'kmyblue'))
|
||||
deliver_to_stl_antennas!
|
||||
deliver_to_ltl_antennas!
|
||||
when :limited
|
||||
deliver_to_lists_mentioned_accounts_only!
|
||||
deliver_to_antennas! unless @account.dissubscribable
|
||||
|
@ -135,11 +136,15 @@ class FanOutOnWriteService < BaseService
|
|||
end
|
||||
|
||||
def deliver_to_stl_antennas!
|
||||
DeliveryAntennaService.new.call(@status, @options[:update], true)
|
||||
DeliveryAntennaService.new.call(@status, @options[:update], mode: :stl)
|
||||
end
|
||||
|
||||
def deliver_to_ltl_antennas!
|
||||
DeliveryAntennaService.new.call(@status, @options[:update], mode: :ltl)
|
||||
end
|
||||
|
||||
def deliver_to_antennas!
|
||||
DeliveryAntennaService.new.call(@status, @options[:update], false)
|
||||
DeliveryAntennaService.new.call(@status, @options[:update], mode: :home)
|
||||
end
|
||||
|
||||
def deliver_to_mentioned_followers!
|
||||
|
|
|
@ -1113,6 +1113,7 @@ en:
|
|||
invalid_context: None or invalid context supplied
|
||||
invalid_list_owner: This list is not yours
|
||||
over_limit: You have exceeded the limit of %{limit} antennas
|
||||
over_ltl_limit: You have exceeded the limit of %{limit} ltl antennas
|
||||
over_stl_limit: You have exceeded the limit of %{limit} stl antennas
|
||||
index:
|
||||
contexts: Antennas in %{contexts}
|
||||
|
|
|
@ -1032,6 +1032,7 @@ ja:
|
|||
keywords: 登録できるキーワード数の上限に達しています
|
||||
tags: 登録できるタグ数の上限に達しています
|
||||
over_limit: 所持できるアンテナ数 %{limit}を超えています
|
||||
over_ltl_limit: 所持できるLTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
|
||||
over_stl_limit: 所持できるSTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
|
||||
too_short_keyword: キーワードが短すぎます
|
||||
edit:
|
||||
|
|
15
db/migrate/20230911022527_add_ltl_to_antennas.rb
Normal file
15
db/migrate/20230911022527_add_ltl_to_antennas.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||
|
||||
class AddLtlToAntennas < ActiveRecord::Migration[7.0]
|
||||
include Mastodon::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
safety_assured do
|
||||
add_column_with_default :antennas, :ltl, :boolean, default: false, allow_null: false
|
||||
end
|
||||
end
|
||||
end
|
13
db/schema.rb
13
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_09_11_022527) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
@ -50,6 +50,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
|||
t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true
|
||||
end
|
||||
|
||||
create_table "account_groups", force: :cascade do |t|
|
||||
t.bigint "account_id", null: false
|
||||
t.bigint "group_account_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id"], name: "index_account_groups_on_account_id"
|
||||
t.index ["group_account_id"], name: "index_account_groups_on_group_account_id"
|
||||
end
|
||||
|
||||
create_table "account_migrations", force: :cascade do |t|
|
||||
t.bigint "account_id"
|
||||
t.string "acct", default: "", null: false
|
||||
|
@ -313,6 +322,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
|||
t.boolean "stl", default: false, null: false
|
||||
t.boolean "ignore_reblog", default: false, null: false
|
||||
t.boolean "insert_feeds", default: false, null: false
|
||||
t.boolean "ltl", default: false, null: false
|
||||
t.index ["account_id"], name: "index_antennas_on_account_id"
|
||||
t.index ["any_accounts"], name: "index_antennas_on_any_accounts"
|
||||
t.index ["any_domains"], name: "index_antennas_on_any_domains"
|
||||
|
@ -1357,6 +1367,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
|||
add_foreign_key "account_conversations", "conversations", on_delete: :cascade
|
||||
add_foreign_key "account_deletion_requests", "accounts", on_delete: :cascade
|
||||
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
|
||||
add_foreign_key "account_groups", "accounts", on_delete: :cascade
|
||||
add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
|
||||
add_foreign_key "account_migrations", "accounts", on_delete: :cascade
|
||||
add_foreign_key "account_moderation_notes", "accounts"
|
||||
|
|
|
@ -56,6 +56,10 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
antenna
|
||||
end
|
||||
|
||||
def antenna_with_options(owner, **options)
|
||||
Fabricate(:antenna, account: owner, **options)
|
||||
end
|
||||
|
||||
context 'when status is public' do
|
||||
let(:visibility) { 'public' }
|
||||
|
||||
|
@ -97,6 +101,26 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with STL antenna' do
|
||||
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||
let!(:empty_antenna) { antenna_with_options(tom) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with LTL antenna' do
|
||||
let!(:antenna) { antenna_with_options(bob, ltl: true) }
|
||||
let!(:empty_antenna) { antenna_with_options(tom) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is limited' do
|
||||
|
@ -176,6 +200,24 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with STL antenna' do
|
||||
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||
let!(:empty_antenna) { antenna_with_options(ohagi, stl: true) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with LTL antenna' do
|
||||
let!(:empty_antenna) { antenna_with_options(bob, ltl: true) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is unlisted' do
|
||||
|
@ -215,6 +257,24 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with STL antenna' do
|
||||
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||
let!(:empty_antenna) { antenna_with_options(ohagi, stl: true) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(antenna)).to include status.id
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with LTL antenna' do
|
||||
let!(:empty_antenna) { antenna_with_options(bob, ltl: true) }
|
||||
|
||||
it 'is added to the antenna feed of antenna follower' do
|
||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-public searchability' do
|
||||
let(:searchability) { 'direct' }
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue