闭社主体 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.

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