diff --git a/app/soapbox/actions/importer/index.js b/app/soapbox/actions/importer/index.js
index ee3af3ac3..9ad58e114 100644
--- a/app/soapbox/actions/importer/index.js
+++ b/app/soapbox/actions/importer/index.js
@@ -93,7 +93,7 @@ const isBroken = status => {
// https://gitlab.com/soapbox-pub/soapbox/-/issues/28
if (status.reblog && !status.reblog.account.id) return true;
return false;
- } catch(e) {
+ } catch (e) {
return true;
}
};
diff --git a/app/soapbox/actions/notifications.js b/app/soapbox/actions/notifications.js
index a4331bf8c..ce7e5eca0 100644
--- a/app/soapbox/actions/notifications.js
+++ b/app/soapbox/actions/notifications.js
@@ -121,7 +121,7 @@ export function updateNotificationsQueue(notification, intlMessages, intlLocale,
}).catch(console.error);
}).catch(console.error);
}
- } catch(e) {
+ } catch (e) {
console.warn(e);
}
diff --git a/app/soapbox/api.ts b/app/soapbox/api.ts
index bfa07b71a..6c81042bb 100644
--- a/app/soapbox/api.ts
+++ b/app/soapbox/api.ts
@@ -31,7 +31,7 @@ const getToken = (state: RootState, authType: string) => {
const maybeParseJSON = (data: string) => {
try {
return JSON.parse(data);
- } catch(Exception) {
+ } catch (Exception) {
return data;
}
};
diff --git a/app/soapbox/compare_id.js b/app/soapbox/compare_id.ts
similarity index 74%
rename from app/soapbox/compare_id.js
rename to app/soapbox/compare_id.ts
index f8c15e327..e92d13ef5 100644
--- a/app/soapbox/compare_id.js
+++ b/app/soapbox/compare_id.ts
@@ -1,6 +1,6 @@
'use strict';
-export default function compareId(id1, id2) {
+export default function compareId(id1: string, id2: string) {
if (id1 === id2) {
return 0;
}
diff --git a/app/soapbox/components/domain.js b/app/soapbox/components/domain.js
deleted file mode 100644
index 026497a14..000000000
--- a/app/soapbox/components/domain.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl } from 'react-intl';
-
-import IconButton from './icon_button';
-
-const messages = defineMessages({
- unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
-});
-
-export default @injectIntl
-class Account extends ImmutablePureComponent {
-
- static propTypes = {
- domain: PropTypes.string,
- onUnblockDomain: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- handleDomainUnblock = () => {
- this.props.onUnblockDomain(this.props.domain);
- }
-
- render() {
- const { domain, intl } = this.props;
-
- return (
-
- );
- }
-
-}
diff --git a/app/soapbox/components/domain.tsx b/app/soapbox/components/domain.tsx
new file mode 100644
index 000000000..005848c89
--- /dev/null
+++ b/app/soapbox/components/domain.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { defineMessages, useIntl } from 'react-intl';
+import { useDispatch } from 'react-redux';
+
+import { unblockDomain } from 'soapbox/actions/domain_blocks';
+
+import IconButton from './icon_button';
+
+const messages = defineMessages({
+ blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
+ unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+});
+
+interface IDomain {
+ domain: string,
+}
+
+const Domain: React.FC = ({ domain }) => {
+ const dispatch = useDispatch();
+ const intl = useIntl();
+
+ // const onBlockDomain = () => {
+ // dispatch(openModal('CONFIRM', {
+ // icon: require('@tabler/icons/icons/ban.svg'),
+ // heading: ,
+ // message: {domain} }} />,
+ // confirm: intl.formatMessage(messages.blockDomainConfirm),
+ // onConfirm: () => dispatch(blockDomain(domain)),
+ // }));
+ // }
+
+ const handleDomainUnblock = () => {
+ dispatch(unblockDomain(domain));
+ };
+
+ return (
+
+ );
+};
+
+export default Domain;
diff --git a/app/soapbox/components/dropdown_menu.tsx b/app/soapbox/components/dropdown_menu.tsx
index 8114ca7dc..01a60948d 100644
--- a/app/soapbox/components/dropdown_menu.tsx
+++ b/app/soapbox/components/dropdown_menu.tsx
@@ -278,7 +278,7 @@ class Dropdown extends React.PureComponent {
onShiftClick(e);
} else if (this.state.id === openDropdownId) {
this.handleClose();
- } else if(onOpen) {
+ } else if (onOpen) {
const { top } = e.currentTarget.getBoundingClientRect();
const placement: DropdownPlacement = top * 2 < innerHeight ? 'bottom' : 'top';
diff --git a/app/soapbox/components/status_content.js b/app/soapbox/components/status_content.js
index 2bd5c0712..663480a75 100644
--- a/app/soapbox/components/status_content.js
+++ b/app/soapbox/components/status_content.js
@@ -87,7 +87,7 @@ class StatusContent extends React.PureComponent {
&& this.state.collapsed === null
&& this.props.status.get('spoiler_text').length === 0
) {
- if (node.clientHeight > MAX_HEIGHT){
+ if (node.clientHeight > MAX_HEIGHT) {
this.setState({ collapsed: true });
}
}
diff --git a/app/soapbox/containers/domain_container.js b/app/soapbox/containers/domain_container.js
deleted file mode 100644
index a1d705eaf..000000000
--- a/app/soapbox/containers/domain_container.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { blockDomain, unblockDomain } from '../actions/domain_blocks';
-import { openModal } from '../actions/modals';
-import Domain from '../components/domain';
-
-const messages = defineMessages({
- blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
-});
-
-const makeMapStateToProps = () => {
- const mapStateToProps = () => ({});
-
- return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
- onBlockDomain(domain) {
- dispatch(openModal('CONFIRM', {
- icon: require('@tabler/icons/icons/ban.svg'),
- heading: ,
- message: {domain} }} />,
- confirm: intl.formatMessage(messages.blockDomainConfirm),
- onConfirm: () => dispatch(blockDomain(domain)),
- }));
- },
-
- onUnblockDomain(domain) {
- dispatch(unblockDomain(domain));
- },
-});
-
-export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Domain));
diff --git a/app/soapbox/features/account_timeline/components/moved_note.js b/app/soapbox/features/account_timeline/components/moved_note.js
deleted file mode 100644
index fb2f000e8..000000000
--- a/app/soapbox/features/account_timeline/components/moved_note.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage } from 'react-intl';
-import { NavLink } from 'react-router-dom';
-
-import Icon from 'soapbox/components/icon';
-
-import AvatarOverlay from '../../../components/avatar_overlay';
-import DisplayName from '../../../components/display_name';
-
-export default class MovedNote extends ImmutablePureComponent {
-
- static propTypes = {
- from: ImmutablePropTypes.map.isRequired,
- to: ImmutablePropTypes.map.isRequired,
- };
-
- render() {
- const { from, to } = this.props;
- const displayNameHtml = { __html: from.get('display_name_html') };
-
- return (
-
- );
- }
-
-}
diff --git a/app/soapbox/features/account_timeline/components/moved_note.tsx b/app/soapbox/features/account_timeline/components/moved_note.tsx
new file mode 100644
index 000000000..6e901252d
--- /dev/null
+++ b/app/soapbox/features/account_timeline/components/moved_note.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { NavLink } from 'react-router-dom';
+
+import AvatarOverlay from 'soapbox/components/avatar_overlay';
+import DisplayName from 'soapbox/components/display_name';
+import Icon from 'soapbox/components/icon';
+
+import type { Account as AccountEntity } from 'soapbox/types/entities';
+
+interface IMovedNote {
+ from: AccountEntity,
+ to: AccountEntity,
+}
+
+const MovedNote: React.FC = ({ from, to }) => {
+ const displayNameHtml = { __html: from.display_name_html };
+
+ return (
+
+ );
+};
+
+export default MovedNote;
diff --git a/app/soapbox/features/audio/index.js b/app/soapbox/features/audio/index.js
index 7553f2388..b8ef5892c 100644
--- a/app/soapbox/features/audio/index.js
+++ b/app/soapbox/features/audio/index.js
@@ -255,7 +255,7 @@ class Audio extends React.PureComponent {
handleMouseVolSlide = throttle(e => {
const { x } = getPointerPosition(this.volume, e);
- if(!isNaN(x)) {
+ if (!isNaN(x)) {
this.setState({ volume: x }, () => {
this.audio.volume = x;
});
diff --git a/app/soapbox/features/birthdays/account.js b/app/soapbox/features/birthdays/account.js
deleted file mode 100644
index b29d79589..000000000
--- a/app/soapbox/features/birthdays/account.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-
-import Avatar from 'soapbox/components/avatar';
-import DisplayName from 'soapbox/components/display_name';
-import Icon from 'soapbox/components/icon';
-import Permalink from 'soapbox/components/permalink';
-import { makeGetAccount } from 'soapbox/selectors';
-
-const messages = defineMessages({
- birthday: { id: 'account.birthday', defaultMessage: 'Born {date}' },
-});
-
-const makeMapStateToProps = () => {
- const getAccount = makeGetAccount();
-
- const mapStateToProps = (state, { accountId }) => {
- const account = getAccount(state, accountId);
-
- return {
- account,
- };
- };
-
- return mapStateToProps;
-};
-
-export default @connect(makeMapStateToProps)
-@injectIntl
-class Account extends ImmutablePureComponent {
-
- static propTypes = {
- accountId: PropTypes.string.isRequired,
- intl: PropTypes.object.isRequired,
- account: ImmutablePropTypes.record,
- };
-
- static defaultProps = {
- added: false,
- };
-
- componentDidMount() {
- const { account, accountId } = this.props;
-
- if (accountId && !account) {
- this.props.fetchAccount(accountId);
- }
- }
-
- render() {
- const { account, intl } = this.props;
-
- if (!account) return null;
-
- const birthday = account.get('birthday');
- if (!birthday) return null;
-
- const formattedBirthday = intl.formatDate(birthday, { day: 'numeric', month: 'short', year: 'numeric' });
-
- return (
-
-
-
-
-
-
-
- {formattedBirthday}
-
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/birthdays/account.tsx b/app/soapbox/features/birthdays/account.tsx
new file mode 100644
index 000000000..e72499b3d
--- /dev/null
+++ b/app/soapbox/features/birthdays/account.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { useEffect } from 'react';
+import { defineMessages, useIntl } from 'react-intl';
+
+import Avatar from 'soapbox/components/avatar';
+import DisplayName from 'soapbox/components/display_name';
+import Icon from 'soapbox/components/icon';
+import Permalink from 'soapbox/components/permalink';
+import { useAppSelector } from 'soapbox/hooks';
+import { makeGetAccount } from 'soapbox/selectors';
+
+const messages = defineMessages({
+ birthday: { id: 'account.birthday', defaultMessage: 'Born {date}' },
+});
+
+const getAccount = makeGetAccount();
+
+interface IAccount {
+ accountId: string,
+ fetchAccount: (id: string) => void,
+}
+
+const Account: React.FC = ({ accountId, fetchAccount }) => {
+ const intl = useIntl();
+ const account = useAppSelector((state) => getAccount(state, accountId));
+
+ useEffect(() => {
+ if (accountId && !account) {
+ fetchAccount(accountId);
+ }
+ }, [accountId]);
+
+ if (!account) return null;
+
+ const birthday = account.get('birthday');
+ if (!birthday) return null;
+
+ const formattedBirthday = intl.formatDate(birthday, { day: 'numeric', month: 'short', year: 'numeric' });
+
+ return (
+
+
+
+
+
+
+
+ {formattedBirthday}
+
+
+
+ );
+};
+
+export default Account;
diff --git a/app/soapbox/features/birthdays/date_picker.js b/app/soapbox/features/birthdays/date_picker.ts
similarity index 100%
rename from app/soapbox/features/birthdays/date_picker.js
rename to app/soapbox/features/birthdays/date_picker.ts
diff --git a/app/soapbox/features/blocks/index.js b/app/soapbox/features/blocks/index.js
deleted file mode 100644
index 7bf3ca43e..000000000
--- a/app/soapbox/features/blocks/index.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { debounce } from 'lodash';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { Column, Spinner } from 'soapbox/components/ui';
-
-import { fetchBlocks, expandBlocks } from '../../actions/blocks';
-import ScrollableList from '../../components/scrollable_list';
-import AccountContainer from '../../containers/account_container';
-
-const messages = defineMessages({
- heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
-});
-
-const mapStateToProps = state => ({
- accountIds: state.getIn(['user_lists', 'blocks', 'items']),
- hasMore: !!state.getIn(['user_lists', 'blocks', 'next']),
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Blocks extends ImmutablePureComponent {
-
- static propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.orderedSet,
- hasMore: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- };
-
- componentDidMount() {
- this.props.dispatch(fetchBlocks());
- }
-
- handleLoadMore = debounce(() => {
- this.props.dispatch(expandBlocks());
- }, 300, { leading: true });
-
- render() {
- const { intl, accountIds, hasMore } = this.props;
-
- if (!accountIds) {
- return (
-
-
-
- );
- }
-
- const emptyMessage = ;
-
- return (
-
-
- {accountIds.map(id =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/blocks/index.tsx b/app/soapbox/features/blocks/index.tsx
new file mode 100644
index 000000000..cdc3b0a98
--- /dev/null
+++ b/app/soapbox/features/blocks/index.tsx
@@ -0,0 +1,58 @@
+import { debounce } from 'lodash';
+import React from 'react';
+import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
+import { useDispatch } from 'react-redux';
+
+import { fetchBlocks, expandBlocks } from 'soapbox/actions/blocks';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { Column, Spinner } from 'soapbox/components/ui';
+import AccountContainer from 'soapbox/containers/account_container';
+import { useAppSelector } from 'soapbox/hooks';
+
+const messages = defineMessages({
+ heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
+});
+
+const handleLoadMore = debounce((dispatch) => {
+ dispatch(expandBlocks());
+}, 300, { leading: true });
+
+const Blocks: React.FC = () => {
+ const dispatch = useDispatch();
+ const intl = useIntl();
+
+ const accountIds = useAppSelector((state) => state.user_lists.getIn(['blocks', 'items']));
+ const hasMore = useAppSelector((state) => !!state.user_lists.getIn(['blocks', 'next']));
+
+ React.useEffect(() => {
+ dispatch(fetchBlocks());
+ }, []);
+
+ if (!accountIds) {
+ return (
+
+
+
+ );
+ }
+
+ const emptyMessage = ;
+
+ return (
+
+ handleLoadMore(dispatch)}
+ hasMore={hasMore}
+ emptyMessage={emptyMessage}
+ className='space-y-4'
+ >
+ {accountIds.map((id: string) =>
+ ,
+ )}
+
+
+ );
+};
+
+export default Blocks;
\ No newline at end of file
diff --git a/app/soapbox/features/bookmarks/index.js b/app/soapbox/features/bookmarks/index.js
deleted file mode 100644
index d2b7ea613..000000000
--- a/app/soapbox/features/bookmarks/index.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import { debounce } from 'lodash';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import SubNavigation from 'soapbox/components/sub_navigation';
-import { Column } from 'soapbox/components/ui';
-
-import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from '../../actions/bookmarks';
-import StatusList from '../../components/status_list';
-
-const messages = defineMessages({
- heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
-});
-
-const mapStateToProps = state => ({
- statusIds: state.getIn(['status_lists', 'bookmarks', 'items']),
- isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
- hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Bookmarks extends ImmutablePureComponent {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- shouldUpdateScroll: PropTypes.func,
- statusIds: ImmutablePropTypes.orderedSet.isRequired,
- intl: PropTypes.object.isRequired,
- columnId: PropTypes.string,
- multiColumn: PropTypes.bool,
- hasMore: PropTypes.bool,
- isLoading: PropTypes.bool,
- };
-
- fetchData = () => {
- const { dispatch } = this.props;
- return dispatch(fetchBookmarkedStatuses());
- }
-
- componentDidMount() {
- this.fetchData();
- }
-
- handleLoadMore = debounce(() => {
- this.props.dispatch(expandBookmarkedStatuses());
- }, 300, { leading: true })
-
- handleRefresh = () => {
- return this.fetchData();
- }
-
- render() {
- const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
- const pinned = !!columnId;
-
- const emptyMessage = ;
-
- return (
-
-
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/bookmarks/index.tsx b/app/soapbox/features/bookmarks/index.tsx
new file mode 100644
index 000000000..90aae0c1f
--- /dev/null
+++ b/app/soapbox/features/bookmarks/index.tsx
@@ -0,0 +1,56 @@
+import { debounce } from 'lodash';
+import React from 'react';
+import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
+import { useDispatch } from 'react-redux';
+
+import SubNavigation from 'soapbox/components/sub_navigation';
+import { Column } from 'soapbox/components/ui';
+import { useAppSelector } from 'soapbox/hooks';
+
+import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from '../../actions/bookmarks';
+import StatusList from '../../components/status_list';
+
+const messages = defineMessages({
+ heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
+});
+
+const handleLoadMore = debounce((dispatch) => {
+ dispatch(expandBookmarkedStatuses());
+}, 300, { leading: true });
+
+const Bookmarks: React.FC = () => {
+ const dispatch = useDispatch();
+ const intl = useIntl();
+
+ const statusIds = useAppSelector((state) => state.status_lists.getIn(['bookmarks', 'items']));
+ const isLoading = useAppSelector((state) => state.status_lists.getIn(['bookmarks', 'isLoading'], true));
+ const hasMore = useAppSelector((state) => !!state.status_lists.getIn(['bookmarks', 'next']));
+
+ React.useEffect(() => {
+ dispatch(fetchBookmarkedStatuses());
+ }, []);
+
+ const handleRefresh = () => {
+ return dispatch(fetchBookmarkedStatuses());
+ };
+
+ const emptyMessage = ;
+
+ return (
+
+
+ handleLoadMore(dispatch)}
+ onRefresh={handleRefresh}
+ emptyMessage={emptyMessage}
+ divideType='space'
+ />
+
+ );
+};
+
+export default Bookmarks;
diff --git a/app/soapbox/features/chats/components/chat_list.js b/app/soapbox/features/chats/components/chat_list.js
index b31219913..ffb0c1720 100644
--- a/app/soapbox/features/chats/components/chat_list.js
+++ b/app/soapbox/features/chats/components/chat_list.js
@@ -35,12 +35,12 @@ const chatDateComparator = (chatA, chatB) => {
return 0;
};
-const makeMapStateToProps = () => {
- const sortedChatIdsSelector = createSelector(
- [getSortedChatIds],
- chats => chats,
- );
+const sortedChatIdsSelector = createSelector(
+ [getSortedChatIds],
+ chats => chats,
+);
+const makeMapStateToProps = () => {
const mapStateToProps = state => ({
chatIds: sortedChatIdsSelector(state.getIn(['chats', 'items'])),
hasMore: !!state.getIn(['chats', 'next']),
diff --git a/app/soapbox/features/domain_blocks/index.js b/app/soapbox/features/domain_blocks/index.js
deleted file mode 100644
index 3ab084564..000000000
--- a/app/soapbox/features/domain_blocks/index.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import { debounce } from 'lodash';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { Spinner } from 'soapbox/components/ui';
-
-import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
-import ScrollableList from '../../components/scrollable_list';
-import DomainContainer from '../../containers/domain_container';
-import Column from '../ui/components/column';
-
-const messages = defineMessages({
- heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
- unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
-});
-
-const mapStateToProps = state => ({
- domains: state.getIn(['domain_lists', 'blocks', 'items']),
- hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Blocks extends ImmutablePureComponent {
-
- static propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- hasMore: PropTypes.bool,
- domains: ImmutablePropTypes.orderedSet,
- intl: PropTypes.object.isRequired,
- };
-
- componentDidMount() {
- this.props.dispatch(fetchDomainBlocks());
- }
-
- handleLoadMore = debounce(() => {
- this.props.dispatch(expandDomainBlocks());
- }, 300, { leading: true });
-
- render() {
- const { intl, domains, hasMore } = this.props;
-
- if (!domains) {
- return (
-
-
-
- );
- }
-
- const emptyMessage = ;
-
- return (
-
-
- {domains.map(domain =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/domain_blocks/index.tsx b/app/soapbox/features/domain_blocks/index.tsx
new file mode 100644
index 000000000..712a6671f
--- /dev/null
+++ b/app/soapbox/features/domain_blocks/index.tsx
@@ -0,0 +1,60 @@
+import { debounce } from 'lodash';
+import React from 'react';
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+import { useDispatch } from 'react-redux';
+
+import { fetchDomainBlocks, expandDomainBlocks } from 'soapbox/actions/domain_blocks';
+import Domain from 'soapbox/components/domain';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { Spinner } from 'soapbox/components/ui';
+import { useAppSelector } from 'soapbox/hooks';
+
+import Column from '../ui/components/column';
+
+const messages = defineMessages({
+ heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
+ unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+});
+
+const handleLoadMore = debounce((dispatch) => {
+ dispatch(expandDomainBlocks());
+}, 300, { leading: true });
+
+const DomainBlocks: React.FC = () => {
+ const dispatch = useDispatch();
+ const intl = useIntl();
+
+ const domains = useAppSelector((state) => state.domain_lists.getIn(['blocks', 'items'])) as string[];
+ const hasMore = useAppSelector((state) => !!state.domain_lists.getIn(['blocks', 'next']));
+
+ React.useEffect(() => {
+ dispatch(fetchDomainBlocks());
+ }, []);
+
+ if (!domains) {
+ return (
+
+
+
+ );
+ }
+
+ const emptyMessage = ;
+
+ return (
+
+ handleLoadMore(dispatch)}
+ hasMore={hasMore}
+ emptyMessage={emptyMessage}
+ >
+ {domains.map((domain) =>
+ ,
+ )}
+
+
+ );
+};
+
+export default DomainBlocks;
diff --git a/app/soapbox/features/emoji/emoji_compressed.js b/app/soapbox/features/emoji/emoji_compressed.js
index c95bccf08..542bb7feb 100644
--- a/app/soapbox/features/emoji/emoji_compressed.js
+++ b/app/soapbox/features/emoji/emoji_compressed.js
@@ -14,7 +14,7 @@ const { unicodeToFilename } = require('./unicode_to_filename');
const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
-if(data.compressed) {
+if (data.compressed) {
data = emojiMartUncompress(data);
}
diff --git a/app/soapbox/features/mutes/index.js b/app/soapbox/features/mutes/index.js
deleted file mode 100644
index 69664c72f..000000000
--- a/app/soapbox/features/mutes/index.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { debounce } from 'lodash';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { Column, Spinner } from 'soapbox/components/ui';
-
-import { fetchMutes, expandMutes } from '../../actions/mutes';
-import ScrollableList from '../../components/scrollable_list';
-import AccountContainer from '../../containers/account_container';
-
-const messages = defineMessages({
- heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
-});
-
-const mapStateToProps = state => ({
- accountIds: state.getIn(['user_lists', 'mutes', 'items']),
- hasMore: !!state.getIn(['user_lists', 'mutes', 'next']),
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Mutes extends ImmutablePureComponent {
-
- static propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- hasMore: PropTypes.bool,
- accountIds: ImmutablePropTypes.orderedSet,
- intl: PropTypes.object.isRequired,
- };
-
- componentDidMount() {
- this.props.dispatch(fetchMutes());
- }
-
- handleLoadMore = debounce(() => {
- this.props.dispatch(expandMutes());
- }, 300, { leading: true });
-
- render() {
- const { intl, hasMore, accountIds } = this.props;
-
- if (!accountIds) {
- return (
-
-
-
- );
- }
-
- const emptyMessage = ;
-
- return (
-
-
- {accountIds.map(id =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/mutes/index.tsx b/app/soapbox/features/mutes/index.tsx
new file mode 100644
index 000000000..8e27c06d2
--- /dev/null
+++ b/app/soapbox/features/mutes/index.tsx
@@ -0,0 +1,58 @@
+import { debounce } from 'lodash';
+import React from 'react';
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+import { useDispatch } from 'react-redux';
+
+import { fetchMutes, expandMutes } from 'soapbox/actions/mutes';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { Column, Spinner } from 'soapbox/components/ui';
+import AccountContainer from 'soapbox/containers/account_container';
+import { useAppSelector } from 'soapbox/hooks';
+
+const messages = defineMessages({
+ heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
+});
+
+const handleLoadMore = debounce((dispatch) => {
+ dispatch(expandMutes());
+}, 300, { leading: true });
+
+const Mutes: React.FC = () => {
+ const dispatch = useDispatch();
+ const intl = useIntl();
+
+ const accountIds = useAppSelector((state) => state.user_lists.getIn(['mutes', 'items']));
+ const hasMore = useAppSelector((state) => !!state.user_lists.getIn(['mutes', 'next']));
+
+ React.useEffect(() => {
+ dispatch(fetchMutes());
+ }, []);
+
+ if (!accountIds) {
+ return (
+
+
+
+ );
+ }
+
+ const emptyMessage = ;
+
+ return (
+
+ handleLoadMore(dispatch)}
+ hasMore={hasMore}
+ emptyMessage={emptyMessage}
+ className='space-y-4'
+ >
+ {accountIds.map(id =>
+ ,
+ )}
+
+
+ );
+};
+
+export default Mutes;
diff --git a/app/soapbox/features/status/components/quoted_status.tsx b/app/soapbox/features/status/components/quoted_status.tsx
index 25c8e850e..0b715a86f 100644
--- a/app/soapbox/features/status/components/quoted_status.tsx
+++ b/app/soapbox/features/status/components/quoted_status.tsx
@@ -8,7 +8,8 @@ import { withRouter } from 'react-router-dom';
import AttachmentThumbs from 'soapbox/components/attachment_thumbs';
import { Stack, Text } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account_container';
-import { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
+
+import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
diff --git a/app/soapbox/features/ui/components/profile_info_panel.js b/app/soapbox/features/ui/components/profile_info_panel.js
index 57dfcf48e..e4547e270 100644
--- a/app/soapbox/features/ui/components/profile_info_panel.js
+++ b/app/soapbox/features/ui/components/profile_info_panel.js
@@ -22,7 +22,7 @@ const isSafeUrl = text => {
try {
const url = new URL(text);
return ['http:', 'https:'].includes(url.protocol);
- } catch(e) {
+ } catch (e) {
return false;
}
};
diff --git a/app/soapbox/features/video/index.js b/app/soapbox/features/video/index.js
index 708d87149..869fcdfce 100644
--- a/app/soapbox/features/video/index.js
+++ b/app/soapbox/features/video/index.js
@@ -216,12 +216,12 @@ class Video extends React.PureComponent {
handleMouseVolSlide = throttle(e => {
const { x } = getPointerPosition(this.volume, e);
- if(!isNaN(x)) {
+ if (!isNaN(x)) {
let slideamt = x;
- if(x > 1) {
+ if (x > 1) {
slideamt = 1;
- } else if(x < 0) {
+ } else if (x < 0) {
slideamt = 0;
}
diff --git a/app/soapbox/reducers/auth.js b/app/soapbox/reducers/auth.js
index 9bd3f6f33..26d3c8794 100644
--- a/app/soapbox/reducers/auth.js
+++ b/app/soapbox/reducers/auth.js
@@ -44,7 +44,7 @@ const localState = fromJS(JSON.parse(localStorage.getItem(STORAGE_KEY)));
const validUser = user => {
try {
return validId(user.get('id')) && validId(user.get('access_token'));
- } catch(e) {
+ } catch (e) {
return false;
}
};
diff --git a/app/soapbox/reducers/conversations.js b/app/soapbox/reducers/conversations.js
index 29ea929f3..f15408e0c 100644
--- a/app/soapbox/reducers/conversations.js
+++ b/app/soapbox/reducers/conversations.js
@@ -58,7 +58,7 @@ const expandNormalizedConversations = (state, conversations, next, isLoadingRece
list = list.concat(items);
return list.sortBy(x => x.get('last_status'), (a, b) => {
- if(a === null || b === null) {
+ if (a === null || b === null) {
return -1;
}
diff --git a/app/soapbox/stream.js b/app/soapbox/stream.js
index fb0474367..5d201e314 100644
--- a/app/soapbox/stream.js
+++ b/app/soapbox/stream.js
@@ -93,7 +93,7 @@ export default function getStream(streamingAPIBaseURL, accessToken, stream, { co
if (!e.data) return;
try {
received(JSON.parse(e.data));
- } catch(error) {
+ } catch (error) {
console.error(e);
console.error(`Could not parse the above streaming event.\n${error}`);
}
diff --git a/app/soapbox/utils/auth.js b/app/soapbox/utils/auth.js
deleted file mode 100644
index 0d0c6c321..000000000
--- a/app/soapbox/utils/auth.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { List as ImmutableList } from 'immutable';
-
-export const validId = id => typeof id === 'string' && id !== 'null' && id !== 'undefined';
-
-export const isURL = url => {
- try {
- new URL(url);
- return true;
- } catch {
- return false;
- }
-};
-
-export const parseBaseURL = url => {
- try {
- return new URL(url).origin;
- } catch {
- return '';
- }
-};
-
-export const getLoggedInAccount = state => {
- const me = state.get('me');
- return state.getIn(['accounts', me]);
-};
-
-export const isLoggedIn = getState => {
- return validId(getState().get('me'));
-};
-
-export const getAppToken = state => state.getIn(['auth', 'app', 'access_token']);
-
-export const getUserToken = (state, accountId) => {
- const accountUrl = state.getIn(['accounts', accountId, 'url']);
- return state.getIn(['auth', 'users', accountUrl, 'access_token']);
-};
-
-export const getAccessToken = state => {
- const me = state.get('me');
- return getUserToken(state, me);
-};
-
-export const getAuthUserId = state => {
- const me = state.getIn(['auth', 'me']);
-
- return ImmutableList([
- state.getIn(['auth', 'users', me, 'id']),
- me,
- ]).find(validId);
-};
-
-export const getAuthUserUrl = state => {
- const me = state.getIn(['auth', 'me']);
-
- return ImmutableList([
- state.getIn(['auth', 'users', me, 'url']),
- me,
- ]).find(isURL);
-};
-
-/** Get the VAPID public key. */
-export const getVapidKey = state => {
- return state.getIn(['auth', 'app', 'vapid_key']) || state.getIn(['instance', 'pleroma', 'vapid_public_key']);
-};
diff --git a/app/soapbox/utils/auth.ts b/app/soapbox/utils/auth.ts
new file mode 100644
index 000000000..1a5b1b38e
--- /dev/null
+++ b/app/soapbox/utils/auth.ts
@@ -0,0 +1,66 @@
+import { List as ImmutableList } from 'immutable';
+
+import type { RootState } from 'soapbox/store';
+
+export const validId = (id: any) => typeof id === 'string' && id !== 'null' && id !== 'undefined';
+
+export const isURL = (url: string) => {
+ try {
+ new URL(url);
+ return true;
+ } catch {
+ return false;
+ }
+};
+
+export const parseBaseURL = (url: any) => {
+ try {
+ return new URL(url).origin;
+ } catch {
+ return '';
+ }
+};
+
+export const getLoggedInAccount = (state: RootState) => {
+ const me = state.me;
+ return state.accounts.get(me);
+};
+
+export const isLoggedIn = (getState: () => RootState) => {
+ return validId(getState().me);
+};
+
+export const getAppToken = (state: RootState) => state.auth.getIn(['app', 'access_token']);
+
+export const getUserToken = (state: RootState, accountId?: string | false | null) => {
+ const accountUrl = state.accounts.getIn([accountId, 'url']);
+ return state.auth.getIn(['users', accountUrl, 'access_token']);
+};
+
+export const getAccessToken = (state: RootState) => {
+ const me = state.me;
+ return getUserToken(state, me);
+};
+
+export const getAuthUserId = (state: RootState) => {
+ const me = state.auth.get('me');
+
+ return ImmutableList([
+ state.auth.getIn(['users', me, 'id']),
+ me,
+ ]).find(validId);
+};
+
+export const getAuthUserUrl = (state: RootState) => {
+ const me = state.auth.get('me');
+
+ return ImmutableList([
+ state.auth.getIn(['users', me, 'url']),
+ me,
+ ]).find(isURL);
+};
+
+/** Get the VAPID public key. */
+export const getVapidKey = (state: RootState) => {
+ return state.auth.getIn(['app', 'vapid_key']) || state.instance.getIn(['pleroma', 'vapid_public_key']);
+};
diff --git a/app/soapbox/utils/base64.js b/app/soapbox/utils/base64.ts
similarity index 82%
rename from app/soapbox/utils/base64.js
rename to app/soapbox/utils/base64.ts
index 8226e2c54..c512a6594 100644
--- a/app/soapbox/utils/base64.js
+++ b/app/soapbox/utils/base64.ts
@@ -1,4 +1,4 @@
-export const decode = base64 => {
+export const decode = (base64: string) => {
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
diff --git a/app/soapbox/utils/greentext.js b/app/soapbox/utils/greentext.js
index 543bc2f23..f60ee7c3c 100644
--- a/app/soapbox/utils/greentext.js
+++ b/app/soapbox/utils/greentext.js
@@ -15,7 +15,7 @@ export const addGreentext = html => {
} else {
return string;
}
- } catch(e) {
+ } catch (e) {
return string;
}
});
diff --git a/app/soapbox/utils/instance.js b/app/soapbox/utils/instance.js
deleted file mode 100644
index af9d69665..000000000
--- a/app/soapbox/utils/instance.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export const getHost = instance => {
- try {
- return new URL(instance.get('uri')).host;
- } catch {
- try {
- return new URL(`https://${instance.get('uri')}`).host;
- } catch {
- return null;
- }
- }
-};
diff --git a/app/soapbox/utils/media_aspect_ratio.js b/app/soapbox/utils/media_aspect_ratio.ts
similarity index 70%
rename from app/soapbox/utils/media_aspect_ratio.js
rename to app/soapbox/utils/media_aspect_ratio.ts
index 18e6fdc57..8821d9deb 100644
--- a/app/soapbox/utils/media_aspect_ratio.js
+++ b/app/soapbox/utils/media_aspect_ratio.ts
@@ -1,17 +1,17 @@
export const minimumAspectRatio = 9 / 16; // Portrait phone
export const maximumAspectRatio = 10; // Generous min-height
-export const isPanoramic = ar => {
+export const isPanoramic = (ar: number) => {
if (isNaN(ar)) return false;
return ar >= maximumAspectRatio;
};
-export const isPortrait = ar => {
+export const isPortrait = (ar: number) => {
if (isNaN(ar)) return false;
return ar <= minimumAspectRatio;
};
-export const isNonConformingRatio = ar => {
+export const isNonConformingRatio = (ar: number) => {
if (isNaN(ar)) return false;
return !isPanoramic(ar) && !isPortrait(ar);
};
diff --git a/app/soapbox/utils/phone.js b/app/soapbox/utils/phone.ts
similarity index 100%
rename from app/soapbox/utils/phone.js
rename to app/soapbox/utils/phone.ts
diff --git a/app/soapbox/utils/status.js b/app/soapbox/utils/status.ts
similarity index 67%
rename from app/soapbox/utils/status.js
rename to app/soapbox/utils/status.ts
index acd69dc5e..7f2bfe42f 100644
--- a/app/soapbox/utils/status.js
+++ b/app/soapbox/utils/status.ts
@@ -1,6 +1,8 @@
import { isIntegerId } from 'soapbox/utils/numbers';
-export const getFirstExternalLink = status => {
+import type { Status as StatusEntity } from 'soapbox/types/entities';
+
+export const getFirstExternalLink = (status: StatusEntity) => {
try {
// Pulled from Pleroma's media parser
const selector = 'a:not(.mention,.hashtag,.attachment,[rel~="tag"])';
@@ -12,11 +14,11 @@ export const getFirstExternalLink = status => {
}
};
-export const shouldHaveCard = status => {
+export const shouldHaveCard = (status: StatusEntity) => {
return Boolean(getFirstExternalLink(status));
};
// https://gitlab.com/soapbox-pub/soapbox-fe/-/merge_requests/1087
-export const hasIntegerMediaIds = status => {
+export const hasIntegerMediaIds = (status: StatusEntity) => {
return status.media_attachments.some(({ id }) => isIntegerId(id));
};
diff --git a/app/soapbox/utils/timelines.js b/app/soapbox/utils/timelines.js
deleted file mode 100644
index d15a4fa88..000000000
--- a/app/soapbox/utils/timelines.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Map as ImmutableMap } from 'immutable';
-
-export const shouldFilter = (status, columnSettings) => {
- const shows = ImmutableMap({
- reblog: status.get('reblog') !== null,
- reply: status.get('in_reply_to_id') !== null,
- direct: status.get('visibility') === 'direct',
- });
-
- return shows.some((value, key) => {
- return columnSettings.getIn(['shows', key]) === false && value;
- });
-};
diff --git a/app/soapbox/utils/timelines.ts b/app/soapbox/utils/timelines.ts
new file mode 100644
index 000000000..03ba96044
--- /dev/null
+++ b/app/soapbox/utils/timelines.ts
@@ -0,0 +1,15 @@
+import { Map as ImmutableMap } from 'immutable';
+
+import type { Status as StatusEntity } from 'soapbox/types/entities';
+
+export const shouldFilter = (status: StatusEntity, columnSettings: any) => {
+ const shows = ImmutableMap({
+ reblog: status.reblog !== null,
+ reply: status.in_reply_to_id !== null,
+ direct: status.visibility === 'direct',
+ });
+
+ return shows.some((value, key) => {
+ return columnSettings.getIn(['shows', key]) === false && value;
+ });
+};
diff --git a/package.json b/package.json
index c14cd88ae..4caec0c8d 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.180",
"@types/qrcode.react": "^1.0.2",
+ "@types/react-datepicker": "^4.4.0",
"@types/react-helmet": "^6.1.5",
"@types/react-motion": "^0.0.32",
"@types/react-router-dom": "^5.3.3",
@@ -145,7 +146,7 @@
"qrcode.react": "^1.0.0",
"react": "^16.13.1",
"react-color": "^2.18.1",
- "react-datepicker": "^4.6.0",
+ "react-datepicker": "^4.7.0",
"react-dom": "^16.13.1",
"react-helmet": "^6.0.0",
"react-hotkeys": "^1.1.4",
diff --git a/yarn.lock b/yarn.lock
index d86daa869..9ed45677b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2171,6 +2171,16 @@
dependencies:
"@types/react" "*"
+"@types/react-datepicker@^4.4.0":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.4.0.tgz#0072e18536ad305fd57786f9b6f9e499eed2b475"
+ integrity sha512-wzmevaO51rLFwSZd5HSqBU0aAvZlRRkj6QhHqj0jfRDSKnN3y5IKXyhgxPS8R0LOWOtjdpirI1DBryjnIp/7gA==
+ dependencies:
+ "@popperjs/core" "^2.9.2"
+ "@types/react" "*"
+ date-fns "^2.0.1"
+ react-popper "^2.2.5"
+
"@types/react-dom@*":
version "17.0.14"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.14.tgz#c8f917156b652ddf807711f5becbd2ab018dea9f"
@@ -4006,7 +4016,7 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
-date-fns@^2.24.0:
+date-fns@^2.0.1, date-fns@^2.24.0:
version "2.28.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
@@ -8662,7 +8672,7 @@ react-color@^2.18.1:
reactcss "^1.2.0"
tinycolor2 "^1.4.1"
-react-datepicker@^4.6.0:
+react-datepicker@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.7.0.tgz#75e03b0a6718b97b84287933307faf2ed5f03cf4"
integrity sha512-FS8KgbwqpxmJBv/bUdA42MYqYZa+fEYcpc746DZiHvVE2nhjrW/dg7c5B5fIUuI8gZET6FOzuDgezNcj568Czw==