闭社主体 forked from https://github.com/tootsuite/mastodon
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.

152 lines
5.0 KiB

  1. import {
  2. TIMELINE_REFRESH_REQUEST,
  3. TIMELINE_REFRESH_SUCCESS,
  4. TIMELINE_REFRESH_FAIL,
  5. TIMELINE_UPDATE,
  6. TIMELINE_DELETE,
  7. TIMELINE_EXPAND_SUCCESS,
  8. TIMELINE_EXPAND_REQUEST,
  9. TIMELINE_EXPAND_FAIL,
  10. TIMELINE_SCROLL_TOP,
  11. TIMELINE_CONNECT,
  12. TIMELINE_DISCONNECT,
  13. } from '../actions/timelines';
  14. import {
  15. REBLOG_SUCCESS,
  16. UNREBLOG_SUCCESS,
  17. FAVOURITE_SUCCESS,
  18. UNFAVOURITE_SUCCESS,
  19. } from '../actions/interactions';
  20. import {
  21. ACCOUNT_BLOCK_SUCCESS,
  22. ACCOUNT_MUTE_SUCCESS,
  23. } from '../actions/accounts';
  24. import Immutable from 'immutable';
  25. const initialState = Immutable.Map();
  26. const initialTimeline = Immutable.Map({
  27. unread: 0,
  28. online: false,
  29. top: true,
  30. loaded: false,
  31. isLoading: false,
  32. next: false,
  33. items: Immutable.List(),
  34. });
  35. const normalizeTimeline = (state, timeline, statuses, next) => {
  36. const ids = Immutable.List(statuses.map(status => status.get('id')));
  37. const wasLoaded = state.getIn([timeline, 'loaded']);
  38. const hadNext = state.getIn([timeline, 'next']);
  39. const oldIds = state.getIn([timeline, 'items'], Immutable.List());
  40. return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
  41. mMap.set('loaded', true);
  42. mMap.set('isLoading', false);
  43. if (!hadNext) mMap.set('next', next);
  44. mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids);
  45. }));
  46. };
  47. const appendNormalizedTimeline = (state, timeline, statuses, next) => {
  48. const ids = Immutable.List(statuses.map(status => status.get('id')));
  49. const oldIds = state.getIn([timeline, 'items'], Immutable.List());
  50. return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
  51. mMap.set('isLoading', false);
  52. mMap.set('next', next);
  53. mMap.set('items', oldIds.concat(ids));
  54. }));
  55. };
  56. const updateTimeline = (state, timeline, status, references) => {
  57. const top = state.getIn([timeline, 'top']);
  58. const ids = state.getIn([timeline, 'items'], Immutable.List());
  59. const includesId = ids.includes(status.get('id'));
  60. const unread = state.getIn([timeline, 'unread'], 0);
  61. if (includesId) {
  62. return state;
  63. }
  64. let newIds = ids;
  65. return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
  66. if (!top) mMap.set('unread', unread + 1);
  67. if (top && ids.size > 40) newIds = newIds.take(20);
  68. if (status.getIn(['reblog', 'id'], null) !== null) newIds = newIds.filterNot(item => references.includes(item));
  69. mMap.set('items', newIds.unshift(status.get('id')));
  70. }));
  71. };
  72. const deleteStatus = (state, id, accountId, references, reblogOf) => {
  73. state.keySeq().forEach(timeline => {
  74. state = state.updateIn([timeline, 'items'], list => {
  75. if (reblogOf && !list.includes(reblogOf)) {
  76. return list.map(item => item === id ? reblogOf : item);
  77. } else {
  78. return list.filterNot(item => item === id);
  79. }
  80. });
  81. });
  82. // Remove reblogs of deleted status
  83. references.forEach(ref => {
  84. state = deleteStatus(state, ref[0], ref[1], []);
  85. });
  86. return state;
  87. };
  88. const filterTimelines = (state, relationship, statuses) => {
  89. let references;
  90. statuses.forEach(status => {
  91. if (status.get('account') !== relationship.id) {
  92. return;
  93. }
  94. references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
  95. state = deleteStatus(state, status.get('id'), status.get('account'), references);
  96. });
  97. return state;
  98. };
  99. const updateTop = (state, timeline, top) => {
  100. return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
  101. if (top) mMap.set('unread', 0);
  102. mMap.set('top', top);
  103. }));
  104. };
  105. export default function timelines(state = initialState, action) {
  106. switch(action.type) {
  107. case TIMELINE_REFRESH_REQUEST:
  108. case TIMELINE_EXPAND_REQUEST:
  109. return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
  110. case TIMELINE_REFRESH_FAIL:
  111. case TIMELINE_EXPAND_FAIL:
  112. return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
  113. case TIMELINE_REFRESH_SUCCESS:
  114. return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
  115. case TIMELINE_EXPAND_SUCCESS:
  116. return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
  117. case TIMELINE_UPDATE:
  118. return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
  119. case TIMELINE_DELETE:
  120. return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
  121. case ACCOUNT_BLOCK_SUCCESS:
  122. case ACCOUNT_MUTE_SUCCESS:
  123. return filterTimelines(state, action.relationship, action.statuses);
  124. case TIMELINE_SCROLL_TOP:
  125. return updateTop(state, action.timeline, action.top);
  126. case TIMELINE_CONNECT:
  127. return state.update(action.timeline, initialTimeline, map => map.set('online', true));
  128. case TIMELINE_DISCONNECT:
  129. return state.update(action.timeline, initialTimeline, map => map.set('online', false));
  130. default:
  131. return state;
  132. }
  133. };