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.

240 lines
7.6 KiB

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