Add circle visibility
This commit is contained in:
parent
44eb57183e
commit
3af223275f
20 changed files with 154 additions and 12 deletions
|
@ -67,6 +67,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
visibility: status_params[:visibility],
|
visibility: status_params[:visibility],
|
||||||
force_visibility: status_params[:force_visibility],
|
force_visibility: status_params[:force_visibility],
|
||||||
searchability: status_params[:searchability],
|
searchability: status_params[:searchability],
|
||||||
|
circle_id: status_params[:circle_id],
|
||||||
language: status_params[:language],
|
language: status_params[:language],
|
||||||
scheduled_at: status_params[:scheduled_at],
|
scheduled_at: status_params[:scheduled_at],
|
||||||
application: doorkeeper_token.application,
|
application: doorkeeper_token.application,
|
||||||
|
@ -144,6 +145,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
:visibility,
|
:visibility,
|
||||||
:force_visibility,
|
:force_visibility,
|
||||||
:searchability,
|
:searchability,
|
||||||
|
:circle_id,
|
||||||
:language,
|
:language,
|
||||||
:markdown,
|
:markdown,
|
||||||
:scheduled_at,
|
:scheduled_at,
|
||||||
|
|
|
@ -75,6 +75,8 @@ export const COMPOSE_POLL_OPTION_CHANGE = 'COMPOSE_POLL_OPTION_CHANGE';
|
||||||
export const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE';
|
export const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE';
|
||||||
export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
|
export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
|
||||||
|
|
||||||
|
export const COMPOSE_CIRCLE_CHANGE = 'COMPOSE_CIRCLE_CHANGE';
|
||||||
|
|
||||||
export const INIT_MEDIA_EDIT_MODAL = 'INIT_MEDIA_EDIT_MODAL';
|
export const INIT_MEDIA_EDIT_MODAL = 'INIT_MEDIA_EDIT_MODAL';
|
||||||
|
|
||||||
export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTION';
|
export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTION';
|
||||||
|
@ -211,6 +213,7 @@ export function submitCompose(routerHistory) {
|
||||||
markdown: getState().getIn(['compose', 'markdown']),
|
markdown: getState().getIn(['compose', 'markdown']),
|
||||||
visibility: getState().getIn(['compose', 'privacy']),
|
visibility: getState().getIn(['compose', 'privacy']),
|
||||||
searchability: getState().getIn(['compose', 'searchability']),
|
searchability: getState().getIn(['compose', 'searchability']),
|
||||||
|
circle_id: getState().getIn(['compose', 'circle_id']),
|
||||||
poll: getState().getIn(['compose', 'poll'], null),
|
poll: getState().getIn(['compose', 'poll'], null),
|
||||||
language: getState().getIn(['compose', 'language']),
|
language: getState().getIn(['compose', 'language']),
|
||||||
},
|
},
|
||||||
|
@ -837,3 +840,10 @@ export function changePollSettings(expiresIn, isMultiple) {
|
||||||
isMultiple,
|
isMultiple,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function changeCircle(circleId) {
|
||||||
|
return {
|
||||||
|
type: COMPOSE_CIRCLE_CHANGE,
|
||||||
|
circleId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ const messages = defineMessages({
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
|
circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members 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}' },
|
||||||
});
|
});
|
||||||
|
@ -403,6 +404,7 @@ class Status extends ImmutablePureComponent {
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
|
'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { Provider as ReduxProvider } from 'react-redux';
|
||||||
|
|
||||||
import { ScrollContext } from 'react-router-scroll-4';
|
import { ScrollContext } from 'react-router-scroll-4';
|
||||||
|
|
||||||
|
import { fetchCircles } from 'mastodon/actions/circles';
|
||||||
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
|
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
|
||||||
import { fetchReactionDeck } from 'mastodon/actions/reaction_deck';
|
import { fetchReactionDeck } from 'mastodon/actions/reaction_deck';
|
||||||
import { hydrateStore } from 'mastodon/actions/store';
|
import { hydrateStore } from 'mastodon/actions/store';
|
||||||
|
@ -27,6 +28,7 @@ store.dispatch(hydrateAction);
|
||||||
if (initialState.meta.me) {
|
if (initialState.meta.me) {
|
||||||
store.dispatch(fetchCustomEmojis());
|
store.dispatch(fetchCustomEmojis());
|
||||||
store.dispatch(fetchReactionDeck());
|
store.dispatch(fetchReactionDeck());
|
||||||
|
store.dispatch(fetchCircles());
|
||||||
}
|
}
|
||||||
|
|
||||||
const createIdentityContext = state => ({
|
const createIdentityContext = state => ({
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
|
|
||||||
|
import Select, { NonceProvider } from 'react-select';
|
||||||
|
|
||||||
|
class CircleSelect extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
unavailable: PropTypes.bool,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
circles: ImmutablePropTypes.list,
|
||||||
|
circleId: PropTypes.string,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClick = value => {
|
||||||
|
this.props.onChange(value.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
noOptionsMessage = () => '';
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { unavailable, circles, circleId } = this.props;
|
||||||
|
|
||||||
|
if (unavailable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listOptions = circles.toArray().filter((circle) => circle[1]).map((circle) => {
|
||||||
|
return { value: circle[1].get('id'), label: circle[1].get('title') };
|
||||||
|
});
|
||||||
|
const listValue = listOptions.find((opt) => opt.value === circleId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='compose-form__circle-select'>
|
||||||
|
<NonceProvider nonce={document.querySelector('meta[name=style-nonce]').content} cacheKey='circles'>
|
||||||
|
<Select
|
||||||
|
value={listValue}
|
||||||
|
options={listOptions}
|
||||||
|
noOptionsMessage={this.noOptionsMessage}
|
||||||
|
onChange={this.handleClick}
|
||||||
|
className='column-content-select__container'
|
||||||
|
classNamePrefix='column-content-select'
|
||||||
|
name='circles'
|
||||||
|
defaultOptions
|
||||||
|
/>
|
||||||
|
</NonceProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectIntl(CircleSelect);
|
|
@ -14,6 +14,7 @@ import { Icon } from 'mastodon/components/icon';
|
||||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
import AutosuggestInput from '../../../components/autosuggest_input';
|
||||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||||
import Button from '../../../components/button';
|
import Button from '../../../components/button';
|
||||||
|
import CircleSelectContainer from '../containers/circle_select_container';
|
||||||
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
||||||
import ExpirationDropdownContainer from '../containers/expiration_dropdown_container';
|
import ExpirationDropdownContainer from '../containers/expiration_dropdown_container';
|
||||||
import LanguageDropdown from '../containers/language_dropdown_container';
|
import LanguageDropdown from '../containers/language_dropdown_container';
|
||||||
|
@ -76,6 +77,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
isInReply: PropTypes.bool,
|
isInReply: PropTypes.bool,
|
||||||
singleColumn: PropTypes.bool,
|
singleColumn: PropTypes.bool,
|
||||||
lang: PropTypes.string,
|
lang: PropTypes.string,
|
||||||
|
circleId: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -101,11 +103,11 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
canSubmit = () => {
|
canSubmit = () => {
|
||||||
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
|
const { isSubmitting, isChangingUpload, isUploading, anyMedia, privacy, circleId } = this.props;
|
||||||
const fulltext = this.getFulltextForCharacterCounting();
|
const fulltext = this.getFulltextForCharacterCounting();
|
||||||
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
|
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
|
||||||
|
|
||||||
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia));
|
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia) || (privacy === 'circle' && !circleId));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = (e) => {
|
handleSubmit = (e) => {
|
||||||
|
@ -317,6 +319,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CircleSelectContainer />
|
||||||
|
|
||||||
<div className='compose-form__publish'>
|
<div className='compose-form__publish'>
|
||||||
<div className='compose-form__publish-button-wrapper'>
|
<div className='compose-form__publish-button-wrapper'>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -26,6 +26,8 @@ const messages = defineMessages({
|
||||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual' },
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual' },
|
||||||
mutual_long: { id: 'privacy.mutual.long', defaultMessage: 'Mutual follows only' },
|
mutual_long: { id: 'privacy.mutual.long', defaultMessage: 'Mutual follows only' },
|
||||||
|
circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle' },
|
||||||
|
circle_long: { id: 'privacy.circle.long', defaultMessage: 'Circle members only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||||
|
@ -235,6 +237,7 @@ class PrivacyDropdown extends PureComponent {
|
||||||
{ 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) },
|
||||||
{ icon: 'exchange', value: 'mutual', text: formatMessage(messages.mutual_short), meta: formatMessage(messages.mutual_long) },
|
{ icon: 'exchange', value: 'mutual', text: formatMessage(messages.mutual_short), meta: formatMessage(messages.mutual_long) },
|
||||||
|
{ icon: 'user-circle', value: 'circle', text: formatMessage(messages.circle_short), meta: formatMessage(messages.circle_long) },
|
||||||
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||||
];
|
];
|
||||||
this.selectableOptions = [...this.options];
|
this.selectableOptions = [...this.options];
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { changeCircle } from '../../../actions/compose';
|
||||||
|
import CircleSelect from '../components/circle_select';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
unavailable: state.getIn(['compose', 'privacy']) !== 'circle',
|
||||||
|
circles: state.get('circles'),
|
||||||
|
circleId: state.getIn(['compose', 'circle_id']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
||||||
|
onChange (circleId) {
|
||||||
|
dispatch(changeCircle(circleId));
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(CircleSelect);
|
|
@ -30,6 +30,7 @@ const mapStateToProps = state => ({
|
||||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||||
lang: state.getIn(['compose', 'language']),
|
lang: state.getIn(['compose', 'language']),
|
||||||
|
circleId: state.getIn(['compose', 'circle_id']),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|
|
@ -11,10 +11,10 @@ import Warning from '../components/warning';
|
||||||
|
|
||||||
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', 'login'].indexOf(state.getIn(['compose', 'privacy'])) < 0 && state.getIn(['compose', 'searchability']) !== 'public' && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
|
hashtagWarning: ['public', 'public_unlisted', 'login'].includes(state.getIn(['compose', 'privacy'])) && state.getIn(['compose', 'searchability']) !== 'public' && 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',
|
||||||
limitedPostWarning: state.getIn(['compose', 'privacy']) === 'mutual',
|
limitedPostWarning: ['mutual', 'circle'].includes(state.getIn(['compose', 'privacy'])),
|
||||||
});
|
});
|
||||||
|
|
||||||
const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning, searchabilityWarning, limitedPostWarning }) => {
|
const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning, searchabilityWarning, limitedPostWarning }) => {
|
||||||
|
|
|
@ -22,6 +22,7 @@ const messages = defineMessages({
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
|
circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ class StatusCheckBox extends PureComponent {
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
|
'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ const messages = defineMessages({
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
|
circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
||||||
searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
|
searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
|
||||||
|
@ -256,6 +257,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
|
'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ const messages = defineMessages({
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
|
circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ class BoostModal extends ImmutablePureComponent {
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
|
'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import {
|
||||||
COMPOSE_POLL_OPTION_CHANGE,
|
COMPOSE_POLL_OPTION_CHANGE,
|
||||||
COMPOSE_POLL_OPTION_REMOVE,
|
COMPOSE_POLL_OPTION_REMOVE,
|
||||||
COMPOSE_POLL_SETTINGS_CHANGE,
|
COMPOSE_POLL_SETTINGS_CHANGE,
|
||||||
|
COMPOSE_CIRCLE_CHANGE,
|
||||||
INIT_MEDIA_EDIT_MODAL,
|
INIT_MEDIA_EDIT_MODAL,
|
||||||
COMPOSE_CHANGE_MEDIA_DESCRIPTION,
|
COMPOSE_CHANGE_MEDIA_DESCRIPTION,
|
||||||
COMPOSE_CHANGE_MEDIA_FOCUS,
|
COMPOSE_CHANGE_MEDIA_FOCUS,
|
||||||
|
@ -68,6 +69,7 @@ const initialState = ImmutableMap({
|
||||||
spoiler_text: '',
|
spoiler_text: '',
|
||||||
markdown: false,
|
markdown: false,
|
||||||
privacy: null,
|
privacy: null,
|
||||||
|
circle_id: null,
|
||||||
searchability: null,
|
searchability: null,
|
||||||
id: null,
|
id: null,
|
||||||
text: '',
|
text: '',
|
||||||
|
@ -136,6 +138,7 @@ function clearAll(state) {
|
||||||
map.update('media_attachments', list => list.clear());
|
map.update('media_attachments', list => list.clear());
|
||||||
map.set('poll', null);
|
map.set('poll', null);
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
|
map.set('circle_id', null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,6 +600,8 @@ export default function compose(state = initialState, action) {
|
||||||
return state.updateIn(['poll', 'options'], options => options.delete(action.index));
|
return state.updateIn(['poll', 'options'], options => options.delete(action.index));
|
||||||
case COMPOSE_POLL_SETTINGS_CHANGE:
|
case COMPOSE_POLL_SETTINGS_CHANGE:
|
||||||
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
||||||
|
case COMPOSE_CIRCLE_CHANGE:
|
||||||
|
return state.set('circle_id', action.circleId);
|
||||||
case COMPOSE_LANGUAGE_CHANGE:
|
case COMPOSE_LANGUAGE_CHANGE:
|
||||||
return state.set('language', action.language);
|
return state.set('language', action.language);
|
||||||
case COMPOSE_FOCUS:
|
case COMPOSE_FOCUS:
|
||||||
|
|
|
@ -87,8 +87,11 @@ class ActivityPub::Parser::StatusParser
|
||||||
end
|
end
|
||||||
|
|
||||||
def limited_scope
|
def limited_scope
|
||||||
if @object['limitedScope'] == 'Mutual'
|
case @object['limitedScope']
|
||||||
|
when 'Mutual'
|
||||||
:mutual
|
:mutual
|
||||||
|
when 'Circle'
|
||||||
|
:circle
|
||||||
else
|
else
|
||||||
:none
|
:none
|
||||||
end
|
end
|
||||||
|
|
|
@ -222,7 +222,11 @@ class ActivityPub::TagManager
|
||||||
end
|
end
|
||||||
|
|
||||||
def limited_scope(status)
|
def limited_scope(status)
|
||||||
status.mutual_limited? ? 'Mutual' : ''
|
if status.mutual_limited?
|
||||||
|
'Mutual'
|
||||||
|
else
|
||||||
|
status.circle_limited? ? 'Circle' : ''
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribable_by(account)
|
def subscribable_by(account)
|
||||||
|
|
|
@ -55,7 +55,7 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 }, _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, unsupported: 4, public_unlisted: 10 }, _suffix: :searchability
|
enum searchability: { public: 0, private: 1, direct: 2, limited: 3, unsupported: 4, public_unlisted: 10 }, _suffix: :searchability
|
||||||
enum limited_scope: { none: 0, mutual: 1 }, _suffix: :limited
|
enum limited_scope: { none: 0, mutual: 1, circle: 2 }, _suffix: :limited
|
||||||
|
|
||||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,8 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
||||||
:kmyblue_visibility_login,
|
:kmyblue_visibility_login,
|
||||||
:status_reference,
|
:status_reference,
|
||||||
:visibility_mutual,
|
:visibility_mutual,
|
||||||
|
:visibility_limited,
|
||||||
|
:kmyblue_limited_scope,
|
||||||
:kmyblue_antenna,
|
:kmyblue_antenna,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -75,20 +75,29 @@ class PostStatusService < BaseService
|
||||||
@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 = :direct if @in_reply_to&.limited_visibility?
|
@visibility = :direct if @in_reply_to&.limited_visibility?
|
||||||
@visibility = :limited if @options[:visibility] == 'mutual'
|
@visibility = :limited if %w(mutual circle).include?(@options[:visibility])
|
||||||
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @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
|
||||||
|
@limited_scope = @options[:visibility]&.to_sym if @visibility == :limited
|
||||||
@searchability = searchability
|
@searchability = searchability
|
||||||
@searchability = :private if @account.silenced? && @searchability&.to_sym == :public
|
@searchability = :private if @account.silenced? && @searchability&.to_sym == :public
|
||||||
@markdown = @options[:markdown] || false
|
@markdown = @options[:markdown] || false
|
||||||
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
||||||
@scheduled_at = nil if scheduled_in_the_past?
|
@scheduled_at = nil if scheduled_in_the_past?
|
||||||
@reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?)
|
@reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?)
|
||||||
|
load_circle
|
||||||
process_sensitive_words
|
process_sensitive_words
|
||||||
rescue ArgumentError
|
rescue ArgumentError
|
||||||
raise ActiveRecord::RecordInvalid
|
raise ActiveRecord::RecordInvalid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def load_circle
|
||||||
|
return unless @options[:visibility] == 'circle'
|
||||||
|
|
||||||
|
@circle = @options[:circle_id].present? && Circle.find(@options[:circle_id])
|
||||||
|
raise ArgumentError if @circle.nil?
|
||||||
|
end
|
||||||
|
|
||||||
def process_sensitive_words
|
def process_sensitive_words
|
||||||
if [:public, :public_unlisted, :login].include?(@visibility&.to_sym) && Admin::SensitiveWord.sensitive?(@text, @options[:spoiler_text] || '')
|
if [:public, :public_unlisted, :login].include?(@visibility&.to_sym) && Admin::SensitiveWord.sensitive?(@text, @options[:spoiler_text] || '')
|
||||||
@text = Admin::SensitiveWord.modified_text(@text, @options[:spoiler_text])
|
@text = Admin::SensitiveWord.modified_text(@text, @options[:spoiler_text])
|
||||||
|
@ -115,7 +124,7 @@ class PostStatusService < BaseService
|
||||||
|
|
||||||
def process_status!
|
def process_status!
|
||||||
@status = @account.statuses.new(status_attributes)
|
@status = @account.statuses.new(status_attributes)
|
||||||
process_mentions_service.call(@status, limited_type: @status.limited_visibility? ? 'mutual' : '', save_records: false)
|
process_mentions_service.call(@status, limited_type: @status.limited_visibility? ? @limited_scope : '', circle: @circle, save_records: false)
|
||||||
safeguard_mentions!(@status)
|
safeguard_mentions!(@status)
|
||||||
|
|
||||||
UpdateStatusExpirationService.new.call(@status)
|
UpdateStatusExpirationService.new.call(@status)
|
||||||
|
@ -248,7 +257,7 @@ class PostStatusService < BaseService
|
||||||
spoiler_text: @options[:spoiler_text] || '',
|
spoiler_text: @options[:spoiler_text] || '',
|
||||||
markdown: @markdown,
|
markdown: @markdown,
|
||||||
visibility: @visibility,
|
visibility: @visibility,
|
||||||
limited_scope: @visibility == :limited ? :mutual : :none,
|
limited_scope: @limited_scope || :none,
|
||||||
searchability: @searchability,
|
searchability: @searchability,
|
||||||
language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
|
language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
|
||||||
application: @options[:application],
|
application: @options[:application],
|
||||||
|
|
|
@ -7,9 +7,10 @@ class ProcessMentionsService < BaseService
|
||||||
# and create local mention pointers
|
# and create local mention pointers
|
||||||
# @param [Status] status
|
# @param [Status] status
|
||||||
# @param [Boolean] save_records Whether to save records in database
|
# @param [Boolean] save_records Whether to save records in database
|
||||||
def call(status, limited_type: '', save_records: true)
|
def call(status, limited_type: '', circle: nil, save_records: true)
|
||||||
@status = status
|
@status = status
|
||||||
@limited_type = limited_type
|
@limited_type = limited_type
|
||||||
|
@circle = circle
|
||||||
@save_records = save_records
|
@save_records = save_records
|
||||||
|
|
||||||
return unless @status.local?
|
return unless @status.local?
|
||||||
|
@ -63,7 +64,8 @@ class ProcessMentionsService < BaseService
|
||||||
"@#{mentioned_account.acct}"
|
"@#{mentioned_account.acct}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process_mutual! if @limited_type == 'mutual'
|
process_mutual! if @limited_type == :mutual
|
||||||
|
process_circle! if @limited_type == :circle
|
||||||
|
|
||||||
@status.save! if @save_records
|
@status.save! if @save_records
|
||||||
end
|
end
|
||||||
|
@ -103,4 +105,12 @@ class ProcessMentionsService < BaseService
|
||||||
@current_mentions << @status.mentions.new(silent: true, account: target_account) unless mentioned_account_ids.include?(target_account.id)
|
@current_mentions << @status.mentions.new(silent: true, account: target_account) unless mentioned_account_ids.include?(target_account.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_circle!
|
||||||
|
mentioned_account_ids = @current_mentions.map(&:account_id)
|
||||||
|
|
||||||
|
@circle.accounts.find_each do |target_account|
|
||||||
|
@current_mentions << @status.mentions.new(silent: true, account: target_account) unless mentioned_account_ids.include?(target_account.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue