Merge remote-tracking branch 'parent/main' into upstream-20231021
This commit is contained in:
commit
f9eaaec4e7
95 changed files with 1002 additions and 657 deletions
|
@ -236,7 +236,7 @@ module.exports = {
|
|||
},
|
||||
// Common React utilities
|
||||
{
|
||||
pattern: '{classnames,react-helmet,react-router-dom}',
|
||||
pattern: '{classnames,react-helmet,react-router,react-router-dom}',
|
||||
group: 'external',
|
||||
position: 'before',
|
||||
},
|
||||
|
|
1
.github/workflows/test-ruby.yml
vendored
1
.github/workflows/test-ruby.yml
vendored
|
@ -114,6 +114,7 @@ jobs:
|
|||
BUNDLE_WITH: 'pam_authentication test'
|
||||
CI_JOBS: ${{ matrix.ci_job }}/4
|
||||
ES_ENABLED: false
|
||||
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
|
@ -48,27 +48,6 @@ Lint/UnusedBlockArgument:
|
|||
- 'config/initializers/paperclip.rb'
|
||||
- 'config/initializers/simple_form.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Lint/UselessAssignment:
|
||||
Exclude:
|
||||
- 'app/services/activitypub/process_status_update_service.rb'
|
||||
- 'config/initializers/3_omniauth.rb'
|
||||
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
||||
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
|
||||
- 'spec/controllers/api/v1/favourites_controller_spec.rb'
|
||||
- 'spec/controllers/concerns/account_controller_concern_spec.rb'
|
||||
- 'spec/helpers/jsonld_helper_spec.rb'
|
||||
- 'spec/models/account_spec.rb'
|
||||
- 'spec/models/domain_block_spec.rb'
|
||||
- 'spec/models/status_spec.rb'
|
||||
- 'spec/models/user_spec.rb'
|
||||
- 'spec/models/webauthn_credentials_spec.rb'
|
||||
- 'spec/services/account_search_service_spec.rb'
|
||||
- 'spec/services/post_status_service_spec.rb'
|
||||
- 'spec/services/precompute_feed_service_spec.rb'
|
||||
- 'spec/services/resolve_url_service_spec.rb'
|
||||
- 'spec/views/statuses/show.html.haml_spec.rb'
|
||||
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
||||
Metrics/AbcSize:
|
||||
Max: 144
|
||||
|
@ -86,26 +65,6 @@ Metrics/CyclomaticComplexity:
|
|||
Metrics/PerceivedComplexity:
|
||||
Max: 27
|
||||
|
||||
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
|
||||
# SupportedStyles: snake_case, normalcase, non_integer
|
||||
# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
|
||||
Naming/VariableNumber:
|
||||
Exclude:
|
||||
- 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
|
||||
- 'db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
|
||||
- 'db/migrate/20190820003045_update_statuses_index.rb'
|
||||
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
|
||||
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
|
||||
- 'spec/models/account_spec.rb'
|
||||
- 'spec/models/domain_block_spec.rb'
|
||||
- 'spec/models/user_spec.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: SafeMultiline.
|
||||
Performance/DeletePrefix:
|
||||
Exclude:
|
||||
- 'app/models/featured_tag.rb'
|
||||
|
||||
Performance/MapMethodChain:
|
||||
Exclude:
|
||||
- 'app/models/feed.rb'
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Apps::CredentialsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
|
||||
def show
|
||||
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
|
||||
return doorkeeper_render_error unless valid_doorkeeper_token?
|
||||
|
||||
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes)
|
||||
end
|
||||
end
|
||||
|
|
19
app/helpers/admin/disputes_helper.rb
Normal file
19
app/helpers/admin/disputes_helper.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
module DisputesHelper
|
||||
def strike_action_label(appeal)
|
||||
t(key_for_action(appeal),
|
||||
scope: 'admin.strikes.actions',
|
||||
name: content_tag(:span, appeal.strike.account.username, class: 'username'),
|
||||
target: content_tag(:span, appeal.account.username, class: 'target'))
|
||||
.html_safe
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key_for_action(appeal)
|
||||
AccountWarning.actions.slice(appeal.strike.action).keys.first
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
|
@ -4,29 +4,28 @@ import { createPortal } from 'react-dom';
|
|||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
export default class ColumnBackButton extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
class ColumnBackButton extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
multiColumn: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
const { router } = this.context;
|
||||
const { onClick } = this.props;
|
||||
const { onClick, history } = this.props;
|
||||
|
||||
if (onClick) {
|
||||
onClick();
|
||||
} else if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
} else if (history.location?.state?.fromMastodon) {
|
||||
history.goBack();
|
||||
} else {
|
||||
router.history.push('/');
|
||||
history.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -60,3 +59,5 @@ export default class ColumnBackButton extends PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(ColumnBackButton);
|
||||
|
|
|
@ -5,8 +5,10 @@ import { createPortal } from 'react-dom';
|
|||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
const messages = defineMessages({
|
||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||
|
@ -18,7 +20,6 @@ const messages = defineMessages({
|
|||
class ColumnHeader extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -38,6 +39,7 @@ class ColumnHeader extends PureComponent {
|
|||
onClick: PropTypes.func,
|
||||
appendContent: PropTypes.node,
|
||||
collapseIssues: PropTypes.bool,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -63,12 +65,12 @@ class ColumnHeader extends PureComponent {
|
|||
};
|
||||
|
||||
handleBackClick = () => {
|
||||
const { router } = this.context;
|
||||
const { history } = this.props;
|
||||
|
||||
if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
if (history.location?.state?.fromMastodon) {
|
||||
history.goBack();
|
||||
} else {
|
||||
router.history.push('/');
|
||||
history.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -78,15 +80,14 @@ class ColumnHeader extends PureComponent {
|
|||
|
||||
handlePin = () => {
|
||||
if (!this.props.pinned) {
|
||||
this.context.router.history.replace('/');
|
||||
this.props.history.replace('/');
|
||||
}
|
||||
|
||||
this.props.onPin();
|
||||
};
|
||||
|
||||
render () {
|
||||
const { router } = this.context;
|
||||
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
|
||||
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
|
||||
const { collapsed, animating } = this.state;
|
||||
|
||||
const wrapperClassName = classNames('column-header__wrapper', {
|
||||
|
@ -129,7 +130,7 @@ class ColumnHeader extends PureComponent {
|
|||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||
}
|
||||
|
||||
if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) {
|
||||
if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
|
||||
backButton = (
|
||||
<button onClick={this.handleBackClick} className='column-header__back-button'>
|
||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||
|
@ -215,4 +216,4 @@ class ColumnHeader extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(ColumnHeader);
|
||||
export default injectIntl(withRouter(ColumnHeader));
|
||||
|
|
|
@ -2,13 +2,16 @@ import PropTypes from 'prop-types';
|
|||
import { PureComponent, cloneElement, Children } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
|
||||
import { CircularProgress } from "./circular_progress";
|
||||
import { CircularProgress } from 'mastodon/components/circular_progress';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
||||
|
@ -16,10 +19,6 @@ let id = 0;
|
|||
|
||||
class DropdownMenu extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired,
|
||||
loading: PropTypes.bool,
|
||||
|
@ -159,11 +158,7 @@ class DropdownMenu extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default class Dropdown extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
class Dropdown extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
|
@ -183,6 +178,7 @@ export default class Dropdown extends PureComponent {
|
|||
renderItem: PropTypes.func,
|
||||
renderHeader: PropTypes.func,
|
||||
onItemClick: PropTypes.func,
|
||||
...WithRouterPropTypes
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -250,7 +246,7 @@ export default class Dropdown extends PureComponent {
|
|||
item.action();
|
||||
} else if (item && item.to) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(item.to);
|
||||
this.props.history.push(item.to);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -338,3 +334,5 @@ export default class Dropdown extends PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(Dropdown);
|
||||
|
|
|
@ -2,14 +2,13 @@ import PropTypes from 'prop-types';
|
|||
import { PureComponent } from 'react';
|
||||
|
||||
import 'wicg-inert';
|
||||
|
||||
import { multiply } from 'color-blend';
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
export default class ModalRoot extends PureComponent {
|
||||
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router';
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
class ModalRoot extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
|
@ -20,6 +19,7 @@ export default class ModalRoot extends PureComponent {
|
|||
b: PropTypes.number,
|
||||
}),
|
||||
ignoreFocus: PropTypes.bool,
|
||||
...WithOptionalRouterPropTypes,
|
||||
};
|
||||
|
||||
activeElement = this.props.children ? document.activeElement : null;
|
||||
|
@ -55,7 +55,7 @@ export default class ModalRoot extends PureComponent {
|
|||
componentDidMount () {
|
||||
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
this.history = this.context.router ? this.context.router.history : createBrowserHistory();
|
||||
this.history = this.props.history || createBrowserHistory();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
|
@ -156,3 +156,5 @@ export default class ModalRoot extends PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default withOptionalRouter(ModalRoot);
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import { PureComponent } from 'react';
|
||||
|
||||
import { Switch, Route, withRouter } from 'react-router-dom';
|
||||
|
||||
import AccountNavigation from 'mastodon/features/account/navigation';
|
||||
import Trends from 'mastodon/features/getting_started/containers/trends_container';
|
||||
import { showTrends } from 'mastodon/initial_state';
|
||||
|
||||
const DefaultNavigation = () => (
|
||||
showTrends ? (
|
||||
<>
|
||||
<div className='flex-spacer' />
|
||||
<Trends />
|
||||
</>
|
||||
) : null
|
||||
);
|
||||
|
||||
class NavigationPortal extends PureComponent {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path='/@:acct' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/with_replies' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/followers' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/following' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/media' exact component={AccountNavigation} />
|
||||
<Route component={DefaultNavigation} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
export default withRouter(NavigationPortal);
|
25
app/javascript/mastodon/components/navigation_portal.tsx
Normal file
25
app/javascript/mastodon/components/navigation_portal.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { Switch, Route } from 'react-router-dom';
|
||||
|
||||
import AccountNavigation from 'mastodon/features/account/navigation';
|
||||
import Trends from 'mastodon/features/getting_started/containers/trends_container';
|
||||
import { showTrends } from 'mastodon/initial_state';
|
||||
|
||||
const DefaultNavigation: React.FC = () =>
|
||||
showTrends ? (
|
||||
<>
|
||||
<div className='flex-spacer' />
|
||||
<Trends />
|
||||
</>
|
||||
) : null;
|
||||
|
||||
export const NavigationPortal: React.FC = () => (
|
||||
<Switch>
|
||||
<Route path='/@:acct' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/with_replies' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/followers' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/following' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/media' exact component={AccountNavigation} />
|
||||
<Route component={DefaultNavigation} />
|
||||
</Switch>
|
||||
);
|
|
@ -1,15 +1,18 @@
|
|||
import type { PropsWithChildren } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { Router as OriginalRouter } from 'react-router';
|
||||
|
||||
import type { LocationDescriptor, Path } from 'history';
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
|
||||
interface MastodonLocationState {
|
||||
fromMastodon?: boolean;
|
||||
mastodonModalKey?: string;
|
||||
}
|
||||
type HistoryPath = Path | LocationDescriptor<MastodonLocationState>;
|
||||
|
||||
const browserHistory = createBrowserHistory<
|
||||
MastodonLocationState | undefined
|
||||
|
@ -17,25 +20,36 @@ const browserHistory = createBrowserHistory<
|
|||
const originalPush = browserHistory.push.bind(browserHistory);
|
||||
const originalReplace = browserHistory.replace.bind(browserHistory);
|
||||
|
||||
browserHistory.push = (path: string, state?: MastodonLocationState) => {
|
||||
function extractRealPath(path: HistoryPath) {
|
||||
if (typeof path === 'string') return path;
|
||||
else return path.pathname;
|
||||
}
|
||||
|
||||
browserHistory.push = (path: HistoryPath, state?: MastodonLocationState) => {
|
||||
state = state ?? {};
|
||||
state.fromMastodon = true;
|
||||
|
||||
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
|
||||
originalPush(`/deck${path}`, state);
|
||||
const realPath = extractRealPath(path);
|
||||
if (!realPath) return;
|
||||
|
||||
if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) {
|
||||
originalPush(`/deck${realPath}`, state);
|
||||
} else {
|
||||
originalPush(path, state);
|
||||
}
|
||||
};
|
||||
|
||||
browserHistory.replace = (path: string, state?: MastodonLocationState) => {
|
||||
browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => {
|
||||
if (browserHistory.location.state?.fromMastodon) {
|
||||
state = state ?? {};
|
||||
state.fromMastodon = true;
|
||||
}
|
||||
|
||||
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
|
||||
originalReplace(`/deck${path}`, state);
|
||||
const realPath = extractRealPath(path);
|
||||
if (!realPath) return;
|
||||
|
||||
if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) {
|
||||
originalReplace(`/deck${realPath}`, state);
|
||||
} else {
|
||||
originalReplace(path, state);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||
import { Children, cloneElement, PureComponent } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -34,11 +35,32 @@ const mapStateToProps = (state, { scrollKey }) => {
|
|||
};
|
||||
};
|
||||
|
||||
class ScrollableList extends PureComponent {
|
||||
// This component only exists to be able to call useLocation()
|
||||
const IOArticleContainerWrapper = ({id, index, listLength, intersectionObserverWrapper, trackScroll, scrollKey, children}) => {
|
||||
const location = useLocation();
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
return (<IntersectionObserverArticleContainer
|
||||
id={id}
|
||||
index={index}
|
||||
listLength={listLength}
|
||||
intersectionObserverWrapper={intersectionObserverWrapper}
|
||||
saveHeightKey={trackScroll ? `${location.key}:${scrollKey}` : null}
|
||||
>
|
||||
{children}
|
||||
</IntersectionObserverArticleContainer>);
|
||||
};
|
||||
|
||||
IOArticleContainerWrapper.propTypes = {
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
scrollKey: PropTypes.string.isRequired,
|
||||
intersectionObserverWrapper: PropTypes.object.isRequired,
|
||||
trackScroll: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
class ScrollableList extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
scrollKey: PropTypes.string.isRequired,
|
||||
|
@ -331,13 +353,14 @@ class ScrollableList extends PureComponent {
|
|||
{loadPending}
|
||||
|
||||
{Children.map(this.props.children, (child, index) => (
|
||||
<IntersectionObserverArticleContainer
|
||||
<IOArticleContainerWrapper
|
||||
key={child.key}
|
||||
id={child.key}
|
||||
index={index}
|
||||
listLength={childrenCount}
|
||||
intersectionObserverWrapper={this.intersectionObserverWrapper}
|
||||
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
|
||||
trackScroll={trackScroll}
|
||||
scrollKey={scrollKey}
|
||||
>
|
||||
{cloneElement(child, child.type.name === 'ColumnLink' ? {} : {
|
||||
getScrollPosition: this.getScrollPosition,
|
||||
|
@ -345,7 +368,7 @@ class ScrollableList extends PureComponent {
|
|||
cachedMediaWidth: this.state.cachedMediaWidth,
|
||||
cacheMediaWidth: this.cacheMediaWidth,
|
||||
})}
|
||||
</IntersectionObserverArticleContainer>
|
||||
</IOArticleContainerWrapper>
|
||||
))}
|
||||
|
||||
{loadMore}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { HotKeys } from 'react-hotkeys';
|
|||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
import { withOptionalRouter, WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import CompactedStatusContainer from '../containers/compacted_status_container';
|
||||
import Card from '../features/status/components/card';
|
||||
|
@ -81,10 +82,6 @@ const messages = defineMessages({
|
|||
|
||||
class Status extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.map,
|
||||
|
@ -130,6 +127,7 @@ class Status extends ImmutablePureComponent {
|
|||
available: PropTypes.bool,
|
||||
}),
|
||||
withoutEmojiReactions: PropTypes.bool,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
|
@ -272,7 +270,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
handleHotkeyReply = e => {
|
||||
e.preventDefault();
|
||||
this.props.onReply(this._properStatus(), this.context.router.history);
|
||||
this.props.onReply(this._properStatus(), this.props.history);
|
||||
};
|
||||
|
||||
handleHotkeyFavourite = () => {
|
||||
|
@ -285,7 +283,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
handleHotkeyMention = e => {
|
||||
e.preventDefault();
|
||||
this.props.onMention(this._properStatus().get('account'), this.context.router.history);
|
||||
this.props.onMention(this._properStatus().get('account'), this.props.history);
|
||||
};
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
|
@ -294,14 +292,14 @@ class Status extends ImmutablePureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
const { router } = this.context;
|
||||
const { history } = this.props;
|
||||
const status = this._properStatus();
|
||||
|
||||
if (!router) {
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
};
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
|
@ -309,14 +307,14 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
_openProfile = (proper = true) => {
|
||||
const { router } = this.context;
|
||||
const { history } = this.props;
|
||||
const status = proper ? this._properStatus() : this.props.status;
|
||||
|
||||
if (!router) {
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}`);
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}`);
|
||||
};
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
|
@ -686,4 +684,4 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(Status);
|
||||
export default withOptionalRouter(injectIntl(Status));
|
||||
|
|
|
@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
|
@ -70,7 +72,6 @@ const mapStateToProps = (state, { status }) => ({
|
|||
class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -106,6 +107,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
withCounters: PropTypes.bool,
|
||||
scrollKey: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
|
@ -120,7 +122,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
const { signedIn } = this.context.identity;
|
||||
|
||||
if (signedIn) {
|
||||
this.props.onReply(this.props.status, this.context.router.history);
|
||||
this.props.onReply(this.props.status, this.props.history);
|
||||
} else {
|
||||
this.props.onInteractionModal('reply', this.props.status);
|
||||
}
|
||||
|
@ -187,15 +189,15 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleDeleteClick = () => {
|
||||
this.props.onDelete(this.props.status, this.context.router.history);
|
||||
this.props.onDelete(this.props.status, this.props.history);
|
||||
};
|
||||
|
||||
handleRedraftClick = () => {
|
||||
this.props.onDelete(this.props.status, this.context.router.history, true);
|
||||
this.props.onDelete(this.props.status, this.props.history, true);
|
||||
};
|
||||
|
||||
handleEditClick = () => {
|
||||
this.props.onEdit(this.props.status, this.context.router.history);
|
||||
this.props.onEdit(this.props.status, this.props.history);
|
||||
};
|
||||
|
||||
handlePinClick = () => {
|
||||
|
@ -203,11 +205,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleMentionClick = () => {
|
||||
this.props.onMention(this.props.status.get('account'), this.context.router.history);
|
||||
this.props.onMention(this.props.status.get('account'), this.props.history);
|
||||
};
|
||||
|
||||
handleDirectClick = () => {
|
||||
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
|
||||
this.props.onDirect(this.props.status.get('account'), this.props.history);
|
||||
};
|
||||
|
||||
handleMuteClick = () => {
|
||||
|
@ -247,7 +249,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleOpen = () => {
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
|
||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
|
||||
};
|
||||
|
||||
handleOpenMentions = () => {
|
||||
|
@ -477,4 +479,4 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(StatusActionBar));
|
||||
export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar)));
|
||||
|
|
|
@ -4,7 +4,7 @@ import { PureComponent } from 'react';
|
|||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -68,7 +68,6 @@ const mapStateToProps = state => ({
|
|||
class StatusContent extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -83,6 +82,10 @@ class StatusContent extends PureComponent {
|
|||
onCollapsedToggle: PropTypes.func,
|
||||
languages: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object,
|
||||
// from react-router
|
||||
match: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -173,18 +176,18 @@ class StatusContent extends PureComponent {
|
|||
}
|
||||
|
||||
onMentionClick = (mention, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/@${mention.get('acct')}`);
|
||||
this.props.history.push(`/@${mention.get('acct')}`);
|
||||
}
|
||||
};
|
||||
|
||||
onHashtagClick = (hashtag, e) => {
|
||||
hashtag = hashtag.replace(/^#/, '');
|
||||
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/tags/${hashtag}`);
|
||||
this.props.history.push(`/tags/${hashtag}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -252,7 +255,7 @@ class StatusContent extends PureComponent {
|
|||
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
const classNames = classnames('status__content', {
|
||||
'status__content--with-action': this.props.onClick && this.context.router,
|
||||
'status__content--with-action': this.props.onClick && this.props.history,
|
||||
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
||||
'status__content--collapsed': renderReadMore,
|
||||
});
|
||||
|
@ -329,4 +332,4 @@ class StatusContent extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(StatusContent));
|
||||
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent)));
|
||||
|
|
|
@ -14,10 +14,6 @@ const messages = defineMessages({
|
|||
|
||||
class FeaturedTags extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
featuredTags: ImmutablePropTypes.list,
|
||||
|
|
|
@ -4,7 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||
|
||||
import classNames from 'classnames';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { NavLink, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -19,6 +19,7 @@ import { ShortNumber } from 'mastodon/components/short_number';
|
|||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||
import { autoPlayGif, me, domain } from 'mastodon/initial_state';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import AccountNoteContainer from '../containers/account_note_container';
|
||||
import FollowRequestNoteContainer from '../containers/follow_request_note_container';
|
||||
|
@ -86,11 +87,6 @@ const dateFormatOptions = {
|
|||
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
identity_props: ImmutablePropTypes.list,
|
||||
|
@ -117,6 +113,11 @@ class Header extends ImmutablePureComponent {
|
|||
intl: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
setRef = c => {
|
||||
|
@ -179,25 +180,24 @@ class Header extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleHashtagClick = e => {
|
||||
const { router } = this.context;
|
||||
const { history } = this.props;
|
||||
const value = e.currentTarget.textContent.replace(/^#/, '');
|
||||
|
||||
if (router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
if (history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
router.history.push(`/tags/${value}`);
|
||||
history.push(`/tags/${value}`);
|
||||
}
|
||||
};
|
||||
|
||||
handleMentionClick = e => {
|
||||
const { router } = this.context;
|
||||
const { onOpenURL } = this.props;
|
||||
const { history, onOpenURL } = this.props;
|
||||
|
||||
if (router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
if (history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
|
||||
const link = e.currentTarget;
|
||||
|
||||
onOpenURL(link.href, router.history, () => {
|
||||
onOpenURL(link.href, history, () => {
|
||||
window.location = link.href;
|
||||
});
|
||||
}
|
||||
|
@ -506,4 +506,4 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(Header);
|
||||
export default withRouter(injectIntl(Header));
|
||||
|
|
|
@ -2,17 +2,19 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { NavLink, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import InnerHeader from '../../account/components/header';
|
||||
|
||||
import MemorialNote from './memorial_note';
|
||||
import MovedNote from './moved_note';
|
||||
|
||||
export default class Header extends ImmutablePureComponent {
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
|
@ -37,10 +39,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
hideTabs: PropTypes.bool,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
handleFollow = () => {
|
||||
|
@ -52,11 +51,11 @@ export default class Header extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleMention = () => {
|
||||
this.props.onMention(this.props.account, this.context.router.history);
|
||||
this.props.onMention(this.props.account, this.props.history);
|
||||
};
|
||||
|
||||
handleDirect = () => {
|
||||
this.props.onDirect(this.props.account, this.context.router.history);
|
||||
this.props.onDirect(this.props.account, this.props.history);
|
||||
};
|
||||
|
||||
handleReport = () => {
|
||||
|
@ -177,3 +176,5 @@ export default class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(Header);
|
||||
|
|
|
@ -39,7 +39,6 @@ const mapStateToProps = (state, { columnId }) => {
|
|||
class CommunityTimeline extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { length } from 'stringz';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router';
|
||||
|
||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||
|
@ -43,11 +44,6 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
class ComposeForm extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
|
@ -78,6 +74,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
singleColumn: PropTypes.bool,
|
||||
lang: PropTypes.string,
|
||||
circleId: PropTypes.string,
|
||||
...WithOptionalRouterPropTypes
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -121,7 +118,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
this.props.onSubmit(this.context.router ? this.context.router.history : null);
|
||||
this.props.onSubmit(this.props.history || null);
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
|
@ -337,4 +334,4 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(ComposeForm);
|
||||
export default withOptionalRouter(injectIntl(ComposeForm));
|
||||
|
|
|
@ -6,6 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
|
@ -17,14 +18,11 @@ const messages = defineMessages({
|
|||
|
||||
class ReplyIndicator extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
...WithOptionalRouterPropTypes,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
|
@ -34,7 +32,7 @@ class ReplyIndicator extends ImmutablePureComponent {
|
|||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
this.props.history?.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -72,4 +70,4 @@ class ReplyIndicator extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(ReplyIndicator);
|
||||
export default withOptionalRouter(injectIntl(ReplyIndicator));
|
||||
|
|
|
@ -4,12 +4,14 @@ import { PureComponent } from 'react';
|
|||
import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { domain, searchEnabled } from 'mastodon/initial_state';
|
||||
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
||||
|
@ -30,7 +32,6 @@ const labelForRecentSearch = search => {
|
|||
class Search extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
identity: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
@ -48,6 +49,7 @@ class Search extends PureComponent {
|
|||
openInRoute: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
singleColumn: PropTypes.bool,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -163,32 +165,29 @@ class Search extends PureComponent {
|
|||
};
|
||||
|
||||
handleHashtagClick = () => {
|
||||
const { router } = this.context;
|
||||
const { value, onClickSearchResult } = this.props;
|
||||
const { value, onClickSearchResult, history } = this.props;
|
||||
|
||||
const query = value.trim().replace(/^#/, '');
|
||||
|
||||
router.history.push(`/tags/${query}`);
|
||||
history.push(`/tags/${query}`);
|
||||
onClickSearchResult(query, 'hashtag');
|
||||
this._unfocus();
|
||||
};
|
||||
|
||||
handleAccountClick = () => {
|
||||
const { router } = this.context;
|
||||
const { value, onClickSearchResult } = this.props;
|
||||
const { value, onClickSearchResult, history } = this.props;
|
||||
|
||||
const query = value.trim().replace(/^@/, '');
|
||||
|
||||
router.history.push(`/@${query}`);
|
||||
history.push(`/@${query}`);
|
||||
onClickSearchResult(query, 'account');
|
||||
this._unfocus();
|
||||
};
|
||||
|
||||
handleURLClick = () => {
|
||||
const { router } = this.context;
|
||||
const { value, onOpenURL } = this.props;
|
||||
const { value, onOpenURL, history } = this.props;
|
||||
|
||||
onOpenURL(value, router.history);
|
||||
onOpenURL(value, history);
|
||||
this._unfocus();
|
||||
};
|
||||
|
||||
|
@ -201,13 +200,12 @@ class Search extends PureComponent {
|
|||
};
|
||||
|
||||
handleRecentSearchClick = search => {
|
||||
const { onChange } = this.props;
|
||||
const { router } = this.context;
|
||||
const { onChange, history } = this.props;
|
||||
|
||||
if (search.get('type') === 'account') {
|
||||
router.history.push(`/@${search.get('q')}`);
|
||||
history.push(`/@${search.get('q')}`);
|
||||
} else if (search.get('type') === 'hashtag') {
|
||||
router.history.push(`/tags/${search.get('q')}`);
|
||||
history.push(`/tags/${search.get('q')}`);
|
||||
} else {
|
||||
onChange(search.get('q'));
|
||||
this._submit(search.get('type'));
|
||||
|
@ -239,8 +237,7 @@ class Search extends PureComponent {
|
|||
}
|
||||
|
||||
_submit (type) {
|
||||
const { onSubmit, openInRoute, value, onClickSearchResult } = this.props;
|
||||
const { router } = this.context;
|
||||
const { onSubmit, openInRoute, value, onClickSearchResult, history } = this.props;
|
||||
|
||||
onSubmit(type);
|
||||
|
||||
|
@ -249,7 +246,7 @@ class Search extends PureComponent {
|
|||
}
|
||||
|
||||
if (openInRoute) {
|
||||
router.history.push('/search');
|
||||
history.push('/search');
|
||||
}
|
||||
|
||||
this._unfocus();
|
||||
|
@ -398,4 +395,4 @@ class Search extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(Search);
|
||||
export default withRouter(injectIntl(Search));
|
||||
|
|
|
@ -13,10 +13,6 @@ import Motion from '../../ui/util/optional_motion';
|
|||
|
||||
export default class Upload extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
onUndo: PropTypes.func.isRequired,
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -17,6 +17,7 @@ import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
|||
import StatusContent from 'mastodon/components/status_content';
|
||||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
const messages = defineMessages({
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
|
@ -30,10 +31,6 @@ const messages = defineMessages({
|
|||
|
||||
class Conversation extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
conversationId: PropTypes.string.isRequired,
|
||||
accounts: ImmutablePropTypes.list.isRequired,
|
||||
|
@ -45,6 +42,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
markRead: PropTypes.func.isRequired,
|
||||
delete: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
handleMouseEnter = ({ currentTarget }) => {
|
||||
|
@ -74,7 +72,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleClick = () => {
|
||||
if (!this.context.router) {
|
||||
if (!this.props.history) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -84,7 +82,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
markRead();
|
||||
}
|
||||
|
||||
this.context.router.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`);
|
||||
this.props.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`);
|
||||
};
|
||||
|
||||
handleMarkAsRead = () => {
|
||||
|
@ -92,7 +90,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleReply = () => {
|
||||
this.props.reply(this.props.lastStatus, this.context.router.history);
|
||||
this.props.reply(this.props.lastStatus, this.props.history);
|
||||
};
|
||||
|
||||
handleDelete = () => {
|
||||
|
@ -202,4 +200,4 @@ class Conversation extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(Conversation);
|
||||
export default withRouter(injectIntl(Conversation));
|
||||
|
|
|
@ -36,10 +36,6 @@ const mapStateToProps = state => ({
|
|||
|
||||
class Directory extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
accountIds: ImmutablePropTypes.list.isRequired,
|
||||
|
|
|
@ -32,7 +32,6 @@ const mapStateToProps = state => ({
|
|||
class Explore extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -45,24 +45,20 @@ class Statuses extends PureComponent {
|
|||
const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DismissableBanner id='explore/statuses'>
|
||||
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' />
|
||||
</DismissableBanner>
|
||||
|
||||
<StatusList
|
||||
trackScroll
|
||||
timelineId='explore'
|
||||
statusIds={statusIds}
|
||||
scrollKey='explore-statuses'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
withCounters
|
||||
/>
|
||||
</>
|
||||
<StatusList
|
||||
trackScroll
|
||||
prepend={<DismissableBanner id='explore/statuses'><FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' /></DismissableBanner>}
|
||||
alwaysPrepend
|
||||
timelineId='explore'
|
||||
statusIds={statusIds}
|
||||
scrollKey='explore-statuses'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
withCounters
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { PureComponent } from 'react';
|
|||
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -20,6 +21,7 @@ import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_pick
|
|||
import unicodeMapping from 'mastodon/features/emoji/emoji_unicode_mapping_light';
|
||||
import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'mastodon/initial_state';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -27,14 +29,10 @@ const messages = defineMessages({
|
|||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||
});
|
||||
|
||||
class Content extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
class ContentWithRouter extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
announcement: ImmutablePropTypes.map.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
setRef = c => {
|
||||
|
@ -89,25 +87,25 @@ class Content extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
onMentionClick = (mention, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/@${mention.get('acct')}`);
|
||||
this.props.history.push(`/@${mention.get('acct')}`);
|
||||
}
|
||||
};
|
||||
|
||||
onHashtagClick = (hashtag, e) => {
|
||||
hashtag = hashtag.replace(/^#/, '');
|
||||
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
if (this.props.history&& e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/tags/${hashtag}`);
|
||||
this.props.history.push(`/tags/${hashtag}`);
|
||||
}
|
||||
};
|
||||
|
||||
onStatusClick = (status, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -153,6 +151,8 @@ class Content extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
const Content = withRouter(ContentWithRouter);
|
||||
|
||||
class Emoji extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
|
@ -68,7 +68,6 @@ const badgeDisplay = (number, limit) => {
|
|||
class GettingStarted extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ const getHomeFeedSpeed = createSelector([
|
|||
state => state.getIn(['timelines', 'home', 'pendingItems'], ImmutableList()),
|
||||
state => state.get('statuses'),
|
||||
], (statusIds, pendingStatusIds, statusMap) => {
|
||||
const recentStatusIds = pendingStatusIds.size > 0 ? pendingStatusIds : statusIds;
|
||||
const recentStatusIds = pendingStatusIds.concat(statusIds);
|
||||
const statuses = recentStatusIds.filter(id => id !== null).map(id => statusMap.get(id)).filter(status => status?.get('account') !== me).take(20);
|
||||
|
||||
if (statuses.isEmpty()) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { PureComponent } from 'react';
|
|||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -22,6 +23,7 @@ import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
|||
import { RadioButton } from 'mastodon/components/radio_button';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
|
||||
|
@ -38,10 +40,6 @@ const mapStateToProps = (state, props) => ({
|
|||
|
||||
class ListTimeline extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
|
@ -50,6 +48,7 @@ class ListTimeline extends PureComponent {
|
|||
multiColumn: PropTypes.bool,
|
||||
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
intl: PropTypes.object.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
|
@ -59,7 +58,7 @@ class ListTimeline extends PureComponent {
|
|||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
dispatch(addColumn('LIST', { id: this.props.params.id }));
|
||||
this.context.router.history.push('/');
|
||||
this.props.history.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -137,7 +136,7 @@ class ListTimeline extends PureComponent {
|
|||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
this.context.router.history.push('/lists');
|
||||
this.props.history.push('/lists');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -263,4 +262,4 @@ class ListTimeline extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(ListTimeline));
|
||||
export default withRouter(connect(mapStateToProps)(injectIntl(ListTimeline)));
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -15,6 +15,7 @@ import { Icon } from 'mastodon/components/icon';
|
|||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import StatusContainer from 'mastodon/containers/status_container';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import FollowRequestContainer from '../containers/follow_request_container';
|
||||
|
||||
|
@ -44,11 +45,6 @@ const notificationForScreenReader = (intl, message, timestamp) => {
|
|||
};
|
||||
|
||||
class Notification extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
notification: ImmutablePropTypes.map.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
|
@ -65,6 +61,7 @@ class Notification extends ImmutablePureComponent {
|
|||
cacheMediaWidth: PropTypes.func,
|
||||
cachedMediaWidth: PropTypes.number,
|
||||
unread: PropTypes.bool,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
handleMoveUp = () => {
|
||||
|
@ -81,7 +78,7 @@ class Notification extends ImmutablePureComponent {
|
|||
const { notification } = this.props;
|
||||
|
||||
if (notification.get('status')) {
|
||||
this.context.router.history.push(`/@${notification.getIn(['status', 'account', 'acct'])}/${notification.get('status')}`);
|
||||
this.props.history.push(`/@${notification.getIn(['status', 'account', 'acct'])}/${notification.get('status')}`);
|
||||
} else {
|
||||
this.handleOpenProfile();
|
||||
}
|
||||
|
@ -89,14 +86,14 @@ class Notification extends ImmutablePureComponent {
|
|||
|
||||
handleOpenProfile = () => {
|
||||
const { notification } = this.props;
|
||||
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`);
|
||||
this.props.history.push(`/@${notification.getIn(['account', 'acct'])}`);
|
||||
};
|
||||
|
||||
handleMention = e => {
|
||||
e.preventDefault();
|
||||
|
||||
const { notification, onMention } = this.props;
|
||||
onMention(notification.get('account'), this.context.router.history);
|
||||
onMention(notification.get('account'), this.props.history);
|
||||
};
|
||||
|
||||
handleHotkeyFavourite = () => {
|
||||
|
@ -560,4 +557,4 @@ class Notification extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(Notification);
|
||||
export default withRouter(injectIntl(Notification));
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -19,6 +19,7 @@ import Column from 'mastodon/features/ui/components/column';
|
|||
import { me } from 'mastodon/initial_state';
|
||||
import { makeGetAccount } from 'mastodon/selectors';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import ArrowSmallRight from './components/arrow_small_right';
|
||||
import Step from './components/step';
|
||||
|
@ -38,15 +39,11 @@ const mapStateToProps = () => {
|
|||
};
|
||||
|
||||
class Onboarding extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
multiColumn: PropTypes.bool,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -56,11 +53,10 @@ class Onboarding extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleClose = () => {
|
||||
const { dispatch } = this.props;
|
||||
const { router } = this.context;
|
||||
const { dispatch, history } = this.props;
|
||||
|
||||
dispatch(closeOnboarding());
|
||||
router.history.push('/home');
|
||||
history.push('/home');
|
||||
};
|
||||
|
||||
handleProfileClick = () => {
|
||||
|
@ -72,10 +68,9 @@ class Onboarding extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleComposeClick = () => {
|
||||
const { dispatch, intl } = this.props;
|
||||
const { router } = this.context;
|
||||
const { dispatch, intl, history } = this.props;
|
||||
|
||||
dispatch(focusCompose(router.history, intl.formatMessage(messages.template)));
|
||||
dispatch(focusCompose(history, intl.formatMessage(messages.template)));
|
||||
};
|
||||
|
||||
handleShareClick = () => {
|
||||
|
@ -153,4 +148,4 @@ class Onboarding extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Onboarding));
|
||||
export default withRouter(connect(mapStateToProps)(injectIntl(Onboarding)));
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -15,6 +16,7 @@ import { openModal } from 'mastodon/actions/modal';
|
|||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { me, boostModal } from 'mastodon/initial_state';
|
||||
import { makeGetStatus } from 'mastodon/selectors';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
const messages = defineMessages({
|
||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||
|
@ -43,7 +45,6 @@ const makeMapStateToProps = () => {
|
|||
class Footer extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -55,17 +56,17 @@ class Footer extends ImmutablePureComponent {
|
|||
askReplyConfirmation: PropTypes.bool,
|
||||
withOpenButton: PropTypes.bool,
|
||||
onClose: PropTypes.func,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
_performReply = () => {
|
||||
const { dispatch, status, onClose } = this.props;
|
||||
const { router } = this.context;
|
||||
const { dispatch, status, onClose, history } = this.props;
|
||||
|
||||
if (onClose) {
|
||||
onClose(true);
|
||||
}
|
||||
|
||||
dispatch(replyCompose(status, router.history));
|
||||
dispatch(replyCompose(status, history));
|
||||
};
|
||||
|
||||
handleReplyClick = () => {
|
||||
|
@ -149,9 +150,7 @@ class Footer extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleOpenClick = e => {
|
||||
const { router } = this.context;
|
||||
|
||||
if (e.button !== 0 || !router) {
|
||||
if (e.button !== 0 || !history) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -161,7 +160,7 @@ class Footer extends ImmutablePureComponent {
|
|||
onClose();
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
};
|
||||
|
||||
render () {
|
||||
|
@ -204,4 +203,4 @@ class Footer extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps)(injectIntl(Footer));
|
||||
export default withRouter(connect(makeMapStateToProps)(injectIntl(Footer)));
|
||||
|
|
|
@ -41,7 +41,6 @@ const mapStateToProps = (state, { columnId }) => {
|
|||
class PublicTimeline extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ import { PureComponent } from 'react';
|
|||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
|
@ -63,7 +65,6 @@ const mapStateToProps = (state, { status }) => ({
|
|||
class ActionBar extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -94,6 +95,7 @@ class ActionBar extends PureComponent {
|
|||
onPin: PropTypes.func,
|
||||
onEmbed: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
handleOpenMentions = () => {
|
||||
|
@ -129,23 +131,23 @@ class ActionBar extends PureComponent {
|
|||
};
|
||||
|
||||
handleDeleteClick = () => {
|
||||
this.props.onDelete(this.props.status, this.context.router.history);
|
||||
this.props.onDelete(this.props.status, this.props.history);
|
||||
};
|
||||
|
||||
handleRedraftClick = () => {
|
||||
this.props.onDelete(this.props.status, this.context.router.history, true);
|
||||
this.props.onDelete(this.props.status, this.props.history, true);
|
||||
};
|
||||
|
||||
handleEditClick = () => {
|
||||
this.props.onEdit(this.props.status, this.context.router.history);
|
||||
this.props.onEdit(this.props.status, this.props.history);
|
||||
};
|
||||
|
||||
handleDirectClick = () => {
|
||||
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
|
||||
this.props.onDirect(this.props.status.get('account'), this.props.history);
|
||||
};
|
||||
|
||||
handleMentionClick = () => {
|
||||
this.props.onMention(this.props.status.get('account'), this.context.router.history);
|
||||
this.props.onMention(this.props.status.get('account'), this.props.history);
|
||||
};
|
||||
|
||||
handleMuteClick = () => {
|
||||
|
@ -373,4 +375,4 @@ class ActionBar extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(ActionBar));
|
||||
export default withRouter(connect(mapStateToProps)(injectIntl(ActionBar)));
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { injectIntl, defineMessages, FormattedDate, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -14,6 +14,7 @@ import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar';
|
|||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
import { enableEmojiReaction } from 'mastodon/initial_state';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
|
@ -46,10 +47,6 @@ const messages = defineMessages({
|
|||
|
||||
class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
onOpenMedia: PropTypes.func.isRequired,
|
||||
|
@ -68,6 +65,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
onToggleMediaVisibility: PropTypes.func,
|
||||
onEmojiReact: PropTypes.func,
|
||||
onUnEmojiReact: PropTypes.func,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -75,9 +73,9 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
this.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
|
@ -282,7 +280,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
|
||||
if (['private', 'direct'].includes(status.get('visibility_ex'))) {
|
||||
reblogLink = '';
|
||||
} else if (this.context.router) {
|
||||
} else if (this.props.history) {
|
||||
reblogLink = (
|
||||
<>
|
||||
{' · '}
|
||||
|
@ -308,7 +306,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.context.router) {
|
||||
if (this.props.history) {
|
||||
favouriteLink = (
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||
<Icon id='star' />
|
||||
|
@ -420,4 +418,4 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(DetailedStatus);
|
||||
export default withRouter(injectIntl(DetailedStatus));
|
||||
|
|
|
@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||
|
||||
import classNames from 'classnames';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import Immutable from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
@ -17,6 +18,7 @@ import { Icon } from 'mastodon/components/icon';
|
|||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import {
|
||||
unblockAccount,
|
||||
|
@ -71,6 +73,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
|
|||
import ActionBar from './components/action_bar';
|
||||
import DetailedStatus from './components/detailed_status';
|
||||
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||
|
@ -199,7 +202,6 @@ const titleFromStatus = (intl, status) => {
|
|||
class Status extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -219,6 +221,7 @@ class Status extends ImmutablePureComponent {
|
|||
inUse: PropTypes.bool,
|
||||
available: PropTypes.bool,
|
||||
}),
|
||||
...WithRouterPropTypes
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -312,11 +315,11 @@ class Status extends ImmutablePureComponent {
|
|||
modalProps: {
|
||||
message: intl.formatMessage(messages.replyMessage),
|
||||
confirm: intl.formatMessage(messages.replyConfirm),
|
||||
onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
|
||||
onConfirm: () => dispatch(replyCompose(status, this.props.history)),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(replyCompose(status, this.context.router.history));
|
||||
dispatch(replyCompose(status, this.props.history));
|
||||
}
|
||||
} else {
|
||||
dispatch(openModal({
|
||||
|
@ -560,7 +563,7 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
};
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
|
@ -816,4 +819,4 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps)(Status));
|
||||
export default withRouter(injectIntl(connect(makeMapStateToProps)(Status)));
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -12,6 +13,7 @@ import { changeBoostPrivacy } from 'mastodon/actions/boosts';
|
|||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import Button from '../../../components/button';
|
||||
|
@ -49,11 +51,6 @@ const mapDispatchToProps = dispatch => {
|
|||
};
|
||||
|
||||
class BoostModal extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
|
@ -61,6 +58,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
onChangeBoostPrivacy: PropTypes.func.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -76,7 +74,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -155,4 +153,4 @@ class BoostModal extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(BoostModal));
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(injectIntl(BoostModal)));
|
||||
|
|
|
@ -54,11 +54,6 @@ const componentMap = {
|
|||
};
|
||||
|
||||
export default class ColumnsArea extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
columns: ImmutablePropTypes.list.isRequired,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
|
|
|
@ -100,7 +100,7 @@ class LinkFooter extends PureComponent {
|
|||
{DividingCircle}
|
||||
<a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
|
||||
{DividingCircle}
|
||||
<span class='version'>v{version}</span>
|
||||
<span className='version'>v{version}</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -71,4 +69,4 @@ class ListPanel extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default withRouter(connect(mapStateToProps)(ListPanel));
|
||||
export default connect(mapStateToProps)(ListPanel);
|
||||
|
|
|
@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { WordmarkLogo } from 'mastodon/components/logo';
|
||||
import NavigationPortal from 'mastodon/components/navigation_portal';
|
||||
import { NavigationPortal } from 'mastodon/components/navigation_portal';
|
||||
import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag } from 'mastodon/initial_state';
|
||||
import { transientSingleColumn } from 'mastodon/is_mobile';
|
||||
|
||||
|
@ -41,7 +41,6 @@ const messages = defineMessages({
|
|||
class NavigationPanel extends Component {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
identity: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
@ -66,25 +65,31 @@ class NavigationPanel extends Component {
|
|||
) : (
|
||||
<ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />
|
||||
));
|
||||
|
||||
let banner = undefined;
|
||||
|
||||
if(transientSingleColumn)
|
||||
banner = (<div className='switch-to-advanced'>
|
||||
{intl.formatMessage(messages.openedInClassicInterface)}
|
||||
{" "}
|
||||
<a href={`/deck${location.pathname}`} className='switch-to-advanced__toggle'>
|
||||
{intl.formatMessage(messages.advancedInterface)}
|
||||
</a>
|
||||
</div>);
|
||||
|
||||
return (
|
||||
<div className='navigation-panel'>
|
||||
<div className='navigation-panel__logo'>
|
||||
<Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>
|
||||
|
||||
{transientSingleColumn ? (
|
||||
<div class='switch-to-advanced'>
|
||||
{intl.formatMessage(messages.openedInClassicInterface)}
|
||||
{" "}
|
||||
<a href={`/deck${location.pathname}`} class='switch-to-advanced__toggle'>
|
||||
{intl.formatMessage(messages.advancedInterface)}
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<hr />
|
||||
)}
|
||||
{!banner && <hr />}
|
||||
</div>
|
||||
|
||||
{banner &&
|
||||
<div class='navigation-panel__banner'>
|
||||
{banner}
|
||||
</div>
|
||||
}
|
||||
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
|
||||
|
|
|
@ -16,6 +16,7 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodo
|
|||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||
import PictureInPicture from 'mastodon/features/picture_in_picture';
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
||||
import { clearHeight } from '../../actions/height_cache';
|
||||
|
@ -278,7 +279,6 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
class UI extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
identity: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
@ -289,12 +289,12 @@ class UI extends PureComponent {
|
|||
hasComposingText: PropTypes.bool,
|
||||
hasMediaAttachments: PropTypes.bool,
|
||||
canUploadMore: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
intl: PropTypes.object.isRequired,
|
||||
dropdownMenuIsOpen: PropTypes.bool,
|
||||
layout: PropTypes.string.isRequired,
|
||||
firstLaunch: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -391,7 +391,7 @@ class UI extends PureComponent {
|
|||
|
||||
handleServiceWorkerPostMessage = ({ data }) => {
|
||||
if (data.type === 'navigate') {
|
||||
this.context.router.history.push(data.path);
|
||||
this.props.history.push(data.path);
|
||||
} else {
|
||||
console.warn('Unknown message type:', data.type);
|
||||
}
|
||||
|
@ -512,12 +512,12 @@ class UI extends PureComponent {
|
|||
};
|
||||
|
||||
handleHotkeyBack = () => {
|
||||
const { router } = this.context;
|
||||
const { history } = this.props;
|
||||
|
||||
if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
if (history.location?.state?.fromMastodon) {
|
||||
history.goBack();
|
||||
} else {
|
||||
router.history.push('/');
|
||||
history.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -527,38 +527,38 @@ class UI extends PureComponent {
|
|||
|
||||
handleHotkeyToggleHelp = () => {
|
||||
if (this.props.location.pathname === '/keyboard-shortcuts') {
|
||||
this.context.router.history.goBack();
|
||||
this.props.history.goBack();
|
||||
} else {
|
||||
this.context.router.history.push('/keyboard-shortcuts');
|
||||
this.props.history.push('/keyboard-shortcuts');
|
||||
}
|
||||
};
|
||||
|
||||
handleHotkeyGoToHome = () => {
|
||||
this.context.router.history.push('/home');
|
||||
this.props.history.push('/home');
|
||||
};
|
||||
|
||||
handleHotkeyGoToNotifications = () => {
|
||||
this.context.router.history.push('/notifications');
|
||||
this.props.history.push('/notifications');
|
||||
};
|
||||
|
||||
handleHotkeyGoToLocal = () => {
|
||||
this.context.router.history.push('/public/local');
|
||||
this.props.history.push('/public/local');
|
||||
};
|
||||
|
||||
handleHotkeyGoToFederated = () => {
|
||||
this.context.router.history.push('/public');
|
||||
this.props.history.push('/public');
|
||||
};
|
||||
|
||||
handleHotkeyGoToDirect = () => {
|
||||
this.context.router.history.push('/conversations');
|
||||
this.props.history.push('/conversations');
|
||||
};
|
||||
|
||||
handleHotkeyGoToStart = () => {
|
||||
this.context.router.history.push('/getting-started');
|
||||
this.props.history.push('/getting-started');
|
||||
};
|
||||
|
||||
handleHotkeyGoToFavourites = () => {
|
||||
this.context.router.history.push('/favourites');
|
||||
this.props.history.push('/favourites');
|
||||
};
|
||||
|
||||
handleHotkeyGoToEmojiReactions = () => {
|
||||
|
@ -566,23 +566,23 @@ class UI extends PureComponent {
|
|||
};
|
||||
|
||||
handleHotkeyGoToPinned = () => {
|
||||
this.context.router.history.push('/pinned');
|
||||
this.props.history.push('/pinned');
|
||||
};
|
||||
|
||||
handleHotkeyGoToProfile = () => {
|
||||
this.context.router.history.push(`/@${this.props.username}`);
|
||||
this.props.history.push(`/@${this.props.username}`);
|
||||
};
|
||||
|
||||
handleHotkeyGoToBlocked = () => {
|
||||
this.context.router.history.push('/blocks');
|
||||
this.props.history.push('/blocks');
|
||||
};
|
||||
|
||||
handleHotkeyGoToMuted = () => {
|
||||
this.context.router.history.push('/mutes');
|
||||
this.props.history.push('/mutes');
|
||||
};
|
||||
|
||||
handleHotkeyGoToRequests = () => {
|
||||
this.context.router.history.push('/follow_requests');
|
||||
this.props.history.push('/follow_requests');
|
||||
};
|
||||
|
||||
render () {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { Component, PureComponent, cloneElement, Children } from 'react';
|
||||
import { Component, cloneElement, Children } from 'react';
|
||||
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { Switch, Route, useLocation } from 'react-router-dom';
|
||||
|
||||
import StackTrace from 'stacktrace-js';
|
||||
|
||||
|
@ -10,27 +10,20 @@ import ColumnLoading from '../components/column_loading';
|
|||
import BundleContainer from '../containers/bundle_container';
|
||||
|
||||
// Small wrapper to pass multiColumn to the route components
|
||||
export class WrappedSwitch extends PureComponent {
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
export const WrappedSwitch = ({ multiColumn, children }) => {
|
||||
const location = useLocation();
|
||||
|
||||
render () {
|
||||
const { multiColumn, children } = this.props;
|
||||
const { location } = this.context.router.route;
|
||||
const decklessLocation = multiColumn && location.pathname.startsWith('/deck')
|
||||
? {...location, pathname: location.pathname.slice(5)}
|
||||
: location;
|
||||
|
||||
const decklessLocation = multiColumn && location.pathname.startsWith('/deck')
|
||||
? {...location, pathname: location.pathname.slice(5)}
|
||||
: location;
|
||||
return (
|
||||
<Switch location={decklessLocation}>
|
||||
{Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch location={decklessLocation}>
|
||||
{Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WrappedSwitch.propTypes = {
|
||||
multiColumn: PropTypes.bool,
|
||||
|
|
|
@ -71,11 +71,11 @@
|
|||
"account.unmute_notifications_short": "Poista ilmoitusten mykistys",
|
||||
"account.unmute_short": "Poista mykistys",
|
||||
"account_note.placeholder": "Lisää muistiinpano napsauttamalla",
|
||||
"admin.dashboard.daily_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen päivään mennessä",
|
||||
"admin.dashboard.monthly_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen kuukauteen mennessä",
|
||||
"admin.dashboard.daily_retention": "Käyttäjien pysyvyys rekisteröitymisen jälkeen päivittäin",
|
||||
"admin.dashboard.monthly_retention": "Käyttäjien pysyvyys rekisteröitymisen jälkeen kuukausittain",
|
||||
"admin.dashboard.retention.average": "Keskimäärin",
|
||||
"admin.dashboard.retention.cohort": "Kirjautumiset",
|
||||
"admin.dashboard.retention.cohort_size": "Uudet käyttäjät",
|
||||
"admin.dashboard.retention.cohort": "Rekisteröitymis-kk.",
|
||||
"admin.dashboard.retention.cohort_size": "Uusia käyttäjiä",
|
||||
"admin.impact_report.instance_accounts": "Tilien profiilit, jotka tämä poistaisi",
|
||||
"admin.impact_report.instance_followers": "Seuraajat, jotka käyttäjämme menettäisivät",
|
||||
"admin.impact_report.instance_follows": "Seuraajat, jotka heidän käyttäjänsä menettäisivät",
|
||||
|
@ -114,7 +114,7 @@
|
|||
"column.directory": "Selaa profiileja",
|
||||
"column.domain_blocks": "Estetyt verkkotunnukset",
|
||||
"column.favourites": "Suosikit",
|
||||
"column.firehose": "Live-syötteet",
|
||||
"column.firehose": "Livesyötteet",
|
||||
"column.follow_requests": "Seuraamispyynnöt",
|
||||
"column.home": "Koti",
|
||||
"column.lists": "Listat",
|
||||
|
@ -135,7 +135,7 @@
|
|||
"community.column_settings.remote_only": "Vain etätilit",
|
||||
"compose.language.change": "Vaihda kieli",
|
||||
"compose.language.search": "Hae kieliä...",
|
||||
"compose.published.body": "Julkaisusi julkaistiin.",
|
||||
"compose.published.body": "Julkaisu lähetetty.",
|
||||
"compose.published.open": "Avaa",
|
||||
"compose.saved.body": "Julkaisu tallennettu.",
|
||||
"compose_form.direct_message_warning_learn_more": "Lisätietoja",
|
||||
|
@ -436,10 +436,10 @@
|
|||
"notifications.clear": "Tyhjennä ilmoitukset",
|
||||
"notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?",
|
||||
"notifications.column_settings.admin.report": "Uudet ilmoitukset:",
|
||||
"notifications.column_settings.admin.sign_up": "Uudet kirjautumiset:",
|
||||
"notifications.column_settings.admin.sign_up": "Uudet rekisteröitymiset:",
|
||||
"notifications.column_settings.alert": "Työpöytäilmoitukset",
|
||||
"notifications.column_settings.favourite": "Suosikit:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Näytä kaikki kategoriat",
|
||||
"notifications.column_settings.filter_bar.advanced": "Näytä kaikki luokat",
|
||||
"notifications.column_settings.filter_bar.category": "Pikasuodatuspalkki",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Näytä suodatinpalkki",
|
||||
"notifications.column_settings.follow": "Uudet seuraajat:",
|
||||
|
@ -517,7 +517,7 @@
|
|||
"privacy.private.short": "Vain seuraajat",
|
||||
"privacy.public.long": "Näkyy kaikille",
|
||||
"privacy.public.short": "Julkinen",
|
||||
"privacy.unlisted.long": "Näkyy kaikille, mutta jää pois löytämisominaisuuksista",
|
||||
"privacy.unlisted.long": "Näkyy kaikille mutta jää pois löytämisominaisuuksista",
|
||||
"privacy.unlisted.short": "Listaamaton",
|
||||
"privacy_policy.last_updated": "Viimeksi päivitetty {date}",
|
||||
"privacy_policy.title": "Tietosuojakäytäntö",
|
||||
|
@ -589,13 +589,13 @@
|
|||
"search.quick_action.go_to_hashtag": "Siirry aihetunnisteeseen {x}",
|
||||
"search.quick_action.open_url": "Avaa URL-osoite Mastodonissa",
|
||||
"search.quick_action.status_search": "Julkaisut haulla {x}",
|
||||
"search.search_or_paste": "Hae tai kirjoita URL-osoite",
|
||||
"search.search_or_paste": "Hae tai liitä URL-osoite",
|
||||
"search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.",
|
||||
"search_popout.language_code": "ISO-kielikoodi",
|
||||
"search_popout.options": "Hakuvalinnat",
|
||||
"search_popout.quick_actions": "Pikatoiminnot",
|
||||
"search_popout.recent": "Viimeaikaiset haut",
|
||||
"search_popout.specific_date": "tietty päivämäärä",
|
||||
"search_popout.specific_date": "tarkka päiväys",
|
||||
"search_popout.user": "käyttäjä",
|
||||
"search_results.accounts": "Profiilit",
|
||||
"search_results.all": "Kaikki",
|
||||
|
|
|
@ -209,7 +209,7 @@
|
|||
"compose.language.search": "言語を検索...",
|
||||
"compose.published.body": "投稿されました!",
|
||||
"compose.published.open": "開く",
|
||||
"compose.saved.body": "投稿が保存されました",
|
||||
"compose.saved.body": "変更を保存しました。",
|
||||
"compose_form.direct_message_warning_learn_more": "もっと詳しく",
|
||||
"compose_form.encryption_warning": "Mastodonの投稿はエンドツーエンド暗号化に対応していません。安全に送受信されるべき情報をMastodonで共有しないでください。",
|
||||
"compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。",
|
||||
|
|
|
@ -204,7 +204,7 @@
|
|||
"dismissable_banner.explore_links": "이 소식들은 오늘 소셜 웹에서 가장 많이 공유된 내용들입니다. 새 소식을 더 많은 사람들이 공유할수록 높은 순위가 됩니다.",
|
||||
"dismissable_banner.explore_statuses": "이 게시물들은 오늘 소셜 웹에서 호응을 얻고 있는 게시물들입니다. 부스트와 관심을 받는 새로운 글들이 높은 순위가 됩니다.",
|
||||
"dismissable_banner.explore_tags": "이 해시태그들은 이 서버와 분산화된 네트워크의 다른 서버에서 사람들의 인기를 끌고 있는 것들입니다.",
|
||||
"dismissable_banner.public_timeline": "이것들은 {domain}에 있는 사람들이 팔로우한 사람들의 최신 게시물들입니다.",
|
||||
"dismissable_banner.public_timeline": "{domain} 사람들이 팔로우하는 소셜 웹 사람들의 최신 공개 게시물입니다.",
|
||||
"embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.",
|
||||
"embed.preview": "이렇게 표시됩니다:",
|
||||
"emoji_button.activity": "활동",
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
"compose_form.publish": "Legg ut",
|
||||
"compose_form.publish_form": "Legg ut",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.save_changes": "Gøym",
|
||||
"compose_form.save_changes": "Lagre endringar",
|
||||
"compose_form.sensitive.hide": "{count, plural, one {Marker mediet som ømtolig} other {Marker media som ømtolige}}",
|
||||
"compose_form.sensitive.marked": "{count, plural, one {Mediet er markert som ømtolig} other {Media er markerte som ømtolige}}",
|
||||
"compose_form.sensitive.unmarked": "{count, plural, one {Mediet er ikkje markert som ømtolig} other {Media er ikkje markerte som ømtolige}}",
|
||||
|
@ -171,7 +171,7 @@
|
|||
"confirmations.delete_list.confirm": "Slett",
|
||||
"confirmations.delete_list.message": "Er du sikker på at du vil sletta denne lista for alltid?",
|
||||
"confirmations.discard_edit_media.confirm": "Forkast",
|
||||
"confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkaste dei likevel?",
|
||||
"confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkasta dei likevel?",
|
||||
"confirmations.domain_block.confirm": "Skjul alt frå domenet",
|
||||
"confirmations.domain_block.message": "Er du heilt, heilt sikker på at du vil skjula heile {domain}? I dei fleste tilfelle er det godt nok og føretrekt med nokre få målretta blokkeringar eller målbindingar. Du kjem ikkje til å sjå innhald frå domenet i fødererte tidsliner eller i varsla dine. Fylgjarane dine frå domenet vert fjerna.",
|
||||
"confirmations.edit.confirm": "Rediger",
|
||||
|
@ -285,7 +285,7 @@
|
|||
"footer.privacy_policy": "Personvernsreglar",
|
||||
"footer.source_code": "Vis kjeldekode",
|
||||
"footer.status": "Status",
|
||||
"generic.saved": "Gøymt",
|
||||
"generic.saved": "Lagra",
|
||||
"getting_started.heading": "Kom i gang",
|
||||
"hashtag.column_header.tag_mode.all": "og {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "eller {additional}",
|
||||
|
@ -314,7 +314,7 @@
|
|||
"home.pending_critical_update.link": "Sjå oppdateringar",
|
||||
"home.pending_critical_update.title": "Kritisk sikkerheitsoppdatering er tilgjengeleg!",
|
||||
"home.show_announcements": "Vis kunngjeringar",
|
||||
"interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerkja dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.",
|
||||
"interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerka dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.",
|
||||
"interaction_modal.description.follow": "Med ein konto på Mastodon kan du fylgja {name} for å sjå innlegga deira i din heimestraum.",
|
||||
"interaction_modal.description.reblog": "Med ein konto på Mastodon kan du framheva dette innlegget for å dela det med dine eigne fylgjarar.",
|
||||
"interaction_modal.description.reply": "Med ein konto på Mastodon kan du svara på dette innlegget.",
|
||||
|
@ -673,7 +673,7 @@
|
|||
"status.unmute_conversation": "Opphev målbinding av samtalen",
|
||||
"status.unpin": "Løys frå profil",
|
||||
"subscribed_languages.lead": "Kun innlegg på valde språk vil bli dukke opp i heimestraumen din og i listene dine etter denne endringa. For å motta innlegg på alle språk, la vere å velje nokon.",
|
||||
"subscribed_languages.save": "Gøym",
|
||||
"subscribed_languages.save": "Lagre endringar",
|
||||
"subscribed_languages.target": "Endre abonnerte språk for {target}",
|
||||
"tabs_bar.home": "Heim",
|
||||
"tabs_bar.notifications": "Varsel",
|
||||
|
|
|
@ -303,7 +303,7 @@
|
|||
"hashtag.unfollow": "Отпрати хеш ознаку",
|
||||
"hashtags.and_other": "…и {count, plural, one {још #} few {још #}other {још #}}",
|
||||
"home.actions.go_to_explore": "Погледате шта је у тренду",
|
||||
"home.actions.go_to_suggestions": "Пронађeте људе које бисте пратили",
|
||||
"home.actions.go_to_suggestions": "Пронађете људе које бисте пратили",
|
||||
"home.column_settings.basic": "Основна",
|
||||
"home.column_settings.show_reblogs": "Прикажи подржавања",
|
||||
"home.column_settings.show_replies": "Прикажи одговоре",
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
"column.directory": "瀏覽個人檔案",
|
||||
"column.domain_blocks": "已封鎖網域",
|
||||
"column.favourites": "最愛",
|
||||
"column.firehose": "即時內容",
|
||||
"column.firehose": "即時河道",
|
||||
"column.follow_requests": "跟隨請求",
|
||||
"column.home": "首頁",
|
||||
"column.lists": "列表",
|
||||
|
|
60
app/javascript/mastodon/utils/react_router.jsx
Normal file
60
app/javascript/mastodon/utils/react_router.jsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import PropTypes from "prop-types";
|
||||
|
||||
import { __RouterContext } from "react-router";
|
||||
|
||||
import hoistStatics from "hoist-non-react-statics";
|
||||
|
||||
export const WithRouterPropTypes = {
|
||||
match: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export const WithOptionalRouterPropTypes = {
|
||||
match: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
};
|
||||
|
||||
// This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js
|
||||
// but does not fail if called outside of a React Router context
|
||||
export function withOptionalRouter(Component) {
|
||||
const displayName = `withRouter(${Component.displayName || Component.name})`;
|
||||
const C = props => {
|
||||
const { wrappedComponentRef, ...remainingProps } = props;
|
||||
|
||||
return (
|
||||
<__RouterContext.Consumer>
|
||||
{context => {
|
||||
if(context)
|
||||
return (
|
||||
<Component
|
||||
{...remainingProps}
|
||||
{...context}
|
||||
ref={wrappedComponentRef}
|
||||
/>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<Component
|
||||
{...remainingProps}
|
||||
ref={wrappedComponentRef}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</__RouterContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
C.displayName = displayName;
|
||||
C.WrappedComponent = Component;
|
||||
C.propTypes = {
|
||||
wrappedComponentRef: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.func,
|
||||
PropTypes.object
|
||||
])
|
||||
};
|
||||
|
||||
return hoistStatics(C, Component);
|
||||
}
|
|
@ -2536,6 +2536,7 @@ $ui-header-height: 55px;
|
|||
|
||||
.navigation-panel__sign-in-banner,
|
||||
.navigation-panel__logo,
|
||||
.navigation-panel__banner,
|
||||
.getting-started__trends {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ class ActivityPub::LinkedDataSignature
|
|||
|
||||
return unless type == 'RsaSignature2017'
|
||||
|
||||
creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
|
||||
creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false)
|
||||
creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
|
||||
creator = ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false) if creator&.public_key.blank?
|
||||
|
||||
return if creator.nil?
|
||||
|
||||
|
@ -28,6 +28,8 @@ class ActivityPub::LinkedDataSignature
|
|||
to_be_verified = options_hash + document_hash
|
||||
|
||||
creator if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified)
|
||||
rescue OpenSSL::PKey::RSAError
|
||||
false
|
||||
end
|
||||
|
||||
def sign!(creator, sign_with: nil)
|
||||
|
|
|
@ -51,7 +51,7 @@ class FeaturedTag < ApplicationRecord
|
|||
private
|
||||
|
||||
def strip_name
|
||||
self.name = name&.strip&.gsub(/\A#/, '')
|
||||
self.name = name&.strip&.delete_prefix('#')
|
||||
end
|
||||
|
||||
def set_tag
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::ApplicationSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :website, :redirect_uri,
|
||||
attributes :id, :name, :website, :scopes, :redirect_uri,
|
||||
:client_id, :client_secret, :vapid_key
|
||||
|
||||
def id
|
||||
|
|
|
@ -101,8 +101,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
added_media_attachments = @next_media_attachments - previous_media_attachments
|
||||
|
||||
@status.ordered_media_attachment_ids = @next_media_attachments.map(&:id)
|
||||
|
||||
@media_attachments_changed = true if @status.ordered_media_attachment_ids != previous_media_attachments_ids
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
= image_tag appeal.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar'
|
||||
.log-entry__content
|
||||
.log-entry__title
|
||||
= t(appeal.strike.action, scope: 'admin.strikes.actions', name: content_tag(:span, appeal.strike.account.username, class: 'username'), target: content_tag(:span, appeal.account.username, class: 'target')).html_safe
|
||||
= strike_action_label(appeal)
|
||||
.log-entry__timestamp
|
||||
%time.formatted{ datetime: appeal.strike.created_at.iso8601 }
|
||||
= l(appeal.strike.created_at)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
.batch-table__row__content.pending-account
|
||||
.pending-account__header
|
||||
= link_to preview_card.title, preview_card.url
|
||||
= link_to preview_card.title, url_for_preview_card(preview_card)
|
||||
|
||||
%br/
|
||||
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
{
|
||||
"ignored_warnings": [
|
||||
{
|
||||
"warning_type": "Cross-Site Scripting",
|
||||
"warning_code": 2,
|
||||
"fingerprint": "71cf98c8235b5cfa9946b5db8fdc1a2f3a862566abb34e4542be6f3acae78233",
|
||||
"check_name": "CrossSiteScripting",
|
||||
"message": "Unescaped model attribute",
|
||||
"file": "app/views/admin/disputes/appeals/_appeal.html.haml",
|
||||
"line": 7,
|
||||
"link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
|
||||
"code": "t((Unresolved Model).new.strike.action, :scope => \"admin.strikes.actions\", :name => content_tag(:span, (Unresolved Model).new.strike.account.username, :class => \"username\"), :target => content_tag(:span, (Unresolved Model).new.account.username, :class => \"target\"))",
|
||||
"render_path": [
|
||||
{
|
||||
"type": "template",
|
||||
"name": "admin/disputes/appeals/index",
|
||||
"line": 20,
|
||||
"file": "app/views/admin/disputes/appeals/index.html.haml",
|
||||
"rendered": {
|
||||
"name": "admin/disputes/appeals/_appeal",
|
||||
"file": "app/views/admin/disputes/appeals/_appeal.html.haml"
|
||||
}
|
||||
}
|
||||
],
|
||||
"location": {
|
||||
"type": "template",
|
||||
"template": "admin/disputes/appeals/_appeal"
|
||||
},
|
||||
"user_input": "(Unresolved Model).new.strike",
|
||||
"confidence": "Weak",
|
||||
"cwe_id": [
|
||||
79
|
||||
],
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "Cross-Site Scripting",
|
||||
"warning_code": 4,
|
||||
"fingerprint": "cd5cfd7f40037fbfa753e494d7129df16e358bfc43ef0da3febafbf4ee1ed3ac",
|
||||
"check_name": "LinkToHref",
|
||||
"message": "Potentially unsafe model attribute in `link_to` href",
|
||||
"file": "app/views/admin/trends/links/_preview_card.html.haml",
|
||||
"line": 7,
|
||||
"link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
|
||||
"code": "link_to((Unresolved Model).new.title, (Unresolved Model).new.url)",
|
||||
"render_path": [
|
||||
{
|
||||
"type": "template",
|
||||
"name": "admin/trends/links/index",
|
||||
"line": 49,
|
||||
"file": "app/views/admin/trends/links/index.html.haml",
|
||||
"rendered": {
|
||||
"name": "admin/trends/links/_preview_card",
|
||||
"file": "app/views/admin/trends/links/_preview_card.html.haml"
|
||||
}
|
||||
}
|
||||
],
|
||||
"location": {
|
||||
"type": "template",
|
||||
"template": "admin/trends/links/_preview_card"
|
||||
},
|
||||
"user_input": "(Unresolved Model).new.url",
|
||||
"confidence": "Weak",
|
||||
"cwe_id": [
|
||||
79
|
||||
],
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "Mass Assignment",
|
||||
"warning_code": 105,
|
||||
"fingerprint": "d0511f0287aea4ed9511f5a744f880cb15af77a8ec88f81b7365b00b642cf427",
|
||||
"check_name": "PermitAttributes",
|
||||
"message": "Potentially dangerous key allowed for mass assignment",
|
||||
"file": "app/controllers/api/v1/reports_controller.rb",
|
||||
"line": 26,
|
||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
||||
"code": "params.permit(:account_id, :comment, :category, :forward, :forward_to_domains => ([]), :status_ids => ([]), :rule_ids => ([]))",
|
||||
"render_path": null,
|
||||
"location": {
|
||||
"type": "method",
|
||||
"class": "Api::V1::ReportsController",
|
||||
"method": "report_params"
|
||||
},
|
||||
"user_input": ":account_id",
|
||||
"confidence": "High",
|
||||
"cwe_id": [
|
||||
915
|
||||
],
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "Mass Assignment",
|
||||
"warning_code": 105,
|
||||
"fingerprint": "dd59382eb5fda8da4d29f5d52dc8261ed9070d3c61cecc12b8332e18448b1c11",
|
||||
"check_name": "PermitAttributes",
|
||||
"message": "Potentially dangerous key allowed for mass assignment",
|
||||
"file": "app/controllers/api/v2/search_controller.rb",
|
||||
"line": 42,
|
||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
||||
"code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :following, :searchability)",
|
||||
"render_path": null,
|
||||
"location": {
|
||||
"type": "method",
|
||||
"class": "Api::V2::SearchController",
|
||||
"method": "search_params"
|
||||
},
|
||||
"user_input": ":account_id",
|
||||
"confidence": "High",
|
||||
"cwe_id": [
|
||||
915
|
||||
],
|
||||
"note": ""
|
||||
}
|
||||
],
|
||||
"updated": "2023-07-30 22:53:30 +0900",
|
||||
"brakeman_version": "6.0.1"
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
---
|
||||
:skip_checks:
|
||||
- CheckPermitAttributes
|
||||
:url_safe_methods:
|
||||
- url_for_preview_card
|
||||
|
|
|
@ -9,9 +9,6 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|||
end
|
||||
|
||||
Devise.setup do |config|
|
||||
# Devise omniauth strategies
|
||||
options = {}
|
||||
|
||||
# CAS strategy
|
||||
if ENV['CAS_ENABLED'] == 'true'
|
||||
cas_options = {}
|
||||
|
|
|
@ -53,3 +53,7 @@ sk:
|
|||
position:
|
||||
elevated: nemôže byť vyššia ako vaša súčasná rola
|
||||
own_role: nie je možné zmeniť s vašou aktuálnou rolou
|
||||
webhook:
|
||||
attributes:
|
||||
events:
|
||||
invalid_permissions: nemožno zahrnúť udalosti, ku ktorým nemáte práva
|
||||
|
|
|
@ -129,6 +129,7 @@ sk:
|
|||
crypto: Šifrovanie End-to-end
|
||||
favourites: Obľúbené
|
||||
filters: Filtre
|
||||
follow: Sledovanie, stlmenie a blokovanie
|
||||
follows: Sledovania
|
||||
lists: Zoznamy
|
||||
media: Mediálne prílohy
|
||||
|
@ -148,9 +149,19 @@ sk:
|
|||
scopes:
|
||||
admin:read: prezeraj všetky dáta na serveri
|
||||
admin:read:accounts: prezeraj chúlostivé informácie na všetkých účtoch
|
||||
admin:read:canonical_email_blocks: čítať citlivé informácie všetkých kanonických e-mailových blokov
|
||||
admin:read:domain_allows: čítať citlivé informácie zo všetkých povolených domén
|
||||
admin:read:domain_blocks: čítať citlivé informácie zo všetkých blokov domén
|
||||
admin:read:email_domain_blocks: čítať citlivé informácie zo všetkých blokov emailových domén
|
||||
admin:read:ip_blocks: čítať citlivé informácie zo všetkých blokov IP
|
||||
admin:read:reports: čítaj chulostivé informácie o všetkých hláseniach a nahlásených účtoch
|
||||
admin:write: uprav všetky dáta na serveri
|
||||
admin:write:accounts: urob moderovacie úkony na účtoch
|
||||
admin:write:canonical_email_blocks: vykonať akcie moderácie na kanonických emailových blokoch
|
||||
admin:write:domain_allows: vykonať akcie moderácie na povolených doménach
|
||||
admin:write:domain_blocks: vykonať akcie moderácie na doménových blokoch
|
||||
admin:write:email_domain_blocks: vykonať akcie moderácie na blokoch emailových domén
|
||||
admin:write:ip_blocks: vykonať akcie moderácie na blokoch IP
|
||||
admin:write:reports: urob moderovacie úkony voči hláseniam
|
||||
crypto: používať end-to-end šifrovanie
|
||||
follow: uprav vzťahy svojho účtu
|
||||
|
@ -159,6 +170,7 @@ sk:
|
|||
read:accounts: prezri si informácie o účte
|
||||
read:blocks: prezri svoje bloky
|
||||
read:bookmarks: pozri svoje záložky
|
||||
read:favourites: zobraziť vaše obľúbené
|
||||
read:filters: prezri svoje filtrovanie
|
||||
read:follows: prezri si svoje sledovania
|
||||
read:lists: prezri si svoje zoznamy
|
||||
|
|
|
@ -33,7 +33,7 @@ fi:
|
|||
accounts:
|
||||
add_email_domain_block: Estä sähköpostiverkkotunnus
|
||||
approve: Hyväksy
|
||||
approved_msg: Käyttäjän %{username} liittymishakemus hyväksyttiin
|
||||
approved_msg: Käyttäjän %{username} rekisteröitymishakemus hyväksyttiin
|
||||
are_you_sure: Oletko varma?
|
||||
avatar: Profiilikuva
|
||||
by_domain: Verkkotunnus
|
||||
|
@ -364,7 +364,7 @@ fi:
|
|||
other: "<strong>%{count}</strong> odottavaa käyttäjää"
|
||||
resolved_reports: ratkaistut raportit
|
||||
software: Ohjelmisto
|
||||
sources: Rekisteröitymisen lähteet
|
||||
sources: Rekisteröitymislähteet
|
||||
space: Tilankäyttö
|
||||
title: Hallintapaneeli
|
||||
top_languages: Aktiivisimmat kielet
|
||||
|
@ -440,7 +440,7 @@ fi:
|
|||
title: Estä uusi sähköpostiverkkotunnus
|
||||
no_email_domain_block_selected: Sähköpostin verkkotunnuksia ei muutettu, koska yhtään ei ollut valittuna
|
||||
not_permitted: Ei sallittu
|
||||
resolved_dns_records_hint_html: Verkkotunnuksen nimi määräytyy seuraaviin MX-verkkotunnuksiin, jotka ovat viime kädessä vastuussa sähköpostin vastaanottamisesta. MX-verkkotunnuksen estäminen estää kirjautumisen mistä tahansa sähköpostiosoitteesta, joka käyttää samaa MX-verkkotunnusta, vaikka näkyvä verkkotunnuksen nimi olisikin erilainen. <strong>Varo estämästä suuria sähköpostin palveluntarjoajia.</strong>
|
||||
resolved_dns_records_hint_html: Verkkotunnuksen nimi määräytyy seuraaviin MX-verkkotunnuksiin, jotka ovat viime kädessä vastuussa sähköpostin vastaanottamisesta. MX-verkkotunnuksen estäminen estää rekisteröitymisen mistä tahansa sähköpostiosoitteesta, joka käyttää samaa MX-verkkotunnusta, vaikka näkyvä verkkotunnuksen nimi olisikin erilainen. <strong>Varo estämästä suuria sähköpostin palveluntarjoajia.</strong>
|
||||
resolved_through_html: Ratkaistu %{domain} kautta
|
||||
title: Estetyt sähköpostiverkkotunnukset
|
||||
export_domain_allows:
|
||||
|
@ -1405,7 +1405,7 @@ fi:
|
|||
migrations:
|
||||
acct: Muuttanut tunnukselle
|
||||
cancel: Peruuta uudelleenohjaus
|
||||
cancel_explanation: Uudelleenohjauksen peruuttaminen aktivoi uudelleen nykyisen tilisi, mutta ei palauta seuraajia, jotka on siirretty kyseiselle tilille.
|
||||
cancel_explanation: Uudelleenohjauksen peruuttaminen aktivoi nykyisen tilisi uudelleen mutta ei palauta seuraajia, jotka on siirretty kyseiselle tilille.
|
||||
cancelled_msg: Uudelleenohjaus peruttu onnistuneesti.
|
||||
errors:
|
||||
already_moved: on sama tili, jonka olet jo siirtänyt
|
||||
|
|
|
@ -570,7 +570,7 @@ nn:
|
|||
enabled: Skrudd på
|
||||
inbox_url: Overførings-URL
|
||||
pending: Avventer overgangens godkjenning
|
||||
save_and_enable: Lagr og slå på
|
||||
save_and_enable: Lagre og slå på
|
||||
setup: Sett opp en overgangsforbindelse
|
||||
signatures_not_enabled: Overgangar fungerer ikkje så lenge sikker- eller kvitlistingsmodus er aktivert
|
||||
status: Status
|
||||
|
@ -1280,7 +1280,7 @@ nn:
|
|||
deselect: Vel ingen
|
||||
none: Ingen
|
||||
order_by: Sorter etter
|
||||
save_changes: Lagr endringar
|
||||
save_changes: Lagre endringar
|
||||
select_all_matching_items:
|
||||
one: Vel %{count} element som passar til søket ditt.
|
||||
other: Vel %{count} element som passar til søket ditt.
|
||||
|
|
|
@ -59,14 +59,14 @@ fi:
|
|||
setting_display_media_default: Piilota arkaluonteiseksi merkitty media
|
||||
setting_display_media_hide_all: Piilota media aina
|
||||
setting_display_media_show_all: Näytä media aina
|
||||
setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin, mutta sumentavat yksityiskohdat
|
||||
setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin mutta sumentavat yksityiskohdat
|
||||
setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse syötteen automaattisen vierityksen sijaan
|
||||
username: Voit käyttää kirjaimia, numeroita ja alaviivoja
|
||||
whole_word: Kun avainsana tai -fraasi on kokonaan aakkosnumeerinen, se on voimassa vain, jos se vastaa koko sanaa
|
||||
domain_allow:
|
||||
domain: Tämä verkkotunnus voi noutaa tietoja tältä palvelimelta ja sieltä saapuvat tiedot käsitellään ja tallennetaan
|
||||
email_domain_block:
|
||||
domain: Tämä voi olla se verkkotunnus, joka näkyy sähköpostiosoitteessa tai MX tietueessa jota se käyttää. Ne tarkistetaan rekisteröitymisen yhteydessä.
|
||||
domain: Tämä voi olla verkkotunnus, joka näkyy sähköpostiosoitteessa tai sen käyttämässä MX-tietueessa. Ne tarkistetaan rekisteröitymisen yhteydessä.
|
||||
with_dns_records: Annetun verkkotunnuksen DNS-tietueet yritetään ratkaista ja tulokset myös estetään
|
||||
featured_tag:
|
||||
name: 'Tässä muutamia hiljattain käyttämiäsi aihetunnisteita:'
|
||||
|
@ -112,7 +112,7 @@ fi:
|
|||
ip: Kirjoita IPv4- tai IPv6-osoite. Voit estää kokonaisia alueita käyttämällä CIDR-syntaksia. Varo, että et lukitse itseäsi ulos!
|
||||
severities:
|
||||
no_access: Estä pääsy kaikkiin resursseihin
|
||||
sign_up_block: Uudet kirjautumiset eivät ole mahdollisia
|
||||
sign_up_block: Uudet rekisteröitymiset eivät ole mahdollisia
|
||||
sign_up_requires_approval: Uudet rekisteröitymiset edellyttävät hyväksyntääsi
|
||||
severity: Valitse, mitä tapahtuu tämän IP-osoitteen pyynnöille
|
||||
rule:
|
||||
|
|
|
@ -30,7 +30,7 @@ zh-TW:
|
|||
suspend: 禁止所有對該帳號任何互動,並且刪除其內容。三十天內可以撤銷此動作。關閉所有對此帳號之檢舉報告。
|
||||
warning_preset_id: 選用。您仍可在預設的結尾新增自訂文字
|
||||
announcement:
|
||||
all_day: 核取後,只會顯示出時間範圍中的日期部分
|
||||
all_day: 當選取時,僅顯示出時間範圍中的日期部分
|
||||
ends_at: 可選的,公告會於該時間點自動取消發布
|
||||
scheduled_at: 空白則立即發布公告
|
||||
starts_at: 可選的,讓公告在特定時間範圍內顯示
|
||||
|
@ -60,7 +60,7 @@ zh-TW:
|
|||
setting_display_media_hide_all: 總是隱藏所有媒體
|
||||
setting_display_media_show_all: 總是顯示標為敏感內容的媒體
|
||||
setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊
|
||||
setting_use_pending_items: 關閉自動捲動更新,時間軸只會於點擊後更新
|
||||
setting_use_pending_items: 關閉自動捲動更新,時間軸僅於點擊後更新
|
||||
username: 您可以使用字幕、數字與底線
|
||||
whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用
|
||||
domain_allow:
|
||||
|
|
|
@ -578,7 +578,7 @@ zh-TW:
|
|||
mark_as_sensitive_description_html: 被檢舉的嘟文中的媒體將會被標記為敏感內容,並將會記錄一次警告,以協助您升級同一帳號未來的違規行為。
|
||||
other_description_html: 檢視更多控制帳號行為以及自訂檢舉帳號通知之選項。
|
||||
resolve_description_html: 被檢舉的帳號將不被採取任何行動,不會加以刪除線標記,並且此份報告將被關閉。
|
||||
silence_description_html: 此帳號僅會對已跟隨帳號之使用者或手動查詢可見,將大幅度限制觸及範圍。此設定可隨時被還原。關閉所有對此帳號之檢舉報告。
|
||||
silence_description_html: 此帳號僅對已跟隨帳號之使用者或手動查詢可見,將大幅度限制觸及範圍。此設定可隨時被還原。關閉所有對此帳號之檢舉報告。
|
||||
suspend_description_html: 此帳號及其所有內容將不可被存取並且最終被移除,並且無法與之進行互動。三十天內可以撤銷此動作。關閉所有對此帳號之檢舉報告。
|
||||
actions_description_html: 決定應對此報告採取何種行動。若您對檢舉之帳號採取懲罰措施,則將對他們發送 e-mail 通知,如非選擇了 <strong>垃圾郵件</strong> 類別。
|
||||
actions_description_remote_html: 決定將對此檢舉報告採取何種動作。這將僅作用於<strong>您的伺服器</strong>與此遠端帳號及其內容之通訊行為。
|
||||
|
|
4
db/migrate/.rubocop.yml
Normal file
4
db/migrate/.rubocop.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
inherit_from: ../../.rubocop.yml
|
||||
|
||||
Naming/VariableNumber:
|
||||
CheckSymbols: false
|
|
@ -19,7 +19,6 @@ class AddSilencedAtSuspendedAtToAccounts < ActiveRecord::Migration[5.2]
|
|||
# Record suspend date of blocks and silences for users whose limitations match
|
||||
# a domain block
|
||||
DomainBlock.where(severity: [:silence, :suspend]).find_each do |block|
|
||||
scope = block.accounts
|
||||
if block.suspend?
|
||||
block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at)
|
||||
else
|
||||
|
|
|
@ -18,7 +18,6 @@ class RemoveSuspendedSilencedAccountFields < ActiveRecord::Migration[5.2]
|
|||
# Record suspend date of blocks and silences for users whose limitations match
|
||||
# a domain block
|
||||
DomainBlock.where(severity: [:silence, :suspend]).find_each do |block|
|
||||
scope = block.accounts
|
||||
if block.suspend?
|
||||
block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at)
|
||||
else
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
"fuzzysort": "^2.0.4",
|
||||
"glob": "^10.2.6",
|
||||
"history": "^4.10.1",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"http-link-header": "^1.1.1",
|
||||
"immutable": "^4.3.0",
|
||||
"imports-loader": "^1.2.0",
|
||||
|
@ -112,8 +113,8 @@
|
|||
"react-overlays": "^5.2.1",
|
||||
"react-redux": "^8.0.4",
|
||||
"react-redux-loading-bar": "^5.0.4",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-router-scroll-4": "^1.0.0-beta.1",
|
||||
"react-select": "^5.7.3",
|
||||
"react-sparklines": "^1.7.0",
|
||||
|
@ -159,6 +160,7 @@
|
|||
"@types/emoji-mart": "^3.0.9",
|
||||
"@types/escape-html": "^1.0.2",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/http-link-header": "^1.0.3",
|
||||
"@types/intl": "^1.2.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
|
@ -175,6 +177,7 @@
|
|||
"@types/react-immutable-proptypes": "^2.1.0",
|
||||
"@types/react-motion": "^0.0.35",
|
||||
"@types/react-overlays": "^3.1.0",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-select": "^5.0.1",
|
||||
"@types/react-sparklines": "^1.7.2",
|
||||
|
|
|
@ -18,10 +18,14 @@ RSpec.describe Admin::Disputes::AppealsController do
|
|||
describe 'GET #index' do
|
||||
let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
|
||||
|
||||
it 'lists appeals' do
|
||||
before { appeal }
|
||||
|
||||
it 'returns a page that lists details of appeals' do
|
||||
get :index
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.body).to include("<span class=\"username\">#{strike.account.username}</span>")
|
||||
expect(response.body).to include("<span class=\"target\">#{appeal.account.username}</span>")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ describe AccountControllerConcern do
|
|||
end
|
||||
|
||||
it 'sets link headers' do
|
||||
account = Fabricate(:account, username: 'username')
|
||||
Fabricate(:account, username: 'username')
|
||||
get 'success', params: { account_username: 'username' }
|
||||
expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/jrd+json", <https://cb6e6126.ngrok.io/users/username>; rel="alternate"; type="application/activity+json"'
|
||||
end
|
||||
|
|
21
spec/helpers/admin/disputes_helper_spec.rb
Normal file
21
spec/helpers/admin/disputes_helper_spec.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::DisputesHelper do
|
||||
describe 'strike_action_label' do
|
||||
it 'returns html describing the appeal' do
|
||||
adam = Account.new(username: 'Adam')
|
||||
becky = Account.new(username: 'Becky')
|
||||
strike = AccountWarning.new(account: adam, action: :suspend)
|
||||
appeal = Appeal.new(strike: strike, account: becky)
|
||||
|
||||
expected = <<~OUTPUT.strip
|
||||
<span class="username">Adam</span> suspended <span class="target">Becky</span>'s account
|
||||
OUTPUT
|
||||
result = helper.strike_action_label(appeal)
|
||||
|
||||
expect(result).to eq(expected)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -179,14 +179,14 @@ describe JsonLdHelper do
|
|||
it 'deems a safe compacting as such' do
|
||||
json['object'].delete('convo')
|
||||
compacted = compact(json)
|
||||
deemed_compatible = patch_for_forwarding!(json, compacted)
|
||||
patch_for_forwarding!(json, compacted)
|
||||
expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public']
|
||||
expect(safe_for_forwarding?(json, compacted)).to be true
|
||||
end
|
||||
|
||||
it 'deems an unsafe compacting as such' do
|
||||
compacted = compact(json)
|
||||
deemed_compatible = patch_for_forwarding!(json, compacted)
|
||||
patch_for_forwarding!(json, compacted)
|
||||
expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public']
|
||||
expect(safe_for_forwarding?(json, compacted)).to be false
|
||||
end
|
||||
|
|
|
@ -34,6 +34,40 @@ RSpec.describe ActivityPub::LinkedDataSignature do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when local account record is missing a public key' do
|
||||
let(:raw_signature) do
|
||||
{
|
||||
'creator' => 'http://example.com/alice',
|
||||
'created' => '2017-09-23T20:21:34Z',
|
||||
}
|
||||
end
|
||||
|
||||
let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) }
|
||||
|
||||
let(:service_stub) { instance_double(ActivityPub::FetchRemoteKeyService) }
|
||||
|
||||
before do
|
||||
# Ensure signature is computed with the old key
|
||||
signature
|
||||
|
||||
# Unset key
|
||||
old_key = sender.public_key
|
||||
sender.update!(private_key: '', public_key: '')
|
||||
|
||||
allow(ActivityPub::FetchRemoteKeyService).to receive(:new).and_return(service_stub)
|
||||
|
||||
allow(service_stub).to receive(:call).with('http://example.com/alice', id: false) do
|
||||
sender.update!(public_key: old_key)
|
||||
sender
|
||||
end
|
||||
end
|
||||
|
||||
it 'fetches key and returns creator' do
|
||||
expect(subject.verify_actor!).to eq sender
|
||||
expect(service_stub).to have_received(:call).with('http://example.com/alice', id: false).once
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signature is missing' do
|
||||
let(:signature) { nil }
|
||||
|
||||
|
|
|
@ -1356,4 +1356,254 @@ describe Mastodon::CLI::Accounts do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#prune' do
|
||||
let!(:local_account) { Fabricate(:account) }
|
||||
let!(:bot_account) { Fabricate(:account, bot: true, domain: 'example.com') }
|
||||
let!(:group_account) { Fabricate(:account, actor_type: 'Group', domain: 'example.com') }
|
||||
let!(:mentioned_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let!(:prunable_accounts) do
|
||||
Fabricate.times(3, :account, domain: 'example.com', bot: false, suspended_at: nil, silenced_at: nil)
|
||||
end
|
||||
|
||||
before do
|
||||
Fabricate(:mention, account: mentioned_account, status: Fabricate(:status, account: Fabricate(:account)))
|
||||
stub_parallelize_with_progress!
|
||||
end
|
||||
|
||||
it 'prunes all remote accounts with no interactions with local users' do
|
||||
cli.prune
|
||||
|
||||
prunable_account_ids = prunable_accounts.pluck(:id)
|
||||
|
||||
expect(Account.where(id: prunable_account_ids).count).to eq(0)
|
||||
end
|
||||
|
||||
it 'displays a successful message' do
|
||||
expect { cli.prune }.to output(
|
||||
a_string_including("OK, pruned #{prunable_accounts.size} accounts")
|
||||
).to_stdout
|
||||
end
|
||||
|
||||
it 'does not prune local accounts' do
|
||||
cli.prune
|
||||
|
||||
expect(Account.exists?(id: local_account.id)).to be(true)
|
||||
end
|
||||
|
||||
it 'does not prune bot accounts' do
|
||||
cli.prune
|
||||
|
||||
expect(Account.exists?(id: bot_account.id)).to be(true)
|
||||
end
|
||||
|
||||
it 'does not prune group accounts' do
|
||||
cli.prune
|
||||
|
||||
expect(Account.exists?(id: group_account.id)).to be(true)
|
||||
end
|
||||
|
||||
it 'does not prune accounts that have been mentioned' do
|
||||
cli.prune
|
||||
|
||||
expect(Account.exists?(id: mentioned_account.id)).to be true
|
||||
end
|
||||
|
||||
context 'with --dry-run option' do
|
||||
before do
|
||||
cli.options = { dry_run: true }
|
||||
end
|
||||
|
||||
it 'does not prune any account' do
|
||||
cli.prune
|
||||
|
||||
prunable_account_ids = prunable_accounts.pluck(:id)
|
||||
|
||||
expect(Account.where(id: prunable_account_ids).count).to eq(prunable_accounts.size)
|
||||
end
|
||||
|
||||
it 'displays a successful message with (DRY RUN)' do
|
||||
expect { cli.prune }.to output(
|
||||
a_string_including("OK, pruned #{prunable_accounts.size} accounts (DRY RUN)")
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#migrate' do
|
||||
let!(:source_account) { Fabricate(:account) }
|
||||
let!(:target_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:arguments) { [source_account.username] }
|
||||
let(:resolve_account_service) { instance_double(ResolveAccountService, call: nil) }
|
||||
let(:move_service) { instance_double(MoveService, call: nil) }
|
||||
|
||||
before do
|
||||
allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service)
|
||||
allow(MoveService).to receive(:new).and_return(move_service)
|
||||
end
|
||||
|
||||
shared_examples 'a successful migration' do
|
||||
it 'calls the MoveService for the last migration' do
|
||||
cli.invoke(:migrate, arguments, options)
|
||||
|
||||
last_migration = source_account.migrations.last
|
||||
|
||||
expect(move_service).to have_received(:call).with(last_migration).once
|
||||
end
|
||||
|
||||
it 'displays a successful message' do
|
||||
expect { cli.invoke(:migrate, arguments, options) }.to output(
|
||||
a_string_including("OK, migrated #{source_account.acct} to #{target_account.acct}")
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'when both --replay and --target options are given' do
|
||||
let(:options) { { replay: true, target: "#{target_account.username}@example.com" } }
|
||||
|
||||
it 'exits with an error message indicating that using both options is not possible' do
|
||||
expect { cli.invoke(:migrate, arguments, options) }.to output(
|
||||
a_string_including('Use --replay or --target, not both')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no option is given' do
|
||||
it 'exits with an error message indicating that at least one option must be used' do
|
||||
expect { cli.invoke(:migrate, arguments, {}) }.to output(
|
||||
a_string_including('Use either --replay or --target')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given username is not found' do
|
||||
let(:arguments) { ['non_existent_username'] }
|
||||
|
||||
it 'exits with an error message indicating that there is no such account' do
|
||||
expect { cli.invoke(:migrate, arguments, replay: true) }.to output(
|
||||
a_string_including("No such account: #{arguments.first}")
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --replay option' do
|
||||
let(:options) { { replay: true } }
|
||||
|
||||
context 'when the specified account has no previous migrations' do
|
||||
it 'exits with an error message indicating that the given account has no previous migrations' do
|
||||
expect { cli.invoke(:migrate, arguments, options) }.to output(
|
||||
a_string_including('The specified account has not performed any migration')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the specified account has a previous migration' do
|
||||
before do
|
||||
allow(resolve_account_service).to receive(:call).with(source_account.acct, any_args).and_return(source_account)
|
||||
allow(resolve_account_service).to receive(:call).with(target_account.acct, any_args).and_return(target_account)
|
||||
target_account.aliases.create!(acct: source_account.acct)
|
||||
source_account.migrations.create!(acct: target_account.acct)
|
||||
source_account.update!(moved_to_account: target_account)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful migration'
|
||||
|
||||
context 'when the specified account is redirecting to a different target account' do
|
||||
before do
|
||||
source_account.update!(moved_to_account: nil)
|
||||
end
|
||||
|
||||
it 'exits with an error message' do
|
||||
expect { cli.invoke(:migrate, arguments, options) }.to output(
|
||||
a_string_including('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --force option' do
|
||||
let(:options) { { replay: true, force: true } }
|
||||
|
||||
it_behaves_like 'a successful migration'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --target option' do
|
||||
let(:options) { { target: target_account.acct } }
|
||||
|
||||
before do
|
||||
allow(resolve_account_service).to receive(:call).with(source_account.acct, any_args).and_return(source_account)
|
||||
allow(resolve_account_service).to receive(:call).with(target_account.acct, any_args).and_return(target_account)
|
||||
end
|
||||
|
||||
context 'when the specified target account is not found' do
|
||||
before do
|
||||
allow(resolve_account_service).to receive(:call).with(target_account.acct).and_return(nil)
|
||||
end
|
||||
|
||||
it 'exits with an error message indicating that there is no such account' do
|
||||
expect { cli.invoke(:migrate, arguments, options) }.to output(
|
||||
a_string_including("The specified target account could not be found: #{options[:target]}")
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the specified target account exists' do
|
||||
before do
|
||||
target_account.aliases.create!(acct: source_account.acct)
|
||||
end
|
||||
|
||||
it 'creates a migration for the specified account with the target account' do
|
||||
cli.invoke(:migrate, arguments, options)
|
||||
|
||||
last_migration = source_account.migrations.last
|
||||
|
||||
expect(last_migration.acct).to eq(target_account.acct)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful migration'
|
||||
end
|
||||
|
||||
context 'when the migration record is invalid' do
|
||||
it 'exits with an error indicating that the validation failed' do
|
||||
expect { cli.invoke(:migrate, arguments, options) }.to output(
|
||||
a_string_including('Error: Validation failed')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the specified account is redirecting to a different target account' do
|
||||
before do
|
||||
allow(Account).to receive(:find_local).with(source_account.username).and_return(source_account)
|
||||
allow(source_account).to receive(:moved_to_account_id).and_return(-1)
|
||||
end
|
||||
|
||||
it 'exits with an error message' do
|
||||
expect { cli.invoke(:migrate, arguments, options) }.to output(
|
||||
a_string_including('The specified account is redirecting to a different target account. Use --force if you want to change the migration target')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --target and --force options' do
|
||||
let(:options) { { target: target_account.acct, force: true } }
|
||||
|
||||
before do
|
||||
target_account.aliases.create!(acct: source_account.acct)
|
||||
allow(Account).to receive(:find_local).with(source_account.username).and_return(source_account)
|
||||
allow(source_account).to receive(:moved_to_account_id).and_return(-1)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful migration'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -356,7 +356,7 @@ RSpec.describe Account do
|
|||
end
|
||||
|
||||
it 'does not return suspended users' do
|
||||
match = Fabricate(
|
||||
Fabricate(
|
||||
:account,
|
||||
display_name: 'Display Name',
|
||||
username: 'username',
|
||||
|
@ -483,7 +483,7 @@ RSpec.describe Account do
|
|||
end
|
||||
|
||||
it 'does not return non-followed accounts' do
|
||||
match = Fabricate(
|
||||
Fabricate(
|
||||
:account,
|
||||
display_name: 'A & l & i & c & e',
|
||||
username: 'username',
|
||||
|
@ -495,7 +495,7 @@ RSpec.describe Account do
|
|||
end
|
||||
|
||||
it 'does not return suspended users' do
|
||||
match = Fabricate(
|
||||
Fabricate(
|
||||
:account,
|
||||
display_name: 'Display Name',
|
||||
username: 'username',
|
||||
|
@ -535,7 +535,7 @@ RSpec.describe Account do
|
|||
end
|
||||
|
||||
it 'does not return suspended users' do
|
||||
match = Fabricate(
|
||||
Fabricate(
|
||||
:account,
|
||||
display_name: 'Display Name',
|
||||
username: 'username',
|
||||
|
@ -719,10 +719,10 @@ RSpec.describe Account do
|
|||
|
||||
context 'when is local' do
|
||||
it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
|
||||
account_1 = Fabricate(:account, username: 'the_doctor')
|
||||
account_2 = Fabricate.build(:account, username: 'the_Doctor')
|
||||
account_2.valid?
|
||||
expect(account_2).to model_have_error_on_field(:username)
|
||||
_account = Fabricate(:account, username: 'the_doctor')
|
||||
non_unique_account = Fabricate.build(:account, username: 'the_Doctor')
|
||||
non_unique_account.valid?
|
||||
expect(non_unique_account).to model_have_error_on_field(:username)
|
||||
end
|
||||
|
||||
it 'is invalid if the username is reserved' do
|
||||
|
@ -743,9 +743,9 @@ RSpec.describe Account do
|
|||
end
|
||||
|
||||
it 'is valid if we are creating a possibly-conflicting instance actor account' do
|
||||
account_1 = Fabricate(:account, username: 'examplecom')
|
||||
account_2 = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
|
||||
expect(account_2.valid?).to be true
|
||||
_account = Fabricate(:account, username: 'examplecom')
|
||||
instance_account = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
|
||||
expect(instance_account.valid?).to be true
|
||||
end
|
||||
|
||||
it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
|
||||
|
@ -877,17 +877,17 @@ RSpec.describe Account do
|
|||
|
||||
describe 'remote' do
|
||||
it 'returns an array of accounts who have a domain' do
|
||||
account_1 = Fabricate(:account, domain: nil)
|
||||
account_2 = Fabricate(:account, domain: 'example.com')
|
||||
expect(described_class.remote).to contain_exactly(account_2)
|
||||
_account = Fabricate(:account, domain: nil)
|
||||
account_with_domain = Fabricate(:account, domain: 'example.com')
|
||||
expect(described_class.remote).to contain_exactly(account_with_domain)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'local' do
|
||||
it 'returns an array of accounts who do not have a domain' do
|
||||
account_1 = Fabricate(:account, domain: nil)
|
||||
account_2 = Fabricate(:account, domain: 'example.com')
|
||||
expect(described_class.where('id > 0').local).to contain_exactly(account_1)
|
||||
local_account = Fabricate(:account, domain: nil)
|
||||
_account_with_domain = Fabricate(:account, domain: 'example.com')
|
||||
expect(described_class.where('id > 0').local).to contain_exactly(local_account)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -911,17 +911,17 @@ RSpec.describe Account do
|
|||
|
||||
describe 'silenced' do
|
||||
it 'returns an array of accounts who are silenced' do
|
||||
account_1 = Fabricate(:account, silenced: true)
|
||||
account_2 = Fabricate(:account, silenced: false)
|
||||
expect(described_class.silenced).to contain_exactly(account_1)
|
||||
silenced_account = Fabricate(:account, silenced: true)
|
||||
_account = Fabricate(:account, silenced: false)
|
||||
expect(described_class.silenced).to contain_exactly(silenced_account)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'suspended' do
|
||||
it 'returns an array of accounts who are suspended' do
|
||||
account_1 = Fabricate(:account, suspended: true)
|
||||
account_2 = Fabricate(:account, suspended: false)
|
||||
expect(described_class.suspended).to contain_exactly(account_1)
|
||||
suspended_account = Fabricate(:account, suspended: true)
|
||||
_account = Fabricate(:account, suspended: false)
|
||||
expect(described_class.suspended).to contain_exactly(suspended_account)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ RSpec.describe DomainBlock do
|
|||
end
|
||||
|
||||
it 'is invalid if the same normalized domain already exists' do
|
||||
domain_block_1 = Fabricate(:domain_block, domain: 'にゃん')
|
||||
domain_block_2 = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b')
|
||||
domain_block_2.valid?
|
||||
expect(domain_block_2).to model_have_error_on_field(:domain)
|
||||
_domain_block = Fabricate(:domain_block, domain: 'にゃん')
|
||||
domain_block_with_normalized_value = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b')
|
||||
domain_block_with_normalized_value.valid?
|
||||
expect(domain_block_with_normalized_value).to model_have_error_on_field(:domain)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -341,7 +341,7 @@ RSpec.describe Status do
|
|||
|
||||
describe '#replies_count' do
|
||||
it 'is the number of replies' do
|
||||
reply = Fabricate(:status, account: bob, thread: subject)
|
||||
Fabricate(:status, account: bob, thread: subject)
|
||||
expect(subject.replies_count).to eq 1
|
||||
end
|
||||
|
||||
|
|
|
@ -55,17 +55,17 @@ RSpec.describe User do
|
|||
describe 'scopes' do
|
||||
describe 'recent' do
|
||||
it 'returns an array of recent users ordered by id' do
|
||||
user_1 = Fabricate(:user)
|
||||
user_2 = Fabricate(:user)
|
||||
expect(described_class.recent).to eq [user_2, user_1]
|
||||
first_user = Fabricate(:user)
|
||||
second_user = Fabricate(:user)
|
||||
expect(described_class.recent).to eq [second_user, first_user]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'confirmed' do
|
||||
it 'returns an array of users who are confirmed' do
|
||||
user_1 = Fabricate(:user, confirmed_at: nil)
|
||||
user_2 = Fabricate(:user, confirmed_at: Time.zone.now)
|
||||
expect(described_class.confirmed).to contain_exactly(user_2)
|
||||
Fabricate(:user, confirmed_at: nil)
|
||||
confirmed_user = Fabricate(:user, confirmed_at: Time.zone.now)
|
||||
expect(described_class.confirmed).to contain_exactly(confirmed_user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ RSpec.describe WebauthnCredential do
|
|||
end
|
||||
|
||||
it 'is invalid if already exist a webauthn credential with the same external id' do
|
||||
existing_webauthn_credential = Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
|
||||
Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
|
||||
new_webauthn_credential = Fabricate.build(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
|
||||
|
||||
new_webauthn_credential.valid?
|
||||
|
@ -47,7 +47,7 @@ RSpec.describe WebauthnCredential do
|
|||
|
||||
it 'is invalid if user already registered a webauthn credential with the same nickname' do
|
||||
user = Fabricate(:user)
|
||||
existing_webauthn_credential = Fabricate(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
|
||||
Fabricate(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
|
||||
new_webauthn_credential = Fabricate.build(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
|
||||
|
||||
new_webauthn_credential.valid?
|
||||
|
|
|
@ -9,7 +9,8 @@ describe 'Credentials' do
|
|||
end
|
||||
|
||||
context 'with an oauth token' do
|
||||
let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) }
|
||||
let(:application) { Fabricate(:application, scopes: 'read') }
|
||||
let(:token) { Fabricate(:accessible_access_token, application: application) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
it 'returns the app information correctly', :aggregate_failures do
|
||||
|
@ -21,7 +22,35 @@ describe 'Credentials' do
|
|||
a_hash_including(
|
||||
name: token.application.name,
|
||||
website: token.application.website,
|
||||
vapid_key: Rails.configuration.x.vapid_public_key
|
||||
vapid_key: Rails.configuration.x.vapid_public_key,
|
||||
scopes: token.application.scopes.map(&:to_s),
|
||||
client_id: token.application.uid
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non-read scoped oauth token' do
|
||||
let(:application) { Fabricate(:application, scopes: 'admin:write') }
|
||||
let(:token) { Fabricate(:accessible_access_token, application: application) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns the app information correctly' do
|
||||
subject
|
||||
|
||||
expect(body_as_json).to match(
|
||||
a_hash_including(
|
||||
name: token.application.name,
|
||||
website: token.application.website,
|
||||
vapid_key: Rails.configuration.x.vapid_public_key,
|
||||
scopes: token.application.scopes.map(&:to_s),
|
||||
client_id: token.application.uid
|
||||
)
|
||||
)
|
||||
end
|
||||
|
@ -36,5 +65,49 @@ describe 'Credentials' do
|
|||
expect(response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a revoked oauth token' do
|
||||
let(:application) { Fabricate(:application, scopes: 'read') }
|
||||
let(:token) { Fabricate(:accessible_access_token, application: application, revoked_at: DateTime.now.utc) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
it 'returns http authorization error' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(401)
|
||||
end
|
||||
|
||||
it 'returns the error in the json response' do
|
||||
subject
|
||||
|
||||
expect(body_as_json).to match(
|
||||
a_hash_including(
|
||||
error: 'The access token was revoked'
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an invalid oauth token' do
|
||||
let(:application) { Fabricate(:application, scopes: 'read') }
|
||||
let(:token) { Fabricate(:accessible_access_token, application: application) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}-invalid" } }
|
||||
|
||||
it 'returns http authorization error' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(401)
|
||||
end
|
||||
|
||||
it 'returns the error in the json response' do
|
||||
subject
|
||||
|
||||
expect(body_as_json).to match(
|
||||
a_hash_including(
|
||||
error: 'The access token is invalid'
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,7 +56,7 @@ describe AccountSearchService, type: :service do
|
|||
service = instance_double(ResolveAccountService, call: nil)
|
||||
allow(ResolveAccountService).to receive(:new).and_return(service)
|
||||
|
||||
results = subject.call('newuser@remote.com', nil, limit: 10, resolve: true)
|
||||
subject.call('newuser@remote.com', nil, limit: 10, resolve: true)
|
||||
expect(service).to have_received(:call).with('newuser@remote.com')
|
||||
end
|
||||
|
||||
|
@ -64,14 +64,14 @@ describe AccountSearchService, type: :service do
|
|||
service = instance_double(ResolveAccountService, call: nil)
|
||||
allow(ResolveAccountService).to receive(:new).and_return(service)
|
||||
|
||||
results = subject.call('newuser@remote.com', nil, limit: 10, resolve: false)
|
||||
subject.call('newuser@remote.com', nil, limit: 10, resolve: false)
|
||||
expect(service).to_not have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the fuzzy match first, and does not return suspended exacts' do
|
||||
partial = Fabricate(:account, username: 'exactness')
|
||||
exact = Fabricate(:account, username: 'exact', suspended: true)
|
||||
Fabricate(:account, username: 'exact', suspended: true)
|
||||
results = subject.call('exact', nil, limit: 10)
|
||||
|
||||
expect(results.size).to eq 1
|
||||
|
@ -79,7 +79,7 @@ describe AccountSearchService, type: :service do
|
|||
end
|
||||
|
||||
it 'does not return suspended remote accounts' do
|
||||
remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
|
||||
Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
|
||||
results = subject.call('a@example.com', nil, limit: 2)
|
||||
|
||||
expect(results.size).to eq 0
|
||||
|
|
|
@ -307,7 +307,7 @@ RSpec.describe PostStatusService, type: :service do
|
|||
|
||||
it 'processes duplicate mentions correctly' do
|
||||
account = Fabricate(:account)
|
||||
mentioned_account = Fabricate(:account, username: 'alice')
|
||||
Fabricate(:account, username: 'alice')
|
||||
|
||||
expect do
|
||||
subject.call(account, text: '@alice @alice @alice hey @alice')
|
||||
|
@ -377,7 +377,7 @@ RSpec.describe PostStatusService, type: :service do
|
|||
account = Fabricate(:account)
|
||||
media = Fabricate(:media_attachment, account: Fabricate(:account))
|
||||
|
||||
status = subject.call(
|
||||
subject.call(
|
||||
account,
|
||||
text: 'test status update',
|
||||
media_ids: [media.id]
|
||||
|
@ -458,7 +458,7 @@ RSpec.describe PostStatusService, type: :service do
|
|||
|
||||
it 'hit ng words for mention' do
|
||||
account = Fabricate(:account)
|
||||
mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
|
||||
Fabricate(:account, username: 'ohagi', domain: nil)
|
||||
text = 'ng word test @ohagi'
|
||||
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||
|
||||
|
@ -467,7 +467,7 @@ RSpec.describe PostStatusService, type: :service do
|
|||
|
||||
it 'hit ng words for mention but local posts are not checked' do
|
||||
account = Fabricate(:account)
|
||||
mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
|
||||
Fabricate(:account, username: 'ohagi', domain: nil)
|
||||
text = 'ng word test @ohagi'
|
||||
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ RSpec.describe PrecomputeFeedService, type: :service do
|
|||
muted_account = Fabricate(:account)
|
||||
Fabricate(:mute, account: account, target_account: muted_account)
|
||||
reblog = Fabricate(:status, account: muted_account)
|
||||
status = Fabricate(:status, account: account, reblog: reblog)
|
||||
Fabricate(:status, account: account, reblog: reblog)
|
||||
|
||||
subject.call(account)
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ describe ResolveURLService, type: :service do
|
|||
|
||||
describe '#call' do
|
||||
it 'returns nil when there is no resource url' do
|
||||
url = 'http://example.com/missing-resource'
|
||||
known_account = Fabricate(:account, uri: url, domain: 'example.com')
|
||||
url = 'http://example.com/missing-resource'
|
||||
Fabricate(:account, uri: url, domain: 'example.com')
|
||||
service = instance_double(FetchResourceService)
|
||||
|
||||
allow(FetchResourceService).to receive(:new).and_return service
|
||||
|
|
|
@ -44,7 +44,7 @@ RSpec.configure do |config|
|
|||
config.display_try_failure_messages = true
|
||||
|
||||
# Use the GitHub Annotations formatter for CI
|
||||
if ENV['GITHUB_ACTIONS'] == 'true'
|
||||
if ENV['GITHUB_ACTIONS'] == 'true' && ENV['GITHUB_RSPEC'] == 'true'
|
||||
require 'rspec/github'
|
||||
config.add_formatter RSpec::Github::Formatter
|
||||
end
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'admin/trends/links/_preview_card.html.haml' do
|
||||
it 'correctly escapes user supplied url values' do
|
||||
form = instance_double(ActionView::Helpers::FormHelper, check_box: nil)
|
||||
trend = PreviewCardTrend.new(allowed: false)
|
||||
preview_card = Fabricate.build(
|
||||
:preview_card,
|
||||
url: 'https://host.example/path?query=<script>',
|
||||
trend: trend,
|
||||
title: 'Fun'
|
||||
)
|
||||
|
||||
render partial: 'admin/trends/links/preview_card', locals: { preview_card: preview_card, f: form }
|
||||
|
||||
expect(rendered).to include('<a href="https://host.example/path?query=<script>">Fun</a>')
|
||||
end
|
||||
end
|
|
@ -13,7 +13,7 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
|
|||
it 'has valid opengraph tags' do
|
||||
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||
status = Fabricate(:status, account: alice, text: 'Hello World')
|
||||
media = Fabricate(:media_attachment, account: alice, status: status, type: :video)
|
||||
Fabricate(:media_attachment, account: alice, status: status, type: :video)
|
||||
|
||||
assign(:status, status)
|
||||
assign(:account, alice)
|
||||
|
@ -32,7 +32,7 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
|
|||
it 'has twitter player tag' do
|
||||
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||
status = Fabricate(:status, account: alice, text: 'Hello World')
|
||||
media = Fabricate(:media_attachment, account: alice, status: status, type: :video)
|
||||
Fabricate(:media_attachment, account: alice, status: status, type: :video)
|
||||
|
||||
assign(:status, status)
|
||||
assign(:account, alice)
|
||||
|
|
80
yarn.lock
80
yarn.lock
|
@ -1096,12 +1096,12 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8"
|
||||
integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
||||
version "7.21.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
|
||||
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/runtime@^7.12.1", "@babel/runtime@^7.22.5", "@babel/runtime@^7.9.2":
|
||||
version "7.23.1"
|
||||
|
@ -1110,6 +1110,20 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.18.3":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
|
||||
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/runtime@^7.22.3":
|
||||
version "7.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb"
|
||||
integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/template@^7.22.15", "@babel/template@^7.22.5":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
||||
|
@ -2438,7 +2452,7 @@
|
|||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router@*":
|
||||
"@types/react-router@*", "@types/react-router@^5.1.20":
|
||||
version "5.1.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c"
|
||||
integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==
|
||||
|
@ -6497,7 +6511,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
|
|||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
history@^4.10.1, history@^4.7.2:
|
||||
history@^4.10.1, history@^4.9.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
||||
|
@ -6518,12 +6532,7 @@ hmac-drbg@^1.0.1:
|
|||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^2.5.0:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
||||
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
|
@ -9886,7 +9895,7 @@ prompts@^2.0.1:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -10110,7 +10119,7 @@ react-intl@^6.4.2:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
@ -10175,17 +10184,18 @@ react-redux@^8.0.4, react-redux@^8.1.1:
|
|||
react-is "^18.0.0"
|
||||
use-sync-external-store "^1.0.0"
|
||||
|
||||
react-router-dom@^4.1.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
|
||||
integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==
|
||||
react-router-dom@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6"
|
||||
integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==
|
||||
dependencies:
|
||||
history "^4.7.2"
|
||||
invariant "^2.2.4"
|
||||
"@babel/runtime" "^7.12.13"
|
||||
history "^4.9.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.1"
|
||||
react-router "^4.3.1"
|
||||
warning "^4.0.1"
|
||||
prop-types "^15.6.2"
|
||||
react-router "5.3.4"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router-scroll-4@^1.0.0-beta.1:
|
||||
version "1.0.0-beta.2"
|
||||
|
@ -10195,18 +10205,20 @@ react-router-scroll-4@^1.0.0-beta.1:
|
|||
scroll-behavior "^0.9.1"
|
||||
warning "^3.0.0"
|
||||
|
||||
react-router@^4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e"
|
||||
integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==
|
||||
react-router@5.3.4, react-router@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5"
|
||||
integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==
|
||||
dependencies:
|
||||
history "^4.7.2"
|
||||
hoist-non-react-statics "^2.5.0"
|
||||
invariant "^2.2.4"
|
||||
"@babel/runtime" "^7.12.13"
|
||||
history "^4.9.0"
|
||||
hoist-non-react-statics "^3.1.0"
|
||||
loose-envify "^1.3.1"
|
||||
path-to-regexp "^1.7.0"
|
||||
prop-types "^15.6.1"
|
||||
warning "^4.0.1"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.6.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-select@*, react-select@^5.7.3:
|
||||
version "5.7.4"
|
||||
|
@ -10457,7 +10469,7 @@ regenerator-runtime@^0.12.0:
|
|||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
||||
|
||||
regenerator-runtime@^0.13.3:
|
||||
regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.3:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue