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.

457 lines
12 KiB

7 years ago
7 years ago
  1. import api from '../api';
  2. import { CancelToken } from 'axios';
  3. import { throttle } from 'lodash';
  4. import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
  5. import { tagHistory } from '../settings';
  6. import { useEmoji } from './emojis';
  7. import { importFetchedAccounts } from './importer';
  8. import { updateTimeline } from './timelines';
  9. let cancelFetchComposeSuggestionsAccounts;
  10. export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
  11. export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
  12. export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
  13. export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
  14. export const COMPOSE_REPLY = 'COMPOSE_REPLY';
  15. export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
  16. export const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
  17. export const COMPOSE_MENTION = 'COMPOSE_MENTION';
  18. export const COMPOSE_RESET = 'COMPOSE_RESET';
  19. export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
  20. export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
  21. export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
  22. export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
  23. export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
  24. export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
  25. export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
  26. export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
  27. export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
  28. export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
  29. export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
  30. export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
  31. export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
  32. export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
  33. export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
  34. export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
  35. export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
  36. export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
  37. export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
  38. export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST';
  39. export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
  40. export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
  41. export function changeCompose(text) {
  42. return {
  43. type: COMPOSE_CHANGE,
  44. text: text,
  45. };
  46. };
  47. export function replyCompose(status, router) {
  48. return (dispatch, getState) => {
  49. dispatch({
  50. type: COMPOSE_REPLY,
  51. status: status,
  52. });
  53. if (!getState().getIn(['compose', 'mounted'])) {
  54. router.push('/statuses/new');
  55. }
  56. };
  57. };
  58. export function cancelReplyCompose() {
  59. return {
  60. type: COMPOSE_REPLY_CANCEL,
  61. };
  62. };
  63. export function resetCompose() {
  64. return {
  65. type: COMPOSE_RESET,
  66. };
  67. };
  68. export function mentionCompose(account, router) {
  69. return (dispatch, getState) => {
  70. dispatch({
  71. type: COMPOSE_MENTION,
  72. account: account,
  73. });
  74. if (!getState().getIn(['compose', 'mounted'])) {
  75. router.push('/statuses/new');
  76. }
  77. };
  78. };
  79. export function directCompose(account, router) {
  80. return (dispatch, getState) => {
  81. dispatch({
  82. type: COMPOSE_DIRECT,
  83. account: account,
  84. });
  85. if (!getState().getIn(['compose', 'mounted'])) {
  86. router.push('/statuses/new');
  87. }
  88. };
  89. };
  90. export function submitCompose() {
  91. return function (dispatch, getState) {
  92. const status = getState().getIn(['compose', 'text'], '');
  93. const media = getState().getIn(['compose', 'media_attachments']);
  94. if ((!status || !status.length) && media.size === 0) {
  95. return;
  96. }
  97. dispatch(submitComposeRequest());
  98. api(getState).post('/api/v1/statuses', {
  99. status,
  100. in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
  101. media_ids: media.map(item => item.get('id')),
  102. sensitive: getState().getIn(['compose', 'sensitive']),
  103. spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
  104. visibility: getState().getIn(['compose', 'privacy']),
  105. }, {
  106. headers: {
  107. 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
  108. },
  109. }).then(function (response) {
  110. dispatch(insertIntoTagHistory(response.data.tags));
  111. dispatch(submitComposeSuccess({ ...response.data }));
  112. // To make the app more responsive, immediately get the status into the columns
  113. const insertIfOnline = (timelineId) => {
  114. if (getState().getIn(['timelines', timelineId, 'items', 0]) !== null) {
  115. dispatch(updateTimeline(timelineId, { ...response.data }));
  116. }
  117. };
  118. insertIfOnline('home');
  119. if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
  120. insertIfOnline('community');
  121. insertIfOnline('public');
  122. }
  123. }).catch(function (error) {
  124. dispatch(submitComposeFail(error));
  125. });
  126. };
  127. };
  128. export function submitComposeRequest() {
  129. return {
  130. type: COMPOSE_SUBMIT_REQUEST,
  131. };
  132. };
  133. export function submitComposeSuccess(status) {
  134. return {
  135. type: COMPOSE_SUBMIT_SUCCESS,
  136. status: status,
  137. };
  138. };
  139. export function submitComposeFail(error) {
  140. return {
  141. type: COMPOSE_SUBMIT_FAIL,
  142. error: error,
  143. };
  144. };
  145. export function uploadCompose(files) {
  146. return function (dispatch, getState) {
  147. if (getState().getIn(['compose', 'media_attachments']).size > 3) {
  148. return;
  149. }
  150. dispatch(uploadComposeRequest());
  151. let data = new FormData();
  152. data.append('file', files[0]);
  153. api(getState).post('/api/v1/media', data, {
  154. onUploadProgress: function (e) {
  155. dispatch(uploadComposeProgress(e.loaded, e.total));
  156. },
  157. }).then(function (response) {
  158. dispatch(uploadComposeSuccess(response.data));
  159. }).catch(function (error) {
  160. dispatch(uploadComposeFail(error));
  161. });
  162. };
  163. };
  164. export function changeUploadCompose(id, params) {
  165. return (dispatch, getState) => {
  166. dispatch(changeUploadComposeRequest());
  167. api(getState).put(`/api/v1/media/${id}`, params).then(response => {
  168. dispatch(changeUploadComposeSuccess(response.data));
  169. }).catch(error => {
  170. dispatch(changeUploadComposeFail(id, error));
  171. });
  172. };
  173. };
  174. export function changeUploadComposeRequest() {
  175. return {
  176. type: COMPOSE_UPLOAD_CHANGE_REQUEST,
  177. skipLoading: true,
  178. };
  179. };
  180. export function changeUploadComposeSuccess(media) {
  181. return {
  182. type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
  183. media: media,
  184. skipLoading: true,
  185. };
  186. };
  187. export function changeUploadComposeFail(error) {
  188. return {
  189. type: COMPOSE_UPLOAD_CHANGE_FAIL,
  190. error: error,
  191. skipLoading: true,
  192. };
  193. };
  194. export function uploadComposeRequest() {
  195. return {
  196. type: COMPOSE_UPLOAD_REQUEST,
  197. skipLoading: true,
  198. };
  199. };
  200. export function uploadComposeProgress(loaded, total) {
  201. return {
  202. type: COMPOSE_UPLOAD_PROGRESS,
  203. loaded: loaded,
  204. total: total,
  205. };
  206. };
  207. export function uploadComposeSuccess(media) {
  208. return {
  209. type: COMPOSE_UPLOAD_SUCCESS,
  210. media: media,
  211. skipLoading: true,
  212. };
  213. };
  214. export function uploadComposeFail(error) {
  215. return {
  216. type: COMPOSE_UPLOAD_FAIL,
  217. error: error,
  218. skipLoading: true,
  219. };
  220. };
  221. export function undoUploadCompose(media_id) {
  222. return {
  223. type: COMPOSE_UPLOAD_UNDO,
  224. media_id: media_id,
  225. };
  226. };
  227. export function clearComposeSuggestions() {
  228. if (cancelFetchComposeSuggestionsAccounts) {
  229. cancelFetchComposeSuggestionsAccounts();
  230. }
  231. return {
  232. type: COMPOSE_SUGGESTIONS_CLEAR,
  233. };
  234. };
  235. const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
  236. if (cancelFetchComposeSuggestionsAccounts) {
  237. cancelFetchComposeSuggestionsAccounts();
  238. }
  239. api(getState).get('/api/v1/accounts/search', {
  240. cancelToken: new CancelToken(cancel => {
  241. cancelFetchComposeSuggestionsAccounts = cancel;
  242. }),
  243. params: {
  244. q: token.slice(1),
  245. resolve: false,
  246. limit: 4,
  247. },
  248. }).then(response => {
  249. dispatch(importFetchedAccounts(response.data));
  250. dispatch(readyComposeSuggestionsAccounts(token, response.data));
  251. });
  252. }, 200, { leading: true, trailing: true });
  253. const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
  254. const results = emojiSearch(token.replace(':', ''), { maxResults: 5 });
  255. dispatch(readyComposeSuggestionsEmojis(token, results));
  256. };
  257. const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
  258. dispatch(updateSuggestionTags(token));
  259. };
  260. export function fetchComposeSuggestions(token) {
  261. return (dispatch, getState) => {
  262. switch (token[0]) {
  263. case ':':
  264. fetchComposeSuggestionsEmojis(dispatch, getState, token);
  265. break;
  266. case '#':
  267. fetchComposeSuggestionsTags(dispatch, getState, token);
  268. break;
  269. default:
  270. fetchComposeSuggestionsAccounts(dispatch, getState, token);
  271. break;
  272. }
  273. };
  274. };
  275. export function readyComposeSuggestionsEmojis(token, emojis) {
  276. return {
  277. type: COMPOSE_SUGGESTIONS_READY,
  278. token,
  279. emojis,
  280. };
  281. };
  282. export function readyComposeSuggestionsAccounts(token, accounts) {
  283. return {
  284. type: COMPOSE_SUGGESTIONS_READY,
  285. token,
  286. accounts,
  287. };
  288. };
  289. export function selectComposeSuggestion(position, token, suggestion) {
  290. return (dispatch, getState) => {
  291. let completion, startPosition;
  292. if (typeof suggestion === 'object' && suggestion.id) {
  293. completion = suggestion.native || suggestion.colons;
  294. startPosition = position - 1;
  295. dispatch(useEmoji(suggestion));
  296. } else if (suggestion[0] === '#') {
  297. completion = suggestion;
  298. startPosition = position - 1;
  299. } else {
  300. completion = getState().getIn(['accounts', suggestion, 'acct']);
  301. startPosition = position;
  302. }
  303. dispatch({
  304. type: COMPOSE_SUGGESTION_SELECT,
  305. position: startPosition,
  306. token,
  307. completion,
  308. });
  309. };
  310. };
  311. export function updateSuggestionTags(token) {
  312. return {
  313. type: COMPOSE_SUGGESTION_TAGS_UPDATE,
  314. token,
  315. };
  316. }
  317. export function updateTagHistory(tags) {
  318. return {
  319. type: COMPOSE_TAG_HISTORY_UPDATE,
  320. tags,
  321. };
  322. }
  323. export function hydrateCompose() {
  324. return (dispatch, getState) => {
  325. const me = getState().getIn(['meta', 'me']);
  326. const history = tagHistory.get(me);
  327. if (history !== null) {
  328. dispatch(updateTagHistory(history));
  329. }
  330. };
  331. }
  332. function insertIntoTagHistory(tags) {
  333. return (dispatch, getState) => {
  334. const state = getState();
  335. const oldHistory = state.getIn(['compose', 'tagHistory']);
  336. const me = state.getIn(['meta', 'me']);
  337. const names = tags.map(({ name }) => name);
  338. const intersectedOldHistory = oldHistory.filter(name => !names.includes(name));
  339. names.push(...intersectedOldHistory.toJS());
  340. const newHistory = names.slice(0, 1000);
  341. tagHistory.set(me, newHistory);
  342. dispatch(updateTagHistory(newHistory));
  343. };
  344. }
  345. export function mountCompose() {
  346. return {
  347. type: COMPOSE_MOUNT,
  348. };
  349. };
  350. export function unmountCompose() {
  351. return {
  352. type: COMPOSE_UNMOUNT,
  353. };
  354. };
  355. export function changeComposeSensitivity() {
  356. return {
  357. type: COMPOSE_SENSITIVITY_CHANGE,
  358. };
  359. };
  360. export function changeComposeSpoilerness() {
  361. return {
  362. type: COMPOSE_SPOILERNESS_CHANGE,
  363. };
  364. };
  365. export function changeComposeSpoilerText(text) {
  366. return {
  367. type: COMPOSE_SPOILER_TEXT_CHANGE,
  368. text,
  369. };
  370. };
  371. export function changeComposeVisibility(value) {
  372. return {
  373. type: COMPOSE_VISIBILITY_CHANGE,
  374. value,
  375. };
  376. };
  377. export function insertEmojiCompose(position, emoji) {
  378. return {
  379. type: COMPOSE_EMOJI_INSERT,
  380. position,
  381. emoji,
  382. };
  383. };
  384. export function changeComposing(value) {
  385. return {
  386. type: COMPOSE_COMPOSING_CHANGE,
  387. value,
  388. };
  389. }