Merge tag 'v3.3.0' into instance_only_statuses
This commit is contained in:
@ -36,6 +36,7 @@ import trends from './trends';
|
||||
import missed_updates from './missed_updates';
|
||||
import announcements from './announcements';
|
||||
import markers from './markers';
|
||||
import picture_in_picture from './picture_in_picture';
|
||||
|
||||
const reducers = {
|
||||
announcements,
|
||||
@ -75,6 +76,7 @@ const reducers = {
|
||||
trends,
|
||||
missed_updates,
|
||||
markers,
|
||||
picture_in_picture,
|
||||
};
|
||||
|
||||
export default combineReducers(reducers);
|
||||
|
@ -1,15 +1,20 @@
|
||||
import { STORE_HYDRATE } from '../actions/store';
|
||||
import { STORE_HYDRATE } from 'mastodon/actions/store';
|
||||
import { APP_LAYOUT_CHANGE } from 'mastodon/actions/app';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
streaming_api_base_url: null,
|
||||
access_token: null,
|
||||
layout: layoutFromWindow(),
|
||||
});
|
||||
|
||||
export default function meta(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case STORE_HYDRATE:
|
||||
return state.merge(action.state.get('meta'));
|
||||
case APP_LAYOUT_CHANGE:
|
||||
return state.set('layout', action.layout);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -3,12 +3,14 @@ import Immutable from 'immutable';
|
||||
import {
|
||||
MUTES_INIT_MODAL,
|
||||
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
|
||||
MUTES_CHANGE_DURATION,
|
||||
} from '../actions/mutes';
|
||||
|
||||
const initialState = Immutable.Map({
|
||||
new: Immutable.Map({
|
||||
account: null,
|
||||
notifications: true,
|
||||
duration: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -21,6 +23,8 @@ export default function mutes(state = initialState, action) {
|
||||
});
|
||||
case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
|
||||
return state.updateIn(['new', 'notifications'], (old) => !old);
|
||||
case MUTES_CHANGE_DURATION:
|
||||
return state.setIn(['new', 'duration'], Number(action.duration));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ import {
|
||||
NOTIFICATIONS_LOAD_PENDING,
|
||||
NOTIFICATIONS_MOUNT,
|
||||
NOTIFICATIONS_UNMOUNT,
|
||||
NOTIFICATIONS_MARK_AS_READ,
|
||||
NOTIFICATIONS_SET_BROWSER_SUPPORT,
|
||||
NOTIFICATIONS_SET_BROWSER_PERMISSION,
|
||||
} from '../actions/notifications';
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
@ -16,6 +19,13 @@ import {
|
||||
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
||||
FOLLOW_REQUEST_REJECT_SUCCESS,
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
MARKERS_FETCH_SUCCESS,
|
||||
} from '../actions/markers';
|
||||
import {
|
||||
APP_FOCUS,
|
||||
APP_UNFOCUS,
|
||||
} from '../actions/app';
|
||||
import { DOMAIN_BLOCK_SUCCESS } from 'mastodon/actions/domain_blocks';
|
||||
import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines';
|
||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
@ -26,9 +36,14 @@ const initialState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
hasMore: true,
|
||||
top: false,
|
||||
mounted: false,
|
||||
mounted: 0,
|
||||
unread: 0,
|
||||
lastReadId: '0',
|
||||
readMarkerId: '0',
|
||||
isTabVisible: true,
|
||||
isLoading: false,
|
||||
browserSupport: false,
|
||||
browserPermission: 'default',
|
||||
});
|
||||
|
||||
const notificationToMap = notification => ImmutableMap({
|
||||
@ -46,8 +61,10 @@ const normalizeNotification = (state, notification, usePendingItems) => {
|
||||
return state.update('pendingItems', list => list.unshift(notificationToMap(notification))).update('unread', unread => unread + 1);
|
||||
}
|
||||
|
||||
if (!top) {
|
||||
if (shouldCountUnreadNotifications(state)) {
|
||||
state = state.update('unread', unread => unread + 1);
|
||||
} else {
|
||||
state = state.set('lastReadId', notification.id);
|
||||
}
|
||||
|
||||
return state.update('items', list => {
|
||||
@ -60,6 +77,7 @@ const normalizeNotification = (state, notification, usePendingItems) => {
|
||||
};
|
||||
|
||||
const expandNormalizedNotifications = (state, notifications, next, isLoadingRecent, usePendingItems) => {
|
||||
const lastReadId = state.get('lastReadId');
|
||||
let items = ImmutableList();
|
||||
|
||||
notifications.forEach((n, i) => {
|
||||
@ -87,6 +105,15 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece
|
||||
mutable.set('hasMore', false);
|
||||
}
|
||||
|
||||
if (shouldCountUnreadNotifications(state)) {
|
||||
mutable.update('unread', unread => unread + items.count(item => compareId(item.get('id'), lastReadId) > 0));
|
||||
} else {
|
||||
const mostRecent = items.find(item => item !== null);
|
||||
if (mostRecent && compareId(lastReadId, mostRecent.get('id')) < 0) {
|
||||
mutable.set('lastReadId', mostRecent.get('id'));
|
||||
}
|
||||
}
|
||||
|
||||
mutable.set('isLoading', false);
|
||||
});
|
||||
};
|
||||
@ -96,21 +123,93 @@ const filterNotifications = (state, accountIds, type) => {
|
||||
return state.update('items', helper).update('pendingItems', helper);
|
||||
};
|
||||
|
||||
const clearUnread = (state) => {
|
||||
state = state.set('unread', state.get('pendingItems').size);
|
||||
const lastNotification = state.get('items').find(item => item !== null);
|
||||
return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0');
|
||||
};
|
||||
|
||||
const updateTop = (state, top) => {
|
||||
if (top) {
|
||||
state = state.set('unread', state.get('pendingItems').size);
|
||||
state = state.set('top', top);
|
||||
|
||||
if (!shouldCountUnreadNotifications(state)) {
|
||||
state = clearUnread(state);
|
||||
}
|
||||
|
||||
return state.set('top', top);
|
||||
return state;
|
||||
};
|
||||
|
||||
const deleteByStatus = (state, statusId) => {
|
||||
const lastReadId = state.get('lastReadId');
|
||||
|
||||
if (shouldCountUnreadNotifications(state)) {
|
||||
const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
|
||||
state = state.update('unread', unread => unread - deletedUnread.size);
|
||||
}
|
||||
|
||||
const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId);
|
||||
const deletedUnread = state.get('pendingItems').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
|
||||
state = state.update('unread', unread => unread - deletedUnread.size);
|
||||
return state.update('items', helper).update('pendingItems', helper);
|
||||
};
|
||||
|
||||
const updateMounted = (state) => {
|
||||
state = state.update('mounted', count => count + 1);
|
||||
if (!shouldCountUnreadNotifications(state, state.get('mounted') === 1)) {
|
||||
state = state.set('readMarkerId', state.get('lastReadId'));
|
||||
state = clearUnread(state);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
const updateVisibility = (state, visibility) => {
|
||||
state = state.set('isTabVisible', visibility);
|
||||
if (!shouldCountUnreadNotifications(state)) {
|
||||
state = state.set('readMarkerId', state.get('lastReadId'));
|
||||
state = clearUnread(state);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
const shouldCountUnreadNotifications = (state, ignoreScroll = false) => {
|
||||
const isTabVisible = state.get('isTabVisible');
|
||||
const isOnTop = state.get('top');
|
||||
const isMounted = state.get('mounted') > 0;
|
||||
const lastReadId = state.get('lastReadId');
|
||||
const lastItem = state.get('items').findLast(item => item !== null);
|
||||
const lastItemReached = !state.get('hasMore') || lastReadId === '0' || (lastItem && compareId(lastItem.get('id'), lastReadId) <= 0);
|
||||
|
||||
return !(isTabVisible && (ignoreScroll || isOnTop) && isMounted && lastItemReached);
|
||||
};
|
||||
|
||||
const recountUnread = (state, last_read_id) => {
|
||||
return state.withMutations(mutable => {
|
||||
if (compareId(last_read_id, mutable.get('lastReadId')) > 0) {
|
||||
mutable.set('lastReadId', last_read_id);
|
||||
}
|
||||
|
||||
if (compareId(last_read_id, mutable.get('readMarkerId')) > 0) {
|
||||
mutable.set('readMarkerId', last_read_id);
|
||||
}
|
||||
|
||||
if (state.get('unread') > 0 || shouldCountUnreadNotifications(state)) {
|
||||
mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), last_read_id) > 0));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default function notifications(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case MARKERS_FETCH_SUCCESS:
|
||||
return action.markers.notifications ? recountUnread(state, action.markers.notifications.last_read_id) : state;
|
||||
case NOTIFICATIONS_MOUNT:
|
||||
return updateMounted(state);
|
||||
case NOTIFICATIONS_UNMOUNT:
|
||||
return state.update('mounted', count => count - 1);
|
||||
case APP_FOCUS:
|
||||
return updateVisibility(state, true);
|
||||
case APP_UNFOCUS:
|
||||
return updateVisibility(state, false);
|
||||
case NOTIFICATIONS_LOAD_PENDING:
|
||||
return state.update('items', list => state.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0);
|
||||
case NOTIFICATIONS_EXPAND_REQUEST:
|
||||
@ -144,10 +243,13 @@ export default function notifications(state = initialState, action) {
|
||||
return action.timeline === 'home' ?
|
||||
state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
|
||||
state;
|
||||
case NOTIFICATIONS_MOUNT:
|
||||
return state.set('mounted', true);
|
||||
case NOTIFICATIONS_UNMOUNT:
|
||||
return state.set('mounted', false);
|
||||
case NOTIFICATIONS_MARK_AS_READ:
|
||||
const lastNotification = state.get('items').find(item => item !== null);
|
||||
return lastNotification ? recountUnread(state, lastNotification.get('id')) : state;
|
||||
case NOTIFICATIONS_SET_BROWSER_SUPPORT:
|
||||
return state.set('browserSupport', action.value);
|
||||
case NOTIFICATIONS_SET_BROWSER_PERMISSION:
|
||||
return state.set('browserPermission', action.value);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
22
app/javascript/mastodon/reducers/picture_in_picture.js
Normal file
22
app/javascript/mastodon/reducers/picture_in_picture.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { PICTURE_IN_PICTURE_DEPLOY, PICTURE_IN_PICTURE_REMOVE } from 'mastodon/actions/picture_in_picture';
|
||||
|
||||
const initialState = {
|
||||
statusId: null,
|
||||
accountId: null,
|
||||
type: null,
|
||||
src: null,
|
||||
muted: false,
|
||||
volume: 0,
|
||||
currentTime: 0,
|
||||
};
|
||||
|
||||
export default function pictureInPicture(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case PICTURE_IN_PICTURE_DEPLOY:
|
||||
return { statusId: action.statusId, accountId: action.accountId, type: action.playerType, ...action.props };
|
||||
case PICTURE_IN_PICTURE_REMOVE:
|
||||
return { ...initialState };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
@ -45,7 +45,7 @@ const initialState = ImmutableMap();
|
||||
export default function relationships(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_FOLLOW_REQUEST:
|
||||
return state.setIn([action.id, action.locked ? 'requested' : 'following'], true);
|
||||
return state.getIn([action.id, 'following']) ? state : state.setIn([action.id, action.locked ? 'requested' : 'following'], true);
|
||||
case ACCOUNT_FOLLOW_FAIL:
|
||||
return state.setIn([action.id, action.locked ? 'requested' : 'following'], false);
|
||||
case ACCOUNT_UNFOLLOW_REQUEST:
|
||||
|
@ -29,12 +29,13 @@ const initialState = ImmutableMap({
|
||||
|
||||
notifications: ImmutableMap({
|
||||
alerts: ImmutableMap({
|
||||
follow: true,
|
||||
follow: false,
|
||||
follow_request: false,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
favourite: false,
|
||||
reblog: false,
|
||||
mention: false,
|
||||
poll: false,
|
||||
status: false,
|
||||
}),
|
||||
|
||||
quickFilter: ImmutableMap({
|
||||
@ -43,6 +44,8 @@ const initialState = ImmutableMap({
|
||||
advanced: false,
|
||||
}),
|
||||
|
||||
dismissPermissionBanner: false,
|
||||
|
||||
shows: ImmutableMap({
|
||||
follow: true,
|
||||
follow_request: false,
|
||||
@ -50,6 +53,7 @@ const initialState = ImmutableMap({
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
}),
|
||||
|
||||
sounds: ImmutableMap({
|
||||
@ -59,6 +63,7 @@ const initialState = ImmutableMap({
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
}),
|
||||
}),
|
||||
|
||||
|
@ -53,14 +53,20 @@ import {
|
||||
} from 'mastodon/actions/directory';
|
||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
const initialListState = ImmutableMap({
|
||||
next: null,
|
||||
isLoading: false,
|
||||
items: ImmutableList(),
|
||||
});
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
followers: ImmutableMap(),
|
||||
following: ImmutableMap(),
|
||||
reblogged_by: ImmutableMap(),
|
||||
favourited_by: ImmutableMap(),
|
||||
follow_requests: ImmutableMap(),
|
||||
blocks: ImmutableMap(),
|
||||
mutes: ImmutableMap(),
|
||||
followers: initialListState,
|
||||
following: initialListState,
|
||||
reblogged_by: initialListState,
|
||||
favourited_by: initialListState,
|
||||
follow_requests: initialListState,
|
||||
blocks: initialListState,
|
||||
mutes: initialListState,
|
||||
});
|
||||
|
||||
const normalizeList = (state, path, accounts, next) => {
|
||||
|
Reference in New Issue
Block a user