Add login visibility
This commit is contained in:
parent
0907b67239
commit
20497e0c21
42 changed files with 106 additions and 47 deletions
|
@ -130,6 +130,7 @@ Metrics/ClassLength:
|
||||||
- 'app/models/status.rb'
|
- 'app/models/status.rb'
|
||||||
- 'app/models/tag.rb'
|
- 'app/models/tag.rb'
|
||||||
- 'app/models/user.rb'
|
- 'app/models/user.rb'
|
||||||
|
- 'app/policies/status_policy.rb'
|
||||||
- 'app/serializers/activitypub/actor_serializer.rb'
|
- 'app/serializers/activitypub/actor_serializer.rb'
|
||||||
- 'app/serializers/activitypub/note_serializer.rb'
|
- 'app/serializers/activitypub/note_serializer.rb'
|
||||||
- 'app/serializers/rest/account_serializer.rb'
|
- 'app/serializers/rest/account_serializer.rb'
|
||||||
|
@ -155,6 +156,7 @@ Metrics/CyclomaticComplexity:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/policies/status_policy.rb'
|
- 'app/policies/status_policy.rb'
|
||||||
- 'app/services/activitypub/process_account_service.rb'
|
- 'app/services/activitypub/process_account_service.rb'
|
||||||
|
- 'app/services/post_status_service.rb'
|
||||||
- lib/mastodon/*cli*.rb
|
- lib/mastodon/*cli*.rb
|
||||||
- db/*migrate/**/*
|
- db/*migrate/**/*
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,9 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_statuses
|
def default_statuses
|
||||||
@account.statuses.where(visibility: [:public, :unlisted, :public_unlisted])
|
visibilities = [:public, :unlisted, :public_unlisted]
|
||||||
|
visibilities << :login unless current_account.nil?
|
||||||
|
@account.statuses.where(visibility: visibilities)
|
||||||
end
|
end
|
||||||
|
|
||||||
def only_media_scope
|
def only_media_scope
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||||
|
|
||||||
def set_replies
|
def set_replies
|
||||||
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
|
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
|
||||||
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted, :public_unlisted])
|
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted, :public_unlisted, :login])
|
||||||
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
|
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def paginated_statuses
|
def paginated_statuses
|
||||||
Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted, :public_unlisted]).paginate_by_max_id(
|
Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted, :public_unlisted, :login]).paginate_by_max_id(
|
||||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
params[:max_id],
|
params[:max_id],
|
||||||
params[:since_id]
|
params[:since_id]
|
||||||
|
|
|
@ -124,6 +124,8 @@ module ApplicationHelper
|
||||||
fa_icon('unlock', title: I18n.t('statuses.visibilities.unlisted'))
|
fa_icon('unlock', title: I18n.t('statuses.visibilities.unlisted'))
|
||||||
elsif status.public_unlisted_visibility?
|
elsif status.public_unlisted_visibility?
|
||||||
fa_icon('cloud', title: I18n.t('statuses.visibilities.public_unlisted'))
|
fa_icon('cloud', title: I18n.t('statuses.visibilities.public_unlisted'))
|
||||||
|
elsif status.login_visibility?
|
||||||
|
fa_icon('key', title: I18n.t('statuses.visibilities.login'))
|
||||||
elsif status.private_visibility? || status.limited_visibility?
|
elsif status.private_visibility? || status.limited_visibility?
|
||||||
fa_icon('lock', title: I18n.t('statuses.visibilities.private'))
|
fa_icon('lock', title: I18n.t('statuses.visibilities.private'))
|
||||||
elsif status.direct_visibility?
|
elsif status.direct_visibility?
|
||||||
|
@ -189,8 +191,8 @@ module ApplicationHelper
|
||||||
text: [params[:title], params[:text], params[:url]].compact.join(' '),
|
text: [params[:title], params[:text], params[:url]].compact.join(' '),
|
||||||
}
|
}
|
||||||
|
|
||||||
permit_visibilities = %w(public unlisted public_unlisted private direct)
|
permit_visibilities = %w(public unlisted public_unlisted login private direct)
|
||||||
permit_searchabilities = %w(public unlisted public_unlisted private direct)
|
permit_searchabilities = %w(public unlisted public_unlisted login private direct)
|
||||||
default_privacy = current_account&.user&.setting_default_privacy
|
default_privacy = current_account&.user&.setting_default_privacy
|
||||||
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
|
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
|
||||||
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
|
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
|
||||||
|
|
|
@ -100,6 +100,8 @@ module StatusesHelper
|
||||||
fa_icon 'unlock fw'
|
fa_icon 'unlock fw'
|
||||||
when 'public_unlisted'
|
when 'public_unlisted'
|
||||||
fa_icon 'cloud fw'
|
fa_icon 'cloud fw'
|
||||||
|
when 'login'
|
||||||
|
fa_icon 'key fw'
|
||||||
when 'private'
|
when 'private'
|
||||||
fa_icon 'lock fw'
|
fa_icon 'lock fw'
|
||||||
when 'direct'
|
when 'direct'
|
||||||
|
|
|
@ -56,6 +56,7 @@ const messages = defineMessages({
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||||
|
@ -364,6 +365,7 @@ class Status extends ImmutablePureComponent {
|
||||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
|
@ -255,8 +255,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
const { signedIn, permissions } = this.context.identity;
|
const { signedIn, permissions } = this.context.identity;
|
||||||
|
|
||||||
const anonymousAccess = !signedIn;
|
const anonymousAccess = !signedIn;
|
||||||
const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
|
const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
|
||||||
const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'private'].includes(status.get('visibility_ex'));
|
const anonymousStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
|
||||||
|
const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'login', 'private'].includes(status.get('visibility_ex'));
|
||||||
const mutingConversation = status.get('muted');
|
const mutingConversation = status.get('muted');
|
||||||
const account = status.get('account');
|
const account = status.get('account');
|
||||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||||
|
@ -272,7 +273,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||||
|
|
||||||
if (publicStatus) {
|
if (anonymousStatus) {
|
||||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -239,7 +239,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
} else if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
} else if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||||
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
|
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
|
||||||
} else {
|
} else {
|
||||||
publishText = (this.props.privacy !== 'unlisted' && this.props.privacy !== 'public_unlisted') ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
publishText = (this.props.privacy !== 'unlisted' && this.props.privacy !== 'public_unlisted' && this.props.privacy !== 'login') ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Overlay from 'react-overlays/Overlay';
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { enableLoginPrivacy } from 'mastodon/initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
|
@ -14,6 +15,8 @@ const messages = defineMessages({
|
||||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but opted-out of discovery features' },
|
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but opted-out of discovery features' },
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
public_unlisted_long: { id: 'privacy.public_unlisted.long', defaultMessage: 'Visible for all without GTL' },
|
public_unlisted_long: { id: 'privacy.public_unlisted.long', defaultMessage: 'Visible for all without GTL' },
|
||||||
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
|
login_long: { id: 'privacy.login.long', defaultMessage: 'Login user only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
|
@ -220,9 +223,15 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
this.options = [
|
this.options = [
|
||||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||||
{ icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
|
{ icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
|
||||||
|
{ icon: 'key', value: 'login', text: formatMessage(messages.login_short), meta: formatMessage(messages.login_long) },
|
||||||
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_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: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||||
];
|
];
|
||||||
|
this.selectableOptions = [...this.options];
|
||||||
|
|
||||||
|
if (!enableLoginPrivacy) {
|
||||||
|
this.selectableOptions = this.selectableOptions.filter((opt) => opt.value !== 'login');
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.props.noDirect) {
|
if (!this.props.noDirect) {
|
||||||
this.options.push(
|
this.options.push(
|
||||||
|
@ -247,7 +256,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
const { value, container, disabled, intl } = this.props;
|
const { value, container, disabled, intl } = this.props;
|
||||||
const { open, placement } = this.state;
|
const { open, placement } = this.state;
|
||||||
|
|
||||||
const valueOption = this.options.find(item => item.value === value);
|
const valueOption = this.options.find(item => item.value === value) || this.options[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
|
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
|
||||||
|
@ -273,7 +282,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
<div {...props}>
|
<div {...props}>
|
||||||
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
||||||
<PrivacyDropdownMenu
|
<PrivacyDropdownMenu
|
||||||
items={this.options}
|
items={this.selectableOptions}
|
||||||
value={value}
|
value={value}
|
||||||
onClose={this.handleClose}
|
onClose={this.handleClose}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { HASHTAG_PATTERN_REGEX } from 'mastodon/utils/hashtags';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
|
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
|
||||||
hashtagWarning: ['public', 'public_unlisted'].indexOf(state.getIn(['compose', 'privacy'])) < 0 && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
|
hashtagWarning: ['public', 'public_unlisted', 'login'].indexOf(state.getIn(['compose', 'privacy'])) < 0 && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
|
||||||
directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
|
directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
|
||||||
searchabilityWarning: state.getIn(['compose', 'searchability']) === 'limited',
|
searchabilityWarning: state.getIn(['compose', 'searchability']) === 'limited',
|
||||||
});
|
});
|
||||||
|
|
|
@ -152,7 +152,7 @@ class Footer extends ImmutablePureComponent {
|
||||||
render () {
|
render () {
|
||||||
const { status, intl, withOpenButton } = this.props;
|
const { status, intl, withOpenButton } = this.props;
|
||||||
|
|
||||||
const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
|
const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
|
||||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private';
|
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private';
|
||||||
|
|
||||||
let replyIcon, replyTitle;
|
let replyIcon, replyTitle;
|
||||||
|
|
|
@ -14,6 +14,7 @@ const messages = defineMessages({
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
});
|
});
|
||||||
|
@ -44,6 +45,7 @@ class StatusCheckBox extends React.PureComponent {
|
||||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
|
@ -196,8 +196,9 @@ class ActionBar extends React.PureComponent {
|
||||||
const { status, relationship, intl } = this.props;
|
const { status, relationship, intl } = this.props;
|
||||||
const { signedIn, permissions } = this.context.identity;
|
const { signedIn, permissions } = this.context.identity;
|
||||||
|
|
||||||
const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
|
const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
|
||||||
const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'private'].includes(status.get('visibility_ex'));
|
const anonymousStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
|
||||||
|
const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'login', 'private'].includes(status.get('visibility_ex'));
|
||||||
const mutingConversation = status.get('muted');
|
const mutingConversation = status.get('muted');
|
||||||
const account = status.get('account');
|
const account = status.get('account');
|
||||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||||
|
@ -211,7 +212,11 @@ class ActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||||
|
|
||||||
|
if (anonymousStatus) {
|
||||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||||
|
}
|
||||||
|
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
menu.push({ text: intl.formatMessage(messages.reblog), action: this.handleReblogForceModalClick });
|
menu.push({ text: intl.formatMessage(messages.reblog), action: this.handleReblogForceModalClick });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
|
|
@ -23,6 +23,7 @@ const messages = defineMessages({
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||||
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
||||||
|
@ -214,6 +215,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,7 @@ const messages = defineMessages({
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||||
});
|
});
|
||||||
|
@ -87,6 +88,7 @@ class BoostModal extends ImmutablePureComponent {
|
||||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
* @property {string} display_media
|
* @property {string} display_media
|
||||||
* @property {boolean} display_media_expand
|
* @property {boolean} display_media_expand
|
||||||
* @property {string} domain
|
* @property {string} domain
|
||||||
|
* @property {boolean} enable_login_privacy
|
||||||
* @property {boolean=} expand_spoilers
|
* @property {boolean=} expand_spoilers
|
||||||
* @property {boolean} limited_federation_mode
|
* @property {boolean} limited_federation_mode
|
||||||
* @property {string} locale
|
* @property {string} locale
|
||||||
|
@ -111,6 +112,7 @@ export const disabledAccountId = getMeta('disabled_account_id');
|
||||||
export const displayMedia = getMeta('display_media');
|
export const displayMedia = getMeta('display_media');
|
||||||
export const displayMediaExpand = getMeta('display_media_expand');
|
export const displayMediaExpand = getMeta('display_media_expand');
|
||||||
export const domain = getMeta('domain');
|
export const domain = getMeta('domain');
|
||||||
|
export const enableLoginPrivacy = getMeta('enable_login_privacy');
|
||||||
export const expandSpoilers = getMeta('expand_spoilers');
|
export const expandSpoilers = getMeta('expand_spoilers');
|
||||||
export const forceSingleColumn = !getMeta('advanced_layout');
|
export const forceSingleColumn = !getMeta('advanced_layout');
|
||||||
export const limitedFederationMode = getMeta('limited_federation_mode');
|
export const limitedFederationMode = getMeta('limited_federation_mode');
|
||||||
|
|
|
@ -238,7 +238,7 @@ const insertExpiration = (state, position, data) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const privacyPreference = (a, b) => {
|
const privacyPreference = (a, b) => {
|
||||||
const order = ['public', 'public_unlisted', 'unlisted', 'private', 'direct'];
|
const order = ['public', 'public_unlisted', 'unlisted', 'login', 'private', 'direct'];
|
||||||
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
|
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -514,7 +514,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
|
|
||||||
if searchability == visibility || searchability == :limited || searchability == :direct
|
if searchability == visibility || searchability == :limited || searchability == :direct
|
||||||
searchability
|
searchability
|
||||||
elsif [:public, :unlisted, :private].include?(searchability) && [:public, :public_unlisted, :unlisted, :private].include?(visibility)
|
elsif [:public, :unlisted, :private].include?(searchability) && [:public, :public_unlisted, :unlisted, :login, :private].include?(visibility)
|
||||||
:private
|
:private
|
||||||
else
|
else
|
||||||
:direct
|
:direct
|
||||||
|
@ -526,6 +526,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
:public
|
:public
|
||||||
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
|
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
|
||||||
:unlisted
|
:unlisted
|
||||||
|
elsif audience_cc.include?('as:LoginOnly')
|
||||||
|
:login
|
||||||
elsif audience_to.include?(@account.followers_url)
|
elsif audience_to.include?(@account.followers_url)
|
||||||
:private
|
:private
|
||||||
else
|
else
|
||||||
|
|
|
@ -86,7 +86,7 @@ class ActivityPub::TagManager
|
||||||
case status.visibility
|
case status.visibility
|
||||||
when 'public'
|
when 'public'
|
||||||
[COLLECTIONS[:public]]
|
[COLLECTIONS[:public]]
|
||||||
when 'unlisted', 'public_unlisted', 'private'
|
when 'unlisted', 'public_unlisted', 'login', 'private'
|
||||||
[account_followers_url(status.account)]
|
[account_followers_url(status.account)]
|
||||||
when 'direct', 'limited'
|
when 'direct', 'limited'
|
||||||
if status.account.silenced?
|
if status.account.silenced?
|
||||||
|
@ -124,6 +124,8 @@ class ActivityPub::TagManager
|
||||||
cc << account_followers_url(status.account)
|
cc << account_followers_url(status.account)
|
||||||
when 'unlisted', 'public_unlisted'
|
when 'unlisted', 'public_unlisted'
|
||||||
cc << COLLECTIONS[:public]
|
cc << COLLECTIONS[:public]
|
||||||
|
when 'login'
|
||||||
|
cc << 'as:LoginOnly'
|
||||||
end
|
end
|
||||||
|
|
||||||
cc + cc_private_visibility(status)
|
cc + cc_private_visibility(status)
|
||||||
|
|
|
@ -109,7 +109,7 @@ class FeedManager
|
||||||
def merge_into_home(from_account, into_account)
|
def merge_into_home(from_account, into_account)
|
||||||
timeline_key = key(:home, into_account.id)
|
timeline_key = key(:home, into_account.id)
|
||||||
aggregate = into_account.user&.aggregates_reblogs?
|
aggregate = into_account.user&.aggregates_reblogs?
|
||||||
query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
||||||
|
|
||||||
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
||||||
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
||||||
|
@ -135,7 +135,7 @@ class FeedManager
|
||||||
def merge_into_list(from_account, list)
|
def merge_into_list(from_account, list)
|
||||||
timeline_key = key(:list, list.id)
|
timeline_key = key(:list, list.id)
|
||||||
aggregate = list.account.user&.aggregates_reblogs?
|
aggregate = list.account.user&.aggregates_reblogs?
|
||||||
query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
||||||
|
|
||||||
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
||||||
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
||||||
|
@ -253,7 +253,7 @@ class FeedManager
|
||||||
next if last_status_score < oldest_home_score
|
next if last_status_score < oldest_home_score
|
||||||
end
|
end
|
||||||
|
|
||||||
statuses = target_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit)
|
statuses = target_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login, :private]).includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit)
|
||||||
crutches = build_crutches(account.id, statuses)
|
crutches = build_crutches(account.id, statuses)
|
||||||
|
|
||||||
statuses.each do |status|
|
statuses.each do |status|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class AccountStatusesFilter
|
||||||
scope.merge!(scope.where.not(visibility: :public_unlisted)) if domain_block&.reject_send_public_unlisted || (domain_block&.detect_invalid_subscription && @account.user&.setting_reject_public_unlisted_subscription)
|
scope.merge!(scope.where.not(visibility: :public_unlisted)) if domain_block&.reject_send_public_unlisted || (domain_block&.detect_invalid_subscription && @account.user&.setting_reject_public_unlisted_subscription)
|
||||||
scope.merge!(scope.where.not(visibility: :unlisted)) if domain_block&.detect_invalid_subscription && @account.user&.setting_reject_unlisted_subscription
|
scope.merge!(scope.where.not(visibility: :unlisted)) if domain_block&.detect_invalid_subscription && @account.user&.setting_reject_unlisted_subscription
|
||||||
scope.merge!(scope.where(spoiler_text: ['', nil])) if domain_block&.reject_send_sensitive
|
scope.merge!(scope.where(spoiler_text: ['', nil])) if domain_block&.reject_send_sensitive
|
||||||
|
scope.merge!(scope.where.not(visibility: :login)) if current_account.nil?
|
||||||
|
|
||||||
scope
|
scope
|
||||||
end
|
end
|
||||||
|
@ -51,7 +52,7 @@ class AccountStatusesFilter
|
||||||
def filtered_scope
|
def filtered_scope
|
||||||
scope = account.statuses.left_outer_joins(:mentions)
|
scope = account.statuses.left_outer_joins(:mentions)
|
||||||
|
|
||||||
scope.merge!(scope.where(visibility: follower? ? %i(public unlisted public_unlisted private) : %i(public unlisted public_unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
|
scope.merge!(scope.where(visibility: follower? ? %i(public unlisted public_unlisted login private) : %i(public unlisted public_unlisted login)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
|
||||||
scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
|
scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
|
||||||
|
|
||||||
scope
|
scope
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Admin::StatusFilter
|
||||||
end
|
end
|
||||||
|
|
||||||
def results
|
def results
|
||||||
scope = @account.statuses.where(visibility: [:public, :unlisted, :public_unlisted])
|
scope = @account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login])
|
||||||
|
|
||||||
params.each do |key, value|
|
params.each do |key, value|
|
||||||
next if IGNORED_PARAMS.include?(key.to_s)
|
next if IGNORED_PARAMS.include?(key.to_s)
|
||||||
|
|
|
@ -23,6 +23,10 @@ module HasUserSettings
|
||||||
settings['web.auto_play']
|
settings['web.auto_play']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setting_enable_login_privacy
|
||||||
|
settings['web.enable_login_privacy']
|
||||||
|
end
|
||||||
|
|
||||||
def setting_default_sensitive
|
def setting_default_sensitive
|
||||||
settings['default_sensitive']
|
settings['default_sensitive']
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ module StatusThreadingConcern
|
||||||
end
|
end
|
||||||
|
|
||||||
def self_replies(limit)
|
def self_replies(limit)
|
||||||
account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted, :public_unlisted]).reorder(id: :asc).limit(limit)
|
account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted, :public_unlisted, :login]).reorder(id: :asc).limit(limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -45,7 +45,7 @@ class FeaturedTag < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def decrement(deleted_status_id)
|
def decrement(deleted_status_id)
|
||||||
update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
|
update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted public_unlisted login)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -59,8 +59,8 @@ class FeaturedTag < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_data
|
def reset_data
|
||||||
self.statuses_count = account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).count
|
self.statuses_count = account.statuses.where(visibility: %i(public unlisted public_unlisted login)).tagged_with(tag).count
|
||||||
self.last_status_at = account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).select(:created_at).first&.created_at
|
self.last_status_at = account.statuses.where(visibility: %i(public unlisted public_unlisted login)).tagged_with(tag).select(:created_at).first&.created_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_featured_tags_limit
|
def validate_featured_tags_limit
|
||||||
|
|
|
@ -29,6 +29,7 @@ class PublicFeed
|
||||||
scope.merge!(account_filters_scope) if account?
|
scope.merge!(account_filters_scope) if account?
|
||||||
scope.merge!(media_only_scope) if media_only?
|
scope.merge!(media_only_scope) if media_only?
|
||||||
scope.merge!(language_scope) if account&.chosen_languages.present?
|
scope.merge!(language_scope) if account&.chosen_languages.present?
|
||||||
|
scope.merge!(anonymous_scope) unless account?
|
||||||
|
|
||||||
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||||
end
|
end
|
||||||
|
@ -97,6 +98,10 @@ class PublicFeed
|
||||||
Status.where(language: account.chosen_languages)
|
Status.where(language: account.chosen_languages)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def anonymous_scope
|
||||||
|
Status.where.not(visibility: :login)
|
||||||
|
end
|
||||||
|
|
||||||
def account_filters_scope
|
def account_filters_scope
|
||||||
Status.not_excluded_by_account(account).tap do |scope|
|
Status.not_excluded_by_account(account).tap do |scope|
|
||||||
scope.merge!(Status.not_domain_blocked_by_account(account)) unless local_only?
|
scope.merge!(Status.not_domain_blocked_by_account(account)) unless local_only?
|
||||||
|
|
|
@ -52,7 +52,7 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
update_index('statuses', :proper)
|
update_index('statuses', :proper)
|
||||||
|
|
||||||
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10 }, _suffix: :visibility
|
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 }, _suffix: :visibility
|
||||||
enum searchability: { public: 0, private: 1, direct: 2, limited: 3, public_unlisted: 10 }, _suffix: :searchability
|
enum searchability: { public: 0, private: 1, direct: 2, limited: 3, public_unlisted: 10 }, _suffix: :searchability
|
||||||
|
|
||||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
||||||
|
@ -102,8 +102,8 @@ class Status < ApplicationRecord
|
||||||
scope :with_accounts, ->(ids) { where(id: ids).includes(:account) }
|
scope :with_accounts, ->(ids) { where(id: ids).includes(:account) }
|
||||||
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
|
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
|
||||||
scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) }
|
scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) }
|
||||||
scope :with_public_visibility, -> { where(visibility: [:public, :public_unlisted]) }
|
scope :with_public_visibility, -> { where(visibility: [:public, :public_unlisted, :login]) }
|
||||||
scope :with_global_timeline_visibility, -> { where(visibility: [:public]) }
|
scope :with_global_timeline_visibility, -> { where(visibility: [:public, :login]) }
|
||||||
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
|
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
|
||||||
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
|
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
|
||||||
scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
|
scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
|
||||||
|
@ -526,7 +526,7 @@ class Status < ApplicationRecord
|
||||||
def set_searchability
|
def set_searchability
|
||||||
return if searchability.nil?
|
return if searchability.nil?
|
||||||
|
|
||||||
self.searchability = [Status.searchabilities[searchability], Status.visibilities[visibility == 'public_unlisted' ? 'public' : visibility]].max
|
self.searchability = [Status.searchabilities[searchability], Status.visibilities[visibility == 'public_unlisted' || visibility == 'login' ? 'public' : visibility]].max
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_conversation
|
def set_conversation
|
||||||
|
|
|
@ -37,6 +37,7 @@ class UserSettings
|
||||||
setting :use_system_font, default: false
|
setting :use_system_font, default: false
|
||||||
setting :disable_swiping, default: false
|
setting :disable_swiping, default: false
|
||||||
setting :delete_modal, default: true
|
setting :delete_modal, default: true
|
||||||
|
setting :enable_login_privacy, default: false
|
||||||
setting :reblog_modal, default: false
|
setting :reblog_modal, default: false
|
||||||
setting :unfollow_modal, default: true
|
setting :unfollow_modal, default: true
|
||||||
setting :reduce_motion, default: false
|
setting :reduce_motion, default: false
|
||||||
|
|
|
@ -15,6 +15,8 @@ class StatusPolicy < ApplicationPolicy
|
||||||
|
|
||||||
if requires_mention?
|
if requires_mention?
|
||||||
owned? || mention_exists?
|
owned? || mention_exists?
|
||||||
|
elsif login?
|
||||||
|
owned? || !current_account.nil?
|
||||||
elsif private?
|
elsif private?
|
||||||
owned? || following_author? || mention_exists?
|
owned? || following_author? || mention_exists?
|
||||||
else
|
else
|
||||||
|
@ -58,6 +60,10 @@ class StatusPolicy < ApplicationPolicy
|
||||||
record.private_visibility?
|
record.private_visibility?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def login?
|
||||||
|
record.login_visibility?
|
||||||
|
end
|
||||||
|
|
||||||
def public?
|
def public?
|
||||||
record.public_visibility? || record.public_unlisted_visibility?
|
record.public_visibility? || record.public_unlisted_visibility?
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StatusRelationshipsPresenter
|
class StatusRelationshipsPresenter
|
||||||
PINNABLE_VISIBILITIES = %w(public public_unlisted unlisted private).freeze
|
PINNABLE_VISIBILITIES = %w(public public_unlisted unlisted login private).freeze
|
||||||
|
|
||||||
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map,
|
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map,
|
||||||
:bookmarks_map, :filters_map, :emoji_reactions_map
|
:bookmarks_map, :filters_map, :emoji_reactions_map
|
||||||
|
|
|
@ -43,6 +43,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
||||||
store[:display_media] = object.current_account.user.setting_display_media
|
store[:display_media] = object.current_account.user.setting_display_media
|
||||||
store[:display_media_expand] = object.current_account.user.setting_display_media_expand
|
store[:display_media_expand] = object.current_account.user.setting_display_media_expand
|
||||||
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
|
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
|
||||||
|
store[:enable_login_privacy] = object.current_account.user.setting_enable_login_privacy
|
||||||
store[:reduce_motion] = object.current_account.user.setting_reduce_motion
|
store[:reduce_motion] = object.current_account.user.setting_reduce_motion
|
||||||
store[:disable_swiping] = object.current_account.user.setting_disable_swiping
|
store[:disable_swiping] = object.current_account.user.setting_disable_swiping
|
||||||
store[:advanced_layout] = object.current_account.user.setting_advanced_layout
|
store[:advanced_layout] = object.current_account.user.setting_advanced_layout
|
||||||
|
|
|
@ -57,7 +57,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
# UX differences
|
# UX differences
|
||||||
if object.limited_visibility?
|
if object.limited_visibility?
|
||||||
'private'
|
'private'
|
||||||
elsif object.public_unlisted_visibility?
|
elsif object.public_unlisted_visibility? || object.login_visibility?
|
||||||
'public'
|
'public'
|
||||||
else
|
else
|
||||||
object.visibility
|
object.visibility
|
||||||
|
@ -159,7 +159,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
current_user? &&
|
current_user? &&
|
||||||
current_user.account_id == object.account_id &&
|
current_user.account_id == object.account_id &&
|
||||||
!object.reblog? &&
|
!object.reblog? &&
|
||||||
%w(public unlisted public_unlisted private).include?(object.visibility)
|
%w(public unlisted public_unlisted login private).include?(object.visibility)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reactions?
|
def reactions?
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
module AccountScope
|
module AccountScope
|
||||||
def scope_status(status)
|
def scope_status(status)
|
||||||
case status.visibility.to_sym
|
case status.visibility.to_sym
|
||||||
when :public, :unlisted, :public_unlisted
|
when :public, :unlisted, :public_unlisted, :login
|
||||||
# scope_local.merge(scope_list_following_account(status.account))
|
|
||||||
scope_local
|
scope_local
|
||||||
when :private
|
when :private
|
||||||
scope_account_local_followers(status.account)
|
scope_account_local_followers(status.account)
|
||||||
|
|
|
@ -46,10 +46,10 @@ class FanOutOnWriteService < BaseService
|
||||||
notify_about_update! if update?
|
notify_about_update! if update?
|
||||||
|
|
||||||
case @status.visibility.to_sym
|
case @status.visibility.to_sym
|
||||||
when :public, :unlisted, :public_unlisted, :private
|
when :public, :unlisted, :public_unlisted, :login, :private
|
||||||
deliver_to_all_followers!
|
deliver_to_all_followers!
|
||||||
deliver_to_lists!
|
deliver_to_lists!
|
||||||
deliver_to_antennas! if [:public, :public_unlisted].include?(@status.visibility.to_sym) && !@account.dissubscribable
|
deliver_to_antennas! if [:public, :public_unlisted, :login].include?(@status.visibility.to_sym) && !@account.dissubscribable
|
||||||
deliver_to_stl_antennas!
|
deliver_to_stl_antennas!
|
||||||
when :limited
|
when :limited
|
||||||
deliver_to_mentioned_followers!
|
deliver_to_mentioned_followers!
|
||||||
|
@ -121,7 +121,7 @@ class FanOutOnWriteService < BaseService
|
||||||
antennas = Antenna.available_stls
|
antennas = Antenna.available_stls
|
||||||
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
||||||
|
|
||||||
antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where.not(list_id: 0) if !@account.domain.nil? || [:public, :public_unlisted].exclude?(@status.visibility.to_sym)
|
antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where.not(list_id: 0) if !@account.domain.nil? || [:public, :public_unlisted, :login].exclude?(@status.visibility.to_sym)
|
||||||
|
|
||||||
collection = AntennaCollection.new(@status, @options[:update])
|
collection = AntennaCollection.new(@status, @options[:update])
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class GroupReblogService < BaseService
|
||||||
return nil if status.account.group?
|
return nil if status.account.group?
|
||||||
|
|
||||||
visibility = status.visibility.to_sym
|
visibility = status.visibility.to_sym
|
||||||
return nil unless %i(public public_unlisted unlisted private direct).include?(visibility)
|
return nil unless %i(public public_unlisted unlisted login private direct).include?(visibility)
|
||||||
|
|
||||||
accounts = status.mentions.map(&:account) | status.active_mentions.map(&:account)
|
accounts = status.mentions.map(&:account) | status.active_mentions.map(&:account)
|
||||||
transcription = %i(private direct).include?(visibility)
|
transcription = %i(private direct).include?(visibility)
|
||||||
|
|
|
@ -72,7 +72,7 @@ class PostStatusService < BaseService
|
||||||
end) || @options[:spoiler_text].present?
|
end) || @options[:spoiler_text].present?
|
||||||
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
||||||
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
||||||
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted) && @account.silenced?
|
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced?
|
||||||
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
|
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
|
||||||
@searchability = searchability
|
@searchability = searchability
|
||||||
@markdown = @options[:markdown] || false
|
@markdown = @options[:markdown] || false
|
||||||
|
@ -85,9 +85,9 @@ class PostStatusService < BaseService
|
||||||
def searchability
|
def searchability
|
||||||
case @options[:searchability]&.to_sym
|
case @options[:searchability]&.to_sym
|
||||||
when :public
|
when :public
|
||||||
case @visibility&.to_sym when :public, :public_unlisted then :public when :unlisted, :private then :private else :direct end
|
case @visibility&.to_sym when :public, :public_unlisted, :login then :public when :unlisted, :private then :private else :direct end
|
||||||
when :private
|
when :private
|
||||||
case @visibility&.to_sym when :public, :public_unlisted, :unlisted, :private then :private else :direct end
|
case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted, :private then :private else :direct end
|
||||||
when :direct
|
when :direct
|
||||||
:direct
|
:direct
|
||||||
when nil
|
when nil
|
||||||
|
|
|
@ -62,7 +62,7 @@ class ReportService < BaseService
|
||||||
# If the account making reports is remote, it is likely anonymized so we have to relax the requirements for attaching statuses.
|
# If the account making reports is remote, it is likely anonymized so we have to relax the requirements for attaching statuses.
|
||||||
domain = @source_account.domain.to_s.downcase
|
domain = @source_account.domain.to_s.downcase
|
||||||
has_followers = @target_account.followers.where(Account.arel_table[:domain].lower.eq(domain)).exists?
|
has_followers = @target_account.followers.where(Account.arel_table[:domain].lower.eq(domain)).exists?
|
||||||
visibility = has_followers ? %i(public unlisted public_unlisted private) : %i(public unlisted public_unlisted)
|
visibility = has_followers ? %i(public unlisted public_unlisted login private) : %i(public unlisted public_unlisted)
|
||||||
scope = @target_account.statuses.with_discarded
|
scope = @target_account.statuses.with_discarded
|
||||||
scope.merge!(scope.where(visibility: visibility).or(scope.where('EXISTS (SELECT 1 FROM mentions m JOIN accounts a ON m.account_id = a.id WHERE lower(a.domain) = ?)', domain)))
|
scope.merge!(scope.where(visibility: visibility).or(scope.where('EXISTS (SELECT 1 FROM mentions m JOIN accounts a ON m.account_id = a.id WHERE lower(a.domain) = ?)', domain)))
|
||||||
# Allow missing posts to not drop reports that include e.g. a deleted post
|
# Allow missing posts to not drop reports that include e.g. a deleted post
|
||||||
|
|
|
@ -33,6 +33,9 @@
|
||||||
.fields-group.fields-row__column.fields-row__column-6
|
.fields-group.fields-row__column.fields-row__column-6
|
||||||
= ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: lambda { |locale| locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language')
|
= ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: lambda { |locale| locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= ff.input :public_post_to_unlisted, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_public_post_to_unlisted'), hint: I18n.t('simple_form.hints.defaults.setting_public_post_to_unlisted')
|
= ff.input :public_post_to_unlisted, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_public_post_to_unlisted'), hint: I18n.t('simple_form.hints.defaults.setting_public_post_to_unlisted')
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ class DeliveryEmojiReactionWorker
|
||||||
redis.publish("timeline:#{account.id}", payload_json) if !account.user&.setting_stop_emoji_reaction_streaming && redis.exists?("subscribed:timeline:#{account.id}")
|
redis.publish("timeline:#{account.id}", payload_json) if !account.user&.setting_stop_emoji_reaction_streaming && redis.exists?("subscribed:timeline:#{account.id}")
|
||||||
end
|
end
|
||||||
|
|
||||||
if [:public, :unlisted, :public_unlisted].exclude?(status.visibility.to_sym) && status.account_id != my_account_id &&
|
if [:public, :unlisted, :public_unlisted, :login].exclude?(status.visibility.to_sym) && status.account_id != my_account_id &&
|
||||||
redis.exists?("subscribed:timeline:#{status.account_id}") && !status.account.user&.setting_stop_emoji_reaction_streaming
|
redis.exists?("subscribed:timeline:#{status.account_id}") && !status.account.user&.setting_stop_emoji_reaction_streaming
|
||||||
redis.publish("timeline:#{status.account_id}", payload_json)
|
redis.publish("timeline:#{status.account_id}", payload_json)
|
||||||
end
|
end
|
||||||
|
|
|
@ -220,6 +220,7 @@ en:
|
||||||
setting_display_media_hide_all: Hide all
|
setting_display_media_hide_all: Hide all
|
||||||
setting_display_media_show_all: Show all
|
setting_display_media_show_all: Show all
|
||||||
setting_emoji_reaction_streaming_notify_impl2: Enable stamp notification compat with Nyastodon, Catstodon, glitch-soc
|
setting_emoji_reaction_streaming_notify_impl2: Enable stamp notification compat with Nyastodon, Catstodon, glitch-soc
|
||||||
|
setting_enable_login_privacy: Enable login visibility
|
||||||
setting_expand_spoilers: Always expand posts marked with content warnings
|
setting_expand_spoilers: Always expand posts marked with content warnings
|
||||||
setting_hide_followers_count: Hide followers count
|
setting_hide_followers_count: Hide followers count
|
||||||
setting_hide_following_count: Hide following count
|
setting_hide_following_count: Hide following count
|
||||||
|
|
|
@ -227,6 +227,7 @@ ja:
|
||||||
setting_display_media_expand: 5個目以降のメディアも表示する (最大16)
|
setting_display_media_expand: 5個目以降のメディアも表示する (最大16)
|
||||||
setting_display_media_hide_all: 非表示
|
setting_display_media_hide_all: 非表示
|
||||||
setting_display_media_show_all: 表示
|
setting_display_media_show_all: 表示
|
||||||
|
setting_enable_login_privacy: 公開範囲「ログインユーザーのみ」をWeb UIで選択可能にする
|
||||||
setting_emoji_reaction_streaming_notify_impl2: Nyastodon, Catstodon, glitch-soc互換のスタンプ機能を有効にする
|
setting_emoji_reaction_streaming_notify_impl2: Nyastodon, Catstodon, glitch-soc互換のスタンプ機能を有効にする
|
||||||
setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
|
setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
|
||||||
setting_hide_followers_count: フォロワー数を隠す
|
setting_hide_followers_count: フォロワー数を隠す
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue