Wip: アンテナ編集画面周辺

This commit is contained in:
KMY 2024-11-28 12:51:25 +09:00
parent 041b05b15f
commit 946f5bce3e
51 changed files with 1006 additions and 3665 deletions

View file

@ -1,51 +0,0 @@
# frozen_string_literal: true
class AntennasController < ApplicationController
layout 'admin'
before_action :authenticate_user!
before_action :set_antenna, only: [:edit, :update, :destroy]
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 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 resource_params
params.require(:antenna).permit(:title, :available, :expires_in)
end
def thin_resource_params
params.require(:antenna).permit(:title)
end
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -1,8 +1,5 @@
import api from '../api'; import api from '../api';
import { showAlertForError } from './alerts';
import { importFetchedAccounts } from './importer';
export const ANTENNA_FETCH_REQUEST = 'ANTENNA_FETCH_REQUEST'; export const ANTENNA_FETCH_REQUEST = 'ANTENNA_FETCH_REQUEST';
export const ANTENNA_FETCH_SUCCESS = 'ANTENNA_FETCH_SUCCESS'; export const ANTENNA_FETCH_SUCCESS = 'ANTENNA_FETCH_SUCCESS';
export const ANTENNA_FETCH_FAIL = 'ANTENNA_FETCH_FAIL'; export const ANTENNA_FETCH_FAIL = 'ANTENNA_FETCH_FAIL';
@ -11,121 +8,10 @@ export const ANTENNAS_FETCH_REQUEST = 'ANTENNAS_FETCH_REQUEST';
export const ANTENNAS_FETCH_SUCCESS = 'ANTENNAS_FETCH_SUCCESS'; export const ANTENNAS_FETCH_SUCCESS = 'ANTENNAS_FETCH_SUCCESS';
export const ANTENNAS_FETCH_FAIL = 'ANTENNAS_FETCH_FAIL'; export const ANTENNAS_FETCH_FAIL = 'ANTENNAS_FETCH_FAIL';
export const ANTENNA_EDITOR_TITLE_CHANGE = 'ANTENNA_EDITOR_TITLE_CHANGE';
export const ANTENNA_EDITOR_RESET = 'ANTENNA_EDITOR_RESET';
export const ANTENNA_EDITOR_SETUP = 'ANTENNA_EDITOR_SETUP';
export const ANTENNA_CREATE_REQUEST = 'ANTENNA_CREATE_REQUEST';
export const ANTENNA_CREATE_SUCCESS = 'ANTENNA_CREATE_SUCCESS';
export const ANTENNA_CREATE_FAIL = 'ANTENNA_CREATE_FAIL';
export const ANTENNA_UPDATE_REQUEST = 'ANTENNA_UPDATE_REQUEST';
export const ANTENNA_UPDATE_SUCCESS = 'ANTENNA_UPDATE_SUCCESS';
export const ANTENNA_UPDATE_FAIL = 'ANTENNA_UPDATE_FAIL';
export const ANTENNA_DELETE_REQUEST = 'ANTENNA_DELETE_REQUEST'; export const ANTENNA_DELETE_REQUEST = 'ANTENNA_DELETE_REQUEST';
export const ANTENNA_DELETE_SUCCESS = 'ANTENNA_DELETE_SUCCESS'; export const ANTENNA_DELETE_SUCCESS = 'ANTENNA_DELETE_SUCCESS';
export const ANTENNA_DELETE_FAIL = 'ANTENNA_DELETE_FAIL'; export const ANTENNA_DELETE_FAIL = 'ANTENNA_DELETE_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_SUGGESTIONS_CHANGE = 'ANTENNA_EDITOR_SUGGESTIONS_CHANGE';
export const ANTENNA_EDITOR_SUGGESTIONS_READY = 'ANTENNA_EDITOR_SUGGESTIONS_READY';
export const ANTENNA_EDITOR_SUGGESTIONS_CLEAR = 'ANTENNA_EDITOR_SUGGESTIONS_CLEAR';
export const ANTENNA_EDITOR_ADD_REQUEST = 'ANTENNA_EDITOR_ADD_REQUEST';
export const ANTENNA_EDITOR_ADD_SUCCESS = 'ANTENNA_EDITOR_ADD_SUCCESS';
export const ANTENNA_EDITOR_ADD_FAIL = 'ANTENNA_EDITOR_ADD_FAIL';
export const ANTENNA_EDITOR_REMOVE_REQUEST = 'ANTENNA_EDITOR_REMOVE_REQUEST';
export const ANTENNA_EDITOR_REMOVE_SUCCESS = 'ANTENNA_EDITOR_REMOVE_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_FAIL = 'ANTENNA_EDITOR_REMOVE_FAIL';
export const ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST = 'ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST';
export const ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS = 'ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS';
export const ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL = 'ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL';
export const ANTENNA_EDITOR_ADD_EXCLUDE_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDE_REQUEST';
export const ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS';
export const ANTENNA_EDITOR_ADD_EXCLUDE_FAIL = 'ANTENNA_EDITOR_ADD_EXCLUDE_FAIL';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_REQUEST';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_FAIL = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_FAIL';
export const ANTENNA_EDITOR_FETCH_DOMAINS_REQUEST = 'ANTENNA_EDITOR_FETCH_DOMAINS_REQUEST';
export const ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS = 'ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS';
export const ANTENNA_EDITOR_FETCH_DOMAINS_FAIL = 'ANTENNA_EDITOR_FETCH_DOMAINS_FAIL';
export const ANTENNA_EDITOR_ADD_DOMAIN_REQUEST = 'ANTENNA_EDITOR_ADD_DOMAIN_REQUEST';
export const ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS';
export const ANTENNA_EDITOR_ADD_DOMAIN_FAIL = 'ANTENNA_EDITOR_ADD_DOMAIN_FAIL';
export const ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDEDOMAIN_REQUEST';
export const ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS';
export const ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_FAIL = 'ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_FAIL';
export const ANTENNA_EDITOR_REMOVE_DOMAIN_REQUEST = 'ANTENNA_EDITOR_REMOVE_DOMAIN_REQUEST';
export const ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_DOMAIN_FAIL = 'ANTENNA_EDITOR_REMOVE_DOMAIN_FAIL';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_REQUEST';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_FAIL = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_FAIL';
export const ANTENNA_EDITOR_FETCH_KEYWORDS_REQUEST = 'ANTENNA_EDITOR_FETCH_KEYWORDS_REQUEST';
export const ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS = 'ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS';
export const ANTENNA_EDITOR_FETCH_KEYWORDS_FAIL = 'ANTENNA_EDITOR_FETCH_KEYWORDS_FAIL';
export const ANTENNA_EDITOR_ADD_KEYWORD_REQUEST = 'ANTENNA_EDITOR_ADD_KEYWORD_REQUEST';
export const ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS';
export const ANTENNA_EDITOR_ADD_KEYWORD_FAIL = 'ANTENNA_EDITOR_ADD_KEYWORD_FAIL';
export const ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_REQUEST';
export const ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS';
export const ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_FAIL = 'ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_FAIL';
export const ANTENNA_EDITOR_REMOVE_KEYWORD_REQUEST = 'ANTENNA_EDITOR_REMOVE_KEYWORD_REQUEST';
export const ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_KEYWORD_FAIL = 'ANTENNA_EDITOR_REMOVE_KEYWORD_FAIL';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_REQUEST';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_FAIL = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_FAIL';
export const ANTENNA_EDITOR_FETCH_TAGS_REQUEST = 'ANTENNA_EDITOR_FETCH_TAGS_REQUEST';
export const ANTENNA_EDITOR_FETCH_TAGS_SUCCESS = 'ANTENNA_EDITOR_FETCH_TAGS_SUCCESS';
export const ANTENNA_EDITOR_FETCH_TAGS_FAIL = 'ANTENNA_EDITOR_FETCH_TAGS_FAIL';
export const ANTENNA_EDITOR_ADD_TAG_REQUEST = 'ANTENNA_EDITOR_ADD_TAG_REQUEST';
export const ANTENNA_EDITOR_ADD_TAG_SUCCESS = 'ANTENNA_EDITOR_ADD_TAG_SUCCESS';
export const ANTENNA_EDITOR_ADD_TAG_FAIL = 'ANTENNA_EDITOR_ADD_TAG_FAIL';
export const ANTENNA_EDITOR_ADD_EXCLUDE_TAG_REQUEST = 'ANTENNA_EDITOR_ADD_EXCLUDE_TAG_REQUEST';
export const ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS = 'ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS';
export const ANTENNA_EDITOR_ADD_EXCLUDE_TAG_FAIL = 'ANTENNA_EDITOR_ADD_EXCLUDE_TAG_FAIL';
export const ANTENNA_EDITOR_REMOVE_TAG_REQUEST = 'ANTENNA_EDITOR_REMOVE_TAG_REQUEST';
export const ANTENNA_EDITOR_REMOVE_TAG_SUCCESS = 'ANTENNA_EDITOR_REMOVE_TAG_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_TAG_FAIL = 'ANTENNA_EDITOR_REMOVE_TAG_FAIL';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_REQUEST = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_REQUEST';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS';
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_FAIL = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_FAIL';
export const ANTENNA_ADDER_RESET = 'ANTENNA_ADDER_RESET';
export const ANTENNA_ADDER_SETUP = 'ANTENNA_ADDER_SETUP';
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_EXCLUDE_ANTENNAS_FETCH_REQUEST = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST';
export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS';
export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL';
export const fetchAntenna = id => (dispatch, getState) => { export const fetchAntenna = id => (dispatch, getState) => {
if (getState().getIn(['antennas', id])) { if (getState().getIn(['antennas', id])) {
return; return;
@ -176,98 +62,6 @@ export const fetchAntennasFail = error => ({
error, error,
}); });
export const submitAntennaEditor = shouldReset => (dispatch, getState) => {
const antennaId = getState().getIn(['antennaEditor', 'antennaId']);
const title = getState().getIn(['antennaEditor', 'title']);
if (antennaId === null) {
dispatch(createAntenna(title, shouldReset));
} else {
dispatch(updateAntenna(antennaId, title, shouldReset));
}
};
export const setupAntennaEditor = antennaId => (dispatch, getState) => {
dispatch({
type: ANTENNA_EDITOR_SETUP,
antenna: getState().getIn(['antennas', antennaId]),
});
dispatch(fetchAntennaAccounts(antennaId));
};
export const setupExcludeAntennaEditor = antennaId => (dispatch, getState) => {
dispatch({
type: ANTENNA_EDITOR_SETUP,
antenna: getState().getIn(['antennas', antennaId]),
});
dispatch(fetchAntennaExcludeAccounts(antennaId));
};
export const changeAntennaEditorTitle = value => ({
type: ANTENNA_EDITOR_TITLE_CHANGE,
value,
});
export const createAntenna = (title, shouldReset) => (dispatch, getState) => {
dispatch(createAntennaRequest());
api(getState).post('/api/v1/antennas', { title }).then(({ data }) => {
dispatch(createAntennaSuccess(data));
if (shouldReset) {
dispatch(resetAntennaEditor());
}
}).catch(err => dispatch(createAntennaFail(err)));
};
export const createAntennaRequest = () => ({
type: ANTENNA_CREATE_REQUEST,
});
export const createAntennaSuccess = antenna => ({
type: ANTENNA_CREATE_SUCCESS,
antenna,
});
export const createAntennaFail = error => ({
type: ANTENNA_CREATE_FAIL,
error,
});
export const updateAntenna = (id, title, shouldReset, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds) => (dispatch, getState) => {
dispatch(updateAntennaRequest(id));
api(getState).put(`/api/v1/antennas/${id}`, { title, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds }).then(({ data }) => {
dispatch(updateAntennaSuccess(data));
if (shouldReset) {
dispatch(resetAntennaEditor());
}
}).catch(err => dispatch(updateAntennaFail(id, err)));
};
export const updateAntennaRequest = id => ({
type: ANTENNA_UPDATE_REQUEST,
id,
});
export const updateAntennaSuccess = antenna => ({
type: ANTENNA_UPDATE_SUCCESS,
antenna,
});
export const updateAntennaFail = (id, error) => ({
type: ANTENNA_UPDATE_FAIL,
id,
error,
});
export const resetAntennaEditor = () => ({
type: ANTENNA_EDITOR_RESET,
});
export const deleteAntenna = id => (dispatch, getState) => { export const deleteAntenna = id => (dispatch, getState) => {
dispatch(deleteAntennaRequest(id)); dispatch(deleteAntennaRequest(id));
@ -291,696 +85,3 @@ export const deleteAntennaFail = (id, error) => ({
id, id,
error, 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 fetchAntennaExcludeAccounts = antennaId => (dispatch, getState) => {
dispatch(fetchAntennaExcludeAccountsRequest(antennaId));
api(getState).get(`/api/v1/antennas/${antennaId}/exclude_accounts`, { params: { limit: 0 } }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchAntennaExcludeAccountsSuccess(antennaId, data));
}).catch(err => dispatch(fetchAntennaExcludeAccountsFail(antennaId, err)));
};
export const fetchAntennaExcludeAccountsRequest = id => ({
type: ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST,
id,
});
export const fetchAntennaExcludeAccountsSuccess = (id, accounts, next) => ({
type: ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS,
id,
accounts,
next,
});
export const fetchAntennaExcludeAccountsFail = (id, error) => ({
type: ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL,
id,
error,
});
export const fetchAntennaSuggestions = q => (dispatch, getState) => {
const params = {
q,
resolve: false,
};
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchAntennaSuggestionsReady(q, data));
}).catch(error => dispatch(showAlertForError(error)));
};
export const fetchAntennaSuggestionsReady = (query, accounts) => ({
type: ANTENNA_EDITOR_SUGGESTIONS_READY,
query,
accounts,
});
export const clearAntennaSuggestions = () => ({
type: ANTENNA_EDITOR_SUGGESTIONS_CLEAR,
});
export const changeAntennaSuggestions = value => ({
type: ANTENNA_EDITOR_SUGGESTIONS_CHANGE,
value,
});
export const addToAntennaEditor = accountId => (dispatch, getState) => {
dispatch(addToAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
};
export const addToAntenna = (antennaId, accountId) => (dispatch, getState) => {
dispatch(addToAntennaRequest(antennaId, accountId));
api(getState).post(`/api/v1/antennas/${antennaId}/accounts`, { account_ids: [accountId] })
.then(() => dispatch(addToAntennaSuccess(antennaId, accountId)))
.catch(err => dispatch(addToAntennaFail(antennaId, accountId, err)));
};
export const addToAntennaRequest = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_ADD_REQUEST,
antennaId,
accountId,
});
export const addToAntennaSuccess = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_ADD_SUCCESS,
antennaId,
accountId,
});
export const addToAntennaFail = (antennaId, accountId, error) => ({
type: ANTENNA_EDITOR_ADD_FAIL,
antennaId,
accountId,
error,
});
export const addExcludeToAntennaEditor = accountId => (dispatch, getState) => {
dispatch(addExcludeToAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
};
export const addExcludeToAntenna = (antennaId, accountId) => (dispatch, getState) => {
dispatch(addExcludeToAntennaRequest(antennaId, accountId));
api(getState).post(`/api/v1/antennas/${antennaId}/exclude_accounts`, { account_ids: [accountId] })
.then(() => dispatch(addExcludeToAntennaSuccess(antennaId, accountId)))
.catch(err => dispatch(addExcludeToAntennaFail(antennaId, accountId, err)));
};
export const addExcludeToAntennaRequest = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_REQUEST,
antennaId,
accountId,
});
export const addExcludeToAntennaSuccess = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS,
antennaId,
accountId,
});
export const addExcludeToAntennaFail = (antennaId, accountId, error) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_FAIL,
antennaId,
accountId,
error,
});
export const removeFromAntennaEditor = accountId => (dispatch, getState) => {
dispatch(removeFromAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
};
export const removeFromAntenna = (antennaId, accountId) => (dispatch, getState) => {
dispatch(removeFromAntennaRequest(antennaId, accountId));
api(getState).delete(`/api/v1/antennas/${antennaId}/accounts`, { params: { account_ids: [accountId] } })
.then(() => dispatch(removeFromAntennaSuccess(antennaId, accountId)))
.catch(err => dispatch(removeFromAntennaFail(antennaId, accountId, err)));
};
export const removeFromAntennaRequest = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_REMOVE_REQUEST,
antennaId,
accountId,
});
export const removeFromAntennaSuccess = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_REMOVE_SUCCESS,
antennaId,
accountId,
});
export const removeFromAntennaFail = (antennaId, accountId, error) => ({
type: ANTENNA_EDITOR_REMOVE_FAIL,
antennaId,
accountId,
error,
});
export const removeExcludeFromAntennaEditor = accountId => (dispatch, getState) => {
dispatch(removeExcludeFromAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId));
};
export const removeExcludeFromAntenna = (antennaId, accountId) => (dispatch, getState) => {
dispatch(removeExcludeFromAntennaRequest(antennaId, accountId));
api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_accounts`, { params: { account_ids: [accountId] } })
.then(() => dispatch(removeExcludeFromAntennaSuccess(antennaId, accountId)))
.catch(err => dispatch(removeExcludeFromAntennaFail(antennaId, accountId, err)));
};
export const removeExcludeFromAntennaRequest = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_REQUEST,
antennaId,
accountId,
});
export const removeExcludeFromAntennaSuccess = (antennaId, accountId) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS,
antennaId,
accountId,
});
export const removeExcludeFromAntennaFail = (antennaId, accountId, error) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_FAIL,
antennaId,
accountId,
error,
});
export const fetchAntennaDomains = antennaId => (dispatch, getState) => {
dispatch(fetchAntennaDomainsRequest(antennaId));
api(getState).get(`/api/v1/antennas/${antennaId}/domains`, { params: { limit: 0 } }).then(({ data }) => {
dispatch(fetchAntennaDomainsSuccess(antennaId, data));
}).catch(err => dispatch(fetchAntennaDomainsFail(antennaId, err)));
};
export const fetchAntennaDomainsRequest = id => ({
type: ANTENNA_EDITOR_FETCH_DOMAINS_REQUEST,
id,
});
export const fetchAntennaDomainsSuccess = (id, domains) => ({
type: ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS,
id,
domains,
});
export const fetchAntennaDomainsFail = (id, error) => ({
type: ANTENNA_EDITOR_FETCH_DOMAINS_FAIL,
id,
error,
});
export const addDomainToAntenna = (antennaId, domain) => (dispatch, getState) => {
dispatch(addDomainToAntennaRequest(antennaId, domain));
api(getState).post(`/api/v1/antennas/${antennaId}/domains`, { domains: [domain] })
.then(() => dispatch(addDomainToAntennaSuccess(antennaId, domain)))
.catch(err => dispatch(addDomainToAntennaFail(antennaId, domain, err)));
};
export const addDomainToAntennaRequest = (antennaId, domain) => ({
type: ANTENNA_EDITOR_ADD_DOMAIN_REQUEST,
antennaId,
domain,
});
export const addDomainToAntennaSuccess = (antennaId, domain) => ({
type: ANTENNA_EDITOR_ADD_DOMAIN_SUCCESS,
antennaId,
domain,
});
export const addDomainToAntennaFail = (antennaId, domain, error) => ({
type: ANTENNA_EDITOR_ADD_DOMAIN_FAIL,
antennaId,
domain,
error,
});
export const removeDomainFromAntenna = (antennaId, domain) => (dispatch, getState) => {
dispatch(removeDomainFromAntennaRequest(antennaId, domain));
api(getState).delete(`/api/v1/antennas/${antennaId}/domains`, { params: { domains: [domain] } })
.then(() => dispatch(removeDomainFromAntennaSuccess(antennaId, domain)))
.catch(err => dispatch(removeDomainFromAntennaFail(antennaId, domain, err)));
};
export const removeDomainFromAntennaRequest = (antennaId, domain) => ({
type: ANTENNA_EDITOR_REMOVE_DOMAIN_REQUEST,
antennaId,
domain,
});
export const removeDomainFromAntennaSuccess = (antennaId, domain) => ({
type: ANTENNA_EDITOR_REMOVE_DOMAIN_SUCCESS,
antennaId,
domain,
});
export const removeDomainFromAntennaFail = (antennaId, domain, error) => ({
type: ANTENNA_EDITOR_REMOVE_DOMAIN_FAIL,
antennaId,
domain,
error,
});
export const addExcludeDomainToAntenna = (antennaId, domain) => (dispatch, getState) => {
dispatch(addExcludeDomainToAntennaRequest(antennaId, domain));
api(getState).post(`/api/v1/antennas/${antennaId}/exclude_domains`, { domains: [domain] })
.then(() => dispatch(addExcludeDomainToAntennaSuccess(antennaId, domain)))
.catch(err => dispatch(addExcludeDomainToAntennaFail(antennaId, domain, err)));
};
export const addExcludeDomainToAntennaRequest = (antennaId, domain) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_REQUEST,
antennaId,
domain,
});
export const addExcludeDomainToAntennaSuccess = (antennaId, domain) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_SUCCESS,
antennaId,
domain,
});
export const addExcludeDomainToAntennaFail = (antennaId, domain, error) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_DOMAIN_FAIL,
antennaId,
domain,
error,
});
export const removeExcludeDomainFromAntenna = (antennaId, domain) => (dispatch, getState) => {
dispatch(removeExcludeDomainFromAntennaRequest(antennaId, domain));
api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_domains`, { params: { domains: [domain] } })
.then(() => dispatch(removeExcludeDomainFromAntennaSuccess(antennaId, domain)))
.catch(err => dispatch(removeExcludeDomainFromAntennaFail(antennaId, domain, err)));
};
export const removeExcludeDomainFromAntennaRequest = (antennaId, domain) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_REQUEST,
antennaId,
domain,
});
export const removeExcludeDomainFromAntennaSuccess = (antennaId, domain) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_SUCCESS,
antennaId,
domain,
});
export const removeExcludeDomainFromAntennaFail = (antennaId, domain, error) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_DOMAIN_FAIL,
antennaId,
domain,
error,
});
export const fetchAntennaKeywords = antennaId => (dispatch, getState) => {
dispatch(fetchAntennaKeywordsRequest(antennaId));
api(getState).get(`/api/v1/antennas/${antennaId}/keywords`, { params: { limit: 0 } }).then(({ data }) => {
dispatch(fetchAntennaKeywordsSuccess(antennaId, data));
}).catch(err => dispatch(fetchAntennaKeywordsFail(antennaId, err)));
};
export const fetchAntennaKeywordsRequest = id => ({
type: ANTENNA_EDITOR_FETCH_KEYWORDS_REQUEST,
id,
});
export const fetchAntennaKeywordsSuccess = (id, keywords) => ({
type: ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS,
id,
keywords,
});
export const fetchAntennaKeywordsFail = (id, error) => ({
type: ANTENNA_EDITOR_FETCH_KEYWORDS_FAIL,
id,
error,
});
export const addKeywordToAntenna = (antennaId, keyword) => (dispatch, getState) => {
dispatch(addKeywordToAntennaRequest(antennaId, keyword));
api(getState).post(`/api/v1/antennas/${antennaId}/keywords`, { keywords: [keyword] })
.then(() => dispatch(addKeywordToAntennaSuccess(antennaId, keyword)))
.catch(err => dispatch(addKeywordToAntennaFail(antennaId, keyword, err)));
};
export const addKeywordToAntennaRequest = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_ADD_KEYWORD_REQUEST,
antennaId,
keyword,
});
export const addKeywordToAntennaSuccess = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_ADD_KEYWORD_SUCCESS,
antennaId,
keyword,
});
export const addKeywordToAntennaFail = (antennaId, keyword, error) => ({
type: ANTENNA_EDITOR_ADD_KEYWORD_FAIL,
antennaId,
keyword,
error,
});
export const removeKeywordFromAntenna = (antennaId, keyword) => (dispatch, getState) => {
dispatch(removeKeywordFromAntennaRequest(antennaId, keyword));
api(getState).delete(`/api/v1/antennas/${antennaId}/keywords`, { params: { keywords: [keyword] } })
.then(() => dispatch(removeKeywordFromAntennaSuccess(antennaId, keyword)))
.catch(err => dispatch(removeKeywordFromAntennaFail(antennaId, keyword, err)));
};
export const removeKeywordFromAntennaRequest = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_REMOVE_KEYWORD_REQUEST,
antennaId,
keyword,
});
export const removeKeywordFromAntennaSuccess = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_REMOVE_KEYWORD_SUCCESS,
antennaId,
keyword,
});
export const removeKeywordFromAntennaFail = (antennaId, keyword, error) => ({
type: ANTENNA_EDITOR_REMOVE_KEYWORD_FAIL,
antennaId,
keyword,
error,
});
export const addExcludeKeywordToAntenna = (antennaId, keyword) => (dispatch, getState) => {
dispatch(addExcludeKeywordToAntennaRequest(antennaId, keyword));
api(getState).post(`/api/v1/antennas/${antennaId}/exclude_keywords`, { keywords: [keyword] })
.then(() => dispatch(addExcludeKeywordToAntennaSuccess(antennaId, keyword)))
.catch(err => dispatch(addExcludeKeywordToAntennaFail(antennaId, keyword, err)));
};
export const addExcludeKeywordToAntennaRequest = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_REQUEST,
antennaId,
keyword,
});
export const addExcludeKeywordToAntennaSuccess = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS,
antennaId,
keyword,
});
export const addExcludeKeywordToAntennaFail = (antennaId, keyword, error) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_FAIL,
antennaId,
keyword,
error,
});
export const removeExcludeKeywordFromAntenna = (antennaId, keyword) => (dispatch, getState) => {
dispatch(removeExcludeKeywordFromAntennaRequest(antennaId, keyword));
api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_keywords`, { params: { keywords: [keyword] } })
.then(() => dispatch(removeExcludeKeywordFromAntennaSuccess(antennaId, keyword)))
.catch(err => dispatch(removeExcludeKeywordFromAntennaFail(antennaId, keyword, err)));
};
export const removeExcludeKeywordFromAntennaRequest = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_REQUEST,
antennaId,
keyword,
});
export const removeExcludeKeywordFromAntennaSuccess = (antennaId, keyword) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS,
antennaId,
keyword,
});
export const removeExcludeKeywordFromAntennaFail = (antennaId, keyword, error) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_FAIL,
antennaId,
keyword,
error,
});
export const fetchAntennaTags = antennaId => (dispatch, getState) => {
dispatch(fetchAntennaTagsRequest(antennaId));
api(getState).get(`/api/v1/antennas/${antennaId}/tags`, { params: { limit: 0 } }).then(({ data }) => {
dispatch(fetchAntennaTagsSuccess(antennaId, data));
}).catch(err => dispatch(fetchAntennaTagsFail(antennaId, err)));
};
export const fetchAntennaTagsRequest = id => ({
type: ANTENNA_EDITOR_FETCH_TAGS_REQUEST,
id,
});
export const fetchAntennaTagsSuccess = (id, tags) => ({
type: ANTENNA_EDITOR_FETCH_TAGS_SUCCESS,
id,
tags,
});
export const fetchAntennaTagsFail = (id, error) => ({
type: ANTENNA_EDITOR_FETCH_TAGS_FAIL,
id,
error,
});
export const addTagToAntenna = (antennaId, tag) => (dispatch, getState) => {
dispatch(addTagToAntennaRequest(antennaId, tag));
api(getState).post(`/api/v1/antennas/${antennaId}/tags`, { tags: [tag] })
.then(() => dispatch(addTagToAntennaSuccess(antennaId, tag)))
.catch(err => dispatch(addTagToAntennaFail(antennaId, tag, err)));
};
export const addTagToAntennaRequest = (antennaId, tag) => ({
type: ANTENNA_EDITOR_ADD_TAG_REQUEST,
antennaId,
tag,
});
export const addTagToAntennaSuccess = (antennaId, tag) => ({
type: ANTENNA_EDITOR_ADD_TAG_SUCCESS,
antennaId,
tag,
});
export const addTagToAntennaFail = (antennaId, tag, error) => ({
type: ANTENNA_EDITOR_ADD_TAG_FAIL,
antennaId,
tag,
error,
});
export const removeTagFromAntenna = (antennaId, tag) => (dispatch, getState) => {
dispatch(removeTagFromAntennaRequest(antennaId, tag));
api(getState).delete(`/api/v1/antennas/${antennaId}/tags`, { params: { tags: [tag] } })
.then(() => dispatch(removeTagFromAntennaSuccess(antennaId, tag)))
.catch(err => dispatch(removeTagFromAntennaFail(antennaId, tag, err)));
};
export const removeTagFromAntennaRequest = (antennaId, tag) => ({
type: ANTENNA_EDITOR_REMOVE_TAG_REQUEST,
antennaId,
tag,
});
export const removeTagFromAntennaSuccess = (antennaId, tag) => ({
type: ANTENNA_EDITOR_REMOVE_TAG_SUCCESS,
antennaId,
tag,
});
export const removeTagFromAntennaFail = (antennaId, tag, error) => ({
type: ANTENNA_EDITOR_REMOVE_TAG_FAIL,
antennaId,
tag,
error,
});
export const addExcludeTagToAntenna = (antennaId, tag) => (dispatch, getState) => {
dispatch(addExcludeTagToAntennaRequest(antennaId, tag));
api(getState).post(`/api/v1/antennas/${antennaId}/exclude_tags`, { tags: [tag] })
.then(() => dispatch(addExcludeTagToAntennaSuccess(antennaId, tag)))
.catch(err => dispatch(addExcludeTagToAntennaFail(antennaId, tag, err)));
};
export const addExcludeTagToAntennaRequest = (antennaId, tag) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_TAG_REQUEST,
antennaId,
tag,
});
export const addExcludeTagToAntennaSuccess = (antennaId, tag) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS,
antennaId,
tag,
});
export const addExcludeTagToAntennaFail = (antennaId, tag, error) => ({
type: ANTENNA_EDITOR_ADD_EXCLUDE_TAG_FAIL,
antennaId,
tag,
error,
});
export const removeExcludeTagFromAntenna = (antennaId, tag) => (dispatch, getState) => {
dispatch(removeExcludeTagFromAntennaRequest(antennaId, tag));
api(getState).delete(`/api/v1/antennas/${antennaId}/exclude_tags`, { params: { tags: [tag] } })
.then(() => dispatch(removeExcludeTagFromAntennaSuccess(antennaId, tag)))
.catch(err => dispatch(removeExcludeTagFromAntennaFail(antennaId, tag, err)));
};
export const removeExcludeTagFromAntennaRequest = (antennaId, tag) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_REQUEST,
antennaId,
tag,
});
export const removeExcludeTagFromAntennaSuccess = (antennaId, tag) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS,
antennaId,
tag,
});
export const removeExcludeTagFromAntennaFail = (antennaId, tag, error) => ({
type: ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_FAIL,
antennaId,
tag,
error,
});
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));
};
export const setupExcludeAntennaAdder = accountId => (dispatch, getState) => {
dispatch({
type: ANTENNA_ADDER_SETUP,
account: getState().getIn(['accounts', accountId]),
});
dispatch(fetchAntennas());
dispatch(fetchExcludeAccountAntennas(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 fetchExcludeAccountAntennas = accountId => (dispatch, getState) => {
dispatch(fetchExcludeAccountAntennasRequest(accountId));
api(getState).get(`/api/v1/accounts/${accountId}/exclude_antennas`)
.then(({ data }) => dispatch(fetchExcludeAccountAntennasSuccess(accountId, data)))
.catch(err => dispatch(fetchExcludeAccountAntennasFail(accountId, err)));
};
export const fetchExcludeAccountAntennasRequest = id => ({
type:ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST,
id,
});
export const fetchExcludeAccountAntennasSuccess = (id, antennas) => ({
type: ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS,
id,
antennas,
});
export const fetchExcludeAccountAntennasFail = (id, err) => ({
type: ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL,
id,
err,
});
export const addToAntennaAdder = antennaId => (dispatch, getState) => {
dispatch(addToAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
};
export const removeFromAntennaAdder = antennaId => (dispatch, getState) => {
dispatch(removeFromAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
};
export const addExcludeToAntennaAdder = antennaId => (dispatch, getState) => {
dispatch(addExcludeToAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
};
export const removeExcludeFromAntennaAdder = antennaId => (dispatch, getState) => {
dispatch(removeExcludeFromAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId'])));
};

View file

@ -1,7 +1,6 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { showAlertForError } from './alerts'; import { importFetchedStatuses } from './importer';
import { importFetchedAccounts, importFetchedStatuses } from './importer';
export const CIRCLE_FETCH_REQUEST = 'CIRCLE_FETCH_REQUEST'; export const CIRCLE_FETCH_REQUEST = 'CIRCLE_FETCH_REQUEST';
export const CIRCLE_FETCH_SUCCESS = 'CIRCLE_FETCH_SUCCESS'; export const CIRCLE_FETCH_SUCCESS = 'CIRCLE_FETCH_SUCCESS';
@ -11,10 +10,6 @@ export const CIRCLES_FETCH_REQUEST = 'CIRCLES_FETCH_REQUEST';
export const CIRCLES_FETCH_SUCCESS = 'CIRCLES_FETCH_SUCCESS'; export const CIRCLES_FETCH_SUCCESS = 'CIRCLES_FETCH_SUCCESS';
export const CIRCLES_FETCH_FAIL = 'CIRCLES_FETCH_FAIL'; export const CIRCLES_FETCH_FAIL = 'CIRCLES_FETCH_FAIL';
export const CIRCLE_EDITOR_TITLE_CHANGE = 'CIRCLE_EDITOR_TITLE_CHANGE';
export const CIRCLE_EDITOR_RESET = 'CIRCLE_EDITOR_RESET';
export const CIRCLE_EDITOR_SETUP = 'CIRCLE_EDITOR_SETUP';
export const CIRCLE_CREATE_REQUEST = 'CIRCLE_CREATE_REQUEST'; export const CIRCLE_CREATE_REQUEST = 'CIRCLE_CREATE_REQUEST';
export const CIRCLE_CREATE_SUCCESS = 'CIRCLE_CREATE_SUCCESS'; export const CIRCLE_CREATE_SUCCESS = 'CIRCLE_CREATE_SUCCESS';
export const CIRCLE_CREATE_FAIL = 'CIRCLE_CREATE_FAIL'; export const CIRCLE_CREATE_FAIL = 'CIRCLE_CREATE_FAIL';
@ -27,29 +22,6 @@ export const CIRCLE_DELETE_REQUEST = 'CIRCLE_DELETE_REQUEST';
export const CIRCLE_DELETE_SUCCESS = 'CIRCLE_DELETE_SUCCESS'; export const CIRCLE_DELETE_SUCCESS = 'CIRCLE_DELETE_SUCCESS';
export const CIRCLE_DELETE_FAIL = 'CIRCLE_DELETE_FAIL'; export const CIRCLE_DELETE_FAIL = 'CIRCLE_DELETE_FAIL';
export const CIRCLE_ACCOUNTS_FETCH_REQUEST = 'CIRCLE_ACCOUNTS_FETCH_REQUEST';
export const CIRCLE_ACCOUNTS_FETCH_SUCCESS = 'CIRCLE_ACCOUNTS_FETCH_SUCCESS';
export const CIRCLE_ACCOUNTS_FETCH_FAIL = 'CIRCLE_ACCOUNTS_FETCH_FAIL';
export const CIRCLE_EDITOR_SUGGESTIONS_CHANGE = 'CIRCLE_EDITOR_SUGGESTIONS_CHANGE';
export const CIRCLE_EDITOR_SUGGESTIONS_READY = 'CIRCLE_EDITOR_SUGGESTIONS_READY';
export const CIRCLE_EDITOR_SUGGESTIONS_CLEAR = 'CIRCLE_EDITOR_SUGGESTIONS_CLEAR';
export const CIRCLE_EDITOR_ADD_REQUEST = 'CIRCLE_EDITOR_ADD_REQUEST';
export const CIRCLE_EDITOR_ADD_SUCCESS = 'CIRCLE_EDITOR_ADD_SUCCESS';
export const CIRCLE_EDITOR_ADD_FAIL = 'CIRCLE_EDITOR_ADD_FAIL';
export const CIRCLE_EDITOR_REMOVE_REQUEST = 'CIRCLE_EDITOR_REMOVE_REQUEST';
export const CIRCLE_EDITOR_REMOVE_SUCCESS = 'CIRCLE_EDITOR_REMOVE_SUCCESS';
export const CIRCLE_EDITOR_REMOVE_FAIL = 'CIRCLE_EDITOR_REMOVE_FAIL';
export const CIRCLE_ADDER_RESET = 'CIRCLE_ADDER_RESET';
export const CIRCLE_ADDER_SETUP = 'CIRCLE_ADDER_SETUP';
export const CIRCLE_ADDER_CIRCLES_FETCH_REQUEST = 'CIRCLE_ADDER_CIRCLES_FETCH_REQUEST';
export const CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS = 'CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS';
export const CIRCLE_ADDER_CIRCLES_FETCH_FAIL = 'CIRCLE_ADDER_CIRCLES_FETCH_FAIL';
export const CIRCLE_STATUSES_FETCH_REQUEST = 'CIRCLE_STATUSES_FETCH_REQUEST'; export const CIRCLE_STATUSES_FETCH_REQUEST = 'CIRCLE_STATUSES_FETCH_REQUEST';
export const CIRCLE_STATUSES_FETCH_SUCCESS = 'CIRCLE_STATUSES_FETCH_SUCCESS'; export const CIRCLE_STATUSES_FETCH_SUCCESS = 'CIRCLE_STATUSES_FETCH_SUCCESS';
export const CIRCLE_STATUSES_FETCH_FAIL = 'CIRCLE_STATUSES_FETCH_FAIL'; export const CIRCLE_STATUSES_FETCH_FAIL = 'CIRCLE_STATUSES_FETCH_FAIL';
@ -108,89 +80,6 @@ export const fetchCirclesFail = error => ({
error, error,
}); });
export const submitCircleEditor = shouldReset => (dispatch, getState) => {
const circleId = getState().getIn(['circleEditor', 'circleId']);
const title = getState().getIn(['circleEditor', 'title']);
if (circleId === null) {
dispatch(createCircle(title, shouldReset));
} else {
dispatch(updateCircle(circleId, title, shouldReset));
}
};
export const setupCircleEditor = circleId => (dispatch, getState) => {
dispatch({
type: CIRCLE_EDITOR_SETUP,
circle: getState().getIn(['circles', circleId]),
});
dispatch(fetchCircleAccounts(circleId));
};
export const changeCircleEditorTitle = value => ({
type: CIRCLE_EDITOR_TITLE_CHANGE,
value,
});
export const createCircle = (title, shouldReset) => (dispatch, getState) => {
dispatch(createCircleRequest());
api(getState).post('/api/v1/circles', { title }).then(({ data }) => {
dispatch(createCircleSuccess(data));
if (shouldReset) {
dispatch(resetCircleEditor());
}
}).catch(err => dispatch(createCircleFail(err)));
};
export const createCircleRequest = () => ({
type: CIRCLE_CREATE_REQUEST,
});
export const createCircleSuccess = circle => ({
type: CIRCLE_CREATE_SUCCESS,
circle,
});
export const createCircleFail = error => ({
type: CIRCLE_CREATE_FAIL,
error,
});
export const updateCircle = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => {
dispatch(updateCircleRequest(id));
api(getState).put(`/api/v1/circles/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
dispatch(updateCircleSuccess(data));
if (shouldReset) {
dispatch(resetCircleEditor());
}
}).catch(err => dispatch(updateCircleFail(id, err)));
};
export const updateCircleRequest = id => ({
type: CIRCLE_UPDATE_REQUEST,
id,
});
export const updateCircleSuccess = circle => ({
type: CIRCLE_UPDATE_SUCCESS,
circle,
});
export const updateCircleFail = (id, error) => ({
type: CIRCLE_UPDATE_FAIL,
id,
error,
});
export const resetCircleEditor = () => ({
type: CIRCLE_EDITOR_RESET,
});
export const deleteCircle = id => (dispatch, getState) => { export const deleteCircle = id => (dispatch, getState) => {
dispatch(deleteCircleRequest(id)); dispatch(deleteCircleRequest(id));
@ -215,169 +104,6 @@ export const deleteCircleFail = (id, error) => ({
error, error,
}); });
export const fetchCircleAccounts = circleId => (dispatch, getState) => {
dispatch(fetchCircleAccountsRequest(circleId));
api(getState).get(`/api/v1/circles/${circleId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchCircleAccountsSuccess(circleId, data));
}).catch(err => dispatch(fetchCircleAccountsFail(circleId, err)));
};
export const fetchCircleAccountsRequest = id => ({
type: CIRCLE_ACCOUNTS_FETCH_REQUEST,
id,
});
export const fetchCircleAccountsSuccess = (id, accounts, next) => ({
type: CIRCLE_ACCOUNTS_FETCH_SUCCESS,
id,
accounts,
next,
});
export const fetchCircleAccountsFail = (id, error) => ({
type: CIRCLE_ACCOUNTS_FETCH_FAIL,
id,
error,
});
export const fetchCircleSuggestions = q => (dispatch, getState) => {
const params = {
q,
resolve: false,
follower: true,
};
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchCircleSuggestionsReady(q, data));
}).catch(error => dispatch(showAlertForError(error)));
};
export const fetchCircleSuggestionsReady = (query, accounts) => ({
type: CIRCLE_EDITOR_SUGGESTIONS_READY,
query,
accounts,
});
export const clearCircleSuggestions = () => ({
type: CIRCLE_EDITOR_SUGGESTIONS_CLEAR,
});
export const changeCircleSuggestions = value => ({
type: CIRCLE_EDITOR_SUGGESTIONS_CHANGE,
value,
});
export const addToCircleEditor = accountId => (dispatch, getState) => {
dispatch(addToCircle(getState().getIn(['circleEditor', 'circleId']), accountId));
};
export const addToCircle = (circleId, accountId) => (dispatch, getState) => {
dispatch(addToCircleRequest(circleId, accountId));
api(getState).post(`/api/v1/circles/${circleId}/accounts`, { account_ids: [accountId] })
.then(() => dispatch(addToCircleSuccess(circleId, accountId)))
.catch(err => dispatch(addToCircleFail(circleId, accountId, err)));
};
export const addToCircleRequest = (circleId, accountId) => ({
type: CIRCLE_EDITOR_ADD_REQUEST,
circleId,
accountId,
});
export const addToCircleSuccess = (circleId, accountId) => ({
type: CIRCLE_EDITOR_ADD_SUCCESS,
circleId,
accountId,
});
export const addToCircleFail = (circleId, accountId, error) => ({
type: CIRCLE_EDITOR_ADD_FAIL,
circleId,
accountId,
error,
});
export const removeFromCircleEditor = accountId => (dispatch, getState) => {
dispatch(removeFromCircle(getState().getIn(['circleEditor', 'circleId']), accountId));
};
export const removeFromCircle = (circleId, accountId) => (dispatch, getState) => {
dispatch(removeFromCircleRequest(circleId, accountId));
api(getState).delete(`/api/v1/circles/${circleId}/accounts`, { params: { account_ids: [accountId] } })
.then(() => dispatch(removeFromCircleSuccess(circleId, accountId)))
.catch(err => dispatch(removeFromCircleFail(circleId, accountId, err)));
};
export const removeFromCircleRequest = (circleId, accountId) => ({
type: CIRCLE_EDITOR_REMOVE_REQUEST,
circleId,
accountId,
});
export const removeFromCircleSuccess = (circleId, accountId) => ({
type: CIRCLE_EDITOR_REMOVE_SUCCESS,
circleId,
accountId,
});
export const removeFromCircleFail = (circleId, accountId, error) => ({
type: CIRCLE_EDITOR_REMOVE_FAIL,
circleId,
accountId,
error,
});
export const resetCircleAdder = () => ({
type: CIRCLE_ADDER_RESET,
});
export const setupCircleAdder = accountId => (dispatch, getState) => {
dispatch({
type: CIRCLE_ADDER_SETUP,
account: getState().getIn(['accounts', accountId]),
});
dispatch(fetchCircles());
dispatch(fetchAccountCircles(accountId));
};
export const fetchAccountCircles = accountId => (dispatch, getState) => {
dispatch(fetchAccountCirclesRequest(accountId));
api(getState).get(`/api/v1/accounts/${accountId}/circles`)
.then(({ data }) => dispatch(fetchAccountCirclesSuccess(accountId, data)))
.catch(err => dispatch(fetchAccountCirclesFail(accountId, err)));
};
export const fetchAccountCirclesRequest = id => ({
type:CIRCLE_ADDER_CIRCLES_FETCH_REQUEST,
id,
});
export const fetchAccountCirclesSuccess = (id, circles) => ({
type: CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS,
id,
circles,
});
export const fetchAccountCirclesFail = (id, err) => ({
type: CIRCLE_ADDER_CIRCLES_FETCH_FAIL,
id,
err,
});
export const addToCircleAdder = circleId => (dispatch, getState) => {
dispatch(addToCircle(circleId, getState().getIn(['circleAdder', 'accountId'])));
};
export const removeFromCircleAdder = circleId => (dispatch, getState) => {
dispatch(removeFromCircle(circleId, getState().getIn(['circleAdder', 'accountId'])));
};
export function fetchCircleStatuses(circleId) { export function fetchCircleStatuses(circleId) {
return (dispatch, getState) => { return (dispatch, getState) => {
if (getState().getIn(['circles', circleId, 'isLoading'])) { if (getState().getIn(['circles', circleId, 'isLoading'])) {

View file

@ -24,9 +24,12 @@ export const apiGetExcludeAccounts = (antennaId: string) =>
}); });
export const apiGetDomains = (antennaId: string) => export const apiGetDomains = (antennaId: string) =>
apiRequestGet<string[]>(`v1/antennas/${antennaId}/domains`, { apiRequestGet<{ domains: string[]; exclude_domains: string[] }>(
limit: 0, `v1/antennas/${antennaId}/domains`,
}); {
limit: 0,
},
);
export const apiAddDomain = (antennaId: string, domain: string) => export const apiAddDomain = (antennaId: string, domain: string) =>
apiRequestPost(`v1/antennas/${antennaId}/domains`, { apiRequestPost(`v1/antennas/${antennaId}/domains`, {
@ -38,29 +41,70 @@ export const apiRemoveDomain = (antennaId: string, domain: string) =>
domains: [domain], domains: [domain],
}); });
export const apiGetExcludeDomains = (antennaId: string) => export const apiAddExcludeDomain = (antennaId: string, domain: string) =>
apiRequestGet<string[]>(`v1/antennas/${antennaId}/exclude_domains`, { apiRequestPost(`v1/antennas/${antennaId}/exclude_domains`, {
limit: 0, domains: [domain],
});
export const apiRemoveExcludeDomain = (antennaId: string, domain: string) =>
apiRequestDelete(`v1/antennas/${antennaId}/exclude_domains`, {
domains: [domain],
}); });
export const apiGetTags = (antennaId: string) => export const apiGetTags = (antennaId: string) =>
apiRequestGet<string[]>(`v1/antennas/${antennaId}/tags`, { apiRequestGet<{ tags: string[]; exclude_tags: string[] }>(
limit: 0, `v1/antennas/${antennaId}/tags`,
{
limit: 0,
},
);
export const apiAddTag = (antennaId: string, tag: string) =>
apiRequestPost(`v1/antennas/${antennaId}/tags`, {
tags: [tag],
}); });
export const apiGetExcludeTags = (antennaId: string) => export const apiRemoveTag = (antennaId: string, tag: string) =>
apiRequestGet<string[]>(`v1/antennas/${antennaId}/exclude_tags`, { apiRequestDelete(`v1/antennas/${antennaId}/tags`, {
limit: 0, tags: [tag],
});
export const apiAddExcludeTag = (antennaId: string, tag: string) =>
apiRequestPost(`v1/antennas/${antennaId}/exclude_tags`, {
tags: [tag],
});
export const apiRemoveExcludeTag = (antennaId: string, tag: string) =>
apiRequestDelete(`v1/antennas/${antennaId}/exclude_tags`, {
tags: [tag],
}); });
export const apiGetKeywords = (antennaId: string) => export const apiGetKeywords = (antennaId: string) =>
apiRequestGet<string[]>(`v1/antennas/${antennaId}/keywords`, { apiRequestGet<{ keywords: string[]; exclude_keywords: string[] }>(
limit: 0, `v1/antennas/${antennaId}/keywords`,
{
limit: 0,
},
);
export const apiAddKeyword = (antennaId: string, keyword: string) =>
apiRequestPost(`v1/antennas/${antennaId}/keywords`, {
keywords: [keyword],
}); });
export const apiGetExcludeKeywords = (antennaId: string) => export const apiRemoveKeyword = (antennaId: string, keyword: string) =>
apiRequestGet<string[]>(`v1/antennas/${antennaId}/exclude_keywords`, { apiRequestDelete(`v1/antennas/${antennaId}/keywords`, {
limit: 0, keywords: [keyword],
});
export const apiAddExcludeKeyword = (antennaId: string, keyword: string) =>
apiRequestPost(`v1/antennas/${antennaId}/exclude_keywords`, {
keywords: [keyword],
});
export const apiRemoveExcludeKeyword = (antennaId: string, keyword: string) =>
apiRequestDelete(`v1/antennas/${antennaId}/exclude_keywords`, {
keywords: [keyword],
}); });
export const apiGetAccountAntennas = (accountId: string) => export const apiGetAccountAntennas = (accountId: string) =>

View file

@ -1,5 +1,7 @@
// See app/serializers/rest/list_serializer.rb // See app/serializers/rest/list_serializer.rb
import type { ApiAntennaJSON } from './antennas';
export type RepliesPolicyType = 'list' | 'followed' | 'none'; export type RepliesPolicyType = 'list' | 'followed' | 'none';
export interface ApiListJSON { export interface ApiListJSON {
@ -8,4 +10,5 @@ export interface ApiListJSON {
exclusive: boolean; exclusive: boolean;
replies_policy: RepliesPolicyType; replies_policy: RepliesPolicyType;
notify: boolean; notify: boolean;
antennas?: ApiAntennaJSON[];
} }

View file

@ -1,88 +0,0 @@
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { removeFromAntennaEditor, addToAntennaEditor, removeExcludeFromAntennaEditor, addExcludeToAntennaEditor } from '../../../actions/antennas';
import { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name';
import { IconButton } from '../../../components/icon_button';
import { makeGetAccount } from '../../../selectors';
const messages = defineMessages({
remove: { id: 'antennas.account.remove', defaultMessage: 'Remove from antenna' },
add: { id: 'antennas.account.add', defaultMessage: 'Add to antenna' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = (state, { accountId, added }) => ({
account: getAccount(state, accountId),
added: typeof added === 'undefined' ? state.getIn(['antennaEditor', 'accounts', 'items']).includes(accountId) : added,
});
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { accountId }) => ({
onRemove: () => dispatch(removeFromAntennaEditor(accountId)),
onAdd: () => dispatch(addToAntennaEditor(accountId)),
onExcludeRemove: () => dispatch(removeExcludeFromAntennaEditor(accountId)),
onExcludeAdd: () => dispatch(addExcludeToAntennaEditor(accountId)),
});
class Account extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
isExclude: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
onRemove: PropTypes.func.isRequired,
onAdd: PropTypes.func.isRequired,
onExcludeRemove: PropTypes.func.isRequired,
onExcludeAdd: PropTypes.func.isRequired,
added: PropTypes.bool,
};
static defaultProps = {
added: false,
};
render () {
const { account, intl, isExclude, onRemove, onAdd, onExcludeRemove, onExcludeAdd, added } = this.props;
let button;
if (added) {
button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={isExclude ? onExcludeRemove : onRemove} />;
} else {
button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={isExclude ? onExcludeAdd : onAdd} />;
}
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 className='account__relationship'>
{button}
</div>
</div>
</div>
);
}
}
export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account));

View file

@ -1,76 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { changeAntennaEditorTitle, submitAntennaEditor } from '../../../actions/antennas';
import { IconButton } from '../../../components/icon_button';
const messages = defineMessages({
title: { id: 'antennas.edit.submit', defaultMessage: 'Change title' },
});
const mapStateToProps = state => ({
value: state.getIn(['antennaEditor', 'title']),
disabled: !state.getIn(['antennaEditor', 'isChanged']) || !state.getIn(['antennaEditor', 'title']),
});
const mapDispatchToProps = dispatch => ({
onChange: value => dispatch(changeAntennaEditorTitle(value)),
onSubmit: () => dispatch(submitAntennaEditor(false)),
});
class AntennaForm extends PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
disabled: PropTypes.bool,
intl: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};
handleChange = e => {
this.props.onChange(e.target.value);
};
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit();
};
handleClick = () => {
this.props.onSubmit();
};
render () {
const { value, disabled, intl } = this.props;
const title = intl.formatMessage(messages.title);
return (
<form className='column-inline-form' onSubmit={this.handleSubmit}>
<input
className='setting-text'
value={value}
onChange={this.handleChange}
/>
<IconButton
disabled={disabled}
icon='check'
iconComponent={CheckIcon}
title={title}
onClick={this.handleClick}
/>
</form>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(AntennaForm));

View file

@ -1,83 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import classNames from 'classnames';
import { connect } from 'react-redux';
import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon';
import { fetchAntennaSuggestions, clearAntennaSuggestions, changeAntennaSuggestions } from '../../../actions/antennas';
const messages = defineMessages({
search: { id: 'antennas.search', defaultMessage: 'Search among people you follow' },
});
const mapStateToProps = state => ({
value: state.getIn(['antennaEditor', 'suggestions', 'value']),
});
const mapDispatchToProps = dispatch => ({
onSubmit: value => dispatch(fetchAntennaSuggestions(value)),
onClear: () => dispatch(clearAntennaSuggestions()),
onChange: value => dispatch(changeAntennaSuggestions(value)),
});
class Search extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
};
handleChange = e => {
this.props.onChange(e.target.value);
};
handleKeyUp = e => {
if (e.keyCode === 13) {
this.props.onSubmit(this.props.value);
}
};
handleClear = () => {
this.props.onClear();
};
render () {
const { value, intl } = this.props;
const hasValue = value.length > 0;
return (
<div className='list-editor__search search'>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
<input
className='search__input'
type='text'
value={value}
onChange={this.handleChange}
onKeyUp={this.handleKeyUp}
placeholder={intl.formatMessage(messages.search)}
/>
</label>
<div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
<Icon id='search' icon={SearchIcon} className={classNames({ active: !hasValue })} />
<Icon id='times-circle' icon={CancelIcon} aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search));

View file

@ -1,84 +0,0 @@
import PropTypes from 'prop-types';
import { injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import spring from 'react-motion/lib/spring';
import { setupAntennaEditor, setupExcludeAntennaEditor, clearAntennaSuggestions, resetAntennaEditor } from '../../actions/antennas';
import Motion from '../ui/util/optional_motion';
import Account from './components/account';
import EditAntennaForm from './components/edit_antenna_form';
import Search from './components/search';
const mapStateToProps = (state) => ({
accountIds: state.getIn(['antennaEditor', 'accounts', 'items']),
searchAccountIds: state.getIn(['antennaEditor', 'suggestions', 'items']),
});
const mapDispatchToProps = (dispatch, { isExclude }) => ({
onInitialize: antennaId => dispatch(isExclude ? setupExcludeAntennaEditor(antennaId) : setupAntennaEditor(antennaId)),
onClear: () => dispatch(clearAntennaSuggestions()),
onReset: () => dispatch(resetAntennaEditor()),
});
class AntennaEditor extends ImmutablePureComponent {
static propTypes = {
antennaId: PropTypes.string.isRequired,
isExclude: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
onInitialize: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list.isRequired,
searchAccountIds: ImmutablePropTypes.list.isRequired,
};
componentDidMount () {
const { onInitialize, antennaId } = this.props;
onInitialize(antennaId);
}
componentWillUnmount () {
const { onReset } = this.props;
onReset();
}
render () {
const { accountIds, searchAccountIds, onClear, isExclude } = this.props;
const showSearch = searchAccountIds.size > 0;
return (
<div className='modal-root__modal list-editor'>
<EditAntennaForm />
<Search />
<div className='drawer__pager'>
<div className='drawer__inner list-editor__accounts'>
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} isExclude={isExclude} added />)}
</div>
{showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} isExclude={isExclude} />)}
</div>
)}
</Motion>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(AntennaEditor));

View file

@ -1,46 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { injectIntl } from 'react-intl';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
class RadioPanel extends PureComponent {
static propTypes = {
values: ImmutablePropTypes.list.isRequired,
value: PropTypes.object.isRequired,
intl: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
};
handleChange = e => {
const value = e.currentTarget.getAttribute('data-value');
if (value !== this.props.value.get('value')) {
this.props.onChange(value);
}
};
render () {
const { values, value } = this.props;
return (
<div className='setting-radio-panel'>
{values.map((val) => (
<button className={classNames('setting-radio-panel__item', {'setting-radio-panel__item__active': value.get('value') === val.get('value')})}
key={val.get('value')} onClick={this.handleChange} data-value={val.get('value')}>
{val.get('label')}
</button>
))}
</div>
);
}
}
export default connect()(injectIntl(RadioPanel));

View file

@ -1,104 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import { Button } from 'mastodon/components/button';
import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button';
class TextListItem extends PureComponent {
static propTypes = {
icon: PropTypes.string.isRequired,
iconComponent: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
onRemove: PropTypes.func.isRequired,
};
handleRemove = () => {
this.props.onRemove(this.props.value);
};
render () {
const { icon, iconComponent, value } = this.props;
return (
<div className='setting-text-list-item'>
<Icon id={icon} icon={iconComponent} />
<span className='label'>{value}</span>
<IconButton icon='trash' iconComponent={DeleteIcon} onClick={this.handleRemove} />
</div>
);
}
}
class TextList extends PureComponent {
static propTypes = {
values: ImmutablePropTypes.list.isRequired,
value: PropTypes.string.isRequired,
disabled: PropTypes.bool,
intl: PropTypes.object.isRequired,
icon: PropTypes.string.isRequired,
iconComponent: PropTypes.func.isRequired,
label: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onAdd: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
};
handleChange = e => {
this.props.onChange(e.target.value);
};
handleAdd = () => {
this.props.onAdd();
};
handleSubmit = (e) => {
e.preventDefault();
this.handleAdd();
};
render () {
const { icon, iconComponent, value, values, disabled, label, title } = this.props;
return (
<div className='setting-text-list'>
{values.map((val) => (
<TextListItem key={val} value={val} icon={icon} iconComponent={iconComponent} onRemove={this.props.onRemove} />
))}
<form className='add-text-form' onSubmit={this.handleSubmit}>
<label>
<span style={{ display: 'none' }}>{label}</span>
<input
className='setting-text'
value={value}
disabled={disabled}
onChange={this.handleChange}
placeholder={label}
/>
</label>
<Button
disabled={disabled || !value}
text={title}
onClick={this.handleAdd}
/>
</form>
</div>
);
}
}
export default connect()(injectIntl(TextList));

View file

@ -1,587 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import Select, { NonceProvider } from 'react-select';
import Toggle from 'react-toggle';
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import DomainIcon from '@/material-icons/400-24px/dns.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import HashtagIcon from '@/material-icons/400-24px/tag.svg?react';
import KeywordIcon from '@/material-icons/400-24px/title.svg?react';
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
import {
fetchAntenna,
deleteAntenna,
updateAntenna,
addDomainToAntenna,
removeDomainFromAntenna,
addExcludeDomainToAntenna,
removeExcludeDomainFromAntenna,
fetchAntennaDomains,
fetchAntennaKeywords,
removeKeywordFromAntenna,
addKeywordToAntenna,
removeExcludeKeywordFromAntenna,
addExcludeKeywordToAntenna,
fetchAntennaTags,
removeTagFromAntenna,
addTagToAntenna,
removeExcludeTagFromAntenna,
addExcludeTagToAntenna,
} from 'mastodon/actions/antennas';
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
import { fetchLists } from 'mastodon/actions/lists';
import { openModal } from 'mastodon/actions/modal';
import { Button } from 'mastodon/components/button';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { enableLocalTimeline } from 'mastodon/initial_state';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import RadioPanel from './components/radio_panel';
import TextList from './components/text_list';
const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_antenna.message', defaultMessage: 'Are you sure you want to permanently delete this antenna?' },
deleteConfirm: { id: 'confirmations.delete_antenna.confirm', defaultMessage: 'Delete' },
editAccounts: { id: 'antennas.edit_accounts', defaultMessage: 'Edit accounts' },
noOptions: { id: 'antennas.select.no_options_message', defaultMessage: 'Empty lists' },
placeholder: { id: 'antennas.select.placeholder', defaultMessage: 'Select list' },
addDomainLabel: { id: 'antennas.add_domain_placeholder', defaultMessage: 'New domain' },
addKeywordLabel: { id: 'antennas.add_keyword_placeholder', defaultMessage: 'New keyword' },
addTagLabel: { id: 'antennas.add_tag_placeholder', defaultMessage: 'New tag' },
addDomainTitle: { id: 'antennas.add_domain', defaultMessage: 'Add domain' },
addKeywordTitle: { id: 'antennas.add_keyword', defaultMessage: 'Add keyword' },
addTagTitle: { id: 'antennas.add_tag', defaultMessage: 'Add tag' },
accounts: { id: 'antennas.accounts', defaultMessage: '{count} accounts' },
domains: { id: 'antennas.domains', defaultMessage: '{count} domains' },
tags: { id: 'antennas.tags', defaultMessage: '{count} tags' },
keywords: { id: 'antennas.keywords', defaultMessage: '{count} keywords' },
setHome: { id: 'antennas.select.set_home', defaultMessage: 'Set home' },
});
const mapStateToProps = (state, props) => ({
antenna: state.getIn(['antennas', props.params.id]),
lists: state.get('lists'),
domains: state.getIn(['antennas', props.params.id, 'domains']) || ImmutableMap(),
keywords: state.getIn(['antennas', props.params.id, 'keywords']) || ImmutableMap(),
tags: state.getIn(['antennas', props.params.id, 'tags']) || ImmutableMap(),
});
class AntennaSetting extends PureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
antenna: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
lists: ImmutablePropTypes.map,
domains: ImmutablePropTypes.map,
keywords: ImmutablePropTypes.map,
tags: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
...WithRouterPropTypes,
};
state = {
domainName: '',
excludeDomainName: '',
keywordName: '',
excludeKeywordName: '',
tagName: '',
excludeTagName: '',
rangeRadioValue: null,
contentRadioValue: null,
};
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('ANTENNA', { id: this.props.params.id }));
this.props.history.push('/');
}
};
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
};
handleHeaderClick = () => {
this.column.scrollTop();
};
componentDidMount () {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(fetchAntenna(id));
dispatch(fetchAntennaDomains(id));
dispatch(fetchAntennaKeywords(id));
dispatch(fetchAntennaTags(id));
dispatch(fetchLists());
}
UNSAFE_componentWillReceiveProps (nextProps) {
const { dispatch } = this.props;
const { id } = nextProps.params;
if (id !== this.props.params.id) {
dispatch(fetchAntenna(id));
dispatch(fetchAntennaKeywords(id));
dispatch(fetchAntennaDomains(id));
dispatch(fetchAntennaKeywords(id));
dispatch(fetchAntennaTags(id));
dispatch(fetchLists());
}
}
setRef = c => {
this.column = c;
};
handleEditClick = () => {
this.props.dispatch(openModal({
modalType: 'ANTENNA_EDITOR',
modalProps: { antennaId: this.props.params.id, isExclude: false },
}));
};
handleExcludeEditClick = () => {
this.props.dispatch(openModal({
modalType: 'ANTENNA_EDITOR',
modalProps: { antennaId: this.props.params.id, isExclude: true },
}));
};
handleEditAntennaClick = () => {
window.open(`/antennas/${this.props.params.id}/edit`, '_blank');
};
handleDeleteClick = () => {
const { dispatch, columnId, intl } = this.props;
const { id } = this.props.params;
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.deleteMessage),
confirm: intl.formatMessage(messages.deleteConfirm),
onConfirm: () => {
dispatch(deleteAntenna(id));
if (columnId) {
dispatch(removeColumn(columnId));
} else {
this.props.history.push('/antennasw');
}
},
},
}));
};
handleTimelineClick = () => {
this.props.history.push(`/antennast/${this.props.params.id}`);
};
onStlToggle = ({ target }) => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateAntenna(id, undefined, false, undefined, target.checked, undefined, undefined, undefined, undefined));
};
onLtlToggle = ({ target }) => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateAntenna(id, undefined, false, undefined, undefined, target.checked, undefined, undefined, undefined));
};
onMediaOnlyToggle = ({ target }) => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, target.checked, undefined, undefined));
};
onIgnoreReblogToggle = ({ target }) => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, target.checked, undefined));
};
onNoInsertFeedsToggle = ({ target }) => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, undefined, target.checked));
};
onSelect = value => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateAntenna(id, undefined, false, value.value, undefined, undefined, undefined, undefined, undefined));
};
onHomeSelect = () => this.onSelect({ value: '0' });
noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions);
onRangeRadioChanged = (value) => this.setState({ rangeRadioValue: value });
onContentRadioChanged = (value) => this.setState({ contentRadioValue: value });
onDomainNameChanged = (value) => this.setState({ domainName: value });
onDomainAdd = () => {
this.props.dispatch(addDomainToAntenna(this.props.params.id, this.state.domainName));
this.setState({ domainName: '' });
};
onDomainRemove = (value) => this.props.dispatch(removeDomainFromAntenna(this.props.params.id, value));
onKeywordNameChanged = (value) => this.setState({ keywordName: value });
onKeywordAdd = () => {
this.props.dispatch(addKeywordToAntenna(this.props.params.id, this.state.keywordName));
this.setState({ keywordName: '' });
};
onKeywordRemove = (value) => this.props.dispatch(removeKeywordFromAntenna(this.props.params.id, value));
onTagNameChanged = (value) => this.setState({ tagName: value });
onTagAdd = () => {
this.props.dispatch(addTagToAntenna(this.props.params.id, this.state.tagName));
this.setState({ tagName: '' });
};
onTagRemove = (value) => this.props.dispatch(removeTagFromAntenna(this.props.params.id, value));
onExcludeDomainNameChanged = (value) => this.setState({ excludeDomainName: value });
onExcludeDomainAdd = () => {
this.props.dispatch(addExcludeDomainToAntenna(this.props.params.id, this.state.excludeDomainName));
this.setState({ excludeDomainName: '' });
};
onExcludeDomainRemove = (value) => this.props.dispatch(removeExcludeDomainFromAntenna(this.props.params.id, value));
onExcludeKeywordNameChanged = (value) => this.setState({ excludeKeywordName: value });
onExcludeKeywordAdd = () => {
this.props.dispatch(addExcludeKeywordToAntenna(this.props.params.id, this.state.excludeKeywordName));
this.setState({ excludeKeywordName: '' });
};
onExcludeKeywordRemove = (value) => this.props.dispatch(removeExcludeKeywordFromAntenna(this.props.params.id, value));
onExcludeTagNameChanged = (value) => this.setState({ excludeTagName: value });
onExcludeTagAdd = () => {
this.props.dispatch(addExcludeTagToAntenna(this.props.params.id, this.state.excludeTagName));
this.setState({ excludeTagName: '' });
};
onExcludeTagRemove = (value) => this.props.dispatch(removeExcludeTagFromAntenna(this.props.params.id, value));
render () {
const { columnId, multiColumn, antenna, lists, domains, keywords, tags, intl } = this.props;
const { id } = this.props.params;
const pinned = !!columnId;
const title = antenna ? antenna.get('title') : id;
const isStl = antenna ? antenna.get('stl') : undefined;
const isLtl = antenna ? antenna.get('ltl') : undefined;
const isMediaOnly = antenna ? antenna.get('with_media_only') : undefined;
const isIgnoreReblog = antenna ? antenna.get('ignore_reblog') : undefined;
const isInsertFeeds = antenna ? antenna.get('insert_feeds') : undefined;
if (typeof antenna === 'undefined') {
return (
<Column>
<div className='scrollable'>
<LoadingIndicator />
</div>
</Column>
);
} else if (antenna === false) {
return (
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
);
}
let columnSettings;
if (!isStl && !isLtl) {
columnSettings = (
<>
<section className='similar-row'>
<div className='setting-toggle'>
<Toggle id={`antenna-${id}-mediaonly`} checked={isMediaOnly} onChange={this.onMediaOnlyToggle} />
<label htmlFor={`antenna-${id}-mediaonly`} className='setting-toggle__label'>
<FormattedMessage id='antennas.media_only' defaultMessage='Media only' />
</label>
</div>
</section>
<section className='similar-row'>
<div className='setting-toggle'>
<Toggle id={`antenna-${id}-ignorereblog`} checked={isIgnoreReblog} onChange={this.onIgnoreReblogToggle} />
<label htmlFor={`antenna-${id}-ignorereblog`} className='setting-toggle__label'>
<FormattedMessage id='antennas.ignore_reblog' defaultMessage='Exclude boosts' />
</label>
</div>
</section>
</>
);
}
let stlAlert;
if (isStl) {
stlAlert = (
<div className='antenna-setting'>
<p><FormattedMessage id='antennas.in_stl_mode' defaultMessage='This antenna is in STL mode.' /></p>
</div>
);
} else if (isLtl) {
stlAlert = (
<div className='antenna-setting'>
<p><FormattedMessage id='antennas.in_ltl_mode' defaultMessage='This antenna is in LTL mode.' /></p>
</div>
);
}
const rangeRadioValues = ImmutableList([
ImmutableMap({ value: 'accounts', label: intl.formatMessage(messages.accounts, { count: antenna.get('accounts_count') }) }),
ImmutableMap({ value: 'domains', label: intl.formatMessage(messages.domains, { count: antenna.get('domains_count') }) }),
]);
const rangeRadioValue = ImmutableMap({ value: this.state.rangeRadioValue || (antenna.get('domains_count') > 0 ? 'domains' : 'accounts') });
const rangeRadioAlert = antenna.get(rangeRadioValue.get('value') === 'accounts' ? 'domains_count' : 'accounts_count') > 0;
const contentRadioValues = ImmutableList([
ImmutableMap({ value: 'keywords', label: intl.formatMessage(messages.keywords, { count: antenna.get('keywords_count') }) }),
ImmutableMap({ value: 'tags', label: intl.formatMessage(messages.tags, { count: antenna.get('tags_count') }) }),
]);
const contentRadioValue = ImmutableMap({ value: this.state.contentRadioValue || (antenna.get('tags_count') > 0 ? 'tags' : 'keywords') });
const contentRadioAlert = antenna.get(contentRadioValue.get('value') === 'tags' ? 'keywords_count' : 'tags_count') > 0;
const listOptions = lists.toArray().filter((list) => list.length >= 2 && list[1]).map((list) => {
return { value: list[1].get('id'), label: list[1].get('title') };
});
const isShowStlToggle = !isLtl && (enableLocalTimeline || isStl);
const isShowLtlToggle = !isStl && (enableLocalTimeline || isLtl);
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
<ColumnHeader
icon='wifi'
iconComponent={AntennaIcon}
title={title}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
>
<div className='column-settings'>
<section className='column-header__links'>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditAntennaClick}>
<Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='antennas.edit_static' defaultMessage='Edit antenna' />
</button>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
<Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='antennas.delete' defaultMessage='Delete antenna' />
</button>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleTimelineClick}>
<Icon id='wifi' icon={AntennaIcon} /> <FormattedMessage id='antennas.go_timeline' defaultMessage='Go to antenna timeline' />
</button>
</section>
{isShowStlToggle && (
<section>
<div className='setting-toggle'>
<Toggle id={`antenna-${id}-stl`} checked={isStl} onChange={this.onStlToggle} />
<label htmlFor={`antenna-${id}-stl`} className='setting-toggle__label'>
<FormattedMessage id='antennas.stl' defaultMessage='STL mode' />
</label>
</div>
</section>
)}
{isShowLtlToggle && (
<section className={isShowStlToggle && 'similar-row'}>
<div className='setting-toggle'>
<Toggle id={`antenna-${id}-ltl`} checked={isLtl} onChange={this.onLtlToggle} />
<label htmlFor={`antenna-${id}-ltl`} className='setting-toggle__label'>
<FormattedMessage id='antennas.ltl' defaultMessage='LTL mode' />
</label>
</div>
</section>
)}
<section className={(isShowStlToggle || isShowLtlToggle) && 'similar-row'}>
<div className='setting-toggle'>
<Toggle id={`antenna-${id}-noinsertfeeds`} checked={isInsertFeeds} onChange={this.onNoInsertFeedsToggle} />
<label htmlFor={`antenna-${id}-noinsertfeeds`} className='setting-toggle__label'>
<FormattedMessage id='antennas.insert_feeds' defaultMessage='Insert to feeds' />
</label>
</div>
</section>
{columnSettings}
</div>
</ColumnHeader>
{stlAlert}
<div className='antenna-setting'>
{isInsertFeeds && (
<>
{antenna.get('list') ? (
<p><FormattedMessage id='antennas.related_list' defaultMessage='This antenna is related to {listTitle}.' values={{ listTitle: antenna.getIn(['list', 'title']) }} /></p>
) : (
<p><FormattedMessage id='antennas.not_related_list' defaultMessage='This antenna is not related list. Posts will appear in home timeline. Open edit page to set list.' /></p>
)}
<NonceProvider nonce={document.querySelector('meta[name=style-nonce]').content} cacheKey='lists'>
<Select
value={{ value: antenna.getIn(['list', 'id']), label: antenna.getIn(['list', 'title']) }}
options={listOptions}
noOptionsMessage={this.noOptionsMessage}
onChange={this.onSelect}
className='column-content-select__container'
classNamePrefix='column-content-select'
name='lists'
placeholder={this.props.intl.formatMessage(messages.placeholder)}
defaultOptions
/>
</NonceProvider>
<Button secondary text={this.props.intl.formatMessage(messages.setHome)} onClick={this.onHomeSelect} />
</>
)}
{!isStl && !isLtl && (
<>
<h2><FormattedMessage id='antennas.filter' defaultMessage='Filter' /></h2>
<RadioPanel values={rangeRadioValues} value={rangeRadioValue} onChange={this.onRangeRadioChanged} />
{rangeRadioValue.get('value') === 'accounts' && <Button text={intl.formatMessage(messages.editAccounts)} onClick={this.handleEditClick} />}
{rangeRadioValue.get('value') === 'domains' && (
<TextList
onChange={this.onDomainNameChanged}
onAdd={this.onDomainAdd}
onRemove={this.onDomainRemove}
value={this.state.domainName}
values={domains.get('domains') || ImmutableList()}
icon='sitemap'
iconComponent={DomainIcon}
label={intl.formatMessage(messages.addDomainLabel)}
title={intl.formatMessage(messages.addDomainTitle)}
/>
)}
{rangeRadioAlert && <div className='alert'><FormattedMessage id='antennas.warnings.range_radio' defaultMessage='Simultaneous account and domain designation is not recommended.' /></div>}
<RadioPanel values={contentRadioValues} value={contentRadioValue} onChange={this.onContentRadioChanged} />
{contentRadioValue.get('value') === 'tags' && (
<TextList
onChange={this.onTagNameChanged}
onAdd={this.onTagAdd}
onRemove={this.onTagRemove}
value={this.state.tagName}
values={tags.get('tags') || ImmutableList()}
icon='hashtag'
iconComponent={HashtagIcon}
label={intl.formatMessage(messages.addTagLabel)}
title={intl.formatMessage(messages.addTagTitle)}
/>
)}
{contentRadioValue.get('value') === 'keywords' && (
<TextList
onChange={this.onKeywordNameChanged}
onAdd={this.onKeywordAdd}
onRemove={this.onKeywordRemove}
value={this.state.keywordName}
values={keywords.get('keywords') || ImmutableList()}
icon='paragraph'
iconComponent={KeywordIcon}
label={intl.formatMessage(messages.addKeywordLabel)}
title={intl.formatMessage(messages.addKeywordTitle)}
/>
)}
{contentRadioAlert && <div className='alert'><FormattedMessage id='antennas.warnings.content_radio' defaultMessage='Simultaneous keyword and tag designation is not recommended.' /></div>}
<h2><FormattedMessage id='antennas.filter_not' defaultMessage='Filter Not' /></h2>
<h3><FormattedMessage id='antennas.exclude_accounts' defaultMessage='Exclude accounts' /></h3>
<Button text={intl.formatMessage(messages.editAccounts)} onClick={this.handleExcludeEditClick} />
<h3><FormattedMessage id='antennas.exclude_domains' defaultMessage='Exclude domains' /></h3>
<TextList
onChange={this.onExcludeDomainNameChanged}
onAdd={this.onExcludeDomainAdd}
onRemove={this.onExcludeDomainRemove}
value={this.state.excludeDomainName}
values={domains.get('exclude_domains') || ImmutableList()}
icon='sitemap'
iconComponent={DomainIcon}
label={intl.formatMessage(messages.addDomainLabel)}
title={intl.formatMessage(messages.addDomainTitle)}
/>
<h3><FormattedMessage id='antennas.exclude_keywords' defaultMessage='Exclude keywords' /></h3>
<TextList
onChange={this.onExcludeKeywordNameChanged}
onAdd={this.onExcludeKeywordAdd}
onRemove={this.onExcludeKeywordRemove}
value={this.state.excludeKeywordName}
values={keywords.get('exclude_keywords') || ImmutableList()}
icon='paragraph'
iconComponent={KeywordIcon}
label={intl.formatMessage(messages.addKeywordLabel)}
title={intl.formatMessage(messages.addKeywordTitle)}
/>
<h3><FormattedMessage id='antennas.exclude_tags' defaultMessage='Exclude tags' /></h3>
<TextList
onChange={this.onExcludeTagNameChanged}
onAdd={this.onExcludeTagAdd}
onRemove={this.onExcludeTagRemove}
value={this.state.excludeTagName}
values={tags.get('exclude_tags') || ImmutableList()}
icon='hashtag'
iconComponent={HashtagIcon}
label={intl.formatMessage(messages.addTagLabel)}
title={intl.formatMessage(messages.addTagTitle)}
/>
</>
)}
</div>
<Helmet>
<title>{title}</title>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}
}
export default withRouter(connect(mapStateToProps)(injectIntl(AntennaSetting)));

View file

@ -113,7 +113,7 @@ class AntennaTimeline extends PureComponent {
}; };
handleEditClick = () => { handleEditClick = () => {
this.props.history.push(`/antennasw/${this.props.params.id}`); this.props.history.push(`/antennas/${this.props.params.id}/edit`);
}; };
handleDeleteClick = () => { handleDeleteClick = () => {

View file

@ -1,373 +0,0 @@
import { useCallback, useState, useEffect, useRef } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet';
import { useParams, Link } from 'react-router-dom';
import { useDebouncedCallback } from 'use-debounce';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react';
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
import { fetchFollowing } from 'mastodon/actions/accounts';
import { importFetchedAccounts } from 'mastodon/actions/importer';
import { fetchList } from 'mastodon/actions/lists';
import { apiRequest } from 'mastodon/api';
import {
apiGetAccounts,
apiAddAccountToList,
apiRemoveAccountFromList,
} from 'mastodon/api/lists';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import { Avatar } from 'mastodon/components/avatar';
import { Button } from 'mastodon/components/button';
import Column from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header';
import { FollowersCounter } from 'mastodon/components/counters';
import { DisplayName } from 'mastodon/components/display_name';
import { Icon } from 'mastodon/components/icon';
import ScrollableList from 'mastodon/components/scrollable_list';
import { ShortNumber } from 'mastodon/components/short_number';
import { VerifiedBadge } from 'mastodon/components/verified_badge';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { me } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
const messages = defineMessages({
heading: { id: 'column.list_members', defaultMessage: 'Manage list members' },
placeholder: {
id: 'lists.search_placeholder',
defaultMessage: 'Search people you follow',
},
enterSearch: { id: 'lists.add_to_list', defaultMessage: 'Add to list' },
add: { id: 'lists.add_member', defaultMessage: 'Add' },
remove: { id: 'lists.remove_member', defaultMessage: 'Remove' },
back: { id: 'column_back_button.label', defaultMessage: 'Back' },
});
type Mode = 'remove' | 'add';
const ColumnSearchHeader: React.FC<{
onBack: () => void;
onSubmit: (value: string) => void;
}> = ({ onBack, onSubmit }) => {
const intl = useIntl();
const [value, setValue] = useState('');
const handleChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
setValue(value);
onSubmit(value);
},
[setValue, onSubmit],
);
const handleSubmit = useCallback(() => {
onSubmit(value);
}, [onSubmit, value]);
return (
<ButtonInTabsBar>
<form className='column-search-header' onSubmit={handleSubmit}>
<button
type='button'
className='column-header__back-button compact'
onClick={onBack}
aria-label={intl.formatMessage(messages.back)}
>
<Icon
id='chevron-left'
icon={ArrowBackIcon}
className='column-back-button__icon'
/>
</button>
<input
type='search'
value={value}
onChange={handleChange}
placeholder={intl.formatMessage(messages.placeholder)}
/* eslint-disable-next-line jsx-a11y/no-autofocus */
autoFocus
/>
</form>
</ButtonInTabsBar>
);
};
const AccountItem: React.FC<{
accountId: string;
listId: string;
partOfList: boolean;
onToggle: (accountId: string) => void;
}> = ({ accountId, listId, partOfList, onToggle }) => {
const intl = useIntl();
const account = useAppSelector((state) => state.accounts.get(accountId));
const handleClick = useCallback(() => {
if (partOfList) {
void apiRemoveAccountFromList(listId, accountId);
} else {
void apiAddAccountToList(listId, accountId);
}
onToggle(accountId);
}, [accountId, listId, partOfList, onToggle]);
if (!account) {
return null;
}
const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
return (
<div className='account'>
<div className='account__wrapper'>
<Link
key={account.id}
className='account__display-name'
title={account.acct}
to={`/@${account.acct}`}
data-hover-card-account={account.id}
>
<div className='account__avatar-wrapper'>
<Avatar account={account} size={36} />
</div>
<div className='account__contents'>
<DisplayName account={account} />
<div className='account__details'>
<ShortNumber
value={account.followers_count}
renderer={FollowersCounter}
/>{' '}
{firstVerifiedField && (
<VerifiedBadge link={firstVerifiedField.value} />
)}
</div>
</div>
</Link>
<div className='account__relationship'>
<Button
text={intl.formatMessage(
partOfList ? messages.remove : messages.add,
)}
onClick={handleClick}
/>
</div>
</div>
</div>
);
};
const ListMembers: React.FC<{
multiColumn?: boolean;
}> = ({ multiColumn }) => {
const dispatch = useAppDispatch();
const { id } = useParams<{ id: string }>();
const intl = useIntl();
const followingAccountIds = useAppSelector(
(state) => state.user_lists.getIn(['following', me, 'items']) as string[],
);
const [searching, setSearching] = useState(false);
const [accountIds, setAccountIds] = useState<string[]>([]);
const [searchAccountIds, setSearchAccountIds] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [mode, setMode] = useState<Mode>('remove');
useEffect(() => {
if (id) {
setLoading(true);
dispatch(fetchList(id));
void apiGetAccounts(id)
.then((data) => {
dispatch(importFetchedAccounts(data));
setAccountIds(data.map((a) => a.id));
setLoading(false);
return '';
})
.catch(() => {
setLoading(false);
});
dispatch(fetchFollowing(me));
}
}, [dispatch, id]);
const handleSearchClick = useCallback(() => {
setMode('add');
}, [setMode]);
const handleDismissSearchClick = useCallback(() => {
setMode('remove');
setSearching(false);
}, [setMode]);
const handleAccountToggle = useCallback(
(accountId: string) => {
const partOfList = accountIds.includes(accountId);
if (partOfList) {
setAccountIds(accountIds.filter((id) => id !== accountId));
} else {
setAccountIds([accountId, ...accountIds]);
}
},
[accountIds, setAccountIds],
);
const searchRequestRef = useRef<AbortController | null>(null);
const handleSearch = useDebouncedCallback(
(value: string) => {
if (searchRequestRef.current) {
searchRequestRef.current.abort();
}
if (value.trim().length === 0) {
setSearching(false);
return;
}
setLoading(true);
searchRequestRef.current = new AbortController();
void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
signal: searchRequestRef.current.signal,
params: {
q: value,
resolve: false,
following: true,
},
})
.then((data) => {
dispatch(importFetchedAccounts(data));
setSearchAccountIds(data.map((a) => a.id));
setLoading(false);
setSearching(true);
return '';
})
.catch(() => {
setSearching(true);
setLoading(false);
});
},
500,
{ leading: true, trailing: true },
);
let displayedAccountIds: string[];
if (mode === 'add') {
displayedAccountIds = searching ? searchAccountIds : followingAccountIds;
} else {
displayedAccountIds = accountIds;
}
return (
<Column
bindToDocument={!multiColumn}
label={intl.formatMessage(messages.heading)}
>
{mode === 'remove' ? (
<ColumnHeader
title={intl.formatMessage(messages.heading)}
icon='list-ul'
iconComponent={AntennaIcon}
multiColumn={multiColumn}
showBackButton
extraButton={
<button
onClick={handleSearchClick}
type='button'
className='column-header__button'
title={intl.formatMessage(messages.enterSearch)}
aria-label={intl.formatMessage(messages.enterSearch)}
>
<Icon id='plus' icon={AddIcon} />
</button>
}
/>
) : (
<ColumnSearchHeader
onBack={handleDismissSearchClick}
onSubmit={handleSearch}
/>
)}
<ScrollableList
scrollKey='list_members'
trackScroll={!multiColumn}
bindToDocument={!multiColumn}
isLoading={loading}
showLoading={loading && displayedAccountIds.length === 0}
hasMore={false}
footer={
mode === 'remove' && (
<>
{displayedAccountIds.length > 0 && <div className='spacer' />}
<div className='column-footer'>
<Link to={`/lists/${id}`} className='button button--block'>
<FormattedMessage id='lists.done' defaultMessage='Done' />
</Link>
</div>
</>
)
}
emptyMessage={
mode === 'remove' ? (
<>
<span>
<FormattedMessage
id='lists.no_members_yet'
defaultMessage='No members yet.'
/>
<br />
<FormattedMessage
id='lists.find_users_to_add'
defaultMessage='Find users to add'
/>
</span>
<SquigglyArrow className='empty-column-indicator__arrow' />
</>
) : (
<FormattedMessage
id='lists.no_results_found'
defaultMessage='No results found.'
/>
)
}
>
{displayedAccountIds.map((accountId) => (
<AccountItem
key={accountId}
accountId={accountId}
listId={id}
partOfList={
displayedAccountIds === accountIds ||
accountIds.includes(accountId)
}
onToggle={handleAccountToggle}
/>
))}
</ScrollableList>
<Helmet>
<title>{intl.formatMessage(messages.heading)}</title>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
};
// eslint-disable-next-line import/no-default-export
export default ListMembers;

View file

@ -7,27 +7,34 @@ import { useParams, Link } from 'react-router-dom';
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import DomainIcon from '@/material-icons/400-24px/dns.svg?react'; import DomainIcon from '@/material-icons/400-24px/dns.svg?react';
//import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import HashtagIcon from '@/material-icons/400-24px/tag.svg?react';
//import HashtagIcon from '@/material-icons/400-24px/tag.svg?react'; import KeywordIcon from '@/material-icons/400-24px/title.svg?react';
//import KeywordIcon from '@/material-icons/400-24px/title.svg?react';
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react'; import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
import { fetchAntenna } from 'mastodon/actions/antennas';
import { import {
fetchAntenna,
fetchAntennaAccounts,
fetchAntennaDomains,
fetchAntennaExcludeAccounts,
fetchAntennaKeywords,
fetchAntennaTags,
} from 'mastodon/actions/antennas';
import {
apiAddDomain,
apiGetAccounts, apiGetAccounts,
apiGetDomains,
apiAddDomain,
apiRemoveDomain, apiRemoveDomain,
apiGetTags,
apiAddTag,
apiRemoveTag,
apiGetKeywords,
apiAddKeyword,
apiRemoveKeyword,
apiAddExcludeDomain,
apiRemoveExcludeDomain,
apiAddExcludeTag,
apiRemoveExcludeTag,
apiAddExcludeKeyword,
apiRemoveExcludeKeyword,
apiGetExcludeAccounts,
} from 'mastodon/api/antennas'; } from 'mastodon/api/antennas';
import { Button } from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header'; import { ColumnHeader } from 'mastodon/components/column_header';
import type { IconProp , Icon } from 'mastodon/components/icon'; import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button'; import { IconButton } from 'mastodon/components/icon_button';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store';
@ -83,7 +90,9 @@ const TextListItem: React.FC<{
value: string; value: string;
onRemove: (value: string) => void; onRemove: (value: string) => void;
}> = ({ icon, iconComponent, value, onRemove }) => { }> = ({ icon, iconComponent, value, onRemove }) => {
const handleRemove = useCallback(() => { onRemove(value); }, [onRemove, value]); const handleRemove = useCallback(() => {
onRemove(value);
}, [onRemove, value]);
return ( return (
<div className='setting-text-list-item'> <div className='setting-text-list-item'>
@ -133,7 +142,9 @@ const TextList: React.FC<{
}, [onAdd, value]); }, [onAdd, value]);
const handleRemove = useCallback( const handleRemove = useCallback(
(removeValue: string) => { onRemove(removeValue); }, (removeValue: string) => {
onRemove(removeValue);
},
[onRemove], [onRemove],
); );
@ -141,16 +152,6 @@ const TextList: React.FC<{
return ( return (
<div className='setting-text-list'> <div className='setting-text-list'>
{values.map((val) => (
<TextListItem
key={val}
value={val}
icon={icon}
iconComponent={iconComponent}
onRemove={handleRemove}
/>
))}
<form className='add-text-form' onSubmit={handleSubmit}> <form className='add-text-form' onSubmit={handleSubmit}>
<label> <label>
<span style={{ display: 'none' }}>{label}</span> <span style={{ display: 'none' }}>{label}</span>
@ -170,6 +171,16 @@ const TextList: React.FC<{
onClick={handleAdd} onClick={handleAdd}
/> />
</form> </form>
{values.map((val) => (
<TextListItem
key={val}
value={val}
icon={icon}
iconComponent={iconComponent}
onRemove={handleRemove}
/>
))}
</div> </div>
); );
}; };
@ -185,25 +196,26 @@ const RadioPanel: React.FC<{
useEffect(() => { useEffect(() => {
if (valueLengths.length >= 2) { if (valueLengths.length >= 2) {
setError(valueLengths.slice(1).some((v) => v > 0)); setError(valueLengths.filter((v) => v > 0).length > 1);
} else { } else {
setError(false); setError(false);
} }
}, [valueLengths]); }, [valueLengths]);
useEffect(() => { useEffect(() => {
if (items.length > 0 && !items.some((i) => i.value === value)) { if (items.length > 0) {
for (let i = 0; i < valueLengths.length; i++) { for (let i = 0; i < valueLengths.length; i++) {
const length = valueLengths[i] ?? 0; const length = valueLengths[i] ?? 0;
const item = items[i] ?? { value: '' }; const item = items[i] ?? { value: '' };
if (length > 0) { if (length > 0) {
setValue(item.value); setValue(item.value);
onChange(item.value);
return; return;
} }
} }
setValue(items[0]?.value ?? ''); setValue(items[0]?.value ?? '');
} }
}, [items]); }, [items, valueLengths, setValue, onChange]);
const handleChange = useCallback( const handleChange = useCallback(
({ currentTarget }: React.MouseEvent<HTMLButtonElement>) => { ({ currentTarget }: React.MouseEvent<HTMLButtonElement>) => {
@ -213,7 +225,7 @@ const RadioPanel: React.FC<{
setValue(selected); setValue(selected);
} }
}, },
[setValue], [value, setValue, onChange],
); );
return ( return (
@ -240,26 +252,33 @@ const RadioPanel: React.FC<{
const MembersLink: React.FC<{ const MembersLink: React.FC<{
id: string; id: string;
onCountFetched: (count: number) => void; isExclude: boolean;
}> = ({ id, onCountFetched }) => { onCountFetched?: (count: number) => void;
}> = ({ id, isExclude, onCountFetched }) => {
const [count, setCount] = useState(0); const [count, setCount] = useState(0);
const [avatars, setAvatars] = useState<string[]>([]); const [avatars, setAvatars] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
void apiGetAccounts(id) const api = isExclude ? apiGetExcludeAccounts : apiGetAccounts;
void api(id)
.then((data) => { .then((data) => {
setCount(data.length); setCount(data.length);
onCountFetched(data.length); if (onCountFetched) {
onCountFetched(data.length);
}
setAvatars(data.slice(0, 3).map((a) => a.avatar)); setAvatars(data.slice(0, 3).map((a) => a.avatar));
return ''; return '';
}) })
.catch(() => { .catch(() => {
// Nothing // Nothing
}); });
}, [id, setCount, setAvatars]); }, [id, setCount, setAvatars, isExclude, onCountFetched]);
return ( return (
<Link to={`/antennas/${id}/members`} className='app-form__link'> <Link
to={`/antennas/${id}/${isExclude ? 'exclude_members' : 'members'}`}
className='app-form__link'
>
<div className='app-form__link__text'> <div className='app-form__link__text'>
<strong> <strong>
<FormattedMessage <FormattedMessage
@ -294,29 +313,48 @@ const AntennaSetting: React.FC<{
const antenna = useAppSelector((state) => const antenna = useAppSelector((state) =>
id ? state.antennas.get(id) : undefined, id ? state.antennas.get(id) : undefined,
); );
const domains = useAppSelector((state) =>
id ? state.antennas.getIn(['domains', id]) : undefined,
) as string[] | undefined;
const [domainList, setDomainList] = useState([] as string[]); const [domainList, setDomainList] = useState([] as string[]);
const [excludeDomainList, setExcludeDomainList] = useState([] as string[]);
const [tagList, setTagList] = useState([] as string[]);
const [excludeTagList, setExcludeTagList] = useState([] as string[]);
const [keywordList, setKeywordList] = useState([] as string[]);
const [excludeKeywordList, setExcludeKeywordList] = useState([] as string[]);
const [accountsCount, setAccountsCount] = useState(0); const [accountsCount, setAccountsCount] = useState(0);
const [rangeMode, setRangeMode] = useState('accounts'); const [rangeMode, setRangeMode] = useState('accounts');
const [contentMode, setContentMode] = useState('keywords');
useEffect(() => { useEffect(() => {
if (id) { if (id) {
dispatch(fetchAntenna(id)); dispatch(fetchAntenna(id));
dispatch(fetchAntennaKeywords(id));
dispatch(fetchAntennaDomains(id));
dispatch(fetchAntennaTags(id));
dispatch(fetchAntennaAccounts(id));
dispatch(fetchAntennaExcludeAccounts(id));
}
}, [dispatch, id]);
useEffect(() => { void apiGetDomains(id).then((data) => {
if (id && antenna) { setDomainList(data.domains);
setDomainList(domains ?? []); setExcludeDomainList(data.exclude_domains);
return true;
});
void apiGetTags(id).then((data) => {
setTagList(data.tags);
setExcludeTagList(data.exclude_tags);
return true;
});
void apiGetKeywords(id).then((data) => {
setKeywordList(data.keywords);
setExcludeKeywordList(data.exclude_keywords);
return true;
});
} }
}, [id, antenna, domains, setDomainList]); }, [
dispatch,
id,
setDomainList,
setExcludeDomainList,
setTagList,
setExcludeTagList,
setKeywordList,
setExcludeKeywordList,
]);
const handleAccountsFetched = useCallback( const handleAccountsFetched = useCallback(
(count: number) => { (count: number) => {
@ -349,15 +387,166 @@ const AntennaSetting: React.FC<{
[id, domainList, setDomainList], [id, domainList, setDomainList],
); );
const handleAddExcludeDomain = useCallback(
(value: string) => {
if (!id) return;
void apiAddExcludeDomain(id, value).then(() => {
setExcludeDomainList([...excludeDomainList, value]);
return value;
});
},
[id, excludeDomainList, setExcludeDomainList],
);
const handleRemoveExcludeDomain = useCallback(
(value: string) => {
if (!id) return;
void apiRemoveExcludeDomain(id, value).then(() => {
setExcludeDomainList(excludeDomainList.filter((v) => v !== value));
return value;
});
},
[id, excludeDomainList, setExcludeDomainList],
);
const handleAddTag = useCallback(
(value: string) => {
if (!id) return;
void apiAddTag(id, value).then(() => {
setTagList([...tagList, value]);
return value;
});
},
[id, tagList, setTagList],
);
const handleRemoveTag = useCallback(
(value: string) => {
if (!id) return;
void apiRemoveTag(id, value).then(() => {
setTagList(tagList.filter((v) => v !== value));
return value;
});
},
[id, tagList, setTagList],
);
const handleAddExcludeTag = useCallback(
(value: string) => {
if (!id) return;
void apiAddExcludeTag(id, value).then(() => {
setExcludeTagList([...excludeTagList, value]);
return value;
});
},
[id, excludeTagList, setExcludeTagList],
);
const handleRemoveExcludeTag = useCallback(
(value: string) => {
if (!id) return;
void apiRemoveExcludeTag(id, value).then(() => {
setExcludeTagList(excludeTagList.filter((v) => v !== value));
return value;
});
},
[id, excludeTagList, setExcludeTagList],
);
const handleAddKeyword = useCallback(
(value: string) => {
if (!id) return;
void apiAddKeyword(id, value).then(() => {
setKeywordList([...keywordList, value]);
return value;
});
},
[id, keywordList, setKeywordList],
);
const handleRemoveKeyword = useCallback(
(value: string) => {
if (!id) return;
void apiRemoveKeyword(id, value).then(() => {
setKeywordList(keywordList.filter((v) => v !== value));
return value;
});
},
[id, keywordList, setKeywordList],
);
const handleAddExcludeKeyword = useCallback(
(value: string) => {
if (!id) return;
void apiAddExcludeKeyword(id, value).then(() => {
setExcludeKeywordList([...excludeKeywordList, value]);
return value;
});
},
[id, excludeKeywordList, setExcludeKeywordList],
);
const handleRemoveExcludeKeyword = useCallback(
(value: string) => {
if (!id) return;
void apiRemoveExcludeKeyword(id, value).then(() => {
setExcludeKeywordList(excludeKeywordList.filter((v) => v !== value));
return value;
});
},
[id, excludeKeywordList, setExcludeKeywordList],
);
const handleRangeRadioChange = useCallback( const handleRangeRadioChange = useCallback(
(value: string) => { (value: string) => {
setRangeMode(value); setRangeMode(value);
}, },
[id, antenna, setRangeMode], [setRangeMode],
);
const handleContentRadioChange = useCallback(
(value: string) => {
setContentMode(value);
},
[setContentMode],
); );
if (!antenna || !id) return <div />; if (!antenna || !id) return <div />;
if (antenna.stl)
return (
<div className='antenna-setting'>
<p>
<FormattedMessage
id='antennas.in_stl_mode'
defaultMessage='This antenna is in STL mode.'
/>
</p>
</div>
);
if (antenna.ltl)
return (
<div className='antenna-setting'>
<p>
<FormattedMessage
id='antennas.in_ltl_mode'
defaultMessage='This antenna is in LTL mode.'
/>
</p>
</div>
);
const rangeRadioItems = [ const rangeRadioItems = [
{ {
value: 'accounts', value: 'accounts',
@ -368,7 +557,21 @@ const AntennaSetting: React.FC<{
title: intl.formatMessage(messages.domains, { count: domainList.length }), title: intl.formatMessage(messages.domains, { count: domainList.length }),
}, },
]; ];
const rangeRadioLengths = [0, domainList.length]; const rangeRadioLengths = [accountsCount, domainList.length];
const contentRadioItems = [
{
value: 'keywords',
title: intl.formatMessage(messages.keywords, {
count: keywordList.length,
}),
},
{
value: 'tags',
title: intl.formatMessage(messages.tags, { count: tagList.length }),
},
];
const contentRadioLengths = [keywordList.length, tagList.length];
return ( return (
<Column bindToDocument={!multiColumn} label={antenna.title}> <Column bindToDocument={!multiColumn} label={antenna.title}>
@ -395,7 +598,11 @@ const AntennaSetting: React.FC<{
onChange={handleRangeRadioChange} onChange={handleRangeRadioChange}
/> />
{rangeMode === 'accounts' && ( {rangeMode === 'accounts' && (
<MembersLink id={id} onCountFetched={handleAccountsFetched} /> <MembersLink
id={id}
onCountFetched={handleAccountsFetched}
isExclude={false}
/>
)} )}
{rangeMode === 'domains' && ( {rangeMode === 'domains' && (
<TextList <TextList
@ -408,6 +615,101 @@ const AntennaSetting: React.FC<{
onRemove={handleRemoveDomain} onRemove={handleRemoveDomain}
/> />
)} )}
<RadioPanel
items={contentRadioItems}
valueLengths={contentRadioLengths}
alertMessage={
<div className='alert'>
<FormattedMessage
id='antennas.warnings.content_radio'
defaultMessage='Simultaneous keyword and tag designation is not recommended.'
/>
</div>
}
onChange={handleContentRadioChange}
/>
{contentMode === 'keywords' && (
<TextList
values={keywordList}
icon='paragraph'
iconComponent={KeywordIcon}
label={intl.formatMessage(messages.addKeywordLabel)}
title={intl.formatMessage(messages.addKeywordTitle)}
onAdd={handleAddKeyword}
onRemove={handleRemoveKeyword}
/>
)}
{contentMode === 'tags' && (
<TextList
values={tagList}
icon='hashtag'
iconComponent={HashtagIcon}
label={intl.formatMessage(messages.addTagLabel)}
title={intl.formatMessage(messages.addTagTitle)}
onAdd={handleAddTag}
onRemove={handleRemoveTag}
/>
)}
<h2>
<FormattedMessage
id='antennas.filter_not'
defaultMessage='Filter Not'
/>
</h2>
<h3>
<FormattedMessage
id='antennas.exclude_accounts'
defaultMessage='Exclude accounts'
/>
</h3>
<MembersLink id={id} isExclude />
<h3>
<FormattedMessage
id='antennas.exclude_domains'
defaultMessage='Exclude domains'
/>
</h3>
<TextList
values={excludeDomainList}
icon='sitemap'
iconComponent={DomainIcon}
label={intl.formatMessage(messages.addDomainLabel)}
title={intl.formatMessage(messages.addDomainTitle)}
onAdd={handleAddExcludeDomain}
onRemove={handleRemoveExcludeDomain}
/>
<h3>
<FormattedMessage
id='antennas.exclude_keywords'
defaultMessage='Exclude keywords'
/>
</h3>
<TextList
values={excludeKeywordList}
icon='paragraph'
iconComponent={KeywordIcon}
label={intl.formatMessage(messages.addKeywordLabel)}
title={intl.formatMessage(messages.addKeywordTitle)}
onAdd={handleAddExcludeKeyword}
onRemove={handleRemoveExcludeKeyword}
/>
<h3>
<FormattedMessage
id='antennas.exclude_tags'
defaultMessage='Exclude tags'
/>
</h3>
<TextList
values={excludeTagList}
icon='paragraph'
iconComponent={HashtagIcon}
label={intl.formatMessage(messages.addTagLabel)}
title={intl.formatMessage(messages.addTagTitle)}
onAdd={handleAddExcludeTag}
onRemove={handleRemoveExcludeTag}
/>
</div> </div>
</Column> </Column>
); );

View file

@ -34,14 +34,15 @@ const AntennaItem: React.FC<{
title: string; title: string;
insert_feeds: boolean; insert_feeds: boolean;
isList: boolean; isList: boolean;
}> = ({ id, title, insert_feeds, isList }) => { listTitle?: string;
}> = ({ id, title, insert_feeds, isList, listTitle }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const intl = useIntl(); const intl = useIntl();
const handleDeleteClick = useCallback(() => { const handleDeleteClick = useCallback(() => {
dispatch( dispatch(
openModal({ openModal({
modalType: 'CONFIRM_DELETE_LIST', modalType: 'CONFIRM_DELETE_ANTENNA',
modalProps: { modalProps: {
antennaId: id, antennaId: id,
}, },
@ -58,15 +59,18 @@ const AntennaItem: React.FC<{
); );
return ( return (
<div className='antennas__item'> <div className='lists__item'>
<Link to={`/antennas/${id}`} className='antennas__item__title'> <Link to={`/antennas/${id}`} className='lists__item__title'>
<Icon id='antenna-ul' icon={AntennaIcon} /> <Icon id='antenna-ul' icon={AntennaIcon} />
<span>{title}</span> <span>{title}</span>
{insert_feeds {insert_feeds ? (
? intl.formatMessage( <span className='column-link__badge'>
isList ? messages.insert_list : messages.insert_home, {isList
) ? (listTitle?.slice(0, 4) ??
: undefined} intl.formatMessage(messages.insert_list))
: intl.formatMessage(messages.insert_home)}
</span>
) : undefined}
</Link> </Link>
<DropdownMenuContainer <DropdownMenuContainer
@ -144,6 +148,7 @@ const Antennas: React.FC<{
title={antenna.title} title={antenna.title}
insert_feeds={antenna.insert_feeds} insert_feeds={antenna.insert_feeds}
isList={!!antenna.list} isList={!!antenna.list}
listTitle={antenna.list?.title}
/> />
))} ))}
</ScrollableList> </ScrollableList>

View file

@ -19,6 +19,9 @@ import {
apiGetAccounts, apiGetAccounts,
apiAddAccountToAntenna, apiAddAccountToAntenna,
apiRemoveAccountFromAntenna, apiRemoveAccountFromAntenna,
apiRemoveExcludeAccountFromAntenna,
apiAddExcludeAccountToAntenna,
apiGetExcludeAccounts,
} from 'mastodon/api/antennas'; } from 'mastodon/api/antennas';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import { Avatar } from 'mastodon/components/avatar'; import { Avatar } from 'mastodon/components/avatar';
@ -107,20 +110,27 @@ const AccountItem: React.FC<{
accountId: string; accountId: string;
antennaId: string; antennaId: string;
partOfAntenna: boolean; partOfAntenna: boolean;
isExclude?: boolean;
onToggle: (accountId: string) => void; onToggle: (accountId: string) => void;
}> = ({ accountId, antennaId, partOfAntenna, onToggle }) => { }> = ({ accountId, antennaId, partOfAntenna, isExclude, onToggle }) => {
const intl = useIntl(); const intl = useIntl();
const account = useAppSelector((state) => state.accounts.get(accountId)); const account = useAppSelector((state) => state.accounts.get(accountId));
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
if (partOfAntenna) { if (partOfAntenna) {
void apiRemoveAccountFromAntenna(antennaId, accountId); const api = isExclude
? apiRemoveExcludeAccountFromAntenna
: apiRemoveAccountFromAntenna;
void api(antennaId, accountId);
} else { } else {
void apiAddAccountToAntenna(antennaId, accountId); const api = isExclude
? apiAddExcludeAccountToAntenna
: apiAddAccountToAntenna;
void api(antennaId, accountId);
} }
onToggle(accountId); onToggle(accountId);
}, [accountId, antennaId, partOfAntenna, onToggle]); }, [accountId, antennaId, partOfAntenna, onToggle, isExclude]);
if (!account) { if (!account) {
return null; return null;
@ -171,8 +181,9 @@ const AccountItem: React.FC<{
}; };
const AntennaMembers: React.FC<{ const AntennaMembers: React.FC<{
isExclude?: boolean;
multiColumn?: boolean; multiColumn?: boolean;
}> = ({ multiColumn }) => { }> = ({ isExclude, multiColumn }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const intl = useIntl(); const intl = useIntl();
@ -188,7 +199,8 @@ const AntennaMembers: React.FC<{
setLoading(true); setLoading(true);
dispatch(fetchAntenna(id)); dispatch(fetchAntenna(id));
void apiGetAccounts(id) const api = isExclude ? apiGetExcludeAccounts : apiGetAccounts;
void api(id)
.then((data) => { .then((data) => {
dispatch(importFetchedAccounts(data)); dispatch(importFetchedAccounts(data));
setAccountIds(data.map((a) => a.id)); setAccountIds(data.map((a) => a.id));
@ -201,7 +213,7 @@ const AntennaMembers: React.FC<{
dispatch(fetchFollowing(me)); dispatch(fetchFollowing(me));
} }
}, [dispatch, id]); }, [dispatch, id, isExclude]);
const handleSearchClick = useCallback(() => { const handleSearchClick = useCallback(() => {
setMode('add'); setMode('add');
@ -317,7 +329,10 @@ const AntennaMembers: React.FC<{
{displayedAccountIds.length > 0 && <div className='spacer' />} {displayedAccountIds.length > 0 && <div className='spacer' />}
<div className='column-footer'> <div className='column-footer'>
<Link to={`/antennasw/${id}`} className='button button--block'> <Link
to={`/antennas/${id}/filtering`}
className='button button--block'
>
<FormattedMessage id='antennas.done' defaultMessage='Done' /> <FormattedMessage id='antennas.done' defaultMessage='Done' />
</Link> </Link>
</div> </div>
@ -358,6 +373,7 @@ const AntennaMembers: React.FC<{
displayedAccountIds === accountIds || displayedAccountIds === accountIds ||
accountIds.includes(accountId) accountIds.includes(accountId)
} }
isExclude={isExclude}
onToggle={handleAccountToggle} onToggle={handleAccountToggle}
/> />
))} ))}

View file

@ -16,6 +16,7 @@ import { fetchLists } from 'mastodon/actions/lists';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header'; import { ColumnHeader } from 'mastodon/components/column_header';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { getOrderedLists } from 'mastodon/selectors/lists';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store';
const messages = defineMessages({ const messages = defineMessages({
@ -27,12 +28,12 @@ const FiltersLink: React.FC<{
id: string; id: string;
}> = ({ id }) => { }> = ({ id }) => {
return ( return (
<Link to={`/antennasw/${id}`} className='app-form__link'> <Link to={`/antennas/${id}/filtering`} className='app-form__link'>
<div className='app-form__link__text'> <div className='app-form__link__text'>
<strong> <strong>
<FormattedMessage <FormattedMessage
id='antennas.filter_items' id='antennas.filter_items'
defaultMessage='Antenna filtering' defaultMessage='Antenna filter settings'
/> />
</strong> </strong>
</div> </div>
@ -51,7 +52,7 @@ const NewAntenna: React.FC<{
const antenna = useAppSelector((state) => const antenna = useAppSelector((state) =>
id ? state.antennas.get(id) : undefined, id ? state.antennas.get(id) : undefined,
); );
const lists = useAppSelector((state) => state.lists); const lists = useAppSelector((state) => getOrderedLists(state));
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [stl, setStl] = useState(false); const [stl, setStl] = useState(false);
const [ltl, setLtl] = useState(false); const [ltl, setLtl] = useState(false);
@ -59,6 +60,8 @@ const NewAntenna: React.FC<{
const [listId, setListId] = useState(''); const [listId, setListId] = useState('');
const [withMediaOnly, setWithMediaOnly] = useState(false); const [withMediaOnly, setWithMediaOnly] = useState(false);
const [ignoreReblog, setIgnoreReblog] = useState(false); const [ignoreReblog, setIgnoreReblog] = useState(false);
const [mode, setMode] = useState('filtering');
const [destination, setDestination] = useState('home');
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
useEffect(() => { useEffect(() => {
@ -74,9 +77,27 @@ const NewAntenna: React.FC<{
setStl(antenna.stl); setStl(antenna.stl);
setLtl(antenna.ltl); setLtl(antenna.ltl);
setInsertFeeds(antenna.insert_feeds); setInsertFeeds(antenna.insert_feeds);
setListId(antenna.list?.id ?? ''); setListId(antenna.list?.id ?? '0');
setWithMediaOnly(antenna.with_media_only); setWithMediaOnly(antenna.with_media_only);
setIgnoreReblog(antenna.ignore_reblog); setIgnoreReblog(antenna.ignore_reblog);
if (antenna.stl) {
setMode('stl');
} else if (antenna.ltl) {
setMode('ltl');
} else {
setMode('filtering');
}
if (antenna.insert_feeds) {
if (antenna.list) {
setDestination('list');
} else {
setDestination('home');
}
} else {
setDestination('timeline');
}
} }
}, [ }, [
setTitle, setTitle,
@ -86,6 +107,8 @@ const NewAntenna: React.FC<{
setListId, setListId,
setWithMediaOnly, setWithMediaOnly,
setIgnoreReblog, setIgnoreReblog,
setMode,
setDestination,
id, id,
antenna, antenna,
lists, lists,
@ -98,29 +121,6 @@ const NewAntenna: React.FC<{
[setTitle], [setTitle],
); );
/*
const handleStlChange = useCallback(
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
setStl(checked);
},
[setStl],
);
const handleLtlChange = useCallback(
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
setLtl(checked);
},
[setLtl],
);
*/
const handleInsertFeedsChange = useCallback(
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
setInsertFeeds(checked);
},
[setInsertFeeds],
);
const handleListIdChange = useCallback( const handleListIdChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => { ({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
setListId(value); setListId(value);
@ -128,7 +128,43 @@ const NewAntenna: React.FC<{
[setListId], [setListId],
); );
/* const handleModeChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
if (value === 'stl') {
setStl(true);
setLtl(false);
} else if (value === 'ltl') {
setStl(false);
setLtl(true);
} else if (value === 'filtering') {
setStl(false);
setLtl(false);
}
setMode(value);
},
[setLtl, setStl, setMode, listId, setListId, lists],
);
const handleDestinationChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
if (value === 'list') {
setInsertFeeds(true);
if (listId === '0' && lists.length > 0) {
setListId(lists[0]?.id ?? '0');
}
} else if (value === 'home') {
setInsertFeeds(true);
// listId = 0
} else if (value === 'timeline') {
setInsertFeeds(false);
}
setDestination(value);
},
[setDestination, setListId, listId, lists],
);
const handleWithMediaOnlyChange = useCallback( const handleWithMediaOnlyChange = useCallback(
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => { ({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
setWithMediaOnly(checked); setWithMediaOnly(checked);
@ -142,7 +178,6 @@ const NewAntenna: React.FC<{
}, },
[setIgnoreReblog], [setIgnoreReblog],
); );
*/
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
setSubmitting(true); setSubmitting(true);
@ -155,7 +190,7 @@ const NewAntenna: React.FC<{
stl, stl,
ltl, ltl,
insert_feeds: insertFeeds, insert_feeds: insertFeeds,
list_id: listId, list_id: destination === 'list' ? listId : '0',
with_media_only: withMediaOnly, with_media_only: withMediaOnly,
ignore_reblog: ignoreReblog, ignore_reblog: ignoreReblog,
}), }),
@ -170,7 +205,7 @@ const NewAntenna: React.FC<{
stl, stl,
ltl, ltl,
insert_feeds: insertFeeds, insert_feeds: insertFeeds,
list_id: listId, list_id: destination === 'list' ? listId : '0',
with_media_only: withMediaOnly, with_media_only: withMediaOnly,
ignore_reblog: ignoreReblog, ignore_reblog: ignoreReblog,
}), }),
@ -197,6 +232,7 @@ const NewAntenna: React.FC<{
listId, listId,
withMediaOnly, withMediaOnly,
ignoreReblog, ignoreReblog,
destination,
]); ]);
return ( return (
@ -240,32 +276,40 @@ const NewAntenna: React.FC<{
</div> </div>
<div className='fields-group'> <div className='fields-group'>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} <div className='input with_label'>
<label className='app-form__toggle'> <div className='label_input'>
<div className='app-form__toggle__label'> <label htmlFor='antenna_list'>
<strong> <FormattedMessage id='antennas.mode' defaultMessage='Mode' />
<FormattedMessage </label>
id='antennas.insert_feeds'
defaultMessage='Insert to feeds'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='antennas.insert_feeds_hint'
defaultMessage='Insert to any timelines.'
/>
</span>
</div>
<div className='app-form__toggle__toggle'> <div className='label_input__wrapper'>
<div> <select
<Toggle id='antenna_insert_list'
checked={insertFeeds} value={mode}
onChange={handleInsertFeedsChange} onChange={handleModeChange}
/> >
<FormattedMessage
id='antennas.mode.stl'
defaultMessage='Social timeline mode'
>
{(msg) => <option value='stl'>{msg}</option>}
</FormattedMessage>
<FormattedMessage
id='antennas.mode.ltl'
defaultMessage='Local timeline mode'
>
{(msg) => <option value='ltl'>{msg}</option>}
</FormattedMessage>
<FormattedMessage
id='antennas.mode.filtering'
defaultMessage='Filtering'
>
{(msg) => <option value='filtering'>{msg}</option>}
</FormattedMessage>
</select>
</div> </div>
</div> </div>
</label> </div>
</div> </div>
<div className='fields-group'> <div className='fields-group'>
@ -273,38 +317,136 @@ const NewAntenna: React.FC<{
<div className='label_input'> <div className='label_input'>
<label htmlFor='antenna_list'> <label htmlFor='antenna_list'>
<FormattedMessage <FormattedMessage
id='antennas.insert_list' id='antennas.destination'
defaultMessage='List' defaultMessage='Destination'
/> />
</label> </label>
<div className='label_input__wrapper'> <div className='label_input__wrapper'>
<select <select
id='antenna_insert_list' id='antenna_insert_destination'
value={listId} value={destination}
onChange={handleListIdChange} onChange={handleDestinationChange}
> >
<option value=''>Home</option> <FormattedMessage
{lists.forEach( id='antennas.destination.home'
(list) => defaultMessage='Insert to home'
list !== null && ( >
<option key={list.id} value={list.id}> {(msg) => <option value='home'>{msg}</option>}
{list.title} </FormattedMessage>
</option> <FormattedMessage
), id='antennas.destination.list'
)} defaultMessage='Insert to list'
>
{(msg) => <option value='list'>{msg}</option>}
</FormattedMessage>
<FormattedMessage
id='antennas.destination.timeline'
defaultMessage='Antenna timeline only'
>
{(msg) => <option value='timeline'>{msg}</option>}
</FormattedMessage>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{id && ( {destination === 'list' && (
<div className='fields-group'> <div className='fields-group'>
<FiltersLink id={id} /> <div className='input with_label'>
<div className='label_input'>
<label htmlFor='antenna_list'>
<FormattedMessage
id='antennas.insert_list'
defaultMessage='List'
/>
</label>
<div className='label_input__wrapper'>
<select
id='antenna_insert_list'
value={listId}
onChange={handleListIdChange}
>
{lists.map((list) => (
<option key={list.id} value={list.id}>
{list.title}
</option>
))}
</select>
</div>
</div>
</div>
</div> </div>
)} )}
{id && mode === 'filtering' && (
<>
<div className='fields-group'>
<FiltersLink id={id} />
</div>
<div className='fields-group'>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong>
<FormattedMessage
id='antennas.media_only'
defaultMessage='Media only'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='antennas.media_only_hint'
defaultMessage='Only posts with media will be added antenna.'
/>
</span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle
checked={withMediaOnly}
onChange={handleWithMediaOnlyChange}
/>
</div>
</div>
</label>
</div>
<div className='fields-group'>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong>
<FormattedMessage
id='antennas.ignore_reblog'
defaultMessage='Exclude boosts'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='antennas.ignore_reblog_hint'
defaultMessage='Boosts will be excluded from antenna detection.'
/>
</span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle
checked={ignoreReblog}
onChange={handleIgnoreReblogChange}
/>
</div>
</div>
</label>
</div>
</>
)}
<div className='actions'> <div className='actions'>
<button className='button' type='submit'> <button className='button' type='submit'>
{submitting ? ( {submitting ? (

View file

@ -26,15 +26,15 @@ const messages = defineMessages({
}, },
create: { create: {
id: 'bookmark_categories.create_bookmark_category', id: 'bookmark_categories.create_bookmark_category',
defaultMessage: 'Create bookmark_category', defaultMessage: 'Create category',
}, },
edit: { edit: {
id: 'bookmark_categories.edit', id: 'bookmark_categories.edit',
defaultMessage: 'Edit bookmark_category', defaultMessage: 'Edit category',
}, },
delete: { delete: {
id: 'bookmark_categories.delete', id: 'bookmark_categories.delete',
defaultMessage: 'Delete bookmark_category', defaultMessage: 'Delete category',
}, },
more: { id: 'status.more', defaultMessage: 'More' }, more: { id: 'status.more', defaultMessage: 'More' },
}); });
@ -49,7 +49,7 @@ const BookmarkCategoryItem: React.FC<{
const handleDeleteClick = useCallback(() => { const handleDeleteClick = useCallback(() => {
dispatch( dispatch(
openModal({ openModal({
modalType: 'CONFIRM_DELETE_LIST', modalType: 'CONFIRM_DELETE_BOOKMARK_CATEGORY',
modalProps: { modalProps: {
bookmark_categoryId: id, bookmark_categoryId: id,
}, },

View file

@ -1,83 +0,0 @@
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { removeFromCircleEditor, addToCircleEditor } from '../../../actions/circles';
import { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name';
import { IconButton } from '../../../components/icon_button';
import { makeGetAccount } from '../../../selectors';
const messages = defineMessages({
remove: { id: 'circles.account.remove', defaultMessage: 'Remove from circle' },
add: { id: 'circles.account.add', defaultMessage: 'Add to circle' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = (state, { accountId, added }) => ({
account: getAccount(state, accountId),
added: typeof added === 'undefined' ? state.getIn(['circleEditor', 'accounts', 'items']).includes(accountId) : added,
});
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { accountId }) => ({
onRemove: () => dispatch(removeFromCircleEditor(accountId)),
onAdd: () => dispatch(addToCircleEditor(accountId)),
});
class Account extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
onRemove: PropTypes.func.isRequired,
onAdd: PropTypes.func.isRequired,
added: PropTypes.bool,
};
static defaultProps = {
added: false,
};
render () {
const { account, intl, onRemove, onAdd, added } = this.props;
let button;
if (added) {
button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
} else {
button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
}
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 className='account__relationship'>
{button}
</div>
</div>
</div>
);
}
}
export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account));

View file

@ -1,76 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { changeCircleEditorTitle, submitCircleEditor } from '../../../actions/circles';
import { IconButton } from '../../../components/icon_button';
const messages = defineMessages({
title: { id: 'circles.edit.submit', defaultMessage: 'Change title' },
});
const mapStateToProps = state => ({
value: state.getIn(['circleEditor', 'title']),
disabled: !state.getIn(['circleEditor', 'isChanged']) || !state.getIn(['circleEditor', 'title']),
});
const mapDispatchToProps = dispatch => ({
onChange: value => dispatch(changeCircleEditorTitle(value)),
onSubmit: () => dispatch(submitCircleEditor(false)),
});
class CircleForm extends PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
disabled: PropTypes.bool,
intl: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};
handleChange = e => {
this.props.onChange(e.target.value);
};
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit();
};
handleClick = () => {
this.props.onSubmit();
};
render () {
const { value, disabled, intl } = this.props;
const title = intl.formatMessage(messages.title);
return (
<form className='column-inline-form' onSubmit={this.handleSubmit}>
<input
className='setting-text'
value={value}
onChange={this.handleChange}
/>
<IconButton
disabled={disabled}
icon='check'
iconComponent={CheckIcon}
title={title}
onClick={this.handleClick}
/>
</form>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(CircleForm));

View file

@ -1,83 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import classNames from 'classnames';
import { connect } from 'react-redux';
import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon';
import { fetchCircleSuggestions, clearCircleSuggestions, changeCircleSuggestions } from '../../../actions/circles';
const messages = defineMessages({
search: { id: 'circles.search', defaultMessage: 'Search among people follow you' },
});
const mapStateToProps = state => ({
value: state.getIn(['circleEditor', 'suggestions', 'value']),
});
const mapDispatchToProps = dispatch => ({
onSubmit: value => dispatch(fetchCircleSuggestions(value)),
onClear: () => dispatch(clearCircleSuggestions()),
onChange: value => dispatch(changeCircleSuggestions(value)),
});
class Search extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
};
handleChange = e => {
this.props.onChange(e.target.value);
};
handleKeyUp = e => {
if (e.keyCode === 13) {
this.props.onSubmit(this.props.value);
}
};
handleClear = () => {
this.props.onClear();
};
render () {
const { value, intl } = this.props;
const hasValue = value.length > 0;
return (
<div className='list-editor__search search'>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
<input
className='search__input'
type='text'
value={value}
onChange={this.handleChange}
onKeyUp={this.handleKeyUp}
placeholder={intl.formatMessage(messages.search)}
/>
</label>
<div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
<Icon id='search' icon={SearchIcon} className={classNames({ active: !hasValue })} />
<Icon id='times-circle' icon={CancelIcon} aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search));

View file

@ -1,83 +0,0 @@
import PropTypes from 'prop-types';
import { injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import spring from 'react-motion/lib/spring';
import { setupCircleEditor, clearCircleSuggestions, resetCircleEditor } from '../../actions/circles';
import Motion from '../ui/util/optional_motion';
import Account from './components/account';
import EditCircleForm from './components/edit_circle_form';
import Search from './components/search';
const mapStateToProps = state => ({
accountIds: state.getIn(['circleEditor', 'accounts', 'items']),
searchAccountIds: state.getIn(['circleEditor', 'suggestions', 'items']),
});
const mapDispatchToProps = dispatch => ({
onInitialize: circleId => dispatch(setupCircleEditor(circleId)),
onClear: () => dispatch(clearCircleSuggestions()),
onReset: () => dispatch(resetCircleEditor()),
});
class CircleEditor extends ImmutablePureComponent {
static propTypes = {
circleId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
onInitialize: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list.isRequired,
searchAccountIds: ImmutablePropTypes.list.isRequired,
};
componentDidMount () {
const { onInitialize, circleId } = this.props;
onInitialize(circleId);
}
componentWillUnmount () {
const { onReset } = this.props;
onReset();
}
render () {
const { accountIds, searchAccountIds, onClear } = this.props;
const showSearch = searchAccountIds.size > 0;
return (
<div className='modal-root__modal list-editor'>
<EditCircleForm />
<Search />
<div className='drawer__pager'>
<div className='drawer__inner list-editor__accounts'>
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
</div>
{showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
</div>
)}
</Motion>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(CircleEditor));

View file

@ -84,10 +84,7 @@ class CircleStatuses extends ImmutablePureComponent {
}; };
handleEditClick = () => { handleEditClick = () => {
this.props.dispatch(openModal({ this.props.history.push(`/circles/${this.props.params.id}/edit`);
modalType: 'CIRCLE_EDITOR',
modalProps: { circleId: this.props.params.id },
}));
}; };
handleDeleteClick = () => { handleDeleteClick = () => {

View file

@ -37,7 +37,7 @@ const CircleItem: React.FC<{
const handleDeleteClick = useCallback(() => { const handleDeleteClick = useCallback(() => {
dispatch( dispatch(
openModal({ openModal({
modalType: 'CONFIRM_DELETE_LIST', modalType: 'CONFIRM_DELETE_CIRCLE',
modalProps: { modalProps: {
circleId: id, circleId: id,
}, },

View file

@ -145,7 +145,7 @@ class GettingStarted extends ImmutablePureComponent {
<ColumnLink key='bookmark' icon='bookmarks' iconComponent={BookmarksIcon} text={intl.formatMessage(messages.bookmarks)} to='/bookmark_categories' />, <ColumnLink key='bookmark' icon='bookmarks' iconComponent={BookmarksIcon} text={intl.formatMessage(messages.bookmarks)} to='/bookmark_categories' />,
<ColumnLink key='favourites' icon='star' iconComponent={StarIcon} text={intl.formatMessage(messages.favourites)} to='/favourites' />, <ColumnLink key='favourites' icon='star' iconComponent={StarIcon} text={intl.formatMessage(messages.favourites)} to='/favourites' />,
<ColumnLink key='lists' icon='list-ul' iconComponent={ListAltIcon} text={intl.formatMessage(messages.lists)} to='/lists' />, <ColumnLink key='lists' icon='list-ul' iconComponent={ListAltIcon} text={intl.formatMessage(messages.lists)} to='/lists' />,
<ColumnLink key='antennas' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} to='/antennasw' />, <ColumnLink key='antennas' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} to='/antennas' />,
<ColumnLink key='circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} to='/circles' />, <ColumnLink key='circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} to='/circles' />,
); );

View file

@ -115,11 +115,17 @@ class ListTimeline extends PureComponent {
dispatch(openModal({ modalType: 'CONFIRM_DELETE_LIST', modalProps: { listId: id, columnId } })); dispatch(openModal({ modalType: 'CONFIRM_DELETE_LIST', modalProps: { listId: id, columnId } }));
}; };
handleEditAntennaClick = (e) => {
const id = e.currentTarget.getAttribute('data-id');
this.props.history.push(`/antennas/${id}/edit`);
};
render () { render () {
const { hasUnread, columnId, multiColumn, list } = this.props; const { hasUnread, columnId, multiColumn, list } = this.props;
const { id } = this.props.params; const { id } = this.props.params;
const pinned = !!columnId; const pinned = !!columnId;
const title = list ? list.get('title') : id; const title = list ? list.get('title') : id;
const antennas = list ? (list.get('antennas') ?? []) : [];
if (typeof list === 'undefined') { if (typeof list === 'undefined') {
return ( return (
@ -158,6 +164,22 @@ class ListTimeline extends PureComponent {
<Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' /> <Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
</button> </button>
</section> </section>
{ antennas.length > 0 && (
<section aria-labelledby={`list-${id}-antenna`}>
<h3><FormattedMessage id='lists.antennas' defaultMessage='Related antennas:' /></h3>
<ul className='column-settings__row'>
{ antennas.map(antenna => (
<li key={antenna.id} className='column-settings__row__antenna'>
<button type='button' className='text-btn column-header__setting-btn' data-id={antenna.id} onClick={this.handleEditAntennaClick}>
<Icon id='pencil' icon={EditIcon} /> {antenna.title}{antenna.stl && ' [STL]'}{antenna.ltl && ' [LTL]'}
</button>
</li>
))}
</ul>
</section>
)}
</div> </div>
</ColumnHeader> </ColumnHeader>

View file

@ -30,7 +30,8 @@ const messages = defineMessages({
const ListItem: React.FC<{ const ListItem: React.FC<{
id: string; id: string;
title: string; title: string;
}> = ({ id, title }) => { antennaTitles?: string[];
}> = ({ id, title, antennaTitles }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const intl = useIntl(); const intl = useIntl();
@ -58,6 +59,11 @@ const ListItem: React.FC<{
<Link to={`/lists/${id}`} className='lists__item__title'> <Link to={`/lists/${id}`} className='lists__item__title'>
<Icon id='list-ul' icon={ListAltIcon} /> <Icon id='list-ul' icon={ListAltIcon} />
<span>{title}</span> <span>{title}</span>
{antennaTitles?.map((at) => (
<span key={at} className='column-link__badge'>
{at.slice(0, 4)}
</span>
))}
</Link> </Link>
<DropdownMenuContainer <DropdownMenuContainer
@ -129,7 +135,12 @@ const Lists: React.FC<{
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >
{lists.map((list) => ( {lists.map((list) => (
<ListItem key={list.id} id={list.id} title={list.title} /> <ListItem
key={list.id}
id={list.id}
title={list.title}
antennaTitles={list.antennas.map((a) => a.title)}
/>
))} ))}
</ScrollableList> </ScrollableList>

View file

@ -0,0 +1,58 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router';
import { deleteAntenna } from 'mastodon/actions/antennas';
import { removeColumn } from 'mastodon/actions/columns';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
deleteAntennaTitle: {
id: 'confirmations.delete_antenna.title',
defaultMessage: 'Delete antenna?',
},
deleteAntennaMessage: {
id: 'confirmations.delete_antenna.message',
defaultMessage: 'Are you sure you want to permanently delete this antenna?',
},
deleteAntennaConfirm: {
id: 'confirmations.delete_antenna.confirm',
defaultMessage: 'Delete',
},
});
export const ConfirmDeleteAntennaModal: React.FC<
{
antennaId: string;
columnId: string;
} & BaseConfirmationModalProps
> = ({ antennaId, columnId, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const onConfirm = useCallback(() => {
dispatch(deleteAntenna(antennaId));
if (columnId) {
dispatch(removeColumn(columnId));
} else {
history.push('/antennas');
}
}, [dispatch, history, columnId, antennaId]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.deleteAntennaTitle)}
message={intl.formatMessage(messages.deleteAntennaMessage)}
confirm={intl.formatMessage(messages.deleteAntennaConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View file

@ -0,0 +1,59 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router';
import { deleteBookmarkCategory } from 'mastodon/actions/bookmark_categories';
import { removeColumn } from 'mastodon/actions/columns';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
deleteBookmarkCategoryTitle: {
id: 'confirmations.delete_bookmark_category.title',
defaultMessage: 'Delete category?',
},
deleteBookmarkCategoryMessage: {
id: 'confirmations.delete_bookmark_category.message',
defaultMessage:
'Are you sure you want to permanently delete this category?',
},
deleteBookmarkCategoryConfirm: {
id: 'confirmations.delete_bookmark_category.confirm',
defaultMessage: 'Delete',
},
});
export const ConfirmDeleteBookmarkCategoryModal: React.FC<
{
bookmark_categoryId: string;
columnId: string;
} & BaseConfirmationModalProps
> = ({ bookmark_categoryId, columnId, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const onConfirm = useCallback(() => {
dispatch(deleteBookmarkCategory(bookmark_categoryId));
if (columnId) {
dispatch(removeColumn(columnId));
} else {
history.push('/bookmark_categorys');
}
}, [dispatch, history, columnId, bookmark_categoryId]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.deleteBookmarkCategoryTitle)}
message={intl.formatMessage(messages.deleteBookmarkCategoryMessage)}
confirm={intl.formatMessage(messages.deleteBookmarkCategoryConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View file

@ -0,0 +1,58 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router';
import { deleteCircle } from 'mastodon/actions/circles';
import { removeColumn } from 'mastodon/actions/columns';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
deleteCircleTitle: {
id: 'confirmations.delete_circle.title',
defaultMessage: 'Delete circle?',
},
deleteCircleMessage: {
id: 'confirmations.delete_circle.message',
defaultMessage: 'Are you sure you want to permanently delete this circle?',
},
deleteCircleConfirm: {
id: 'confirmations.delete_circle.confirm',
defaultMessage: 'Delete',
},
});
export const ConfirmDeleteCircleModal: React.FC<
{
circleId: string;
columnId: string;
} & BaseConfirmationModalProps
> = ({ circleId, columnId, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const onConfirm = useCallback(() => {
dispatch(deleteCircle(circleId));
if (columnId) {
dispatch(removeColumn(columnId));
} else {
history.push('/circles');
}
}, [dispatch, history, columnId, circleId]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.deleteCircleTitle)}
message={intl.formatMessage(messages.deleteCircleMessage)}
confirm={intl.formatMessage(messages.deleteCircleConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View file

@ -1,6 +1,9 @@
export { ConfirmationModal } from './confirmation_modal'; export { ConfirmationModal } from './confirmation_modal';
export { ConfirmDeleteStatusModal } from './delete_status'; export { ConfirmDeleteStatusModal } from './delete_status';
export { ConfirmDeleteListModal } from './delete_list'; export { ConfirmDeleteListModal } from './delete_list';
export { ConfirmDeleteAntennaModal } from './delete_antenna';
export { ConfirmDeleteCircleModal } from './delete_circle';
export { ConfirmDeleteBookmarkCategoryModal } from './delete_bookmark_category';
export { ConfirmReplyModal } from './reply'; export { ConfirmReplyModal } from './reply';
export { ConfirmEditStatusModal } from './edit_status'; export { ConfirmEditStatusModal } from './edit_status';
export { ConfirmUnfollowModal } from './unfollow'; export { ConfirmUnfollowModal } from './unfollow';

View file

@ -50,7 +50,7 @@ export const ListPanel = () => {
<ColumnLink icon='list-ul' key={list.get('id')} iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={list.get('title')} to={`/lists/${list.get('id')}`} transparent /> <ColumnLink icon='list-ul' key={list.get('id')} iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={list.get('title')} to={`/lists/${list.get('id')}`} transparent />
))} ))}
{antennas && antennas.map(antenna => ( {antennas && antennas.map(antenna => (
<ColumnLink icon='wifi' key={antenna.get('id')} iconComponent={AntennaIcon} activeIconComponent={AntennaIcon} text={antenna.get('title')} to={`/antennast/${antenna.get('id')}`} transparent /> <ColumnLink icon='wifi' key={antenna.get('id')} iconComponent={AntennaIcon} activeIconComponent={AntennaIcon} text={antenna.get('title')} to={`/antennas/${antenna.get('id')}`} transparent />
))} ))}
</div> </div>
); );

View file

@ -34,6 +34,9 @@ import {
ConfirmationModal, ConfirmationModal,
ConfirmDeleteStatusModal, ConfirmDeleteStatusModal,
ConfirmDeleteListModal, ConfirmDeleteListModal,
ConfirmDeleteAntennaModal,
ConfirmDeleteCircleModal,
ConfirmDeleteBookmarkCategoryModal,
ConfirmReplyModal, ConfirmReplyModal,
ConfirmEditStatusModal, ConfirmEditStatusModal,
ConfirmUnfollowModal, ConfirmUnfollowModal,
@ -55,6 +58,9 @@ export const MODAL_COMPONENTS = {
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'CONFIRM_DELETE_STATUS': () => Promise.resolve({ default: ConfirmDeleteStatusModal }), 'CONFIRM_DELETE_STATUS': () => Promise.resolve({ default: ConfirmDeleteStatusModal }),
'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }), 'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }),
'CONFIRM_DELETE_ANTENNA': () => Promise.resolve({ default: ConfirmDeleteAntennaModal }),
'CONFIRM_DELETE_CIRCLE': () => Promise.resolve({ default: ConfirmDeleteCircleModal }),
'CONFIRM_DELETE_BOOKMARK_CATEGORY': () => Promise.resolve({ default: ConfirmDeleteBookmarkCategoryModal }),
'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }), 'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }),
'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }), 'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }),
'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }), 'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }),

View file

@ -122,7 +122,7 @@ class NavigationPanel extends Component {
}; };
isAntennasActive = (match, location) => { isAntennasActive = (match, location) => {
return (match || location.pathname.startsWith('/antennast')); return (match || location.pathname.startsWith('/antennas'));
}; };
render () { render () {
@ -197,7 +197,7 @@ class NavigationPanel extends Component {
{signedIn && ( {signedIn && (
<> <>
<ColumnLink transparent to='/lists' icon='list-ul' iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={intl.formatMessage(messages.lists)} /> <ColumnLink transparent to='/lists' icon='list-ul' iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={intl.formatMessage(messages.lists)} />
<ColumnLink transparent to='/antennasw' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} isActive={this.isAntennasActive} /> <ColumnLink transparent to='/antennas' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} isActive={this.isAntennasActive} />
<ColumnLink transparent to='/circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} /> <ColumnLink transparent to='/circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} />
<FollowRequestsLink /> <FollowRequestsLink />
<ColumnLink transparent to='/conversations' icon='at' iconComponent={AlternateEmailIcon} text={intl.formatMessage(messages.direct)} /> <ColumnLink transparent to='/conversations' icon='at' iconComponent={AlternateEmailIcon} text={intl.formatMessage(messages.direct)} />

View file

@ -80,7 +80,6 @@ import {
PrivacyPolicy, PrivacyPolicy,
CommunityTimeline, CommunityTimeline,
AntennaEdit, AntennaEdit,
AntennaExcludeMembers,
AntennaMembers, AntennaMembers,
CircleEdit, CircleEdit,
CircleMembers, CircleMembers,
@ -235,9 +234,9 @@ class SwitchingColumnsArea extends PureComponent {
<WrappedRoute path='/antennas/new' component={AntennaEdit} content={children} /> <WrappedRoute path='/antennas/new' component={AntennaEdit} content={children} />
<WrappedRoute path='/antennas/:id/edit' component={AntennaEdit} content={children} /> <WrappedRoute path='/antennas/:id/edit' component={AntennaEdit} content={children} />
<WrappedRoute path='/antennas/:id/members' component={AntennaMembers} content={children} /> <WrappedRoute path='/antennas/:id/members' component={AntennaMembers} content={children} />
<WrappedRoute path='/antennas/:id/exclude_members' component={AntennaExcludeMembers} content={children} /> <WrappedRoute path='/antennas/:id/exclude_members' component={AntennaMembers} componentParams={{ isExclude: true }} content={children} />
<WrappedRoute path='/antennasw/:id' component={AntennaSetting} content={children} /> <WrappedRoute path='/antennas/:id/filtering' component={AntennaSetting} content={children} />
<WrappedRoute path='/antennast/:id' component={AntennaTimeline} content={children} /> <WrappedRoute path='/antennas/:id' component={AntennaTimeline} content={children} />
<WrappedRoute path='/circles/new' component={CircleEdit} content={children} /> <WrappedRoute path='/circles/new' component={CircleEdit} content={children} />
<WrappedRoute path='/circles/:id/edit' component={CircleEdit} content={children} /> <WrappedRoute path='/circles/:id/edit' component={CircleEdit} content={children} />
<WrappedRoute path='/circles/:id/members' component={CircleMembers} content={children} /> <WrappedRoute path='/circles/:id/members' component={CircleMembers} content={children} />
@ -289,7 +288,7 @@ class SwitchingColumnsArea extends PureComponent {
<WrappedRoute path='/followed_tags' component={FollowedTags} content={children} /> <WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} /> <WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} /> <WrappedRoute path='/lists' component={Lists} content={children} />
<WrappedRoute path='/antennasw' component={Antennas} content={children} /> <WrappedRoute path='/antennas' component={Antennas} content={children} />
<WrappedRoute path='/circles' component={Circles} content={children} /> <WrappedRoute path='/circles' component={Circles} content={children} />
<WrappedRoute path='/bookmark_categories' component={BookmarkCategories} content={children} /> <WrappedRoute path='/bookmark_categories' component={BookmarkCategories} content={children} />

View file

@ -202,22 +202,10 @@ export function AntennaAdder () {
return import(/*webpackChunkName: "features/antenna_adder" */'../../antenna_adder'); return import(/*webpackChunkName: "features/antenna_adder" */'../../antenna_adder');
} }
export function AntennaEditor () {
return import(/*webpackChunkName: "features/antenna_editor" */'../../antenna_editor');
}
export function CircleAdder () { export function CircleAdder () {
return import(/*webpackChunkName: "features/circle_adder" */'../../circle_adder'); return import(/*webpackChunkName: "features/circle_adder" */'../../circle_adder');
} }
export function CircleEditor () {
return import(/*webpackChunkName: "features/circle_editor" */'../../circle_editor');
}
export function AntennaSetting () {
return import(/*webpackChunkName: "features/antenna_setting" */'../../antenna_setting');
}
export function Tesseract () { export function Tesseract () {
return import(/*webpackChunkName: "tesseract" */'tesseract.js'); return import(/*webpackChunkName: "tesseract" */'tesseract.js');
} }
@ -302,8 +290,8 @@ export function AntennaMembers () {
return import(/* webpackChunkName: "features/antennas" */'../../antennas/members'); return import(/* webpackChunkName: "features/antennas" */'../../antennas/members');
} }
export function AntennaExcludeMembers () { export function AntennaSetting () {
return import(/* webpackChunkName: "features/antennas" */'../../antennas/exclude_members'); return import(/*webpackChunkName: "features/antennas/filtering" */'../../antennas/filtering');
} }
export function CircleEdit () { export function CircleEdit () {

View file

@ -119,47 +119,61 @@
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>", "annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.", "annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
"annual_report.summary.thanks": "Thanks for being part of Mastodon!", "annual_report.summary.thanks": "Thanks for being part of Mastodon!",
"antennas.account.add": "Add to antenna",
"antennas.account.remove": "Remove from antenna",
"antennas.accounts": "{count} accounts", "antennas.accounts": "{count} accounts",
"antennas.add_domain": "Add domain", "antennas.add_domain": "Add domain",
"antennas.add_domain_placeholder": "New domain", "antennas.add_domain_placeholder": "New domain",
"antennas.add_keyword": "Add keyword", "antennas.add_keyword": "Add keyword",
"antennas.add_keyword_placeholder": "New keyword", "antennas.add_keyword_placeholder": "New keyword",
"antennas.add_member": "Add",
"antennas.add_tag": "Add tag", "antennas.add_tag": "Add tag",
"antennas.add_tag_placeholder": "New tag", "antennas.add_tag_placeholder": "New tag",
"antennas.add_to_antenna": "Add to antenna",
"antennas.add_to_antennas": "Add {name} to antennas",
"antennas.antenna_accounts": "Antenna accounts",
"antennas.antenna_accounts_count": "{count, plural, one {# member} other {# accounts}}",
"antennas.antenna_name": "Antenna name",
"antennas.create": "Create",
"antennas.create_a_antenna_to_organize": "Create a new antenna to organize your Home feed",
"antennas.create_antenna": "Create antenna",
"antennas.delete": "Delete antenna", "antennas.delete": "Delete antenna",
"antennas.destination": "Destination",
"antennas.destination.home": "Insert to home",
"antennas.destination.list": "Insert to list",
"antennas.destination.timeline": "Antenna timeline only",
"antennas.domains": "{count} domains", "antennas.domains": "{count} domains",
"antennas.done": "Done",
"antennas.edit": "Edit antenna", "antennas.edit": "Edit antenna",
"antennas.edit.submit": "Change title",
"antennas.edit_accounts": "Edit accounts", "antennas.edit_accounts": "Edit accounts",
"antennas.edit_static": "Edit antenna",
"antennas.exclude_accounts": "Exclude accounts", "antennas.exclude_accounts": "Exclude accounts",
"antennas.exclude_domains": "Exclude domains", "antennas.exclude_domains": "Exclude domains",
"antennas.exclude_keywords": "Exclude keywords", "antennas.exclude_keywords": "Exclude keywords",
"antennas.exclude_tags": "Exclude tags", "antennas.exclude_tags": "Exclude tags",
"antennas.filter": "Filter", "antennas.filter_items": "Antenna filter settings",
"antennas.filter_not": "Filter Not", "antennas.filter_not": "Filter Not",
"antennas.go_timeline": "Go to antenna timeline", "antennas.find_users_to_add": "Find users to add",
"antennas.ignore_reblog": "Exclude boosts", "antennas.ignore_reblog": "Exclude boosts",
"antennas.ignore_reblog_hint": "Boosts will be excluded from antenna detection.",
"antennas.in_ltl_mode": "This antenna is in LTL mode.", "antennas.in_ltl_mode": "This antenna is in LTL mode.",
"antennas.in_stl_mode": "This antenna is in STL mode.", "antennas.in_stl_mode": "This antenna is in STL mode.",
"antennas.insert_feeds": "Insert to feeds",
"antennas.insert_home": "Home", "antennas.insert_home": "Home",
"antennas.insert_list": "List", "antennas.insert_list": "List",
"antennas.keywords": "{count} keywords", "antennas.keywords": "{count} keywords",
"antennas.ltl": "LTL mode",
"antennas.media_only": "Media only", "antennas.media_only": "Media only",
"antennas.new.create": "Add antenna", "antennas.media_only_hint": "Only posts with media will be added antenna.",
"antennas.new.title_placeholder": "New antenna title", "antennas.mode": "Mode",
"antennas.not_related_list": "This antenna is not related list. Posts will appear in home timeline. Open edit page to set list.", "antennas.mode.filtering": "Filtering",
"antennas.related_list": "This antenna is related to {listTitle}.", "antennas.mode.ltl": "Local timeline mode",
"antennas.search": "Search among people you follow", "antennas.mode.stl": "Social timeline mode",
"antennas.new_antenna_name": "New antenna name",
"antennas.no_antennas_yet": "No antennas yet.",
"antennas.no_members_yet": "No members yet.",
"antennas.no_results_found": "No results found.",
"antennas.remove_member": "Remove",
"antennas.save": "Save",
"antennas.search_placeholder": "Search people you follow",
"antennas.select.no_options_message": "Empty lists", "antennas.select.no_options_message": "Empty lists",
"antennas.select.placeholder": "Select list", "antennas.select.placeholder": "Select list",
"antennas.select.set_home": "Set home", "antennas.select.set_home": "Set home",
"antennas.stl": "STL mode",
"antennas.subheading": "Your antennas",
"antennas.tags": "{count} tags", "antennas.tags": "{count} tags",
"antennas.warnings.content_radio": "Simultaneous keyword and tag designation is not recommended.", "antennas.warnings.content_radio": "Simultaneous keyword and tag designation is not recommended.",
"antennas.warnings.range_radio": "Simultaneous account and domain designation is not recommended.", "antennas.warnings.range_radio": "Simultaneous account and domain designation is not recommended.",
@ -173,15 +187,17 @@
"block_modal.they_will_know": "They can see that they're blocked.", "block_modal.they_will_know": "They can see that they're blocked.",
"block_modal.title": "Block user?", "block_modal.title": "Block user?",
"block_modal.you_wont_see_mentions": "You won't see posts that mention them.", "block_modal.you_wont_see_mentions": "You won't see posts that mention them.",
"bookmark_categories.all_bookmarks": "All bookmarks", "bookmark_categories.add_to_bookmark_categories": "Add {name} to bookmark_categories",
"bookmark_categories.bookmark_category_name": "BookmarkCategory name",
"bookmark_categories.create": "Create",
"bookmark_categories.create_a_bookmark_category_to_organize": "Create a new bookmark_category to organize your Home feed",
"bookmark_categories.create_bookmark_category": "Create bookmark_category",
"bookmark_categories.delete": "Delete category", "bookmark_categories.delete": "Delete category",
"bookmark_categories.edit": "Edit category", "bookmark_categories.edit": "Edit category",
"bookmark_categories.edit.submit": "Change title", "bookmark_categories.edit.submit": "Change title",
"bookmark_categories.new.create": "Add category", "bookmark_categories.new_bookmark_category_name": "New bookmark_category name",
"bookmark_categories.new.title_placeholder": "New category title", "bookmark_categories.no_bookmark_categories_yet": "No bookmark_categories yet.",
"bookmark_categories.status.add": "Add to bookmark category", "bookmark_categories.save": "Save",
"bookmark_categories.status.remove": "Remove from bookmark category",
"bookmark_categories.subheading": "Your categories",
"boost_modal.combo": "You can press {combo} to skip this next time", "boost_modal.combo": "You can press {combo} to skip this next time",
"boost_modal.reblog": "Boost post?", "boost_modal.reblog": "Boost post?",
"boost_modal.undo_reblog": "Unboost post?", "boost_modal.undo_reblog": "Unboost post?",
@ -197,15 +213,26 @@
"bundle_modal_error.close": "Close", "bundle_modal_error.close": "Close",
"bundle_modal_error.message": "Something went wrong while loading this component.", "bundle_modal_error.message": "Something went wrong while loading this component.",
"bundle_modal_error.retry": "Try again", "bundle_modal_error.retry": "Try again",
"circles.account.add": "Add to circle", "circles.add_member": "Add",
"circles.account.remove": "Remove from circle", "circles.add_to_circle": "Add to circle",
"circles.add_to_circles": "Add {name} to circles",
"circles.circle_members": "Circle members",
"circles.circle_members_count": "{count, plural, one {# member} other {# members}}",
"circles.circle_name": "Circle name",
"circles.create": "Create",
"circles.create_a_circle_to_organize": "Create a new circle to organize your Home feed",
"circles.create_circle": "Create circle",
"circles.delete": "Delete circle", "circles.delete": "Delete circle",
"circles.done": "Done",
"circles.edit": "Edit circle", "circles.edit": "Edit circle",
"circles.edit.submit": "Change title", "circles.find_users_to_add": "Find users to add",
"circles.new.create": "Add circle", "circles.new_circle_name": "New circle name",
"circles.new.title_placeholder": "New circle title", "circles.no_circles_yet": "No circles yet.",
"circles.search": "Search among people follow you", "circles.no_members_yet": "No members yet.",
"circles.subheading": "Your circles", "circles.no_results_found": "No results found.",
"circles.remove_member": "Remove",
"circles.save": "Save",
"circles.search_placeholder": "Search people you follow",
"closed_registrations.other_server_instructions": "Since Mastodon is decentralized, you can create an account on another server and still interact with this one.", "closed_registrations.other_server_instructions": "Since Mastodon is decentralized, you can create an account on another server and still interact with this one.",
"closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.", "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.",
"closed_registrations_modal.description_when_reaching_limit": "New registrations are currently temporarily restricted. Either the maximum number of registrations has been reached for registration. Please contact the administrator for more information or wait until the restriction is lifted.", "closed_registrations_modal.description_when_reaching_limit": "New registrations are currently temporarily restricted. Either the maximum number of registrations has been reached for registration. Please contact the administrator for more information or wait until the restriction is lifted.",
@ -213,17 +240,25 @@
"closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!", "closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
"closed_registrations_modal.title": "Signing up on Mastodon", "closed_registrations_modal.title": "Signing up on Mastodon",
"column.about": "About", "column.about": "About",
"column.antenna_members": "Manage antenna members",
"column.antennas": "Antennas", "column.antennas": "Antennas",
"column.blocks": "Blocked users", "column.blocks": "Blocked users",
"column.bookmark_categories": "Bookmark categories", "column.bookmark_categories": "Bookmark categories",
"column.bookmarks": "Bookmarks", "column.bookmarks": "Bookmarks",
"column.circle_members": "Manage circle members",
"column.circles": "Circles", "column.circles": "Circles",
"column.community": "Local timeline", "column.community": "Local timeline",
"column.create_antenna": "Create antenna",
"column.create_bookmark_category": "Create bookmark_category",
"column.create_circle": "Create circle",
"column.create_list": "Create list", "column.create_list": "Create list",
"column.deep_local": "Deep", "column.deep_local": "Deep",
"column.direct": "Private mentions", "column.direct": "Private mentions",
"column.directory": "Browse profiles", "column.directory": "Browse profiles",
"column.domain_blocks": "Blocked domains", "column.domain_blocks": "Blocked domains",
"column.edit_antenna": "Edit antenna",
"column.edit_bookmark_category": "Edit bookmark_category",
"column.edit_circle": "Edit circle",
"column.edit_list": "Edit list", "column.edit_list": "Edit list",
"column.emoji_reactions": "Emoji Reactions", "column.emoji_reactions": "Emoji Reactions",
"column.favourites": "Favorites", "column.favourites": "Favorites",
@ -286,10 +321,13 @@
"confirmations.delete.title": "Delete post?", "confirmations.delete.title": "Delete post?",
"confirmations.delete_antenna.confirm": "Delete", "confirmations.delete_antenna.confirm": "Delete",
"confirmations.delete_antenna.message": "Are you sure you want to permanently delete this antenna?", "confirmations.delete_antenna.message": "Are you sure you want to permanently delete this antenna?",
"confirmations.delete_antenna.title": "Delete antenna?",
"confirmations.delete_bookmark_category.confirm": "Delete", "confirmations.delete_bookmark_category.confirm": "Delete",
"confirmations.delete_bookmark_category.message": "Are you sure you want to permanently delete this category?", "confirmations.delete_bookmark_category.message": "Are you sure you want to permanently delete this category?",
"confirmations.delete_bookmark_category.title": "Delete bookmark_category?",
"confirmations.delete_circle.confirm": "Delete", "confirmations.delete_circle.confirm": "Delete",
"confirmations.delete_circle.message": "Are you sure you want to permanently delete this circle?", "confirmations.delete_circle.message": "Are you sure you want to permanently delete this circle?",
"confirmations.delete_circle.title": "Delete circle?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.delete_list.title": "Delete list?", "confirmations.delete_list.title": "Delete list?",
@ -377,12 +415,9 @@
"empty_column.account_timeline": "No posts here!", "empty_column.account_timeline": "No posts here!",
"empty_column.account_unavailable": "Profile unavailable", "empty_column.account_unavailable": "Profile unavailable",
"empty_column.antenna": "There is nothing in this antenna yet. When members of this list post new statuses, they will appear here.", "empty_column.antenna": "There is nothing in this antenna yet. When members of this list post new statuses, they will appear here.",
"empty_column.antennas": "You don't have any antennas yet. When you create one, it will show up here.",
"empty_column.blocks": "You haven't blocked any users yet.", "empty_column.blocks": "You haven't blocked any users yet.",
"empty_column.bookmark_categories": "You don't have any categories yet. When you create one, it will show up here.",
"empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.", "empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
"empty_column.circle_statuses": "You don't have any circle posts yet. When you post one as circle, it will show up here.", "empty_column.circle_statuses": "You don't have any circle posts yet. When you post one as circle, it will show up here.",
"empty_column.circles": "You don't have any circles yet. When you create one, it will show up here.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.", "empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no blocked domains yet.", "empty_column.domain_blocks": "There are no blocked domains yet.",
@ -576,7 +611,7 @@
"lists.add_member": "Add", "lists.add_member": "Add",
"lists.add_to_list": "Add to list", "lists.add_to_list": "Add to list",
"lists.add_to_lists": "Add {name} to lists", "lists.add_to_lists": "Add {name} to lists",
"lists.antennas": "Related antennas", "lists.antennas": "Related antennas:",
"lists.create": "Create", "lists.create": "Create",
"lists.create_a_list_to_organize": "Create a new list to organize your Home feed", "lists.create_a_list_to_organize": "Create a new list to organize your Home feed",
"lists.create_list": "Create list", "lists.create_list": "Create list",
@ -594,6 +629,7 @@
"lists.no_members_yet": "No members yet.", "lists.no_members_yet": "No members yet.",
"lists.no_results_found": "No results found.", "lists.no_results_found": "No results found.",
"lists.notify": "Notify these posts", "lists.notify": "Notify these posts",
"lists.notify_hint": "Notify when new post is added.",
"lists.remove_member": "Remove", "lists.remove_member": "Remove",
"lists.replies_policy.followed": "Any followed user", "lists.replies_policy.followed": "Any followed user",
"lists.replies_policy.list": "Members of the list", "lists.replies_policy.list": "Members of the list",
@ -601,7 +637,6 @@
"lists.save": "Save", "lists.save": "Save",
"lists.search_placeholder": "Search people you follow", "lists.search_placeholder": "Search people you follow",
"lists.show_replies_to": "Include replies from list members to", "lists.show_replies_to": "Include replies from list members to",
"lists.with_antenna": "Antenna",
"load_pending": "{count, plural, one {# new item} other {# new items}}", "load_pending": "{count, plural, one {# new item} other {# new items}}",
"loading_indicator.label": "Loading…", "loading_indicator.label": "Loading…",
"media_gallery.hide": "Hide", "media_gallery.hide": "Hide",

View file

@ -117,33 +117,50 @@
"annual_report.summary.percentile.text": "<topLabel>それにより、あなたは上位に位置しています。</topLabel><percentage></percentage><bottomLabel>Mastodonユーザーの中で。</bottomLabel>", "annual_report.summary.percentile.text": "<topLabel>それにより、あなたは上位に位置しています。</topLabel><percentage></percentage><bottomLabel>Mastodonユーザーの中で。</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "バー二ーには秘密にしておくよ。", "annual_report.summary.percentile.we_wont_tell_bernie": "バー二ーには秘密にしておくよ。",
"annual_report.summary.thanks": "Mastodonの一員になってくれてありがとう", "annual_report.summary.thanks": "Mastodonの一員になってくれてありがとう",
"antennas.account.add": "アンテナに追加",
"antennas.account.remove": "アンテナから外す",
"antennas.accounts": "{count} のアカウント", "antennas.accounts": "{count} のアカウント",
"antennas.add_domain": "新規ドメイン", "antennas.add_domain": "新規ドメイン",
"antennas.add_domain_placeholder": "新しいドメイン名", "antennas.add_domain_placeholder": "新しいドメイン名",
"antennas.add_keyword": "新規キーワード", "antennas.add_keyword": "新規キーワード",
"antennas.add_keyword_placeholder": "新しいキーワード", "antennas.add_keyword_placeholder": "新しいキーワード",
"antennas.add_member": "追加",
"antennas.add_tag": "新規タグ", "antennas.add_tag": "新規タグ",
"antennas.add_tag_placeholder": "新しいタグ", "antennas.add_tag_placeholder": "新しいタグ",
"antennas.add_to_antenna": "アンテナに追加",
"antennas.add_to_antennas": "{name}をアンテナに追加",
"antennas.antenna_accounts": "アンテナのアカウント",
"antennas.antenna_accounts_count": "{count}のアカウント",
"antennas.antenna_name": "アンテナの名前",
"antennas.create": "新規作成",
"antennas.create_a_antenna_to_organize": "興味ある話題を検出するためにアンテナを作成する",
"antennas.create_antenna": "アンテナを作成",
"antennas.delete": "アンテナを削除", "antennas.delete": "アンテナを削除",
"antennas.destination": "検出された投稿の配置先",
"antennas.destination.home": "ホームに追加",
"antennas.destination.list": "リストに追加",
"antennas.destination.timeline": "アンテナタイムラインのみ",
"antennas.domains": "{count} のドメイン", "antennas.domains": "{count} のドメイン",
"antennas.done": "保存",
"antennas.edit": "アンテナを編集", "antennas.edit": "アンテナを編集",
"antennas.edit.submit": "タイトルを変更",
"antennas.edit_static": "旧編集画面に移動",
"antennas.edit_accounts": "アカウントを編集", "antennas.edit_accounts": "アカウントを編集",
"antennas.exclude_accounts": "除外するアカウント", "antennas.exclude_accounts": "除外するアカウント",
"antennas.exclude_domains": "除外するドメイン", "antennas.exclude_domains": "除外するドメイン",
"antennas.exclude_keywords": "除外するキーワード", "antennas.exclude_keywords": "除外するキーワード",
"antennas.exclude_tags": "除外するタグ", "antennas.exclude_tags": "除外するタグ",
"antennas.filter": "絞り込み条件", "antennas.filter_items": "絞り込み条件を設定する",
"antennas.filter_not": "絞り込み条件の例外", "antennas.filter_not": "絞り込み条件の例外",
"antennas.go_timeline": "タイムラインを見る",
"antennas.ignore_reblog": "ブーストを除外", "antennas.ignore_reblog": "ブーストを除外",
"antennas.ignore_reblog_hint": "ブーストはアンテナの検出対象から外れます",
"antennas.in_ltl_mode": "LTLモードが有効になっています",
"antennas.in_stl_mode": "STLモードが有効になっています", "antennas.in_stl_mode": "STLモードが有効になっています",
"antennas.insert_feeds": "リストまたはホームに挿入", "antennas.insert_home": "ホーム",
"antennas.insert_list": "リスト",
"antennas.keywords": "{count} のキーワード", "antennas.keywords": "{count} のキーワード",
"antennas.media_only": "メディアのみ", "antennas.media_only": "メディアのみ",
"antennas.media_only_hint": "メディアの添付された投稿のみがアンテナに検出されます",
"antennas.mode": "動作モード",
"antennas.mode.filtering": "フィルタリング",
"antennas.mode.ltl": "ローカルタイムラインモード",
"antennas.mode.stl": "ソーシャルタイムラインモード",
"antennas.new.create": "アンテナを作成", "antennas.new.create": "アンテナを作成",
"antennas.new.title_placeholder": "新規アンテナ名", "antennas.new.title_placeholder": "新規アンテナ名",
"antennas.not_related_list": "このアンテナはどのリストにも関連付けられていません。", "antennas.not_related_list": "このアンテナはどのリストにも関連付けられていません。",
@ -274,15 +291,18 @@
"confirmations.delete.confirm": "削除", "confirmations.delete.confirm": "削除",
"confirmations.delete.message": "本当に削除しますか?", "confirmations.delete.message": "本当に削除しますか?",
"confirmations.delete.title": "投稿を削除しようとしています", "confirmations.delete.title": "投稿を削除しようとしています",
"confirmations.delete_antenna.confirm": "削除",
"confirmations.delete_antenna.message": "本当にこのアンテナを完全に削除しますか?",
"confirmations.delete_antenna.title": "アンテナを削除しようとしています",
"confirmations.delete_bookmark_category.confirm": "削除", "confirmations.delete_bookmark_category.confirm": "削除",
"confirmations.delete_bookmark_category.message": "本当にこの分類を完全に削除しますか?各投稿からこの分類は削除されますが、ブックマークは解除されません。", "confirmations.delete_bookmark_category.message": "本当にこの分類を完全に削除しますか?各投稿からこの分類は削除されますが、ブックマークは解除されません。",
"confirmations.delete_bookmark_category.title": "分類を削除しようとしています",
"confirmations.delete_circle.confirm": "削除", "confirmations.delete_circle.confirm": "削除",
"confirmations.delete_circle.message": "本当にこのサークルを完全に削除しますか?", "confirmations.delete_circle.message": "本当にこのサークルを完全に削除しますか?",
"confirmations.delete_circle.title": "サークルを削除しようとしています",
"confirmations.delete_list.confirm": "削除", "confirmations.delete_list.confirm": "削除",
"confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?", "confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
"confirmations.delete_list.title": "リストを削除しようとしています", "confirmations.delete_list.title": "リストを削除しようとしています",
"confirmations.delete_antenna.confirm": "削除",
"confirmations.delete_antenna.message": "本当にこのアンテナを完全に削除しますか?",
"confirmations.discard_edit_media.confirm": "破棄", "confirmations.discard_edit_media.confirm": "破棄",
"confirmations.discard_edit_media.message": "メディアの説明またはプレビューに保存されていない変更があります。それでも破棄しますか?", "confirmations.discard_edit_media.message": "メディアの説明またはプレビューに保存されていない変更があります。それでも破棄しますか?",
"confirmations.edit.confirm": "編集", "confirmations.edit.confirm": "編集",

View file

@ -12,6 +12,7 @@ const ListFactory = Record<ListShape>({
exclusive: false, exclusive: false,
replies_policy: 'list', replies_policy: 'list',
notify: false, notify: false,
antennas: [],
}); });
export function createList(attributes: Partial<ListShape>) { export function createList(attributes: Partial<ListShape>) {

View file

@ -1,58 +0,0 @@
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_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST,
ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS,
ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL,
ANTENNA_EDITOR_ADD_SUCCESS,
ANTENNA_EDITOR_REMOVE_SUCCESS,
ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS,
ANTENNA_EDITOR_REMOVE_EXCLUDE_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:
case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST:
return state.setIn(['antennas', 'isLoading'], true);
case ANTENNA_ADDER_ANTENNAS_FETCH_FAIL:
case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL:
return state.setIn(['antennas', 'isLoading'], false);
case ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS:
case ANTENNA_ADDER_EXCLUDE_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_SUCCESS:
case ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS:
return state.updateIn(['antennas', 'items'], antenna => antenna.unshift(action.antennaId));
case ANTENNA_EDITOR_REMOVE_SUCCESS:
case ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS:
return state.updateIn(['antennas', 'items'], antenna => antenna.filterNot(item => item === action.antennaId));
default:
return state;
}
}

View file

@ -1,116 +0,0 @@
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import {
ANTENNA_CREATE_REQUEST,
ANTENNA_CREATE_FAIL,
ANTENNA_CREATE_SUCCESS,
ANTENNA_UPDATE_REQUEST,
ANTENNA_UPDATE_FAIL,
ANTENNA_UPDATE_SUCCESS,
ANTENNA_EDITOR_RESET,
ANTENNA_EDITOR_SETUP,
ANTENNA_EDITOR_TITLE_CHANGE,
ANTENNA_ACCOUNTS_FETCH_REQUEST,
ANTENNA_ACCOUNTS_FETCH_SUCCESS,
ANTENNA_ACCOUNTS_FETCH_FAIL,
ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST,
ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS,
ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL,
ANTENNA_EDITOR_SUGGESTIONS_READY,
ANTENNA_EDITOR_SUGGESTIONS_CLEAR,
ANTENNA_EDITOR_SUGGESTIONS_CHANGE,
ANTENNA_EDITOR_ADD_SUCCESS,
ANTENNA_EDITOR_REMOVE_SUCCESS,
ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS,
ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS,
} from '../actions/antennas';
const initialState = ImmutableMap({
antennaId: null,
isSubmitting: false,
isChanged: false,
title: '',
accountsCount: 0,
accounts: ImmutableMap({
items: ImmutableList(),
loaded: false,
isLoading: false,
}),
suggestions: ImmutableMap({
value: '',
items: ImmutableList(),
}),
});
export default function antennaEditorReducer(state = initialState, action) {
switch(action.type) {
case ANTENNA_EDITOR_RESET:
return initialState;
case ANTENNA_EDITOR_SETUP:
return state.withMutations(map => {
map.set('antennaId', action.antenna.get('id'));
map.set('title', action.antenna.get('title'));
map.set('accountsCount', action.antenna.get('accounts_count'));
map.set('isSubmitting', false);
});
case ANTENNA_EDITOR_TITLE_CHANGE:
return state.withMutations(map => {
map.set('title', action.value);
map.set('isChanged', true);
});
case ANTENNA_CREATE_REQUEST:
case ANTENNA_UPDATE_REQUEST:
return state.withMutations(map => {
map.set('isSubmitting', true);
map.set('isChanged', false);
});
case ANTENNA_CREATE_FAIL:
case ANTENNA_UPDATE_FAIL:
return state.set('isSubmitting', false);
case ANTENNA_CREATE_SUCCESS:
case ANTENNA_UPDATE_SUCCESS:
return state.withMutations(map => {
map.set('isSubmitting', false);
map.set('antennaId', action.antenna.id);
});
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)));
}));
case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST:
return state.setIn(['accounts', 'isLoading'], true);
case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL:
return state.setIn(['accounts', 'isLoading'], false);
case ANTENNA_EXCLUDE_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)));
}));
case ANTENNA_EDITOR_SUGGESTIONS_CHANGE:
return state.setIn(['suggestions', 'value'], action.value);
case ANTENNA_EDITOR_SUGGESTIONS_READY:
return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
case ANTENNA_EDITOR_SUGGESTIONS_CLEAR:
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
map.set('items', ImmutableList());
map.set('value', '');
}));
case ANTENNA_EDITOR_ADD_SUCCESS:
case ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS:
return state.updateIn(['accounts', 'items'], antenna => antenna.unshift(action.accountId));
case ANTENNA_EDITOR_REMOVE_SUCCESS:
case ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS:
return state.updateIn(['accounts', 'items'], antenna => antenna.filterNot(item => item === action.accountId));
default:
return state;
}
}

View file

@ -11,9 +11,6 @@ import {
ANTENNA_FETCH_FAIL, ANTENNA_FETCH_FAIL,
ANTENNAS_FETCH_SUCCESS, ANTENNAS_FETCH_SUCCESS,
ANTENNA_DELETE_SUCCESS, ANTENNA_DELETE_SUCCESS,
ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS,
//ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS,
//ANTENNA_EDITOR_FETCH_TAGS_SUCCESS,
} from '../actions/antennas'; } from '../actions/antennas';
const initialState = ImmutableMap<string, Antenna | null>(); const initialState = ImmutableMap<string, Antenna | null>();
@ -48,16 +45,6 @@ export const antennasReducer: Reducer<State> = (
case ANTENNA_DELETE_SUCCESS: case ANTENNA_DELETE_SUCCESS:
case ANTENNA_FETCH_FAIL: case ANTENNA_FETCH_FAIL:
return state.set(action.id as string, null); return state.set(action.id as string, null);
case ANTENNA_EDITOR_FETCH_DOMAINS_SUCCESS:
return state
.setIn(
['domains', action.id],
(action.domains as { domains: string[] }).domains,
)
.setIn(
['exclude_domains', action.id],
(action.domains as { exclude_domains: string[] }).exclude_domains,
);
default: default:
return state; return state;
} }

View file

@ -1,48 +0,0 @@
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import {
CIRCLE_ADDER_RESET,
CIRCLE_ADDER_SETUP,
CIRCLE_ADDER_CIRCLES_FETCH_REQUEST,
CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS,
CIRCLE_ADDER_CIRCLES_FETCH_FAIL,
CIRCLE_EDITOR_ADD_SUCCESS,
CIRCLE_EDITOR_REMOVE_SUCCESS,
} from '../actions/circles';
const initialState = ImmutableMap({
accountId: null,
circles: ImmutableMap({
items: ImmutableList(),
loaded: false,
isLoading: false,
}),
});
export default function circleAdderReducer(state = initialState, action) {
switch(action.type) {
case CIRCLE_ADDER_RESET:
return initialState;
case CIRCLE_ADDER_SETUP:
return state.withMutations(map => {
map.set('accountId', action.account.get('id'));
});
case CIRCLE_ADDER_CIRCLES_FETCH_REQUEST:
return state.setIn(['circles', 'isLoading'], true);
case CIRCLE_ADDER_CIRCLES_FETCH_FAIL:
return state.setIn(['circles', 'isLoading'], false);
case CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS:
return state.update('circles', circles => circles.withMutations(map => {
map.set('isLoading', false);
map.set('loaded', true);
map.set('items', ImmutableList(action.circles.map(item => item.id)));
}));
case CIRCLE_EDITOR_ADD_SUCCESS:
return state.updateIn(['circles', 'items'], circle => circle.unshift(action.circleId));
case CIRCLE_EDITOR_REMOVE_SUCCESS:
return state.updateIn(['circles', 'items'], circle => circle.filterNot(item => item === action.circleId));
default:
return state;
}
}

View file

@ -1,99 +0,0 @@
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import {
CIRCLE_CREATE_REQUEST,
CIRCLE_CREATE_FAIL,
CIRCLE_CREATE_SUCCESS,
CIRCLE_UPDATE_REQUEST,
CIRCLE_UPDATE_FAIL,
CIRCLE_UPDATE_SUCCESS,
CIRCLE_EDITOR_RESET,
CIRCLE_EDITOR_SETUP,
CIRCLE_EDITOR_TITLE_CHANGE,
CIRCLE_ACCOUNTS_FETCH_REQUEST,
CIRCLE_ACCOUNTS_FETCH_SUCCESS,
CIRCLE_ACCOUNTS_FETCH_FAIL,
CIRCLE_EDITOR_SUGGESTIONS_READY,
CIRCLE_EDITOR_SUGGESTIONS_CLEAR,
CIRCLE_EDITOR_SUGGESTIONS_CHANGE,
CIRCLE_EDITOR_ADD_SUCCESS,
CIRCLE_EDITOR_REMOVE_SUCCESS,
} from '../actions/circles';
const initialState = ImmutableMap({
circleId: null,
isSubmitting: false,
isChanged: false,
title: '',
isExclusive: false,
accounts: ImmutableMap({
items: ImmutableList(),
loaded: false,
isLoading: false,
}),
suggestions: ImmutableMap({
value: '',
items: ImmutableList(),
}),
});
export default function circleEditorReducer(state = initialState, action) {
switch(action.type) {
case CIRCLE_EDITOR_RESET:
return initialState;
case CIRCLE_EDITOR_SETUP:
return state.withMutations(map => {
map.set('circleId', action.circle.get('id'));
map.set('title', action.circle.get('title'));
map.set('isExclusive', action.circle.get('is_exclusive'));
map.set('isSubmitting', false);
});
case CIRCLE_EDITOR_TITLE_CHANGE:
return state.withMutations(map => {
map.set('title', action.value);
map.set('isChanged', true);
});
case CIRCLE_CREATE_REQUEST:
case CIRCLE_UPDATE_REQUEST:
return state.withMutations(map => {
map.set('isSubmitting', true);
map.set('isChanged', false);
});
case CIRCLE_CREATE_FAIL:
case CIRCLE_UPDATE_FAIL:
return state.set('isSubmitting', false);
case CIRCLE_CREATE_SUCCESS:
case CIRCLE_UPDATE_SUCCESS:
return state.withMutations(map => {
map.set('isSubmitting', false);
map.set('circleId', action.circle.id);
});
case CIRCLE_ACCOUNTS_FETCH_REQUEST:
return state.setIn(['accounts', 'isLoading'], true);
case CIRCLE_ACCOUNTS_FETCH_FAIL:
return state.setIn(['accounts', 'isLoading'], false);
case CIRCLE_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)));
}));
case CIRCLE_EDITOR_SUGGESTIONS_CHANGE:
return state.setIn(['suggestions', 'value'], action.value);
case CIRCLE_EDITOR_SUGGESTIONS_READY:
return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
case CIRCLE_EDITOR_SUGGESTIONS_CLEAR:
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
map.set('items', ImmutableList());
map.set('value', '');
}));
case CIRCLE_EDITOR_ADD_SUCCESS:
return state.updateIn(['accounts', 'items'], circle => circle.unshift(action.accountId));
case CIRCLE_EDITOR_REMOVE_SUCCESS:
return state.updateIn(['accounts', 'items'], circle => circle.filterNot(item => item === action.accountId));
default:
return state;
}
}

View file

@ -8386,11 +8386,14 @@ noscript {
i.fa { i.fa {
color: $darker-text-color; color: $darker-text-color;
width: 24px;
height: 24px;
} }
.label { .label {
flex: 1; flex: 1;
margin: 0 8px; margin: 0 8px;
line-height: 24px;
} }
} }

View file

@ -1,77 +0,0 @@
.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? && !antenna.stl
.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 || '[Insert to Home]'
.filters-list__item__permissions
%ul.permissions-list
- unless antenna.stl
- unless antenna.antenna_domains.empty?
%li.permissions-list__item
.permissions-list__item__icon
= material_symbol('cloud')
.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(&: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
= material_symbol('groups')
.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(', ')
- if antenna.keywords.present?
%li.permissions-list__item
.permissions-list__item__icon
= material_symbol('format_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
= material_symbol('tag')
.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 = tags.take(5) + ['…'] if tags.size > 5 # TODO
= tags.join(', ')
.announcements-list__item__action-bar
.announcements-list__item__meta
- if antenna.stl
= t('antennas.index.stl')
- elsif 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 'edit', t('antennas.edit.title'), edit_antenna_path(antenna)
= table_link_to 'close', t('antennas.index.delete'), antenna_path(antenna), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }

View file

@ -1,11 +0,0 @@
%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: ->(i) { I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt')
.fields-row
= f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false

View file

@ -1,9 +0,0 @@
- content_for :page_title do
= t('antennas.edit.title')
= simple_form_for @antenna, url: antenna_path(@antenna), method: :put do |f|
= render 'shared/error_messages', object: @antenna
= render 'antenna_fields', f: f, lists: @lists
.actions
= f.button :button, t('generic.save_changes'), type: :submit

View file

@ -1,8 +0,0 @@
- content_for :page_title do
= t('antennas.index.title')
- if @antennas.empty?
.muted-hint.center-text= t 'antennas.index.empty'
- else
.applications-list
= render partial: 'antenna', collection: @antennas

View file

@ -28,8 +28,7 @@ Rails.application.routes.draw do
/public/remote /public/remote
/conversations /conversations
/lists/(*any) /lists/(*any)
/antennasw/(*any) /antennas/(*any)
/antennast/(*any)
/circles/(*any) /circles/(*any)
/links/(*any) /links/(*any)
/notifications/(*any) /notifications/(*any)
@ -213,7 +212,6 @@ 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]
resources :severed_relationships, only: [:index] do resources :severed_relationships, only: [:index] do