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.

409 lines
10 KiB

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