The code powering m.abunchtell.com https://m.abunchtell.com
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

452 рядки
12 KiB

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