* Add: #483 特定の公開範囲を無効にするオプション * Fix test partically * Complete
This commit is contained in:
parent
7f9ec2e510
commit
f79fb3adae
24 changed files with 177 additions and 59 deletions
|
@ -19,6 +19,16 @@ class Settings::Preferences::BaseController < Settings::BaseController
|
|||
end
|
||||
|
||||
def user_params
|
||||
original_user_params.tap do |params|
|
||||
params[:settings_attributes]&.merge!(disabled_visibilities_params[:settings_attributes] || {})
|
||||
end
|
||||
end
|
||||
|
||||
def original_user_params
|
||||
params.require(:user).permit(:locale, :time_zone, chosen_languages: [], settings_attributes: UserSettings.keys)
|
||||
end
|
||||
|
||||
def disabled_visibilities_params
|
||||
params.require(:user).permit(settings_attributes: { enabled_visibilities: [] })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -116,7 +116,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
const fulltext = this.getFulltextForCharacterCounting();
|
||||
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
|
||||
|
||||
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxChars || (isOnlyWhitespace && !anyMedia) || (privacy === 'circle' && !isEditing && !circleId));
|
||||
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxChars || (isOnlyWhitespace && !anyMedia) || (privacy === 'circle' && !isEditing && !circleId) || privacy === 'banned');
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
|
|
|
@ -9,6 +9,7 @@ import Overlay from 'react-overlays/Overlay';
|
|||
|
||||
import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
|
||||
import PublicUnlistedIcon from '@/material-icons/400-24px/cloud.svg?react';
|
||||
import MutualIcon from '@/material-icons/400-24px/compare_arrows.svg?react';
|
||||
import LoginIcon from '@/material-icons/400-24px/key.svg?react';
|
||||
|
@ -18,7 +19,7 @@ import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react';
|
|||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import LimitedIcon from '@/material-icons/400-24px/shield.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { enableLoginPrivacy, enableLocalPrivacy, enablePublicPrivacy } from 'mastodon/initial_state';
|
||||
import { enabledVisibilites } from 'mastodon/initial_state';
|
||||
|
||||
import { PrivacyDropdownMenu } from './privacy_dropdown_menu';
|
||||
|
||||
|
@ -42,6 +43,8 @@ const messages = defineMessages({
|
|||
reply_long: { id: 'privacy.reply.long', defaultMessage: 'Reply to limited post' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Specific people' },
|
||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Everyone mentioned in the post' },
|
||||
banned_short: { id: 'privacy.banned.short', defaultMessage: 'No posting' },
|
||||
banned_long: { id: 'privacy.banned.long', defaultMessage: 'All public range submissions are disabled. User settings need to be modified.' },
|
||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Change post privacy' },
|
||||
unlisted_extra: { id: 'privacy.unlisted.additional', defaultMessage: 'This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.' },
|
||||
});
|
||||
|
@ -131,7 +134,12 @@ class PrivacyDropdown extends PureComponent {
|
|||
UNSAFE_componentWillMount () {
|
||||
const { intl: { formatMessage } } = this.props;
|
||||
|
||||
this.options = [
|
||||
this.dynamicOptions = [
|
||||
{ icon: 'reply', iconComponent: ReplyIcon, value: 'reply', text: formatMessage(messages.reply_short), meta: formatMessage(messages.reply_long), extra: formatMessage(messages.limited_short), extraIcomComponent: LimitedIcon },
|
||||
{ icon: 'ban', iconComponent: BlockIcon, value: 'banned', text: formatMessage(messages.banned_short), meta: formatMessage(messages.banned_long) },
|
||||
];
|
||||
|
||||
this.originalOptions = [
|
||||
{ icon: 'globe', iconComponent: PublicIcon, value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'cloud', iconComponent: PublicUnlistedIcon, value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
|
||||
{ icon: 'key', iconComponent: LoginIcon, value: 'login', text: formatMessage(messages.login_short), meta: formatMessage(messages.login_long) },
|
||||
|
@ -139,31 +147,28 @@ class PrivacyDropdown extends PureComponent {
|
|||
{ icon: 'lock', iconComponent: LockIcon, value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
{ icon: 'exchange', iconComponent: MutualIcon, value: 'mutual', text: formatMessage(messages.mutual_short), meta: formatMessage(messages.mutual_long), extra: formatMessage(messages.limited_short), extraIcomComponent: LimitedIcon },
|
||||
{ icon: 'user-circle', iconComponent: CircleIcon, value: 'circle', text: formatMessage(messages.circle_short), meta: formatMessage(messages.circle_long), extra: formatMessage(messages.limited_short), extraIcomComponent: LimitedIcon },
|
||||
{ icon: 'at', iconComponent: AlternateEmailIcon, value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
...this.dynamicOptions,
|
||||
];
|
||||
|
||||
if (!this.props.noDirect) {
|
||||
this.options.push(
|
||||
{ icon: 'at', iconComponent: AlternateEmailIcon, value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
);
|
||||
this.options = [...this.originalOptions];
|
||||
|
||||
if (this.props.noDirect) {
|
||||
this.options = this.options.filter((opt) => opt.value !== 'direct');
|
||||
}
|
||||
|
||||
if (this.props.noLimited) {
|
||||
this.options = this.options.filter((opt) => !['mutual', 'circle'].includes(opt.value));
|
||||
}
|
||||
|
||||
if (!enableLoginPrivacy) {
|
||||
this.options = this.options.filter((opt) => opt.value !== 'login');
|
||||
if (enabledVisibilites) {
|
||||
this.options = this.options.filter((opt) => enabledVisibilites.includes(opt.value));
|
||||
}
|
||||
|
||||
if (!enableLocalPrivacy) {
|
||||
this.options = this.options.filter((opt) => opt.value !== 'public_unlisted');
|
||||
if (this.options.length === 0) {
|
||||
this.options.push(this.dynamicOptions.find((opt) => opt.value === 'banned'));
|
||||
this.props.onChange('banned');
|
||||
}
|
||||
|
||||
if (!enablePublicPrivacy) {
|
||||
this.options = this.options.filter((opt) => opt.value !== 'public');
|
||||
}
|
||||
|
||||
this.selectableOptions = [...this.options];
|
||||
}
|
||||
|
||||
setTargetRef = c => {
|
||||
|
@ -183,18 +188,16 @@ class PrivacyDropdown extends PureComponent {
|
|||
const { open, placement } = this.state;
|
||||
|
||||
if (replyToLimited) {
|
||||
if (!this.selectableOptions.some((op) => op.value === 'reply')) {
|
||||
this.selectableOptions.unshift(
|
||||
{ icon: 'reply', iconComponent: ReplyIcon, value: 'reply', text: intl.formatMessage(messages.reply_short), meta: intl.formatMessage(messages.reply_long), extra: intl.formatMessage(messages.limited_short), extraIcomComponent: LimitedIcon },
|
||||
);
|
||||
if (!this.options.some((op) => op.value === 'reply')) {
|
||||
this.options.unshift(this.dynamicOptions.find((opt) => opt.value === 'reply'));
|
||||
}
|
||||
} else {
|
||||
if (this.selectableOptions.some((op) => op.value === 'reply')) {
|
||||
this.selectableOptions = this.selectableOptions.filter((op) => op.value !== 'reply');
|
||||
if (this.options.some((op) => op.value === 'reply')) {
|
||||
this.options = this.options.filter((op) => op.value !== 'reply');
|
||||
}
|
||||
}
|
||||
|
||||
const valueOption = this.selectableOptions.find(item => item.value === value) || this.selectableOptions[0];
|
||||
const valueOption = (disabled ? this.originalOptions : this.options).find(item => item.value === value) || this.options[0];
|
||||
|
||||
return (
|
||||
<div ref={this.setTargetRef} onKeyDown={this.handleKeyDown}>
|
||||
|
@ -217,7 +220,7 @@ class PrivacyDropdown extends PureComponent {
|
|||
<div {...props}>
|
||||
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
||||
<PrivacyDropdownMenu
|
||||
items={this.selectableOptions}
|
||||
items={this.options}
|
||||
value={value}
|
||||
onClose={this.handleClose}
|
||||
onChange={this.handleChange}
|
||||
|
|
|
@ -14,7 +14,7 @@ import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
|||
import LockOpenIcon from '@/material-icons/400-24px/no_encryption.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { enableLocalPrivacy } from 'mastodon/initial_state';
|
||||
import { enabledVisibilites } from 'mastodon/initial_state';
|
||||
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -236,7 +236,7 @@ class SearchabilityDropdown extends PureComponent {
|
|||
{ icon: 'at', iconComponent: AlternateEmailIcon, value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) },
|
||||
];
|
||||
|
||||
if (!enableLocalPrivacy) {
|
||||
if (!enabledVisibilites.includes('public_unlisted')) {
|
||||
this.options = this.options.filter((opt) => opt.value !== 'public_unlisted');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,16 +32,14 @@
|
|||
* @property {boolean=} delete_modal
|
||||
* @property {boolean=} disable_swiping
|
||||
* @property {string=} disabled_account_id
|
||||
* @property {string[]} enabled_visibilities
|
||||
* @property {string} display_media
|
||||
* @property {boolean} display_media_expand
|
||||
* @property {string} domain
|
||||
* @property {string} dtl_tag
|
||||
* @property {boolean} enable_emoji_reaction
|
||||
* @property {boolean} enable_login_privacy
|
||||
* @property {boolean} enable_local_privacy
|
||||
* @property {boolean} enable_local_timeline
|
||||
* @property {boolean} enable_dtl_menu
|
||||
* @property {boolean} enable_public_privacy
|
||||
* @property {boolean=} expand_spoilers
|
||||
* @property {string[]} featured_tags
|
||||
* @property {HideItemsDefinition[]} hide_items
|
||||
|
@ -121,15 +119,13 @@ export const boostModal = getMeta('boost_modal');
|
|||
export const deleteModal = getMeta('delete_modal');
|
||||
export const disableSwiping = getMeta('disable_swiping');
|
||||
export const disabledAccountId = getMeta('disabled_account_id');
|
||||
export const enabledVisibilites = getMeta('enabled_visibilities');
|
||||
export const displayMedia = getMeta('display_media');
|
||||
export const displayMediaExpand = getMeta('display_media_expand');
|
||||
export const domain = getMeta('domain');
|
||||
export const dtlTag = getMeta('dtl_tag');
|
||||
export const enableEmojiReaction = getMeta('enable_emoji_reaction');
|
||||
export const enableLocalPrivacy = getMeta('enable_local_privacy');
|
||||
export const enablePublicPrivacy = getMeta('enable_public_privacy');
|
||||
export const enableLocalTimeline = getMeta('enable_local_timeline');
|
||||
export const enableLoginPrivacy = getMeta('enable_login_privacy');
|
||||
export const enableDtlMenu = getMeta('enable_dtl_menu');
|
||||
export const expandSpoilers = getMeta('expand_spoilers');
|
||||
export const featuredTags = getMeta('featured_tags') || [];
|
||||
|
|
|
@ -712,6 +712,8 @@
|
|||
"poll.votes": "{votes, plural, one {# vote} other {# votes}}",
|
||||
"poll_button.add_poll": "Add a poll",
|
||||
"poll_button.remove_poll": "Remove poll",
|
||||
"privacy.banned.long": "All public range submissions are disabled. User settings need to be modified.",
|
||||
"privacy.banned.short": "No posting",
|
||||
"privacy.change": "Change post privacy",
|
||||
"privacy.circle.long": "Circle members only",
|
||||
"privacy.circle.short": "Circle",
|
||||
|
|
|
@ -696,6 +696,8 @@
|
|||
"poll.votes": "{votes}票",
|
||||
"poll_button.add_poll": "アンケートを追加",
|
||||
"poll_button.remove_poll": "アンケートを削除",
|
||||
"privacy.banned.long": "全ての公開範囲が、ユーザー設定により無効になっています",
|
||||
"privacy.banned.short": "投稿不可",
|
||||
"privacy.change": "公開範囲を変更",
|
||||
"privacy.circle.long": "サークルメンバーのみ閲覧可",
|
||||
"privacy.circle.short": "サークル (投稿時点)",
|
||||
|
|
|
@ -58,7 +58,7 @@ import {
|
|||
import { REDRAFT } from '../actions/statuses';
|
||||
import { STORE_HYDRATE } from '../actions/store';
|
||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||
import { enableLocalPrivacy, enableLoginPrivacy, enablePublicPrivacy, me } from '../initial_state';
|
||||
import { enabledVisibilites, me } from '../initial_state';
|
||||
import { unescapeHTML } from '../utils/html';
|
||||
import { uuid } from '../uuid';
|
||||
|
||||
|
@ -143,9 +143,6 @@ function clearAll(state) {
|
|||
if (state.get('stay_privacy') && !state.get('in_reply_to')) {
|
||||
map.set('default_privacy', state.get('privacy'));
|
||||
}
|
||||
if ((map.get('privacy') === 'login' && !enableLoginPrivacy) || (map.get('privacy') === 'public_unlisted' && !enableLocalPrivacy)) {
|
||||
map.set('privacy', enablePublicPrivacy ? 'public' : 'unlisted');
|
||||
}
|
||||
if (!state.get('in_reply_to')) {
|
||||
map.set('posted_on_this_session', true);
|
||||
}
|
||||
|
@ -153,7 +150,7 @@ function clearAll(state) {
|
|||
map.set('limited_scope', null);
|
||||
map.set('id', null);
|
||||
map.set('in_reply_to', null);
|
||||
if (state.get('default_searchability') === 'public_unlisted' && !enableLocalPrivacy) {
|
||||
if (state.get('default_searchability') === 'public_unlisted' && !enabledVisibilites.includes('public_unlisted')) {
|
||||
map.set('searchability', 'public');
|
||||
} else {
|
||||
map.set('searchability', state.get('default_searchability'));
|
||||
|
@ -163,6 +160,7 @@ function clearAll(state) {
|
|||
map.update('media_attachments', list => list.clear());
|
||||
map.set('poll', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
normalizePrivacy(map);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -243,6 +241,22 @@ const sortHashtagsByUse = (state, tags) => {
|
|||
return sorted;
|
||||
};
|
||||
|
||||
const normalizePrivacy = (map, last) => {
|
||||
if (!enabledVisibilites) {
|
||||
return;
|
||||
}
|
||||
|
||||
const current = map.get('privacy');
|
||||
const invalid = !enabledVisibilites.includes(current);
|
||||
|
||||
if (invalid) {
|
||||
if (enabledVisibilites.length > 0) {
|
||||
const index = last ? enabledVisibilites.length - 1 : 0;
|
||||
map.set('privacy', enabledVisibilites[index]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const insertEmoji = (state, position, emojiData, needsSpace) => {
|
||||
const oldText = state.get('text');
|
||||
const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native;
|
||||
|
@ -467,6 +481,8 @@ export default function compose(state = initialState, action) {
|
|||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
}
|
||||
|
||||
normalizePrivacy(map);
|
||||
});
|
||||
case COMPOSE_SUBMIT_REQUEST:
|
||||
return state.set('is_submitting', true);
|
||||
|
@ -532,6 +548,7 @@ export default function compose(state = initialState, action) {
|
|||
return state.withMutations(map => {
|
||||
map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
|
||||
map.set('privacy', 'direct');
|
||||
normalizePrivacy(map, true);
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
@ -584,6 +601,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status)));
|
||||
map.set('in_reply_to', action.status.get('in_reply_to_id'));
|
||||
map.set('privacy', action.status.get('visibility_ex'));
|
||||
normalizePrivacy(map);
|
||||
map.set('reply_to_limited', action.status.get('limited_scope') === 'reply');
|
||||
map.set('limited_scope', null);
|
||||
map.set('media_attachments', action.status.get('media_attachments').map((media) => media.set('unattached', true)));
|
||||
|
|
|
@ -105,13 +105,18 @@ code {
|
|||
|
||||
.overridden,
|
||||
.recommended,
|
||||
.not_recommended,
|
||||
.kmyblue {
|
||||
.not_recommended {
|
||||
position: absolute;
|
||||
margin: 0 4px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.kmyblue {
|
||||
position: absolute;
|
||||
margin: 0 4px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
|
|
|
@ -27,10 +27,6 @@ module User::HasSettings
|
|||
settings['reaction_deck']
|
||||
end
|
||||
|
||||
def setting_enable_login_privacy
|
||||
settings['web.enable_login_privacy']
|
||||
end
|
||||
|
||||
def setting_enable_dtl_menu
|
||||
settings['web.enable_dtl_menu']
|
||||
end
|
||||
|
@ -235,6 +231,14 @@ module User::HasSettings
|
|||
settings['default_reblog_privacy'] || 'unset'
|
||||
end
|
||||
|
||||
def setting_enabled_visibilities
|
||||
settings['enabled_visibilities']
|
||||
end
|
||||
|
||||
def setting_disabled_visibilities
|
||||
settings['disabled_visibilities']
|
||||
end
|
||||
|
||||
def setting_default_searchability
|
||||
settings['default_searchability'] || 'direct'
|
||||
end
|
||||
|
|
|
@ -497,7 +497,11 @@ class Status < ApplicationRecord
|
|||
|
||||
class << self
|
||||
def selectable_visibilities
|
||||
vs = visibilities.keys - %w(direct limited)
|
||||
selectable_all_visibilities - %w(mutual circle reply direct)
|
||||
end
|
||||
|
||||
def selectable_all_visibilities
|
||||
vs = %w(public public_unlisted login unlisted private mutual circle reply direct)
|
||||
vs -= %w(public_unlisted) unless Setting.enable_public_unlisted_visibility
|
||||
vs -= %w(public) unless Setting.enable_public_visibility
|
||||
vs
|
||||
|
|
|
@ -23,6 +23,7 @@ class UserSettings
|
|||
setting :default_privacy, default: nil, in: %w(public public_unlisted login unlisted private)
|
||||
setting :stay_privacy, default: false
|
||||
setting :default_reblog_privacy, default: nil
|
||||
setting :disabled_visibilities, default: %w()
|
||||
setting :default_searchability, default: :direct, in: %w(public private direct limited public_unlisted)
|
||||
setting :default_searchability_of_search, default: :public, in: %w(public private direct limited)
|
||||
setting :use_public_index, default: true
|
||||
|
@ -47,6 +48,7 @@ class UserSettings
|
|||
setting_inverse_alias :show_statuses_count, :hide_statuses_count
|
||||
setting_inverse_alias :show_following_count, :hide_following_count
|
||||
setting_inverse_alias :show_followers_count, :hide_followers_count
|
||||
setting_inverse_array :enabled_visibilities, :disabled_visibilities, %w(public public_unlisted login unlisted private mutual circle reply personal direct)
|
||||
|
||||
namespace :web do
|
||||
setting :advanced_layout, default: false
|
||||
|
@ -57,7 +59,6 @@ class UserSettings
|
|||
setting :bookmark_category_needed, default: false
|
||||
setting :disable_swiping, default: false
|
||||
setting :delete_modal, default: true
|
||||
setting :enable_login_privacy, default: false
|
||||
setting :enable_dtl_menu, default: false
|
||||
setting :hide_recent_emojis, default: false
|
||||
setting :enable_emoji_reaction, default: true
|
||||
|
|
|
@ -14,6 +14,10 @@ module UserSettings::DSL
|
|||
@definitions[key] = @definitions[original_key].inverse_of(key)
|
||||
end
|
||||
|
||||
def setting_inverse_array(key, original_key, reverse_array)
|
||||
@definitions[key] = @definitions[original_key].array_inverse_of(key, reverse_array)
|
||||
end
|
||||
|
||||
def namespace(key, &block)
|
||||
@definitions ||= {}
|
||||
|
||||
|
|
|
@ -22,4 +22,8 @@ class UserSettings::Namespace
|
|||
def setting_inverse_alias(key, original_key)
|
||||
@definitions[key] = @definitions[original_key].inverse_of(key)
|
||||
end
|
||||
|
||||
def setting_inverse_array(key, original_key, reverse_array)
|
||||
@definitions[key] = @definitions[original_key].array_inverse_of(key, reverse_array)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,12 @@ class UserSettings::Setting
|
|||
self
|
||||
end
|
||||
|
||||
def array_inverse_of(name, arr)
|
||||
@inverse_of_array = name.to_sym
|
||||
@reverse_array = arr
|
||||
self
|
||||
end
|
||||
|
||||
def value_for(name, original_value)
|
||||
value = begin
|
||||
if original_value.nil?
|
||||
|
@ -24,13 +30,21 @@ class UserSettings::Setting
|
|||
end
|
||||
end
|
||||
|
||||
value = value.compact_blank if value.is_a?(Array)
|
||||
|
||||
if !@inverse_of.nil? && @inverse_of == name.to_sym
|
||||
!value
|
||||
elsif !@inverse_of_array.nil? && @inverse_of_array == name.to_sym
|
||||
reverse_array(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def reverse_array(value)
|
||||
@reverse_array.clone.filter { |v| value.exclude?(v) }
|
||||
end
|
||||
|
||||
def default_value
|
||||
if @default_value.respond_to?(:call)
|
||||
@default_value.call
|
||||
|
@ -39,7 +53,13 @@ class UserSettings::Setting
|
|||
end
|
||||
end
|
||||
|
||||
def array_type?
|
||||
default_value.is_a?(Array) || default_value == []
|
||||
end
|
||||
|
||||
def type
|
||||
return ActiveRecord::Type.lookup(:string, array: true) if array_type?
|
||||
|
||||
case default_value
|
||||
when TrueClass, FalseClass
|
||||
ActiveModel::Type::Boolean.new
|
||||
|
|
|
@ -26,7 +26,6 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:display_media_expand] = object_account_user.setting_display_media_expand
|
||||
store[:expand_spoilers] = object_account_user.setting_expand_spoilers
|
||||
store[:enable_emoji_reaction] = object_account_user.setting_enable_emoji_reaction && Setting.enable_emoji_reaction
|
||||
store[:enable_login_privacy] = object_account_user.setting_enable_login_privacy
|
||||
store[:enable_dtl_menu] = object_account_user.setting_enable_dtl_menu
|
||||
store[:reduce_motion] = object_account_user.setting_reduce_motion
|
||||
store[:disable_swiping] = object_account_user.setting_disable_swiping
|
||||
|
@ -50,6 +49,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
object_account_user.setting_show_quote_in_public ? nil : 'quote_in_public',
|
||||
object_account_user.setting_show_relationships ? nil : 'relationships',
|
||||
].compact
|
||||
store[:enabled_visibilities] = enabled_visibilities
|
||||
store[:featured_tags] = object.current_account.featured_tags.pluck(:name)
|
||||
else
|
||||
store[:auto_play_gif] = Setting.auto_play_gif
|
||||
|
@ -112,6 +112,13 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
LanguagesHelper::SUPPORTED_LOCALES.map { |(key, value)| [key, value[0], value[1]] }
|
||||
end
|
||||
|
||||
def enabled_visibilities
|
||||
vs = object_account_user.setting_enabled_visibilities
|
||||
vs -= %w(public_unlisted) unless Setting.enable_public_unlisted_visibility
|
||||
vs -= %w(public) unless Setting.enable_public_visibility
|
||||
vs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_meta_store
|
||||
|
@ -121,8 +128,6 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
admin: object.admin&.id&.to_s,
|
||||
domain: Addressable::IDNA.to_unicode(instance_presenter.domain),
|
||||
dtl_tag: dtl_enabled? ? dtl_tag_name : nil,
|
||||
enable_local_privacy: Setting.enable_public_unlisted_visibility,
|
||||
enable_public_privacy: Setting.enable_public_visibility,
|
||||
enable_local_timeline: Setting.enable_local_timeline,
|
||||
limited_federation_mode: Rails.configuration.x.limited_federation_mode,
|
||||
locale: I18n.locale,
|
||||
|
|
|
@ -71,11 +71,7 @@ class PostStatusService < BaseService
|
|||
def preprocess_attributes!
|
||||
@sensitive = (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?
|
||||
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
||||
@visibility = @options[:visibility]&.to_sym || @account.user&.setting_default_privacy&.to_sym
|
||||
@visibility = :limited if %w(mutual circle reply).include?(@options[:visibility])
|
||||
@visibility = :unlisted if (@visibility == :public || @visibility == :public_unlisted || @visibility == :login) && @account.silenced?
|
||||
@visibility = :public_unlisted if @visibility == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted && Setting.enable_public_unlisted_visibility
|
||||
@visibility = Setting.enable_public_unlisted_visibility ? :public_unlisted : :unlisted if !Setting.enable_public_visibility && @visibility == :public
|
||||
@visibility = visibility
|
||||
@limited_scope = @options[:visibility]&.to_sym if @visibility == :limited && @options[:visibility] != 'limited'
|
||||
@searchability = searchability
|
||||
@searchability = :private if @account.silenced? && %i(public public_unlisted).include?(@searchability&.to_sym)
|
||||
|
@ -84,6 +80,7 @@ class PostStatusService < BaseService
|
|||
@scheduled_at = nil if scheduled_in_the_past?
|
||||
@reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?)
|
||||
raise ArgumentError if !Setting.enable_public_unlisted_visibility && (@visibility == :public_unlisted || @searchability == :public_unlisted)
|
||||
raise ArgumentError if @account.user&.setting_disabled_visibilities&.include?((@limited_scope || @visibility).to_s)
|
||||
|
||||
if @in_reply_to.present? && ((@options[:visibility] == 'limited' && @options[:circle_id].nil?) || @limited_scope == :reply)
|
||||
@visibility = :limited
|
||||
|
@ -97,6 +94,15 @@ class PostStatusService < BaseService
|
|||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
|
||||
def visibility
|
||||
v = @options[:visibility]&.to_sym || @account.user&.setting_default_privacy&.to_sym
|
||||
v = :limited if %w(mutual circle reply).include?(@options[:visibility])
|
||||
v = :unlisted if %i(public public_unlisted login).include?(v) && @account.silenced?
|
||||
v = :public_unlisted if v == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted && Setting.enable_public_unlisted_visibility
|
||||
v = Setting.enable_public_unlisted_visibility ? :public_unlisted : :unlisted if !Setting.enable_public_visibility && v == :public
|
||||
v
|
||||
end
|
||||
|
||||
def load_circle
|
||||
return if @visibility == :limited && @limited_scope == :reply && @in_reply_to.present?
|
||||
return unless %w(circle limited reply).include?(@options[:visibility])
|
||||
|
|
|
@ -40,7 +40,18 @@
|
|||
= 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')
|
||||
|
||||
.fields-group
|
||||
= ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false
|
||||
= ff.input :enabled_visibilities,
|
||||
as: :check_boxes,
|
||||
collection_wrapper_tag: 'ul',
|
||||
collection: Status.selectable_all_visibilities,
|
||||
hint: I18n.t('simple_form.hints.defaults.setting_enabled_visibilities'),
|
||||
include_blank: false,
|
||||
item_wrapper_tag: 'li',
|
||||
kmyblue: true,
|
||||
label: I18n.t('simple_form.labels.defaults.setting_enabled_visibilities'),
|
||||
label_method: ->(visibility) { I18n.t("statuses.visibilities.#{visibility}") },
|
||||
required: false,
|
||||
wrapper: :with_block_label
|
||||
|
||||
%h4= t 'preferences.searchability'
|
||||
|
||||
|
|
|
@ -135,6 +135,7 @@ SimpleForm.setup do |config|
|
|||
|
||||
config.wrappers :with_block_label, class: [:input, :with_block_label], hint_class: :field_with_hint, error_class: :field_with_errors do |b|
|
||||
b.use :html5
|
||||
b.optional :kmyblue
|
||||
b.use :label
|
||||
b.use :warning_hint, wrap_with: { tag: :span, class: [:hint, 'warning-hint'] }
|
||||
b.use :hint, wrap_with: { tag: :span, class: :hint }
|
||||
|
|
|
@ -2084,17 +2084,20 @@ en:
|
|||
too_many_mentions: Too many mentions
|
||||
violate_rules: Violate NG rules
|
||||
visibilities:
|
||||
circle: Circle-only
|
||||
direct: Direct
|
||||
limited: Limited
|
||||
login: Login-only
|
||||
login_long: Only logined users
|
||||
mutual: Mutual-only
|
||||
private: Followers-only
|
||||
private_long: Only show to followers
|
||||
public: Public
|
||||
public_long: Everyone can see
|
||||
public_unlisted: Local-public
|
||||
public_unlisted_long: Everyone can see, but not listed on public timelines of other servers
|
||||
unlisted: Unlisted
|
||||
reply: Reply
|
||||
unlisted: Quiet Public (Unlisted)
|
||||
unlisted_long: Everyone can see, but not listed on public timelines
|
||||
unset: Default
|
||||
unset_long: Following Mastodon default behavior
|
||||
|
|
|
@ -2062,17 +2062,20 @@ ja:
|
|||
too_many_mentions: メンションが多すぎます
|
||||
violate_rules: サーバールールに違反するため投稿できません
|
||||
visibilities:
|
||||
circle: サークル限定
|
||||
direct: ダイレクト
|
||||
limited: 限定投稿
|
||||
login: ログインユーザーのみ
|
||||
login_long: ログインしたユーザーのみが見ることができます
|
||||
mutual: 相互限定
|
||||
private: フォロワー限定
|
||||
private_long: フォロワーにのみ表示されます
|
||||
public: 公開
|
||||
public_long: 誰でも見ることができ、かつ公開タイムラインに表示されます
|
||||
public_unlisted: ローカル公開
|
||||
public_unlisted_long: 誰でも見ることができますが、他のサーバーの連合タイムラインには表示されません
|
||||
unlisted: 非収載
|
||||
reply: 返信
|
||||
unlisted: ひかえめな公開 (非収載)
|
||||
unlisted_long: 誰でも見ることができますが、公開タイムラインには表示されません
|
||||
unset: 設定なし
|
||||
unset_long: デフォルトの挙動に従います
|
||||
|
|
|
@ -71,6 +71,7 @@ en:
|
|||
setting_dtl_force_visibility: "With using #%{tag} tag, your post settings will be changed forcibly"
|
||||
setting_emoji_reaction_policy: Even with this setting, users on other servers are free to put their emoji reaction on the post and share it within the same server. If you simply want to remove the emoji reaction from your own screen, you can disable it from the appearance settings
|
||||
setting_enable_emoji_reaction: If turn off, other users still can react your posts
|
||||
setting_enabled_visibilities: If turn off, you cannot select and post the privacy.
|
||||
setting_reject_send_limited_to_suspects: This applies to "Mutual Only" posts. Circle posts will be delivered without exception. Some Misskey servers have independently supported limited posting, but this is a setting for those who are concerned about it, as mutual-only posting exposes some of the users you are mutual with to Misskey users!
|
||||
setting_reject_unlisted_subscription: Misskey and its forks can **subscribe and search** for "non-following" posts from accounts they do not follow. This differs from kmyblue's behavior. It delivers posts in the specified public range to such servers as "followers only". Please understand, however, that due to its structure, it is difficult to handle perfectly and will occasionally be delivered as non-subscribed.
|
||||
setting_reverse_search_quote: Double-quotes will result in a search with a wider range of notation, which is the opposite of Mastodon's default behavior.
|
||||
|
@ -268,7 +269,7 @@ en:
|
|||
setting_dtl_menu: Show DTL menu on web
|
||||
setting_emoji_reaction_streaming_notify_impl2: Enable emoji reaction notification compat with Nyastodon, Catstodon, glitch-soc
|
||||
setting_enable_emoji_reaction: Use emoji reaction function
|
||||
setting_enable_login_privacy: Enable login visibility
|
||||
setting_enabled_visibilities: Enabled visibilities
|
||||
setting_emoji_reaction_policy: Emoji reaction receive/display policy
|
||||
setting_emoji_reaction_policy_items:
|
||||
allow: Allow all
|
||||
|
|
|
@ -75,6 +75,7 @@ ja:
|
|||
setting_emoji_reaction_policy: この設定をしても他のサーバーのユーザーはその投稿に自由に絵文字をつけ、同じサーバーの中で共有できます。単にあなた自身の画面から絵文字リアクションを除去したいだけなら、外観設定から絵文字リアクションを無効にすることができます
|
||||
setting_emoji_reaction_streaming_notify_impl2: 当該サーバーの独自機能に対応したアプリを利用時に、絵文字リアクション機能を利用できます。動作確認していないため(そもそもそのようなアプリ自体を確認できていないため)正しく動かない場合があります
|
||||
setting_enable_emoji_reaction: この機能を無効にしても、他の人はあなたの投稿に絵文字をつけられます
|
||||
setting_enabled_visibilities: チェックを外した公開範囲は投稿することができなくなります。投稿しようとするとエラーが出ます
|
||||
setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
|
||||
setting_public_post_to_unlisted: 未対応のサードパーティアプリからもローカル公開で投稿できますが、公開投稿はWeb以外できなくなります
|
||||
setting_reject_send_limited_to_suspects: これは「相互のみ」投稿に適用されます。サークル投稿は例外なく配送されます。一部のMisskeyサーバーが独自に限定投稿へ対応しましたが、相互のみ投稿を行うとあなたと相互になっているユーザーの一部がMisskeyのユーザーに公開されるため、気になる人向けの設定です
|
||||
|
@ -273,7 +274,7 @@ ja:
|
|||
setting_dtl_force_searchability: DTL投稿の検索許可
|
||||
setting_dtl_force_visibility: DTL投稿の公開範囲
|
||||
setting_dtl_menu: Webクライアントのメニューにディープタイムラインを追加する
|
||||
setting_enable_login_privacy: 公開範囲「ログインユーザーのみ」をWeb UIで選択可能にする
|
||||
setting_enabled_visibilities: 投稿を有効にする公開範囲
|
||||
setting_emoji_reaction_policy: 絵文字リアクション受け入れと表示設定
|
||||
setting_emoji_reaction_policy_items:
|
||||
allow: 全員に許可
|
||||
|
|
|
@ -192,6 +192,20 @@ RSpec.describe PostStatusService do
|
|||
expect(mention_service).to have_received(:call).with(status, limited_type: '', circle: nil, save_records: false)
|
||||
end
|
||||
|
||||
it 'self-banned visibility is set' do
|
||||
user = Fabricate(:user)
|
||||
user.settings.update(disabled_visibilities: ['public_unlisted'])
|
||||
|
||||
expect { subject.call(user.account, text: 'text', visibility: 'public_unlisted') }.to raise_error ActiveRecord::RecordInvalid
|
||||
end
|
||||
|
||||
it 'self-banned visibility is not set' do
|
||||
user = Fabricate(:user)
|
||||
user.settings.update(disabled_visibilities: ['public_unlisted'])
|
||||
|
||||
expect { subject.call(user.account, text: 'text', visibility: 'unlisted') }.to_not raise_error
|
||||
end
|
||||
|
||||
context 'with mutual visibility' do
|
||||
let(:sender) { Fabricate(:user).account }
|
||||
let(:io_account) { Fabricate(:account, domain: 'misskey.io', uri: 'https://misskey.io/actor') }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue