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.

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