Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit b85a468
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Sat Sep 5 10:50:03 2020 +0900

    Changed to remove restrictions on privacy options and allow users to switch circles when replying

commit 0a8c014
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Sat Sep 5 09:33:07 2020 +0900

    Change limited visibility icon

commit b64adf1
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Mon Aug 31 06:50:56 2020 +0900

    Fix a change to limited-visibility-bearcaps replies

commit ed36140
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Thu Aug 27 15:53:18 2020 +0900

    Fix composer text when change visibility

commit 4da3add
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Sat Aug 22 22:34:23 2020 +0900

    Fix wrong circle_id when changing visibility

commit 752d7fc
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Sun Aug 9 13:12:51 2020 +0900

    Add circle reply and redraft

commit 5978bc0
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Mon Jul 27 01:07:52 2020 +0900

    Fix remove unused props

commit 7970f69
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Sun Jul 26 21:17:07 2020 +0900

    Separate circle choice from privacy

commit 36f6a68
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Thu Jul 23 10:54:25 2020 +0900

    Add UI for posting to circles

commit 7ef4800
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Fri Jul 24 12:55:10 2020 +0900

    Fix silent mention by circle
  • Loading branch information
noellabo committed Sep 5, 2020
1 parent 7a1caed commit b7f4b77
Show file tree
Hide file tree
Showing 16 changed files with 282 additions and 26 deletions.
9 changes: 9 additions & 0 deletions app/javascript/mastodon/actions/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
export const COMPOSE_CIRCLE_CHANGE = 'COMPOSE_CIRCLE_CHANGE';
export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';

