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.

436 lines
11 KiB

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