Add safety and privacy features
This commit is contained in:
parent
1885016a4c
commit
206b5dbf04
51 changed files with 492 additions and 477 deletions
|
@ -38,6 +38,8 @@ class Api::V1::AccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def follow
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_follow && !current_user.account.following?(@account)
|
||||
|
||||
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, delivery: params.key?(:delivery) ? truthy_param?(:delivery) : nil, with_rate_limit: true)
|
||||
options = @account.locked? || current_user.account.silenced? ? {} : {
|
||||
following_map: { @account.id => true },
|
||||
|
@ -56,6 +58,8 @@ class Api::V1::AccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def block
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_block
|
||||
|
||||
BlockService.new.call(current_user.account, @account)
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
@ -66,6 +70,8 @@ class Api::V1::AccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def unfollow
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_unfollow && (current_user.account.following?(@account) || !current_user.account.requested?(@account))
|
||||
|
||||
UnfollowService.new.call(current_user.account, @account)
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
|
|
@ -14,6 +14,8 @@ class Api::V1::DomainBlocksController < Api::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_domain_block
|
||||
|
||||
current_account.block_domain!(domain_block_params[:domain])
|
||||
AfterAccountDomainBlockWorker.perform_async(current_account.id, domain_block_params[:domain])
|
||||
render_empty
|
||||
|
|
|
@ -19,6 +19,8 @@ class Api::V1::NotificationsController < Api::BaseController
|
|||
end
|
||||
|
||||
def clear
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_clear_all_notifications
|
||||
|
||||
current_account.notifications.delete_all
|
||||
render_empty
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
before_action :set_status
|
||||
|
||||
def update
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_reactions
|
||||
|
||||
if EmojiReactionService.new.call(current_account, @status, params[:id]).present?
|
||||
@status = Status.include_expired.find(params[:status_id])
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
|||
before_action :set_status, only: [:create]
|
||||
|
||||
def create
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_reactions
|
||||
|
||||
FavouriteService.new.call(current_account, @status)
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
|
|
@ -11,6 +11,8 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
|||
override_rate_limit_headers :create, family: :statuses
|
||||
|
||||
def create
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_reactions
|
||||
|
||||
@status = ReblogService.new.call(current_account, @reblog, reblog_params)
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
|
|
|
@ -54,6 +54,8 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
raise Mastodon::NotPermittedError if current_user.setting_disable_post
|
||||
|
||||
@status = PostStatusService.new.call(current_user.account,
|
||||
text: status_params[:status],
|
||||
thread: @thread,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::Preferences::SafetyController < Settings::PreferencesController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
settings_preferences_safety_path
|
||||
end
|
||||
end
|
|
@ -101,6 +101,16 @@ class Settings::PreferencesController < Settings::BaseController
|
|||
:setting_confirm_domain_block,
|
||||
:setting_default_expires_in,
|
||||
:setting_default_expires_action,
|
||||
:setting_disable_post,
|
||||
:setting_disable_reactions,
|
||||
:setting_disable_follow,
|
||||
:setting_disable_unfollow,
|
||||
:setting_disable_block,
|
||||
:setting_disable_domain_block,
|
||||
:setting_disable_clear_all_notifications,
|
||||
:setting_disable_account_delete,
|
||||
:setting_prohibited_words,
|
||||
setting_prohibited_visibilities: [],
|
||||
notification_emails: %i(follow follow_request reblog favourite emoji_reaction status_reference mention digest report pending_account trending_tag),
|
||||
interactions: %i(must_be_follower must_be_following must_be_following_dm must_be_dm_to_send_email must_be_following_reference)
|
||||
)
|
||||
|
|
|
@ -204,7 +204,7 @@ export function followAccount(id, options = { reblogs: true, delivery: true }) {
|
|||
api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => {
|
||||
dispatch(followAccountSuccess(response.data, alreadyFollowing));
|
||||
}).catch(error => {
|
||||
dispatch(followAccountFail(error, locked));
|
||||
dispatch(followAccountFail(id, error, locked));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -216,7 +216,7 @@ export function unfollowAccount(id) {
|
|||
api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => {
|
||||
dispatch(unfollowAccountSuccess(response.data, getState().get('statuses')));
|
||||
}).catch(error => {
|
||||
dispatch(unfollowAccountFail(error));
|
||||
dispatch(unfollowAccountFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -239,9 +239,10 @@ export function followAccountSuccess(relationship, alreadyFollowing) {
|
|||
};
|
||||
};
|
||||
|
||||
export function followAccountFail(error, locked) {
|
||||
export function followAccountFail(id, error, locked) {
|
||||
return {
|
||||
type: ACCOUNT_FOLLOW_FAIL,
|
||||
id,
|
||||
error,
|
||||
locked,
|
||||
skipLoading: true,
|
||||
|
@ -265,9 +266,10 @@ export function unfollowAccountSuccess(relationship, statuses) {
|
|||
};
|
||||
};
|
||||
|
||||
export function unfollowAccountFail(error) {
|
||||
export function unfollowAccountFail(id, error) {
|
||||
return {
|
||||
type: ACCOUNT_UNFOLLOW_FAIL,
|
||||
id,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
|
@ -283,7 +285,7 @@ export function subscribeAccount(id, reblogs = true, list_id = null) {
|
|||
api(getState).post(`/api/v1/accounts/${id}/subscribe`, { reblogs, list_id }).then(response => {
|
||||
dispatch(subscribeAccountSuccess(response.data, alreadySubscribe));
|
||||
}).catch(error => {
|
||||
dispatch(subscribeAccountFail(error, locked));
|
||||
dispatch(subscribeAccountFail(id, error, locked));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -295,7 +297,7 @@ export function unsubscribeAccount(id, list_id = null) {
|
|||
api(getState).post(`/api/v1/accounts/${id}/unsubscribe`, { list_id }).then(response => {
|
||||
dispatch(unsubscribeAccountSuccess(response.data, getState().get('statuses')));
|
||||
}).catch(error => {
|
||||
dispatch(unsubscribeAccountFail(error));
|
||||
dispatch(unsubscribeAccountFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -318,9 +320,10 @@ export function subscribeAccountSuccess(relationship, alreadySubscribe) {
|
|||
};
|
||||
};
|
||||
|
||||
export function subscribeAccountFail(error, locked) {
|
||||
export function subscribeAccountFail(id, error, locked) {
|
||||
return {
|
||||
type: ACCOUNT_SUBSCRIBE_FAIL,
|
||||
id,
|
||||
error,
|
||||
locked,
|
||||
skipLoading: true,
|
||||
|
@ -344,9 +347,10 @@ export function unsubscribeAccountSuccess(relationship, statuses) {
|
|||
};
|
||||
};
|
||||
|
||||
export function unsubscribeAccountFail(error) {
|
||||
export function unsubscribeAccountFail(id, error) {
|
||||
return {
|
||||
type: ACCOUNT_UNSUBSCRIBE_FAIL,
|
||||
id,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
|
@ -392,9 +396,10 @@ export function blockAccountSuccess(relationship, statuses) {
|
|||
};
|
||||
};
|
||||
|
||||
export function blockAccountFail(error) {
|
||||
export function blockAccountFail(id, error) {
|
||||
return {
|
||||
type: ACCOUNT_BLOCK_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
@ -413,9 +418,10 @@ export function unblockAccountSuccess(relationship) {
|
|||
};
|
||||
};
|
||||
|
||||
export function unblockAccountFail(error) {
|
||||
export function unblockAccountFail(id, error) {
|
||||
return {
|
||||
type: ACCOUNT_UNBLOCK_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ import Permalink from './permalink';
|
|||
import IconButton from './icon_button';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me, show_followed_by, follow_button_to_list_adder } from '../initial_state';
|
||||
import { me, show_followed_by, follow_button_to_list_adder, disableFollow, disableUnfollow } from '../initial_state';
|
||||
import RelativeTimestamp from './relative_timestamp';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -132,9 +132,7 @@ class Account extends ImmutablePureComponent {
|
|||
subscribing_buttons = (
|
||||
<IconButton
|
||||
icon='rss-square'
|
||||
title={intl.formatMessage(
|
||||
subscribing ? messages.unsubscribe : messages.subscribe
|
||||
)}
|
||||
title={intl.formatMessage(subscribing ? messages.unsubscribe : messages.subscribe)}
|
||||
onClick={this.handleSubscribe}
|
||||
active={subscribing}
|
||||
no_delivery={subscribing && !subscribing_home}
|
||||
|
@ -144,10 +142,9 @@ class Account extends ImmutablePureComponent {
|
|||
if (!account.get('moved') || following) {
|
||||
following_buttons = (
|
||||
<IconButton
|
||||
disabled={following ? disableUnfollow : disableFollow}
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow
|
||||
)}
|
||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
passive={followed_by}
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
show_subscribe_button_on_timeline,
|
||||
show_followed_by,
|
||||
follow_button_to_list_adder,
|
||||
disableFollow,
|
||||
disableUnfollow,
|
||||
} from '../initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -75,10 +77,10 @@ class AccountActionBar extends ImmutablePureComponent {
|
|||
if (requested) {
|
||||
following_buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} active={followed_by} />;
|
||||
} else {
|
||||
following_buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} passive={followed_by} no_delivery={following && !delivery} />;
|
||||
following_buttons = <IconButton disabled={following ? disableUnfollow : disableFollow} icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} passive={followed_by} no_delivery={following && !delivery} />;
|
||||
}
|
||||
}
|
||||
buttons = <span>{subscribing_buttons}{following_buttons}</span>
|
||||
buttons = <span>{subscribing_buttons}{following_buttons}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -9,7 +9,7 @@ import Emoji from './emoji';
|
|||
import unicodeMapping from 'mastodon/features/emoji/emoji_unicode_mapping_light';
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import AnimatedNumber from 'mastodon/components/animated_number';
|
||||
import { reduceMotion, me } from 'mastodon/initial_state';
|
||||
import { reduceMotion, me, disableReactions } from 'mastodon/initial_state';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
import { isUserTouching } from 'mastodon/is_mobile';
|
||||
|
@ -110,7 +110,7 @@ class EmojiReaction extends ImmutablePureComponent {
|
|||
const { emojiReaction, status, myReaction } = this.props;
|
||||
|
||||
if (!emojiReaction) {
|
||||
return <Fragment></Fragment>;
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
let shortCode = emojiReaction.get('name');
|
||||
|
@ -122,7 +122,7 @@ class EmojiReaction extends ImmutablePureComponent {
|
|||
return (
|
||||
<Fragment>
|
||||
<div className='reactions-bar__item-wrapper' ref={this.setTargetRef}>
|
||||
<button className={classNames('reactions-bar__item', { active: myReaction })} disabled={status.get('emoji_reactioned') && !myReaction} onClick={this.handleClick} title={`:${shortCode}:`} style={this.props.style}>
|
||||
<button className={classNames('reactions-bar__item', { active: myReaction })} disabled={disableReactions || status.get('emoji_reactioned') && !myReaction} onClick={this.handleClick} title={`:${shortCode}:`} style={this.props.style}>
|
||||
<span className='reactions-bar__item__emoji'><Emoji hovered={this.state.hovered} emoji={emojiReaction.get('name')} emojiMap={this.props.emojiMap} url={emojiReaction.get('url')} static_url={emojiReaction.get('static_url')} /></span>
|
||||
<span className='reactions-bar__item__count'><AnimatedNumber value={emojiReaction.get('count')} /></span>
|
||||
</button>
|
||||
|
@ -157,11 +157,11 @@ export default class EmojiReactionsBar extends ImmutablePureComponent {
|
|||
|
||||
render () {
|
||||
const { status } = this.props;
|
||||
const emoji_reactions = status.get("emoji_reactions");
|
||||
const emoji_reactions = status.get('emoji_reactions');
|
||||
const visibleReactions = emoji_reactions.filter(x => x.get('count') > 0);
|
||||
|
||||
if (visibleReactions.isEmpty() ) {
|
||||
return <Fragment></Fragment>;
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
const styles = visibleReactions.map(emoji_reaction => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import IconButton from './icon_button';
|
|||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me, isStaff, show_bookmark_button, show_quote_button, enableReaction, compactReaction, enableStatusReference, maxReferences, matchVisibilityOfReferences, addReferenceModal } from '../initial_state';
|
||||
import { me, isStaff, show_bookmark_button, show_quote_button, enableReaction, compactReaction, enableStatusReference, maxReferences, matchVisibilityOfReferences, addReferenceModal, disablePost, disableReactions, disableBlock, disableDomainBlock } from '../initial_state';
|
||||
import classNames from 'classnames';
|
||||
import { openModal } from '../actions/modal';
|
||||
|
||||
|
@ -404,11 +404,15 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
if (writtenByMe) {
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
if (!disablePost) {
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
}
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
if (!disablePost) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
||||
|
@ -418,18 +422,18 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
if (relationship && relationship.get('blocking')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
|
||||
} else {
|
||||
} else if (!disableBlock) {
|
||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport });
|
||||
|
||||
if (domain) {
|
||||
menu.push(null);
|
||||
|
||||
if (relationship && relationship.get('domain_blocking')) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
|
||||
} else {
|
||||
} else if (!disableDomainBlock) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain });
|
||||
}
|
||||
}
|
||||
|
@ -474,22 +478,21 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar-button' disabled={expired} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
|
||||
{enableStatusReference && me && <IconButton className={classNames('status__action-bar-button link-icon', {referenced, 'context-referenced': contextReferenced})} animate disabled={referenceDisabled} active={referenced} pressed={referenced} title={intl.formatMessage(messages.reference)} icon='link' onClick={this.handleReferenceClick} />}
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate || expired} active={reblogged} pressed={reblogged} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate disabled={!favourited && expired} active={favourited} pressed={favourited} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
{show_quote_button && <IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus || expired} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} />}
|
||||
<IconButton className='status__action-bar-button' disabled={disablePost || expired} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
|
||||
{enableStatusReference && me && <IconButton className={classNames('status__action-bar-button link-icon', {referenced, 'context-referenced': contextReferenced})} animate disabled={disablePost || referenceDisabled} active={referenced} pressed={referenced} title={intl.formatMessage(messages.reference)} icon='link' onClick={this.handleReferenceClick} />}
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={disableReactions || !publicStatus && !reblogPrivate || expired} active={reblogged} pressed={reblogged} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate disabled={disableReactions || !favourited && expired} active={favourited} pressed={favourited} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
{show_quote_button && <IconButton className='status__action-bar-button' disabled={disablePost || anonymousAccess || !publicStatus || expired} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} />}
|
||||
{shareButton}
|
||||
{show_bookmark_button && <IconButton className='status__action-bar-button bookmark-icon' disabled={!bookmarked && expired} active={bookmarked} pressed={bookmarked} title={intl.formatMessage(bookmarked ? messages.removeBookmark : messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />}
|
||||
|
||||
{enableReaction && <div className={classNames('status__action-bar-dropdown', {'icon-button--with-counter': reactionsCounter})}>
|
||||
{enableReaction && <div className={classNames('status__action-bar-dropdown', { 'icon-button--with-counter': reactionsCounter })}>
|
||||
<ReactionPickerDropdownContainer
|
||||
scrollKey={scrollKey}
|
||||
disabled={expired}
|
||||
disabled={disableReactions || expired || anonymousAccess}
|
||||
active={emoji_reactioned}
|
||||
pressed={emoji_reactioned}
|
||||
className='status__action-bar-button'
|
||||
disabled={anonymousAccess}
|
||||
status={status}
|
||||
title={intl.formatMessage(messages.emoji_reaction)}
|
||||
icon='smile-o'
|
||||
|
|
|
@ -6,7 +6,7 @@ import Permalink from './permalink';
|
|||
import classnames from 'classnames';
|
||||
import PollContainer from 'mastodon/containers/poll_container';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
import { autoPlayGif, disableReactions } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
linkToAcct: { id: 'status.link_to_acct', defaultMessage: 'Link to @{acct}' },
|
||||
|
@ -268,7 +268,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
);
|
||||
|
||||
const pollContainer = (
|
||||
<PollContainer pollId={status.get('poll')} />
|
||||
<PollContainer pollId={status.get('poll')} disabled={disableReactions} />
|
||||
);
|
||||
|
||||
if (status.get('spoiler_text').length > 0) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
|
||||
import Button from 'mastodon/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { autoPlayGif, me, isStaff, show_followed_by, follow_button_to_list_adder } from 'mastodon/initial_state';
|
||||
import { autoPlayGif, me, isStaff, show_followed_by, follow_button_to_list_adder, disablePost, disableBlock, disableDomainBlock, disableFollow, disableUnfollow } from 'mastodon/initial_state';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
|
@ -191,7 +191,11 @@ class Header extends ImmutablePureComponent {
|
|||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
|
||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
actionBtn = <Button disabled={disableUnfollow || account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': !disableUnfollow, 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.unfollow)} onClick={this.props.onFollow} />;
|
||||
} else {
|
||||
actionBtn = <Button disabled={disableFollow || account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.follow)} onClick={this.props.onFollow} />;
|
||||
}
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
|
||||
}
|
||||
|
@ -208,9 +212,11 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
if (account.get('id') !== me) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||
menu.push(null);
|
||||
if (!disablePost) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.conversations, { name: account.get('username') }), action: this.props.onConversations });
|
||||
} else {
|
||||
|
@ -265,7 +271,7 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
if (account.getIn(['relationship', 'blocking'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
|
||||
} else {
|
||||
} else if (!disableBlock) {
|
||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
|
||||
}
|
||||
|
||||
|
@ -278,8 +284,10 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push(null);
|
||||
|
||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
|
||||
} else {
|
||||
} else if (!disableDomainBlock) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
|
||||
}
|
||||
}
|
||||
|
@ -318,9 +326,7 @@ class Header extends ImmutablePureComponent {
|
|||
subscribing_buttons = (
|
||||
<IconButton
|
||||
icon='rss-square'
|
||||
title={intl.formatMessage(
|
||||
subscribing ? messages.unsubscribe : messages.subscribe
|
||||
)}
|
||||
title={intl.formatMessage(subscribing ? messages.unsubscribe : messages.subscribe)}
|
||||
onClick={this.handleSubscribe}
|
||||
active={subscribing}
|
||||
no_delivery={subscribing && !subscribing_home}
|
||||
|
@ -330,10 +336,9 @@ class Header extends ImmutablePureComponent {
|
|||
if(!account.get('moved') || following) {
|
||||
following_buttons = (
|
||||
<IconButton
|
||||
disabled={following ? disableUnfollow : disableFollow}
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow
|
||||
)}
|
||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
passive={followed_by}
|
||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from 'mastodon/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { autoPlayGif, me, isStaff, show_followed_by, follow_button_to_list_adder } from 'mastodon/initial_state';
|
||||
import { autoPlayGif, me, isStaff, show_followed_by, follow_button_to_list_adder, disablePost, disableBlock, disableDomainBlock, disableFollow, disableUnfollow } from 'mastodon/initial_state';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
|
@ -22,6 +22,7 @@ const messages = defineMessages({
|
|||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
|
||||
conversations: { id: 'account.conversations', defaultMessage: 'Show conversations with @{name}' },
|
||||
conversations_all: { id: 'account.conversations_all', defaultMessage: 'Show all conversations' },
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||
direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
|
@ -172,7 +173,11 @@ class HeaderCommon extends ImmutablePureComponent {
|
|||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
|
||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
actionBtn = <Button disabled={disableUnfollow || account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': !disableUnfollow, 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.unfollow)} onClick={this.props.onFollow} />;
|
||||
} else {
|
||||
actionBtn = <Button disabled={disableFollow || account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.follow)} onClick={this.props.onFollow} />;
|
||||
}
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
|
||||
}
|
||||
|
@ -189,12 +194,16 @@ class HeaderCommon extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
if (account.get('id') !== me) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||
menu.push(null);
|
||||
}
|
||||
if (!disablePost) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.conversations, { name: account.get('username') }), action: this.props.onConversations });
|
||||
menu.push({ text: intl.formatMessage(messages.conversations, { name: account.get('username') }), action: this.props.onConversations });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.conversations_all), action: this.props.onConversations });
|
||||
}
|
||||
menu.push(null);
|
||||
|
||||
if ('share' in navigator) {
|
||||
|
@ -244,7 +253,7 @@ class HeaderCommon extends ImmutablePureComponent {
|
|||
|
||||
if (account.getIn(['relationship', 'blocking'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
|
||||
} else {
|
||||
} else if (!disableBlock) {
|
||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
|
||||
}
|
||||
|
||||
|
@ -254,11 +263,11 @@ class HeaderCommon extends ImmutablePureComponent {
|
|||
if (account.get('acct') !== account.get('username')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
|
||||
} else {
|
||||
} else if (!disableDomainBlock) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
|
||||
}
|
||||
}
|
||||
|
@ -295,9 +304,7 @@ class HeaderCommon extends ImmutablePureComponent {
|
|||
subscribing_buttons = (
|
||||
<IconButton
|
||||
icon='rss-square'
|
||||
title={intl.formatMessage(
|
||||
subscribing ? messages.unsubscribe : messages.subscribe
|
||||
)}
|
||||
title={intl.formatMessage(subscribing ? messages.unsubscribe : messages.subscribe)}
|
||||
onClick={this.handleSubscribe}
|
||||
active={subscribing}
|
||||
no_delivery={subscribing && !subscribing_home}
|
||||
|
@ -307,10 +314,9 @@ class HeaderCommon extends ImmutablePureComponent {
|
|||
if(!account.get('moved') || following) {
|
||||
following_buttons = (
|
||||
<IconButton
|
||||
disabled={following ? disableUnfollow : disableFollow}
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow
|
||||
)}
|
||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
passive={followed_by}
|
||||
|
|
|
@ -26,6 +26,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { length } from 'stringz';
|
||||
import { countableText } from '../util/counter';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { disablePost } from '../../../initial_state';
|
||||
|
||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||
|
||||
|
@ -57,6 +58,8 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
isChangingUpload: PropTypes.bool,
|
||||
isUploading: PropTypes.bool,
|
||||
isCircleUnselected: PropTypes.bool,
|
||||
prohibitedVisibilities: ImmutablePropTypes.set,
|
||||
prohibitedWords: ImmutablePropTypes.set,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onClearSuggestions: PropTypes.func.isRequired,
|
||||
|
@ -89,11 +92,13 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
canSubmit = () => {
|
||||
const { isSubmitting, isChangingUpload, isUploading, isCircleUnselected, anyMedia } = this.props;
|
||||
const { isSubmitting, isChangingUpload, isUploading, isCircleUnselected, anyMedia, prohibitedVisibilities, privacy, prohibitedWords, text, spoilerText } = this.props;
|
||||
const fulltext = this.getFulltextForCharacterCounting();
|
||||
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
|
||||
const noVisibility = prohibitedVisibilities?.includes(privacy);
|
||||
const ngWords = prohibitedWords.some( word => text.includes(word) || spoilerText?.includes(word) );
|
||||
|
||||
return !(isSubmitting || isUploading || isChangingUpload || isCircleUnselected || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia));
|
||||
return !(isSubmitting || isUploading || isChangingUpload || isCircleUnselected || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia) || noVisibility || ngWords);
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
|
@ -274,7 +279,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
<CircleDropdownContainer />
|
||||
|
||||
<div className='compose-form__publish'>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={!this.canSubmit()} block /></div>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disablePost || !this.canSubmit()} block /></div>
|
||||
</div>
|
||||
|
||||
<ReferenceStack />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
|
@ -22,6 +23,8 @@ const messages = defineMessages({
|
|||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
|
||||
limited_long: { id: 'privacy.limited.long', defaultMessage: 'Visible for circle users only' },
|
||||
none_short: { id: 'privacy.none.short', defaultMessage: 'None' },
|
||||
none_long: { id: 'privacy.none.long', defaultMessage: 'No visibility allowed' },
|
||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||
});
|
||||
|
||||
|
@ -160,6 +163,7 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
prohibitedVisibilities: ImmutablePropTypes.set,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
noDirect: PropTypes.bool,
|
||||
container: PropTypes.func,
|
||||
|
@ -235,28 +239,25 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { intl: { formatMessage } } = this.props;
|
||||
const { intl: { formatMessage }, prohibitedVisibilities } = this.props;
|
||||
|
||||
this.options = [
|
||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
{ icon: 'exchange', value: 'mutual', text: formatMessage(messages.mutual_short), meta: formatMessage(messages.mutual_long) },
|
||||
];
|
||||
|
||||
if (!this.props.noDirect) {
|
||||
this.options.push(
|
||||
...!this.props.noDirect && [
|
||||
{ icon: 'user-circle', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) },
|
||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
);
|
||||
}
|
||||
],
|
||||
].filter(option => !prohibitedVisibilities?.includes(option.value));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, container, intl } = this.props;
|
||||
const { open, placement } = this.state;
|
||||
|
||||
const valueOption = this.options.find(item => item.value === value);
|
||||
const valueOption = this.options.find(item => item.value === value) || { icon: 'ban', value: 'none', text: intl.formatMessage(messages.none_short), meta: intl.formatMessage(messages.none_long) };
|
||||
|
||||
return (
|
||||
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
|
@ -155,6 +156,7 @@ class SearchabilityDropdown extends React.PureComponent {
|
|||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
prohibitedVisibilities: ImmutablePropTypes.set,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
noDirect: PropTypes.bool,
|
||||
container: PropTypes.func,
|
||||
|
@ -230,13 +232,13 @@ class SearchabilityDropdown extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { intl: { formatMessage } } = this.props;
|
||||
const { intl: { formatMessage }, prohibitedVisibilities } = this.props;
|
||||
|
||||
this.options = [
|
||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
];
|
||||
].filter(option => !prohibitedVisibilities?.includes(option.value));
|
||||
}
|
||||
|
||||
render () {
|
||||
|
|
|
@ -27,6 +27,8 @@ const mapStateToProps = state => ({
|
|||
isCircleUnselected: state.getIn(['compose', 'privacy']) === 'limited' && state.getIn(['compose', 'reply_status', 'visibility']) !== 'limited' && !state.getIn(['compose', 'circle_id']),
|
||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
prohibitedVisibilities: state.getIn(['compose', 'prohibited_visibilities']),
|
||||
prohibitedWords: state.getIn(['compose', 'prohibited_words']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
|
|
|
@ -6,6 +6,7 @@ import { isUserTouching } from '../../../is_mobile';
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.getIn(['compose', 'privacy']),
|
||||
prohibitedVisibilities: state.getIn(['compose', 'prohibited_visibilities']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -6,6 +6,7 @@ import { isUserTouching } from '../../../is_mobile';
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.getIn(['compose', 'searchability']),
|
||||
prohibitedVisibilities: state.getIn(['compose', 'prohibited_visibilities']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
unsubscribeModal,
|
||||
show_followed_by,
|
||||
follow_button_to_list_adder,
|
||||
disableFollow,
|
||||
disableUnfollow,
|
||||
} from 'mastodon/initial_state';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import {
|
||||
|
@ -245,9 +247,7 @@ class AccountCard extends ImmutablePureComponent {
|
|||
subscribing_buttons = (
|
||||
<IconButton
|
||||
icon='rss-square'
|
||||
title={intl.formatMessage(
|
||||
subscribing ? messages.unsubscribe : messages.subscribe
|
||||
)}
|
||||
title={intl.formatMessage(subscribing ? messages.unsubscribe : messages.subscribe)}
|
||||
onClick={this.handleSubscribe}
|
||||
active={subscribing}
|
||||
no_delivery={subscribing && !subscribing_home}
|
||||
|
@ -257,10 +257,9 @@ class AccountCard extends ImmutablePureComponent {
|
|||
if(!account.get('moved') || following) {
|
||||
following_buttons = (
|
||||
<IconButton
|
||||
disabled={following ? disableUnfollow : disableFollow}
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow
|
||||
)}
|
||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
passive={followed_by}
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
unsubscribeModal,
|
||||
show_followed_by,
|
||||
follow_button_to_list_adder,
|
||||
disableFollow,
|
||||
disableUnfollow,
|
||||
} from 'mastodon/initial_state';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import {
|
||||
|
@ -260,9 +262,7 @@ class AccountCard extends ImmutablePureComponent {
|
|||
subscribing_buttons = (
|
||||
<IconButton
|
||||
icon='rss-square'
|
||||
title={intl.formatMessage(
|
||||
subscribing ? messages.unsubscribe : messages.subscribe
|
||||
)}
|
||||
title={intl.formatMessage(subscribing ? messages.unsubscribe : messages.subscribe)}
|
||||
onClick={this.handleSubscribe}
|
||||
active={subscribing}
|
||||
no_delivery={subscribing && !subscribing_home}
|
||||
|
@ -272,10 +272,9 @@ class AccountCard extends ImmutablePureComponent {
|
|||
if(!account.get('moved') || following) {
|
||||
following_buttons = (
|
||||
<IconButton
|
||||
disabled={following ? disableUnfollow : disableFollow}
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow
|
||||
)}
|
||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
passive={followed_by}
|
||||
|
|
|
@ -16,6 +16,8 @@ import {
|
|||
unsubscribeModal,
|
||||
show_followed_by,
|
||||
follow_button_to_list_adder,
|
||||
disableFollow,
|
||||
disableUnfollow,
|
||||
} from 'mastodon/initial_state';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import {
|
||||
|
@ -260,9 +262,7 @@ class GroupDetail extends ImmutablePureComponent {
|
|||
subscribing_buttons = (
|
||||
<IconButton
|
||||
icon='rss-square'
|
||||
title={intl.formatMessage(
|
||||
subscribing ? messages.unsubscribe : messages.subscribe
|
||||
)}
|
||||
title={intl.formatMessage(subscribing ? messages.unsubscribe : messages.subscribe)}
|
||||
onClick={this.handleSubscribe}
|
||||
active={subscribing}
|
||||
no_delivery={subscribing && !subscribing_home}
|
||||
|
@ -272,10 +272,9 @@ class GroupDetail extends ImmutablePureComponent {
|
|||
if(!account.get('moved') || following) {
|
||||
following_buttons = (
|
||||
<IconButton
|
||||
disabled={following ? disableUnfollow : disableFollow}
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow
|
||||
)}
|
||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
passive={followed_by}
|
||||
|
|
|
@ -7,7 +7,7 @@ import Avatar from '../../../components/avatar';
|
|||
import DisplayName from '../../../components/display_name';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import { unfollowAccount, followAccount } from '../../../actions/accounts';
|
||||
import { me, show_followed_by, unfollowModal } from '../../../initial_state';
|
||||
import { me, show_followed_by, unfollowModal, disableFollow, disableUnfollow } from '../../../initial_state';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -18,7 +18,7 @@ const messages = defineMessages({
|
|||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||
});
|
||||
|
||||
const MapStateToProps = (state) => ({
|
||||
const MapStateToProps = () => ({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
|
@ -68,7 +68,7 @@ class Account extends ImmutablePureComponent {
|
|||
if (requested) {
|
||||
buttons = <IconButton icon='hourglass' title={intl.formatMessage(messages.requested)} active={followed_by} onClick={this.handleFollow} />;
|
||||
} else {
|
||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} passive={followed_by} no_delivery={following && !delivery} />;
|
||||
buttons = <IconButton disabled={following ? disableUnfollow : disableFollow} icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} passive={followed_by} no_delivery={following && !delivery} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import { setupListAdder, resetListAdder } from '../../actions/lists';
|
||||
import { createSelector } from 'reselect';
|
||||
import { makeGetAccount } from '../../selectors';
|
||||
|
@ -34,7 +33,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class ListAdder extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -57,21 +55,21 @@ class ListAdder extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, listIds, intl } = this.props;
|
||||
const { account, listIds } = this.props;
|
||||
|
||||
const following = account.getIn(['relationship', 'following']);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal list-adder'>
|
||||
<div className='list-adder__account'>
|
||||
<Account account={account} intl={intl} />
|
||||
<Account account={account} />
|
||||
</div>
|
||||
|
||||
<NewListForm />
|
||||
|
||||
<div className='list-adder__lists'>
|
||||
<Home account={account} disabled={following} intl={intl} />
|
||||
{listIds.map(ListId => <List key={ListId} account={account} listId={ListId} disabled={following} intl={intl} />)}
|
||||
<Home account={account} disabled={following} />
|
||||
{listIds.map(ListId => <List key={ListId} account={account} listId={ListId} disabled={following} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
import ClearColumnButton from './clear_column_button';
|
||||
import GrantPermissionButton from './grant_permission_button';
|
||||
import SettingToggle from './setting_toggle';
|
||||
import { enableReaction, enableStatusReference } from 'mastodon/initial_state';
|
||||
import { enableReaction, enableStatusReference, disableClearAllNotifications } from 'mastodon/initial_state';
|
||||
|
||||
export default class ColumnSettings extends React.PureComponent {
|
||||
|
||||
|
@ -52,9 +52,9 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className='column-settings__row'>
|
||||
{!disableClearAllNotifications && <div className='column-settings__row'>
|
||||
<ClearColumnButton onClick={onClear} />
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<div role='group' aria-labelledby='notifications-unread-markers'>
|
||||
<span id='notifications-unread-markers' className='column-settings__section'>
|
||||
|
|
|
@ -5,7 +5,7 @@ import IconButton from '../../../components/icon_button';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { me, isStaff, show_quote_button, enableReaction, enableStatusReference, maxReferences, matchVisibilityOfReferences, addReferenceModal } from '../../../initial_state';
|
||||
import { me, isStaff, show_quote_button, enableReaction, enableStatusReference, maxReferences, matchVisibilityOfReferences, addReferenceModal, disablePost, disableReactions, disableBlock, disableDomainBlock } from '../../../initial_state';
|
||||
import classNames from 'classnames';
|
||||
import ReactionPickerDropdownContainer from 'mastodon/containers/reaction_picker_dropdown_container';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
|
@ -295,11 +295,11 @@ class ActionBar extends React.PureComponent {
|
|||
const reblogsCount = status.get('reblogs_count');
|
||||
const referredByCount = status.get('status_referred_by_count');
|
||||
const favouritesCount = status.get('favourites_count');
|
||||
const [ _, domain ] = account.get('acct').split('@');
|
||||
const [ , domain ] = account.get('acct').split('@');
|
||||
|
||||
const expires_at = status.get('expires_at')
|
||||
const expires_date = expires_at && new Date(expires_at)
|
||||
const expired = expires_date && expires_date.getTime() < intl.now()
|
||||
const expires_at = status.get('expires_at');
|
||||
const expires_date = expires_at && new Date(expires_at);
|
||||
const expired = expires_date && expires_date.getTime() < intl.now();
|
||||
|
||||
let menu = [];
|
||||
|
||||
|
@ -350,12 +350,18 @@ class ActionBar extends React.PureComponent {
|
|||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
|
||||
if (!disablePost) {
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
}
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
if (!disablePost) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
||||
|
@ -365,18 +371,18 @@ class ActionBar extends React.PureComponent {
|
|||
|
||||
if (relationship && relationship.get('blocking')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
|
||||
} else {
|
||||
} else if (!disableBlock) {
|
||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
||||
|
||||
if (domain) {
|
||||
menu.push(null);
|
||||
|
||||
if (relationship && relationship.get('domain_blocking')) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
|
||||
} else {
|
||||
} else if (!disableDomainBlock) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain });
|
||||
}
|
||||
}
|
||||
|
@ -416,17 +422,17 @@ class ActionBar extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<div className='detailed-status__action-bar'>
|
||||
<div className='detailed-status__button'><IconButton disabled={expired} title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
|
||||
{enableStatusReference && me && <div className='detailed-status__button'><IconButton className={classNames('link-icon', {referenced, 'context-referenced': contextReferenced})} animate disabled={referenceDisabled} active={referenced} pressed={referenced} title={intl.formatMessage(messages.reference)} icon='link' onClick={this.handleReferenceClick} /></div>}
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate || expired} active={reblogged} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={favourited} disabled={!favourited && expired} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
{show_quote_button && <div className='detailed-status__button'><IconButton disabled={!publicStatus || expired} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} /></div>}
|
||||
<div className='detailed-status__button'><IconButton disabled={disablePost || expired} title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
|
||||
{enableStatusReference && me && <div className='detailed-status__button'><IconButton className={classNames('link-icon', {referenced, 'context-referenced': contextReferenced})} animate disabled={disablePost || referenceDisabled} active={referenced} pressed={referenced} title={intl.formatMessage(messages.reference)} icon='link' onClick={this.handleReferenceClick} /></div>}
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={disableReactions || !publicStatus && !reblogPrivate || expired} active={reblogged} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={favourited} disabled={disableReactions || !favourited && expired} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
{show_quote_button && <div className='detailed-status__button'><IconButton disabled={disablePost || !publicStatus || expired} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} /></div>}
|
||||
{shareButton}
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={bookmarked} disabled={!bookmarked && expired} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
|
||||
|
||||
{enableReaction && <div className='detailed-status__action-bar-dropdown'>
|
||||
<ReactionPickerDropdownContainer
|
||||
disabled={expired}
|
||||
disabled={disableReactions || expired}
|
||||
active={emoji_reactioned}
|
||||
pressed={emoji_reactioned}
|
||||
className='status__action-bar-button'
|
||||
|
|
|
@ -29,6 +29,7 @@ const messages = defineMessages({
|
|||
const mapStateToProps = state => {
|
||||
return {
|
||||
privacy: state.getIn(['boosts', 'new', 'privacy']),
|
||||
prohibitedVisibilities: state.getIn(['compose', 'prohibited_visibilities']),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -40,7 +41,7 @@ const mapDispatchToProps = dispatch => {
|
|||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
export default @connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })
|
||||
@injectIntl
|
||||
class BoostModal extends ImmutablePureComponent {
|
||||
|
||||
|
@ -88,7 +89,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { status, privacy, intl } = this.props;
|
||||
const { status, privacy, prohibitedVisibilities, intl } = this.props;
|
||||
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
|
||||
|
||||
const visibilityIconInfo = {
|
||||
|
@ -138,11 +139,12 @@ class BoostModal extends ImmutablePureComponent {
|
|||
<PrivacyDropdown
|
||||
noDirect
|
||||
value={privacy}
|
||||
prohibitedVisibilities={prohibitedVisibilities}
|
||||
container={this._findContainer}
|
||||
onChange={this.props.onChangeBoostPrivacy}
|
||||
/>
|
||||
)}
|
||||
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
|
||||
<Button disabled={prohibitedVisibilities?.includes(privacy)} text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -55,5 +55,13 @@ export const enableEmptyColumn = getMeta('enable_empty_column');
|
|||
export const showReloadButton = getMeta('show_reload_button');
|
||||
export const defaultColumnWidth = getMeta('default_column_width');
|
||||
export const pickerEmojiSize = getMeta('picker_emoji_size');
|
||||
export const disablePost = getMeta('disable_post');
|
||||
export const disableReactions = getMeta('disable_reactions');
|
||||
export const disableFollow = getMeta('disable_follow');
|
||||
export const disableUnfollow = getMeta('disable_unfollow');
|
||||
export const disableBlock = getMeta('disable_block');
|
||||
export const disableDomainBlock = getMeta('disable_domain_block');
|
||||
export const disableClearAllNotifications = getMeta('disable_clear_all_notifications');
|
||||
export const disableAccountDelete = getMeta('disable_account_delete');
|
||||
|
||||
export default initialState;
|
||||
|
|
|
@ -495,6 +495,8 @@
|
|||
"privacy.limited.short": "Circle",
|
||||
"privacy.mutual.long": "Visible for mutual followers only (Supported servers only)",
|
||||
"privacy.mutual.short": "Mutual-followers-only",
|
||||
"privacy.none.long": "No visibility allowed",
|
||||
"privacy.none.short": "None",
|
||||
"privacy.private.long": "Visible for followers only",
|
||||
"privacy.private.short": "Followers-only",
|
||||
"privacy.public.long": "Visible for all, shown in public timelines",
|
||||
|
|
|
@ -495,6 +495,8 @@
|
|||
"privacy.limited.short": "サークル",
|
||||
"privacy.mutual.long": "相互フォローのみ閲覧可(対応サーバのみ)",
|
||||
"privacy.mutual.short": "相互フォロー限定",
|
||||
"privacy.none.long": "許可された公開範囲なし",
|
||||
"privacy.none.short": "なし",
|
||||
"privacy.private.long": "フォロワーのみ閲覧可",
|
||||
"privacy.private.short": "フォロワー限定",
|
||||
"privacy.public.long": "誰でも閲覧可、公開TLに表示",
|
||||
|
|
|
@ -111,6 +111,8 @@ const initialState = ImmutableMap({
|
|||
expires_action: 'mark',
|
||||
references: ImmutableSet(),
|
||||
context_references: ImmutableSet(),
|
||||
prohibited_visibilities: ImmutableSet(),
|
||||
prohibited_words: ImmutableSet(),
|
||||
});
|
||||
|
||||
const initialPoll = ImmutableMap({
|
||||
|
@ -266,6 +268,14 @@ const hydrate = (state, hydratedState) => {
|
|||
state = state.set('text', hydratedState.get('text'));
|
||||
}
|
||||
|
||||
if (hydratedState.has('prohibited_visibilities')) {
|
||||
state = state.set('prohibited_visibilities', hydratedState.get('prohibited_visibilities').toSet());
|
||||
}
|
||||
|
||||
if (hydratedState.has('prohibited_words')) {
|
||||
state = state.set('prohibited_words', hydratedState.get('prohibited_words').toSet());
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
|
|
|
@ -134,6 +134,11 @@ a.table-action-link {
|
|||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:disabled, &:disabled:hover {
|
||||
color: darken($ui-primary-color, 30%);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-table {
|
||||
|
|
|
@ -24,341 +24,107 @@ class UserSettingsDecorator
|
|||
setting_enable_reaction
|
||||
).freeze
|
||||
|
||||
NESTED_KEYS = %w(
|
||||
notification_emails
|
||||
interactions
|
||||
).freeze
|
||||
|
||||
BOOLEAN_KEYS = %w(
|
||||
default_sensitive
|
||||
unfollow_modal
|
||||
unsubscribe_modal
|
||||
boost_modal
|
||||
delete_modal
|
||||
post_reference_modal
|
||||
add_reference_modal
|
||||
unselect_reference_modal
|
||||
auto_play_gif
|
||||
expand_spoiers
|
||||
reduce_motion
|
||||
disable_swiping
|
||||
system_font_ui
|
||||
noindex
|
||||
hide_network
|
||||
aggregate_reblogs
|
||||
show_application
|
||||
advanced_layout
|
||||
use_blurhash
|
||||
use_pending_items
|
||||
trends
|
||||
crop_images
|
||||
confirm_domain_block
|
||||
show_follow_button_on_timeline
|
||||
show_subscribe_button_on_timeline
|
||||
show_followed_by
|
||||
follow_button_to_list_adder
|
||||
show_navigation_panel
|
||||
show_quote_button
|
||||
show_bookmark_button
|
||||
show_target
|
||||
place_tab_bar_at_bottom
|
||||
show_tab_bar_label
|
||||
enable_limited_timeline
|
||||
enable_reaction
|
||||
compact_reaction
|
||||
show_reply_tree_button
|
||||
hide_statuses_count
|
||||
hide_following_count
|
||||
hide_followers_count
|
||||
disable_joke_appearance
|
||||
theme_public
|
||||
enable_status_reference
|
||||
match_visibility_of_references
|
||||
hexagon_avatar
|
||||
enable_empty_column
|
||||
hide_bot_on_public_timeline
|
||||
confirm_follow_from_bot
|
||||
show_reload_button
|
||||
disable_post
|
||||
disable_reactions
|
||||
disable_follow
|
||||
disable_unfollow
|
||||
disable_block
|
||||
disable_domain_block
|
||||
disable_clear_all_notifications
|
||||
disable_account_delete
|
||||
).freeze
|
||||
|
||||
STRING_KEYS = %w(
|
||||
default_privacy
|
||||
default_language
|
||||
theme
|
||||
display_media
|
||||
new_features_policy
|
||||
theme_instance_ticker
|
||||
content_font_size
|
||||
info_font_size
|
||||
content_emoji_reaction_size
|
||||
emoji_scale
|
||||
picker_emoji_size
|
||||
default_search_searchability
|
||||
default_column_width
|
||||
default_expires_in
|
||||
default_expires_action
|
||||
prohibited_visibilities
|
||||
prohibited_words
|
||||
).freeze
|
||||
|
||||
def profile_change?
|
||||
settings.keys.intersection(PROFILE_KEYS).any?
|
||||
end
|
||||
|
||||
def process_update
|
||||
user.settings['notification_emails'] = merged_notification_emails if change?('notification_emails')
|
||||
user.settings['interactions'] = merged_interactions if change?('interactions')
|
||||
user.settings['default_privacy'] = default_privacy_preference if change?('setting_default_privacy')
|
||||
user.settings['default_sensitive'] = default_sensitive_preference if change?('setting_default_sensitive')
|
||||
user.settings['default_language'] = default_language_preference if change?('setting_default_language')
|
||||
user.settings['unfollow_modal'] = unfollow_modal_preference if change?('setting_unfollow_modal')
|
||||
user.settings['unsubscribe_modal'] = unsubscribe_modal_preference if change?('setting_unsubscribe_modal')
|
||||
user.settings['boost_modal'] = boost_modal_preference if change?('setting_boost_modal')
|
||||
user.settings['delete_modal'] = delete_modal_preference if change?('setting_delete_modal')
|
||||
user.settings['post_reference_modal'] = post_reference_modal_preference if change?('setting_post_reference_modal')
|
||||
user.settings['add_reference_modal'] = add_reference_modal_preference if change?('setting_add_reference_modal')
|
||||
user.settings['unselect_reference_modal'] = unselect_reference_modal_preference if change?('setting_unselect_reference_modal')
|
||||
user.settings['auto_play_gif'] = auto_play_gif_preference if change?('setting_auto_play_gif')
|
||||
user.settings['display_media'] = display_media_preference if change?('setting_display_media')
|
||||
user.settings['expand_spoilers'] = expand_spoilers_preference if change?('setting_expand_spoilers')
|
||||
user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion')
|
||||
user.settings['disable_swiping'] = disable_swiping_preference if change?('setting_disable_swiping')
|
||||
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
|
||||
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
|
||||
user.settings['theme'] = theme_preference if change?('setting_theme')
|
||||
user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
|
||||
user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
|
||||
user.settings['show_application'] = show_application_preference if change?('setting_show_application')
|
||||
user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout')
|
||||
user.settings['use_blurhash'] = use_blurhash_preference if change?('setting_use_blurhash')
|
||||
user.settings['use_pending_items'] = use_pending_items_preference if change?('setting_use_pending_items')
|
||||
user.settings['trends'] = trends_preference if change?('setting_trends')
|
||||
user.settings['crop_images'] = crop_images_preference if change?('setting_crop_images')
|
||||
user.settings['confirm_domain_block'] = confirm_domain_block_preference if change?('setting_confirm_domain_block')
|
||||
user.settings['show_follow_button_on_timeline'] = show_follow_button_on_timeline_preference if change?('setting_show_follow_button_on_timeline')
|
||||
user.settings['show_subscribe_button_on_timeline'] = show_subscribe_button_on_timeline_preference if change?('setting_show_subscribe_button_on_timeline')
|
||||
user.settings['show_followed_by'] = show_followed_by_preference if change?('setting_show_followed_by')
|
||||
user.settings['follow_button_to_list_adder'] = follow_button_to_list_adder_preference if change?('setting_follow_button_to_list_adder')
|
||||
user.settings['show_navigation_panel'] = show_navigation_panel_preference if change?('setting_show_navigation_panel')
|
||||
user.settings['show_quote_button'] = show_quote_button_preference if change?('setting_show_quote_button')
|
||||
user.settings['show_bookmark_button'] = show_bookmark_button_preference if change?('setting_show_bookmark_button')
|
||||
user.settings['show_target'] = show_target_preference if change?('setting_show_target')
|
||||
user.settings['place_tab_bar_at_bottom'] = place_tab_bar_at_bottom_preference if change?('setting_place_tab_bar_at_bottom')
|
||||
user.settings['show_tab_bar_label'] = show_tab_bar_label_preference if change?('setting_show_tab_bar_label')
|
||||
user.settings['enable_limited_timeline'] = enable_limited_timeline_preference if change?('setting_enable_limited_timeline')
|
||||
user.settings['enable_reaction'] = enable_reaction_preference if change?('setting_enable_reaction')
|
||||
user.settings['compact_reaction'] = compact_reaction_preference if change?('setting_compact_reaction')
|
||||
user.settings['show_reply_tree_button'] = show_reply_tree_button_preference if change?('setting_show_reply_tree_button')
|
||||
user.settings['hide_statuses_count'] = hide_statuses_count_preference if change?('setting_hide_statuses_count')
|
||||
user.settings['hide_following_count'] = hide_following_count_preference if change?('setting_hide_following_count')
|
||||
user.settings['hide_followers_count'] = hide_followers_count_preference if change?('setting_hide_followers_count')
|
||||
user.settings['disable_joke_appearance'] = disable_joke_appearance_preference if change?('setting_disable_joke_appearance')
|
||||
user.settings['new_features_policy'] = new_features_policy if change?('setting_new_features_policy')
|
||||
user.settings['theme_instance_ticker'] = theme_instance_ticker if change?('setting_theme_instance_ticker')
|
||||
user.settings['theme_public'] = theme_public if change?('setting_theme_public')
|
||||
user.settings['enable_status_reference'] = enable_status_reference_preference if change?('setting_enable_status_reference')
|
||||
user.settings['match_visibility_of_references'] = match_visibility_of_references_preference if change?('setting_match_visibility_of_references')
|
||||
user.settings['hexagon_avatar'] = hexagon_avatar_preference if change?('setting_hexagon_avatar')
|
||||
user.settings['enable_empty_column'] = enable_empty_column_preference if change?('setting_enable_empty_column')
|
||||
user.settings['content_font_size'] = content_font_size_preference if change?('setting_content_font_size')
|
||||
user.settings['info_font_size'] = info_font_size_preference if change?('setting_info_font_size')
|
||||
user.settings['content_emoji_reaction_size'] = content_emoji_reaction_size_preference if change?('setting_content_emoji_reaction_size')
|
||||
user.settings['emoji_scale'] = emoji_scale_preference if change?('setting_emoji_scale')
|
||||
user.settings['picker_emoji_size'] = picker_emoji_size_preference if change?('setting_picker_emoji_size')
|
||||
user.settings['hide_bot_on_public_timeline'] = hide_bot_on_public_timeline_preference if change?('setting_hide_bot_on_public_timeline')
|
||||
user.settings['confirm_follow_from_bot'] = confirm_follow_from_bot_preference if change?('setting_confirm_follow_from_bot')
|
||||
user.settings['default_search_searchability'] = default_search_searchability_preference if change?('setting_default_search_searchability')
|
||||
user.settings['show_reload_button'] = show_reload_button_preference if change?('setting_show_reload_button')
|
||||
user.settings['default_column_width'] = default_column_width_preference if change?('setting_default_column_width')
|
||||
user.settings['default_expires_in'] = default_expires_in_preference if change?('setting_default_expires_in')
|
||||
user.settings['default_expires_action'] = default_expires_action_preference if change?('setting_default_expires_action')
|
||||
end
|
||||
NESTED_KEYS.each do |key|
|
||||
user.settings[key] = user.settings[key].merge coerced_settings(key) if change?(key)
|
||||
end
|
||||
|
||||
def merged_notification_emails
|
||||
user.settings['notification_emails'].merge coerced_settings('notification_emails').to_h
|
||||
end
|
||||
STRING_KEYS.each do |key|
|
||||
user.settings[key] = settings["setting_#{key}"] if change?("setting_#{key}")
|
||||
end
|
||||
|
||||
def merged_interactions
|
||||
user.settings['interactions'].merge coerced_settings('interactions').to_h
|
||||
end
|
||||
|
||||
def default_privacy_preference
|
||||
settings['setting_default_privacy']
|
||||
end
|
||||
|
||||
def default_sensitive_preference
|
||||
boolean_cast_setting 'setting_default_sensitive'
|
||||
end
|
||||
|
||||
def unfollow_modal_preference
|
||||
boolean_cast_setting 'setting_unfollow_modal'
|
||||
end
|
||||
|
||||
def unsubscribe_modal_preference
|
||||
boolean_cast_setting 'setting_unsubscribe_modal'
|
||||
end
|
||||
|
||||
def boost_modal_preference
|
||||
boolean_cast_setting 'setting_boost_modal'
|
||||
end
|
||||
|
||||
def delete_modal_preference
|
||||
boolean_cast_setting 'setting_delete_modal'
|
||||
end
|
||||
|
||||
def post_reference_modal_preference
|
||||
boolean_cast_setting 'setting_post_reference_modal'
|
||||
end
|
||||
|
||||
def add_reference_modal_preference
|
||||
boolean_cast_setting 'setting_add_reference_modal'
|
||||
end
|
||||
|
||||
def unselect_reference_modal_preference
|
||||
boolean_cast_setting 'setting_unselect_reference_modal'
|
||||
end
|
||||
|
||||
def system_font_ui_preference
|
||||
boolean_cast_setting 'setting_system_font_ui'
|
||||
end
|
||||
|
||||
def auto_play_gif_preference
|
||||
boolean_cast_setting 'setting_auto_play_gif'
|
||||
end
|
||||
|
||||
def display_media_preference
|
||||
settings['setting_display_media']
|
||||
end
|
||||
|
||||
def expand_spoilers_preference
|
||||
boolean_cast_setting 'setting_expand_spoilers'
|
||||
end
|
||||
|
||||
def reduce_motion_preference
|
||||
boolean_cast_setting 'setting_reduce_motion'
|
||||
end
|
||||
|
||||
def disable_swiping_preference
|
||||
boolean_cast_setting 'setting_disable_swiping'
|
||||
end
|
||||
|
||||
def noindex_preference
|
||||
boolean_cast_setting 'setting_noindex'
|
||||
end
|
||||
|
||||
def hide_network_preference
|
||||
boolean_cast_setting 'setting_hide_network'
|
||||
end
|
||||
|
||||
def show_application_preference
|
||||
boolean_cast_setting 'setting_show_application'
|
||||
end
|
||||
|
||||
def theme_preference
|
||||
settings['setting_theme']
|
||||
end
|
||||
|
||||
def default_language_preference
|
||||
settings['setting_default_language']
|
||||
end
|
||||
|
||||
def aggregate_reblogs_preference
|
||||
boolean_cast_setting 'setting_aggregate_reblogs'
|
||||
end
|
||||
|
||||
def advanced_layout_preference
|
||||
boolean_cast_setting 'setting_advanced_layout'
|
||||
end
|
||||
|
||||
def use_blurhash_preference
|
||||
boolean_cast_setting 'setting_use_blurhash'
|
||||
end
|
||||
|
||||
def use_pending_items_preference
|
||||
boolean_cast_setting 'setting_use_pending_items'
|
||||
end
|
||||
|
||||
def trends_preference
|
||||
boolean_cast_setting 'setting_trends'
|
||||
end
|
||||
|
||||
def crop_images_preference
|
||||
boolean_cast_setting 'setting_crop_images'
|
||||
end
|
||||
|
||||
def confirm_domain_block_preference
|
||||
boolean_cast_setting 'setting_confirm_domain_block'
|
||||
end
|
||||
|
||||
def show_follow_button_on_timeline_preference
|
||||
boolean_cast_setting 'setting_show_follow_button_on_timeline'
|
||||
end
|
||||
|
||||
def show_subscribe_button_on_timeline_preference
|
||||
boolean_cast_setting 'setting_show_subscribe_button_on_timeline'
|
||||
end
|
||||
|
||||
def show_followed_by_preference
|
||||
boolean_cast_setting 'setting_show_followed_by'
|
||||
end
|
||||
|
||||
def follow_button_to_list_adder_preference
|
||||
boolean_cast_setting 'setting_follow_button_to_list_adder'
|
||||
end
|
||||
|
||||
def show_navigation_panel_preference
|
||||
boolean_cast_setting 'setting_show_navigation_panel'
|
||||
end
|
||||
|
||||
def show_quote_button_preference
|
||||
boolean_cast_setting 'setting_show_quote_button'
|
||||
end
|
||||
|
||||
def show_bookmark_button_preference
|
||||
boolean_cast_setting 'setting_show_bookmark_button'
|
||||
end
|
||||
|
||||
def show_target_preference
|
||||
boolean_cast_setting 'setting_show_target'
|
||||
end
|
||||
|
||||
def place_tab_bar_at_bottom_preference
|
||||
boolean_cast_setting 'setting_place_tab_bar_at_bottom'
|
||||
end
|
||||
|
||||
def show_tab_bar_label_preference
|
||||
boolean_cast_setting 'setting_show_tab_bar_label'
|
||||
end
|
||||
|
||||
def enable_limited_timeline_preference
|
||||
boolean_cast_setting 'setting_enable_limited_timeline'
|
||||
end
|
||||
|
||||
def enable_reaction_preference
|
||||
boolean_cast_setting 'setting_enable_reaction'
|
||||
end
|
||||
|
||||
def compact_reaction_preference
|
||||
boolean_cast_setting 'setting_compact_reaction'
|
||||
end
|
||||
|
||||
def show_reply_tree_button_preference
|
||||
boolean_cast_setting 'setting_show_reply_tree_button'
|
||||
end
|
||||
|
||||
def hide_statuses_count_preference
|
||||
boolean_cast_setting 'setting_hide_statuses_count'
|
||||
end
|
||||
|
||||
def hide_following_count_preference
|
||||
boolean_cast_setting 'setting_hide_following_count'
|
||||
end
|
||||
|
||||
def hide_followers_count_preference
|
||||
boolean_cast_setting 'setting_hide_followers_count'
|
||||
end
|
||||
|
||||
def disable_joke_appearance_preference
|
||||
boolean_cast_setting 'setting_disable_joke_appearance'
|
||||
end
|
||||
|
||||
def new_features_policy
|
||||
settings['setting_new_features_policy']
|
||||
end
|
||||
|
||||
def theme_instance_ticker
|
||||
settings['setting_theme_instance_ticker']
|
||||
end
|
||||
|
||||
def theme_public
|
||||
boolean_cast_setting 'setting_theme_public'
|
||||
end
|
||||
|
||||
def hexagon_avatar_preference
|
||||
boolean_cast_setting 'setting_hexagon_avatar'
|
||||
end
|
||||
|
||||
def enable_status_reference_preference
|
||||
boolean_cast_setting 'setting_enable_status_reference'
|
||||
end
|
||||
|
||||
def match_visibility_of_references_preference
|
||||
boolean_cast_setting 'setting_match_visibility_of_references'
|
||||
end
|
||||
|
||||
def enable_empty_column_preference
|
||||
boolean_cast_setting 'setting_enable_empty_column'
|
||||
end
|
||||
|
||||
def content_font_size_preference
|
||||
settings['setting_content_font_size']
|
||||
end
|
||||
|
||||
def info_font_size_preference
|
||||
settings['setting_info_font_size']
|
||||
end
|
||||
|
||||
def content_emoji_reaction_size_preference
|
||||
settings['setting_content_emoji_reaction_size']
|
||||
end
|
||||
|
||||
def emoji_scale_preference
|
||||
settings['setting_emoji_scale']
|
||||
end
|
||||
|
||||
def picker_emoji_size_preference
|
||||
settings['setting_picker_emoji_size']
|
||||
end
|
||||
|
||||
def hide_bot_on_public_timeline_preference
|
||||
boolean_cast_setting 'setting_hide_bot_on_public_timeline'
|
||||
end
|
||||
|
||||
def confirm_follow_from_bot_preference
|
||||
boolean_cast_setting 'setting_confirm_follow_from_bot'
|
||||
end
|
||||
|
||||
def default_search_searchability_preference
|
||||
settings['setting_default_search_searchability']
|
||||
end
|
||||
|
||||
def show_reload_button_preference
|
||||
boolean_cast_setting 'setting_show_reload_button'
|
||||
end
|
||||
|
||||
def default_column_width_preference
|
||||
settings['setting_default_column_width']
|
||||
end
|
||||
|
||||
def default_expires_in_preference
|
||||
settings['setting_default_expires_in']
|
||||
end
|
||||
|
||||
def default_expires_action_preference
|
||||
settings['setting_default_expires_action']
|
||||
BOOLEAN_KEYS.each do |key|
|
||||
user.settings[key] = boolean_cast_setting "setting_#{key}" if change?("setting_#{key}")
|
||||
end
|
||||
end
|
||||
|
||||
def boolean_cast_setting(key)
|
||||
|
|
|
@ -141,6 +141,8 @@ class User < ApplicationRecord
|
|||
:hide_bot_on_public_timeline, :confirm_follow_from_bot,
|
||||
:default_search_searchability, :default_expires_in, :default_expires_action,
|
||||
:show_reload_button, :default_column_width,
|
||||
:disable_post, :disable_reactions, :disable_follow, :disable_unfollow, :disable_block, :disable_domain_block, :disable_clear_all_notifications, :disable_account_delete,
|
||||
:prohibited_visibilities, :prohibited_words,
|
||||
|
||||
to: :settings, prefix: :setting, allow_nil: false
|
||||
|
||||
|
|
|
@ -76,6 +76,14 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:confirm_follow_from_bot] = object.current_account.user.setting_confirm_follow_from_bot
|
||||
store[:show_reload_button] = object.current_account.user.setting_show_reload_button
|
||||
store[:default_column_width] = object.current_account.user.setting_default_column_width
|
||||
store[:disable_post] = object.current_account.user.setting_disable_post
|
||||
store[:disable_reactions] = object.current_account.user.setting_disable_reactions
|
||||
store[:disable_follow] = object.current_account.user.setting_disable_follow
|
||||
store[:disable_unfollow] = object.current_account.user.setting_disable_unfollow
|
||||
store[:disable_block] = object.current_account.user.setting_disable_block
|
||||
store[:disable_domain_block] = object.current_account.user.setting_disable_domain_block
|
||||
store[:disable_clear_all_notifications] = object.current_account.user.setting_disable_clear_all_notifications
|
||||
store[:disable_account_delete] = object.current_account.user.setting_disable_account_delete
|
||||
else
|
||||
store[:auto_play_gif] = Setting.auto_play_gif
|
||||
store[:display_media] = Setting.display_media
|
||||
|
@ -91,12 +99,14 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store = {}
|
||||
|
||||
if object.current_account
|
||||
store[:me] = object.current_account.id.to_s
|
||||
store[:default_privacy] = object.visibility || object.current_account.user.setting_default_privacy
|
||||
store[:default_searchability] = object.current_account.searchability
|
||||
store[:default_sensitive] = object.current_account.user.setting_default_sensitive
|
||||
store[:default_expires_in] = object.current_account.user.setting_default_expires_in
|
||||
store[:default_expires_action] = object.current_account.user.setting_default_expires_action
|
||||
store[:me] = object.current_account.id.to_s
|
||||
store[:default_privacy] = object.visibility || object.current_account.user.setting_default_privacy
|
||||
store[:default_searchability] = object.current_account.searchability
|
||||
store[:default_sensitive] = object.current_account.user.setting_default_sensitive
|
||||
store[:default_expires_in] = object.current_account.user.setting_default_expires_in
|
||||
store[:default_expires_action] = object.current_account.user.setting_default_expires_action
|
||||
store[:prohibited_visibilities] = object.current_account.user.setting_prohibited_visibilities.filter(&:present?)
|
||||
store[:prohibited_words] = (object.current_account.user.setting_prohibited_words || '').split(',').map(&:strip).filter(&:present?)
|
||||
end
|
||||
|
||||
store[:text] = object.text if object.text
|
||||
|
|
|
@ -38,7 +38,9 @@ class PostStatusService < BaseService
|
|||
|
||||
validate_media!
|
||||
validate_expires!
|
||||
validate_prohibited_words!
|
||||
preprocess_attributes!
|
||||
validate_prohibited_visibilities!
|
||||
preprocess_quote!
|
||||
|
||||
if scheduled?
|
||||
|
@ -152,6 +154,19 @@ class PostStatusService < BaseService
|
|||
expires_at.present? && expires_at <= Time.now.utc + MIN_SCHEDULE_OFFSET
|
||||
end
|
||||
|
||||
def validate_prohibited_words!
|
||||
return if @options[:spoiler_text].blank? && @options[:text].blank?
|
||||
|
||||
text = [@options[:spoiler_text], @options[:text]].join(' ')
|
||||
words = (@account&.user&.setting_prohibited_words || '').split(',').map(&:strip).filter(&:present?)
|
||||
|
||||
raise Mastodon::ValidationError, I18n.t('status_prohibit.validations.prohibited_words') if words.any? { |word| text.include? word }
|
||||
end
|
||||
|
||||
def validate_prohibited_visibilities!
|
||||
raise Mastodon::ValidationError, I18n.t('status_prohibit.validations.prohibited_visibilities') if @account.user&.setting_prohibited_visibilities&.filter(&:present?)&.include?(@visibility.to_s)
|
||||
end
|
||||
|
||||
def validate_media!
|
||||
return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ class ReblogService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
raise Mastodon::ValidationError, I18n.t('status_prohibit.validations.prohibited_visibilities') if account.user&.setting_prohibited_visibilities&.filter(&:present?)&.include?(visibility)
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit])
|
||||
reblog.capability_tokens.create! if reblog.limited_visibility?
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
:ruby
|
||||
disable_follow = current_user.setting_disable_follow
|
||||
disable_unfollow = current_user.setting_disable_unfollow
|
||||
|
||||
- content_for :page_title do
|
||||
= t('settings.relationships')
|
||||
|
||||
|
@ -48,13 +52,13 @@
|
|||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
= f.button safe_join([fa_icon('user-plus'), t('relationships.follow_selected_followers')]), name: :follow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship? && !mutual_relationship?
|
||||
= f.button safe_join([fa_icon('user-plus'), t('relationships.follow_selected_followers')]), name: :follow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }, disabled: disable_follow if followed_by_relationship? && !mutual_relationship?
|
||||
|
||||
= f.button safe_join([fa_icon('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless followed_by_relationship?
|
||||
= f.button safe_join([fa_icon('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }, disabled: disable_unfollow unless followed_by_relationship?
|
||||
|
||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless following_relationship?
|
||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }, disabled: disable_unfollow unless following_relationship?
|
||||
|
||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :block_domains, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship?
|
||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :block_domains, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }, disabled: disable_unfollow if followed_by_relationship?
|
||||
.batch-table__body
|
||||
- if @accounts.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
:ruby
|
||||
disable = current_user.setting_disable_account_delete
|
||||
|
||||
- content_for :page_title do
|
||||
= t('settings.delete')
|
||||
|
||||
|
@ -26,4 +29,4 @@
|
|||
= f.input :username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_username')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('deletes.proceed'), type: :submit, class: 'negative'
|
||||
= f.button :button, t('deletes.proceed'), type: :submit, class: disable ? 'button disabled' : 'negative', disabled: disable
|
||||
|
|
45
app/views/settings/preferences/safety/show.html.haml
Normal file
45
app/views/settings/preferences/safety/show.html.haml
Normal file
|
@ -0,0 +1,45 @@
|
|||
- content_for :page_title do
|
||||
= t('settings.safety')
|
||||
|
||||
- content_for :heading_actions do
|
||||
= button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences'
|
||||
|
||||
= simple_form_for current_user, url: settings_preferences_safety_path, html: { method: :put, id: 'edit_preferences' } do |f|
|
||||
= render 'shared/error_messages', object: current_user
|
||||
|
||||
%h4= t 'preferences.disable_actions'
|
||||
|
||||
%p.hint= t 'preferences.disable_actions_hint'
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_reactions, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_follow, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_unfollow, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_block, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_domain_block, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_clear_all_notifications, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_account_delete, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_disable_post, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
%h4= t 'preferences.post_prohibite'
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_prohibited_visibilities, collection: Status.visibilities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |visibility| t("statuses.visibilities.#{visibility}") }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_prohibited_words, wrapper: :with_label, fedibird_features: true
|
||||
|
|
@ -1245,10 +1245,14 @@ en:
|
|||
too_many_options: can't contain more than %{max} items
|
||||
preferences:
|
||||
beta_features: Beta features
|
||||
disable_actions: Disable actions
|
||||
disable_actions_hint: This setting is used to prevent accidents due to mishandling or to disable certain actions in accounts with limited use. It also applies to client applications.
|
||||
fedibird_features: Fedibird features
|
||||
other: Other
|
||||
post_prohibite: Post prohibite
|
||||
posting_defaults: Posting defaults
|
||||
public_timelines: Public timelines
|
||||
safety: Safety & Privacy
|
||||
searching_defaults: Searching default
|
||||
reactions:
|
||||
errors:
|
||||
|
@ -1371,6 +1375,7 @@ en:
|
|||
preferences: Preferences
|
||||
profile: Profile
|
||||
relationships: Follows and followers
|
||||
safety: Safety & Privacy
|
||||
statuses_cleanup: Automated post deletion
|
||||
two_factor_authentication: Two-factor Auth
|
||||
webauthn_authentication: Security keys
|
||||
|
@ -1487,6 +1492,10 @@ en:
|
|||
expire_in_the_past: It is already past expires. You cannot specify expires before the posting time.
|
||||
invalid_expire_at: Invalid expires are specified.
|
||||
invalid_expire_action: Invalid expires_action are specified.
|
||||
status_prohibit:
|
||||
validations:
|
||||
prohibited_visibilities: Prohibited visibility is specified.
|
||||
prohibited_words: Prohibited words is specified.
|
||||
status_references:
|
||||
errors:
|
||||
limit: You have already reached your reference limit
|
||||
|
|
|
@ -1191,10 +1191,14 @@ ja:
|
|||
too_many_options: は%{max}個までです
|
||||
preferences:
|
||||
beta_features: ベータ機能
|
||||
disable_actions: 操作の無効化
|
||||
disable_actions_hint: 誤操作による事故を防いだり、用途を限定したアカウント向けに、特定の操作を無効化するための設定です。クライアントアプリに対しても有効です。
|
||||
fedibird_features: Fedibirdの機能
|
||||
other: その他
|
||||
post_prohibite: 投稿の禁止
|
||||
posting_defaults: デフォルトの投稿設定
|
||||
public_timelines: 公開タイムライン
|
||||
safety: 安全とプライバシー
|
||||
searching_defaults: デフォルトの検索設定
|
||||
reactions:
|
||||
errors:
|
||||
|
@ -1316,6 +1320,7 @@ ja:
|
|||
preferences: ユーザー設定
|
||||
profile: プロフィール
|
||||
relationships: フォロー・フォロワー
|
||||
safety: 安全とプライバシー
|
||||
two_factor_authentication: 二段階認証
|
||||
webauthn_authentication: セキュリティキー
|
||||
statuses:
|
||||
|
@ -1385,6 +1390,10 @@ ja:
|
|||
public_long: 誰でも見ることができ、かつ公開タイムラインに表示されます
|
||||
unlisted: 未収載
|
||||
unlisted_long: 誰でも見ることができますが、公開タイムラインには表示されません
|
||||
status_prohibit:
|
||||
validations:
|
||||
prohibited_visibilities: 禁止された公開範囲を指定しています
|
||||
prohibited_words: 禁止された単語を含んでいます
|
||||
status_expire:
|
||||
validations:
|
||||
expire_in_the_past: 既に公開期限を過ぎています。投稿時間より前の公開期限は指定できません
|
||||
|
|
|
@ -70,7 +70,15 @@ en:
|
|||
The format is 1y2mo3d4h5m (1 year, 2 months, 3 days, 4 hours, 5 minutes later).
|
||||
setting_default_search_searchability: For clients that do not support advanced range settings, switch the settings here. Mastodon's standard behavior is "Reacted-users-only". Targeting "Public" makes it easier to discover unknown information, but if the results are noisy, narrowing the search range is effective.
|
||||
setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
|
||||
setting_disable_account_delete: Restrict account deletion due to temporary hesitation
|
||||
setting_disable_block: Restricts you from accidentally blocking
|
||||
setting_disable_clear_all_notifications: Restrict clearing all notifications
|
||||
setting_disable_domain_block: Restrict domain blocking
|
||||
setting_disable_follow: Restricts you from accidentally following
|
||||
setting_disable_joke_appearance: Disable April Fools' Day and other joke functions
|
||||
setting_disable_post: Restricts you from accidentally posting
|
||||
setting_disable_reactions: Restrict reactions for favorites, boosts ,emoji reactions and polls
|
||||
setting_disable_unfollow: Restricts you from accidentally unfollowing
|
||||
setting_display_media_default: Hide media marked as sensitive
|
||||
setting_display_media_hide_all: Always hide media
|
||||
setting_display_media_show_all: Always show media
|
||||
|
@ -89,6 +97,8 @@ en:
|
|||
setting_new_features_policy: Set the acceptance policy when new features are added to Fedibird. The recommended setting will enable many new features, so set it to disabled if it is not desirable
|
||||
setting_noindex: Affects your public profile and post pages
|
||||
setting_place_tab_bar_at_bottom: When using a touch device, you can operate tabs within the reach of your fingers.
|
||||
setting_prohibited_visibilities: Prohibited visibilities
|
||||
setting_prohibited_words: Prohibited words
|
||||
setting_show_application: The application you use to post will be displayed in the detailed view of your posts
|
||||
setting_show_bookmark_button: When turned off, the bookmark call will be in Mastodon's standard position (in the menu on the action bar)
|
||||
setting_show_follow_button_on_timeline: You can easily check the follow status and build a follow list quickly
|
||||
|
@ -241,8 +251,16 @@ en:
|
|||
setting_default_search_searchability: Search range
|
||||
setting_default_sensitive: Always mark media as sensitive
|
||||
setting_delete_modal: Show confirmation dialog before deleting a post
|
||||
setting_disable_account_delete: Disable account delete
|
||||
setting_disable_block: Disable block
|
||||
setting_disable_clear_all_notifications: Disable clear all notifications
|
||||
setting_disable_domain_block: Disable domain block
|
||||
setting_disable_follow: Disable follow
|
||||
setting_disable_joke_appearance: Disable joke feature to change appearance
|
||||
setting_disable_post: Disable post
|
||||
setting_disable_reactions: Disable reactions
|
||||
setting_disable_swiping: Disable swiping motions
|
||||
setting_disable_unfollow: Disable unfollow
|
||||
setting_display_media: Media display
|
||||
setting_display_media_default: Default
|
||||
setting_display_media_hide_all: Hide all
|
||||
|
@ -270,6 +288,8 @@ en:
|
|||
setting_picker_emoji_size: Emoji size in picker
|
||||
setting_place_tab_bar_at_bottom: Place the tab bar at the bottom
|
||||
setting_post_reference_modal: Show a confirmation dialog before making a post containing references
|
||||
setting_prohibited_visibilities: Prohibited visibility
|
||||
setting_prohibited_words: Prohibited words
|
||||
setting_reduce_motion: Reduce motion in animations
|
||||
setting_unselect_reference_modal: Show confirmation dialog before removing a reference
|
||||
setting_show_application: Disclose application used to send posts
|
||||
|
|
|
@ -66,7 +66,15 @@ ja:
|
|||
setting_default_expires_in: 投稿日時を起点とする終了日時を指定します。書式は1y2mo3d4h5m(1年2ヶ月3日4時間5分後)です。
|
||||
setting_default_search_searchability: 範囲の詳細設定に対応していないクライアントでは、ここで設定を切り替えてください。Mastodonの標準動作は『リアクション限定』です。『公開』を対象にすると未知の情報を発見しやすくなりますが、結果にノイズが多い場合は検索範囲を狭めると効果的です。
|
||||
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
|
||||
setting_disable_account_delete: 一時的な迷いでアカウント削除することを防ぎます
|
||||
setting_disable_block: 誤ってブロックすることを防ぎます
|
||||
setting_disable_clear_all_notifications: 通知の全消去を防ぎます
|
||||
setting_disable_domain_block: 誤ってドメインブロックを実行することを防ぎます
|
||||
setting_disable_follow: 誤ってフォローすることを防ぎます
|
||||
setting_disable_joke_appearance: エイプリルフール等のジョーク機能を無効にします
|
||||
setting_disable_post: 誤って投稿することを防ぎます
|
||||
setting_disable_reactions: 誤ってお気に入り・ブースト・絵文字リアクション・投票することを防ぎます
|
||||
setting_disable_unfollow: 誤ってフォロー解除することを防ぎます
|
||||
setting_display_media_default: 閲覧注意としてマークされたメディアは隠す
|
||||
setting_display_media_hide_all: メディアを常に隠す
|
||||
setting_display_media_show_all: メディアを常に表示する
|
||||
|
@ -85,6 +93,8 @@ ja:
|
|||
setting_new_features_policy: Fedibirdに新しい機能が追加された時の受け入れポリシーを設定します。推奨設定は多くの新機能を有効にするので、望ましくない場合は無効に設定してください
|
||||
setting_noindex: 公開プロフィールおよび各投稿ページに影響します
|
||||
setting_place_tab_bar_at_bottom: タッチデバイス使用時に、タブの操作を指の届く範囲で行えます
|
||||
setting_prohibited_visibilities: 指定した公開範囲で投稿することを禁止します
|
||||
setting_prohibited_words: 投稿で使用禁止する単語をカンマ区切りで指定します
|
||||
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
|
||||
setting_show_bookmark_button: オフにした場合、ブックマークの呼び出しはMastodon標準の位置となります(アクションバーのメニューの中)
|
||||
setting_show_follow_button_on_timeline: フォロー状態を確認し易くなり、素早くフォローリストを構築できます
|
||||
|
@ -237,8 +247,16 @@ ja:
|
|||
setting_default_search_searchability: 検索の対象とする範囲
|
||||
setting_default_sensitive: メディアを常に閲覧注意としてマークする
|
||||
setting_delete_modal: 投稿を削除する前に確認ダイアログを表示する
|
||||
setting_disable_account_delete: アカウント削除を無効にする
|
||||
setting_disable_block: ブロックを無効にする
|
||||
setting_disable_clear_all_notifications: 通知の全消去を無効にする
|
||||
setting_disable_domain_block: ドメインブロックを無効にする
|
||||
setting_disable_follow: フォローを無効にする
|
||||
setting_disable_joke_appearance: ジョーク機能による見た目の変更を無効にする
|
||||
setting_disable_post: 投稿を無効にする
|
||||
setting_disable_reactions: リアクションを無効にする
|
||||
setting_disable_swiping: スワイプでの切り替えを無効にする
|
||||
setting_disable_unfollow: フォロー解除を無効にする
|
||||
setting_display_media: メディアの表示
|
||||
setting_display_media_default: 標準
|
||||
setting_display_media_hide_all: 非表示
|
||||
|
@ -266,6 +284,8 @@ ja:
|
|||
setting_picker_emoji_size: 絵文字ピッカーの表示サイズ
|
||||
setting_place_tab_bar_at_bottom: タブバーを下に配置する
|
||||
setting_post_reference_modal: 参照を含む投稿をする前に確認ダイアログを表示する
|
||||
setting_prohibited_visibilities: 投稿禁止する公開範囲
|
||||
setting_prohibited_words: 投稿禁止する単語
|
||||
setting_reduce_motion: アニメーションの動きを減らす
|
||||
setting_unselect_reference_modal: 参照を解除する前に確認ダイアログを表示する
|
||||
setting_show_application: 送信したアプリを開示する
|
||||
|
|
|
@ -15,6 +15,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
|||
s.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_preferences_notifications_url
|
||||
s.item :favourite_domains, safe_join([fa_icon('users fw'), t('settings.favourite_domains')]), settings_favourite_domains_url
|
||||
s.item :favourite_tags, safe_join([fa_icon('hashtag fw'), t('settings.favourite_tags')]), settings_favourite_tags_url
|
||||
s.item :safety, safe_join([fa_icon('shield fw'), t('preferences.safety')]), settings_preferences_safety_url
|
||||
s.item :other, safe_join([fa_icon('cog fw'), t('preferences.other')]), settings_preferences_other_url
|
||||
end
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ Rails.application.routes.draw do
|
|||
namespace :preferences do
|
||||
resource :appearance, only: [:show, :update], controller: :appearance
|
||||
resource :notifications, only: [:show, :update]
|
||||
resource :safety, only: [:show, :update], controller: :safety
|
||||
resource :other, only: [:show, :update], controller: :other
|
||||
end
|
||||
|
||||
|
|
|
@ -116,6 +116,16 @@ defaults: &defaults
|
|||
default_column_width: 'x100'
|
||||
default_expires_in: ''
|
||||
default_expires_action: 'mark'
|
||||
disable_post: false
|
||||
disable_reactions: false
|
||||
disable_follow: false
|
||||
disable_unfollow: false
|
||||
disable_block: false
|
||||
disable_domain_block: false
|
||||
disable_clear_all_notifications: false
|
||||
disable_account_delete: false
|
||||
prohibited_visibilities: []
|
||||
prohibited_words: ''
|
||||
|
||||
development:
|
||||
<<: *defaults
|
||||
|
|
Loading…
Reference in a new issue