Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
f522998541
25 changed files with 599 additions and 11 deletions
50
app/controllers/api/v1/antennas/exclude_tags_controller.rb
Normal file
50
app/controllers/api/v1/antennas/exclude_tags_controller.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Antennas::ExcludeTagsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:lists' }
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_antenna
|
||||||
|
|
||||||
|
def create
|
||||||
|
new_tags = @antenna.exclude_tags || []
|
||||||
|
tags.map(&:id).each do |tag|
|
||||||
|
raise Mastodon::ValidationError, I18n.t('antennas.errors.duplicate_tag') if new_tags.include?(tag)
|
||||||
|
|
||||||
|
new_tags << tag
|
||||||
|
end
|
||||||
|
|
||||||
|
raise Mastodon::ValidationError, I18n.t('antennas.errors.limit.tags') if new_tags.size > Antenna::TAGS_PER_ANTENNA_LIMIT
|
||||||
|
|
||||||
|
@antenna.update!(exclude_tags: new_tags)
|
||||||
|
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
new_tags = @antenna.exclude_tags || []
|
||||||
|
new_tags -= exist_tags.pluck(:id)
|
||||||
|
|
||||||
|
@antenna.update!(exclude_tags: new_tags)
|
||||||
|
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_antenna
|
||||||
|
@antenna = Antenna.where(account: current_account).find(params[:antenna_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def tags
|
||||||
|
Tag.find_or_create_by_names(Array(resource_params[:tags]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def exist_tags
|
||||||
|
Tag.matching_name(Array(resource_params[:tags]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.permit(tags: [])
|
||||||
|
end
|
||||||
|
end
|
58
app/controllers/api/v1/antennas/tags_controller.rb
Normal file
58
app/controllers/api/v1/antennas/tags_controller.rb
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Antennas::TagsController < 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
|
||||||
|
@tags = load_tags
|
||||||
|
@exclude_tags = load_exclude_tags
|
||||||
|
render json: { tags: @tags, exclude_tags: @exclude_tags.pluck(:name) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
tags.each do |tag|
|
||||||
|
@antenna.antenna_tags.create!(tag: tag, exclude: false)
|
||||||
|
@antenna.update!(any_tags: false) if @antenna.any_tags
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
AntennaTag.where(antenna: @antenna, tag: exist_tags).destroy_all
|
||||||
|
@antenna.update!(any_tags: true) unless @antenna.antenna_tags.where(exclude: false).any?
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_antenna
|
||||||
|
@antenna = Antenna.where(account: current_account).find(params[:antenna_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_tags
|
||||||
|
@antenna.tags.pluck(:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_exclude_tags
|
||||||
|
Tag.where(id: @antenna.exclude_tags || [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def tags
|
||||||
|
Tag.find_or_create_by_names(Array(resource_params[:tags]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def exist_tags
|
||||||
|
Tag.matching_name(Array(resource_params[:tags]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.permit(tags: [])
|
||||||
|
end
|
||||||
|
end
|
|
@ -95,6 +95,26 @@ export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_REQUEST = 'ANTENNA_EDITOR_REM
|
||||||
export const ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS = 'ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS';
|
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_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_RESET = 'ANTENNA_ADDER_RESET';
|
||||||
export const ANTENNA_ADDER_SETUP = 'ANTENNA_ADDER_SETUP';
|
export const ANTENNA_ADDER_SETUP = 'ANTENNA_ADDER_SETUP';
|
||||||
|
|
||||||
|
@ -739,6 +759,139 @@ export const removeExcludeKeywordFromAntennaFail = (antennaId, keyword, error) =
|
||||||
error,
|
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 = () => ({
|
export const resetAntennaAdder = () => ({
|
||||||
type: ANTENNA_ADDER_RESET,
|
type: ANTENNA_ADDER_RESET,
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,7 +25,12 @@ import {
|
||||||
removeKeywordFromAntenna,
|
removeKeywordFromAntenna,
|
||||||
addKeywordToAntenna,
|
addKeywordToAntenna,
|
||||||
removeExcludeKeywordFromAntenna,
|
removeExcludeKeywordFromAntenna,
|
||||||
addExcludeKeywordToAntenna
|
addExcludeKeywordToAntenna,
|
||||||
|
fetchAntennaTags,
|
||||||
|
removeTagFromAntenna,
|
||||||
|
addTagToAntenna,
|
||||||
|
removeExcludeTagFromAntenna,
|
||||||
|
addExcludeTagToAntenna,
|
||||||
} from 'mastodon/actions/antennas';
|
} from 'mastodon/actions/antennas';
|
||||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||||
import { fetchLists } from 'mastodon/actions/lists';
|
import { fetchLists } from 'mastodon/actions/lists';
|
||||||
|
@ -48,8 +53,10 @@ const messages = defineMessages({
|
||||||
placeholder: { id: 'antennas.select.placeholder', defaultMessage: 'Select list' },
|
placeholder: { id: 'antennas.select.placeholder', defaultMessage: 'Select list' },
|
||||||
addDomainLabel: { id: 'antennas.add_domain_placeholder', defaultMessage: 'New domain' },
|
addDomainLabel: { id: 'antennas.add_domain_placeholder', defaultMessage: 'New domain' },
|
||||||
addKeywordLabel: { id: 'antennas.add_keyword_placeholder', defaultMessage: 'New keyword' },
|
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' },
|
addDomainTitle: { id: 'antennas.add_domain', defaultMessage: 'Add domain' },
|
||||||
addKeywordTitle: { id: 'antennas.add_keyword', defaultMessage: 'Add keyword' },
|
addKeywordTitle: { id: 'antennas.add_keyword', defaultMessage: 'Add keyword' },
|
||||||
|
addTagTitle: { id: 'antennas.add_tag', defaultMessage: 'Add tag' },
|
||||||
accounts: { id: 'antennas.accounts', defaultMessage: '{count} accounts' },
|
accounts: { id: 'antennas.accounts', defaultMessage: '{count} accounts' },
|
||||||
domains: { id: 'antennas.domains', defaultMessage: '{count} domains' },
|
domains: { id: 'antennas.domains', defaultMessage: '{count} domains' },
|
||||||
tags: { id: 'antennas.tags', defaultMessage: '{count} tags' },
|
tags: { id: 'antennas.tags', defaultMessage: '{count} tags' },
|
||||||
|
@ -62,6 +69,7 @@ const mapStateToProps = (state, props) => ({
|
||||||
lists: state.get('lists'),
|
lists: state.get('lists'),
|
||||||
domains: state.getIn(['antennas', props.params.id, 'domains']) || ImmutableMap(),
|
domains: state.getIn(['antennas', props.params.id, 'domains']) || ImmutableMap(),
|
||||||
keywords: state.getIn(['antennas', props.params.id, 'keywords']) || ImmutableMap(),
|
keywords: state.getIn(['antennas', props.params.id, 'keywords']) || ImmutableMap(),
|
||||||
|
tags: state.getIn(['antennas', props.params.id, 'tags']) || ImmutableMap(),
|
||||||
});
|
});
|
||||||
|
|
||||||
class AntennaSetting extends PureComponent {
|
class AntennaSetting extends PureComponent {
|
||||||
|
@ -79,6 +87,7 @@ class AntennaSetting extends PureComponent {
|
||||||
lists: ImmutablePropTypes.map,
|
lists: ImmutablePropTypes.map,
|
||||||
domains: ImmutablePropTypes.map,
|
domains: ImmutablePropTypes.map,
|
||||||
keywords: ImmutablePropTypes.map,
|
keywords: ImmutablePropTypes.map,
|
||||||
|
tags: ImmutablePropTypes.map,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,6 +96,8 @@ class AntennaSetting extends PureComponent {
|
||||||
excludeDomainName: '',
|
excludeDomainName: '',
|
||||||
keywordName: '',
|
keywordName: '',
|
||||||
excludeKeywordName: '',
|
excludeKeywordName: '',
|
||||||
|
tagName: '',
|
||||||
|
excludeTagName: '',
|
||||||
rangeRadioValue: null,
|
rangeRadioValue: null,
|
||||||
contentRadioValue: null,
|
contentRadioValue: null,
|
||||||
};
|
};
|
||||||
|
@ -118,6 +129,7 @@ class AntennaSetting extends PureComponent {
|
||||||
dispatch(fetchAntenna(id));
|
dispatch(fetchAntenna(id));
|
||||||
dispatch(fetchAntennaDomains(id));
|
dispatch(fetchAntennaDomains(id));
|
||||||
dispatch(fetchAntennaKeywords(id));
|
dispatch(fetchAntennaKeywords(id));
|
||||||
|
dispatch(fetchAntennaTags(id));
|
||||||
dispatch(fetchLists());
|
dispatch(fetchLists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +142,7 @@ class AntennaSetting extends PureComponent {
|
||||||
dispatch(fetchAntennaKeywords(id));
|
dispatch(fetchAntennaKeywords(id));
|
||||||
dispatch(fetchAntennaDomains(id));
|
dispatch(fetchAntennaDomains(id));
|
||||||
dispatch(fetchAntennaKeywords(id));
|
dispatch(fetchAntennaKeywords(id));
|
||||||
|
dispatch(fetchAntennaTags(id));
|
||||||
dispatch(fetchLists());
|
dispatch(fetchLists());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,6 +251,15 @@ class AntennaSetting extends PureComponent {
|
||||||
|
|
||||||
onKeywordRemove = (value) => this.props.dispatch(removeKeywordFromAntenna(this.props.params.id, value));
|
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 });
|
onExcludeDomainNameChanged = (value) => this.setState({ excludeDomainName: value });
|
||||||
|
|
||||||
onExcludeDomainAdd = () => {
|
onExcludeDomainAdd = () => {
|
||||||
|
@ -256,8 +278,17 @@ class AntennaSetting extends PureComponent {
|
||||||
|
|
||||||
onExcludeKeywordRemove = (value) => this.props.dispatch(removeExcludeKeywordFromAntenna(this.props.params.id, value));
|
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 () {
|
render () {
|
||||||
const { columnId, multiColumn, antenna, lists, domains, keywords, intl } = this.props;
|
const { columnId, multiColumn, antenna, lists, domains, keywords, tags, intl } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
const title = antenna ? antenna.get('title') : id;
|
const title = antenna ? antenna.get('title') : id;
|
||||||
|
@ -422,6 +453,19 @@ class AntennaSetting extends PureComponent {
|
||||||
|
|
||||||
<RadioPanel values={contentRadioValues} value={contentRadioValue} onChange={this.onContentRadioChanged} />
|
<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'
|
||||||
|
label={intl.formatMessage(messages.addTagLabel)}
|
||||||
|
title={intl.formatMessage(messages.addTagTitle)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{contentRadioValue.get('value') === 'keywords' && (
|
{contentRadioValue.get('value') === 'keywords' && (
|
||||||
<TextList
|
<TextList
|
||||||
onChange={this.onKeywordNameChanged}
|
onChange={this.onKeywordNameChanged}
|
||||||
|
@ -462,6 +506,17 @@ class AntennaSetting extends PureComponent {
|
||||||
label={intl.formatMessage(messages.addKeywordLabel)}
|
label={intl.formatMessage(messages.addKeywordLabel)}
|
||||||
title={intl.formatMessage(messages.addKeywordTitle)}
|
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'
|
||||||
|
label={intl.formatMessage(messages.addTagLabel)}
|
||||||
|
title={intl.formatMessage(messages.addTagTitle)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,8 +24,8 @@ import NewCircleForm from './components/new_circle_form';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.circles', defaultMessage: 'Circles' },
|
heading: { id: 'column.circles', defaultMessage: 'Circles' },
|
||||||
subheading: { id: 'circles.subheading', defaultMessage: 'Your circles' },
|
subheading: { id: 'circles.subheading', defaultMessage: 'Your circles' },
|
||||||
deleteMessage: { id: 'circles.subheading', defaultMessage: 'Your circles' },
|
deleteMessage: { id: 'confirmations.delete_circle.message', defaultMessage: 'Are you sure you want to permanently delete this circle?' },
|
||||||
deleteConfirm: { id: 'circles.subheading', defaultMessage: 'Your circles' },
|
deleteConfirm: { id: 'confirmations.delete_circle.confirm', defaultMessage: 'Delete' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOrderedCircles = createSelector([state => state.get('circles')], circles => {
|
const getOrderedCircles = createSelector([state => state.get('circles')], circles => {
|
||||||
|
|
|
@ -19,6 +19,11 @@ import {
|
||||||
ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS,
|
ANTENNA_EDITOR_ADD_EXCLUDE_KEYWORD_SUCCESS,
|
||||||
ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS,
|
ANTENNA_EDITOR_REMOVE_EXCLUDE_KEYWORD_SUCCESS,
|
||||||
ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS,
|
ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS,
|
||||||
|
ANTENNA_EDITOR_ADD_TAG_SUCCESS,
|
||||||
|
ANTENNA_EDITOR_REMOVE_TAG_SUCCESS,
|
||||||
|
ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS,
|
||||||
|
ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS,
|
||||||
|
ANTENNA_EDITOR_FETCH_TAGS_SUCCESS,
|
||||||
} from '../actions/antennas';
|
} from '../actions/antennas';
|
||||||
|
|
||||||
const initialState = ImmutableMap();
|
const initialState = ImmutableMap();
|
||||||
|
@ -85,6 +90,16 @@ export default function antennas(state = initialState, action) {
|
||||||
return state.updateIn([action.antennaId, 'keywords', 'exclude_keywords'], keywords => (ImmutableList(keywords || [])).filterNot(keyword => keyword === action.keyword));
|
return state.updateIn([action.antennaId, 'keywords', 'exclude_keywords'], keywords => (ImmutableList(keywords || [])).filterNot(keyword => keyword === action.keyword));
|
||||||
case ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS:
|
case ANTENNA_EDITOR_FETCH_KEYWORDS_SUCCESS:
|
||||||
return state.setIn([action.id, 'keywords'], ImmutableMap({ keywords: ImmutableList(action.keywords.keywords), exclude_keywords: ImmutableList(action.keywords.exclude_keywords) }));
|
return state.setIn([action.id, 'keywords'], ImmutableMap({ keywords: ImmutableList(action.keywords.keywords), exclude_keywords: ImmutableList(action.keywords.exclude_keywords) }));
|
||||||
|
case ANTENNA_EDITOR_ADD_TAG_SUCCESS:
|
||||||
|
return state.setIn([action.antennaId, 'tags_count'], state.getIn([action.antennaId, 'tags_count']) + 1).updateIn([action.antennaId, 'tags', 'tags'], tags => (ImmutableList(tags || [])).push(action.tag));
|
||||||
|
case ANTENNA_EDITOR_REMOVE_TAG_SUCCESS:
|
||||||
|
return state.setIn([action.antennaId, 'tags_count'], state.getIn([action.antennaId, 'tags_count']) - 1).updateIn([action.antennaId, 'tags', 'tags'], tags => (ImmutableList(tags || [])).filterNot(tag => tag === action.tag));
|
||||||
|
case ANTENNA_EDITOR_ADD_EXCLUDE_TAG_SUCCESS:
|
||||||
|
return state.updateIn([action.antennaId, 'tags', 'exclude_tags'], tags => (ImmutableList(tags || [])).push(action.tag));
|
||||||
|
case ANTENNA_EDITOR_REMOVE_EXCLUDE_TAG_SUCCESS:
|
||||||
|
return state.updateIn([action.antennaId, 'tags', 'exclude_tags'], tags => (ImmutableList(tags || [])).filterNot(tag => tag === action.tag));
|
||||||
|
case ANTENNA_EDITOR_FETCH_TAGS_SUCCESS:
|
||||||
|
return state.setIn([action.id, 'tags'], ImmutableMap({ tags: ImmutableList(action.tags.tags), exclude_tags: ImmutableList(action.tags.exclude_tags) }));
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@ const initialState = ImmutableMap({
|
||||||
suggestion_token: null,
|
suggestion_token: null,
|
||||||
suggestions: ImmutableList(),
|
suggestions: ImmutableList(),
|
||||||
default_privacy: 'public',
|
default_privacy: 'public',
|
||||||
|
stay_privacy: false,
|
||||||
default_searchability: 'private',
|
default_searchability: 'private',
|
||||||
default_sensitive: false,
|
default_sensitive: false,
|
||||||
default_language: 'en',
|
default_language: 'en',
|
||||||
|
@ -103,6 +104,7 @@ const initialState = ImmutableMap({
|
||||||
focusY: 0,
|
focusY: 0,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
}),
|
}),
|
||||||
|
posted_on_this_session: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialPoll = ImmutableMap({
|
const initialPoll = ImmutableMap({
|
||||||
|
@ -130,15 +132,23 @@ function clearAll(state) {
|
||||||
map.set('markdown', false);
|
map.set('markdown', false);
|
||||||
map.set('is_submitting', false);
|
map.set('is_submitting', false);
|
||||||
map.set('is_changing_upload', false);
|
map.set('is_changing_upload', false);
|
||||||
|
if (!state.get('stay_privacy') || state.get('in_reply_to') || !state.get('posted_on_this_session')) {
|
||||||
|
map.set('privacy', state.get('default_privacy'));
|
||||||
|
map.set('circle_id', null);
|
||||||
|
}
|
||||||
|
if (state.get('stay_privacy') && !state.get('in_reply_to')) {
|
||||||
|
map.set('default_privacy', state.get('privacy'));
|
||||||
|
}
|
||||||
|
if (!state.get('in_reply_to')) {
|
||||||
|
map.set('posted_on_this_session', true);
|
||||||
|
}
|
||||||
map.set('in_reply_to', null);
|
map.set('in_reply_to', null);
|
||||||
map.set('privacy', state.get('default_privacy'));
|
|
||||||
map.set('searchability', state.get('default_searchability'));
|
map.set('searchability', state.get('default_searchability'));
|
||||||
map.set('sensitive', state.get('default_sensitive'));
|
map.set('sensitive', state.get('default_sensitive'));
|
||||||
map.set('language', state.get('default_language'));
|
map.set('language', state.get('default_language'));
|
||||||
map.update('media_attachments', list => list.clear());
|
map.update('media_attachments', list => list.clear());
|
||||||
map.set('poll', null);
|
map.set('poll', null);
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
map.set('circle_id', null);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Antenna < ApplicationRecord
|
||||||
|
|
||||||
has_many :antenna_domains, inverse_of: :antenna, dependent: :destroy
|
has_many :antenna_domains, inverse_of: :antenna, dependent: :destroy
|
||||||
has_many :antenna_tags, inverse_of: :antenna, dependent: :destroy
|
has_many :antenna_tags, inverse_of: :antenna, dependent: :destroy
|
||||||
|
has_many :tags, through: :antenna_tags
|
||||||
has_many :antenna_accounts, inverse_of: :antenna, dependent: :destroy
|
has_many :antenna_accounts, inverse_of: :antenna, dependent: :destroy
|
||||||
has_many :accounts, through: :antenna_accounts
|
has_many :accounts, through: :antenna_accounts
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,10 @@ module HasUserSettings
|
||||||
settings['default_privacy'] || (account.locked? ? 'private' : 'public')
|
settings['default_privacy'] || (account.locked? ? 'private' : 'public')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setting_stay_privacy
|
||||||
|
settings['stay_privacy']
|
||||||
|
end
|
||||||
|
|
||||||
def setting_default_reblog_privacy
|
def setting_default_reblog_privacy
|
||||||
settings['default_reblog_privacy'] || 'unset'
|
settings['default_reblog_privacy'] || 'unset'
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,6 +21,7 @@ class UserSettings
|
||||||
setting :default_language, default: nil
|
setting :default_language, default: nil
|
||||||
setting :default_sensitive, default: false
|
setting :default_sensitive, default: false
|
||||||
setting :default_privacy, default: nil, in: %w(public public_unlisted login unlisted private)
|
setting :default_privacy, default: nil, in: %w(public public_unlisted login unlisted private)
|
||||||
|
setting :stay_privacy, default: false
|
||||||
setting :default_reblog_privacy, default: nil
|
setting :default_reblog_privacy, default: nil
|
||||||
setting :default_searchability, default: :direct, in: %w(public private direct limited)
|
setting :default_searchability, default: :direct, in: %w(public private direct limited)
|
||||||
setting :disallow_unlisted_public_searchability, default: false
|
setting :disallow_unlisted_public_searchability, default: false
|
||||||
|
|
|
@ -73,6 +73,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
||||||
if object.current_account
|
if object.current_account
|
||||||
store[:me] = object.current_account.id.to_s
|
store[:me] = object.current_account.id.to_s
|
||||||
store[:default_privacy] = object.visibility || object.current_account.user.setting_default_privacy
|
store[:default_privacy] = object.visibility || object.current_account.user.setting_default_privacy
|
||||||
|
store[:stay_privacy] = object.current_account.user.setting_stay_privacy
|
||||||
store[:default_searchability] = object.searchability || object.current_account.user.setting_default_searchability
|
store[:default_searchability] = object.searchability || object.current_account.user.setting_default_searchability
|
||||||
store[:default_sensitive] = object.current_account.user.setting_default_sensitive
|
store[:default_sensitive] = object.current_account.user.setting_default_sensitive
|
||||||
store[:default_language] = object.current_account.user.preferred_posting_language
|
store[:default_language] = object.current_account.user.preferred_posting_language
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
class REST::PreferencesSerializer < ActiveModel::Serializer
|
class REST::PreferencesSerializer < ActiveModel::Serializer
|
||||||
attribute :posting_default_privacy, key: 'posting:default:visibility'
|
attribute :posting_default_privacy, key: 'posting:default:visibility'
|
||||||
|
attribute :posting_stay_privacy, key: 'posting:keep:visibility'
|
||||||
attribute :posting_default_searchability, key: 'posting:default:searchability'
|
attribute :posting_default_searchability, key: 'posting:default:searchability'
|
||||||
attribute :posting_default_sensitive, key: 'posting:default:sensitive'
|
attribute :posting_default_sensitive, key: 'posting:default:sensitive'
|
||||||
attribute :posting_default_language, key: 'posting:default:language'
|
attribute :posting_default_language, key: 'posting:default:language'
|
||||||
|
@ -14,6 +15,10 @@ class REST::PreferencesSerializer < ActiveModel::Serializer
|
||||||
object.user.setting_default_privacy
|
object.user.setting_default_privacy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def posting_stay_privacy
|
||||||
|
object.user.setting_stay_privacy
|
||||||
|
end
|
||||||
|
|
||||||
def posting_default_searchability
|
def posting_default_searchability
|
||||||
object.user.setting_default_searchability
|
object.user.setting_default_searchability
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,7 +95,7 @@ class PostStatusService < BaseService
|
||||||
return unless @options[:visibility] == 'circle'
|
return unless @options[:visibility] == 'circle'
|
||||||
|
|
||||||
@circle = @options[:circle_id].present? && Circle.find(@options[:circle_id])
|
@circle = @options[:circle_id].present? && Circle.find(@options[:circle_id])
|
||||||
raise ArgumentError if @circle.nil?
|
raise ArgumentError if @circle.nil? || @circle.account_id != @account.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_sensitive_words
|
def process_sensitive_words
|
||||||
|
@ -169,6 +169,8 @@ class PostStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def postprocess_status!
|
def postprocess_status!
|
||||||
|
@account.user.update!(settings_attributes: { default_privacy: @options[:visibility] }) if @account.user&.setting_stay_privacy && !@status.reply? && %i(public public_unlisted login unlisted private).include?(@status.visibility.to_sym) && @status.visibility.to_s != @account.user&.setting_default_privacy
|
||||||
|
|
||||||
process_hashtags_service.call(@status)
|
process_hashtags_service.call(@status)
|
||||||
ProcessReferencesWorker.perform_async(@status.id, @reference_ids, [])
|
ProcessReferencesWorker.perform_async(@status.id, @reference_ids, [])
|
||||||
Trends.tags.register(@status)
|
Trends.tags.register(@status)
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
.fields-group.fields-row__column.fields-row__column-6
|
.fields-group.fields-row__column.fields-row__column-6
|
||||||
= ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: ->(locale) { locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language')
|
= ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: ->(locale) { locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= ff.input :stay_privacy, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_stay_privacy')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false
|
= ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false
|
||||||
|
|
||||||
|
|
|
@ -245,6 +245,7 @@ en:
|
||||||
setting_reject_unlisted_subscription: Reject sending unlisted posts to Misskey, Calckey
|
setting_reject_unlisted_subscription: Reject sending unlisted posts to Misskey, Calckey
|
||||||
setting_send_without_domain_blocks: Send your post to all server with administrator set as rejecting-post-server for protect you [DEPRECATED]
|
setting_send_without_domain_blocks: Send your post to all server with administrator set as rejecting-post-server for protect you [DEPRECATED]
|
||||||
setting_show_application: Disclose application used to send posts
|
setting_show_application: Disclose application used to send posts
|
||||||
|
setting_stay_privacy: Not change privacy after post
|
||||||
setting_stop_emoji_reaction_streaming: Disable stamp streamings
|
setting_stop_emoji_reaction_streaming: Disable stamp streamings
|
||||||
setting_system_font_ui: Use system's default font
|
setting_system_font_ui: Use system's default font
|
||||||
setting_theme: Site theme
|
setting_theme: Site theme
|
||||||
|
|
|
@ -247,13 +247,14 @@ ja:
|
||||||
setting_hide_network: 繋がりを隠す
|
setting_hide_network: 繋がりを隠す
|
||||||
setting_hide_recent_emojis: 絵文字ピッカーで最近使用した絵文字を隠す(リアクションデッキのみを表示する)
|
setting_hide_recent_emojis: 絵文字ピッカーで最近使用した絵文字を隠す(リアクションデッキのみを表示する)
|
||||||
setting_hide_statuses_count: 投稿数を隠す
|
setting_hide_statuses_count: 投稿数を隠す
|
||||||
|
setting_stay_privacy: 投稿時に公開範囲を保存する
|
||||||
setting_noai: 自分のコンテンツのAI学習利用に対して不快感を表明する
|
setting_noai: 自分のコンテンツのAI学習利用に対して不快感を表明する
|
||||||
setting_public_post_to_unlisted: サードパーティアプリから投稿するとき、公開投稿をローカル公開に変更する
|
|
||||||
setting_reduce_motion: アニメーションの動きを減らす
|
setting_reduce_motion: アニメーションの動きを減らす
|
||||||
setting_reject_public_unlisted_subscription: Misskey系サーバーに「ローカル公開」投稿を「フォロワーのみ」に変換して配送する
|
setting_reject_public_unlisted_subscription: Misskey系サーバーに「ローカル公開」投稿を「フォロワーのみ」に変換して配送する
|
||||||
setting_reject_unlisted_subscription: Misskey系サーバーに「未収載」投稿を「フォロワーのみ」に変換して配送する
|
setting_reject_unlisted_subscription: Misskey系サーバーに「未収載」投稿を「フォロワーのみ」に変換して配送する
|
||||||
setting_send_without_domain_blocks: 管理人の設定した配送停止設定を拒否する (非推奨)
|
setting_send_without_domain_blocks: 管理人の設定した配送停止設定を拒否する (非推奨)
|
||||||
setting_show_application: 送信したアプリを開示する
|
setting_show_application: 送信したアプリを開示する
|
||||||
|
setting_stay_privacy: 投稿時に公開範囲を保存する
|
||||||
setting_stop_emoji_reaction_streaming: スタンプのストリーミングを停止する
|
setting_stop_emoji_reaction_streaming: スタンプのストリーミングを停止する
|
||||||
setting_system_font_ui: システムのデフォルトフォントを使う
|
setting_system_font_ui: システムのデフォルトフォントを使う
|
||||||
setting_theme: サイトテーマ
|
setting_theme: サイトテーマ
|
||||||
|
|
|
@ -211,9 +211,11 @@ namespace :api, format: false do
|
||||||
resource :accounts, only: [:show, :create, :destroy], controller: 'antennas/accounts'
|
resource :accounts, only: [:show, :create, :destroy], controller: 'antennas/accounts'
|
||||||
resource :domains, only: [:show, :create, :destroy], controller: 'antennas/domains'
|
resource :domains, only: [:show, :create, :destroy], controller: 'antennas/domains'
|
||||||
resource :keywords, only: [:show, :create, :destroy], controller: 'antennas/keywords'
|
resource :keywords, only: [:show, :create, :destroy], controller: 'antennas/keywords'
|
||||||
|
resource :tags, only: [:show, :create, :destroy], controller: 'antennas/tags'
|
||||||
resource :exclude_accounts, only: [:show, :create, :destroy], controller: 'antennas/exclude_accounts'
|
resource :exclude_accounts, only: [:show, :create, :destroy], controller: 'antennas/exclude_accounts'
|
||||||
resource :exclude_domains, only: [:create, :destroy], controller: 'antennas/exclude_domains'
|
resource :exclude_domains, only: [:create, :destroy], controller: 'antennas/exclude_domains'
|
||||||
resource :exclude_keywords, only: [:create, :destroy], controller: 'antennas/exclude_keywords'
|
resource :exclude_keywords, only: [:create, :destroy], controller: 'antennas/exclude_keywords'
|
||||||
|
resource :exclude_tags, only: [:create, :destroy], controller: 'antennas/exclude_tags'
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :circles, only: [:index, :create, :show, :update, :destroy] do
|
resources :circles, only: [:index, :create, :show, :update, :destroy] do
|
||||||
|
|
5
spec/fabricators/antenna_account_fabricator.rb
Normal file
5
spec/fabricators/antenna_account_fabricator.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Fabricator(:antenna_account) do
|
||||||
|
exclude false
|
||||||
|
end
|
3
spec/fabricators/circle_account_fabricator.rb
Normal file
3
spec/fabricators/circle_account_fabricator.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Fabricator(:circle_account)
|
6
spec/fabricators/circle_fabricator.rb
Normal file
6
spec/fabricators/circle_fabricator.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Fabricator(:circle) do
|
||||||
|
account { Fabricate.build(:account) }
|
||||||
|
title 'MyString'
|
||||||
|
end
|
|
@ -47,11 +47,13 @@ RSpec.describe AccountStatusesFilter do
|
||||||
status!(:login)
|
status!(:login)
|
||||||
status!(:private)
|
status!(:private)
|
||||||
status!(:direct)
|
status!(:direct)
|
||||||
|
status!(:limited)
|
||||||
status_with_parent!(:public)
|
status_with_parent!(:public)
|
||||||
status_with_reblog!(:public)
|
status_with_reblog!(:public)
|
||||||
status_with_tag!(:public, tag)
|
status_with_tag!(:public, tag)
|
||||||
status_with_mention!(:direct)
|
status_with_mention!(:direct)
|
||||||
status_with_media_attachment!(:public)
|
status_with_media_attachment!(:public)
|
||||||
|
status_with_mention!(:limited)
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'filter params' do
|
shared_examples 'filter params' do
|
||||||
|
@ -123,7 +125,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
let(:current_account) { account }
|
let(:current_account) { account }
|
||||||
|
|
||||||
it 'returns everything' do
|
it 'returns everything' do
|
||||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private login unlisted public_unlisted public)
|
expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private login unlisted public_unlisted public limited)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns replies' do
|
it 'returns replies' do
|
||||||
|
@ -164,6 +166,30 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when there is a direct status mentioning other user' do
|
||||||
|
let!(:direct_status) { status_with_mention!(:direct) }
|
||||||
|
|
||||||
|
it 'not returns the direct status' do
|
||||||
|
expect(subject.results.pluck(:id)).to_not include(direct_status.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a limited status mentioning the non-follower' do
|
||||||
|
let!(:limited_status) { status_with_mention!(:limited, current_account) }
|
||||||
|
|
||||||
|
it 'returns the limited status' do
|
||||||
|
expect(subject.results.pluck(:id)).to include(limited_status.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a limited status mentioning other user' do
|
||||||
|
let!(:limited_status) { status_with_mention!(:limited) }
|
||||||
|
|
||||||
|
it 'not returns the limited status' do
|
||||||
|
expect(subject.results.pluck(:id)).to_not include(limited_status.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like 'filter params'
|
it_behaves_like 'filter params'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ RSpec.describe PublicFeed do
|
||||||
let!(:unlisted_status) { Fabricate(:status, visibility: :unlisted) }
|
let!(:unlisted_status) { Fabricate(:status, visibility: :unlisted) }
|
||||||
let!(:private_status) { Fabricate(:status, visibility: :private) }
|
let!(:private_status) { Fabricate(:status, visibility: :private) }
|
||||||
let!(:direct_status) { Fabricate(:status, visibility: :direct) }
|
let!(:direct_status) { Fabricate(:status, visibility: :direct) }
|
||||||
|
let!(:limited_status) { Fabricate(:status, visibility: :limited) }
|
||||||
|
|
||||||
it 'without user' do
|
it 'without user' do
|
||||||
expect(subject).to include(public_status.id)
|
expect(subject).to include(public_status.id)
|
||||||
|
@ -23,6 +24,7 @@ RSpec.describe PublicFeed do
|
||||||
expect(subject).to_not include(unlisted_status.id)
|
expect(subject).to_not include(unlisted_status.id)
|
||||||
expect(subject).to_not include(private_status.id)
|
expect(subject).to_not include(private_status.id)
|
||||||
expect(subject).to_not include(direct_status.id)
|
expect(subject).to_not include(direct_status.id)
|
||||||
|
expect(subject).to_not include(limited_status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with user' do
|
context 'with user' do
|
||||||
|
@ -34,6 +36,7 @@ RSpec.describe PublicFeed do
|
||||||
expect(subject).to_not include(unlisted_status.id)
|
expect(subject).to_not include(unlisted_status.id)
|
||||||
expect(subject).to_not include(private_status.id)
|
expect(subject).to_not include(private_status.id)
|
||||||
expect(subject).to_not include(direct_status.id)
|
expect(subject).to_not include(direct_status.id)
|
||||||
|
expect(subject).to_not include(limited_status.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -57,6 +57,34 @@ RSpec.describe StatusPolicy, type: :model do
|
||||||
expect(subject).to_not permit(viewer, status)
|
expect(subject).to_not permit(viewer, status)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'grants access when limited and account is viewer' do
|
||||||
|
status.visibility = :limited
|
||||||
|
|
||||||
|
expect(subject).to permit(status.account, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'grants access when limited and viewer is mentioned' do
|
||||||
|
status.visibility = :limited
|
||||||
|
status.mentions = [Fabricate(:mention, account: alice)]
|
||||||
|
|
||||||
|
expect(subject).to permit(alice, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'grants access when limited and non-owner viewer is mentioned and mentions are loaded' do
|
||||||
|
status.visibility = :limited
|
||||||
|
status.mentions = [Fabricate(:mention, account: bob)]
|
||||||
|
status.mentions.load
|
||||||
|
|
||||||
|
expect(subject).to permit(bob, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'denies access when limited and viewer is not mentioned' do
|
||||||
|
viewer = Fabricate(:account)
|
||||||
|
status.visibility = :limited
|
||||||
|
|
||||||
|
expect(subject).to_not permit(viewer, status)
|
||||||
|
end
|
||||||
|
|
||||||
it 'grants access when private and account is viewer' do
|
it 'grants access when private and account is viewer' do
|
||||||
status.visibility = :private
|
status.visibility = :private
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,23 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
let(:last_active_at) { Time.now.utc }
|
let(:last_active_at) { Time.now.utc }
|
||||||
let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob #hoge') }
|
let(:searchability) { 'public' }
|
||||||
|
let(:status) { Fabricate(:status, account: alice, visibility: visibility, searchability: searchability, text: 'Hello @bob #hoge') }
|
||||||
|
|
||||||
let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
||||||
let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account }
|
let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account }
|
||||||
let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
||||||
|
let!(:ohagi) { Fabricate(:user, current_sign_in_at: last_active_at).account }
|
||||||
|
|
||||||
|
let!(:list) { nil }
|
||||||
|
let!(:empty_list) { nil }
|
||||||
|
let!(:antenna) { nil }
|
||||||
|
let!(:empty_antenna) { nil }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
bob.follow!(alice)
|
bob.follow!(alice)
|
||||||
tom.follow!(alice)
|
tom.follow!(alice)
|
||||||
|
ohagi.follow!(bob)
|
||||||
|
|
||||||
ProcessMentionsService.new.call(status)
|
ProcessMentionsService.new.call(status)
|
||||||
ProcessHashtagsService.new.call(status)
|
ProcessHashtagsService.new.call(status)
|
||||||
|
@ -28,6 +36,26 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
HomeFeed.new(account).get(10).map(&:id)
|
HomeFeed.new(account).get(10).map(&:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list_feed_of(list)
|
||||||
|
ListFeed.new(list).get(10).map(&:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def antenna_feed_of(antenna)
|
||||||
|
AntennaFeed.new(antenna).get(10).map(&:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_with_account(owner, target_account)
|
||||||
|
list = Fabricate(:list, account: owner)
|
||||||
|
Fabricate(:list_account, list: list, account: target_account)
|
||||||
|
list
|
||||||
|
end
|
||||||
|
|
||||||
|
def antenna_with_account(owner, target_account)
|
||||||
|
antenna = Fabricate(:antenna, account: owner, any_accounts: false)
|
||||||
|
Fabricate(:antenna_account, antenna: antenna, account: target_account)
|
||||||
|
antenna
|
||||||
|
end
|
||||||
|
|
||||||
context 'when status is public' do
|
context 'when status is public' do
|
||||||
let(:visibility) { 'public' }
|
let(:visibility) { 'public' }
|
||||||
|
|
||||||
|
@ -49,6 +77,26 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
expect(redis).to have_received(:publish).with('timeline:public', anything)
|
expect(redis).to have_received(:publish).with('timeline:public', anything)
|
||||||
expect(redis).to have_received(:publish).with('timeline:public:local', anything)
|
expect(redis).to have_received(:publish).with('timeline:public:local', anything)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with list' do
|
||||||
|
let!(:list) { list_with_account(bob, alice) }
|
||||||
|
let!(:empty_list) { Fabricate(:list, account: tom) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(list_feed_of(list)).to include status.id
|
||||||
|
expect(list_feed_of(empty_list)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with antenna' do
|
||||||
|
let!(:antenna) { antenna_with_account(bob, alice) }
|
||||||
|
let!(:empty_antenna) { antenna_with_account(tom, bob) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when status is limited' do
|
context 'when status is limited' do
|
||||||
|
@ -70,6 +118,26 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||||
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with list' do
|
||||||
|
let!(:list) { list_with_account(bob, alice) }
|
||||||
|
let!(:empty_list) { list_with_account(tom, alice) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(list_feed_of(list)).to include status.id
|
||||||
|
expect(list_feed_of(empty_list)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with antenna' do
|
||||||
|
let!(:antenna) { antenna_with_account(bob, alice) }
|
||||||
|
let!(:empty_antenna) { antenna_with_account(tom, alice) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when status is private' do
|
context 'when status is private' do
|
||||||
|
@ -88,6 +156,73 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||||
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with list' do
|
||||||
|
let!(:list) { list_with_account(bob, alice) }
|
||||||
|
let!(:empty_list) { list_with_account(ohagi, bob) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(list_feed_of(list)).to include status.id
|
||||||
|
expect(list_feed_of(empty_list)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with antenna' do
|
||||||
|
let!(:antenna) { antenna_with_account(bob, alice) }
|
||||||
|
let!(:empty_antenna) { antenna_with_account(ohagi, alice) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when status is unlisted' do
|
||||||
|
let(:visibility) { 'unlisted' }
|
||||||
|
|
||||||
|
it 'is added to the home feed of its author' do
|
||||||
|
expect(home_feed_of(alice)).to include status.id
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is added to the home feed of a follower' do
|
||||||
|
expect(home_feed_of(bob)).to include status.id
|
||||||
|
expect(home_feed_of(tom)).to include status.id
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is not broadcast publicly' do
|
||||||
|
expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||||
|
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with list' do
|
||||||
|
let!(:list) { list_with_account(bob, alice) }
|
||||||
|
let!(:empty_list) { list_with_account(ohagi, bob) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(list_feed_of(list)).to include status.id
|
||||||
|
expect(list_feed_of(empty_list)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with antenna' do
|
||||||
|
let!(:antenna) { antenna_with_account(bob, alice) }
|
||||||
|
let!(:empty_antenna) { antenna_with_account(ohagi, alice) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with non-public searchability' do
|
||||||
|
let(:searchability) { 'direct' }
|
||||||
|
|
||||||
|
it 'hashtag-timeline is not detected' do
|
||||||
|
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||||
|
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when status is direct' do
|
context 'when status is direct' do
|
||||||
|
@ -109,5 +244,25 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||||
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with list' do
|
||||||
|
let!(:list) { list_with_account(bob, alice) }
|
||||||
|
let!(:empty_list) { list_with_account(ohagi, bob) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(list_feed_of(list)).to_not include status.id
|
||||||
|
expect(list_feed_of(empty_list)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with antenna' do
|
||||||
|
let!(:antenna) { antenna_with_account(bob, alice) }
|
||||||
|
let!(:empty_antenna) { antenna_with_account(ohagi, alice) }
|
||||||
|
|
||||||
|
it 'is added to the list feed of list follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to_not include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -168,7 +168,7 @@ RSpec.describe PostStatusService, type: :service do
|
||||||
status = subject.call(account, text: 'test status update')
|
status = subject.call(account, text: 'test status update')
|
||||||
|
|
||||||
expect(ProcessMentionsService).to have_received(:new)
|
expect(ProcessMentionsService).to have_received(:new)
|
||||||
expect(mention_service).to have_received(:call).with(status, limited_type: '', save_records: false)
|
expect(mention_service).to have_received(:call).with(status, limited_type: '', circle: nil, save_records: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'mutual visibility' do
|
it 'mutual visibility' do
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue