diff --git a/app/controllers/api/v1/antennas/domains_controller.rb b/app/controllers/api/v1/antennas/domains_controller.rb new file mode 100644 index 0000000000..554b8d613c --- /dev/null +++ b/app/controllers/api/v1/antennas/domains_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::DomainsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_antenna + + def show + @domains = load_domains + @exclude_domains = load_exclude_domains + render json: { domains: @domains, exclude_domains: @exclude_domains } + end + + def create + ApplicationRecord.transaction do + domains.each do |domain| + @antenna.antenna_domains.create!(name: domain, exclude: false) + @antenna.update!(any_domains: false) if @antenna.any_domains + end + end + + render_empty + end + + def destroy + AntennaDomain.where(antenna: @antenna, name: domains).destroy_all + @antenna.update!(any_domains: true) unless @antenna.antenna_domains.where(exclude: false).any? + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def load_domains + @antenna.antenna_domains.pluck(:name) + end + + def load_exclude_domains + @antenna.exclude_domains || [] + end + + def domains + Array(resource_params[:domains]) + end + + def resource_params + params.permit(domains: []) + end +end diff --git a/app/controllers/api/v1/antennas/keywords_controller.rb b/app/controllers/api/v1/antennas/keywords_controller.rb new file mode 100644 index 0000000000..94b9c396cc --- /dev/null +++ b/app/controllers/api/v1/antennas/keywords_controller.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::KeywordsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_antenna + + def show + @keywords = load_keywords + @exclude_keywords = load_exclude_keywords + render json: { keywords: @keywords, exclude_keywords: @exclude_keywords } + end + + def create + new_keywords = @antenna.keywords || [] + keywords.each do |keyword| + raise Mastodon::ValidationError, I18n.t('antennas.errors.same_keyword') if new_keywords.include?(keyword) + + new_keywords << keyword + end + + @antenna.update!(keywords: new_keywords, any_keywords: new_keywords.empty?) + + render_empty + end + + def destroy + new_keywords = @antenna.keywords || [] + new_keywords -= keywords + + @antenna.update!(keywords: new_keywords, any_keywords: new_keywords.empty?) + + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def load_keywords + @antenna.keywords || [] + end + + def load_exclude_keywords + @antenna.exclude_keywords || [] + end + + def keywords + Array(resource_params[:keywords]) + end + + def resource_params + params.permit(keywords: []) + end +end diff --git a/app/javascript/mastodon/actions/antennas.js b/app/javascript/mastodon/actions/antennas.js index 8cd1e7d90d..37e6b13802 100644 --- a/app/javascript/mastodon/actions/antennas.js +++ b/app/javascript/mastodon/actions/antennas.js @@ -43,6 +43,30 @@ 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_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_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_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_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_ADDER_RESET = 'ANTENNA_ADDER_RESET'; export const ANTENNA_ADDER_SETUP = 'ANTENNA_ADDER_SETUP'; @@ -292,6 +316,110 @@ export const addToAntennaFail = (antennaId, accountId, error) => ({ 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 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 removeFromAntennaEditor = accountId => (dispatch, getState) => { dispatch(removeFromAntenna(getState().getIn(['antennaEditor', 'antennaId']), accountId)); }; @@ -323,6 +451,60 @@ export const removeFromAntennaFail = (antennaId, accountId, error) => ({ 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 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 resetAntennaAdder = () => ({ type: ANTENNA_ADDER_RESET, }); diff --git a/app/javascript/mastodon/features/antenna_setting/components/text_list.jsx b/app/javascript/mastodon/features/antenna_setting/components/text_list.jsx new file mode 100644 index 0000000000..7f4d5b67ac --- /dev/null +++ b/app/javascript/mastodon/features/antenna_setting/components/text_list.jsx @@ -0,0 +1,96 @@ +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 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, + value: PropTypes.string.isRequired, + onRemove: PropTypes.func.isRequired, + }; + + handleRemove = () => { + this.props.onRemove(this.props.value); + }; + + render () { + const { icon, value } = this.props; + + return ( +