Expand Down Expand Up @@ -146,6 +147,7 @@ export function submitCompose(routerHistory) {
sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '',
visibility: getState().getIn(['compose', 'privacy']),
circle_id: getState().getIn(['compose', 'circle_id']),
poll: getState().getIn(['compose', 'poll'], null),
}, {
headers: {
Expand Down Expand Up @@ -594,6 +596,13 @@ export function changeComposeVisibility(value) {
};
};

export function changeComposeCircle(value) {
return {
type: COMPOSE_CIRCLE_CHANGE,
value,
};
};

export function insertEmojiCompose(position, emoji, needsSpace) {
return {
type: COMPOSE_EMOJI_INSERT,
Expand Down
6 changes: 4 additions & 2 deletions app/javascript/mastodon/actions/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,19 @@ export function fetchStatusFail(id, error, skipLoading) {
};
};

export function redraft(status, raw_text) {
export function redraft(status, replyStatus, raw_text) {
return {
type: REDRAFT,
status,
replyStatus,
raw_text,
};
};

export function deleteStatus(id, routerHistory, withRedraft = false) {
return (dispatch, getState) => {
let status = getState().getIn(['statuses', id]);
const replyStatus = status.get('in_reply_to_id') ? getState().getIn(['statuses', status.get('in_reply_to_id')]) : null;

if (status.get('poll')) {
status = status.set('poll', getState().getIn(['polls', status.get('poll')]));
Expand All @@ -100,7 +102,7 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
dispatch(importFetchedAccount(response.data.account));

if (withRedraft) {
dispatch(redraft(status, response.data.text));
dispatch(redraft(status, replyStatus, response.data.text));
ensureComposeIsVisible(getState, routerHistory);
}
}).catch(error => {
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/mastodon/containers/mastodon.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ScrollContext } from 'react-router-scroll-4';
import UI from '../features/ui';
import Introduction from '../features/introduction';
import { fetchCustomEmojis } from '../actions/custom_emojis';
import { fetchCircles } from '../actions/circles';
import { hydrateStore } from '../actions/store';
import { connectUserStream } from '../actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl';
Expand All @@ -25,6 +26,7 @@ const hydrateAction = hydrateStore(initialState);

store.dispatch(hydrateAction);
store.dispatch(fetchCustomEmojis());
store.dispatch(fetchCircles());

const mapStateToProps = state => ({
showIntroduction: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
import IconButton from 'mastodon/components/icon_button';
import { createSelector } from 'reselect';

const messages = defineMessages({
circle_unselect: { id: 'circle.unselect', defaultMessage: '(Select circle)' },
circle_reply: { id: 'circle.reply', defaultMessage: '(Reply to circle context)' },
circle_open_circle_column: { id: 'circle.open_circle_column', defaultMessage: 'Open circle column' },
circle_add_new_circle: { id: 'circle.add_new_circle', defaultMessage: '(Add new circle)' },
circle_select: { id: 'circle.select', defaultMessage: 'Select circle' },
});

const getOrderedCircles = createSelector([state => state.get('circles')], circles => {
if (!circles) {
return circles;
}

return circles.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
});

const mapStateToProps = (state) => {
return {
circles: getOrderedCircles(state),
};
};

export default @connect(mapStateToProps)
@injectIntl
class CircleDropdown extends React.PureComponent {

static contextTypes = {
router: PropTypes.object,
};

static propTypes = {
circles: ImmutablePropTypes.list,
value: PropTypes.string.isRequired,
visible: PropTypes.bool.isRequired,
limitedReply: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
onOpenCircleColumn: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};

handleChange = e => {
this.props.onChange(e.target.value);
};

handleOpenCircleColumn = () => {
this.props.onOpenCircleColumn(this.context.router ? this.context.router.history : null);
};

render () {
const { circles, value, visible, limitedReply, intl } = this.props;

return (
<div className={classNames('circle-dropdown', { 'circle-dropdown--visible': visible })}>
<IconButton icon='user-circle' className='circle-dropdown__icon' title={intl.formatMessage(messages.circle_open_circle_column)} style={{ width: 'auto', height: 'auto' }} onClick={this.handleOpenCircleColumn} />

{circles.isEmpty() && !limitedReply ?
<button className='circle-dropdown__menu' onClick={this.handleOpenCircleColumn}>{intl.formatMessage(messages.circle_add_new_circle)}</button>
:
/* eslint-disable-next-line jsx-a11y/no-onchange */
<select className='circle-dropdown__menu' title={intl.formatMessage(messages.circle_select)} value={value} onChange={this.handleChange}>
<option value='' key='unselect'>{intl.formatMessage(limitedReply ? messages.circle_reply : messages.circle_unselect)}</option>
{circles.map(circle =>
<option value={circle.get('id')} key={circle.get('id')}>{circle.get('title')}</option>,
)}
</select>
}
</div>
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import CircleDropdownContainer from '../containers/circle_dropdown_container';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import PollFormContainer from '../containers/poll_form_container';
import UploadFormContainer from '../containers/upload_form_container';
Expand Down Expand Up @@ -50,6 +51,7 @@ class ComposeForm extends ImmutablePureComponent {
isSubmitting: PropTypes.bool,
isChangingUpload: PropTypes.bool,
isUploading: PropTypes.bool,
isCircleUnselected: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onClearSuggestions: PropTypes.func.isRequired,
Expand Down Expand Up @@ -85,10 +87,10 @@ class ComposeForm extends ImmutablePureComponent {
}

// Submit disabled:
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
const { isSubmitting, isChangingUpload, isUploading, isCircleUnselected, anyMedia } = this.props;
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');

if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
if (isSubmitting || isUploading || isChangingUpload || isCircleUnselected || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return;
}

Expand Down Expand Up @@ -181,7 +183,7 @@ class ComposeForm extends ImmutablePureComponent {
const { intl, onPaste, showSearch, anyMedia } = this.props;
const disabled = this.props.isSubmitting;
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || this.props.isCircleUnselected || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
let publishText = '';

if (this.props.privacy !== 'public' && this.props.privacy !== 'unlisted') {
Expand Down Expand Up @@ -246,6 +248,8 @@ class ComposeForm extends ImmutablePureComponent {
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
</div>

<CircleDropdownContainer />

<div className='compose-form__publish'>
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabledButton} block /></div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const messages = defineMessages({
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
limited_long: { id: 'privacy.limited.long', defaultMessage: 'Visible for circle users only' },
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
});

Expand Down Expand Up @@ -236,6 +238,7 @@ class PrivacyDropdown extends React.PureComponent {
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
{ icon: 'user-circle', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) },
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { connect } from 'react-redux';
import CircleDropdown from '../components/circle_dropdown';
import { changeComposeCircle } from '../../../actions/compose';

const mapStateToProps = state => {
let value = state.getIn(['compose', 'circle_id']);
value = value === null ? '' : value;

return {
value: value,
visible: state.getIn(['compose', 'privacy']) === 'limited',
limitedReply: state.getIn(['compose', 'privacy']) === 'limited' && state.getIn(['compose', 'reply_status', 'visibility']) === 'limited',
};
};

const mapDispatchToProps = dispatch => ({

onChange (value) {
dispatch(changeComposeCircle(value));
},

onOpenCircleColumn (router) {
if(router && router.location.pathname !== '/circles') {
router.push('/circles');
}
},

});

export default connect(mapStateToProps, mapDispatchToProps)(CircleDropdown);
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const mapStateToProps = state => ({
isSubmitting: state.getIn(['compose', 'is_submitting']),
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isUploading: state.getIn(['compose', 'is_uploading']),
isCircleUnselected: state.getIn(['compose', 'privacy']) === 'limited' && state.getIn(['compose', 'reply_status', 'visibility']) !== 'limited' && !state.getIn(['compose', 'circle_id']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])),
directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
limitedMessageWarning: state.getIn(['compose', 'privacy']) === 'limited',
});

const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => {
const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning, limitedMessageWarning }) => {
if (needsLockWarning) {
return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
}
Expand All @@ -55,13 +56,18 @@ const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning
return <Warning message={message} />;
}

if (limitedMessageWarning) {
return <Warning message={<FormattedMessage id='compose_form.limited_message_warning' defaultMessage='This toot will only be sent to users in the circle.' />} />;
}

return null;
};

WarningWrapper.propTypes = {
needsLockWarning: PropTypes.bool,
hashtagWarning: PropTypes.bool,
directMessageWarning: PropTypes.bool,
limitedMessageWarning: PropTypes.bool,
};

export default connect(mapStateToProps)(WarningWrapper);
6 changes: 6 additions & 0 deletions app/javascript/mastodon/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
"circles.new.title_placeholder": "New circle title",
"circles.search": "Search among people following you",
"circles.subheading": "Your circles",
"circle.add_new_circle": "(Add new circle)",
"circle.open_circle_column": "Open circle column",
"circle.reply": "(Reply to circle context)",
"circle.select": "Select circle",
"circle.unselect": "(Select circle)",
"column.blocks": "Blocked users",
"column.bookmarks": "Bookmarks",
"column.circles": "Circles",
Expand Down Expand Up @@ -94,6 +99,7 @@
"compose_form.direct_message_warning": "This toot will only be sent to the mentioned users.",
"compose_form.direct_message_warning_learn_more": "Learn more",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.limited_message_warning": "This toot will only be sent to users in the circle.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "What's on your mind?",
Expand Down
Loading

0 comments on commit b7f4b77

Please sign in to comment.