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.

287 lines
9.0 KiB

7 years ago
7 years ago
  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. } from '../actions/timelines';
  12. import {
  13. REBLOG_SUCCESS,
  14. UNREBLOG_SUCCESS,
  15. FAVOURITE_SUCCESS,
  16. UNFAVOURITE_SUCCESS
  17. } from '../actions/interactions';
  18. import {
  19. ACCOUNT_TIMELINE_FETCH_REQUEST,
  20. ACCOUNT_TIMELINE_FETCH_SUCCESS,
  21. ACCOUNT_TIMELINE_FETCH_FAIL,
  22. ACCOUNT_TIMELINE_EXPAND_REQUEST,
  23. ACCOUNT_TIMELINE_EXPAND_SUCCESS,
  24. ACCOUNT_TIMELINE_EXPAND_FAIL,
  25. ACCOUNT_BLOCK_SUCCESS
  26. } from '../actions/accounts';
  27. import {
  28. CONTEXT_FETCH_SUCCESS
  29. } from '../actions/statuses';
  30. import Immutable from 'immutable';
  31. const initialState = Immutable.Map({
  32. home: Immutable.Map({
  33. path: () => '/api/v1/timelines/home',
  34. next: null,
  35. isLoading: false,
  36. loaded: false,
  37. top: true,
  38. items: Immutable.List()
  39. }),
  40. public: Immutable.Map({
  41. path: () => '/api/v1/timelines/public',
  42. next: null,
  43. isLoading: false,
  44. loaded: false,
  45. top: true,
  46. items: Immutable.List()
  47. }),
  48. community: Immutable.Map({
  49. path: () => '/api/v1/timelines/public',
  50. next: null,
  51. params: { local: true },
  52. isLoading: false,
  53. loaded: false,
  54. top: true,
  55. items: Immutable.List()
  56. }),
  57. tag: Immutable.Map({
  58. path: (id) => `/api/v1/timelines/tag/${id}`,
  59. next: null,
  60. isLoading: false,
  61. id: null,
  62. loaded: false,
  63. top: true,
  64. items: Immutable.List()
  65. }),
  66. accounts_timelines: Immutable.Map(),
  67. ancestors: Immutable.Map(),
  68. descendants: Immutable.Map()
  69. });
  70. const normalizeStatus = (state, status) => {
  71. const replyToId = status.get('in_reply_to_id');
  72. const id = status.get('id');
  73. if (replyToId) {
  74. if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) {
  75. state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id));
  76. }
  77. if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
  78. state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
  79. }
  80. }
  81. return state;
  82. };
  83. const normalizeTimeline = (state, timeline, statuses, next) => {
  84. let ids = Immutable.List();
  85. const loaded = state.getIn([timeline, 'loaded']);
  86. statuses.forEach((status, i) => {
  87. state = normalizeStatus(state, status);
  88. ids = ids.set(i, status.get('id'));
  89. });
  90. state = state.setIn([timeline, 'loaded'], true);
  91. state = state.setIn([timeline, 'isLoading'], false);
  92. if (state.getIn([timeline, 'next']) === null) {
  93. state = state.setIn([timeline, 'next'], next);
  94. }
  95. return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : ids));
  96. };
  97. const appendNormalizedTimeline = (state, timeline, statuses, next) => {
  98. let moreIds = Immutable.List();
  99. statuses.forEach((status, i) => {
  100. state = normalizeStatus(state, status);
  101. moreIds = moreIds.set(i, status.get('id'));
  102. });
  103. state = state.setIn([timeline, 'isLoading'], false);
  104. state = state.setIn([timeline, 'next'], next);
  105. return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds));
  106. };
  107. const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
  108. let ids = Immutable.List();
  109. statuses.forEach((status, i) => {
  110. state = normalizeStatus(state, status);
  111. ids = ids.set(i, status.get('id'));
  112. });
  113. return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
  114. .set('isLoading', false)
  115. .set('loaded', true)
  116. .update('items', Immutable.List(), list => (replace ? ids : list.unshift(...ids))));
  117. };
  118. const appendNormalizedAccountTimeline = (state, accountId, statuses) => {
  119. let moreIds = Immutable.List([]);
  120. statuses.forEach((status, i) => {
  121. state = normalizeStatus(state, status);
  122. moreIds = moreIds.set(i, status.get('id'));
  123. });
  124. return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
  125. .set('isLoading', false)
  126. .update('items', list => list.push(...moreIds)));
  127. };
  128. const updateTimeline = (state, timeline, status, references) => {
  129. const top = state.getIn([timeline, 'top']);
  130. state = normalizeStatus(state, status);
  131. state = state.updateIn([timeline, 'items'], Immutable.List(), list => {
  132. if (top && list.size > 40) {
  133. list = list.take(20);
  134. }
  135. if (list.includes(status.get('id'))) {
  136. return list;
  137. }
  138. const reblogOfId = status.getIn(['reblog', 'id'], null);
  139. if (reblogOfId !== null) {
  140. list = list.filterNot(itemId => references.includes(itemId));
  141. }
  142. return list.unshift(status.get('id'));
  143. });
  144. return state;
  145. };
  146. const deleteStatus = (state, id, accountId, references, reblogOf) => {
  147. if (reblogOf) {
  148. // If we are deleting a reblog, just replace reblog with its original
  149. return state.updateIn(['home', 'items'], list => list.map(item => item === id ? reblogOf : item));
  150. }
  151. // Remove references from timelines
  152. ['home', 'public', 'community', 'tag'].forEach(function (timeline) {
  153. state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
  154. });
  155. // Remove references from account timelines
  156. state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
  157. // Remove references from context
  158. state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
  159. state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
  160. });
  161. state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => {
  162. state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
  163. });
  164. state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
  165. // Remove reblogs of deleted status
  166. references.forEach(ref => {
  167. state = deleteStatus(state, ref[0], ref[1], []);
  168. });
  169. return state;
  170. };
  171. const filterTimelines = (state, relationship, statuses) => {
  172. let references;
  173. statuses.forEach(status => {
  174. if (status.get('account') !== relationship.id) {
  175. return;
  176. }
  177. references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
  178. state = deleteStatus(state, status.get('id'), status.get('account'), references);
  179. });
  180. return state;
  181. };
  182. const normalizeContext = (state, id, ancestors, descendants) => {
  183. const ancestorsIds = ancestors.map(ancestor => ancestor.get('id'));
  184. const descendantsIds = descendants.map(descendant => descendant.get('id'));
  185. return state.withMutations(map => {
  186. map.setIn(['ancestors', id], ancestorsIds);
  187. map.setIn(['descendants', id], descendantsIds);
  188. });
  189. };
  190. const resetTimeline = (state, timeline, id) => {
  191. if (timeline === 'tag' && typeof id !== 'undefined' && state.getIn([timeline, 'id']) !== id) {
  192. state = state.update(timeline, map => map
  193. .set('id', id)
  194. .set('isLoading', true)
  195. .set('loaded', false)
  196. .set('next', null)
  197. .update('items', list => list.clear()));
  198. } else {
  199. state = state.setIn([timeline, 'isLoading'], true);
  200. }
  201. return state;
  202. };
  203. export default function timelines(state = initialState, action) {
  204. switch(action.type) {
  205. case TIMELINE_REFRESH_REQUEST:
  206. case TIMELINE_EXPAND_REQUEST:
  207. return resetTimeline(state, action.timeline, action.id);
  208. case TIMELINE_REFRESH_FAIL:
  209. case TIMELINE_EXPAND_FAIL:
  210. return state.setIn([action.timeline, 'isLoading'], false);
  211. case TIMELINE_REFRESH_SUCCESS:
  212. return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
  213. case TIMELINE_EXPAND_SUCCESS:
  214. return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
  215. case TIMELINE_UPDATE:
  216. return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
  217. case TIMELINE_DELETE:
  218. return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
  219. case CONTEXT_FETCH_SUCCESS:
  220. return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants));
  221. case ACCOUNT_TIMELINE_FETCH_REQUEST:
  222. case ACCOUNT_TIMELINE_EXPAND_REQUEST:
  223. return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true));
  224. case ACCOUNT_TIMELINE_FETCH_FAIL:
  225. case ACCOUNT_TIMELINE_EXPAND_FAIL:
  226. return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', false));
  227. case ACCOUNT_TIMELINE_FETCH_SUCCESS:
  228. return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
  229. case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
  230. return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
  231. case ACCOUNT_BLOCK_SUCCESS:
  232. return filterTimelines(state, action.relationship, action.statuses);
  233. case TIMELINE_SCROLL_TOP:
  234. return state.setIn([action.timeline, 'top'], action.top);
  235. default:
  236. return state;
  237. }
  238. };