Merge tag 'v3.3.0' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2020-12-27 11:00:43 +01:00
877 changed files with 35407 additions and 11128 deletions

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View 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;
}
};

View File

@ -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:

View File

@ -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,
}),
}),

View File

@ -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) => {