Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions app/javascript/mastodon/actions/announcements.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
export const ANNOUNCEMENTS_FETCH_FAIL = 'ANNOUNCEMENTS_FETCH_FAIL';
export const ANNOUNCEMENTS_UPDATE = 'ANNOUNCEMENTS_UPDATE';
export const ANNOUNCEMENTS_DISMISS = 'ANNOUNCEMENTS_DISMISS';

export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
Expand All @@ -17,6 +16,8 @@ export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL = 'ANNOUNCEMENTS_REACTION_REM

export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';

export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';

const noOp = () => {};

export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
Expand Down Expand Up @@ -54,15 +55,6 @@ export const updateAnnouncements = announcement => ({
announcement: normalizeAnnouncement(announcement),
});

export const dismissAnnouncement = announcementId => (dispatch, getState) => {
dispatch({
type: ANNOUNCEMENTS_DISMISS,
id: announcementId,
});

api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`);
};

export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name));

Expand Down Expand Up @@ -131,3 +123,9 @@ export const updateReaction = reaction => ({
type: ANNOUNCEMENTS_REACTION_UPDATE,
reaction,
});

export function toggleShowAnnouncements() {
return dispatch => {
dispatch({ type: ANNOUNCEMENTS_TOGGLE_SHOW });
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,19 +277,13 @@ class Announcement extends ImmutablePureComponent {
static propTypes = {
announcement: ImmutablePropTypes.map.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
dismissAnnouncement: PropTypes.func.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};

handleDismissClick = () => {
const { dismissAnnouncement, announcement } = this.props;
dismissAnnouncement(announcement.get('id'));
}

render () {
const { announcement, intl } = this.props;
const { announcement } = this.props;
const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
const now = new Date();
Expand All @@ -314,8 +308,6 @@ class Announcement extends ImmutablePureComponent {
removeReaction={this.props.removeReaction}
emojiMap={this.props.emojiMap}
/>

<IconButton title={intl.formatMessage(messages.close)} icon='times' className='announcements__item__dismiss-icon' onClick={this.handleDismissClick} />
</div>
);
}
Expand All @@ -328,8 +320,6 @@ class Announcements extends ImmutablePureComponent {
static propTypes = {
announcements: ImmutablePropTypes.list,
emojiMap: ImmutablePropTypes.map.isRequired,
fetchAnnouncements: PropTypes.func.isRequired,
dismissAnnouncement: PropTypes.func.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
Expand All @@ -339,11 +329,6 @@ class Announcements extends ImmutablePureComponent {
index: 0,
};

componentDidMount () {
const { fetchAnnouncements } = this.props;
fetchAnnouncements();
}

handleChangeIndex = index => {
this.setState({ index: index % this.props.announcements.size });
}
Expand All @@ -369,13 +354,12 @@ class Announcements extends ImmutablePureComponent {
<img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />

<div className='announcements__container'>
<ReactSwipeableViews index={index} onChangeIndex={this.handleChangeIndex}>
<ReactSwipeableViews animateHeight index={index} onChangeIndex={this.handleChangeIndex}>
{announcements.map(announcement => (
<Announcement
key={announcement.get('id')}
announcement={announcement}
emojiMap={this.props.emojiMap}
dismissAnnouncement={this.props.dismissAnnouncement}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
intl={intl}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'mastodon/actions/announcements';
import { addReaction, removeReaction } from 'mastodon/actions/announcements';
import Announcements from '../components/announcements';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable';
Expand All @@ -12,8 +12,6 @@ const mapStateToProps = state => ({
});

const mapDispatchToProps = dispatch => ({
fetchAnnouncements: () => dispatch(fetchAnnouncements()),
dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
addReaction: (id, name) => dispatch(addReaction(id, name)),
removeReaction: (id, name) => dispatch(removeReaction(id, name)),
});
Expand Down
40 changes: 37 additions & 3 deletions app/javascript/mastodon/features/home_timeline/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,23 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { Link } from 'react-router-dom';
import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/announcements';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import classNames from 'classnames';
import IconWithBadge from 'mastodon/components/icon_with_badge';

const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' },
show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' },
hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
});

const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(),
unreadAnnouncements: state.getIn(['announcements', 'unread']).size,
showAnnouncements: state.getIn(['announcements', 'show']),
});

export default @connect(mapStateToProps)
Expand All @@ -32,6 +40,9 @@ class HomeTimeline extends React.PureComponent {
isPartial: PropTypes.bool,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
hasAnnouncements: PropTypes.bool,
unreadAnnouncements: PropTypes.number,
showAnnouncements: PropTypes.bool,
};

handlePin = () => {
Expand Down Expand Up @@ -62,6 +73,7 @@ class HomeTimeline extends React.PureComponent {
}

componentDidMount () {
this.props.dispatch(fetchAnnouncements());
this._checkIfReloadNeeded(false, this.props.isPartial);
}

Expand Down Expand Up @@ -94,10 +106,31 @@ class HomeTimeline extends React.PureComponent {
}
}

handleToggleAnnouncementsClick = (e) => {
e.stopPropagation();
this.props.dispatch(toggleShowAnnouncements());
}

render () {
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;

let announcementsButton = null;

if (hasAnnouncements) {
announcementsButton = (
<button
className={classNames('column-header__button', { 'active': showAnnouncements })}
title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
aria-pressed={showAnnouncements ? 'true' : 'false'}
onClick={this.handleToggleAnnouncementsClick}
>
<IconWithBadge id='bullhorn' count={unreadAnnouncements} />
</button>
);
}

return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader
Expand All @@ -109,13 +142,14 @@ class HomeTimeline extends React.PureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
extraButton={announcementsButton}
>
<ColumnSettingsContainer />
</ColumnHeader>

{hasAnnouncements && showAnnouncements && <AnnouncementsContainer />}

<StatusListContainer
prepend={<AnnouncementsContainer />}
alwaysPrepend
trackScroll={!pinned}
scrollKey={`home_timeline-${columnId}`}
onLoadMore={this.handleLoadMore}
Expand Down
28 changes: 22 additions & 6 deletions app/javascript/mastodon/reducers/announcements.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ import {
ANNOUNCEMENTS_FETCH_SUCCESS,
ANNOUNCEMENTS_FETCH_FAIL,
ANNOUNCEMENTS_UPDATE,
ANNOUNCEMENTS_DISMISS,
ANNOUNCEMENTS_REACTION_UPDATE,
ANNOUNCEMENTS_REACTION_ADD_REQUEST,
ANNOUNCEMENTS_REACTION_ADD_FAIL,
ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
ANNOUNCEMENTS_TOGGLE_SHOW,
} from '../actions/announcements';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
import { Map as ImmutableMap, List as ImmutableList, Set as ImmutableSet, fromJS } from 'immutable';

const initialState = ImmutableMap({
items: ImmutableList(),
isLoading: false,
show: true,
unread: ImmutableSet(),
});

const updateReaction = (state, id, name, updater) => state.update('items', list => list.map(announcement => {
Expand Down Expand Up @@ -43,21 +45,35 @@ const addReaction = (state, id, name) => updateReaction(state, id, name, x => x.

const removeReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', false).update('count', y => y - 1));

const addUnread = (state, items) => {
if (state.get('show')) return state;

const newIds = ImmutableSet(items.map(x => x.get('id')));
const oldIds = ImmutableSet(state.get('items').map(x => x.get('id')));
return state.update('unread', unread => unread.union(newIds.subtract(oldIds)));
};

export default function announcementsReducer(state = initialState, action) {
switch(action.type) {
case ANNOUNCEMENTS_TOGGLE_SHOW:
return state.withMutations(map => {
if (!map.get('show')) map.set('unread', ImmutableSet());
map.set('show', !map.get('show'));
});
case ANNOUNCEMENTS_FETCH_REQUEST:
return state.set('isLoading', true);
case ANNOUNCEMENTS_FETCH_SUCCESS:
return state.withMutations(map => {
map.set('items', fromJS(action.announcements));
const items = fromJS(action.announcements);
map.set('unread', ImmutableSet());
addUnread(map, items);
map.set('items', items);
map.set('isLoading', false);
});
case ANNOUNCEMENTS_FETCH_FAIL:
return state.set('isLoading', false);
case ANNOUNCEMENTS_UPDATE:
return state.update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
case ANNOUNCEMENTS_DISMISS:
return state.update('items', list => list.filterNot(announcement => announcement.get('id') === action.id));
return addUnread(state, [fromJS(action.announcement)]).update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
case ANNOUNCEMENTS_REACTION_UPDATE:
return updateReactionCount(state, action.reaction);
case ANNOUNCEMENTS_REACTION_ADD_REQUEST:
Expand Down
8 changes: 1 addition & 7 deletions app/javascript/styles/mastodon/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6631,7 +6631,7 @@ noscript {
}

.announcements {
background: lighten($ui-base-color, 4%);
background: lighten($ui-base-color, 8%);
border-top: 1px solid $ui-base-color;
font-size: 13px;
display: flex;
Expand Down Expand Up @@ -6672,12 +6672,6 @@ noscript {
font-weight: 500;
margin-bottom: 10px;
}

&__dismiss-icon {
position: absolute;
top: 12px;
right: 12px;
}
}

&__pagination {
Expand Down