You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
5.6 KiB

  1. import {
  2. NOTIFICATIONS_UPDATE,
  3. NOTIFICATIONS_EXPAND_SUCCESS,
  4. NOTIFICATIONS_EXPAND_REQUEST,
  5. NOTIFICATIONS_EXPAND_FAIL,
  6. NOTIFICATIONS_CLEAR,
  7. NOTIFICATIONS_SCROLL_TOP,
  8. NOTIFICATIONS_DELETE_MARKED_REQUEST,
  9. NOTIFICATIONS_DELETE_MARKED_SUCCESS,
  10. NOTIFICATION_MARK_FOR_DELETE,
  11. NOTIFICATIONS_DELETE_MARKED_FAIL,
  12. NOTIFICATIONS_ENTER_CLEARING_MODE,
  13. NOTIFICATIONS_MARK_ALL_FOR_DELETE,
  14. } from 'flavours/glitch/actions/notifications';
  15. import {
  16. ACCOUNT_BLOCK_SUCCESS,
  17. ACCOUNT_MUTE_SUCCESS,
  18. } from 'flavours/glitch/actions/accounts';
  19. import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines';
  20. import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
  21. import compareId from 'flavours/glitch/util/compare_id';
  22. const initialState = ImmutableMap({
  23. items: ImmutableList(),
  24. hasMore: true,
  25. top: true,
  26. unread: 0,
  27. isLoading: false,
  28. cleaningMode: false,
  29. // notification removal mark of new notifs loaded whilst cleaningMode is true.
  30. markNewForDelete: false,
  31. });
  32. const notificationToMap = (state, notification) => ImmutableMap({
  33. id: notification.id,
  34. type: notification.type,
  35. account: notification.account.id,
  36. markedForDelete: state.get('markNewForDelete'),
  37. status: notification.status ? notification.status.id : null,
  38. });
  39. const normalizeNotification = (state, notification) => {
  40. const top = state.get('top');
  41. if (!top) {
  42. state = state.update('unread', unread => unread + 1);
  43. }
  44. return state.update('items', list => {
  45. if (top && list.size > 40) {
  46. list = list.take(20);
  47. }
  48. return list.unshift(notificationToMap(state, notification));
  49. });
  50. };
  51. const expandNormalizedNotifications = (state, notifications, next) => {
  52. let items = ImmutableList();
  53. notifications.forEach((n, i) => {
  54. items = items.set(i, notificationToMap(state, n));
  55. });
  56. return state.withMutations(mutable => {
  57. if (!items.isEmpty()) {
  58. mutable.update('items', list => {
  59. const lastIndex = 1 + list.findLastIndex(
  60. item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id'))
  61. );
  62. const firstIndex = 1 + list.take(lastIndex).findLastIndex(
  63. item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0
  64. );
  65. return list.take(firstIndex).concat(items, list.skip(lastIndex));
  66. });
  67. }
  68. if (!next) {
  69. mutable.set('hasMore', true);
  70. }
  71. mutable.set('isLoading', false);
  72. });
  73. };
  74. const filterNotifications = (state, relationship) => {
  75. return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id));
  76. };
  77. const updateTop = (state, top) => {
  78. if (top) {
  79. state = state.set('unread', 0);
  80. }
  81. return state.set('top', top);
  82. };
  83. const deleteByStatus = (state, statusId) => {
  84. return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId));
  85. };
  86. const markForDelete = (state, notificationId, yes) => {
  87. return state.update('items', list => list.map(item => {
  88. if(item.get('id') === notificationId) {
  89. return item.set('markedForDelete', yes);
  90. } else {
  91. return item;
  92. }
  93. }));
  94. };
  95. const markAllForDelete = (state, yes) => {
  96. return state.update('items', list => list.map(item => {
  97. if(yes !== null) {
  98. return item.set('markedForDelete', yes);
  99. } else {
  100. return item.set('markedForDelete', !item.get('markedForDelete'));
  101. }
  102. }));
  103. };
  104. const unmarkAllForDelete = (state) => {
  105. return state.update('items', list => list.map(item => item.set('markedForDelete', false)));
  106. };
  107. const deleteMarkedNotifs = (state) => {
  108. return state.update('items', list => list.filterNot(item => item.get('markedForDelete')));
  109. };
  110. export default function notifications(state = initialState, action) {
  111. let st;
  112. switch(action.type) {
  113. case NOTIFICATIONS_EXPAND_REQUEST:
  114. case NOTIFICATIONS_DELETE_MARKED_REQUEST:
  115. return state.set('isLoading', true);
  116. case NOTIFICATIONS_DELETE_MARKED_FAIL:
  117. case NOTIFICATIONS_EXPAND_FAIL:
  118. return state.set('isLoading', false);
  119. case NOTIFICATIONS_SCROLL_TOP:
  120. return updateTop(state, action.top);
  121. case NOTIFICATIONS_UPDATE:
  122. return normalizeNotification(state, action.notification);
  123. case NOTIFICATIONS_EXPAND_SUCCESS:
  124. return expandNormalizedNotifications(state, action.notifications, action.next);
  125. case ACCOUNT_BLOCK_SUCCESS:
  126. case ACCOUNT_MUTE_SUCCESS:
  127. return filterNotifications(state, action.relationship);
  128. case NOTIFICATIONS_CLEAR:
  129. return state.set('items', ImmutableList()).set('hasMore', false);
  130. case TIMELINE_DELETE:
  131. return deleteByStatus(state, action.id);
  132. case TIMELINE_DISCONNECT:
  133. return action.timeline === 'home' ?
  134. state.update('items', items => items.first() ? items.unshift(null) : items) :
  135. state;
  136. case NOTIFICATION_MARK_FOR_DELETE:
  137. return markForDelete(state, action.id, action.yes);
  138. case NOTIFICATIONS_DELETE_MARKED_SUCCESS:
  139. return deleteMarkedNotifs(state).set('isLoading', false);
  140. case NOTIFICATIONS_ENTER_CLEARING_MODE:
  141. st = state.set('cleaningMode', action.yes);
  142. if (!action.yes) {
  143. return unmarkAllForDelete(st).set('markNewForDelete', false);
  144. } else {
  145. return st;
  146. }
  147. case NOTIFICATIONS_MARK_ALL_FOR_DELETE:
  148. st = state;
  149. if (action.yes === null) {
  150. // Toggle - this is a bit confusing, as it toggles the all-none mode
  151. //st = st.set('markNewForDelete', !st.get('markNewForDelete'));
  152. } else {
  153. st = st.set('markNewForDelete', action.yes);
  154. }
  155. return markAllForDelete(st, action.yes);
  156. default:
  157. return state;
  158. }
  159. };