Merge upstream changesclosed-social-glitch-2
@ -0,0 +1,89 @@ | |||
import { debounce } from 'lodash'; | |||
import PropTypes from 'prop-types'; | |||
import React from 'react'; | |||
import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
import { connect } from 'react-redux'; | |||
import ColumnHeader from 'flavours/glitch/components/column_header'; | |||
import ScrollableList from 'flavours/glitch/components/scrollable_list'; | |||
import Column from 'flavours/glitch/features/ui/components/column'; | |||
import { Helmet } from 'react-helmet'; | |||
import Hashtag from 'flavours/glitch/components/hashtag'; | |||
import { expandFollowedHashtags, fetchFollowedHashtags } from 'flavours/glitch/actions/tags'; | |||
const messages = defineMessages({ | |||
heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' }, | |||
}); | |||
const mapStateToProps = state => ({ | |||
hashtags: state.getIn(['followed_tags', 'items']), | |||
isLoading: state.getIn(['followed_tags', 'isLoading'], true), | |||
hasMore: !!state.getIn(['followed_tags', 'next']), | |||
}); | |||
export default @connect(mapStateToProps) | |||
@injectIntl | |||
class FollowedTags extends ImmutablePureComponent { | |||
static propTypes = { | |||
params: PropTypes.object.isRequired, | |||
dispatch: PropTypes.func.isRequired, | |||
intl: PropTypes.object.isRequired, | |||
hashtags: ImmutablePropTypes.list, | |||
isLoading: PropTypes.bool, | |||
hasMore: PropTypes.bool, | |||
multiColumn: PropTypes.bool, | |||
}; | |||
componentDidMount() { | |||
this.props.dispatch(fetchFollowedHashtags()); | |||
}; | |||
handleLoadMore = debounce(() => { | |||
this.props.dispatch(expandFollowedHashtags()); | |||
}, 300, { leading: true }); | |||
render () { | |||
const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props; | |||
const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.' />; | |||
return ( | |||
<Column bindToDocument={!multiColumn}> | |||
<ColumnHeader | |||
icon='hashtag' | |||
title={intl.formatMessage(messages.heading)} | |||
showBackButton | |||
multiColumn={multiColumn} | |||
/> | |||
<ScrollableList | |||
scrollKey='followed_tags' | |||
emptyMessage={emptyMessage} | |||
hasMore={hasMore} | |||
isLoading={isLoading} | |||
onLoadMore={this.handleLoadMore} | |||
bindToDocument={!multiColumn} | |||
> | |||
{hashtags.map((hashtag) => ( | |||
<Hashtag | |||
key={hashtag.get('name')} | |||
name={hashtag.get('name')} | |||
to={`/tags/${hashtag.get('name')}`} | |||
withGraph={false} | |||
// Taken from ImmutableHashtag. Should maybe refactor ImmutableHashtag to accept more options? | |||
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1} | |||
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()} | |||
/> | |||
))} | |||
</ScrollableList> | |||
<Helmet> | |||
<meta name='robots' content='noindex' /> | |||
</Helmet> | |||
</Column> | |||
); | |||
} | |||
} |
@ -0,0 +1,42 @@ | |||
import { | |||
FOLLOWED_HASHTAGS_FETCH_REQUEST, | |||
FOLLOWED_HASHTAGS_FETCH_SUCCESS, | |||
FOLLOWED_HASHTAGS_FETCH_FAIL, | |||
FOLLOWED_HASHTAGS_EXPAND_REQUEST, | |||
FOLLOWED_HASHTAGS_EXPAND_SUCCESS, | |||
FOLLOWED_HASHTAGS_EXPAND_FAIL, | |||
} from 'flavours/glitch/actions/tags'; | |||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; | |||
const initialState = ImmutableMap({ | |||
items: ImmutableList(), | |||
isLoading: false, | |||
next: null, | |||
}); | |||
export default function followed_tags(state = initialState, action) { | |||
switch(action.type) { | |||
case FOLLOWED_HASHTAGS_FETCH_REQUEST: | |||
return state.set('isLoading', true); | |||
case FOLLOWED_HASHTAGS_FETCH_SUCCESS: | |||
return state.withMutations(map => { | |||
map.set('items', fromJS(action.followed_tags)); | |||
map.set('isLoading', false); | |||
map.set('next', action.next); | |||
}); | |||
case FOLLOWED_HASHTAGS_FETCH_FAIL: | |||
return state.set('isLoading', false); | |||
case FOLLOWED_HASHTAGS_EXPAND_REQUEST: | |||
return state.set('isLoading', true); | |||
case FOLLOWED_HASHTAGS_EXPAND_SUCCESS: | |||
return state.withMutations(map => { | |||
map.update('items', set => set.concat(fromJS(action.followed_tags))); | |||
map.set('isLoading', false); | |||
map.set('next', action.next); | |||
}); | |||
case FOLLOWED_HASHTAGS_EXPAND_FAIL: | |||
return state.set('isLoading', false); | |||
default: | |||
return state; | |||
} | |||
}; |
@ -0,0 +1,89 @@ | |||
import { debounce } from 'lodash'; | |||
import PropTypes from 'prop-types'; | |||
import React from 'react'; | |||
import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
import { connect } from 'react-redux'; | |||
import ColumnHeader from 'mastodon/components/column_header'; | |||
import ScrollableList from 'mastodon/components/scrollable_list'; | |||
import Column from 'mastodon/features/ui/components/column'; | |||
import { Helmet } from 'react-helmet'; | |||
import Hashtag from 'mastodon/components/hashtag'; | |||
import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags'; | |||
const messages = defineMessages({ | |||
heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' }, | |||
}); | |||
const mapStateToProps = state => ({ | |||
hashtags: state.getIn(['followed_tags', 'items']), | |||
isLoading: state.getIn(['followed_tags', 'isLoading'], true), | |||
hasMore: !!state.getIn(['followed_tags', 'next']), | |||
}); | |||
export default @connect(mapStateToProps) | |||
@injectIntl | |||
class FollowedTags extends ImmutablePureComponent { | |||
static propTypes = { | |||
params: PropTypes.object.isRequired, | |||
dispatch: PropTypes.func.isRequired, | |||
intl: PropTypes.object.isRequired, | |||
hashtags: ImmutablePropTypes.list, | |||
isLoading: PropTypes.bool, | |||
hasMore: PropTypes.bool, | |||
multiColumn: PropTypes.bool, | |||
}; | |||
componentDidMount() { | |||
this.props.dispatch(fetchFollowedHashtags()); | |||
}; | |||
handleLoadMore = debounce(() => { | |||
this.props.dispatch(expandFollowedHashtags()); | |||
}, 300, { leading: true }); | |||
render () { | |||
const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props; | |||
const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.' />; | |||
return ( | |||
<Column bindToDocument={!multiColumn}> | |||
<ColumnHeader | |||
icon='hashtag' | |||
title={intl.formatMessage(messages.heading)} | |||
showBackButton | |||
multiColumn={multiColumn} | |||
/> | |||
<ScrollableList | |||
scrollKey='followed_tags' | |||
emptyMessage={emptyMessage} | |||
hasMore={hasMore} | |||
isLoading={isLoading} | |||
onLoadMore={this.handleLoadMore} | |||
bindToDocument={!multiColumn} | |||
> | |||
{hashtags.map((hashtag) => ( | |||
<Hashtag | |||
key={hashtag.get('name')} | |||
name={hashtag.get('name')} | |||
to={`/tags/${hashtag.get('name')}`} | |||
withGraph={false} | |||
// Taken from ImmutableHashtag. Should maybe refactor ImmutableHashtag to accept more options? | |||
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1} | |||
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()} | |||
/> | |||
))} | |||
</ScrollableList> | |||
<Helmet> | |||
<meta name='robots' content='noindex' /> | |||
</Helmet> | |||
</Column> | |||
); | |||
} | |||
} |
@ -0,0 +1,42 @@ | |||
import { | |||
FOLLOWED_HASHTAGS_FETCH_REQUEST, | |||
FOLLOWED_HASHTAGS_FETCH_SUCCESS, | |||
FOLLOWED_HASHTAGS_FETCH_FAIL, | |||
FOLLOWED_HASHTAGS_EXPAND_REQUEST, | |||
FOLLOWED_HASHTAGS_EXPAND_SUCCESS, | |||
FOLLOWED_HASHTAGS_EXPAND_FAIL, | |||
} from 'mastodon/actions/tags'; | |||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; | |||
const initialState = ImmutableMap({ | |||
items: ImmutableList(), | |||
isLoading: false, | |||
next: null, | |||
}); | |||
export default function followed_tags(state = initialState, action) { | |||
switch(action.type) { | |||
case FOLLOWED_HASHTAGS_FETCH_REQUEST: | |||
return state.set('isLoading', true); | |||
case FOLLOWED_HASHTAGS_FETCH_SUCCESS: | |||
return state.withMutations(map => { | |||
map.set('items', fromJS(action.followed_tags)); | |||
map.set('isLoading', false); | |||
map.set('next', action.next); | |||
}); | |||
case FOLLOWED_HASHTAGS_FETCH_FAIL: | |||
return state.set('isLoading', false); | |||
case FOLLOWED_HASHTAGS_EXPAND_REQUEST: | |||
return state.set('isLoading', true); | |||
case FOLLOWED_HASHTAGS_EXPAND_SUCCESS: | |||
return state.withMutations(map => { | |||
map.update('items', set => set.concat(fromJS(action.followed_tags))); | |||
map.set('isLoading', false); | |||
map.set('next', action.next); | |||
}); | |||
case FOLLOWED_HASHTAGS_EXPAND_FAIL: | |||
return state.set('isLoading', false); | |||
default: | |||
return state; | |||
} | |||
}; |
@ -0,0 +1,78 @@ | |||
- target_acct = @report.target_account.acct | |||
- warning_action = { 'delete' => 'delete_statuses', 'mark_as_sensitive' => 'mark_statuses_as_sensitive' }.fetch(@moderation_action, @moderation_action) | |||
- content_for :page_title do | |||
= t('admin.reports.confirm_action', acct: target_acct) | |||
= form_tag admin_report_actions_path(@report), class: 'simple_form', method: :post do | |||
= hidden_field_tag :moderation_action, @moderation_action | |||
%p.hint= t("admin.reports.summary.action_preambles.#{@moderation_action}_html", acct: target_acct) | |||
%ul.hint | |||
%li.warning-hint= t("admin.reports.summary.actions.#{@moderation_action}_html", acct: target_acct) | |||
- if @moderation_action == 'suspend' | |||
%li.warning-hint= t('admin.reports.summary.delete_data_html', acct: target_acct) | |||
- if %w(silence suspend).include?(@moderation_action) | |||
%li.warning-hint= t('admin.reports.summary.close_reports_html', acct: target_acct) | |||
- else | |||
%li= t('admin.reports.summary.close_report', id: @report.id) | |||
%li= t('admin.reports.summary.record_strike_html', acct: target_acct) | |||
- if @report.target_account.local? && !@report.spam? | |||
%li= t('admin.reports.summary.send_email_html', acct: target_acct) | |||
%hr.spacer/ | |||
- if @report.target_account.local? | |||
%p.hint= t('admin.reports.summary.preview_preamble_html', acct: target_acct) | |||
.strike-card | |||
- unless warning_action == 'none' | |||
%p= t "user_mailer.warning.explanation.#{warning_action}", instance: Rails.configuration.x.local_domain | |||
.fields-group | |||
= text_area_tag :text, nil, placeholder: t('admin.reports.summary.warning_placeholder') | |||
- if !@report.other? | |||
%p | |||
%strong= t('user_mailer.warning.reason') | |||
= t("user_mailer.warning.categories.#{@report.category}") | |||
- if @report.violation? && @report.rule_ids.present? | |||
%ul.strike-card__rules | |||
- @report.rules.each do |rule| | |||
%li | |||
%span.strike-card__rules__text= rule.text | |||
- if @report.status_ids.present? && !@report.status_ids.empty? | |||
%p | |||
%strong= t('user_mailer.warning.statuses') | |||
.strike-card__statuses-list | |||
- status_map = @report.statuses.includes(:application, :media_attachments).index_by(&:id) | |||
- @report.status_ids.each do |status_id| | |||
.strike-card__statuses-list__item | |||
- if (status = status_map[status_id.to_i]) | |||
.one-liner | |||
= link_to short_account_status_url(@report.target_account, status_id), class: 'emojify' do | |||
= one_line_preview(status) | |||
- status.ordered_media_attachments.each do |media_attachment| | |||
%abbr{ title: media_attachment.description } | |||
= fa_icon 'link' | |||
= media_attachment.file_file_name | |||
.strike-card__statuses-list__item__meta | |||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) | |||
- unless status.application.nil? | |||
· | |||
= status.application.name | |||
- else | |||
.one-liner= t('disputes.strikes.status', id: status_id) | |||
.strike-card__statuses-list__item__meta | |||
= t('disputes.strikes.status_removed') | |||
%hr.spacer/ | |||
.actions | |||
= link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-tertiary' | |||
= button_tag t('admin.reports.confirm'), name: :confirm, class: 'button', type: :submit |
@ -1 +1 @@ | |||
Account.create_with(actor_type: 'Application', locked: true, username: ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain).find_or_create_by(id: -99) | |||
Account.create_with(actor_type: 'Application', locked: true, username: 'mastodon.internal').find_or_create_by(id: -99) |
@ -1,4 +1,4 @@ | |||
#domain,#severity,#reject_media,#reject_reports,#public_comment,#obfuscate | |||
bad.domain,silence,false,false,bad,false | |||
worse.domain,suspend,true,true,worse,true | |||
reject.media,noop,true,false,reject media,false | |||
bad.domain,silence,false,false,bad server,false | |||
worse.domain,suspend,true,true,worse server,true | |||
reject.media,noop,true,false,reject media and test unicode characters ♥,false |
@ -0,0 +1,3 @@ | |||
bad.domain | |||
worse.domain | |||
reject.media |