diff options
Diffstat (limited to 'app/javascript/mastodon/actions')
-rw-r--r-- | app/javascript/mastodon/actions/compose.js | 127 | ||||
-rw-r--r-- | app/javascript/mastodon/actions/markers.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/actions/picture_in_picture.js | 1 | ||||
-rw-r--r-- | app/javascript/mastodon/actions/push_notifications/registerer.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/actions/search.js | 45 | ||||
-rw-r--r-- | app/javascript/mastodon/actions/server.js | 27 | ||||
-rw-r--r-- | app/javascript/mastodon/actions/streaming.js | 20 | ||||
-rw-r--r-- | app/javascript/mastodon/actions/tags.js | 82 |
8 files changed, 253 insertions, 53 deletions
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 12d25490b..e1db44359 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -4,7 +4,6 @@ import { defineMessages } from 'react-intl'; import api from 'mastodon/api'; import { search as emojiSearch } from 'mastodon/features/emoji/emoji_mart_search_light'; import { tagHistory } from 'mastodon/settings'; -import resizeImage from 'mastodon/utils/resize_image'; import { showAlert, showAlertForError } from './alerts'; import { useEmoji } from './emojis'; import { importFetchedAccounts, importFetchedStatus } from './importer'; @@ -160,6 +159,26 @@ export function submitCompose(routerHistory) { dispatch(submitComposeRequest()); + // If we're editing a post with media attachments, those have not + // necessarily been changed on the server. Do it now in the same + // API call. + let media_attributes; + if (statusId !== null) { + media_attributes = media.map(item => { + let focus; + + if (item.getIn(['meta', 'focus'])) { + focus = `${item.getIn(['meta', 'focus', 'x']).toFixed(2)},${item.getIn(['meta', 'focus', 'y']).toFixed(2)}`; + } + + return { + id: item.get('id'), + description: item.get('description'), + focus, + }; + }); + } + api(getState).request({ url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`, method: statusId === null ? 'post' : 'put', @@ -167,6 +186,7 @@ export function submitCompose(routerHistory) { status, in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: media.map(item => item.get('id')), + media_attributes, sensitive: getState().getIn(['compose', 'sensitive']), spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '', visibility: getState().getIn(['compose', 'privacy']), @@ -255,46 +275,42 @@ export function uploadCompose(files) { dispatch(uploadComposeRequest()); - for (const [i, f] of Array.from(files).entries()) { + for (const [i, file] of Array.from(files).entries()) { if (media.size + i > 3) break; - resizeImage(f).then(file => { - const data = new FormData(); - data.append('file', file); - // Account for disparity in size of original image and resized data - total += file.size - f.size; - - return api(getState).post('/api/v2/media', data, { - onUploadProgress: function({ loaded }){ - progress[i] = loaded; - dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); - }, - }).then(({ status, data }) => { - // If server-side processing of the media attachment has not completed yet, - // poll the server until it is, before showing the media attachment as uploaded - - if (status === 200) { - dispatch(uploadComposeSuccess(data, f)); - } else if (status === 202) { - dispatch(uploadComposeProcessing()); - - let tryCount = 1; - - const poll = () => { - api(getState).get(`/api/v1/media/${data.id}`).then(response => { - if (response.status === 200) { - dispatch(uploadComposeSuccess(response.data, f)); - } else if (response.status === 206) { - const retryAfter = (Math.log2(tryCount) || 1) * 1000; - tryCount += 1; - setTimeout(() => poll(), retryAfter); - } - }).catch(error => dispatch(uploadComposeFail(error))); - }; - - poll(); - } - }); + const data = new FormData(); + data.append('file', file); + + api(getState).post('/api/v2/media', data, { + onUploadProgress: function({ loaded }){ + progress[i] = loaded; + dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); + }, + }).then(({ status, data }) => { + // If server-side processing of the media attachment has not completed yet, + // poll the server until it is, before showing the media attachment as uploaded + + if (status === 200) { + dispatch(uploadComposeSuccess(data, file)); + } else if (status === 202) { + dispatch(uploadComposeProcessing()); + + let tryCount = 1; + + const poll = () => { + api(getState).get(`/api/v1/media/${data.id}`).then(response => { + if (response.status === 200) { + dispatch(uploadComposeSuccess(response.data, file)); + } else if (response.status === 206) { + const retryAfter = (Math.log2(tryCount) || 1) * 1000; + tryCount += 1; + setTimeout(() => poll(), retryAfter); + } + }).catch(error => dispatch(uploadComposeFail(error))); + }; + + poll(); + } }).catch(error => dispatch(uploadComposeFail(error))); } }; @@ -377,11 +393,31 @@ export function changeUploadCompose(id, params) { return (dispatch, getState) => { dispatch(changeUploadComposeRequest()); - api(getState).put(`/api/v1/media/${id}`, params).then(response => { - dispatch(changeUploadComposeSuccess(response.data)); - }).catch(error => { - dispatch(changeUploadComposeFail(id, error)); - }); + let media = getState().getIn(['compose', 'media_attachments']).find((item) => item.get('id') === id); + + // Editing already-attached media is deferred to editing the post itself. + // For simplicity's sake, fake an API reply. + if (media && !media.get('unattached')) { + let { description, focus } = params; + const data = media.toJS(); + + if (description) { + data.description = description; + } + + if (focus) { + focus = focus.split(','); + data.meta = { focus: { x: parseFloat(focus[0]), y: parseFloat(focus[1]) } }; + } + + dispatch(changeUploadComposeSuccess(data, true)); + } else { + api(getState).put(`/api/v1/media/${id}`, params).then(response => { + dispatch(changeUploadComposeSuccess(response.data, false)); + }).catch(error => { + dispatch(changeUploadComposeFail(id, error)); + }); + } }; } @@ -392,10 +428,11 @@ export function changeUploadComposeRequest() { }; } -export function changeUploadComposeSuccess(media) { +export function changeUploadComposeSuccess(media, attached) { return { type: COMPOSE_UPLOAD_CHANGE_SUCCESS, media: media, + attached: attached, skipLoading: true, }; } diff --git a/app/javascript/mastodon/actions/markers.js b/app/javascript/mastodon/actions/markers.js index 16ec7fe77..ca246dce7 100644 --- a/app/javascript/mastodon/actions/markers.js +++ b/app/javascript/mastodon/actions/markers.js @@ -55,7 +55,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => { client.open('POST', '/api/v1/markers', false); client.setRequestHeader('Content-Type', 'application/json'); client.setRequestHeader('Authorization', `Bearer ${accessToken}`); - client.SUBMIT(JSON.stringify(params)); + client.send(JSON.stringify(params)); } catch (e) { // Do not make the BeforeUnload handler error out } diff --git a/app/javascript/mastodon/actions/picture_in_picture.js b/app/javascript/mastodon/actions/picture_in_picture.js index 33d8d57d4..6b9ff7709 100644 --- a/app/javascript/mastodon/actions/picture_in_picture.js +++ b/app/javascript/mastodon/actions/picture_in_picture.js @@ -23,6 +23,7 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE'; * @return {object} */ export const deployPictureInPicture = (statusId, accountId, playerType, props) => { + // @ts-expect-error return (dispatch, getState) => { // Do not open a player for a toot that does not exist if (getState().hasIn(['statuses', statusId])) { diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js index b0f42b6a2..b491f85c2 100644 --- a/app/javascript/mastodon/actions/push_notifications/registerer.js +++ b/app/javascript/mastodon/actions/push_notifications/registerer.js @@ -8,7 +8,7 @@ import { me } from '../../initial_state'; const urlBase64ToUint8Array = (base64String) => { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) - .replace(/\-/g, '+') + .replace(/-/g, '+') .replace(/_/g, '/'); return decodeBase64(base64); diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 666c6c223..56608f28b 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -14,6 +14,9 @@ export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST'; export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS'; export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; +export const SEARCH_RESULT_CLICK = 'SEARCH_RESULT_CLICK'; +export const SEARCH_RESULT_FORGET = 'SEARCH_RESULT_FORGET'; + export function changeSearch(value) { return { type: SEARCH_CHANGE, @@ -27,7 +30,7 @@ export function clearSearch() { }; } -export function submitSearch() { +export function submitSearch(type) { return (dispatch, getState) => { const value = getState().getIn(['search', 'value']); const signedIn = !!getState().getIn(['meta', 'me']); @@ -44,6 +47,7 @@ export function submitSearch() { q: value, resolve: signedIn, limit: 5, + type, }, }).then(response => { if (response.data.accounts) { @@ -130,3 +134,42 @@ export const expandSearchFail = error => ({ export const showSearch = () => ({ type: SEARCH_SHOW, }); + +export const openURL = routerHistory => (dispatch, getState) => { + const value = getState().getIn(['search', 'value']); + const signedIn = !!getState().getIn(['meta', 'me']); + + if (!signedIn) { + return; + } + + dispatch(fetchSearchRequest()); + + api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => { + if (response.data.accounts?.length > 0) { + dispatch(importFetchedAccounts(response.data.accounts)); + routerHistory.push(`/@${response.data.accounts[0].acct}`); + } else if (response.data.statuses?.length > 0) { + dispatch(importFetchedStatuses(response.data.statuses)); + routerHistory.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`); + } + + dispatch(fetchSearchSuccess(response.data, value)); + }).catch(err => { + dispatch(fetchSearchFail(err)); + }); +}; + +export const clickSearchResult = (q, type) => ({ + type: SEARCH_RESULT_CLICK, + + result: { + type, + q, + }, +}); + +export const forgetSearchResult = q => ({ + type: SEARCH_RESULT_FORGET, + q, +}); diff --git a/app/javascript/mastodon/actions/server.js b/app/javascript/mastodon/actions/server.js index 31d4aea10..091af0f0f 100644 --- a/app/javascript/mastodon/actions/server.js +++ b/app/javascript/mastodon/actions/server.js @@ -5,6 +5,10 @@ export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST'; export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS'; export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL'; +export const SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST = 'SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST'; +export const SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS = 'SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS'; +export const SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL = 'SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL'; + export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST'; export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS'; export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL'; @@ -37,6 +41,29 @@ const fetchServerFail = error => ({ error, }); +export const fetchServerTranslationLanguages = () => (dispatch, getState) => { + dispatch(fetchServerTranslationLanguagesRequest()); + + api(getState) + .get('/api/v1/instance/translation_languages').then(({ data }) => { + dispatch(fetchServerTranslationLanguagesSuccess(data)); + }).catch(err => dispatch(fetchServerTranslationLanguagesFail(err))); +}; + +const fetchServerTranslationLanguagesRequest = () => ({ + type: SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST, +}); + +const fetchServerTranslationLanguagesSuccess = translationLanguages => ({ + type: SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS, + translationLanguages, +}); + +const fetchServerTranslationLanguagesFail = error => ({ + type: SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL, + error, +}); + export const fetchExtendedDescription = () => (dispatch, getState) => { dispatch(fetchExtendedDescriptionRequest()); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 84709083f..4d4ea83e4 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -46,6 +46,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti connectStream(channelName, params, (dispatch, getState) => { const locale = getState().getIn(['meta', 'locale']); + // @ts-expect-error let pollingId; /** @@ -61,9 +62,10 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti onConnect() { dispatch(connectTimeline(timelineId)); + // @ts-expect-error if (pollingId) { - clearTimeout(pollingId); - pollingId = null; + // @ts-ignore + clearTimeout(pollingId); pollingId = null; } if (options.fillGaps) { @@ -75,31 +77,38 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti dispatch(disconnectTimeline(timelineId)); if (options.fallback) { + // @ts-expect-error pollingId = setTimeout(() => useFallback(options.fallback), randomUpTo(40000)); } }, - onReceive (data) { - switch(data.event) { + onReceive(data) { + switch (data.event) { case 'update': + // @ts-expect-error dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept)); break; case 'status.update': + // @ts-expect-error dispatch(updateStatus(JSON.parse(data.payload))); break; case 'delete': dispatch(deleteFromTimelines(data.payload)); break; case 'notification': + // @ts-expect-error dispatch(updateNotifications(JSON.parse(data.payload), messages, locale)); break; case 'conversation': + // @ts-expect-error dispatch(updateConversations(JSON.parse(data.payload))); break; case 'announcement': + // @ts-expect-error dispatch(updateAnnouncements(JSON.parse(data.payload))); break; case 'announcement.reaction': + // @ts-expect-error dispatch(updateAnnouncementsReaction(JSON.parse(data.payload))); break; case 'announcement.delete': @@ -115,7 +124,9 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti * @param {function(): void} done */ const refreshHomeTimelineAndNotification = (dispatch, done) => { + // @ts-expect-error dispatch(expandHomeTimeline({}, () => + // @ts-expect-error dispatch(expandNotifications({}, () => dispatch(fetchAnnouncements(done)))))); }; @@ -124,6 +135,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => { * @return {function(): void} */ export const connectUserStream = () => + // @ts-expect-error connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps }); /** diff --git a/app/javascript/mastodon/actions/tags.js b/app/javascript/mastodon/actions/tags.js index 37e79d4cb..dda8c924b 100644 --- a/app/javascript/mastodon/actions/tags.js +++ b/app/javascript/mastodon/actions/tags.js @@ -1,9 +1,17 @@ -import api from '../api'; +import api, { getLinks } from '../api'; export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST'; export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS'; export const HASHTAG_FETCH_FAIL = 'HASHTAG_FETCH_FAIL'; +export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST'; +export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS'; +export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL'; + +export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST'; +export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS'; +export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL'; + export const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST'; export const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS'; export const HASHTAG_FOLLOW_FAIL = 'HASHTAG_FOLLOW_FAIL'; @@ -37,6 +45,78 @@ export const fetchHashtagFail = error => ({ error, }); +export const fetchFollowedHashtags = () => (dispatch, getState) => { + dispatch(fetchFollowedHashtagsRequest()); + + api(getState).get('/api/v1/followed_tags').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null)); + }).catch(err => { + dispatch(fetchFollowedHashtagsFail(err)); + }); +}; + +export function fetchFollowedHashtagsRequest() { + return { + type: FOLLOWED_HASHTAGS_FETCH_REQUEST, + }; +} + +export function fetchFollowedHashtagsSuccess(followed_tags, next) { + return { + type: FOLLOWED_HASHTAGS_FETCH_SUCCESS, + followed_tags, + next, + }; +} + +export function fetchFollowedHashtagsFail(error) { + return { + type: FOLLOWED_HASHTAGS_FETCH_FAIL, + error, + }; +} + +export function expandFollowedHashtags() { + return (dispatch, getState) => { + const url = getState().getIn(['followed_tags', 'next']); + + if (url === null) { + return; + } + + dispatch(expandFollowedHashtagsRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(expandFollowedHashtagsFail(error)); + }); + }; +} + +export function expandFollowedHashtagsRequest() { + return { + type: FOLLOWED_HASHTAGS_EXPAND_REQUEST, + }; +} + +export function expandFollowedHashtagsSuccess(followed_tags, next) { + return { + type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS, + followed_tags, + next, + }; +} + +export function expandFollowedHashtagsFail(error) { + return { + type: FOLLOWED_HASHTAGS_EXPAND_FAIL, + error, + }; +} + export const followHashtag = name => (dispatch, getState) => { dispatch(followHashtagRequest(name)); |