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

350 lines
11 KiB

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