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.

269 lines
8.9 KiB

7 years ago
7 years ago
7 years ago
7 years ago
  1. import {
  2. TIMELINE_REFRESH_SUCCESS,
  3. TIMELINE_UPDATE,
  4. TIMELINE_DELETE,
  5. TIMELINE_EXPAND_SUCCESS
  6. } from '../actions/timelines';
  7. import {
  8. REBLOG_SUCCESS,
  9. UNREBLOG_SUCCESS,
  10. FAVOURITE_SUCCESS,
  11. UNFAVOURITE_SUCCESS
  12. } from '../actions/interactions';
  13. import {
  14. ACCOUNT_SET_SELF,
  15. ACCOUNT_FETCH_SUCCESS,
  16. ACCOUNT_FOLLOW_SUCCESS,
  17. ACCOUNT_UNFOLLOW_SUCCESS,
  18. ACCOUNT_BLOCK_SUCCESS,
  19. ACCOUNT_UNBLOCK_SUCCESS,
  20. ACCOUNT_TIMELINE_FETCH_SUCCESS,
  21. ACCOUNT_TIMELINE_EXPAND_SUCCESS,
  22. FOLLOWERS_FETCH_SUCCESS,
  23. FOLLOWING_FETCH_SUCCESS,
  24. RELATIONSHIPS_FETCH_SUCCESS
  25. } from '../actions/accounts';
  26. import {
  27. STATUS_FETCH_SUCCESS,
  28. STATUS_DELETE_SUCCESS
  29. } from '../actions/statuses';
  30. import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow';
  31. import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions';
  32. import Immutable from 'immutable';
  33. const initialState = Immutable.Map({
  34. home: Immutable.List([]),
  35. mentions: Immutable.List([]),
  36. public: Immutable.List([]),
  37. statuses: Immutable.Map(),
  38. accounts: Immutable.Map(),
  39. accounts_timelines: Immutable.Map(),
  40. me: null,
  41. ancestors: Immutable.Map(),
  42. descendants: Immutable.Map(),
  43. relationships: Immutable.Map(),
  44. suggestions: Immutable.List([])
  45. });
  46. function normalizeStatus(state, status) {
  47. // Separate account
  48. let account = status.get('account');
  49. status = status.set('account', account.get('id'));
  50. // Separate reblog, repeat for reblog
  51. let reblog = status.get('reblog', null);
  52. if (reblog !== null) {
  53. status = status.set('reblog', reblog.get('id'));
  54. state = normalizeStatus(state, reblog);
  55. }
  56. // Replies
  57. if (status.get('in_reply_to_id')) {
  58. state = state.updateIn(['descendants', status.get('in_reply_to_id')], set => {
  59. if (!Immutable.OrderedSet.isOrderedSet(set)) {
  60. return Immutable.OrderedSet([status.get('id')]);
  61. } else {
  62. return set.add(status.get('id'));
  63. }
  64. });
  65. }
  66. return state.withMutations(map => {
  67. if (status.get('in_reply_to_id')) {
  68. map.updateIn(['descendants', status.get('in_reply_to_id')], Immutable.OrderedSet(), set => set.add(status.get('id')));
  69. map.updateIn(['ancestors', status.get('id')], Immutable.OrderedSet(), set => set.add(status.get('in_reply_to_id')));
  70. }
  71. map.setIn(['accounts', account.get('id')], account);
  72. map.setIn(['statuses', status.get('id')], status);
  73. });
  74. };
  75. function normalizeTimeline(state, timeline, statuses, replace = false) {
  76. let ids = Immutable.List([]);
  77. statuses.forEach((status, i) => {
  78. state = normalizeStatus(state, status);
  79. ids = ids.set(i, status.get('id'));
  80. });
  81. return state.update(timeline, list => (replace ? ids : list.unshift(...ids)));
  82. };
  83. function appendNormalizedTimeline(state, timeline, statuses) {
  84. let moreIds = Immutable.List([]);
  85. statuses.forEach((status, i) => {
  86. state = normalizeStatus(state, status);
  87. moreIds = moreIds.set(i, status.get('id'));
  88. });
  89. return state.update(timeline, list => list.push(...moreIds));
  90. };
  91. function normalizeAccountTimeline(state, accountId, statuses, replace = false) {
  92. let ids = Immutable.List([]);
  93. statuses.forEach((status, i) => {
  94. state = normalizeStatus(state, status);
  95. ids = ids.set(i, status.get('id'));
  96. });
  97. return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => (replace ? ids : list.unshift(...ids)));
  98. };
  99. function appendNormalizedAccountTimeline(state, accountId, statuses) {
  100. let moreIds = Immutable.List([]);
  101. statuses.forEach((status, i) => {
  102. state = normalizeStatus(state, status);
  103. moreIds = moreIds.set(i, status.get('id'));
  104. });
  105. return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.push(...moreIds));
  106. };
  107. function updateTimeline(state, timeline, status) {
  108. state = normalizeStatus(state, status);
  109. state = state.update(timeline, list => {
  110. const reblogOfId = status.getIn(['reblog', 'id'], null);
  111. if (reblogOfId !== null) {
  112. const otherReblogs = state.get('statuses').filter(item => item.get('reblog') === reblogOfId).map((_, itemId) => itemId);
  113. list = list.filterNot(itemId => (itemId === reblogOfId || otherReblogs.includes(itemId)));
  114. }
  115. return list.unshift(status.get('id'));
  116. });
  117. //state = state.updateIn(['accounts_timelines', status.getIn(['account', 'id'])], Immutable.List([]), list => (list.includes(status.get('id')) ? list : list.unshift(status.get('id'))));
  118. return state;
  119. };
  120. function deleteStatus(state, id) {
  121. const status = state.getIn(['statuses', id]);
  122. if (!status) {
  123. return state;
  124. }
  125. // Remove references from timelines
  126. ['home', 'mentions'].forEach(function (timeline) {
  127. state = state.update(timeline, list => list.filterNot(item => item === id));
  128. });
  129. // Remove references from account timelines
  130. state = state.updateIn(['accounts_timelines', status.get('account')], Immutable.List([]), list => list.filterNot(item => item === id));
  131. // Remove reblogs of deleted status
  132. const references = state.get('statuses').filter(item => item.get('reblog') === id);
  133. references.forEach(referencingId => {
  134. state = deleteStatus(state, referencingId);
  135. });
  136. // Remove normalized status
  137. return state.deleteIn(['statuses', id]);
  138. };
  139. function normalizeAccount(state, account, relationship) {
  140. if (relationship) {
  141. state = normalizeRelationship(state, relationship);
  142. }
  143. return state.setIn(['accounts', account.get('id')], account);
  144. };
  145. function normalizeRelationship(state, relationship) {
  146. if (state.get('suggestions').includes(relationship.get('id')) && (relationship.get('following') || relationship.get('blocking'))) {
  147. state = state.update('suggestions', list => list.filterNot(id => id === relationship.get('id')));
  148. }
  149. return state.setIn(['relationships', relationship.get('id')], relationship);
  150. };
  151. function normalizeRelationships(state, relationships) {
  152. relationships.forEach(relationship => {
  153. state = normalizeRelationship(state, relationship);
  154. });
  155. return state;
  156. };
  157. function setSelf(state, account) {
  158. state = normalizeAccount(state, account);
  159. return state.set('me', account.get('id'));
  160. };
  161. function normalizeContext(state, status, ancestors, descendants) {
  162. state = normalizeStatus(state, status);
  163. let ancestorsIds = ancestors.map(ancestor => {
  164. state = normalizeStatus(state, ancestor);
  165. return ancestor.get('id');
  166. }).toOrderedSet();
  167. let descendantsIds = descendants.map(descendant => {
  168. state = normalizeStatus(state, descendant);
  169. return descendant.get('id');
  170. }).toOrderedSet();
  171. return state.withMutations(map => {
  172. map.setIn(['ancestors', status.get('id')], ancestorsIds);
  173. map.setIn(['descendants', status.get('id')], descendantsIds);
  174. });
  175. };
  176. function normalizeAccounts(state, accounts) {
  177. accounts.forEach(account => {
  178. state = state.setIn(['accounts', account.get('id')], account);
  179. });
  180. return state;
  181. };
  182. export default function timelines(state = initialState, action) {
  183. switch(action.type) {
  184. case TIMELINE_REFRESH_SUCCESS:
  185. return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.replace);
  186. case TIMELINE_EXPAND_SUCCESS:
  187. return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
  188. case TIMELINE_UPDATE:
  189. return updateTimeline(state, action.timeline, Immutable.fromJS(action.status));
  190. case TIMELINE_DELETE:
  191. case STATUS_DELETE_SUCCESS:
  192. return deleteStatus(state, action.id);
  193. case REBLOG_SUCCESS:
  194. case FAVOURITE_SUCCESS:
  195. case UNREBLOG_SUCCESS:
  196. case UNFAVOURITE_SUCCESS:
  197. return normalizeStatus(state, Immutable.fromJS(action.response));
  198. case ACCOUNT_SET_SELF:
  199. return setSelf(state, Immutable.fromJS(action.account));
  200. case ACCOUNT_FETCH_SUCCESS:
  201. case FOLLOW_SUBMIT_SUCCESS:
  202. return normalizeAccount(state, Immutable.fromJS(action.account), Immutable.fromJS(action.relationship));
  203. case ACCOUNT_FOLLOW_SUCCESS:
  204. case ACCOUNT_UNFOLLOW_SUCCESS:
  205. case ACCOUNT_UNBLOCK_SUCCESS:
  206. case ACCOUNT_BLOCK_SUCCESS:
  207. return normalizeRelationship(state, Immutable.fromJS(action.relationship));
  208. case STATUS_FETCH_SUCCESS:
  209. return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
  210. case ACCOUNT_TIMELINE_FETCH_SUCCESS:
  211. return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
  212. case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
  213. return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
  214. case SUGGESTIONS_FETCH_SUCCESS:
  215. case FOLLOWERS_FETCH_SUCCESS:
  216. case FOLLOWING_FETCH_SUCCESS:
  217. return normalizeAccounts(state, Immutable.fromJS(action.accounts));
  218. case RELATIONSHIPS_FETCH_SUCCESS:
  219. return normalizeRelationships(state, Immutable.fromJS(action.relationships));
  220. default:
  221. return state;
  222. }
  223. };