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

237 lines
7.2 KiB

8 years ago
8 years ago
8 years ago
  1. import {
  2. TIMELINE_REFRESH_REQUEST,
  3. TIMELINE_REFRESH_SUCCESS,
  4. TIMELINE_UPDATE,
  5. TIMELINE_DELETE,
  6. TIMELINE_EXPAND_SUCCESS,
  7. TIMELINE_SCROLL_TOP
  8. } from '../actions/timelines';
  9. import {
  10. REBLOG_SUCCESS,
  11. UNREBLOG_SUCCESS,
  12. FAVOURITE_SUCCESS,
  13. UNFAVOURITE_SUCCESS
  14. } from '../actions/interactions';
  15. import {
  16. ACCOUNT_FETCH_SUCCESS,
  17. ACCOUNT_TIMELINE_FETCH_SUCCESS,
  18. ACCOUNT_TIMELINE_EXPAND_SUCCESS,
  19. ACCOUNT_BLOCK_SUCCESS
  20. } from '../actions/accounts';
  21. import {
  22. STATUS_FETCH_SUCCESS,
  23. CONTEXT_FETCH_SUCCESS
  24. } from '../actions/statuses';
  25. import Immutable from 'immutable';
  26. const initialState = Immutable.Map({
  27. home: Immutable.Map({
  28. loaded: false,
  29. top: true,
  30. items: Immutable.List()
  31. }),
  32. mentions: Immutable.Map({
  33. loaded: false,
  34. top: true,
  35. items: Immutable.List()
  36. }),
  37. public: Immutable.Map({
  38. loaded: false,
  39. top: true,
  40. items: Immutable.List()
  41. }),
  42. tag: Immutable.Map({
  43. id: null,
  44. loaded: false,
  45. top: true,
  46. items: Immutable.List()
  47. }),
  48. accounts_timelines: Immutable.Map(),
  49. ancestors: Immutable.Map(),
  50. descendants: Immutable.Map()
  51. });
  52. const normalizeStatus = (state, status) => {
  53. const replyToId = status.get('in_reply_to_id');
  54. const id = status.get('id');
  55. if (replyToId) {
  56. if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) {
  57. state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id));
  58. }
  59. if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
  60. state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
  61. }
  62. }
  63. return state;
  64. };
  65. const normalizeTimeline = (state, timeline, statuses, replace = false) => {
  66. let ids = Immutable.List();
  67. const loaded = state.getIn([timeline, 'loaded']);
  68. statuses.forEach((status, i) => {
  69. state = normalizeStatus(state, status);
  70. ids = ids.set(i, status.get('id'));
  71. });
  72. state = state.setIn([timeline, 'loaded'], true);
  73. return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : ids));
  74. };
  75. const appendNormalizedTimeline = (state, timeline, statuses) => {
  76. let moreIds = Immutable.List();
  77. statuses.forEach((status, i) => {
  78. state = normalizeStatus(state, status);
  79. moreIds = moreIds.set(i, status.get('id'));
  80. });
  81. return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds));
  82. };
  83. const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
  84. let ids = Immutable.List();
  85. statuses.forEach((status, i) => {
  86. state = normalizeStatus(state, status);
  87. ids = ids.set(i, status.get('id'));
  88. });
  89. return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => (replace ? ids : list.unshift(...ids)));
  90. };
  91. const appendNormalizedAccountTimeline = (state, accountId, statuses) => {
  92. let moreIds = Immutable.List([]);
  93. statuses.forEach((status, i) => {
  94. state = normalizeStatus(state, status);
  95. moreIds = moreIds.set(i, status.get('id'));
  96. });
  97. return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.push(...moreIds));
  98. };
  99. const updateTimeline = (state, timeline, status, references) => {
  100. const top = state.getIn([timeline, 'top']);
  101. state = normalizeStatus(state, status);
  102. state = state.updateIn([timeline, 'items'], Immutable.List(), list => {
  103. if (top && list.size > 40) {
  104. list = list.take(20);
  105. }
  106. if (list.includes(status.get('id'))) {
  107. return list;
  108. }
  109. const reblogOfId = status.getIn(['reblog', 'id'], null);
  110. if (reblogOfId !== null) {
  111. list = list.filterNot(itemId => references.includes(itemId));
  112. }
  113. return list.unshift(status.get('id'));
  114. });
  115. return state;
  116. };
  117. const deleteStatus = (state, id, accountId, references) => {
  118. // Remove references from timelines
  119. ['home', 'mentions', 'public', 'tag'].forEach(function (timeline) {
  120. state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
  121. });
  122. // Remove references from account timelines
  123. state = state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.filterNot(item => item === id));
  124. // Remove references from context
  125. state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
  126. state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
  127. });
  128. state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => {
  129. state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
  130. });
  131. state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
  132. // Remove reblogs of deleted status
  133. references.forEach(ref => {
  134. state = deleteStatus(state, ref[0], ref[1], []);
  135. });
  136. return state;
  137. };
  138. const filterTimelines = (state, relationship, statuses) => {
  139. let references;
  140. statuses.forEach(status => {
  141. if (status.get('account') !== relationship.id) {
  142. return;
  143. }
  144. references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
  145. state = deleteStatus(state, status.get('id'), status.get('account'), references);
  146. });
  147. return state;
  148. };
  149. const normalizeContext = (state, id, ancestors, descendants) => {
  150. const ancestorsIds = ancestors.map(ancestor => ancestor.get('id'));
  151. const descendantsIds = descendants.map(descendant => descendant.get('id'));
  152. return state.withMutations(map => {
  153. map.setIn(['ancestors', id], ancestorsIds);
  154. map.setIn(['descendants', id], descendantsIds);
  155. });
  156. };
  157. const resetTimeline = (state, timeline, id) => {
  158. if (timeline === 'tag' && state.getIn([timeline, 'id']) !== id) {
  159. state = state.update(timeline, map => map
  160. .set('id', id)
  161. .set('loaded', false)
  162. .update('items', list => list.clear()));
  163. }
  164. return state;
  165. };
  166. export default function timelines(state = initialState, action) {
  167. switch(action.type) {
  168. case TIMELINE_REFRESH_REQUEST:
  169. return resetTimeline(state, action.timeline, action.id);
  170. case TIMELINE_REFRESH_SUCCESS:
  171. return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
  172. case TIMELINE_EXPAND_SUCCESS:
  173. return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
  174. case TIMELINE_UPDATE:
  175. return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
  176. case TIMELINE_DELETE:
  177. return deleteStatus(state, action.id, action.accountId, action.references);
  178. case CONTEXT_FETCH_SUCCESS:
  179. return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants));
  180. case ACCOUNT_TIMELINE_FETCH_SUCCESS:
  181. return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
  182. case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
  183. return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
  184. case ACCOUNT_BLOCK_SUCCESS:
  185. return filterTimelines(state, action.relationship, action.statuses);
  186. case TIMELINE_SCROLL_TOP:
  187. return state.setIn([action.timeline, 'top'], action.top);
  188. default:
  189. return state;
  190. }
  191. };