diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features')
19 files changed, 218 insertions, 72 deletions
diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js index 2d4cc7f49..0f1b83b2d 100644 --- a/app/javascript/flavours/glitch/features/account/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js @@ -44,14 +44,20 @@ class ActionBar extends React.PureComponent { if (account.get('acct') !== account.get('username')) { extraInfo = ( <div className='account__disclaimer'> - <Icon id='info-circle' fixedWidth /> <FormattedMessage - id='account.disclaimer_full' - defaultMessage="Information below may reflect the user's profile incompletely." - /> - {' '} - <a target='_blank' rel='noopener' href={account.get('url')}> - <FormattedMessage id='account.view_full_profile' defaultMessage='View full profile' /> - </a> + <p> + <Icon id='info-circle' fixedWidth /> <FormattedMessage + id='account.disclaimer_full' + defaultMessage="Information below may reflect the user's profile incompletely." + /> + </p> + <p> + <Icon id='link' fixedWidth /> <a target='_blank' rel='noopener' href={account.get('url')}> + <FormattedMessage + id='account.view_full_profile' + defaultMessage='View full profile' + /> + </a> + </p> </div> ); } @@ -64,17 +70,14 @@ class ActionBar extends React.PureComponent { <div className='account__action-bar-links'> <NavLink isActive={this.isStatusesPageActive} activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}> <FormattedMessage id='account.posts' defaultMessage='Posts' /> - <strong><FormattedNumber value={account.get('statuses_count')} /></strong> </NavLink> <NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}> <FormattedMessage id='account.follows' defaultMessage='Follows' /> - <strong><FormattedNumber value={account.get('following_count')} /></strong> </NavLink> <NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}> <FormattedMessage id='account.followers' defaultMessage='Followers' /> - <strong>{ account.get('followers_count') < 0 ? '-' : <FormattedNumber value={account.get('followers_count')} /> }</strong> </NavLink> </div> </div> diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index a6b57d331..d399d4aa9 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -7,6 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; import { NavLink } from 'react-router-dom'; import MovedNote from './moved_note'; +import { me } from 'flavours/glitch/util/initial_state'; export default class Header extends ImmutablePureComponent { @@ -128,9 +129,12 @@ export default class Header extends ImmutablePureComponent { {!hideTabs && ( <div className='account__section-headline'> - <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink> - <NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink> + <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.threads' defaultMessage='Threads' /></NavLink> + { (account.get('id') === me || account.get('show_replies')) && + (<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>) } + { (account.get('id') !== me) && (<NavLink exact to={`/accounts/${account.get('id')}/mentions`}><FormattedMessage id='account.mentions' defaultMessage='Mentions' /></NavLink>) } <NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink> + <NavLink exact to={`/accounts/${account.get('id')}/reblogs`}><FormattedMessage id='account.reblogs' defaultMessage='Boosts' /></NavLink> </div> )} </div> diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index c56cc9b8e..c88f6ac89 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -19,15 +19,15 @@ import TimelineHint from 'flavours/glitch/components/timeline_hint'; const emptyList = ImmutableList(); -const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { - const path = withReplies ? `${accountId}:with_replies` : accountId; +const mapStateToProps = (state, { params: { accountId }, filter = '' }) => { + const path = `${accountId}${filter}`; return { remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), remoteUrl: state.getIn(['accounts', accountId, 'url']), isAccount: !!state.getIn(['accounts', accountId]), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), - featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), + featuredStatusIds: !filter ? state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()) : ImmutableList(), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), suspended: state.getIn(['accounts', accountId, 'suspended'], false), @@ -52,7 +52,7 @@ class AccountTimeline extends ImmutablePureComponent { featuredStatusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, hasMore: PropTypes.bool, - withReplies: PropTypes.bool, + filter: PropTypes.string, isAccount: PropTypes.bool, suspended: PropTypes.bool, remote: PropTypes.bool, @@ -61,24 +61,24 @@ class AccountTimeline extends ImmutablePureComponent { }; componentWillMount () { - const { params: { accountId }, withReplies } = this.props; + const { params: { accountId }, filter } = this.props; this.props.dispatch(fetchAccount(accountId)); this.props.dispatch(fetchAccountIdentityProofs(accountId)); - if (!withReplies) { + if (!filter) { this.props.dispatch(expandAccountFeaturedTimeline(accountId)); } - this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); + this.props.dispatch(expandAccountTimeline(accountId, { filter })); } componentWillReceiveProps (nextProps) { - if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { + if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.filter !== this.props.filter) { this.props.dispatch(fetchAccount(nextProps.params.accountId)); this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId)); - if (!nextProps.withReplies) { + if (!nextProps.filter) { this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); } - this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies })); + this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { filter: nextProps.params.filter })); } } @@ -87,7 +87,7 @@ class AccountTimeline extends ImmutablePureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies })); + this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, filter: this.props.filter })); } setRef = c => { diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index a7cb95222..e812ba982 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -63,6 +63,8 @@ class ComposeForm extends ImmutablePureComponent { layout: PropTypes.string, media: ImmutablePropTypes.list, sideArm: PropTypes.string, + sideArmWarning: PropTypes.bool, + privacyWarning: PropTypes.bool, sensitive: PropTypes.bool, spoilersAlwaysOn: PropTypes.bool, mediaDescriptionConfirmation: PropTypes.bool, @@ -71,10 +73,12 @@ class ComposeForm extends ImmutablePureComponent { onChangeVisibility: PropTypes.func, onPaste: PropTypes.func, onMediaDescriptionConfirm: PropTypes.func, + clearTimeout: PropTypes.bool, }; static defaultProps = { showSearch: false, + clearTimeout: null, }; handleChange = (e) => { @@ -149,6 +153,17 @@ class ComposeForm extends ImmutablePureComponent { this.handleSubmit(sideArm === 'none' ? null : sideArm); } + handleClearAll = () => { + if(!this.clearTimeout || this.clearTimeout === null) { + this.clearTimeout = window.setTimeout(() => { + this.clearTimeout = null; + }, 500); + } else { + this.clearTimeout = null; + this.props.onClearAll(); + } + } + // Selects a suggestion from the autofill. onSuggestionSelected = (tokenStart, token, value) => { this.props.onSuggestionSelected(tokenStart, token, value, ['text']); @@ -256,6 +271,7 @@ class ComposeForm extends ImmutablePureComponent { handleSecondarySubmit, handleSelect, handleSubmit, + handleClearAll, handleRefTextarea, } = this; const { @@ -273,19 +289,22 @@ class ComposeForm extends ImmutablePureComponent { onFetchSuggestions, onPaste, privacy, + privacyWarning, sensitive, showSearch, sideArm, + sideArmWarning, spoiler, spoilerText, suggestions, text, spoilersAlwaysOn, + clearTimeout, } = this.props; let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia); - const countText = `${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`; + const countText = `${spoilerText}${countableText(text)}`; return ( <div className='composer'> @@ -356,8 +375,11 @@ class ComposeForm extends ImmutablePureComponent { disabled={disabledButton} onSecondarySubmit={handleSecondarySubmit} onSubmit={handleSubmit} + onClearAll={handleClearAll} privacy={privacy} + privacyWarning={privacyWarning} sideArm={sideArm} + sideArmWarning={sideArmWarning} /> </div> ); diff --git a/app/javascript/flavours/glitch/features/compose/components/publisher.js b/app/javascript/flavours/glitch/features/compose/components/publisher.js index 97890f40d..d42c578aa 100644 --- a/app/javascript/flavours/glitch/features/compose/components/publisher.js +++ b/app/javascript/flavours/glitch/features/compose/components/publisher.js @@ -23,6 +23,10 @@ const messages = defineMessages({ defaultMessage: '{publish}!', id: 'compose_form.publish_loud', }, + clear: { + defaultMessage: 'Double-click to clear', + id: 'compose_form.clear', + }, }); export default @injectIntl @@ -34,8 +38,11 @@ class Publisher extends ImmutablePureComponent { intl: PropTypes.object.isRequired, onSecondarySubmit: PropTypes.func, onSubmit: PropTypes.func, + onClearAll: PropTypes.func, privacy: PropTypes.oneOf(['direct', 'private', 'unlisted', 'public']), + privacyWarning: PropTypes.bool, sideArm: PropTypes.oneOf(['none', 'direct', 'private', 'unlisted', 'public']), + sideArmWarning: PropTypes.bool, }; handleSubmit = () => { @@ -43,7 +50,7 @@ class Publisher extends ImmutablePureComponent { }; render () { - const { countText, disabled, intl, onSecondarySubmit, privacy, sideArm } = this.props; + const { countText, disabled, intl, onClearAll, onSecondarySubmit, privacy, privacyWarning, sideArm, sideArmWarning } = this.props; const diff = maxChars - length(countText || ''); const computedClass = classNames('composer--publisher', { @@ -53,9 +60,20 @@ class Publisher extends ImmutablePureComponent { return ( <div className={computedClass}> + <Button + className='clear' + onClick={onClearAll} + style={{ padding: null }} + title={intl.formatMessage(messages.clear)} + text={ + <span> + <Icon id='trash-o' /> + </span> + } + /> {sideArm && sideArm !== 'none' ? ( <Button - className='side_arm' + className={classNames('side_arm', {privacy_warning: sideArmWarning})} disabled={disabled || diff < 0} onClick={onSecondarySubmit} style={{ padding: null }} @@ -75,7 +93,7 @@ class Publisher extends ImmutablePureComponent { /> ) : null} <Button - className='primary' + className={classNames('primary', {privacy_warning: privacyWarning})} text={function () { switch (true) { case !!sideArm && sideArm !== 'none': diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js index fcd2caf1b..cf953ec3d 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js @@ -12,13 +12,14 @@ import { selectComposeSuggestion, submitCompose, uploadCompose, + resetCompose, } from 'flavours/glitch/actions/compose'; import { openModal, } from 'flavours/glitch/actions/modal'; import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; -import { privacyPreference } from 'flavours/glitch/util/privacy_preference'; +import { privacyPreference, order as privacyOrder } from 'flavours/glitch/util/privacy_preference'; const messages = defineMessages({ missingDescriptionMessage: { id: 'confirmations.missing_media_description.message', @@ -57,7 +58,9 @@ function mapStateToProps (state) { media: state.getIn(['compose', 'media_attachments']), preselectDate: state.getIn(['compose', 'preselectDate']), privacy: state.getIn(['compose', 'privacy']), + privacyWarning: replyPrivacy && privacyOrder.indexOf(state.getIn(['compose', 'privacy'])) < privacyOrder.indexOf(replyPrivacy), sideArm: sideArmPrivacy, + sideArmWarning: sideArmPrivacy && sideArmRestrictedPrivacy && privacyOrder.indexOf(sideArmPrivacy) < privacyOrder.indexOf(sideArmRestrictedPrivacy), sensitive: state.getIn(['compose', 'sensitive']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']), @@ -82,6 +85,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(submitCompose(routerHistory)); }, + onClearAll() { + dispatch(resetCompose()); + }, + onClearSuggestions() { dispatch(clearComposeSuggestions()); }, diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index b4549fdf8..43d535ac5 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -46,7 +46,10 @@ const makeMapStateToProps = () => { return lists; } - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); + return lists.toList().filter(item => !!item).sort((a, b) => { + const r = (b.get('reblogs') ? 1 : 0) - (a.get('reblogs') ? 1 : 0); + return r === 0 ? a.get('title').localeCompare(b.get('title')) : r; + }); }); const mapStateToProps = state => ({ diff --git a/app/javascript/flavours/glitch/features/list_adder/index.js b/app/javascript/flavours/glitch/features/list_adder/index.js index cb8a15e8c..b7f3d1ef7 100644 --- a/app/javascript/flavours/glitch/features/list_adder/index.js +++ b/app/javascript/flavours/glitch/features/list_adder/index.js @@ -16,7 +16,10 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => { return lists; } - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); + return lists.toList().filter(item => !!item).sort((a, b) => { + const r = (b.get('reblogs') ? 1 : 0) - (a.get('reblogs') ? 1 : 0); + return r === 0 ? a.get('title').localeCompare(b.get('title')) : r; + }); }); const mapStateToProps = state => ({ diff --git a/app/javascript/flavours/glitch/features/lists/index.js b/app/javascript/flavours/glitch/features/lists/index.js index e384f301b..3863b8e25 100644 --- a/app/javascript/flavours/glitch/features/lists/index.js +++ b/app/javascript/flavours/glitch/features/lists/index.js @@ -24,7 +24,10 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => { return lists; } - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); + return lists.toList().filter(item => !!item).sort((a, b) => { + const r = (b.get('reblogs') ? 1 : 0) - (a.get('reblogs') ? 1 : 0); + return r === 0 ? a.get('title').localeCompare(b.get('title')) : r; + }); }); const mapStateToProps = state => ({ diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.js index 0d3162fc9..7b47f411b 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow.js +++ b/app/javascript/flavours/glitch/features/notifications/components/follow.js @@ -84,11 +84,13 @@ export default class NotificationFollow extends ImmutablePureComponent { <Icon fixedWidth id='user-plus' /> </div> - <FormattedMessage - id='notification.follow' - defaultMessage='{name} followed you' - values={{ name: link }} - /> + <span title={notification.get('created_at')}> + <FormattedMessage + id='notification.follow' + defaultMessage='{name} followed you' + values={{ name: link }} + /> + </span> </div> <AccountContainer hidden={hidden} id={account.get('id')} withNote={false} /> diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js index 0f16d93fe..b2c8ac87f 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js @@ -11,6 +11,8 @@ import classNames from 'classnames'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, + edit: { id: 'status.edit', defaultMessage: 'Edit' }, + publish: { id: 'status.publish', defaultMessage: 'Publish' }, direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, @@ -52,6 +54,8 @@ class ActionBar extends React.PureComponent { onMuteConversation: PropTypes.func, onBlock: PropTypes.func, onDelete: PropTypes.func.isRequired, + onEdit: PropTypes.func.isRequired, + onPublish: PropTypes.func.isRequired, onDirect: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, onReport: PropTypes.func, @@ -84,6 +88,14 @@ class ActionBar extends React.PureComponent { this.props.onDelete(this.props.status, this.context.router.history, true); } + handleEditClick = () => { + this.props.onEdit(this.props.status, this.context.router.history); + } + + handlePublishClick = () => { + this.props.onPublish(this.props.status); + } + handleDirectClick = () => { this.props.onDirect(this.props.status.get('account'), this.context.router.history); } @@ -166,6 +178,11 @@ class ActionBar extends React.PureComponent { menu.push(null); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); + menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick }); + + if (status.get('published') === false) { + menu.push({ text: intl.formatMessage(messages.publish), action: this.handlePublishClick }); + } } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick }); diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index e4aecbf94..4344e9cce 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -17,7 +17,7 @@ import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task'; import classNames from 'classnames'; import PollContainer from 'flavours/glitch/containers/poll_container'; import Icon from 'flavours/glitch/components/icon'; -import AnimatedNumber from 'flavours/glitch/components/animated_number'; +import { me } from 'flavours/glitch/util/initial_state'; export default class DetailedStatus extends ImmutablePureComponent { @@ -195,7 +195,7 @@ export default class DetailedStatus extends ImmutablePureComponent { applicationLink = <React.Fragment> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></React.Fragment>; } - const visibilityLink = <React.Fragment> · <VisibilityIcon visibility={status.get('visibility')} /></React.Fragment>; + const visibilityLink = <React.Fragment><VisibilityIcon visibility={status.get('visibility')} /> · </React.Fragment>; if (status.get('visibility') === 'direct') { reblogIcon = 'envelope'; @@ -203,7 +203,7 @@ export default class DetailedStatus extends ImmutablePureComponent { reblogIcon = 'lock'; } - if (!['unlisted', 'public'].includes(status.get('visibility'))) { + if (status.getIn(['account', 'id']) !== me || !['unlisted', 'public'].includes(status.get('visibility'))) { reblogLink = null; } else if (this.context.router) { reblogLink = ( @@ -211,9 +211,6 @@ export default class DetailedStatus extends ImmutablePureComponent { <React.Fragment> · </React.Fragment> <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'> <Icon id={reblogIcon} /> - <span className='detailed-status__reblogs'> - <AnimatedNumber value={status.get('reblogs_count')} /> - </span> </Link> </React.Fragment> ); @@ -223,37 +220,43 @@ export default class DetailedStatus extends ImmutablePureComponent { <React.Fragment> · </React.Fragment> <a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}> <Icon id={reblogIcon} /> - <span className='detailed-status__reblogs'> - <AnimatedNumber value={status.get('reblogs_count')} /> - </span> </a> </React.Fragment> ); } - if (this.context.router) { + if (status.getIn(['account', 'id']) !== me) { + favouriteLink = null; + } else if (this.context.router) { favouriteLink = ( - <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'> - <Icon id='star' /> - <span className='detailed-status__favorites'> - <AnimatedNumber value={status.get('favourites_count')} /> - </span> - </Link> + <React.Fragment> + <React.Fragment> · </React.Fragment> + <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'> + <Icon id='star' /> + </Link> + </React.Fragment> ); } else { favouriteLink = ( - <a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}> - <Icon id='star' /> - <span className='detailed-status__favorites'> - <AnimatedNumber value={status.get('favourites_count')} /> - </span> - </a> + <React.Fragment> + <React.Fragment> · </React.Fragment> + <a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}> + <Icon id='star' /> + </a> + </React.Fragment> ); } + const selectorAttribs = { + 'data-status-by': `@${status.getIn(['account', 'acct'])}`, + 'data-nest-level': status.get('nest_level'), + 'data-nest-deep': status.get('nest_level') >= 15, + 'data-local-only': !!status.get('local_only'), + }; + return ( <div style={outerStyle}> - <div ref={this.setRef} className={classNames('detailed-status', `detailed-status-${status.get('visibility')}`, { compact })} data-status-by={status.getIn(['account', 'acct'])}> + <div ref={this.setRef} className={classNames('detailed-status', `detailed-status-${status.get('visibility')}`, { compact, unpublished: status.get('published') === false })} {...selectorAttribs}> <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div> <DisplayName account={status.get('account')} localDomain={this.props.domain} /> @@ -270,13 +273,15 @@ export default class DetailedStatus extends ImmutablePureComponent { onUpdate={this.handleChildUpdate} tagLinks={settings.get('tag_misleading_links')} rewriteMentions={settings.get('rewrite_mentions')} + article disabled /> <div className='detailed-status__meta'> + {visibilityLink} <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'> <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /> - </a>{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink} + </a>{applicationLink}{reblogLink}{favouriteLink} </div> </div> </div> diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js index 9d11f37e0..124de903a 100644 --- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js +++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js @@ -17,7 +17,9 @@ import { import { muteStatus, unmuteStatus, + editStatus, deleteStatus, + publishStatus, hideStatus, revealStatus, } from 'flavours/glitch/actions/statuses'; @@ -34,6 +36,8 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' }, + publishConfirm: { id: 'confirmations.publish.confirm', defaultMessage: 'Publish' }, + publishMessage: { id: 'confirmations.publish.message', defaultMessage: 'Are you ready to publish your post?' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, }); @@ -118,6 +122,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onEdit (status, history) { + dispatch(editStatus(status, history)); + }, + + onPublish (status) { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.publishMessage), + confirm: intl.formatMessage(messages.publishConfirm), + onConfirm: () => dispatch(publishStatus(status.get('id'))), + })); + }, + onDirect (account, router) { dispatch(directCompose(account, router)); }, diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 3e2e95f35..3a6847e8d 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -26,7 +26,7 @@ import { directCompose, } from 'flavours/glitch/actions/compose'; import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; -import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses'; +import { muteStatus, unmuteStatus, deleteStatus, editStatus, publishStatus } from 'flavours/glitch/actions/statuses'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; import { initBlockModal } from 'flavours/glitch/actions/blocks'; import { initReport } from 'flavours/glitch/actions/reports'; @@ -50,6 +50,8 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' }, + publishConfirm: { id: 'confirmations.publish.confirm', defaultMessage: 'Publish' }, + publishMessage: { id: 'confirmations.publish.message', defaultMessage: 'Are you ready to publish your post?' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' }, detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' }, @@ -304,6 +306,20 @@ class Status extends ImmutablePureComponent { } } + handleEditClick = (status, history) => { + this.props.dispatch(editStatus(status, history)); + } + + handlePublishClick = (status) => { + const { dispatch, intl } = this.props; + + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.publishMessage), + confirm: intl.formatMessage(messages.publishConfirm), + onConfirm: () => dispatch(publishStatus(status.get('id'))), + })); + } + handleDirectClick = (account, router) => { this.props.dispatch(directCompose(account, router)); } @@ -588,6 +604,8 @@ class Status extends ImmutablePureComponent { onReblog={this.handleReblogClick} onBookmark={this.handleBookmarkClick} onDelete={this.handleDeleteClick} + onEdit={this.handleEditClick} + onPublish={this.handlePublishClick} onDirect={this.handleDirectClick} onMention={this.handleMentionClick} onMute={this.handleMuteClick} diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js index 4d7fc36c2..f8a61d2fb 100644 --- a/app/javascript/flavours/glitch/features/ui/components/link_footer.js +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.js @@ -60,6 +60,7 @@ class LinkFooter extends React.PureComponent { id='getting_started.open_source_notice' defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.' values={{ + monsterware: <span><a href='https://monsterware.dev/monsterpit/monsterpit-mastodon' rel='noopener noreferrer' target='_blank'>MonsterWare</a></span>, github: <span><a href='https://github.com/glitch-soc/mastodon' rel='noopener noreferrer' target='_blank'>glitch-soc/mastodon</a> (v{version})</span>, Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener noreferrer' target='_blank'>Mastodon</a> }} /> diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.js b/app/javascript/flavours/glitch/features/ui/components/list_panel.js index 354e35027..f351e2a01 100644 --- a/app/javascript/flavours/glitch/features/ui/components/list_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.js @@ -13,7 +13,10 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => { return lists; } - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(4); + return lists.toList().filter(item => !!item).sort((a, b) => { + const r = (b.get('reblogs') ? 1 : 0) - (a.get('reblogs') ? 1 : 0); + return r === 0 ? a.get('title').localeCompare(b.get('title')) : r; + }); }); const mapStateToProps = state => ({ diff --git a/app/javascript/flavours/glitch/features/ui/components/mute_modal.js b/app/javascript/flavours/glitch/features/ui/components/mute_modal.js index 2aab82751..eb4bc02d2 100644 --- a/app/javascript/flavours/glitch/features/ui/components/mute_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/mute_modal.js @@ -7,19 +7,21 @@ import Button from 'flavours/glitch/components/button'; import { closeModal } from 'flavours/glitch/actions/modal'; import { muteAccount } from 'flavours/glitch/actions/accounts'; import { toggleHideNotifications } from 'flavours/glitch/actions/mutes'; +import { toggleTimelinesOnly } from 'flavours/glitch/actions/mutes'; const mapStateToProps = state => { return { account: state.getIn(['mutes', 'new', 'account']), notifications: state.getIn(['mutes', 'new', 'notifications']), + timelinesOnly: state.getIn(['mutes', 'new', 'timelines_only']), }; }; const mapDispatchToProps = dispatch => { return { - onConfirm(account, notifications) { - dispatch(muteAccount(account.get('id'), notifications)); + onConfirm(account, notifications, timelinesOnly) { + dispatch(muteAccount(account.get('id'), notifications, timelinesOnly)); }, onClose() { @@ -29,6 +31,10 @@ const mapDispatchToProps = dispatch => { onToggleNotifications() { dispatch(toggleHideNotifications()); }, + + onToggleTimelinesOnly() { + dispatch(toggleTimelinesOnly()); + }, }; }; @@ -39,9 +45,11 @@ class MuteModal extends React.PureComponent { static propTypes = { account: PropTypes.object.isRequired, notifications: PropTypes.bool.isRequired, + timelinesOnly: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, onConfirm: PropTypes.func.isRequired, onToggleNotifications: PropTypes.func.isRequired, + onTimelinesOnly: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; @@ -51,7 +59,7 @@ class MuteModal extends React.PureComponent { handleClick = () => { this.props.onClose(); - this.props.onConfirm(this.props.account, this.props.notifications); + this.props.onConfirm(this.props.account, this.props.notifications, this.props.timelinesOnly); } handleCancel = () => { @@ -66,8 +74,12 @@ class MuteModal extends React.PureComponent { this.props.onToggleNotifications(); } + toggleTimelinesOnly = () => { + this.props.onToggleTimelinesOnly(); + } + render () { - const { account, notifications } = this.props; + const { account, notifications, timelinesOnly } = this.props; return ( <div className='modal-root__modal mute-modal'> @@ -91,6 +103,13 @@ class MuteModal extends React.PureComponent { <FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' /> </label> </div> + <div> + <label htmlFor='mute-modal__timelines-only-checkbox'> + <FormattedMessage id='mute_modal.timelines_only' defaultMessage='Hide from timelines only?' /> + {' '} + <Toggle id='mute-modal__timelines-only-checkbox' checked={timelinesOnly} onChange={this.toggleTimelinesOnly} /> + </label> + </div> </div> <div className='mute-modal__action-bar'> diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.js index 9016b08d7..7473cfbe0 100644 --- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.js @@ -30,7 +30,7 @@ const makeMapStateToProps = () => { account: getAccount(state, accountId), comment: state.getIn(['reports', 'new', 'comment']), forward: state.getIn(['reports', 'new', 'forward']), - statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])), + statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])), }; }; @@ -70,12 +70,12 @@ class ReportModal extends ImmutablePureComponent { } componentDidMount () { - this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { withReplies: true })); + this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { filter: ':replies' })); } componentWillReceiveProps (nextProps) { if (this.props.account !== nextProps.account && nextProps.account) { - this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { withReplies: true })); + this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { filter: ':replies' })); } } diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index a399fc2b3..9b0ff2426 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -214,8 +214,10 @@ class SwitchingColumnsArea extends React.PureComponent { <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} /> <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> - <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} /> - <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} /> + <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ filter: '' }} /> + <WrappedRoute path='/accounts/:accountId/mentions' component={AccountTimeline} content={children} componentParams={{ filter: ':mentions' }} /> + <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ filter: ':replies' }} /> + <WrappedRoute path='/accounts/:accountId/reblogs' component={AccountTimeline} content={children} componentParams={{ filter: ':reblogs' }} /> <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} /> <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} /> <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} /> |