diff --git a/app/soapbox/components/column-header.js b/app/soapbox/components/column-header.js deleted file mode 100644 index 2d915c9f81..0000000000 Binary files a/app/soapbox/components/column-header.js and /dev/null differ diff --git a/app/soapbox/components/sub-navigation.tsx b/app/soapbox/components/sub-navigation.tsx deleted file mode 100644 index 1e6afb85a7..0000000000 --- a/app/soapbox/components/sub-navigation.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// import throttle from 'lodash/throttle'; -import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; -// import { connect } from 'react-redux'; -import { useHistory } from 'react-router-dom'; - -import { CardHeader, CardTitle } from './ui'; - -const messages = defineMessages({ - back: { id: 'column_back_button.label', defaultMessage: 'Back' }, -}); - -interface ISubNavigation { - message: React.ReactNode, - /** @deprecated Unused. */ - settings?: React.ComponentType, -} - -const SubNavigation: React.FC = ({ message }) => { - const intl = useIntl(); - const history = useHistory(); - - const handleBackClick = () => { - if (window.history && window.history.length === 1) { - history.push('/'); - } else { - history.goBack(); - } - }; - - return ( - - - - ); -}; - -export default SubNavigation; diff --git a/app/soapbox/components/ui/card/card.tsx b/app/soapbox/components/ui/card/card.tsx index 675a9d15b5..fd01e5a94e 100644 --- a/app/soapbox/components/ui/card/card.tsx +++ b/app/soapbox/components/ui/card/card.tsx @@ -44,13 +44,14 @@ const Card = React.forwardRef(({ children, variant = 'def interface ICardHeader { backHref?: string, onBackClick?: (event: React.MouseEvent) => void + className?: string } /** * Card header container with back button. * Typically holds a CardTitle. */ -const CardHeader: React.FC = ({ children, backHref, onBackClick }): JSX.Element => { +const CardHeader: React.FC = ({ className, children, backHref, onBackClick }): JSX.Element => { const intl = useIntl(); const renderBackButton = () => { @@ -70,7 +71,7 @@ const CardHeader: React.FC = ({ children, backHref, onBackClick }): }; return ( - + {renderBackButton()} {children} diff --git a/app/soapbox/components/ui/column/__tests__/column.test.tsx b/app/soapbox/components/ui/column/__tests__/column.test.tsx index 6241773fa9..f4796dfafd 100644 --- a/app/soapbox/components/ui/column/__tests__/column.test.tsx +++ b/app/soapbox/components/ui/column/__tests__/column.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render, screen } from '../../../../jest/test-helpers'; -import Column from '../column'; +import { Column } from '../column'; describe('', () => { it('renders correctly with minimal props', () => { diff --git a/app/soapbox/components/ui/column/column.tsx b/app/soapbox/components/ui/column/column.tsx index e0e86a398d..62aede9b29 100644 --- a/app/soapbox/components/ui/column/column.tsx +++ b/app/soapbox/components/ui/column/column.tsx @@ -1,3 +1,4 @@ +import classNames from 'clsx'; import React from 'react'; import { useHistory } from 'react-router-dom'; @@ -6,6 +7,35 @@ import { useSoapboxConfig } from 'soapbox/hooks'; import { Card, CardBody, CardHeader, CardTitle } from '../card/card'; +type IColumnHeader = Pick; + +/** Contains the column title with optional back button. */ +const ColumnHeader: React.FC = ({ label, backHref, transparent }) => { + const history = useHistory(); + + const handleBackClick = () => { + if (backHref) { + history.push(backHref); + return; + } + + if (history.length === 1) { + history.push('/'); + } else { + history.goBack(); + } + }; + + return ( + + + + ); +}; + export interface IColumn { /** Route the back button goes to. */ backHref?: string, @@ -24,37 +54,8 @@ export interface IColumn { /** A backdrop for the main section of the UI. */ const Column: React.FC = React.forwardRef((props, ref: React.ForwardedRef): JSX.Element => { const { backHref, children, label, transparent = false, withHeader = true, className } = props; - - const history = useHistory(); const soapboxConfig = useSoapboxConfig(); - const handleBackClick = () => { - if (backHref) { - history.push(backHref); - return; - } - - if (history.length === 1) { - history.push('/'); - } else { - history.goBack(); - } - }; - - const renderChildren = () => ( - - {withHeader ? ( - - - - ) : null} - - - {children} - - - ); - return (
@@ -69,9 +70,20 @@ const Column: React.FC = React.forwardRef((props, ref: React.ForwardedR )} - {renderChildren()} + + {withHeader && ( + + )} + + + {children} + +
); }); -export default Column; +export { + Column, + ColumnHeader, +}; diff --git a/app/soapbox/components/ui/index.ts b/app/soapbox/components/ui/index.ts index 4953ed9ebb..4903927423 100644 --- a/app/soapbox/components/ui/index.ts +++ b/app/soapbox/components/ui/index.ts @@ -4,7 +4,7 @@ export { default as Banner } from './banner/banner'; export { default as Button } from './button/button'; export { Card, CardBody, CardHeader, CardTitle } from './card/card'; export { default as Checkbox } from './checkbox/checkbox'; -export { default as Column } from './column/column'; +export { Column, ColumnHeader } from './column/column'; export { default as Counter } from './counter/counter'; export { default as Datepicker } from './datepicker/datepicker'; export { default as Divider } from './divider/divider'; diff --git a/app/soapbox/features/admin/moderation-log.tsx b/app/soapbox/features/admin/moderation-log.tsx index 063e3092aa..baf002eb49 100644 --- a/app/soapbox/features/admin/moderation-log.tsx +++ b/app/soapbox/features/admin/moderation-log.tsx @@ -3,10 +3,9 @@ import { defineMessages, FormattedDate, useIntl } from 'react-intl'; import { fetchModerationLog } from 'soapbox/actions/admin'; import ScrollableList from 'soapbox/components/scrollable-list'; +import { Column } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import Column from '../ui/components/column'; - const messages = defineMessages({ heading: { id: 'column.admin.moderation_log', defaultMessage: 'Moderation Log' }, emptyMessage: { id: 'admin.moderation_log.empty_message', defaultMessage: 'You have not performed any moderation actions yet. When you do, a history will be shown here.' }, @@ -47,7 +46,7 @@ const ModerationLog = () => { }; return ( - + { ); return ( - + { const emptyMessage = ; return ( - -
- -
+ { }, [onlyMedia]); return ( - -
- -
- + { const emptyMessage = ; return ( - + handleLoadMore(dispatch)} diff --git a/app/soapbox/features/export-data/index.tsx b/app/soapbox/features/export-data/index.tsx index 5883f5ac26..27273c3393 100644 --- a/app/soapbox/features/export-data/index.tsx +++ b/app/soapbox/features/export-data/index.tsx @@ -6,8 +6,7 @@ import { exportBlocks, exportMutes, } from 'soapbox/actions/export-data'; - -import Column from '../ui/components/column'; +import { Column } from 'soapbox/components/ui'; import CSVExporter from './components/csv-exporter'; @@ -38,7 +37,7 @@ const ExportData = () => { const intl = useIntl(); return ( - + diff --git a/app/soapbox/features/favourited-statuses/index.tsx b/app/soapbox/features/favourited-statuses/index.tsx index fe284fce10..1e381d37ef 100644 --- a/app/soapbox/features/favourited-statuses/index.tsx +++ b/app/soapbox/features/favourited-statuses/index.tsx @@ -7,11 +7,10 @@ import { fetchAccount, fetchAccountByUsername } from 'soapbox/actions/accounts'; import { fetchFavouritedStatuses, expandFavouritedStatuses, fetchAccountFavouritedStatuses, expandAccountFavouritedStatuses } from 'soapbox/actions/favourites'; import MissingIndicator from 'soapbox/components/missing-indicator'; import StatusList from 'soapbox/components/status-list'; +import { Column } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; import { findAccountByUsername } from 'soapbox/selectors'; -import Column from '../ui/components/column'; - const messages = defineMessages({ heading: { id: 'column.favourited_statuses', defaultMessage: 'Liked posts' }, }); diff --git a/app/soapbox/features/federation-restrictions/index.tsx b/app/soapbox/features/federation-restrictions/index.tsx index f0b1a3ae25..47f5a218b9 100644 --- a/app/soapbox/features/federation-restrictions/index.tsx +++ b/app/soapbox/features/federation-restrictions/index.tsx @@ -2,13 +2,11 @@ import React, { useState, useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import ScrollableList from 'soapbox/components/scrollable-list'; -import { Accordion } from 'soapbox/components/ui'; +import { Column, Accordion } from 'soapbox/components/ui'; import { useAppSelector, useInstance } from 'soapbox/hooks'; import { makeGetHosts } from 'soapbox/selectors'; import { federationRestrictionsDisclosed } from 'soapbox/utils/state'; -import Column from '../ui/components/column'; - import RestrictedInstance from './components/restricted-instance'; import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; @@ -39,7 +37,7 @@ const FederationRestrictions = () => { const emptyMessage = disclosed ? messages.emptyMessage : messages.notDisclosed; return ( - + { const emptyMessage = ; return ( - + handleLoadMore(dispatch)} diff --git a/app/soapbox/features/followers/index.tsx b/app/soapbox/features/followers/index.tsx index d9025499bd..c01dae0bbf 100644 --- a/app/soapbox/features/followers/index.tsx +++ b/app/soapbox/features/followers/index.tsx @@ -11,13 +11,11 @@ import { } from 'soapbox/actions/accounts'; import MissingIndicator from 'soapbox/components/missing-indicator'; import ScrollableList from 'soapbox/components/scrollable-list'; -import { Spinner } from 'soapbox/components/ui'; +import { Column, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; import { findAccountByUsername } from 'soapbox/selectors'; -import Column from '../ui/components/column'; - const messages = defineMessages({ heading: { id: 'column.followers', defaultMessage: 'Followers' }, }); diff --git a/app/soapbox/features/following/index.tsx b/app/soapbox/features/following/index.tsx index a189fddf13..2392edd8d9 100644 --- a/app/soapbox/features/following/index.tsx +++ b/app/soapbox/features/following/index.tsx @@ -11,13 +11,11 @@ import { } from 'soapbox/actions/accounts'; import MissingIndicator from 'soapbox/components/missing-indicator'; import ScrollableList from 'soapbox/components/scrollable-list'; -import { Spinner } from 'soapbox/components/ui'; +import { Column, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; import { findAccountByUsername } from 'soapbox/selectors'; -import Column from '../ui/components/column'; - const messages = defineMessages({ heading: { id: 'column.following', defaultMessage: 'Following' }, }); diff --git a/app/soapbox/features/hashtag-timeline/index.tsx b/app/soapbox/features/hashtag-timeline/index.tsx index 2282bc1c7f..133a96a5fb 100644 --- a/app/soapbox/features/hashtag-timeline/index.tsx +++ b/app/soapbox/features/hashtag-timeline/index.tsx @@ -1,9 +1,8 @@ import React, { useEffect, useRef } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { useIntl, defineMessages } from 'react-intl'; import { connectHashtagStream } from 'soapbox/actions/streaming'; import { expandHashtagTimeline, clearTimeline } from 'soapbox/actions/timelines'; -import SubNavigation from 'soapbox/components/sub-navigation'; import { Column } from 'soapbox/components/ui'; import Timeline from 'soapbox/features/ui/components/timeline'; import { useAppDispatch } from 'soapbox/hooks'; @@ -15,6 +14,13 @@ type Mode = 'any' | 'all' | 'none'; type Tag = { value: string }; type Tags = { [k in Mode]: Tag[] }; +const messages = defineMessages({ + any: { id: 'hashtag.column_header.tag_mode.any', defaultMessage: 'or {additional}' }, + all: { id: 'hashtag.column_header.tag_mode.all', defaultMessage: 'and {additional}' }, + none: { id: 'hashtag.column_header.tag_mode.none', defaultMessage: 'without {additional}' }, + empty: { id: 'empty_column.hashtag', defaultMessage: 'There is nothing in this hashtag yet.' }, +}); + interface IHashtagTimeline { params?: { id?: string, @@ -23,6 +29,7 @@ interface IHashtagTimeline { } export const HashtagTimeline: React.FC = ({ params }) => { + const intl = useIntl(); const id = params?.id || ''; const tags = params?.tags || { any: [], all: [], none: [] }; @@ -31,22 +38,22 @@ export const HashtagTimeline: React.FC = ({ params }) => { // Mastodon supports displaying results from multiple hashtags. // https://github.com/mastodon/mastodon/issues/6359 - const title = () => { - const title: React.ReactNode[] = [`#${id}`]; + const title = (): string => { + const title: string[] = [`#${id}`]; if (additionalFor('any')) { - title.push(' ', ); + title.push(' ', intl.formatMessage(messages.any, { additional: additionalFor('any') })); } if (additionalFor('all')) { - title.push(' ', ); + title.push(' ', intl.formatMessage(messages.any, { additional: additionalFor('all') })); } if (additionalFor('none')) { - title.push(' ', ); + title.push(' ', intl.formatMessage(messages.any, { additional: additionalFor('none') })); } - return title; + return title.join(''); }; const additionalFor = (mode: Mode) => { @@ -98,16 +105,12 @@ export const HashtagTimeline: React.FC = ({ params }) => { }, [id]); return ( - -
- -
- + } + emptyMessage={intl.formatMessage(messages.empty)} divideType='space' /> diff --git a/app/soapbox/features/import-data/index.tsx b/app/soapbox/features/import-data/index.tsx index 9955ec6c3a..812fb20fb1 100644 --- a/app/soapbox/features/import-data/index.tsx +++ b/app/soapbox/features/import-data/index.tsx @@ -6,8 +6,7 @@ import { importBlocks, importMutes, } from 'soapbox/actions/import-data'; - -import Column from '../ui/components/column'; +import { Column } from 'soapbox/components/ui'; import CSVImporter from './components/csv-importer'; @@ -38,7 +37,7 @@ const ImportData = () => { const intl = useIntl(); return ( - + diff --git a/app/soapbox/features/list-timeline/index.tsx b/app/soapbox/features/list-timeline/index.tsx index c8acc7a4c5..9751f24407 100644 --- a/app/soapbox/features/list-timeline/index.tsx +++ b/app/soapbox/features/list-timeline/index.tsx @@ -7,26 +7,16 @@ import { openModal } from 'soapbox/actions/modals'; import { connectListStream } from 'soapbox/actions/streaming'; import { expandListTimeline } from 'soapbox/actions/timelines'; import MissingIndicator from 'soapbox/components/missing-indicator'; -import { Button, Spinner } from 'soapbox/components/ui'; -import Column from 'soapbox/features/ui/components/column'; +import { Column, Button, Spinner } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import Timeline from '../ui/components/timeline'; -// const messages = defineMessages({ -// deleteHeading: { id: 'confirmations.delete_list.heading', defaultMessage: 'Delete list' }, -// deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, -// deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' }, -// }); - const ListTimeline: React.FC = () => { const dispatch = useAppDispatch(); const { id } = useParams<{ id: string }>(); - // const intl = useIntl(); - // const history = useHistory(); const list = useAppSelector((state) => state.lists.get(id)); - // const hasUnread = useAppSelector((state) => state.timelines.get(`list:${props.params.id}`)?.unread > 0); useEffect(() => { dispatch(fetchList(id)); @@ -47,19 +37,6 @@ const ListTimeline: React.FC = () => { dispatch(openModal('LIST_EDITOR', { listId: id })); }; - // const handleDeleteClick = () => { - // dispatch(openModal('CONFIRM', { - // icon: require('@tabler/icons/trash.svg'), - // heading: intl.formatMessage(messages.deleteHeading), - // message: intl.formatMessage(messages.deleteMessage), - // confirm: intl.formatMessage(messages.deleteConfirm), - // onConfirm: () => { - // dispatch(deleteList(id)); - // history.push('/lists'); - // }, - // })); - // }; - const title = list ? list.title : id; if (typeof list === 'undefined') { @@ -85,26 +62,7 @@ const ListTimeline: React.FC = () => { ); return ( - - {/* -
- - - - -
- - - - - -
-
*/} - + { } return ( - + { }, [onlyMedia]); return ( - -
- -
- + {showExplanationBox &&
diff --git a/app/soapbox/features/remote-timeline/index.tsx b/app/soapbox/features/remote-timeline/index.tsx index a6963df48e..67d8c31d50 100644 --- a/app/soapbox/features/remote-timeline/index.tsx +++ b/app/soapbox/features/remote-timeline/index.tsx @@ -1,11 +1,10 @@ import React, { useEffect, useRef } from 'react'; -import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { connectRemoteStream } from 'soapbox/actions/streaming'; import { expandRemoteTimeline } from 'soapbox/actions/timelines'; import IconButton from 'soapbox/components/icon-button'; -import SubNavigation from 'soapbox/components/sub-navigation'; import { Column, HStack, Text } from 'soapbox/components/ui'; import { useAppDispatch, useSettings } from 'soapbox/hooks'; @@ -13,10 +12,6 @@ import Timeline from '../ui/components/timeline'; import PinnedHostsPicker from './components/pinned-hosts-picker'; -const messages = defineMessages({ - heading: { id: 'column.remote', defaultMessage: 'Federated timeline' }, -}); - interface IRemoteTimeline { params?: { instance?: string, @@ -25,7 +20,6 @@ interface IRemoteTimeline { /** View statuses from a remote instance. */ const RemoteTimeline: React.FC = ({ params }) => { - const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); @@ -65,25 +59,21 @@ const RemoteTimeline: React.FC = ({ params }) => { }, [onlyMedia]); return ( - -
- + + {instance && } - {instance && } - - {!pinned && ( - - - - - - - )} -
+ {!pinned && ( + + + + + + + )} { const emptyMessage = ; return ( - + = (props) => { } return ( - -
- -
- +
diff --git a/app/soapbox/features/test-timeline/index.tsx b/app/soapbox/features/test-timeline/index.tsx index 7102d96022..1c64ecad53 100644 --- a/app/soapbox/features/test-timeline/index.tsx +++ b/app/soapbox/features/test-timeline/index.tsx @@ -4,7 +4,6 @@ import { useDispatch } from 'react-redux'; import { importFetchedStatuses } from 'soapbox/actions/importer'; import { expandTimelineSuccess } from 'soapbox/actions/timelines'; -import SubNavigation from 'soapbox/components/sub-navigation'; import { Column } from '../../components/ui'; import Timeline from '../ui/components/timeline'; @@ -40,8 +39,7 @@ const TestTimeline: React.FC = () => { }, []); return ( - - + = ({ type }) => { - return ; -}; - -export default ColumnHeader; - -// export default class ColumnHeader extends React.PureComponent { - -// static propTypes = { -// icon: PropTypes.string, -// type: PropTypes.string, -// active: PropTypes.bool, -// onClick: PropTypes.func, -// columnHeaderId: PropTypes.string, -// }; - -// handleClick = () => { -// this.props.onClick(); -// } - -// render() { -// const { icon, type, active, columnHeaderId } = this.props; - -// return ( -//

-// -//

-// ); -// } - -// } diff --git a/app/soapbox/features/ui/components/column.tsx b/app/soapbox/features/ui/components/column.tsx deleted file mode 100644 index 9604049b49..0000000000 --- a/app/soapbox/features/ui/components/column.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; - -import Pullable from 'soapbox/components/pullable'; -import { Column } from 'soapbox/components/ui'; - -import ColumnHeader from './column-header'; - -import type { IColumn } from 'soapbox/components/ui/column/column'; - -interface IUIColumn extends IColumn { - heading?: string, - icon?: string, - active?: boolean, -} - -const UIColumn: React.FC = ({ - heading, - icon, - children, - active, - ...rest -}) => { - const columnHeaderId = heading && heading.replace(/ /g, '-'); - - return ( - - {heading && } - - {children} - - - ); - -}; - -export default UIColumn; diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 6e6b7427bf..9c1b5c5458 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -240,7 +240,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => { {features.lists && } - {features.lists && } + {features.lists && } {features.bookmarks && }