diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
index f44cf79730..f0d583bc54 100644
--- a/app/helpers/formatting_helper.rb
+++ b/app/helpers/formatting_helper.rb
@@ -9,6 +9,10 @@ module FormattingHelper
TextFormatter.new(text, options).to_s
end
+ def url_for_preview_card(preview_card)
+ preview_card.url
+ end
+
def extract_status_plain_text(status)
PlainTextFormatter.new(status.text, status.local?).to_s
end
diff --git a/app/javascript/mastodon/components/__tests__/button-test.jsx b/app/javascript/mastodon/components/__tests__/button-test.jsx
index 6de961f784..ad7a0c49ca 100644
--- a/app/javascript/mastodon/components/__tests__/button-test.jsx
+++ b/app/javascript/mastodon/components/__tests__/button-test.jsx
@@ -1,7 +1,7 @@
import { render, fireEvent, screen } from '@testing-library/react';
import renderer from 'react-test-renderer';
-import Button from '../button';
+import { Button } from '../button';
describe('', () => {
it('renders a button element', () => {
diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx
index fd5ea60407..69189a84b4 100644
--- a/app/javascript/mastodon/components/account.jsx
+++ b/app/javascript/mastodon/components/account.jsx
@@ -15,7 +15,7 @@ import { VerifiedBadge } from 'mastodon/components/verified_badge';
import { me } from '../initial_state';
import { Avatar } from './avatar';
-import Button from './button';
+import { Button } from './button';
import { FollowersCounter } from './counters';
import { DisplayName } from './display_name';
import { IconButton } from './icon_button';
diff --git a/app/javascript/mastodon/components/button.jsx b/app/javascript/mastodon/components/button.jsx
deleted file mode 100644
index faa6cb2910..0000000000
--- a/app/javascript/mastodon/components/button.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import PropTypes from 'prop-types';
-import { PureComponent } from 'react';
-
-import classNames from 'classnames';
-
-export default class Button extends PureComponent {
-
- static propTypes = {
- text: PropTypes.node,
- type: PropTypes.string,
- onClick: PropTypes.func,
- disabled: PropTypes.bool,
- block: PropTypes.bool,
- secondary: PropTypes.bool,
- className: PropTypes.string,
- title: PropTypes.string,
- children: PropTypes.node,
- };
-
- static defaultProps = {
- type: 'button',
- };
-
- handleClick = (e) => {
- if (!this.props.disabled && this.props.onClick) {
- this.props.onClick(e);
- }
- };
-
- setRef = (c) => {
- this.node = c;
- };
-
- focus() {
- this.node.focus();
- }
-
- render () {
- const className = classNames('button', this.props.className, {
- 'button-secondary': this.props.secondary,
- 'button--block': this.props.block,
- });
-
- return (
-
- );
- }
-
-}
diff --git a/app/javascript/mastodon/components/button.tsx b/app/javascript/mastodon/components/button.tsx
new file mode 100644
index 0000000000..0b6a0f267e
--- /dev/null
+++ b/app/javascript/mastodon/components/button.tsx
@@ -0,0 +1,58 @@
+import { useCallback } from 'react';
+
+import classNames from 'classnames';
+
+interface BaseProps extends React.ButtonHTMLAttributes {
+ block?: boolean;
+ secondary?: boolean;
+ text?: JSX.Element;
+}
+
+interface PropsWithChildren extends BaseProps {
+ text?: never;
+}
+
+interface PropsWithText extends BaseProps {
+ text: JSX.Element;
+ children: never;
+}
+
+type Props = PropsWithText | PropsWithChildren;
+
+export const Button: React.FC = ({
+ text,
+ type = 'button',
+ onClick,
+ disabled,
+ block,
+ secondary,
+ className,
+ title,
+ children,
+ ...props
+}) => {
+ const handleClick = useCallback>(
+ (e) => {
+ if (!disabled && onClick) {
+ onClick(e);
+ }
+ },
+ [disabled, onClick],
+ );
+
+ return (
+
+ );
+};
diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx
index a378868020..e10c31b02d 100644
--- a/app/javascript/mastodon/components/status.jsx
+++ b/app/javascript/mastodon/components/status.jsx
@@ -11,7 +11,7 @@ import { HotKeys } from 'react-hotkeys';
import { Icon } from 'mastodon/components/icon';
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
-import { withOptionalRouter, WithRouterPropTypes } from 'mastodon/utils/react_router';
+import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router';
import Card from '../features/status/components/card';
// We use the component (and not the container) since we do not want
@@ -113,7 +113,7 @@ class Status extends ImmutablePureComponent {
inUse: PropTypes.bool,
available: PropTypes.bool,
}),
- ...WithRouterPropTypes,
+ ...WithOptionalRouterPropTypes,
};
// Avoid checking props that are functions (and whose equality will always
diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx
index 7183f7af90..9425b25b71 100644
--- a/app/javascript/mastodon/features/account/components/header.jsx
+++ b/app/javascript/mastodon/features/account/components/header.jsx
@@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { Avatar } from 'mastodon/components/avatar';
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button';
diff --git a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx
index 4872455e97..59b7358233 100644
--- a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx
+++ b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx
@@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { revealAccount } from 'mastodon/actions/accounts';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { domain } from 'mastodon/initial_state';
const mapDispatchToProps = (dispatch, { accountId }) => ({
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx
index 030f7df953..5d99a5eedb 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.jsx
+++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx
@@ -14,7 +14,7 @@ import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/
import AutosuggestInput from '../../../components/autosuggest_input';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
-import Button from '../../../components/button';
+import { Button } from '../../../components/button';
import { maxChars } from '../../../initial_state';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import LanguageDropdown from '../containers/language_dropdown_container';
diff --git a/app/javascript/mastodon/features/directory/components/account_card.jsx b/app/javascript/mastodon/features/directory/components/account_card.jsx
index 0306b63d32..a5e5717b32 100644
--- a/app/javascript/mastodon/features/directory/components/account_card.jsx
+++ b/app/javascript/mastodon/features/directory/components/account_card.jsx
@@ -17,7 +17,7 @@ import {
} from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal';
import { Avatar } from 'mastodon/components/avatar';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name';
import { ShortNumber } from 'mastodon/components/short_number';
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
diff --git a/app/javascript/mastodon/features/explore/statuses.jsx b/app/javascript/mastodon/features/explore/statuses.jsx
index f32a4a5368..0d8d212b25 100644
--- a/app/javascript/mastodon/features/explore/statuses.jsx
+++ b/app/javascript/mastodon/features/explore/statuses.jsx
@@ -45,24 +45,20 @@ class Statuses extends PureComponent {
const emptyMessage = ;
return (
- <>
-
-
-
-
-
- >
+ }
+ alwaysPrepend
+ timelineId='explore'
+ statusIds={statusIds}
+ scrollKey='explore-statuses'
+ hasMore={hasMore}
+ isLoading={isLoading}
+ onLoadMore={this.handleLoadMore}
+ emptyMessage={emptyMessage}
+ bindToDocument={!multiColumn}
+ withCounters
+ />
);
}
diff --git a/app/javascript/mastodon/features/filters/added_to_filter.jsx b/app/javascript/mastodon/features/filters/added_to_filter.jsx
index ec848f622e..e341eddbad 100644
--- a/app/javascript/mastodon/features/filters/added_to_filter.jsx
+++ b/app/javascript/mastodon/features/filters/added_to_filter.jsx
@@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { toServerSideType } from 'mastodon/utils/filters';
const mapStateToProps = (state, { filterId }) => ({
diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
index 46050309ff..d2fe8851f6 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
+++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
@@ -4,7 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { ShortNumber } from 'mastodon/components/short_number';
const messages = defineMessages({
@@ -76,4 +76,4 @@ HashtagHeader.propTypes = {
disabled: PropTypes.bool,
onClick: PropTypes.func,
intl: PropTypes.object,
-};
\ No newline at end of file
+};
diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx
index 69bbd8cd87..d79a3d4154 100644
--- a/app/javascript/mastodon/features/interaction_modal/index.jsx
+++ b/app/javascript/mastodon/features/interaction_modal/index.jsx
@@ -11,7 +11,7 @@ import { throttle, escapeRegExp } from 'lodash';
import { openModal, closeModal } from 'mastodon/actions/modal';
import api from 'mastodon/api';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { Icon } from 'mastodon/components/icon';
import { registrationsOpen, sso_redirect } from 'mastodon/initial_state';
diff --git a/app/javascript/mastodon/features/lists/components/new_list_form.jsx b/app/javascript/mastodon/features/lists/components/new_list_form.jsx
index 40e2d4a1c2..0fed9d70a2 100644
--- a/app/javascript/mastodon/features/lists/components/new_list_form.jsx
+++ b/app/javascript/mastodon/features/lists/components/new_list_form.jsx
@@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { changeListEditorTitle, submitListEditor } from 'mastodon/actions/lists';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
const messages = defineMessages({
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
diff --git a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx
index 59b0335c10..9ce014d258 100644
--- a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx
+++ b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx
@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import { requestBrowserPermission } from 'mastodon/actions/notifications';
import { changeSetting } from 'mastodon/actions/settings';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button';
diff --git a/app/javascript/mastodon/features/report/category.jsx b/app/javascript/mastodon/features/report/category.jsx
index fb9e55c579..a2fc3b23b4 100644
--- a/app/javascript/mastodon/features/report/category.jsx
+++ b/app/javascript/mastodon/features/report/category.jsx
@@ -7,7 +7,7 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import Option from './components/option';
diff --git a/app/javascript/mastodon/features/report/comment.jsx b/app/javascript/mastodon/features/report/comment.jsx
index ca9ea9d268..ec59746923 100644
--- a/app/javascript/mastodon/features/report/comment.jsx
+++ b/app/javascript/mastodon/features/report/comment.jsx
@@ -11,7 +11,7 @@ import { createSelector } from 'reselect';
import Toggle from 'react-toggle';
import { fetchAccount } from 'mastodon/actions/accounts';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
const messages = defineMessages({
diff --git a/app/javascript/mastodon/features/report/rules.jsx b/app/javascript/mastodon/features/report/rules.jsx
index 67d92839ed..621f140adb 100644
--- a/app/javascript/mastodon/features/report/rules.jsx
+++ b/app/javascript/mastodon/features/report/rules.jsx
@@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import Option from './components/option';
diff --git a/app/javascript/mastodon/features/report/statuses.jsx b/app/javascript/mastodon/features/report/statuses.jsx
index 4ba8d6065f..5e416f1e1a 100644
--- a/app/javascript/mastodon/features/report/statuses.jsx
+++ b/app/javascript/mastodon/features/report/statuses.jsx
@@ -7,7 +7,7 @@ import { OrderedSet } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container';
diff --git a/app/javascript/mastodon/features/report/thanks.jsx b/app/javascript/mastodon/features/report/thanks.jsx
index 79ddc2e802..146d4b3897 100644
--- a/app/javascript/mastodon/features/report/thanks.jsx
+++ b/app/javascript/mastodon/features/report/thanks.jsx
@@ -11,7 +11,7 @@ import {
muteAccount,
blockAccount,
} from 'mastodon/actions/accounts';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
const mapStateToProps = () => ({});
diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx
index eb128f68e7..003f50183d 100644
--- a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx
+++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { followAccount } from 'mastodon/actions/accounts';
-import Button from 'mastodon/components/button';
+import { Button } from 'mastodon/components/button';
import { IconButton } from 'mastodon/components/icon_button';
import Option from 'mastodon/features/report/components/option';
import { languages as preloadedLanguages } from 'mastodon/initial_state';
diff --git a/app/javascript/mastodon/features/ui/components/block_modal.jsx b/app/javascript/mastodon/features/ui/components/block_modal.jsx
index 7cfd252e0b..cfac692324 100644
--- a/app/javascript/mastodon/features/ui/components/block_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/block_modal.jsx
@@ -8,7 +8,7 @@ import { connect } from 'react-redux';
import { blockAccount } from '../../../actions/accounts';
import { closeModal } from '../../../actions/modal';
import { initReport } from '../../../actions/reports';
-import Button from '../../../components/button';
+import { Button } from '../../../components/button';
import { makeGetAccount } from '../../../selectors';
const makeMapStateToProps = () => {
@@ -51,10 +51,6 @@ class BlockModal extends PureComponent {
intl: PropTypes.object.isRequired,
};
- componentDidMount() {
- this.button.focus();
- }
-
handleClick = () => {
this.props.onClose();
this.props.onConfirm(this.props.account);
@@ -69,10 +65,6 @@ class BlockModal extends PureComponent {
this.props.onClose();
};
- setRef = (c) => {
- this.button = c;
- };
-
render () {
const { account } = this.props;
@@ -95,7 +87,7 @@ class BlockModal extends PureComponent {
-