('APP_LAYOUT_CHANGE');
diff --git a/app/javascript/mastodon/actions/blocks.js b/app/javascript/mastodon/actions/blocks.js
index 192aa3ce4..e293657ad 100644
--- a/app/javascript/mastodon/actions/blocks.js
+++ b/app/javascript/mastodon/actions/blocks.js
@@ -1,4 +1,5 @@
import api, { getLinks } from '../api';
+
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { openModal } from './modal';
@@ -94,6 +95,6 @@ export function initBlockModal(account) {
account,
});
- dispatch(openModal('BLOCK'));
+ dispatch(openModal({ modalType: 'BLOCK' }));
};
}
diff --git a/app/javascript/mastodon/actions/bookmarks.js b/app/javascript/mastodon/actions/bookmarks.js
index 3c8eec546..0b16f61e6 100644
--- a/app/javascript/mastodon/actions/bookmarks.js
+++ b/app/javascript/mastodon/actions/bookmarks.js
@@ -1,4 +1,5 @@
import api, { getLinks } from '../api';
+
import { importFetchedStatuses } from './importer';
export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST';
diff --git a/app/javascript/mastodon/actions/boosts.js b/app/javascript/mastodon/actions/boosts.js
index c0f0f3acc..1fc2e391e 100644
--- a/app/javascript/mastodon/actions/boosts.js
+++ b/app/javascript/mastodon/actions/boosts.js
@@ -14,7 +14,10 @@ export function initBoostModal(props) {
privacy,
});
- dispatch(openModal('BOOST', props));
+ dispatch(openModal({
+ modalType: 'BOOST',
+ modalProps: props,
+ }));
};
}
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index e1db44359..7cc8516fd 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -1,9 +1,12 @@
+import { defineMessages } from 'react-intl';
+
import axios from 'axios';
import { throttle } from 'lodash';
-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 { showAlert, showAlertForError } from './alerts';
import { useEmoji } from './emojis';
import { importFetchedAccounts, importFetchedStatus } from './importer';
@@ -74,6 +77,7 @@ export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTIO
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
+export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
@@ -125,6 +129,15 @@ export function resetCompose() {
};
}
+export const focusCompose = (routerHistory, defaultText) => (dispatch, getState) => {
+ dispatch({
+ type: COMPOSE_FOCUS,
+ defaultText,
+ });
+
+ ensureComposeIsVisible(getState, routerHistory);
+};
+
export function mentionCompose(account, routerHistory) {
return (dispatch, getState) => {
dispatch({
@@ -370,7 +383,10 @@ export function initMediaEditModal(id) {
id,
});
- dispatch(openModal('FOCAL_POINT', { id }));
+ dispatch(openModal({
+ modalType: 'FOCAL_POINT',
+ modalProps: { id },
+ }));
};
}
@@ -398,16 +414,12 @@ export function changeUploadCompose(id, params) {
// 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;
- }
+ const { focus, ...other } = params;
+ const data = { ...media.toJS(), ...other };
if (focus) {
- focus = focus.split(',');
- data.meta = { focus: { x: parseFloat(focus[0]), y: parseFloat(focus[1]) } };
+ const [x, y] = focus.split(',');
+ data.meta = { focus: { x: parseFloat(x), y: parseFloat(y) } };
}
dispatch(changeUploadComposeSuccess(data, true));
diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js
index 4ef654b1f..8c4c4529f 100644
--- a/app/javascript/mastodon/actions/conversations.js
+++ b/app/javascript/mastodon/actions/conversations.js
@@ -1,4 +1,5 @@
import api, { getLinks } from '../api';
+
import {
importFetchedAccounts,
importFetchedStatuses,
diff --git a/app/javascript/mastodon/actions/directory.js b/app/javascript/mastodon/actions/directory.js
index 4b2b6dd56..cda63f2b5 100644
--- a/app/javascript/mastodon/actions/directory.js
+++ b/app/javascript/mastodon/actions/directory.js
@@ -1,6 +1,7 @@
import api from '../api';
-import { importFetchedAccounts } from './importer';
+
import { fetchRelationships } from './accounts';
+import { importFetchedAccounts } from './importer';
export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST';
export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS';
diff --git a/app/javascript/mastodon/actions/favourites.js b/app/javascript/mastodon/actions/favourites.js
index 7388e0c58..2d4d4e620 100644
--- a/app/javascript/mastodon/actions/favourites.js
+++ b/app/javascript/mastodon/actions/favourites.js
@@ -1,4 +1,5 @@
import api, { getLinks } from '../api';
+
import { importFetchedStatuses } from './importer';
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
diff --git a/app/javascript/mastodon/actions/filters.js b/app/javascript/mastodon/actions/filters.js
index e9c609fc8..a11956ac5 100644
--- a/app/javascript/mastodon/actions/filters.js
+++ b/app/javascript/mastodon/actions/filters.js
@@ -1,4 +1,5 @@
import api from '../api';
+
import { openModal } from './modal';
export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
@@ -14,9 +15,12 @@ export const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS';
export const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL';
export const initAddFilter = (status, { contextType }) => dispatch =>
- dispatch(openModal('FILTER', {
- statusId: status?.get('id'),
- contextType: contextType,
+ dispatch(openModal({
+ modalType: 'FILTER',
+ modalProps: {
+ statusId: status?.get('id'),
+ contextType: contextType,
+ },
}));
export const fetchFilters = () => (dispatch, getState) => {
diff --git a/app/javascript/mastodon/actions/history.js b/app/javascript/mastodon/actions/history.js
index c142aaf61..52401b7dc 100644
--- a/app/javascript/mastodon/actions/history.js
+++ b/app/javascript/mastodon/actions/history.js
@@ -1,4 +1,5 @@
import api from '../api';
+
import { importFetchedAccounts } from './importer';
export const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST';
diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index 8a22f83fa..9ed6b583b 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -1,11 +1,12 @@
import escapeTextContentForBrowser from 'escape-html';
+
import emojify from '../../features/emoji/emoji';
-import { unescapeHTML } from '../../utils/html';
import { expandSpoilers } from '../../initial_state';
+import { unescapeHTML } from '../../utils/html';
const domParser = new DOMParser();
-const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
+const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
@@ -19,7 +20,7 @@ export function searchTextFromRawStatus (status) {
export function normalizeAccount(account) {
account = { ...account };
- const emojiMap = makeEmojiMap(account);
+ const emojiMap = makeEmojiMap(account.emojis);
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
@@ -85,7 +86,7 @@ export function normalizeStatus(status, normalOldStatus) {
const spoilerText = normalStatus.spoiler_text || '';
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/
/g, '\n').replace(/<\/p>/g, '\n\n');
- const emojiMap = makeEmojiMap(normalStatus);
+ const emojiMap = makeEmojiMap(normalStatus.emojis);
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
@@ -96,22 +97,48 @@ export function normalizeStatus(status, normalOldStatus) {
return normalStatus;
}
+export function normalizeStatusTranslation(translation, status) {
+ const emojiMap = makeEmojiMap(status.get('emojis').toJS());
+
+ const normalTranslation = {
+ detected_source_language: translation.detected_source_language,
+ language: translation.language,
+ provider: translation.provider,
+ contentHtml: emojify(translation.content, emojiMap),
+ spoilerHtml: emojify(escapeTextContentForBrowser(translation.spoiler_text), emojiMap),
+ spoiler_text: translation.spoiler_text,
+ };
+
+ return normalTranslation;
+}
+
export function normalizePoll(poll) {
const normalPoll = { ...poll };
- const emojiMap = makeEmojiMap(normalPoll);
+ const emojiMap = makeEmojiMap(poll.emojis);
normalPoll.options = poll.options.map((option, index) => ({
...option,
voted: poll.own_votes && poll.own_votes.includes(index),
- title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
+ titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap),
}));
return normalPoll;
}
+export function normalizePollOptionTranslation(translation, poll) {
+ const emojiMap = makeEmojiMap(poll.get('emojis').toJS());
+
+ const normalTranslation = {
+ ...translation,
+ titleHtml: emojify(escapeTextContentForBrowser(translation.title), emojiMap),
+ };
+
+ return normalTranslation;
+}
+
export function normalizeAnnouncement(announcement) {
const normalAnnouncement = { ...announcement };
- const emojiMap = makeEmojiMap(normalAnnouncement);
+ const emojiMap = makeEmojiMap(normalAnnouncement.emojis);
normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js
index 28c728d61..1d3d27c68 100644
--- a/app/javascript/mastodon/actions/interactions.js
+++ b/app/javascript/mastodon/actions/interactions.js
@@ -1,4 +1,5 @@
import api from '../api';
+
import { importFetchedAccounts, importFetchedStatus } from './importer';
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js
index 5ab922436..b0789cd42 100644
--- a/app/javascript/mastodon/actions/lists.js
+++ b/app/javascript/mastodon/actions/lists.js
@@ -1,6 +1,7 @@
import api from '../api';
-import { importFetchedAccounts } from './importer';
+
import { showAlertForError } from './alerts';
+import { importFetchedAccounts } from './importer';
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
@@ -150,10 +151,10 @@ export const createListFail = error => ({
error,
});
-export const updateList = (id, title, shouldReset, replies_policy) => (dispatch, getState) => {
+export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => {
dispatch(updateListRequest(id));
- api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy }).then(({ data }) => {
+ api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
dispatch(updateListSuccess(data));
if (shouldReset) {
diff --git a/app/javascript/mastodon/actions/markers.js b/app/javascript/mastodon/actions/markers.js
index 16ec7fe77..cfc329a8b 100644
--- a/app/javascript/mastodon/actions/markers.js
+++ b/app/javascript/mastodon/actions/markers.js
@@ -1,8 +1,10 @@
-import api from '../api';
-import { debounce } from 'lodash';
-import compareId from '../compare_id';
import { List as ImmutableList } from 'immutable';
+import { debounce } from 'lodash';
+
+import api from '../api';
+import { compareId } from '../compare_id';
+
export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS';
export const MARKERS_FETCH_FAIL = 'MARKERS_FETCH_FAIL';
@@ -55,7 +57,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/modal.js b/app/javascript/mastodon/actions/modal.js
deleted file mode 100644
index ef2ae0e4c..000000000
--- a/app/javascript/mastodon/actions/modal.js
+++ /dev/null
@@ -1,18 +0,0 @@
-export const MODAL_OPEN = 'MODAL_OPEN';
-export const MODAL_CLOSE = 'MODAL_CLOSE';
-
-export function openModal(type, props) {
- return {
- type: MODAL_OPEN,
- modalType: type,
- modalProps: props,
- };
-}
-
-export function closeModal(type, options = { ignoreFocus: false }) {
- return {
- type: MODAL_CLOSE,
- modalType: type,
- ignoreFocus: options.ignoreFocus,
- };
-}
diff --git a/app/javascript/mastodon/actions/modal.ts b/app/javascript/mastodon/actions/modal.ts
new file mode 100644
index 000000000..af34f5d6a
--- /dev/null
+++ b/app/javascript/mastodon/actions/modal.ts
@@ -0,0 +1,17 @@
+import { createAction } from '@reduxjs/toolkit';
+
+import type { MODAL_COMPONENTS } from '../features/ui/components/modal_root';
+
+export type ModalType = keyof typeof MODAL_COMPONENTS;
+
+interface OpenModalPayload {
+ modalType: ModalType;
+ modalProps: unknown;
+}
+export const openModal = createAction('MODAL_OPEN');
+
+interface CloseModalPayload {
+ modalType: ModalType | undefined;
+ ignoreFocus: boolean;
+}
+export const closeModal = createAction('MODAL_CLOSE');
diff --git a/app/javascript/mastodon/actions/mutes.js b/app/javascript/mastodon/actions/mutes.js
index cbc42a67e..fb041078b 100644
--- a/app/javascript/mastodon/actions/mutes.js
+++ b/app/javascript/mastodon/actions/mutes.js
@@ -1,4 +1,5 @@
import api, { getLinks } from '../api';
+
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { openModal } from './modal';
@@ -96,7 +97,7 @@ export function initMuteModal(account) {
account,
});
- dispatch(openModal('MUTE'));
+ dispatch(openModal({ modalType: 'MUTE' }));
};
}
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 6834f0189..f8163e283 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -1,5 +1,15 @@
+import { IntlMessageFormat } from 'intl-messageformat';
+import { defineMessages } from 'react-intl';
+
+import { List as ImmutableList } from 'immutable';
+
+import { compareId } from 'mastodon/compare_id';
+import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
+
import api, { getLinks } from '../api';
-import IntlMessageFormat from 'intl-messageformat';
+import { unescapeHTML } from '../utils/html';
+import { requestNotificationPermission } from '../utils/notifications';
+
import { fetchFollowRequests, fetchRelationships } from './accounts';
import {
importFetchedAccount,
@@ -9,12 +19,6 @@ import {
} from './importer';
import { submitMarkers } from './markers';
import { saveSettings } from './settings';
-import { defineMessages } from 'react-intl';
-import { List as ImmutableList } from 'immutable';
-import { unescapeHTML } from '../utils/html';
-import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
-import compareId from 'mastodon/compare_id';
-import { requestNotificationPermission } from '../utils/notifications';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
diff --git a/app/javascript/mastodon/actions/picture_in_picture.js b/app/javascript/mastodon/actions/picture_in_picture.js
index 6b9ff7709..898375abe 100644
--- a/app/javascript/mastodon/actions/picture_in_picture.js
+++ b/app/javascript/mastodon/actions/picture_in_picture.js
@@ -20,7 +20,7 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
* @param {string} accountId
* @param {string} playerType
* @param {MediaProps} props
- * @return {object}
+ * @returns {object}
*/
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
// @ts-expect-error
diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js
index e2de98ca9..baa10d156 100644
--- a/app/javascript/mastodon/actions/pin_statuses.js
+++ b/app/javascript/mastodon/actions/pin_statuses.js
@@ -1,12 +1,12 @@
import api from '../api';
+import { me } from '../initial_state';
+
import { importFetchedStatuses } from './importer';
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
-import { me } from '../initial_state';
-
export function fetchPinnedStatuses() {
return (dispatch, getState) => {
dispatch(fetchPinnedStatusesRequest());
diff --git a/app/javascript/mastodon/actions/polls.js b/app/javascript/mastodon/actions/polls.js
index 8e8b82df5..a37410dc9 100644
--- a/app/javascript/mastodon/actions/polls.js
+++ b/app/javascript/mastodon/actions/polls.js
@@ -1,4 +1,5 @@
import api from '../api';
+
import { importFetchedPoll } from './importer';
export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST';
diff --git a/app/javascript/mastodon/actions/push_notifications/index.js b/app/javascript/mastodon/actions/push_notifications/index.js
index 9dcc4bd4b..46b63867f 100644
--- a/app/javascript/mastodon/actions/push_notifications/index.js
+++ b/app/javascript/mastodon/actions/push_notifications/index.js
@@ -1,5 +1,5 @@
-import { setAlerts } from './setter';
import { saveSettings } from './registerer';
+import { setAlerts } from './setter';
export function changeAlerts(path, value) {
return dispatch => {
diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js
index b491f85c2..b3d3850e3 100644
--- a/app/javascript/mastodon/actions/push_notifications/registerer.js
+++ b/app/javascript/mastodon/actions/push_notifications/registerer.js
@@ -1,8 +1,9 @@
import api from '../../api';
-import { decode as decodeBase64 } from '../../utils/base64';
-import { pushNotificationsSetting } from '../../settings';
-import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
import { me } from '../../initial_state';
+import { pushNotificationsSetting } from '../../settings';
+import { decode as decodeBase64 } from '../../utils/base64';
+
+import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
// Taken from https://www.npmjs.com/package/web-push
const urlBase64ToUint8Array = (base64String) => {
diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js
index fbe5b3791..756b8cd05 100644
--- a/app/javascript/mastodon/actions/reports.js
+++ b/app/javascript/mastodon/actions/reports.js
@@ -1,4 +1,5 @@
import api from '../api';
+
import { openModal } from './modal';
export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
@@ -6,9 +7,12 @@ export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
export const initReport = (account, status) => dispatch =>
- dispatch(openModal('REPORT', {
- accountId: account.get('id'),
- statusId: status?.get('id'),
+ dispatch(openModal({
+ modalType: 'REPORT',
+ modalProps: {
+ accountId: account.get('id'),
+ statusId: status?.get('id'),
+ },
}));
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {
diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js
index 56608f28b..94e7f2ed7 100644
--- a/app/javascript/mastodon/actions/search.js
+++ b/app/javascript/mastodon/actions/search.js
@@ -1,4 +1,5 @@
import api from '../api';
+
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatuses } from './importer';
@@ -135,8 +136,7 @@ export const showSearch = () => ({
type: SEARCH_SHOW,
});
-export const openURL = routerHistory => (dispatch, getState) => {
- const value = getState().getIn(['search', 'value']);
+export const openURL = (value, history, onFailure) => (dispatch, getState) => {
const signedIn = !!getState().getIn(['meta', 'me']);
if (!signedIn) {
@@ -148,15 +148,21 @@ export const openURL = routerHistory => (dispatch, getState) => {
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}`);
+ history.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}`);
+ history.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`);
+ } else if (onFailure) {
+ onFailure();
}
dispatch(fetchSearchSuccess(response.data, value));
}).catch(err => {
dispatch(fetchSearchFail(err));
+
+ if (onFailure) {
+ onFailure();
+ }
});
};
diff --git a/app/javascript/mastodon/actions/server.js b/app/javascript/mastodon/actions/server.js
index 091af0f0f..65f3efc3a 100644
--- a/app/javascript/mastodon/actions/server.js
+++ b/app/javascript/mastodon/actions/server.js
@@ -1,4 +1,5 @@
import api from '../api';
+
import { importFetchedAccount } from './importer';
export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST';
@@ -18,6 +19,10 @@ export const SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS = 'SERVER_DOMAIN_BLOCKS_FETCH_SU
export const SERVER_DOMAIN_BLOCKS_FETCH_FAIL = 'SERVER_DOMAIN_BLOCKS_FETCH_FAIL';
export const fetchServer = () => (dispatch, getState) => {
+ if (getState().getIn(['server', 'server', 'isLoading'])) {
+ return;
+ }
+
dispatch(fetchServerRequest());
api(getState)
@@ -65,6 +70,10 @@ const fetchServerTranslationLanguagesFail = error => ({
});
export const fetchExtendedDescription = () => (dispatch, getState) => {
+ if (getState().getIn(['server', 'extendedDescription', 'isLoading'])) {
+ return;
+ }
+
dispatch(fetchExtendedDescriptionRequest());
api(getState)
@@ -88,6 +97,10 @@ const fetchExtendedDescriptionFail = error => ({
});
export const fetchDomainBlocks = () => (dispatch, getState) => {
+ if (getState().getIn(['server', 'domainBlocks', 'isLoading'])) {
+ return;
+ }
+
dispatch(fetchDomainBlocksRequest());
api(getState)
diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js
index 6ae001b6f..3685b0684 100644
--- a/app/javascript/mastodon/actions/settings.js
+++ b/app/javascript/mastodon/actions/settings.js
@@ -1,5 +1,7 @@
-import api from '../api';
import { debounce } from 'lodash';
+
+import api from '../api';
+
import { showAlertForError } from './alerts';
export const SETTING_CHANGE = 'SETTING_CHANGE';
diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js
index 275280a53..3aed80735 100644
--- a/app/javascript/mastodon/actions/statuses.js
+++ b/app/javascript/mastodon/actions/statuses.js
@@ -1,8 +1,8 @@
import api from '../api';
-import { deleteFromTimelines } from './timelines';
-import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
+import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
+import { deleteFromTimelines } from './timelines';
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
@@ -343,7 +343,8 @@ export const translateStatusFail = (id, error) => ({
error,
});
-export const undoStatusTranslation = id => ({
+export const undoStatusTranslation = (id, pollId) => ({
type: STATUS_TRANSLATE_UNDO,
id,
+ pollId,
});
diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js
index b3030467b..6b0743439 100644
--- a/app/javascript/mastodon/actions/store.js
+++ b/app/javascript/mastodon/actions/store.js
@@ -1,4 +1,5 @@
import { Iterable, fromJS } from 'immutable';
+
import { hydrateCompose } from './compose';
import { importFetchedAccounts } from './importer';
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index 4d4ea83e4..9daeb3c60 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -1,6 +1,17 @@
// @ts-check
+import { getLocale } from '../locales';
import { connectStream } from '../stream';
+
+import {
+ fetchAnnouncements,
+ updateAnnouncements,
+ updateReaction as updateAnnouncementsReaction,
+ deleteAnnouncement,
+} from './announcements';
+import { updateConversations } from './conversations';
+import { updateNotifications, expandNotifications } from './notifications';
+import { updateStatus } from './statuses';
import {
updateTimeline,
deleteFromTimelines,
@@ -12,22 +23,10 @@ import {
fillCommunityTimelineGaps,
fillListTimelineGaps,
} from './timelines';
-import { updateNotifications, expandNotifications } from './notifications';
-import { updateConversations } from './conversations';
-import { updateStatus } from './statuses';
-import {
- fetchAnnouncements,
- updateAnnouncements,
- updateReaction as updateAnnouncementsReaction,
- deleteAnnouncement,
-} from './announcements';
-import { getLocale } from '../locales';
-
-const { messages } = getLocale();
/**
* @param {number} max
- * @return {number}
+ * @returns {number}
*/
const randomUpTo = max =>
Math.floor(Math.random() * Math.floor(max));
@@ -40,10 +39,12 @@ const randomUpTo = max =>
* @param {function(Function, Function): void} [options.fallback]
* @param {function(): void} [options.fillGaps]
* @param {function(object): boolean} [options.accept]
- * @return {function(): void}
+ * @returns {function(): void}
*/
-export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) =>
- connectStream(channelName, params, (dispatch, getState) => {
+export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => {
+ const { messages } = getLocale();
+
+ return connectStream(channelName, params, (dispatch, getState) => {
const locale = getState().getIn(['meta', 'locale']);
// @ts-expect-error
@@ -52,8 +53,10 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
/**
* @param {function(Function, Function): void} fallback
*/
+
const useFallback = fallback => {
fallback(dispatch, () => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000));
});
};
@@ -118,6 +121,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
},
};
});
+};
/**
* @param {Function} dispatch
@@ -132,7 +136,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
};
/**
- * @return {function(): void}
+ * @returns {function(): void}
*/
export const connectUserStream = () =>
// @ts-expect-error
@@ -141,7 +145,7 @@ export const connectUserStream = () =>
/**
* @param {Object} options
* @param {boolean} [options.onlyMedia]
- * @return {function(): void}
+ * @returns {function(): void}
*/
export const connectCommunityStream = ({ onlyMedia } = {}) =>
connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => (fillCommunityTimelineGaps({ onlyMedia })) });
@@ -150,7 +154,7 @@ export const connectCommunityStream = ({ onlyMedia } = {}) =>
* @param {Object} options
* @param {boolean} [options.onlyMedia]
* @param {boolean} [options.onlyRemote]
- * @return {function(): void}
+ * @returns {function(): void}
*/
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) =>
connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => fillPublicTimelineGaps({ onlyMedia, onlyRemote }) });
@@ -160,20 +164,20 @@ export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) =>
* @param {string} tagName
* @param {boolean} onlyLocal
* @param {function(object): boolean} accept
- * @return {function(): void}
+ * @returns {function(): void}
*/
export const connectHashtagStream = (columnId, tagName, onlyLocal, accept) =>
connectTimelineStream(`hashtag:${columnId}${onlyLocal ? ':local' : ''}`, `hashtag${onlyLocal ? ':local' : ''}`, { tag: tagName }, { accept });
/**
- * @return {function(): void}
+ * @returns {function(): void}
*/
export const connectDirectStream = () =>
connectTimelineStream('direct', 'direct');
/**
* @param {string} listId
- * @return {function(): void}
+ * @returns {function(): void}
*/
export const connectListStream = listId =>
connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) });
diff --git a/app/javascript/mastodon/actions/suggestions.js b/app/javascript/mastodon/actions/suggestions.js
index 9e8cd1ea4..870a31102 100644
--- a/app/javascript/mastodon/actions/suggestions.js
+++ b/app/javascript/mastodon/actions/suggestions.js
@@ -1,6 +1,7 @@
import api from '../api';
-import { importFetchedAccounts } from './importer';
+
import { fetchRelationships } from './accounts';
+import { importFetchedAccounts } from './importer';
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index 4f772a55f..96dc4a2a1 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -1,9 +1,11 @@
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+import api, { getLinks } from 'mastodon/api';
+import { compareId } from 'mastodon/compare_id';
+import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
+
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { submitMarkers } from './markers';
-import api, { getLinks } from 'mastodon/api';
-import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
-import compareId from 'mastodon/compare_id';
-import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
diff --git a/app/javascript/mastodon/actions/trends.js b/app/javascript/mastodon/actions/trends.js
index edda0b5b5..d31442388 100644
--- a/app/javascript/mastodon/actions/trends.js
+++ b/app/javascript/mastodon/actions/trends.js
@@ -1,4 +1,5 @@
import api, { getLinks } from '../api';
+
import { importFetchedStatuses } from './importer';
export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST';
diff --git a/app/javascript/mastodon/api.js b/app/javascript/mastodon/api.js
index 42b64d6cc..1c171a1c4 100644
--- a/app/javascript/mastodon/api.js
+++ b/app/javascript/mastodon/api.js
@@ -2,6 +2,7 @@
import axios from 'axios';
import LinkHeader from 'http-link-header';
+
import ready from './ready';
/**
diff --git a/app/javascript/mastodon/base_polyfills.js b/app/javascript/mastodon/base_polyfills.js
deleted file mode 100644
index d3ac0d510..000000000
--- a/app/javascript/mastodon/base_polyfills.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'intl';
-import 'intl/locale-data/jsonp/en';
-import 'es6-symbol/implement';
-import includes from 'array-includes';
-import assign from 'object-assign';
-import values from 'object.values';
-import { decode as decodeBase64 } from './utils/base64';
-import promiseFinally from 'promise.prototype.finally';
-
-if (!Array.prototype.includes) {
- includes.shim();
-}
-
-if (!Object.assign) {
- Object.assign = assign;
-}
-
-if (!Object.values) {
- values.shim();
-}
-
-promiseFinally.shim();
-
-if (!HTMLCanvasElement.prototype.toBlob) {
- const BASE64_MARKER = ';base64,';
-
- Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
- value(callback, type = 'image/png', quality) {
- const dataURL = this.toDataURL(type, quality);
- let data;
-
- if (dataURL.indexOf(BASE64_MARKER) >= 0) {
- const [, base64] = dataURL.split(BASE64_MARKER);
- data = decodeBase64(base64);
- } else {
- [, data] = dataURL.split(',');
- }
-
- callback(new Blob([data], { type }));
- },
- });
-}
diff --git a/app/javascript/mastodon/blurhash.js b/app/javascript/mastodon/blurhash.ts
similarity index 81%
rename from app/javascript/mastodon/blurhash.js
rename to app/javascript/mastodon/blurhash.ts
index 5adcc3e77..dadf2b7f2 100644
--- a/app/javascript/mastodon/blurhash.js
+++ b/app/javascript/mastodon/blurhash.ts
@@ -84,7 +84,7 @@ const DIGIT_CHARACTERS = [
'~',
];
-export const decode83 = (str) => {
+export const decode83 = (str: string) => {
let value = 0;
let c, digit;
@@ -97,13 +97,13 @@ export const decode83 = (str) => {
return value;
};
-export const intToRGB = int => ({
- r: Math.max(0, (int >> 16)),
+export const intToRGB = (int: number) => ({
+ r: Math.max(0, int >> 16),
g: Math.max(0, (int >> 8) & 255),
- b: Math.max(0, (int & 255)),
+ b: Math.max(0, int & 255),
});
-export const getAverageFromBlurhash = blurhash => {
+export const getAverageFromBlurhash = (blurhash: string) => {
if (!blurhash) {
return null;
}
diff --git a/app/javascript/mastodon/common.js b/app/javascript/mastodon/common.js
index 8f3505303..0ec844934 100644
--- a/app/javascript/mastodon/common.js
+++ b/app/javascript/mastodon/common.js
@@ -1,7 +1,7 @@
import Rails from '@rails/ujs';
+import 'font-awesome/css/font-awesome.css';
export function start() {
- require('font-awesome/css/font-awesome.css');
require.context('../images/', true);
try {
diff --git a/app/javascript/mastodon/compare_id.js b/app/javascript/mastodon/compare_id.ts
similarity index 75%
rename from app/javascript/mastodon/compare_id.js
rename to app/javascript/mastodon/compare_id.ts
index d2bd74f44..30b057248 100644
--- a/app/javascript/mastodon/compare_id.js
+++ b/app/javascript/mastodon/compare_id.ts
@@ -1,4 +1,4 @@
-export default function compareId (id1, id2) {
+export function compareId(id1: string, id2: string) {
if (id1 === id2) {
return 0;
}
diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap
index f8385357a..fbd44ecc5 100644
--- a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap
+++ b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap
@@ -3,6 +3,8 @@
exports[`
', () => {
diff --git a/app/javascript/mastodon/components/__tests__/avatar-test.jsx b/app/javascript/mastodon/components/__tests__/avatar-test.jsx
index dd3f7b7d2..21c3ae580 100644
--- a/app/javascript/mastodon/components/__tests__/avatar-test.jsx
+++ b/app/javascript/mastodon/components/__tests__/avatar-test.jsx
@@ -1,7 +1,8 @@
-import React from 'react';
-import renderer from 'react-test-renderer';
import { fromJS } from 'immutable';
-import Avatar from '../avatar';
+
+import renderer from 'react-test-renderer';
+
+import { Avatar } from '../avatar';
describe('
', () => {
const account = fromJS({
diff --git a/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx b/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx
index 44addea83..99a440af7 100644
--- a/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx
+++ b/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx
@@ -1,7 +1,8 @@
-import React from 'react';
-import renderer from 'react-test-renderer';
import { fromJS } from 'immutable';
-import AvatarOverlay from '../avatar_overlay';
+
+import renderer from 'react-test-renderer';
+
+import { AvatarOverlay } from '../avatar_overlay';
describe('
{
const account = fromJS({
diff --git a/app/javascript/mastodon/components/__tests__/button-test.jsx b/app/javascript/mastodon/components/__tests__/button-test.jsx
index f5a649f70..6de961f78 100644
--- a/app/javascript/mastodon/components/__tests__/button-test.jsx
+++ b/app/javascript/mastodon/components/__tests__/button-test.jsx
@@ -1,6 +1,6 @@
import { render, fireEvent, screen } from '@testing-library/react';
-import React from 'react';
import renderer from 'react-test-renderer';
+
import Button from '../button';
describe('', () => {
diff --git a/app/javascript/mastodon/components/__tests__/display_name-test.jsx b/app/javascript/mastodon/components/__tests__/display_name-test.jsx
index 0d040c4cd..05a0f4717 100644
--- a/app/javascript/mastodon/components/__tests__/display_name-test.jsx
+++ b/app/javascript/mastodon/components/__tests__/display_name-test.jsx
@@ -1,7 +1,8 @@
-import React from 'react';
-import renderer from 'react-test-renderer';
import { fromJS } from 'immutable';
-import DisplayName from '../display_name';
+
+import renderer from 'react-test-renderer';
+
+import { DisplayName } from '../display_name';
describe('', () => {
it('renders display name + account name', () => {
diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx
index a8a47ecac..dd5aff1d8 100644
--- a/app/javascript/mastodon/components/account.jsx
+++ b/app/javascript/mastodon/components/account.jsx
@@ -1,52 +1,38 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import Avatar from './avatar';
-import DisplayName from './display_name';
-import IconButton from './icon_button';
-import { defineMessages, injectIntl } from 'react-intl';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me } from '../initial_state';
-import RelativeTimestamp from './relative_timestamp';
-import Skeleton from 'mastodon/components/skeleton';
-import { Link } from 'react-router-dom';
-import { counterRenderer } from 'mastodon/components/common_counter';
-import ShortNumber from 'mastodon/components/short_number';
-import Icon from 'mastodon/components/icon';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
import classNames from 'classnames';
+import { Link } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+import { counterRenderer } from 'mastodon/components/common_counter';
+import { EmptyAccount } from 'mastodon/components/empty_account';
+import ShortNumber from 'mastodon/components/short_number';
+import { VerifiedBadge } from 'mastodon/components/verified_badge';
+
+import { me } from '../initial_state';
+
+import { Avatar } from './avatar';
+import Button from './button';
+import { DisplayName } from './display_name';
+import { IconButton } from './icon_button';
+import { RelativeTimestamp } from './relative_timestamp';
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
- requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
- unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
- unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
- mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' },
- unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' },
- mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
- block: { id: 'account.block', defaultMessage: 'Block @{name}' },
+ cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
+ unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
+ unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
+ mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
+ unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
+ mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
+ block: { id: 'account.block_short', defaultMessage: 'Block' },
});
-class VerifiedBadge extends React.PureComponent {
-
- static propTypes = {
- link: PropTypes.string.isRequired,
- verifiedAt: PropTypes.string.isRequired,
- };
-
- render () {
- const { link } = this.props;
-
- return (
-
-
-
-
- );
- }
-
-}
-
class Account extends ImmutablePureComponent {
static propTypes = {
@@ -63,6 +49,7 @@ class Account extends ImmutablePureComponent {
actionTitle: PropTypes.string,
defaultAction: PropTypes.string,
onActionClick: PropTypes.func,
+ withBio: PropTypes.bool,
};
static defaultProps = {
@@ -94,23 +81,10 @@ class Account extends ImmutablePureComponent {
};
render () {
- const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
+ const { account, intl, hidden, withBio, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
if (!account) {
- return (
-
- );
+ return ;
}
if (hidden) {
@@ -124,39 +98,39 @@ class Account extends ImmutablePureComponent {
let buttons;
- if (actionIcon) {
- if (onActionClick) {
- buttons = ;
- }
- } else if (account.get('id') !== me && account.get('relationship', null) !== null) {
+ if (actionIcon && onActionClick) {
+ buttons = ;
+ } else if (!actionIcon && account.get('id') !== me && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']);
const muting = account.getIn(['relationship', 'muting']);
if (requested) {
- buttons = ;
+ buttons = ;
} else if (blocking) {
- buttons = ;
+ buttons = ;
} else if (muting) {
let hidingNotificationsButton;
+
if (account.getIn(['relationship', 'muting_notifications'])) {
- hidingNotificationsButton = ;
+ hidingNotificationsButton = ;
} else {
- hidingNotificationsButton = ;
+ hidingNotificationsButton = ;
}
+
buttons = (
<>
-
+
{hidingNotificationsButton}
>
);
} else if (defaultAction === 'mute') {
- buttons = ;
+ buttons = ;
} else if (defaultAction === 'block') {
- buttons = ;
+ buttons = ;
} else if (!account.get('moved') || following) {
- buttons = ;
+ buttons = ;
}
}
@@ -171,7 +145,7 @@ class Account extends ImmutablePureComponent {
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
if (firstVerifiedField) {
- verification = <>· >;
+ verification = ;
}
return (
@@ -182,9 +156,13 @@ class Account extends ImmutablePureComponent {
-
+
- {!minimal && <>
{verification} {muteTimeRemaining}>}
+ {!minimal && (
+
+ {verification} {muteTimeRemaining}
+
+ )}
@@ -194,6 +172,15 @@ class Account extends ImmutablePureComponent {
)}