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 ( -
-
- - {domain} - - -
- -
-
-
- ); - } - -} 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 ( +
+
+ + {domain} + + +
+ +
+
+
+ ); +}; + +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==