Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
9b032185b8
59 changed files with 1441 additions and 28 deletions
76
app/controllers/antennas_controller.rb
Normal file
76
app/controllers/antennas_controller.rb
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AntennasController < ApplicationController
|
||||||
|
layout 'admin'
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :set_antenna, only: [:edit, :update, :destroy]
|
||||||
|
before_action :set_lists, only: [:new, :edit]
|
||||||
|
before_action :set_body_classes
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
|
def index
|
||||||
|
@antennas = current_account.antennas.includes(:antenna_domains).includes(:antenna_tags).includes(:antenna_accounts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@antenna = current_account.antennas.build
|
||||||
|
@antenna.antenna_domains.build
|
||||||
|
@antenna.antenna_tags.build
|
||||||
|
@antenna.antenna_accounts.build
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@antenna = current_account.antennas.build(thin_resource_params)
|
||||||
|
|
||||||
|
saved = @antenna.save
|
||||||
|
saved = @antenna.update(resource_params) if saved
|
||||||
|
|
||||||
|
if saved
|
||||||
|
redirect_to antennas_path
|
||||||
|
else
|
||||||
|
render action: :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit; end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if @antenna.update(resource_params)
|
||||||
|
redirect_to antennas_path
|
||||||
|
else
|
||||||
|
render action: :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@antenna.destroy
|
||||||
|
redirect_to antennas_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_antenna
|
||||||
|
@antenna = current_account.antennas.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_lists
|
||||||
|
@lists = current_account.owned_lists
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.require(:antenna).permit(:title, :list, :available, :expires_in, :keywords_raw, :exclude_keywords_raw, :domains_raw, :exclude_domains_raw, :accounts_raw, :exclude_accounts_raw, :tags_raw, :exclude_tags_raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
def thin_resource_params
|
||||||
|
params.require(:antenna).permit(:title, :list)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_body_classes
|
||||||
|
@body_classes = 'admin'
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
|
end
|
18
app/controllers/api/v1/accounts/antennas_controller.rb
Normal file
18
app/controllers/api/v1/accounts/antennas_controller.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::AntennasController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:lists' }
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
def index
|
||||||
|
@antennas = @account.suspended? ? [] : @account.joined_antennas.where(account: current_account)
|
||||||
|
render json: @antennas, each_serializer: REST::AntennaSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -30,6 +30,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||||
:bot,
|
:bot,
|
||||||
:discoverable,
|
:discoverable,
|
||||||
:searchability,
|
:searchability,
|
||||||
|
:dissubscribable,
|
||||||
:hide_collections,
|
:hide_collections,
|
||||||
fields_attributes: [:name, :value]
|
fields_attributes: [:name, :value]
|
||||||
)
|
)
|
||||||
|
|
82
app/controllers/api/v1/antennas/accounts_controller.rb
Normal file
82
app/controllers/api/v1/antennas/accounts_controller.rb
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Antennas::AccountsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show]
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show]
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_antenna
|
||||||
|
|
||||||
|
after_action :insert_pagination_headers, only: :show
|
||||||
|
|
||||||
|
def create
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
antenna_accounts.each do |account|
|
||||||
|
@antenna.antenna_accounts.create!(account: account, exclude: false)
|
||||||
|
@antenna.update!(any_accounts: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
AntennaAccount.where(antenna: @antenna, account_id: account_ids).destroy_all
|
||||||
|
@antenna.update!(any_accounts: true) if !@antenna.antenna_accounts.where(exclude: false).any?
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_antenna
|
||||||
|
@antenna = Antenna.where(account: current_account).find(params[:antenna_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def antenna_accounts
|
||||||
|
Account.find(account_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_ids
|
||||||
|
Array(resource_params[:account_ids])
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.permit(account_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
return if unlimited?
|
||||||
|
|
||||||
|
api_v1_list_accounts_url pagination_params(max_id: pagination_max_id) if records_continue?
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
return if unlimited?
|
||||||
|
|
||||||
|
api_v1_list_accounts_url pagination_params(since_id: pagination_since_id) unless @accounts.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.slice(:limit).permit(:limit).merge(core_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlimited?
|
||||||
|
params[:limit] == '0'
|
||||||
|
end
|
||||||
|
end
|
28
app/controllers/api/v1/antennas_controller.rb
Normal file
28
app/controllers/api/v1/antennas_controller.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::AntennasController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index, :show]
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:index, :show]
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_antenna, except: [:index]
|
||||||
|
|
||||||
|
rescue_from ArgumentError do |e|
|
||||||
|
render json: { error: e.to_s }, status: 422
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@antennas = Antenna.where(account: current_account).all
|
||||||
|
render json: @antennas, each_serializer: REST::AntennaSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: @antenna, serializer: REST::AntennaSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_antenna
|
||||||
|
@antenna = Antenna.where(account: current_account).find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,7 +20,7 @@ class Settings::ProfilesController < Settings::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :my_actor_type, :searchability, :group_allow_private_message, :discoverable, :hide_collections, fields_attributes: [:name, :value])
|
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :my_actor_type, :searchability, :dissubscribable, :group_allow_private_message, :discoverable, :hide_collections, fields_attributes: [:name, :value])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
|
|
|
@ -23,6 +23,7 @@ module ContextHelper
|
||||||
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
|
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
|
||||||
emoji_reactions: { 'fedibird' => 'http://fedibird.com/ns#', 'emojiReactions' => { '@id' => "fedibird:emojiReactions", '@type' => '@id' } },
|
emoji_reactions: { 'fedibird' => 'http://fedibird.com/ns#', 'emojiReactions' => { '@id' => "fedibird:emojiReactions", '@type' => '@id' } },
|
||||||
searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => "fedibird:searchableBy", '@type' => '@id' } },
|
searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => "fedibird:searchableBy", '@type' => '@id' } },
|
||||||
|
subscribable_by: { 'kmyblue' => 'http://kmy.blue/ns#', 'subscribableBy' => { '@id' => "kmyblue:subscribableBy", '@type' => '@id' } },
|
||||||
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' },
|
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' },
|
||||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
179
app/javascript/mastodon/actions/antennas.js
Normal file
179
app/javascript/mastodon/actions/antennas.js
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
import api from '../api';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
|
export const ANTENNAS_FETCH_REQUEST = 'ANTENNAS_FETCH_REQUEST';
|
||||||
|
export const ANTENNAS_FETCH_SUCCESS = 'ANTENNAS_FETCH_SUCCESS';
|
||||||
|
export const ANTENNAS_FETCH_FAIL = 'ANTENNAS_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const ANTENNA_ACCOUNTS_FETCH_REQUEST = 'ANTENNA_ACCOUNTS_FETCH_REQUEST';
|
||||||
|
export const ANTENNA_ACCOUNTS_FETCH_SUCCESS = 'ANTENNA_ACCOUNTS_FETCH_SUCCESS';
|
||||||
|
export const ANTENNA_ACCOUNTS_FETCH_FAIL = 'ANTENNA_ACCOUNTS_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const ANTENNA_EDITOR_ADD_ACCOUNT_REQUEST = 'ANTENNA_EDITOR_ADD_ACCOUNT_REQUEST';
|
||||||
|
export const ANTENNA_EDITOR_ADD_ACCOUNT_SUCCESS = 'ANTENNA_EDITOR_ADD_ACCOUNT_SUCCESS';
|
||||||
|
export const ANTENNA_EDITOR_ADD_ACCOUNT_FAIL = 'ANTENNA_EDITOR_ADD_ACCOUNT_FAIL';
|
||||||
|
|
||||||
|
export const ANTENNA_EDITOR_REMOVE_ACCOUNT_REQUEST = 'ANTENNA_EDITOR_REMOVE_ACCOUNT_REQUEST';
|
||||||
|
export const ANTENNA_EDITOR_REMOVE_ACCOUNT_SUCCESS = 'ANTENNA_EDITOR_REMOVE_ACCOUNT_SUCCESS';
|
||||||
|
export const ANTENNA_EDITOR_REMOVE_ACCOUNT_FAIL = 'ANTENNA_EDITOR_REMOVE_ACCOUNT_FAIL';
|
||||||
|
|
||||||
|
export const ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST = 'ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST';
|
||||||
|
export const ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS = 'ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS';
|
||||||
|
export const ANTENNA_ADDER_ANTENNAS_FETCH_FAIL = 'ANTENNA_ADDER_ANTENNAS_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const ANTENNA_ADDER_RESET = 'ANTENNA_ADDER_RESET';
|
||||||
|
export const ANTENNA_ADDER_SETUP = 'ANTENNA_ADDER_SETUP';
|
||||||
|
|
||||||
|
export const fetchAntennas = () => (dispatch, getState) => {
|
||||||
|
dispatch(fetchAntennasRequest());
|
||||||
|
|
||||||
|
api(getState).get('/api/v1/antennas')
|
||||||
|
.then(({ data }) => dispatch(fetchAntennasSuccess(data)))
|
||||||
|
.catch(err => dispatch(fetchAntennasFail(err)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchAntennasRequest = () => ({
|
||||||
|
type: ANTENNAS_FETCH_REQUEST,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAntennasSuccess = antennas => ({
|
||||||
|
type: ANTENNAS_FETCH_SUCCESS,
|
||||||
|
antennas,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAntennasFail = error => ({
|
||||||
|
type: ANTENNAS_FETCH_FAIL,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAntennaAccounts = antennaId => (dispatch, getState) => {
|
||||||
|
dispatch(fetchAntennaAccountsRequest(antennaId));
|
||||||
|
|
||||||
|
api(getState).get(`/api/v1/antennas/${antennaId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
dispatch(fetchAntennaAccountsSuccess(antennaId, data));
|
||||||
|
}).catch(err => dispatch(fetchAntennaAccountsFail(antennaId, err)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchAntennaAccountsRequest = id => ({
|
||||||
|
type: ANTENNA_ACCOUNTS_FETCH_REQUEST,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAntennaAccountsSuccess = (id, accounts, next) => ({
|
||||||
|
type: ANTENNA_ACCOUNTS_FETCH_SUCCESS,
|
||||||
|
id,
|
||||||
|
accounts,
|
||||||
|
next,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAntennaAccountsFail = (id, error) => ({
|
||||||
|
type: ANTENNA_ACCOUNTS_FETCH_FAIL,
|
||||||
|
id,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addAccountToAntenna = (antennaId, accountId) => (dispatch, getState) => {
|
||||||
|
dispatch(addAccountToAntennaRequest(antennaId, accountId));
|
||||||
|
|
||||||
|
api(getState).post(`/api/v1/antennas/${antennaId}/accounts`, { account_ids: [accountId] })
|
||||||
|
.then(() => dispatch(addAccountToAntennaSuccess(antennaId, accountId)))
|
||||||
|
.catch(err => dispatch(addAccountToAntennaFail(antennaId, accountId, err)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addAccountToAntennaRequest = (antennaId, accountId) => ({
|
||||||
|
type: ANTENNA_EDITOR_ADD_ACCOUNT_REQUEST,
|
||||||
|
antennaId,
|
||||||
|
accountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addAccountToAntennaSuccess = (antennaId, accountId) => ({
|
||||||
|
type: ANTENNA_EDITOR_ADD_ACCOUNT_SUCCESS,
|
||||||
|
antennaId,
|
||||||
|
accountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addAntennaToAntennaFail = (antennaId, accountId, error) => ({
|
||||||
|
type: ANTENNA_EDITOR_ADD_ACCOUNT_FAIL,
|
||||||
|
antennaId,
|
||||||
|
accountId,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeAccountFromAntennaEditor = accountId => (dispatch, getState) => {
|
||||||
|
dispatch(removeAccountFromAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeAccountFromAntenna = (antennaId, accountId) => (dispatch, getState) => {
|
||||||
|
dispatch(removeAccountFromAntennaRequest(antennaId, accountId));
|
||||||
|
|
||||||
|
api(getState).delete(`/api/v1/antennas/${antennaId}/accounts`, { params: { account_ids: [accountId] } })
|
||||||
|
.then(() => dispatch(removeAccountFromAntennaSuccess(antennaId, accountId)))
|
||||||
|
.catch(err => dispatch(removeAccountFromAntennaFail(antennaId, accountId, err)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeAccountFromAntennaRequest = (antennaId, accountId) => ({
|
||||||
|
type: ANTENNA_EDITOR_REMOVE_ACCOUNT_REQUEST,
|
||||||
|
antennaId,
|
||||||
|
accountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeAccountFromAntennaSuccess = (antennaId, accountId) => ({
|
||||||
|
type: ANTENNA_EDITOR_REMOVE_ACCOUNT_SUCCESS,
|
||||||
|
antennaId,
|
||||||
|
accountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeAccountFromAntennaFail = (antennaId, accountId, error) => ({
|
||||||
|
type: ANTENNA_EDITOR_REMOVE_ACCOUNT_FAIL,
|
||||||
|
antennaId,
|
||||||
|
accountId,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addToAntennaAdder = antennaId => (dispatch, getState) => {
|
||||||
|
dispatch(addAccountToAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeFromAntennaAdder = antennaId => (dispatch, getState) => {
|
||||||
|
dispatch(removeAccountFromAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchAccountAntennas = accountId => (dispatch, getState) => {
|
||||||
|
dispatch(fetchAccountAntennasRequest(accountId));
|
||||||
|
|
||||||
|
api(getState).get(`/api/v1/accounts/${accountId}/antennas`)
|
||||||
|
.then(({ data }) => dispatch(fetchAccountAntennasSuccess(accountId, data)))
|
||||||
|
.catch(err => dispatch(fetchAccountAntennasFail(accountId, err)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchAccountAntennasRequest = id => ({
|
||||||
|
type: ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAccountAntennasSuccess = (id, antennas) => ({
|
||||||
|
type: ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS,
|
||||||
|
id,
|
||||||
|
antennas,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAccountAntennasFail = (id, err) => ({
|
||||||
|
type: ANTENNA_ADDER_ANTENNAS_FETCH_FAIL,
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const resetAntennaAdder = () => ({
|
||||||
|
type: ANTENNA_ADDER_RESET,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setupAntennaAdder = accountId => (dispatch, getState) => {
|
||||||
|
dispatch({
|
||||||
|
type: ANTENNA_ADDER_SETUP,
|
||||||
|
account: getState().getIn(['accounts', accountId]),
|
||||||
|
});
|
||||||
|
dispatch(fetchAntennas());
|
||||||
|
dispatch(fetchAccountAntennas(accountId));
|
||||||
|
};
|
||||||
|
|
|
@ -70,10 +70,6 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||||
normalStatus.emoji_reactions = normalizeEmojiReactions(status.emoji_reactions);
|
normalStatus.emoji_reactions = normalizeEmojiReactions(status.emoji_reactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.media_attachments_ex) {
|
|
||||||
normalStatus.media_attachments = status.media_attachments.concat(status.media_attachments_ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!status.visibility_ex) {
|
if (!status.visibility_ex) {
|
||||||
normalStatus.visibility_ex = status.visibility;
|
normalStatus.visibility_ex = status.visibility;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ const messages = defineMessages({
|
||||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||||
|
add_or_remove_from_antenna: { id: 'account.add_or_remove_from_antenna', defaultMessage: 'Add or Remove from antennas' },
|
||||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
||||||
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
|
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
|
||||||
|
@ -97,6 +98,7 @@ class Header extends ImmutablePureComponent {
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
onUnblockDomain: PropTypes.func.isRequired,
|
||||||
onEndorseToggle: PropTypes.func.isRequired,
|
onEndorseToggle: PropTypes.func.isRequired,
|
||||||
onAddToList: PropTypes.func.isRequired,
|
onAddToList: PropTypes.func.isRequired,
|
||||||
|
onAddToAntenna: PropTypes.func.isRequired,
|
||||||
onEditAccountNote: PropTypes.func.isRequired,
|
onEditAccountNote: PropTypes.func.isRequired,
|
||||||
onChangeLanguages: PropTypes.func.isRequired,
|
onChangeLanguages: PropTypes.func.isRequired,
|
||||||
onInteractionModal: PropTypes.func.isRequired,
|
onInteractionModal: PropTypes.func.isRequired,
|
||||||
|
@ -262,8 +264,9 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
||||||
menu.push(null);
|
|
||||||
}
|
}
|
||||||
|
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_antenna), action: this.props.onAddToAntenna });
|
||||||
|
menu.push(null);
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
if (account.getIn(['relationship', 'muting'])) {
|
||||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
|
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
|
||||||
|
|
|
@ -23,6 +23,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
onUnblockDomain: PropTypes.func.isRequired,
|
||||||
onEndorseToggle: PropTypes.func.isRequired,
|
onEndorseToggle: PropTypes.func.isRequired,
|
||||||
onAddToList: PropTypes.func.isRequired,
|
onAddToList: PropTypes.func.isRequired,
|
||||||
|
onAddToAntenna: PropTypes.func.isRequired,
|
||||||
onChangeLanguages: PropTypes.func.isRequired,
|
onChangeLanguages: PropTypes.func.isRequired,
|
||||||
onInteractionModal: PropTypes.func.isRequired,
|
onInteractionModal: PropTypes.func.isRequired,
|
||||||
onOpenAvatar: PropTypes.func.isRequired,
|
onOpenAvatar: PropTypes.func.isRequired,
|
||||||
|
@ -91,6 +92,10 @@ export default class Header extends ImmutablePureComponent {
|
||||||
this.props.onAddToList(this.props.account);
|
this.props.onAddToList(this.props.account);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleAddToAntenna = () => {
|
||||||
|
this.props.onAddToAntenna(this.props.account);
|
||||||
|
};
|
||||||
|
|
||||||
handleEditAccountNote = () => {
|
handleEditAccountNote = () => {
|
||||||
this.props.onEditAccountNote(this.props.account);
|
this.props.onEditAccountNote(this.props.account);
|
||||||
};
|
};
|
||||||
|
@ -133,6 +138,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onUnblockDomain={this.handleUnblockDomain}
|
onUnblockDomain={this.handleUnblockDomain}
|
||||||
onEndorseToggle={this.handleEndorseToggle}
|
onEndorseToggle={this.handleEndorseToggle}
|
||||||
onAddToList={this.handleAddToList}
|
onAddToList={this.handleAddToList}
|
||||||
|
onAddToAntenna={this.handleAddToAntenna}
|
||||||
onEditAccountNote={this.handleEditAccountNote}
|
onEditAccountNote={this.handleEditAccountNote}
|
||||||
onChangeLanguages={this.handleChangeLanguages}
|
onChangeLanguages={this.handleChangeLanguages}
|
||||||
onInteractionModal={this.handleInteractionModal}
|
onInteractionModal={this.handleInteractionModal}
|
||||||
|
|
|
@ -146,6 +146,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onAddToAntenna (account) {
|
||||||
|
dispatch(openModal('ANTENNA_ADDER', {
|
||||||
|
accountId: account.get('id'),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
onChangeLanguages (account) {
|
onChangeLanguages (account) {
|
||||||
dispatch(openModal('SUBSCRIBED_LANGUAGES', {
|
dispatch(openModal('SUBSCRIBED_LANGUAGES', {
|
||||||
accountId: account.get('id'),
|
accountId: account.get('id'),
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeGetAccount } from '../../../selectors';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import Avatar from '../../../components/avatar';
|
||||||
|
import DisplayName from '../../../components/display_name';
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
const makeMapStateToProps = () => {
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { accountId }) => ({
|
||||||
|
account: getAccount(state, accountId),
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default @connect(makeMapStateToProps)
|
||||||
|
@injectIntl
|
||||||
|
class Account extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { account } = this.props;
|
||||||
|
return (
|
||||||
|
<div className='account'>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<div className='account__display-name'>
|
||||||
|
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import IconButton from '../../../components/icon_button';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
import { removeFromAntennaAdder, addToAntennaAdder } from '../../../actions/antennas';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||||
|
add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const MapStateToProps = (state, { antennaId, added }) => ({
|
||||||
|
antenna: state.get('antennas').get(antennaId),
|
||||||
|
added: typeof added === 'undefined' ? state.getIn(['antennaAdder', 'antennas', 'items']).includes(antennaId) : added,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { antennaId }) => ({
|
||||||
|
onRemove: () => dispatch(removeFromAntennaAdder(antennaId)),
|
||||||
|
onAdd: () => dispatch(addToAntennaAdder(antennaId)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(MapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class Antenna extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
antenna: ImmutablePropTypes.map.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
onRemove: PropTypes.func.isRequired,
|
||||||
|
onAdd: PropTypes.func.isRequired,
|
||||||
|
added: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
added: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { antenna, intl, onRemove, onAdd, added } = this.props;
|
||||||
|
|
||||||
|
let button;
|
||||||
|
|
||||||
|
if (added) {
|
||||||
|
button = <IconButton icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
||||||
|
} else {
|
||||||
|
button = <IconButton icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='list'>
|
||||||
|
<div className='list__wrapper'>
|
||||||
|
<div className='list__display-name'>
|
||||||
|
<Icon id='wifi' className='column-link__icon' fixedWidth />
|
||||||
|
{antenna.get('title')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account__relationship'>
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
69
app/javascript/mastodon/features/antenna_adder/index.jsx
Normal file
69
app/javascript/mastodon/features/antenna_adder/index.jsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
import { setupAntennaAdder, resetAntennaAdder } from '../../actions/antennas';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import Antenna from './components/antenna';
|
||||||
|
import Account from './components/account';
|
||||||
|
// hack
|
||||||
|
|
||||||
|
const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
|
||||||
|
if (!antennas) {
|
||||||
|
return antennas;
|
||||||
|
}
|
||||||
|
|
||||||
|
return antennas.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
antennaIds: getOrderedAntennas(state).map(antenna=>antenna.get('id')),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onInitialize: accountId => dispatch(setupAntennaAdder(accountId)),
|
||||||
|
onReset: () => dispatch(resetAntennaAdder()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class AntennaAdder extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
accountId: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
onInitialize: PropTypes.func.isRequired,
|
||||||
|
onReset: PropTypes.func.isRequired,
|
||||||
|
antennaIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { onInitialize, accountId } = this.props;
|
||||||
|
onInitialize(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
const { onReset } = this.props;
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { accountId, antennaIds } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal list-adder'>
|
||||||
|
<div className='list-adder__account'>
|
||||||
|
<Account accountId={accountId} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='list-adder__lists'>
|
||||||
|
{antennaIds.map(AntennaId => <Antenna key={AntennaId} antennaId={AntennaId} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import {
|
||||||
EmbedModal,
|
EmbedModal,
|
||||||
ListEditor,
|
ListEditor,
|
||||||
ListAdder,
|
ListAdder,
|
||||||
|
AntennaAdder,
|
||||||
CompareHistoryModal,
|
CompareHistoryModal,
|
||||||
FilterModal,
|
FilterModal,
|
||||||
InteractionModal,
|
InteractionModal,
|
||||||
|
@ -43,6 +44,7 @@ const MODAL_COMPONENTS = {
|
||||||
'LIST_EDITOR': ListEditor,
|
'LIST_EDITOR': ListEditor,
|
||||||
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
||||||
'LIST_ADDER': ListAdder,
|
'LIST_ADDER': ListAdder,
|
||||||
|
'ANTENNA_ADDER': AntennaAdder,
|
||||||
'COMPARE_HISTORY': CompareHistoryModal,
|
'COMPARE_HISTORY': CompareHistoryModal,
|
||||||
'FILTER': FilterModal,
|
'FILTER': FilterModal,
|
||||||
'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal,
|
'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal,
|
||||||
|
|
|
@ -146,6 +146,10 @@ export function ListAdder () {
|
||||||
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder');
|
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function AntennaAdder () {
|
||||||
|
return import(/*webpackChunkName: "features/antenna_adder" */'../../antenna_adder');
|
||||||
|
}
|
||||||
|
|
||||||
export function Tesseract () {
|
export function Tesseract () {
|
||||||
return import(/*webpackChunkName: "tesseract" */'tesseract.js');
|
return import(/*webpackChunkName: "tesseract" */'tesseract.js');
|
||||||
}
|
}
|
||||||
|
|
|
@ -459,11 +459,11 @@
|
||||||
"privacy.direct.short": "指定された相手のみ",
|
"privacy.direct.short": "指定された相手のみ",
|
||||||
"privacy.private.long": "フォロワーのみ閲覧可",
|
"privacy.private.long": "フォロワーのみ閲覧可",
|
||||||
"privacy.private.short": "フォロワーのみ",
|
"privacy.private.short": "フォロワーのみ",
|
||||||
"privacy.public.long": "誰でも閲覧可、全てのTL",
|
"privacy.public.long": "誰でも閲覧可、ホーム+ローカル+連合TL",
|
||||||
"privacy.public.short": "公開",
|
"privacy.public.short": "公開",
|
||||||
"privacy.public_unlisted.long": "誰でも閲覧可、ローカル+ホームTL",
|
"privacy.public_unlisted.long": "誰でも閲覧可、ホーム+ローカルTL",
|
||||||
"privacy.public_unlisted.short": "ローカル公開",
|
"privacy.public_unlisted.short": "ローカル公開",
|
||||||
"privacy.unlisted.long": "誰でも閲覧可、ホームTL",
|
"privacy.unlisted.long": "誰でも閲覧可、ホームTL",
|
||||||
"privacy.unlisted.short": "未収載",
|
"privacy.unlisted.short": "未収載",
|
||||||
"privacy_policy.last_updated": "{date}に更新",
|
"privacy_policy.last_updated": "{date}に更新",
|
||||||
"privacy_policy.title": "プライバシーポリシー",
|
"privacy_policy.title": "プライバシーポリシー",
|
||||||
|
|
47
app/javascript/mastodon/reducers/antenna_adder.js
Normal file
47
app/javascript/mastodon/reducers/antenna_adder.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
|
import {
|
||||||
|
ANTENNA_ADDER_RESET,
|
||||||
|
ANTENNA_ADDER_SETUP,
|
||||||
|
ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST,
|
||||||
|
ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS,
|
||||||
|
ANTENNA_ADDER_ANTENNAS_FETCH_FAIL,
|
||||||
|
ANTENNA_EDITOR_ADD_ACCOUNT_SUCCESS,
|
||||||
|
ANTENNA_EDITOR_REMOVE_ACCOUNT_SUCCESS,
|
||||||
|
} from '../actions/antennas';
|
||||||
|
|
||||||
|
const initialState = ImmutableMap({
|
||||||
|
accountId: null,
|
||||||
|
|
||||||
|
antennas: ImmutableMap({
|
||||||
|
items: ImmutableList(),
|
||||||
|
loaded: false,
|
||||||
|
isLoading: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function antennaAdderReducer(state = initialState, action) {
|
||||||
|
switch(action.type) {
|
||||||
|
case ANTENNA_ADDER_RESET:
|
||||||
|
return initialState;
|
||||||
|
case ANTENNA_ADDER_SETUP:
|
||||||
|
return state.withMutations(map => {
|
||||||
|
map.set('accountId', action.account.get('id'));
|
||||||
|
});
|
||||||
|
case ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST:
|
||||||
|
return state.setIn(['antennas', 'isLoading'], true);
|
||||||
|
case ANTENNA_ADDER_ANTENNAS_FETCH_FAIL:
|
||||||
|
return state.setIn(['antennas', 'isLoading'], false);
|
||||||
|
case ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS:
|
||||||
|
return state.update('antennas', antennas => antennas.withMutations(map => {
|
||||||
|
map.set('isLoading', false);
|
||||||
|
map.set('loaded', true);
|
||||||
|
map.set('items', ImmutableList(action.antennas.map(item => item.id)));
|
||||||
|
}));
|
||||||
|
case ANTENNA_EDITOR_ADD_ACCOUNT_SUCCESS:
|
||||||
|
return state.updateIn(['antennas', 'items'], antenna => antenna.unshift(action.antennaId));
|
||||||
|
case ANTENNA_EDITOR_REMOVE_ACCOUNT_SUCCESS:
|
||||||
|
return state.updateIn(['antennas', 'items'], antenna => antenna.filterNot(item => item === action.antennaId));
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
36
app/javascript/mastodon/reducers/antenna_editor.js
Normal file
36
app/javascript/mastodon/reducers/antenna_editor.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
|
import {
|
||||||
|
ANTENNA_ACCOUNTS_FETCH_REQUEST,
|
||||||
|
ANTENNA_ACCOUNTS_FETCH_SUCCESS,
|
||||||
|
ANTENNA_ACCOUNTS_FETCH_FAIL,
|
||||||
|
} from '../actions/antennas';
|
||||||
|
|
||||||
|
const initialState = ImmutableMap({
|
||||||
|
antennaId: null,
|
||||||
|
isSubmitting: false,
|
||||||
|
isChanged: false,
|
||||||
|
title: '',
|
||||||
|
|
||||||
|
accounts: ImmutableMap({
|
||||||
|
items: ImmutableList(),
|
||||||
|
loaded: false,
|
||||||
|
isLoading: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function antennaEditorReducer(state = initialState, action) {
|
||||||
|
switch(action.type) {
|
||||||
|
case ANTENNA_ACCOUNTS_FETCH_REQUEST:
|
||||||
|
return state.setIn(['accounts', 'isLoading'], true);
|
||||||
|
case ANTENNA_ACCOUNTS_FETCH_FAIL:
|
||||||
|
return state.setIn(['accounts', 'isLoading'], false);
|
||||||
|
case ANTENNA_ACCOUNTS_FETCH_SUCCESS:
|
||||||
|
return state.update('accounts', accounts => accounts.withMutations(map => {
|
||||||
|
map.set('isLoading', false);
|
||||||
|
map.set('loaded', true);
|
||||||
|
map.set('items', ImmutableList(action.accounts.map(item => item.id)));
|
||||||
|
}));
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
25
app/javascript/mastodon/reducers/antennas.js
Normal file
25
app/javascript/mastodon/reducers/antennas.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
ANTENNAS_FETCH_SUCCESS,
|
||||||
|
} from '../actions/antennas';
|
||||||
|
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||||
|
|
||||||
|
const initialState = ImmutableMap();
|
||||||
|
|
||||||
|
const normalizeAntenna = (state, antenna) => state.set(antenna.id, fromJS(antenna));
|
||||||
|
|
||||||
|
const normalizeAntennas = (state, antennas) => {
|
||||||
|
antennas.forEach(antenna => {
|
||||||
|
state = normalizeAntenna(state, antenna);
|
||||||
|
});
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function antennas(state = initialState, action) {
|
||||||
|
switch(action.type) {
|
||||||
|
case ANTENNAS_FETCH_SUCCESS:
|
||||||
|
return normalizeAntennas(state, action.antennas);
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,9 @@ import custom_emojis from './custom_emojis';
|
||||||
import lists from './lists';
|
import lists from './lists';
|
||||||
import listEditor from './list_editor';
|
import listEditor from './list_editor';
|
||||||
import listAdder from './list_adder';
|
import listAdder from './list_adder';
|
||||||
|
import antennas from './antennas';
|
||||||
|
import antennaEditor from './antenna_editor';
|
||||||
|
import antennaAdder from './antenna_adder';
|
||||||
import filters from './filters';
|
import filters from './filters';
|
||||||
import conversations from './conversations';
|
import conversations from './conversations';
|
||||||
import suggestions from './suggestions';
|
import suggestions from './suggestions';
|
||||||
|
@ -74,6 +77,9 @@ const reducers = {
|
||||||
lists,
|
lists,
|
||||||
listEditor,
|
listEditor,
|
||||||
listAdder,
|
listAdder,
|
||||||
|
antennas,
|
||||||
|
antennaEditor,
|
||||||
|
antennaAdder,
|
||||||
filters,
|
filters,
|
||||||
conversations,
|
conversations,
|
||||||
suggestions,
|
suggestions,
|
||||||
|
|
|
@ -1067,6 +1067,13 @@ a.name-tag,
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.listname {
|
||||||
|
color: $dark-text-color;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.expiration {
|
.expiration {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -449,11 +449,27 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
SCAN_SEARCHABILITY_RE = /\[searchability:(public|followers|reactors|private)\]/.freeze
|
||||||
|
|
||||||
def searchability
|
def searchability
|
||||||
searchability = searchability_from_audience
|
searchability = searchability_from_audience
|
||||||
|
|
||||||
|
if searchability.nil?
|
||||||
|
note = @account&.note
|
||||||
|
return nil unless note.present?
|
||||||
|
|
||||||
|
searchability_bio = note.scan(SCAN_SEARCHABILITY_RE).first
|
||||||
|
return nil unless searchability_bio
|
||||||
|
|
||||||
|
searchability = searchability_bio[0]
|
||||||
return nil if searchability.nil?
|
return nil if searchability.nil?
|
||||||
|
|
||||||
|
searchability = :public if searchability == 'public'
|
||||||
|
searchability = :unlisted if searchability == 'followers'
|
||||||
|
searchability = :direct if searchability == 'private'
|
||||||
|
searchability = :private if searchability == 'reactors'
|
||||||
|
end
|
||||||
|
|
||||||
visibility = visibility_from_audience_with_silence
|
visibility = visibility_from_audience_with_silence
|
||||||
|
|
||||||
if searchability === visibility
|
if searchability === visibility
|
||||||
|
|
|
@ -186,9 +186,13 @@ class ActivityPub::TagManager
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def subscribable_by(account)
|
||||||
|
account.dissubscribable ? [] : [COLLECTIONS[:public]]
|
||||||
|
end
|
||||||
|
|
||||||
def searchable_by(status)
|
def searchable_by(status)
|
||||||
searchable_by =
|
searchable_by =
|
||||||
case status.compute_searchability
|
case status.compute_searchability_activitypub
|
||||||
when 'public'
|
when 'public'
|
||||||
[COLLECTIONS[:public]]
|
[COLLECTIONS[:public]]
|
||||||
when 'unlisted' # Followers only in kmyblue (generics: private)
|
when 'unlisted' # Followers only in kmyblue (generics: private)
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
# requested_review_at :datetime
|
# requested_review_at :datetime
|
||||||
# group_allow_private_message :boolean
|
# group_allow_private_message :boolean
|
||||||
# searchability :integer default("private"), not null
|
# searchability :integer default("private"), not null
|
||||||
|
# dissubscribable :boolean default(FALSE), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
|
|
209
app/models/antenna.rb
Normal file
209
app/models/antenna.rb
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: antennas
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# account_id :bigint(8) not null
|
||||||
|
# list_id :bigint(8) not null
|
||||||
|
# title :string default(""), not null
|
||||||
|
# keywords :jsonb
|
||||||
|
# exclude_keywords :jsonb
|
||||||
|
# any_domains :boolean default(TRUE), not null
|
||||||
|
# any_tags :boolean default(TRUE), not null
|
||||||
|
# any_accounts :boolean default(TRUE), not null
|
||||||
|
# any_keywords :boolean default(TRUE), not null
|
||||||
|
# available :boolean default(TRUE), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# expires_at :datetime
|
||||||
|
# with_media_only :boolean default(FALSE), not null
|
||||||
|
#
|
||||||
|
class Antenna < ApplicationRecord
|
||||||
|
include Expireable
|
||||||
|
|
||||||
|
has_many :antenna_domains, inverse_of: :antenna, dependent: :destroy
|
||||||
|
has_many :antenna_tags, inverse_of: :antenna, dependent: :destroy
|
||||||
|
has_many :antenna_accounts, inverse_of: :antenna, dependent: :destroy
|
||||||
|
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :list
|
||||||
|
|
||||||
|
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')) }
|
||||||
|
|
||||||
|
validate :list_owner
|
||||||
|
|
||||||
|
def list_owner
|
||||||
|
raise Mastodon::ValidationError, I18n.t('antennas.errors.invalid_list_owner') if list.account != account
|
||||||
|
end
|
||||||
|
|
||||||
|
def enabled?
|
||||||
|
enabled_config? && !expired?
|
||||||
|
end
|
||||||
|
|
||||||
|
def enabled_config?
|
||||||
|
available && enabled_config_raws?
|
||||||
|
end
|
||||||
|
|
||||||
|
def enabled_config_raws?
|
||||||
|
!(any_keywords && any_domains && any_accounts && any_tags)
|
||||||
|
end
|
||||||
|
|
||||||
|
def expires_in
|
||||||
|
return @expires_in if defined?(@expires_in)
|
||||||
|
return nil if expires_at.nil?
|
||||||
|
|
||||||
|
[30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].find { |expires_in| expires_in.from_now >= expires_at }
|
||||||
|
end
|
||||||
|
|
||||||
|
def context
|
||||||
|
context = []
|
||||||
|
context << 'domain' if !any_domains
|
||||||
|
context << 'tag' if !any_tags
|
||||||
|
context << 'keyword' if !any_keywords
|
||||||
|
context << 'account' if !any_accounts
|
||||||
|
context
|
||||||
|
end
|
||||||
|
|
||||||
|
def list=(list_id)
|
||||||
|
list_id = list_id.to_i if list_id.is_a?(String)
|
||||||
|
if list_id.is_a?(Numeric)
|
||||||
|
self[:list_id] = list_id
|
||||||
|
else
|
||||||
|
self[:list] = list_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def keywords_raw
|
||||||
|
return '' if !keywords.present?
|
||||||
|
|
||||||
|
keywords.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def keywords_raw=(raw)
|
||||||
|
keywords = raw.split(/\R/).filter { |r| r.present? && r.length >= 2 }.uniq
|
||||||
|
self[:keywords] = keywords
|
||||||
|
self[:any_keywords] = !keywords.any? && !exclude_keywords&.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_keywords_raw
|
||||||
|
return '' if !exclude_keywords.present?
|
||||||
|
|
||||||
|
exclude_keywords.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_keywords_raw=(raw)
|
||||||
|
exclude_keywords = raw.split(/\R/).filter { |r| r.present? }.uniq
|
||||||
|
self[:exclude_keywords] = exclude_keywords
|
||||||
|
self[:any_keywords] = !keywords&.any? && !exclude_keywords.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def tags_raw
|
||||||
|
antenna_tags.where(exclude: false).map(&:tag).map(&:name).join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def tags_raw=(raw)
|
||||||
|
return if tags_raw == raw
|
||||||
|
|
||||||
|
tag_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('#') ? r[1..-1] : r }.uniq
|
||||||
|
|
||||||
|
antenna_tags.where(exclude: false).destroy_all
|
||||||
|
Tag.find_or_create_by_names(tag_names).each do |tag|
|
||||||
|
antenna_tags.create!(tag: tag, exclude: false)
|
||||||
|
end
|
||||||
|
self[:any_tags] = !tag_names.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_tags_raw
|
||||||
|
antenna_tags.where(exclude: true).map(&:tag).map(&:name).join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_tags_raw=(raw)
|
||||||
|
return if exclude_tags_raw == raw
|
||||||
|
|
||||||
|
tag_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('#') ? r[1..-1] : r }.uniq
|
||||||
|
|
||||||
|
antenna_tags.where(exclude: true).destroy_all
|
||||||
|
Tag.find_or_create_by_names(tag_names).each do |tag|
|
||||||
|
antenna_tags.create!(tag: tag, exclude: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def domains_raw
|
||||||
|
antenna_domains.where(exclude: false).map(&:name).join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def domains_raw=(raw)
|
||||||
|
return if domains_raw == raw
|
||||||
|
|
||||||
|
domain_names = raw.split(/\R/).filter { |r| r.present? }.uniq
|
||||||
|
|
||||||
|
antenna_domains.where(exclude: false).destroy_all
|
||||||
|
domain_names.each do |domain|
|
||||||
|
antenna_domains.create!(name: domain, exclude: false)
|
||||||
|
end
|
||||||
|
self[:any_domains] = !domain_names.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_domains_raw
|
||||||
|
antenna_domains.where(exclude: true).map(&:name).join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_domains_raw=(raw)
|
||||||
|
return if exclude_domains_raw == raw
|
||||||
|
|
||||||
|
domain_names = raw.split(/\R/).filter { |r| r.present? }.uniq
|
||||||
|
|
||||||
|
antenna_domains.where(exclude: true).destroy_all
|
||||||
|
domain_names.each do |domain|
|
||||||
|
antenna_domains.create!(name: domain, exclude: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def accounts_raw
|
||||||
|
antenna_accounts.where(exclude: false).map(&:account).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def accounts_raw=(raw)
|
||||||
|
return if accounts_raw == raw
|
||||||
|
|
||||||
|
account_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('@') ? r[1..-1] : r }.uniq
|
||||||
|
|
||||||
|
hit = false
|
||||||
|
antenna_accounts.where(exclude: false).destroy_all
|
||||||
|
account_names.each do |name|
|
||||||
|
username, domain = name.split('@')
|
||||||
|
account = Account.find_by(username: username, domain: domain)
|
||||||
|
if account.present?
|
||||||
|
antenna_accounts.create!(account: account, exclude: false)
|
||||||
|
hit = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self[:any_accounts] = !hit
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_accounts_raw
|
||||||
|
antenna_accounts.where(exclude: true).map(&:account).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_accounts_raw=(raw)
|
||||||
|
return if exclude_accounts_raw == raw
|
||||||
|
|
||||||
|
account_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('@') ? r[1..-1] : r }.uniq
|
||||||
|
|
||||||
|
hit = false
|
||||||
|
antenna_accounts.where(exclude: true).destroy_all
|
||||||
|
account_names.each do |name|
|
||||||
|
username, domain = name.split('@')
|
||||||
|
account = Account.find_by(username: username, domain: domain)
|
||||||
|
if account.present?
|
||||||
|
antenna_accounts.create!(account: account, exclude: true)
|
||||||
|
hit = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
19
app/models/antenna_account.rb
Normal file
19
app/models/antenna_account.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: antenna_accounts
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# antenna_id :bigint(8) not null
|
||||||
|
# account_id :bigint(8) not null
|
||||||
|
# exclude :boolean default(FALSE), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
class AntennaAccount < ApplicationRecord
|
||||||
|
|
||||||
|
belongs_to :antenna
|
||||||
|
belongs_to :account
|
||||||
|
|
||||||
|
validates :account_id, uniqueness: { scope: :antenna_id }
|
||||||
|
|
||||||
|
end
|
16
app/models/antenna_domain.rb
Normal file
16
app/models/antenna_domain.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: antenna_domains
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# antenna_id :bigint(8) not null
|
||||||
|
# name :string
|
||||||
|
# exclude :boolean default(FALSE), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
class AntennaDomain < ApplicationRecord
|
||||||
|
|
||||||
|
belongs_to :antenna
|
||||||
|
|
||||||
|
end
|
17
app/models/antenna_tag.rb
Normal file
17
app/models/antenna_tag.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: antenna_tags
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# antenna_id :bigint(8) not null
|
||||||
|
# tag_id :bigint(8) not null
|
||||||
|
# exclude :boolean default(FALSE), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
class AntennaTag < ApplicationRecord
|
||||||
|
|
||||||
|
belongs_to :antenna
|
||||||
|
belongs_to :tag
|
||||||
|
|
||||||
|
end
|
|
@ -39,6 +39,8 @@ module AccountAssociations
|
||||||
|
|
||||||
has_many :report_notes, dependent: :destroy
|
has_many :report_notes, dependent: :destroy
|
||||||
has_many :custom_filters, inverse_of: :account, dependent: :destroy
|
has_many :custom_filters, inverse_of: :account, dependent: :destroy
|
||||||
|
has_many :antennas, inverse_of: :account, dependent: :destroy
|
||||||
|
has_many :antenna_accounts, inverse_of: :account, dependent: :destroy
|
||||||
|
|
||||||
# Moderation notes
|
# Moderation notes
|
||||||
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
|
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
|
||||||
|
@ -46,6 +48,10 @@ module AccountAssociations
|
||||||
has_many :account_warnings, dependent: :destroy, inverse_of: :account
|
has_many :account_warnings, dependent: :destroy, inverse_of: :account
|
||||||
has_many :strikes, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
has_many :strikes, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
||||||
|
|
||||||
|
# Antennas (that the account is on, not owned by the account)
|
||||||
|
has_many :antenna_accounts, inverse_of: :account, dependent: :destroy
|
||||||
|
has_many :joined_antennas, class_name: 'Antenna', through: :antenna_accounts, source: :antenna
|
||||||
|
|
||||||
# Lists (that the account is on, not owned by the account)
|
# Lists (that the account is on, not owned by the account)
|
||||||
has_many :list_accounts, inverse_of: :account, dependent: :destroy
|
has_many :list_accounts, inverse_of: :account, dependent: :destroy
|
||||||
has_many :lists, through: :list_accounts
|
has_many :lists, through: :list_accounts
|
||||||
|
|
|
@ -23,6 +23,7 @@ class List < ApplicationRecord
|
||||||
|
|
||||||
has_many :list_accounts, inverse_of: :list, dependent: :destroy
|
has_many :list_accounts, inverse_of: :list, dependent: :destroy
|
||||||
has_many :accounts, through: :list_accounts
|
has_many :accounts, through: :list_accounts
|
||||||
|
has_many :antennas, inverse_of: :list, dependent: :destroy
|
||||||
|
|
||||||
validates :title, presence: true
|
validates :title, presence: true
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ class MediaAttachment < ApplicationRecord
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
LOCAL_STATUS_ATTACHMENT_MAX = 4
|
LOCAL_STATUS_ATTACHMENT_MAX = 4
|
||||||
ACTIVITYPUB_STATUS_ATTACHMENT_MAX = 8
|
ACTIVITYPUB_STATUS_ATTACHMENT_MAX = 16
|
||||||
|
|
||||||
enum type: { :image => 0, :gifv => 1, :video => 2, :unknown => 3, :audio => 4 }
|
enum type: { :image => 0, :gifv => 1, :video => 2, :unknown => 3, :audio => 4 }
|
||||||
enum processing: { :queued => 0, :in_progress => 1, :complete => 2, :failed => 3 }, _prefix: true
|
enum processing: { :queued => 0, :in_progress => 1, :complete => 2, :failed => 3 }, _prefix: true
|
||||||
|
|
|
@ -291,14 +291,6 @@ class Status < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def ordered_media_attachments_original_mastodon
|
|
||||||
ordered_media_attachments.take(4)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ordered_media_attachments_extra
|
|
||||||
ordered_media_attachments.drop(4).take(4)
|
|
||||||
end
|
|
||||||
|
|
||||||
def replies_count
|
def replies_count
|
||||||
status_stat&.replies_count || 0
|
status_stat&.replies_count || 0
|
||||||
end
|
end
|
||||||
|
@ -380,6 +372,11 @@ class Status < ApplicationRecord
|
||||||
'private'
|
'private'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def compute_searchability_activitypub
|
||||||
|
return 'unlisted' if public_unlisted_visibility? && public_searchability?
|
||||||
|
compute_searchability
|
||||||
|
end
|
||||||
|
|
||||||
after_create_commit :increment_counter_caches
|
after_create_commit :increment_counter_caches
|
||||||
after_destroy_commit :decrement_counter_caches
|
after_destroy_commit :decrement_counter_caches
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ class Tag < ApplicationRecord
|
||||||
has_many :featured_tags, dependent: :destroy, inverse_of: :tag
|
has_many :featured_tags, dependent: :destroy, inverse_of: :tag
|
||||||
has_many :followers, through: :passive_relationships, source: :account
|
has_many :followers, through: :passive_relationships, source: :account
|
||||||
|
|
||||||
|
has_one :antenna_tag, dependent: :destroy, inverse_of: :tag
|
||||||
|
|
||||||
HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c"
|
HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c"
|
||||||
HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]"
|
HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]"
|
||||||
HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]"
|
HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]"
|
||||||
|
|
|
@ -7,13 +7,13 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||||
context :security
|
context :security
|
||||||
|
|
||||||
context_extensions :manually_approves_followers, :featured, :also_known_as,
|
context_extensions :manually_approves_followers, :featured, :also_known_as,
|
||||||
:moved_to, :property_value, :discoverable, :olm, :suspended, :searchable_by
|
:moved_to, :property_value, :discoverable, :olm, :suspended, :searchable_by, :subscribable_by
|
||||||
|
|
||||||
attributes :id, :type, :following, :followers,
|
attributes :id, :type, :following, :followers,
|
||||||
:inbox, :outbox, :featured, :featured_tags,
|
:inbox, :outbox, :featured, :featured_tags,
|
||||||
:preferred_username, :name, :summary,
|
:preferred_username, :name, :summary,
|
||||||
:url, :manually_approves_followers,
|
:url, :manually_approves_followers,
|
||||||
:discoverable, :published, :searchable_by
|
:discoverable, :published, :searchable_by, :subscribable_by
|
||||||
|
|
||||||
has_one :public_key, serializer: ActivityPub::PublicKeySerializer
|
has_one :public_key, serializer: ActivityPub::PublicKeySerializer
|
||||||
|
|
||||||
|
@ -166,6 +166,10 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||||
ActivityPub::TagManager.instance.account_searchable_by(object)
|
ActivityPub::TagManager.instance.account_searchable_by(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def subscribable_by
|
||||||
|
ActivityPub::TagManager.instance.subscribable_by(object)
|
||||||
|
end
|
||||||
|
|
||||||
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
|
||||||
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
|
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
|
||||||
:note, :url, :avatar, :avatar_static, :header, :header_static, :searchability,
|
:note, :url, :avatar, :avatar_static, :header, :header_static, :searchability, :dissubscribable,
|
||||||
:followers_count, :following_count, :statuses_count, :last_status_at
|
:followers_count, :following_count, :statuses_count, :last_status_at
|
||||||
|
|
||||||
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
||||||
|
|
9
app/serializers/rest/antenna_serializer.rb
Normal file
9
app/serializers/rest/antenna_serializer.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::AntennaSerializer < ActiveModel::Serializer
|
||||||
|
attributes :id, :title
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.id.to_s
|
||||||
|
end
|
||||||
|
end
|
|
@ -55,6 +55,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
||||||
statuses: {
|
statuses: {
|
||||||
max_characters: StatusLengthValidator::MAX_CHARS,
|
max_characters: StatusLengthValidator::MAX_CHARS,
|
||||||
max_media_attachments: MediaAttachment::LOCAL_STATUS_ATTACHMENT_MAX,
|
max_media_attachments: MediaAttachment::LOCAL_STATUS_ATTACHMENT_MAX,
|
||||||
|
max_media_attachments_from_activitypub: MediaAttachment::ACTIVITYPUB_STATUS_ATTACHMENT_MAX,
|
||||||
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
|
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -97,7 +98,6 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
||||||
def fedibird_capabilities
|
def fedibird_capabilities
|
||||||
capabilities = [
|
capabilities = [
|
||||||
:emoji_reaction,
|
:emoji_reaction,
|
||||||
:kmyblue_extra_media_attachments,
|
|
||||||
:kmyblue_visibility_public_unlisted,
|
:kmyblue_visibility_public_unlisted,
|
||||||
:enable_wide_emoji,
|
:enable_wide_emoji,
|
||||||
:enable_wide_emoji_reaction,
|
:enable_wide_emoji_reaction,
|
||||||
|
|
|
@ -22,8 +22,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
belongs_to :application, if: :show_application?
|
belongs_to :application, if: :show_application?
|
||||||
belongs_to :account, serializer: REST::AccountSerializer
|
belongs_to :account, serializer: REST::AccountSerializer
|
||||||
|
|
||||||
has_many :ordered_media_attachments_original_mastodon, key: :media_attachments, serializer: REST::MediaAttachmentSerializer
|
has_many :ordered_media_attachments, key: :media_attachments, serializer: REST::MediaAttachmentSerializer
|
||||||
has_many :ordered_media_attachments_extra, key: :media_attachments_ex, serializer: REST::MediaAttachmentSerializer
|
|
||||||
has_many :ordered_mentions, key: :mentions
|
has_many :ordered_mentions, key: :mentions
|
||||||
has_many :tags
|
has_many :tags
|
||||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||||
|
|
|
@ -65,6 +65,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
||||||
statuses: {
|
statuses: {
|
||||||
max_characters: StatusLengthValidator::MAX_CHARS,
|
max_characters: StatusLengthValidator::MAX_CHARS,
|
||||||
max_media_attachments: MediaAttachment::LOCAL_STATUS_ATTACHMENT_MAX,
|
max_media_attachments: MediaAttachment::LOCAL_STATUS_ATTACHMENT_MAX,
|
||||||
|
max_media_attachments_from_activitypub: MediaAttachment::ACTIVITYPUB_STATUS_ATTACHMENT_MAX,
|
||||||
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
|
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -107,7 +108,6 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
||||||
def fedibird_capabilities
|
def fedibird_capabilities
|
||||||
capabilities = [
|
capabilities = [
|
||||||
:emoji_reaction,
|
:emoji_reaction,
|
||||||
:kmyblue_extra_media_attachments,
|
|
||||||
:kmyblue_visibility_public_unlisted,
|
:kmyblue_visibility_public_unlisted,
|
||||||
:enable_wide_emoji,
|
:enable_wide_emoji,
|
||||||
:enable_wide_emoji_reaction,
|
:enable_wide_emoji_reaction,
|
||||||
|
|
|
@ -78,6 +78,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
@account.suspension_origin = :local if auto_suspend?
|
@account.suspension_origin = :local if auto_suspend?
|
||||||
@account.silenced_at = domain_block.created_at if auto_silence?
|
@account.silenced_at = domain_block.created_at if auto_silence?
|
||||||
@account.searchability = :private # not null
|
@account.searchability = :private # not null
|
||||||
|
@account.dissubscribable = false # not null
|
||||||
@account.save
|
@account.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -115,6 +116,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
|
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
|
||||||
@account.discoverable = @json['discoverable'] || false
|
@account.discoverable = @json['discoverable'] || false
|
||||||
@account.searchability = searchability_from_audience
|
@account.searchability = searchability_from_audience
|
||||||
|
@account.dissubscribable = !subscribable(@account.note)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_fetchable_key!
|
def set_fetchable_key!
|
||||||
|
@ -249,6 +251,20 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def subscribable_by
|
||||||
|
return nil if @json['subscribableBy'].nil?
|
||||||
|
|
||||||
|
@subscribable_by = as_array(@json['subscribableBy']).map { |x| value_or_id(x) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscribable(note)
|
||||||
|
if subscribable_by.nil?
|
||||||
|
!note.include?('[subscribable:no]')
|
||||||
|
else
|
||||||
|
subscribable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def property_values
|
def property_values
|
||||||
return unless @json['attachment'].is_a?(Array)
|
return unless @json['attachment'].is_a?(Array)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ class DeleteAccountService < BaseService
|
||||||
account_pins
|
account_pins
|
||||||
active_relationships
|
active_relationships
|
||||||
aliases
|
aliases
|
||||||
|
antennas
|
||||||
block_relationships
|
block_relationships
|
||||||
blocked_by_relationships
|
blocked_by_relationships
|
||||||
conversation_mutes
|
conversation_mutes
|
||||||
|
|
|
@ -49,6 +49,7 @@ class FanOutOnWriteService < BaseService
|
||||||
when :public, :unlisted, :public_unlisted, :private
|
when :public, :unlisted, :public_unlisted, :private
|
||||||
deliver_to_all_followers!
|
deliver_to_all_followers!
|
||||||
deliver_to_lists!
|
deliver_to_lists!
|
||||||
|
deliver_to_antennas! if [:public, :public_unlisted].include?(@status.visibility.to_sym) && !@status.account.dissubscribable
|
||||||
when :limited
|
when :limited
|
||||||
deliver_to_mentioned_followers!
|
deliver_to_mentioned_followers!
|
||||||
else
|
else
|
||||||
|
@ -115,6 +116,35 @@ class FanOutOnWriteService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deliver_to_antennas!
|
||||||
|
lists = []
|
||||||
|
antennas = Antenna.availables
|
||||||
|
antennas = antennas.left_joins(:antenna_accounts).where(any_accounts: true).or(Antenna.availables.left_joins(:antenna_accounts) .where(antenna_accounts: { exclude: false, account: @status.account }))
|
||||||
|
antennas = antennas.left_joins(:antenna_domains) .where(any_domains: true) .or(Antenna.availables.left_joins(:antenna_accounts).left_joins(:antenna_domains) .where(antenna_domains: { exclude: false, name: @status.account.domain }))
|
||||||
|
antennas = antennas.left_joins(:antenna_tags) .where(any_tags: true) .or(Antenna.availables.left_joins(:antenna_accounts).left_joins(:antenna_domains).left_joins(:antenna_tags).where(antenna_tags: { exclude: false, tag: @status.tags }))
|
||||||
|
antennas = antennas.where(account: @status.account.followers) if @status.visibility.to_sym == :unlisted
|
||||||
|
antennas.in_batches do |ans|
|
||||||
|
ans.each do |antenna|
|
||||||
|
next if !antenna.enabled?
|
||||||
|
next if @status.account.blocking?(antenna.account)
|
||||||
|
next if antenna.keywords.any? && !([nil, :public].include?(@status.searchability&.to_sym))
|
||||||
|
next if antenna.keywords.any? && !antenna.keywords.any? { |keyword| @status.text.include?(keyword) }
|
||||||
|
next if antenna.exclude_keywords.any? && antenna.exclude_keywords.any? { |keyword| @status.text.include?(keyword) }
|
||||||
|
next if antenna.antenna_accounts.where(exclude: true, account: @status.account).any?
|
||||||
|
next if antenna.antenna_domains.where(exclude: true, name: @status.account.domain).any?
|
||||||
|
next if antenna.antenna_tags.where(exclude: true, tag: @status.tags).any?
|
||||||
|
lists << antenna.list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lists = lists.uniq
|
||||||
|
|
||||||
|
if lists.any?
|
||||||
|
FeedInsertWorker.push_bulk(lists) do |list|
|
||||||
|
[@status.id, list.id, 'list', { 'update' => update? }]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def deliver_to_mentioned_followers!
|
def deliver_to_mentioned_followers!
|
||||||
@status.mentions.joins(:account).merge(@account.followers_for_local_distribution).select(:id, :account_id).reorder(nil).find_in_batches do |mentions|
|
@status.mentions.joins(:account).merge(@account.followers_for_local_distribution).select(:id, :account_id).reorder(nil).find_in_batches do |mentions|
|
||||||
FeedInsertWorker.push_bulk(mentions) do |mention|
|
FeedInsertWorker.push_bulk(mentions) do |mention|
|
||||||
|
|
74
app/views/antennas/_antenna.html.haml
Normal file
74
app/views/antennas/_antenna.html.haml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
.filters-list__item{ class: [(antenna.expired? || !antenna.enabled_config?) && 'expired'] }
|
||||||
|
= link_to edit_antenna_path(antenna), class: 'filters-list__item__title' do
|
||||||
|
= antenna.title
|
||||||
|
|
||||||
|
- if !antenna.enabled_config?
|
||||||
|
.expiration{ title: t('antennas.index.disabled') }
|
||||||
|
= t('antennas.index.disabled')
|
||||||
|
- elsif antenna.expires?
|
||||||
|
.expiration{ title: t('antennas.index.expires_on', date: l(antenna.expires_at)) }
|
||||||
|
- if antenna.expired?
|
||||||
|
= t('invites.expired')
|
||||||
|
- else
|
||||||
|
= t('antennas.index.expires_in', distance: distance_of_time_in_words_to_now(antenna.expires_at))
|
||||||
|
|
||||||
|
.listname
|
||||||
|
= antenna.list.title
|
||||||
|
|
||||||
|
.filters-list__item__permissions
|
||||||
|
%ul.permissions-list
|
||||||
|
- unless antenna.antenna_domains.empty?
|
||||||
|
%li.permissions-list__item
|
||||||
|
.permissions-list__item__icon
|
||||||
|
= fa_icon('sitemap')
|
||||||
|
.permissions-list__item__text
|
||||||
|
.permissions-list__item__text__title
|
||||||
|
= t('antennas.index.domains', count: antenna.antenna_domains.size)
|
||||||
|
.permissions-list__item__text__type
|
||||||
|
- domains = antenna.antenna_domains.map { |domain| domain.name }
|
||||||
|
- domains = domains.take(5) + ['…'] if domains.size > 5 # TODO
|
||||||
|
= domains.join(', ')
|
||||||
|
- unless antenna.antenna_accounts.empty?
|
||||||
|
%li.permissions-list__item
|
||||||
|
.permissions-list__item__icon
|
||||||
|
= fa_icon('users')
|
||||||
|
.permissions-list__item__text
|
||||||
|
.permissions-list__item__text__title
|
||||||
|
= t('antennas.index.accounts', count: antenna.antenna_accounts.size)
|
||||||
|
.permissions-list__item__text__type
|
||||||
|
- accounts = antenna.antenna_accounts.map { |account| account.account.domain ? "@#{account.account.username}@#{account.account.domain}" : "@#{account.account.username}" }
|
||||||
|
- accounts = accounts.take(5) + ['…'] if accounts.size > 5 # TODO
|
||||||
|
= accounts.join(', ')
|
||||||
|
- unless antenna.keywords.nil? || antenna.keywords.empty?
|
||||||
|
%li.permissions-list__item
|
||||||
|
.permissions-list__item__icon
|
||||||
|
= fa_icon('paragraph')
|
||||||
|
.permissions-list__item__text
|
||||||
|
.permissions-list__item__text__title
|
||||||
|
= t('antennas.index.keywords', count: antenna.keywords.size)
|
||||||
|
.permissions-list__item__text__type
|
||||||
|
- keywords = antenna.keywords
|
||||||
|
- keywords = keywords.take(5) + ['…'] if keywords.size > 5 # TODO
|
||||||
|
= keywords.join(', ')
|
||||||
|
- unless antenna.antenna_tags.empty?
|
||||||
|
%li.permissions-list__item
|
||||||
|
.permissions-list__item__icon
|
||||||
|
= fa_icon('hashtag')
|
||||||
|
.permissions-list__item__text
|
||||||
|
.permissions-list__item__text__title
|
||||||
|
= t('antennas.index.tags', count: antenna.antenna_tags.size)
|
||||||
|
.permissions-list__item__text__type
|
||||||
|
- tags = antenna.antenna_tags.map { |tag| tag.tag.name }
|
||||||
|
- tags = keywords.take(5) + ['…'] if tags.size > 5 # TODO
|
||||||
|
= tags.join(', ')
|
||||||
|
|
||||||
|
.announcements-list__item__action-bar
|
||||||
|
.announcements-list__item__meta
|
||||||
|
- if antenna.enabled_config_raws?
|
||||||
|
= t('antennas.index.contexts', contexts: antenna.context.map { |context| I18n.t("antennas.contexts.#{context}") }.join(', '))
|
||||||
|
- else
|
||||||
|
= t('antennas.errors.empty_contexts')
|
||||||
|
|
||||||
|
%div
|
||||||
|
= table_link_to 'pencil', t('antennas.edit.title'), edit_antenna_path(antenna)
|
||||||
|
= table_link_to 'times', t('antennas.index.delete'), antenna_path(antenna), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
51
app/views/antennas/_antenna_fields.html.haml
Normal file
51
app/views/antennas/_antenna_fields.html.haml
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
%p= t 'antennas.edit.description'
|
||||||
|
%hr.spacer/
|
||||||
|
|
||||||
|
.fields-row
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :title, as: :string, wrapper: :with_label, hint: false
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt')
|
||||||
|
|
||||||
|
.fields-row
|
||||||
|
.fields-group.fields-row__column.fields-row__column-6
|
||||||
|
= f.input :list, collection: lists, wrapper: :with_label, label_method: lambda { |list| list.title }, label: t('antennas.edit.list'), selected: f.object.list&.id, hint: false
|
||||||
|
.fields-group.fields-row__column.fields-row__column-6
|
||||||
|
= f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false
|
||||||
|
|
||||||
|
%hr.spacer/
|
||||||
|
%p.hint= t 'antennas.edit.hint'
|
||||||
|
%hr.spacer/
|
||||||
|
|
||||||
|
%h4= t('antennas.contexts.domain')
|
||||||
|
|
||||||
|
.fields-row
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.domains_raw')
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :exclude_domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_domains_raw')
|
||||||
|
|
||||||
|
%h4= t('antennas.contexts.account')
|
||||||
|
%p.hint= t 'antennas.edit.accounts_hint'
|
||||||
|
|
||||||
|
.fields-row
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.accounts_raw')
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :exclude_accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_accounts_raw')
|
||||||
|
|
||||||
|
%h4= t('antennas.contexts.tag')
|
||||||
|
|
||||||
|
.fields-row
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.tags_raw')
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :exclude_tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_tags_raw')
|
||||||
|
|
||||||
|
%h4= t('antennas.contexts.keyword')
|
||||||
|
|
||||||
|
.fields-row
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.keywords_raw')
|
||||||
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
|
= f.input :exclude_keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_keywords_raw')
|
8
app/views/antennas/edit.html.haml
Normal file
8
app/views/antennas/edit.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= t('antennas.edit.title')
|
||||||
|
|
||||||
|
= simple_form_for @antenna, url: antenna_path(@antenna), method: :put do |f|
|
||||||
|
= render 'antenna_fields', f: f, lists: @lists
|
||||||
|
|
||||||
|
.actions
|
||||||
|
= f.button :button, t('generic.save_changes'), type: :submit
|
14
app/views/antennas/index.html.haml
Normal file
14
app/views/antennas/index.html.haml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= t('antennas.index.title')
|
||||||
|
|
||||||
|
- content_for :heading_actions do
|
||||||
|
= link_to t('antennas.new.title'), new_antenna_path, class: 'button'
|
||||||
|
|
||||||
|
.flash-message.alert
|
||||||
|
%strong= t('antennas.beta')
|
||||||
|
|
||||||
|
- if @antennas.empty?
|
||||||
|
.muted-hint.center-text= t 'antennas.index.empty'
|
||||||
|
- else
|
||||||
|
.applications-list
|
||||||
|
= render partial: 'antenna', collection: @antennas
|
8
app/views/antennas/new.html.haml
Normal file
8
app/views/antennas/new.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= t('antennas.new.title')
|
||||||
|
|
||||||
|
= simple_form_for @antenna, url: antennas_path do |f|
|
||||||
|
= render 'antenna_fields', f: f, lists: @lists
|
||||||
|
|
||||||
|
.actions
|
||||||
|
= f.button :button, t('antennas.new.save'), type: :submit
|
|
@ -38,6 +38,9 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :hide_collections, as: :boolean, wrapper: :with_label, label: t('simple_form.labels.defaults.setting_hide_network'), hint: t('simple_form.hints.defaults.setting_hide_network')
|
= f.input :hide_collections, as: :boolean, wrapper: :with_label, label: t('simple_form.labels.defaults.setting_hide_network'), hint: t('simple_form.hints.defaults.setting_hide_network')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :dissubscribable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.dissubscribable')
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
.fields-row
|
.fields-row
|
||||||
|
|
|
@ -964,6 +964,44 @@ en:
|
||||||
empty: You have no aliases.
|
empty: You have no aliases.
|
||||||
hint_html: If you want to move from another account to this one, here you can create an alias, which is required before you can proceed with moving followers from the old account to this one. This action by itself is <strong>harmless and reversible</strong>. <strong>The account migration is initiated from the old account</strong>.
|
hint_html: If you want to move from another account to this one, here you can create an alias, which is required before you can proceed with moving followers from the old account to this one. This action by itself is <strong>harmless and reversible</strong>. <strong>The account migration is initiated from the old account</strong>.
|
||||||
remove: Unlink alias
|
remove: Unlink alias
|
||||||
|
antennas:
|
||||||
|
beta: This function is in beta.
|
||||||
|
contexts:
|
||||||
|
account: Accounts
|
||||||
|
domain: Domains
|
||||||
|
keyword: Keywords
|
||||||
|
tag: Tags
|
||||||
|
edit:
|
||||||
|
accounts_hint: \@askyq or @askyq@example.com
|
||||||
|
accounts_raw: Account list
|
||||||
|
available: Available
|
||||||
|
description: アンテナは、サーバーが認識した全ての公開・ローカル公開投稿のうち、検索許可が「公開」または明示的に設定されていないもの(検索許可システムに対応していないサーバーからの投稿)、かつ購読を拒否していないすべてのアカウントからの投稿が対象です。検出された投稿は、指定したリストに追加されます。
|
||||||
|
domains_raw: Domain list
|
||||||
|
exclude_accounts_raw: Excluding account list
|
||||||
|
exclude_domains_raw: Excluding domain list
|
||||||
|
exclude_keywords_raw: Excluding keyword list
|
||||||
|
exclude_tags_raw: Excluding hashtag list
|
||||||
|
hint: 下のリストに、絞り込み条件・除外条件を入力します。条件は複数指定することができます。1行につき1つずつ入力してください。空行、コメント、重複を含めることはできません。
|
||||||
|
keywords_raw: Keyword list
|
||||||
|
list: Destination list
|
||||||
|
tags_raw: Hashtag list
|
||||||
|
title: Edit antenna
|
||||||
|
errors:
|
||||||
|
deprecated_api_multiple_keywords: These parameters cannot be changed from this application because they apply to more than one filter keyword. Use a more recent application or the web interface.
|
||||||
|
empty_contexts: No contexts! You must set any context filters
|
||||||
|
invalid_context: None or invalid context supplied
|
||||||
|
invalid_list_owner: This list is not yours
|
||||||
|
index:
|
||||||
|
contexts: Antennas in %{contexts}
|
||||||
|
delete: Delete
|
||||||
|
disabled: Disabled
|
||||||
|
empty: You have no antennas.
|
||||||
|
expires_in: Expires in %{distance}
|
||||||
|
expires_on: Expires on %{date}
|
||||||
|
title: Antennas
|
||||||
|
new:
|
||||||
|
save: Save new antenna
|
||||||
|
title: Add new antenna
|
||||||
appearance:
|
appearance:
|
||||||
advanced_web_interface: Advanced web interface
|
advanced_web_interface: Advanced web interface
|
||||||
advanced_web_interface_hint: 'If you want to make use of your entire screen width, the advanced web interface allows you to configure many different columns to see as much information at the same time as you want: Home, notifications, federated timeline, any number of lists and hashtags.'
|
advanced_web_interface_hint: 'If you want to make use of your entire screen width, the advanced web interface allows you to configure many different columns to see as much information at the same time as you want: Home, notifications, federated timeline, any number of lists and hashtags.'
|
||||||
|
|
|
@ -946,6 +946,50 @@ ja:
|
||||||
empty: エイリアスがありません。
|
empty: エイリアスがありません。
|
||||||
hint_html: 他のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。エイリアス自体は<strong>無害で、取り消す</strong>ことができます。<strong>引っ越しは以前のアカウント側から開始する必要があります</strong>。
|
hint_html: 他のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。エイリアス自体は<strong>無害で、取り消す</strong>ことができます。<strong>引っ越しは以前のアカウント側から開始する必要があります</strong>。
|
||||||
remove: エイリアスを削除
|
remove: エイリアスを削除
|
||||||
|
antennas:
|
||||||
|
beta: アンテナ機能はベータ版です。今後、予告なく全データリセット・機能削除を行う場合があります。この機能の存在は外部に積極的に宣伝しないよう、ご協力をお願いします。
|
||||||
|
contexts:
|
||||||
|
account: アカウント
|
||||||
|
domain: ドメイン
|
||||||
|
keyword: キーワード
|
||||||
|
tag: ハッシュタグ
|
||||||
|
errors:
|
||||||
|
empty_contexts: 絞り込み条件が1つも指定されていないため無効です(除外条件はカウントされません)
|
||||||
|
invalid_list_owner: これはあなたのリストではありません
|
||||||
|
edit:
|
||||||
|
accounts_hint: ローカルアカウントの場合は「@info」、リモートアカウントの場合は「@info@example.com」の形式で指定します。サーバーが認識していないアカウントは保存時に自動的に削除されます。
|
||||||
|
accounts_raw: 絞り込むアカウント
|
||||||
|
available: 有効
|
||||||
|
description: アンテナは、サーバーが認識した全ての公開・ローカル公開投稿のうち、検索許可が「公開」または明示的に設定されていないもの(検索許可システムに対応していないサーバーからの投稿)、かつ購読を拒否していないすべてのアカウントからの投稿が対象です。検出された投稿は、指定したリストに追加されます。
|
||||||
|
domains_raw: 絞り込むドメイン
|
||||||
|
exclude_accounts_raw: 除外するアカウント
|
||||||
|
exclude_domains_raw: 除外するドメイン
|
||||||
|
exclude_keywords_raw: 除外するキーワード
|
||||||
|
exclude_tags_raw: 除外するハッシュタグ
|
||||||
|
hint: 下のリストに、絞り込み条件・除外条件を入力します。条件は複数指定することができます。1行につき1つずつ入力してください。空行、コメント、重複を含めることはできません。絞り込み条件(除外条件ではない)は最低1つ設定しなければいけません。
|
||||||
|
keywords_raw: 絞り込むキーワード
|
||||||
|
list: 投稿配置先リスト
|
||||||
|
tags_raw: 絞り込むハッシュタグ
|
||||||
|
title: アンテナを編集
|
||||||
|
index:
|
||||||
|
accounts:
|
||||||
|
other: "%{count}件のアカウント"
|
||||||
|
contexts: "%{contexts}のアンテナ"
|
||||||
|
delete: 削除
|
||||||
|
disabled: 無効
|
||||||
|
domains:
|
||||||
|
other: "%{count}件のドメイン"
|
||||||
|
empty: アンテナはありません。
|
||||||
|
expires_in: "%{distance}で期限切れ"
|
||||||
|
expires_on: 有効期限 %{date}
|
||||||
|
keywords:
|
||||||
|
other: "%{count}件のキーワード"
|
||||||
|
tags:
|
||||||
|
other: "%{count}件のタグ"
|
||||||
|
title: アンテナ
|
||||||
|
new:
|
||||||
|
save: 新規アンテナを保存
|
||||||
|
title: 新規アンテナを追加
|
||||||
appearance:
|
appearance:
|
||||||
advanced_web_interface: 上級者向けUI
|
advanced_web_interface: 上級者向けUI
|
||||||
advanced_web_interface_hint: ディスプレイを幅いっぱいまで活用したい場合、上級者向け UI をおすすめします。ホーム、通知、連合タイムライン、更にはリストやハッシュタグなど、様々な異なるカラムから望む限りの情報を一度に受け取れるような設定が可能になります。
|
advanced_web_interface_hint: ディスプレイを幅いっぱいまで活用したい場合、上級者向け UI をおすすめします。ホーム、通知、連合タイムライン、更にはリストやハッシュタグなど、様々な異なるカラムから望む限りの情報を一度に受け取れるような設定が可能になります。
|
||||||
|
|
|
@ -38,6 +38,7 @@ en:
|
||||||
current_username: To confirm, please enter the username of the current account
|
current_username: To confirm, please enter the username of the current account
|
||||||
digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
|
digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
|
||||||
discoverable: Allow your account to be discovered by strangers through recommendations, trends and other features
|
discoverable: Allow your account to be discovered by strangers through recommendations, trends and other features
|
||||||
|
dissubscribable: Your post is not picked by antenna
|
||||||
email: You will be sent a confirmation e-mail
|
email: You will be sent a confirmation e-mail
|
||||||
fields: You can have up to 4 items displayed as a table on your profile
|
fields: You can have up to 4 items displayed as a table on your profile
|
||||||
group: Reps sent to this account will be automatically BT'd and distributed to all accounts you follow!
|
group: Reps sent to this account will be automatically BT'd and distributed to all accounts you follow!
|
||||||
|
@ -179,6 +180,7 @@ en:
|
||||||
data: Data
|
data: Data
|
||||||
discoverable: Suggest account to others
|
discoverable: Suggest account to others
|
||||||
display_name: Display name
|
display_name: Display name
|
||||||
|
dissubscribable: Reject any subscriptions
|
||||||
email: E-mail address
|
email: E-mail address
|
||||||
expires_in: Expire after
|
expires_in: Expire after
|
||||||
fields: Profile metadata
|
fields: Profile metadata
|
||||||
|
|
|
@ -38,6 +38,7 @@ ja:
|
||||||
current_username: 確認のため、現在のアカウントのユーザー名を入力してください
|
current_username: 確認のため、現在のアカウントのユーザー名を入力してください
|
||||||
digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます
|
digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます
|
||||||
discoverable: レコメンド、トレンド、その他の機能により、あなたのアカウントを他の人から見つけられるようにします
|
discoverable: レコメンド、トレンド、その他の機能により、あなたのアカウントを他の人から見つけられるようにします
|
||||||
|
dissubscribable: あなたの投稿はすべてのアンテナに掲載されなくなります。Fedibirdからの購読やMisskeyのアンテナを拒否することはできません
|
||||||
email: 確認のメールが送信されます
|
email: 確認のメールが送信されます
|
||||||
fields: プロフィールに表として4つまでの項目を表示することができます
|
fields: プロフィールに表として4つまでの項目を表示することができます
|
||||||
group: このアカウントに送られたメンションは自動でBTされ、フォローしている全てのアカウントに配信されます
|
group: このアカウントに送られたメンションは自動でBTされ、フォローしている全てのアカウントに配信されます
|
||||||
|
@ -180,6 +181,7 @@ ja:
|
||||||
data: データ
|
data: データ
|
||||||
discoverable: ディレクトリに掲載する
|
discoverable: ディレクトリに掲載する
|
||||||
display_name: 表示名
|
display_name: 表示名
|
||||||
|
dissubscribable: 購読を拒否する
|
||||||
email: メールアドレス
|
email: メールアドレス
|
||||||
expires_in: 有効期限
|
expires_in: 有効期限
|
||||||
fields: プロフィール補足情報
|
fields: プロフィール補足情報
|
||||||
|
|
|
@ -17,6 +17,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
|
|
||||||
n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_path, if: -> { current_user.functional? }
|
n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_path, if: -> { current_user.functional? }
|
||||||
n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? }
|
n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? }
|
||||||
|
n.item :antennas, safe_join([fa_icon('wifi fw'), t('antennas.index.title')]), antennas_path, highlights_on: %r{/antennas}, if: -> { current_user.functional? }
|
||||||
n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional_or_moved? }
|
n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional_or_moved? }
|
||||||
|
|
||||||
n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_path do |s|
|
n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_path do |s|
|
||||||
|
|
|
@ -216,6 +216,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
resources :antennas, except: [:show]
|
||||||
|
|
||||||
resource :relationships, only: [:show, :update]
|
resource :relationships, only: [:show, :update]
|
||||||
resource :statuses_cleanup, controller: :statuses_cleanup, only: [:show, :update]
|
resource :statuses_cleanup, controller: :statuses_cleanup, only: [:show, :update]
|
||||||
|
@ -602,6 +603,7 @@ Rails.application.routes.draw do
|
||||||
resources :followers, only: :index, controller: 'accounts/follower_accounts'
|
resources :followers, only: :index, controller: 'accounts/follower_accounts'
|
||||||
resources :following, only: :index, controller: 'accounts/following_accounts'
|
resources :following, only: :index, controller: 'accounts/following_accounts'
|
||||||
resources :lists, only: :index, controller: 'accounts/lists'
|
resources :lists, only: :index, controller: 'accounts/lists'
|
||||||
|
resources :antennas, only: :index, controller: 'accounts/antennas'
|
||||||
resources :identity_proofs, only: :index, controller: 'accounts/identity_proofs'
|
resources :identity_proofs, only: :index, controller: 'accounts/identity_proofs'
|
||||||
resources :featured_tags, only: :index, controller: 'accounts/featured_tags'
|
resources :featured_tags, only: :index, controller: 'accounts/featured_tags'
|
||||||
|
|
||||||
|
@ -633,6 +635,10 @@ Rails.application.routes.draw do
|
||||||
resource :accounts, only: [:show, :create, :destroy], controller: 'lists/accounts'
|
resource :accounts, only: [:show, :create, :destroy], controller: 'lists/accounts'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :antennas, only: [:index, :create, :show, :update, :destroy] do
|
||||||
|
resource :accounts, only: [:show, :create, :destroy], controller: 'antennas/accounts'
|
||||||
|
end
|
||||||
|
|
||||||
namespace :featured_tags do
|
namespace :featured_tags do
|
||||||
get :suggestions, to: 'suggestions#index'
|
get :suggestions, to: 'suggestions#index'
|
||||||
end
|
end
|
||||||
|
|
40
db/migrate/20230423002728_create_antennas.rb
Normal file
40
db/migrate/20230423002728_create_antennas.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
class CreateAntennas < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_table :antennas do |t|
|
||||||
|
t.belongs_to :account, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.belongs_to :list, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.string :title, null: false, default: ''
|
||||||
|
t.jsonb :keywords
|
||||||
|
t.jsonb :exclude_keywords
|
||||||
|
t.boolean :any_domains, null: false, default: true, index: true
|
||||||
|
t.boolean :any_tags, null: false, default: true, index: true
|
||||||
|
t.boolean :any_accounts, null: false, default: true, index: true
|
||||||
|
t.boolean :any_keywords, null: false, default: true, index: true
|
||||||
|
t.boolean :available, null: false, default: true, index: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
t.datetime :expires_at
|
||||||
|
end
|
||||||
|
create_table :antenna_domains do |t|
|
||||||
|
t.belongs_to :antenna, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.string :name, index: true
|
||||||
|
t.boolean :exclude, null: false, default: false, index: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
end
|
||||||
|
create_table :antenna_tags do |t|
|
||||||
|
t.belongs_to :antenna, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.belongs_to :tag, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.boolean :exclude, null: false, default: false, index: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
end
|
||||||
|
create_table :antenna_accounts do |t|
|
||||||
|
t.belongs_to :antenna, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.belongs_to :account, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.boolean :exclude, null: false, default: false, index: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddDissubscribableToAccounts < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :antennas, :with_media_only, :boolean, null: false, default: false, index: true
|
||||||
|
add_column :accounts, :dissubscribable, :boolean, null: false, default: false
|
||||||
|
end
|
||||||
|
end
|
67
db/schema.rb
67
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2023_04_14_010523) do
|
ActiveRecord::Schema.define(version: 2023_04_23_233429) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -189,6 +189,7 @@ ActiveRecord::Schema.define(version: 2023_04_14_010523) do
|
||||||
t.datetime "requested_review_at"
|
t.datetime "requested_review_at"
|
||||||
t.boolean "group_allow_private_message"
|
t.boolean "group_allow_private_message"
|
||||||
t.integer "searchability", default: 2, null: false
|
t.integer "searchability", default: 2, null: false
|
||||||
|
t.boolean "dissubscribable", default: false, null: false
|
||||||
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
||||||
t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
|
t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
|
||||||
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id", where: "(moved_to_account_id IS NOT NULL)"
|
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id", where: "(moved_to_account_id IS NOT NULL)"
|
||||||
|
@ -251,6 +252,63 @@ ActiveRecord::Schema.define(version: 2023_04_14_010523) do
|
||||||
t.bigint "status_ids", array: true
|
t.bigint "status_ids", array: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "antenna_accounts", force: :cascade do |t|
|
||||||
|
t.bigint "antenna_id", null: false
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.boolean "exclude", default: false, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id"], name: "index_antenna_accounts_on_account_id"
|
||||||
|
t.index ["antenna_id"], name: "index_antenna_accounts_on_antenna_id"
|
||||||
|
t.index ["exclude"], name: "index_antenna_accounts_on_exclude"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "antenna_domains", force: :cascade do |t|
|
||||||
|
t.bigint "antenna_id", null: false
|
||||||
|
t.string "name"
|
||||||
|
t.boolean "exclude", default: false, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["antenna_id"], name: "index_antenna_domains_on_antenna_id"
|
||||||
|
t.index ["exclude"], name: "index_antenna_domains_on_exclude"
|
||||||
|
t.index ["name"], name: "index_antenna_domains_on_name"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "antenna_tags", force: :cascade do |t|
|
||||||
|
t.bigint "antenna_id", null: false
|
||||||
|
t.bigint "tag_id", null: false
|
||||||
|
t.boolean "exclude", default: false, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["antenna_id"], name: "index_antenna_tags_on_antenna_id"
|
||||||
|
t.index ["exclude"], name: "index_antenna_tags_on_exclude"
|
||||||
|
t.index ["tag_id"], name: "index_antenna_tags_on_tag_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "antennas", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.bigint "list_id", null: false
|
||||||
|
t.string "title", default: "", null: false
|
||||||
|
t.jsonb "keywords"
|
||||||
|
t.jsonb "exclude_keywords"
|
||||||
|
t.boolean "any_domains", default: true, null: false
|
||||||
|
t.boolean "any_tags", default: true, null: false
|
||||||
|
t.boolean "any_accounts", default: true, null: false
|
||||||
|
t.boolean "any_keywords", default: true, null: false
|
||||||
|
t.boolean "available", default: true, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.datetime "expires_at"
|
||||||
|
t.boolean "with_media_only", 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"
|
||||||
|
t.index ["any_keywords"], name: "index_antennas_on_any_keywords"
|
||||||
|
t.index ["any_tags"], name: "index_antennas_on_any_tags"
|
||||||
|
t.index ["available"], name: "index_antennas_on_available"
|
||||||
|
t.index ["list_id"], name: "index_antennas_on_list_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "appeals", force: :cascade do |t|
|
create_table "appeals", force: :cascade do |t|
|
||||||
t.bigint "account_id", null: false
|
t.bigint "account_id", null: false
|
||||||
t.bigint "account_warning_id", null: false
|
t.bigint "account_warning_id", null: false
|
||||||
|
@ -1173,6 +1231,13 @@ ActiveRecord::Schema.define(version: 2023_04_14_010523) do
|
||||||
add_foreign_key "announcement_reactions", "accounts", on_delete: :cascade
|
add_foreign_key "announcement_reactions", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "announcement_reactions", "announcements", on_delete: :cascade
|
add_foreign_key "announcement_reactions", "announcements", on_delete: :cascade
|
||||||
add_foreign_key "announcement_reactions", "custom_emojis", on_delete: :cascade
|
add_foreign_key "announcement_reactions", "custom_emojis", on_delete: :cascade
|
||||||
|
add_foreign_key "antenna_accounts", "accounts", on_delete: :cascade
|
||||||
|
add_foreign_key "antenna_accounts", "antennas", on_delete: :cascade
|
||||||
|
add_foreign_key "antenna_domains", "antennas", on_delete: :cascade
|
||||||
|
add_foreign_key "antenna_tags", "antennas", on_delete: :cascade
|
||||||
|
add_foreign_key "antenna_tags", "tags", on_delete: :cascade
|
||||||
|
add_foreign_key "antennas", "accounts", on_delete: :cascade
|
||||||
|
add_foreign_key "antennas", "lists", on_delete: :cascade
|
||||||
add_foreign_key "appeals", "account_warnings", on_delete: :cascade
|
add_foreign_key "appeals", "account_warnings", on_delete: :cascade
|
||||||
add_foreign_key "appeals", "accounts", column: "approved_by_account_id", on_delete: :nullify
|
add_foreign_key "appeals", "accounts", column: "approved_by_account_id", on_delete: :nullify
|
||||||
add_foreign_key "appeals", "accounts", column: "rejected_by_account_id", on_delete: :nullify
|
add_foreign_key "appeals", "accounts", column: "rejected_by_account_id", on_delete: :nullify
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue